function trim(s) return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)' end parseManager={} parseManager._VERSION={1,0,0} dialogueManager=parseManager -- for backwards purposes parseManager.OnExtendedBlock=multi:newConnection(true) -- true protects the module from crashes parseManager.OnCustomSyntax=multi:newConnection(true) -- true protects the module from crashes function string:split( inSplitPattern, outResults ) if not outResults then outResults = {} end local theStart = 1 local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) while theSplitStart do table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) ) theStart = theSplitEnd + 1 theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) end table.insert( outResults, string.sub( self, theStart ) ) return outResults end function parseManager:debug(txt) if self.stats.debugging then self._methods:debug(txt) end end function parseManager.split(s,pat) local pat=pat or "," local res = {} local start = 1 local state = 0 local c = '.' local elem = '' for i = 1, #s do c = s:sub(i, i) if state == 0 or state == 3 then -- start state or space after comma if state == 3 and c == ' ' then state = 0 -- skipped the space after the comma else state = 0 if c == '"' or c=="'" then state = 1 elem = elem .. '"' elseif c=="[" then state = 1 elem = elem .. '[' elseif c == pat then res[#res + 1] = elem elem = '' state = 3 -- skip over the next space if present else elem = elem .. c end end elseif state == 1 then -- inside quotes if c == '"' or c=="'" then --quote detection could be done here state = 0 elem = elem .. '"' elseif c=="]" then state = 0 elem = elem .. ']' elseif c == '\\' then state = 2 else elem = elem .. c end elseif state == 2 then -- after \ in string elem = elem .. c state = 1 end end res[#res + 1] = elem return res end parseManager._chunks={} parseManager._cblock={} parseManager._cblockname="" parseManager._pos=1 parseManager._labels={ -- {chunkname,pos} } parseManager.stats={ leaking=false, debugging=false, topdown=true, forseelabels=true, } parseManager._types={} parseManager.__index=parseManager parseManager._variables={__TYPE="ENV"} parseManager.defualtENV=parseManager._variables function parseManager:varExists(var) if var==nil or var=="nil" then return end if type(var)=="userdata" then return var end if tonumber(var) then return tonumber(var) end local aa,bb=var:match("(.-)%[\"(.-)\"%]") if aa and bb then return self.defualtENV[aa][bb] end if var:find('"') then return self:parseHeader(var:sub(2,-2),self.defualtENV) end if var:find("%[%]") then return {} end if var:sub(1,1)=="[" and var:sub(-1,-1)=="]" then local list=var:match("[(.+)]") if not list then self:pushError("Invalid List assignment!") end local t=list:split(",") local nlist={} local a=":)" for i=1,#t do a=self:varExists(t[i]) if a then table.insert(nlist,a) end end return nlist end if var=="true" then return true elseif var=="false" then return false end local a,b=var:match("(.-)%[(.-)%]") if a and b then if type(self.defualtENV[a])=="table" then if b=="-1" then return self.defualtENV[a][#self.defualtENV[a]] elseif b=="#" then return self.defualtENV[a][math.random(1,#self.defualtENV[a])] else return self.defualtENV[a][tonumber(b) or self:varExists(b)] end end if type(self.defualtENV[var])=="table" then return self.defualtENV[var] end end return self.defualtENV[var] or var -- if all tests fail, just pass on the data for the function to manage end function parseManager:isList(var) local a,b=var:match("(.-)%[(.-)%]") if not a or b then return end if type(self.defualtENV[a])=="table" then if b=="-1" then return self.defualtENV[a][#self.defualtENV[a]] else return self.defualtENV[a][tonumber(b)] end end return end function parseManager:loadString(data) self:_load(bin.new(data),self) end parseManager.loadeddata={} parseManager.envs={} parseManager._methods={ getLength=function(self,list) return #(self:varExists(list) or {}) end, emptyList=function(self) return {} end, DEBUG=function(self,text) print(text) end, DIS=function(self,var) print(var) end, SEED=function(self,n) math.randomseed(tonumber(self:varExists(n) or n) or os.time()) end, delElem=function(self,l,i) table.remove(l,i) end, addElem=function(self,l,d,i) table.insert(l,(i or -1),d) return l end, RANDOM=function(self,v1,v2) if v1 then return math.random(1,v1) elseif v1 or v2 then return math.random(tonumber(v1),tonumber(v2)) else return math.random() end end, CALC=function(self,eq) return self:evaluate(eq) end, GOTOV=function(self,label) print(self:varExists(label)) self._methods.GOTO(self,self:varExists(label)) end, GOTO=function(self,label) label=label:gsub("-","") if label=="__LASTGOTO" then self:setBlock(self._labels.__LASTGOTO[1]) self.pos=self._labels[label][2] return true end --search current block for a label if self.pos==nil then error("Attempt to load a non existing block from the host script!") end for i=self.pos,#self._cblock do local line=self._cblock[i] local labeltest=line:match("::(.-)::") if labeltest==label then self._labels["__LASTGOTO"]={self._cblockname,self.pos} self.pos=i return true end end --search for saved labels if self._labels[label] then self._labels["__LASTGOTO"]={self._cblockname,self.pos} self:setBlock(self._labels[label][1]) self.pos=self._labels[label][2] return true end --search other blocks if enabled for labels if self.stats.forseelabels then for i,v in pairs(self._chunks) do local chunks=bin._lines(v[1]) for p=1,#chunks do local line=chunks[p] local labeltest=line:match("::(.-)::") if labeltest==label then self._labels["__LASTGOTO"]={self._cblockname,self.pos} self:setBlock(i) self.pos=p-1 return true end end end end if self.stats.forseelabels then if self._methods.GOTOV(self,label) then return end end self:pushError("Attempt to goto a non existing label! You can only access labels in the current scope! Or labels that the code has seen thus far! "..label.." does not exist as a label!") end, QUIT=function() os.exit() end, EXIT=function(self) self.pos=math.huge end, TYPE=function(self,val) return type(val) end, SAVE=function(self,filename) if trim(filename)=="" then filename="saveData.sav" end local t=bin.new() t:addBlock(self.defualtENV) t:addBlock(self._cblockname) t:addBlock(self.pos) t:addBlock(self._labels) t:tofile(filename) end, UNSAVE=function(self,filename) if trim(filename)=="" then filename="saveData.sav" end self.defualtENV={} os.remove(filename) end, RESTORE=function(self) if not(self.loadeddata.load) then self:pushError("A call to RESTORE without calling LOAD") end self.defualtENV=self.loadeddata:getBlock("t") self:setBlock(self.loadeddata:getBlock("s")) self.pos=self.loadeddata:getBlock("n") self._labels=self.loadeddata:getBlock("t") end, LOAD=function(self,filename) print(filename) if not filename then filename="saveData.sav" end if io.fileExists(filename) then self.loadeddata=bin.load(filename) return 1 end return 0 end, JUMP=function(self,to) self:setBlock(to) end, SKIP=function(self,n) self.pos=self.pos+tonumber(n) end, PRINT=function(self,text) print(text) end, TRIGGER=function(self,to) self:setBlock(to) end, COMPARE=function(self,t,v1,v2,trueto,falseto) -- if a blockname is __STAY then it will continue on if t=="=" or t=="==" then if v1==v2 then self:setBlock(trueto) else self:setBlock(falseto) end elseif t==">=" then if v1>=v2 then self:setBlock(trueto) else self:setBlock(falseto) end elseif t=="<=" then if v1<=v2 then self:setBlock(trueto) else self:setBlock(falseto) end elseif t==">" then if v1>v2 then self:setBlock(trueto) else self:setBlock(falseto) end elseif t=="<" then if v1"..a end) local choicetest=line:find("<$") or line:find("^<") local lasttest=line:match("^\"(.+)\"$") local labeltest=line:match("::(.-)::") local var,list=line:match("([%w_]-)=%[(.+)%]") local assignA,assignB=line:match("(.-)=(.+)") local cond,f1,f2=line:match("^if%s*(.-)%s*then%s*([%w-%(%)]-)%s*|%s*([%w-%(%)]*)") if choicetest then local c=self._chunks[self._cblockname][1] local test=bin.new(c:match("\"<(.-)>")) test:fullTrim(true) local header=line:match("\"(.-)\"<") local stuff=test:lines() local cho,met={},{} for i=1,#stuff do local a1,a2=stuff[i]:match("\"(.-)\" (.+)") a1=tostring(self:parseHeader(a1,env)) table.insert(cho,a1) table.insert(met,a2) end return { Type="choice", text=tostring(self:parseHeader(header,env)), choices=cho, methods=met, blocktype=self._chunks[self._cblockname][2] } elseif cond and f1 and f2 then conds={["andors"]={}} mtc="" for a,b in cond:gmatch("(.-)([and ]+[or ]+)") do b=b:gsub(" ","") mtc=mtc..".-"..b v1,c,v2=a:match("(.-)%s*([<>!~=]+)%s*(.+)") table.insert(conds,{v1,c,v2}) table.insert(conds.andors,b) end a=cond:match(mtc.."%s*(.+)") v1,c,v2=a:match("(.-)%s*([<>!~=]+)%s*(.+)") table.insert(conds,{v1,c,v2}) truths={} for i=1,#conds do conds[i][1]=conds[i][1]:gsub("\"","") conds[i][3]=conds[i][3]:gsub("\"","") if conds[i][2]=="==" then table.insert(truths,tostring((self:varExists(conds[i][1]) or conds[i][1]))==tostring((self:varExists(conds[i][3]) or conds[i][3]))) elseif conds[i][2]=="!=" or conds[i][2]=="~=" then table.insert(truths,tostring((self:varExists(conds[i][1]) or conds[i][1]))~=tostring((self:varExists(conds[i][3]) or conds[i][3]))) elseif conds[i][2]==">=" then table.insert(truths,tonumber((self:varExists(conds[i][1]) or conds[i][1]))>=tonumber((self:varExists(conds[i][3]) or conds[i][3]))) elseif conds[i][2]=="<=" then table.insert(truths,tonumber((self:varExists(conds[i][1]) or conds[i][1]))<=tonumber((self:varExists(conds[i][3]) or conds[i][3]))) elseif conds[i][2]==">" then table.insert(truths,tonumber((self:varExists(conds[i][1]) or conds[i][1]))>tonumber((self:varExists(conds[i][3]) or conds[i][3]))) elseif conds[i][2]=="<" then table.insert(truths,tonumber((self:varExists(conds[i][1]) or conds[i][1]))])(.+)") if d then if d=="<-" then self:setVariable(assignA,self.defualtENV[_env][vv]) self:p() return { Type="assignment", var=assignA, value=assignB, env=true, text=assignA.."="..assignB } elseif d=="->" then self.defualtENV[_env][assignA]=self:varExists(vv) self:p() return { Type="assignment", var=assignA, value=assignB, env=true, text=assignA.."="..assignB } end end local a1,a2=parseManager.split(assignA),parseManager.split(assignB) for i=1,#a1 do local a=self._methods.CALC(self,a2[i]) if a then a2[i]=a end local t=tonumber(a2[i]) if not t then t=a2[i] end env[a1[i]]=t end self:p() return { Type="assignment", var=assignA, value=assignB, text=assignA.."="..assignB } else local rets=self.OnCustomSyntax:Fire(self,line) for i=1,#rets do if type(rets[i][1])=="table" then return rets[i][1] else return { Type="unknown", text=line } end end self:p() return { Type="unknown", text=line } end end function parseManager:RunCode(code,entry,sel,env) -- returns an env or selectVarName local file = bin.new("ENTRY "..(entry or "START").."\n"..code) local run=parseManager:load(file) run._methods = self._methods run.defualtENV=self.defualtENV run.defualtENV=self.defualtENV for i,v in pairs(env or {}) do run.defualtENV[i]=v end local t=run:start() while true do if t.Type=="text" then print(t.text) t=run:next() elseif t.Type=="condition" then t=run:next() elseif t.Type=="assignment" then t=run:next() elseif t.Type=="label" then t=run:next() elseif t.Type=="method" then t=run:next() elseif t.Type=="choice" then t=run:next(nil,math.random(1,#t.choices),nil,t) elseif t.Type=="end" then if t.text=="leaking" then -- go directly to the block right under the current block if it exists t=run:next() else return (run.defualtENV[sel] or run.defualtENV) end elseif t.Type=="error" then error(t.text) else t=run:next() end end end parseManager.symbols={} -- {sym,code} function parseManager:registerSymbol(sym,code) self.symbols[#self.symbols+1]={sym,code} end function parseManager:populateSymbolList(o) local str="" for i=1,#self.symbols do str=self.symbols[i][1]..str end return str end function parseManager:isRegisteredSymbol(o,r,v) for i=1,#self.symbols do if self.symbols[i][1]==o then return parseManager:RunCode(self.symbols[i][2],"CODE","ret-urn",{["l"]=r,["r"]=v,["mainenv"]=self.defualtENV}) end end return false --self:pushError("Invalid Symbol "..o.."!") end function parseManager:evaluate(cmd,v) v=v or 0 local loop local count=0 local function helper(o,v,r) if type(v)=="string" then if v:find("%D") then v=self:varExists(v) end end if type(r)=="string" then if r:find("%D") then r=self:varExists(r) end end local r=tonumber(r) or 0 local gg=self:isRegisteredSymbol(o,r,v) if gg then return gg elseif o=="+" then return r+v elseif o=="-" then return r-v elseif o=="/" then return r/v elseif o=="*" then return r*v elseif o=="^" then return r^v end end for i,v in pairs(math) do cmd=cmd:gsub(i.."(%b())",function(a) a=a:sub(2,-2) if a:sub(1,1)=="-" then a="0"..a end return v(self:evaluate(a)) end) end cmd=cmd:gsub("%b()",function(a) return self:evaluate(a:sub(2,-2)) end) for l,o,r in cmd:gmatch("(.*)([%+%^%-%*/"..self:populateSymbolList().."])(.*)") do loop=true count=count+1 if l:find("[%+%^%-%*/]") then v=self:evaluate(l,v) v=helper(o,r,v) else if count==1 then v=helper(o,r,l) end end end if not loop then return self:varExists(cmd) end return v end parseManager.constructType=function(self,name,t,data,filename) if t~="construct" then return end --print(name,t,"[CODE]{"..data.."}") self:registerSymbol(name,"[CODE]{"..data.."}") end -- Let's add function Stack = {} function Stack:Create() local t = {} t._et = {} function t:push(...) if ... then local targs = {...} for _,v in ipairs(targs) do table.insert(self._et, v) end end end function t:pop(num) local num = num or 1 local entries = {} for i = 1, num do if #self._et ~= 0 then table.insert(entries, self._et[#self._et]) table.remove(self._et) else break end end return unpack(entries) end function t:getn() return #self._et end function t:list() for i,v in pairs(self._et) do print(i, v) end end return t end parseManager.funcstack=Stack:Create() parseManager:define{ __TRACEBACK=function(self) -- internal function to handle function calls local t=self.funcstack:pop() self:setBlock(t[1]) self.pos=t[2] -- We finished the function great. Lets restore the old env self._methods.setENV(self,t[3]) end } parseManager.funcType=function(link,name,t,data,filename) local test,args=t:match("(function)%(*([%w,]*)%)*") if not test then return false end local vars={} if args~="" then for k, v in ipairs(parseManager.split(args)) do table.insert(vars,v) end -- Time to collect local vars to populate we will use these below end link._chunks[name][1]=link._chunks[name][1].."\n__TRACEBACK()" local func=function(self,...) -- Here we will use the vars. First lets capture the args from the other side local args={...} -- Here we will play a matching game assigning vars to values. This cannot be done yet... -- Now we have to change the enviroment so function vars are local to the function. -- Also we need functions to be able to access the globals too -- Now we invoke the createnv method local env=self._methods.createENV(self) -- A little messy compared to how its done within the interpreted language -- Now we need a copy of the previous Env -- We then invoke getEnv method local lastEnv=self._methods.getENV(self) -- Great now we have a new enviroment to play with and the current one -- Next we need to store the current one somewhere self.funcstack:push({self._cblockname,self.pos,lastEnv}) -- We use a stack to keep track of function calls. Before I tried something else and it was a horrible mess -- Stacks make it real nice and easy to use. We store a bit of data into the stack to use later if self.funcstack:getn()>1024 then self:pushError("Stack Overflow!") end -- Throw an error if the stack reaches 1024 elements. We don't want it to go forever and odds are neither does the user -- Lets set that new env and prepare for the jump. To do this we invoke setEnv self._methods.setENV(self,env) -- Now lets play match making for i=1,#vars do self:setVariable(vars[i],args[i]) -- this method defualts to the current env end -- We are ready to make the jump with our stored data self._methods.JUMP(self,name) -- we need to be able to catch returns... This is where things get tricky. -- We need a way to run the other code while also waiting here so we can return data -- What we can do is return a reference to the enviroment and from there you can take what you want from the function -- This is a really strange way to do things, but whats wrong with different return env end link._methods[name]=func end parseManager.OnExtendedBlock(parseManager.funcType) parseManager.constructType=function(link,name,t,data,filename) local test,args=t:match("(construct)%(*([%w,]*)%)*") if not test then return false end local vars={} if args~="" then for k, v in ipairs(parseManager.split(args)) do table.insert(vars,v) end end link._chunks[name][1]=link._chunks[name][1].."\n__TRACEBACK()" local func=function(self,...) local args={...} local env=self._methods.createENV(self) local lastEnv=self._methods.getENV(self) self.funcstack:push({self._cblockname,self.pos,lastEnv}) if self.funcstack:getn()>1024 then self:pushError("Stack Overflow!") end self._methods.setENV(self,env) for i=1,#vars do self:setVariable(vars[i],args[i]) end self._methods.JUMP(self,name) return env end link._methods[name]=func end parseManager.OnExtendedBlock(parseManager.constructType)