From ec13f75d67f9061709cec4ad4d7d17883dc31b72 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 10 Jun 2017 11:25:13 -0400 Subject: [PATCH] Initial commit --- .gitattributes | 2 + AICM.lua | 37 ++ EBIM.lua | 71 +++ bytecode.lua | 44 ++ init.lua | 1158 +++++++++++++++++++++++++++++++++++++++++++++++ interpreter.lua | 10 + 6 files changed, 1322 insertions(+) create mode 100644 .gitattributes create mode 100644 AICM.lua create mode 100644 EBIM.lua create mode 100644 bytecode.lua create mode 100644 init.lua create mode 100644 interpreter.lua diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..eba1110 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto \ No newline at end of file diff --git a/AICM.lua b/AICM.lua new file mode 100644 index 0000000..23496fa --- /dev/null +++ b/AICM.lua @@ -0,0 +1,37 @@ +AICM={} +AICM.functions={ + getAICMVersion=function(self) + return "1.0.0" + end, +} +function AICM:InitSyntax(obj,name) + obj:debug("Now using the Artificial Intelligence Communication module!") + obj.OnExtendedBlock(self.blockModule) + obj.OnCustomSyntax(self.syntaxModule) + obj:define(self.functions) +end +AICM.syntaxModule=function(self,line) + pVars,mStr=line:match("p%((.-)%)(.+)") + if pVars then + local vRef,vars=pVars:match("(.-):(.+)") + if vars:find(",") then + vars={unpack(vars:split(","))} + else + vars={vars} + end + tab={self:varExists(vRef):match(mStr)} -- self:varExists allows for all internal structures to just work + for i=1,#tab do + if vars[i] then + self._variables[vars[i]]=tab[i] + end + end + self:p() -- requried to progress the script + return { + text=line, + Type="AICMModule" + } + end +end +AICM.blockModule=function(obj,name,t,chunk,filename) + -- +end diff --git a/EBIM.lua b/EBIM.lua new file mode 100644 index 0000000..08ac193 --- /dev/null +++ b/EBIM.lua @@ -0,0 +1,71 @@ +EBIM={} +EBIM.functions={ + getEBIMVersion=function(self) + return "1.0.0" + end, +} +EBIM.registry={} +function EBIM:registerEBlock(name,func) + self.registry[name]=func +end +function EBIM:InitSyntax(obj,name) + obj:debug("Now using the Extended Block Interface module!") + obj.OnExtendedBlock(self.blockModule) + obj.OnCustomSyntax(self.syntaxModule) + obj:define(self.functions) +end +EBIM.syntaxModule=function(self,line) + local cmd,args=line:match("(.-) (.+):") + if cmd then + local goal=nil + local _tab={} + for i=self.pos+1,#self._cblock do + if self._cblock[i]=="end"..cmd then + goal=i + break + else + table.insert(_tab,self._cblock[i]) + end + end + if goal==nil then + self:pushError("'end"..cmd.."' Expected to close '"..cmd.."'") + end + if EBIM.registry[cmd] then + EBIM.registry[cmd](self,args,_tab) + self.pos=goal+1 + else + self:pushError("Unknown command: "..cmd) + end + return { + Type="EBIM-Data", + text=cmd.." Block" + } + else + return + end +end +EBIM.blockModule=function(obj,name,t,chunk,filename) + --print(">: ",obj,name,t,chunk,filename) +end +EBIM:registerEBlock("string",function(self,args,tab) + local str={} + for i=1,#tab do + table.insert(str,tab[i]) + end + self:setVariable(args,table.concat(str,"\n")) +end) +EBIM:registerEBlock("list",function(self,args,tab) + local str={} + for i=1,#tab do + table.insert(str,self:varExists(tab[i])) + end + self:setVariable(args,str) +end) +EBIM:registerEBlock("dict",function(self,args,tab) + local str={} + for i=1,#tab do + local a,b=tab[i]:match("(.-):%s*(.+)") + str[a]=self:varExists(b) + end + self:setVariable(args,str) +end) diff --git a/bytecode.lua b/bytecode.lua new file mode 100644 index 0000000..26fb46f --- /dev/null +++ b/bytecode.lua @@ -0,0 +1,44 @@ +-- In an attempt to speed up my library I will use a virtual machine that runs bytecode +compiler={} +compiler.cmds={ -- list of all of the commands + EVAL="\01", -- evaluate + SPLT="\02", -- split + TRIM="\03", -- trim + VEXT="\04", -- variable exists + ILST="\05", -- is a list + LSTR="\06", -- load string + FCAL="\07", -- Function call + SVAR="\08", -- set variable + LOAD="\09", -- load file + LAOD="\10", -- _load file + DEFN="\11", -- define external functions + HCBK="\12", -- Has c Block + CMBT="\13", -- combine truths + SETB="\14", -- set block + STRT="\15", -- start + PERR="\16", -- push error + PROG="\17", -- progress + PHED="\18", -- parse header + SSLT="\19", -- split string + NEXT="\20", -- next + -- Needs refining... One step at a time right! +} +function compiler:compile(filename) -- compiles the code into bytecode + -- First we load the code but don't run it + local engine=parseManager:load(filename) + -- This captures all of the methods and important info. This also ensures that the compiler and interperter stay in sync! + local bytecodeheader=bin.new() -- header will contain the order of blocks and important flags + local bytecode=bin.newDataBuffer() -- lets leave it at unlimited size because we don't know how long it will need to be + local functions={} -- will be populated with the important methods that must be preloaded + local prebytecode={} -- this contains bytecode that has yet to be sterilized + for blockname,blockdata in pairs(engine._chunks) do + -- lets get some variables ready + local code,_type,nextblock,filename=blockdata[1],blockdata[2],blockdata.next,blockdata.file + -- note nextblock may be nil on 2 condidions. The first is when the leaking flag is disabled and the other is when the block in question was the last block defined + local lines=bin._lines(code) + print("\n["..blockname.."]\n") + for i=1,#lines do + print(lines[i]) + end + end +end diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..7fcb979 --- /dev/null +++ b/init.lua @@ -0,0 +1,1158 @@ +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 +-- 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) diff --git a/interpreter.lua b/interpreter.lua new file mode 100644 index 0000000..e4a5b84 --- /dev/null +++ b/interpreter.lua @@ -0,0 +1,10 @@ +engine={} +function engine:init(bytecodeFile) + self.code=bin.load(bytecodeFile).data +end +--[[OP-CODES + +]] +function engine:run(assessors) + -- +end