require("dms.utils") local char = string.char local Stack = require("dms.stack") local Chunk = require("dms.chunk") local Cmd = require("dms.cmd") local Value = require("dms.value") local Queue = require("dms.queue") local ENTR,ENAB,DISA,LOAD,VERN,USIN,STAT,DISP,ASGN,LABL,CHOI,OPTN,FORE,UNWN,WHLE,FNWR,FNNR,IFFF,ELIF,ELSE,DEFN,SKIP,COMP,INDX,JMPZ = "ENTR","ENAB","DISA","LOAD","VERN","USIN","STAT","DISP","ASGN","LABL","CHOI","OPTN","FORE","????","WHLE","FNWR","FNNR","IFFF","ELIF","ELSE","DEFN","SKIP","COMP","INDX","JMPZ" local controls = {STAT,CHOI,FORE,WHLE,IFFF,ELIF,ELSE} local flags = {ENTR,ENAB,DISA,LOAD,VERN,USIN,DEFN} local recognizedFlags = { "debugging", "noprint", "warnings" } local parser = {} parser.__index = parser local iStack = Stack:new() local fStack = Stack:new() local wStack = Stack:new() function parser:new(path) local c = {} setmetatable(c,self) c.filename = path or error("Must provied a path!") c.content = io.open(c.filename,"rb"):read("*a") c.flags = {} c.chunks = {} c.ver = {1,0,0} c.pos = 0 c.lines = {} return c end function parser:setVersion(ver) self.ver = ver end -- Output Stuffs function parser:debug(...) if not self.flags.debugging then return end self:print(...) end function parser:print(...) if self.flags.noprint then return end print(...) end function parser:error(err,line) if not line then print(err) os.exit() end print("Error: <"..line[4]..":"..line[1].."> \""..line[5].."\" "..err) os.exit() end function parser:warn(msg,line) if not self.flags.warnings then return end if not line then print(msg) return end print("Warning: <"..line[4]..":"..line[1].."> \""..line[5].."\" "..msg) end function parser:manageFlag(line) if self:isFlag(line) then local flag = line[3] local dat = line[5]:match("%s+(.+)$") if flag == ENTR then if self.entry then self:error("Entry was already set to: "..self.entry,line) else self.entry = dat end elseif flag == ENAB then if not self:isRecognizedFlag(dat) then self:warn("Flag \""..dat.."\" is not recognized!",line) end self.flags[dat] = true elseif flag == DISA then if not self:isRecognizedFlag(dat) then self:warn("Flag \""..dat.."\" is not recognized!",line) end self.flags[dat] = false elseif flag == LOAD then -- TODO elseif flag == VERN then local v local a,b,c = dat:match("(%d*)%.?(%d*)%.?(%d*)") a,b,c = tonumber(a),tonumber(b) or 0,tonumber(c) or 0 if a>self.ver[1] or a>self.ver[2] then self:warn("This script was created for a different version of the DMS! Code may behave unexpectedly or not work at all!",line) end elseif flag == USIN then -- TODO end else self:error("Flag Expected! Got: "..line[3],line) end end function parser:isControl(line) for i,v in pairs(controls) do if line[3] == v then return true end end return false end function parser:isFlag(line) for i,v in pairs(flags) do if line[3] == v then return true end end return false end function parser:isRecognizedFlag(flag) for i,v in pairs(recognizedFlags) do if v == flag:lower() then return true end end return false end function parser:split(s,pat,l) local pat=pat or "," local res = {} local start = 1 local state = 0 local c = '.' local elem = '' local function helper() if tonumber(elem) then elem = tonumber(elem) elseif elem:sub(1,1) == "\"" and elem:sub(-1,-1) == "\"" then elem = elem:sub(2,-2) elseif elem == "true" then elem = true elseif elem == "false" then elem = false elseif elem:sub(1,1)=="{" and elem:sub(-1,-1)=="}" then elem = self:split(elem:sub(2,-2),nil,true) elseif elem:match("[%-%+/%%%(%)%*%^]") then gen("$",true) -- Flush the temp variables elem = "\1"..self:parseExpr(elem) else -- \1 Tells the interperter that we need to do a lookup elem = "\1"..elem end end local counter = 0 local function next() counter = counter + 1 return s:sub(counter,counter) end local function peek() return s:sub(counter + 1,counter + 1) end local function getarr() local a = peek() local str = "" local open = 1 while open~=0 and a~="" do a = next() if a == "{" then open = open+1 end if a == "}" then open = open-1 if open == 0 then next() return str end end str = str .. a a = peek() end next() return str end c = true while c~="" do c = next() 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 local t = self:split(getarr(),nil,true) res[#res + 1] = t elem = '' state = 3 elseif c == pat then helper() res[#res + 1] = elem elem = '' state = 3 -- skip over the next space if present elseif c == "(" then state = 1 elem = elem .. '(' 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 = 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 helper() if elem ~= "" then res[#res + 1] = elem end if l then setmetatable(res,{ __tostring = function(self) local str = "{" for i,v in ipairs(self) do str = str .. tostring(v) .. ", " end return str:sub(1,-3).."}" end }) else setmetatable(res,{ __tostring = function(self) local str = "(" for i,v in ipairs(self) do str = str .. tostring(v) .. ", " end return str:sub(1,-3)..")" end }) end return res end function parser:parse() local link = self local line_num = 0 local choice local group = 0 local lastgroup = 0 local groupStack = Stack:new({}) local lastpop local noblock = true local arr = {} local multilined = false function groupStack:append(t) local c = self:peek() val = pcall(function() table.insert(c,1,t) end) if not val then link:error("Inconsistant indentation!",t) end end groupStack:push({}) for line in self.content:gmatch("(.-)\n") do line_num = line_num + 1 line = line:gsub("//(.+)$","") -- Filter out comments if not multilined and line:match("/%*(.+)$") then multilined = true line = line:gsub("/%*(.*)$","") end if multilined then if line:match("%*/") then multilined = false line = line:gsub("(.+)%*/","") if #line:trim() == 0 then goto continue end else goto continue end end if line:trim()=="" then goto continue end -- Skip all whitespace completely lastgroup = group group = line:tabs() if lastgroup>group then for i=1,lastgroup-group do local c = groupStack:pop() lastpop = c for i,v in pairs(c) do table.insert(arr,v) end end elseif lastgroup","<" function parser:JMPZ(v,label) local cmd = Cmd:new({self.current_lineStats[1],0,JMPZ,self.current_lineStats[2],"?"},JMPZ,{label = label,var = v}) function cmd:tostring() return self.args.var .. ", " ..self.args.label end self.current_chunk:addCmd(cmd) end function parser:parseIFFF(line) line[5] = self:logicChop(line[5]) print(line[5]) local v = self:parseExpr(line[5]) local labelE = gen("$labelEnd_") self:JMPZ(v,labelE) iStack:push({cmds = Queue:new()}) io.read() end function parser:buildLogic(l,o,r,v) local cmd = Cmd:new({self.current_lineStats[1],0,COMP,self.current_lineStats[2],"?"},COMP,{left=l,op=o,right=r,var=v}) function cmd:tostring() return table.concat({self.args.op,tostring(self.args.left),tostring(self.args.right)},", ").." -> "..self.args.var end self.current_chunk:addCmd(cmd) end function parser:buildIndex(name,ind,v) local cmd = Cmd:new({self.current_lineStats[1],0,INDX,self.current_lineStats[2],"?"},INDX,{name = name, index = ind, var = v}) function cmd:tostring() return table.concat({tostring(self.args.name),tostring(self.args.index)},", ").." -> "..self.args.var end self.current_chunk:addCmd(cmd) end function parser:logicChop(expr) expr = expr:gsub("([_%w]+)(%b())",function(func,args) local v = gen("$") print(v.." = "..func..args) local fnwr = {self.current_lineStats[1],0,FWNR,self.current_lineStats[2],v.." = "..func..args} self:parseFNWR(fnwr) return v end) local counter = 0 local v local function next() counter = counter + 1 return expr:sub(counter,counter) end local function prev() counter = counter - 1 end local function peek() return expr:sub(counter + 1,counter + 1) end local c = "." local l,o,r = '','','' local state = 0 local start = 1 local elem = "" local function setOp(op) if l~='' then o = op else self:error("Invalid Syntax 2") end end local function index(right) local ind = "" c = peek() while c ~= "]" do next() ind = ind..c c = peek() end c = next() if right then local vv = gen("$") v = gen("$") r = right self:buildIndex(r,ind,vv) self:buildLogic(l,o,vv,v) l,o,r = '','','' elem = elem .. v elseif l~= "" and r=="" then v = gen("$") print(l.."["..ind.."] -> "..v) l = v end if not(c=="]") then self:error("']' expected to close '['") end end local function default() if c == "=" and peek()=="=" then next() -- eat second = setOp(EQ) elseif c == ">" and peek()=="=" then next() setOp(GTE) elseif c == "<" and peek()=="=" then next() setOp(LTE) elseif c == "!" or c == "~" and peek()== "=" then next() setOp(NEQ) elseif c == "<" then setOp(LT) elseif c == ">" then setOp(GT) elseif c == "[" then -- handle index stuff index() elseif c == "o" and peek()=="r" then next() elem = elem .. "+" elseif c == "a" and peek()=="n" then if next() == "n" and peek()=="d" then next() elem = elem .. "*" else --self:error("Invalid syntax!"..c) -- Grab the current line end elseif c == "i" and peek()=="f" then next() elseif c == ' ' then -- Ignore white space if not part of a string elseif isDigit(c) then local dot = false prev() c = peek() local digit = "" while isDigit(c) or (dot==false and c==".") do next() if c=="." then dot = true digit = digit .. "." elseif isDigit(c) then digit = digit .. c end c = peek() end digit = tonumber(digit) if l=="" then l = digit elseif r=="" then r = digit v = gen("$") self:buildLogic(l,o,r,v) l,o,r = '','','' elem = elem .. v end elseif isLetter(c) then if l=='' then l=c local k = peek() while isLetter(k) or isDigit(k) do next() l=l..k k = peek() end if l == "true" then l = true elseif l == "false" then l = false else l = "\1" .. l end elseif o~= '' then r=c local k = peek() while isLetter(k) or isDigit(k) do next() r=r..k k = peek() end if k == "[" then next() index(r) else if r == "true" then r = true elseif r == "false" then r = false else r = "\1" .. r end v = gen("$") self:buildLogic(l,o,r,v) l,o,r = '','','' elem = elem .. v end else print(l,o,r) self:error("Invalid syntax!") end else elem = elem .. c end end local str = "" while c~="" do c = next() if c == '"' then c = peek() str = "" while c~="\"" do next() if c=="\\" and peek()=="\"" then next() str = str .. "\"" else str = str .. c end c = peek() end if l=="" then l = str elseif r=="" then r = str v = gen("$") self:buildLogic(l,o,r,v) l,o,r = '','','' elem = elem .. v end next() elseif c == "'" then elem = elem .. '"' c = peek() while c~="\"" do next() if c=="\\" and peek()=="\"" then next() elem = elem + "\"" else elem = elem .. c end c = peek() end if l=="" then l = str elseif r=="" then r = str v = gen("$") self:buildLogic(l,o,r,v) l,o,r = '','','' elem = elem .. v end next() else default() end end return elem end function parser:chop(expr) for l,o,r in expr:gmatch("(.-)([/%^%+%-%*%%])(.+)") do if r:match("(.-)([/%^%+%-%*%%])(.+)") then local v = gen("$") --print(l,o,self:chop(r),"->",v) self:buildMFunc(l,o,self:chop(r),v) return v else local v = gen("$") --print(l,o,r,"->",v) self:buildMFunc(l,o,r,v) return v end end end function parser:buildMFunc(l,o,r,v) local fnwr = {self.current_lineStats[1],0,FWNR,self.current_lineStats[2]} local line if o == "+" then line = v.." = ADD("..table.concat({l,r},",")..")" elseif o == "-" then line = v.." = SUB("..table.concat({l,r},",")..")" elseif o == "*" then line = v.." = MUL("..table.concat({l,r},",")..")" elseif o == "/" then line = v.." = DIV("..table.concat({l,r},",")..")" elseif o == "%" then line = v.." = MOD("..table.concat({l,r},",")..")" elseif o == "^" then line = v.." = POW("..table.concat({l,r},",")..")" else -- Soon we will allow for custom symbols for now error end table.insert(fnwr,line) self:parseFNWR(fnwr) end function parser:parseExpr(expr) -- handle pharanses expr=expr:gsub("([%W])(%-%d)",function(b,a) return b.."(0-"..a:match("%d+")..")" end) -- Work on functions expr = expr:gsub("([_%w]+)(%b())",function(func,args) local v = gen("$") local fnwr = {self.current_lineStats[1],0,FWNR,self.current_lineStats[2],v.." = "..func..args} self:parseFNWR(fnwr) return v end) expr = expr:gsub("(%b())",function(a) return self:parseExpr(a:sub(2,-2)) end) return self:chop(expr) end function parser:parseChoice(line) local text = line[5]:match("\"(.+)\"") local copt = self:peek() local choice = {text = text,options = {}} local cmd = Cmd:new(line,CHOI,choice) function cmd:tostring() return "\""..self.args.text.."\", "..table.concat(self.args.options,", ")-- disp choices end self.current_chunk:addCmd(cmd) if copt[3]~=OPTN then self:error("Choices must have at least one option to choose!") return end local opts = {} while copt do copt = self:next() local a,b = copt[5]:match("(.+) %s*(.+)") table.insert(choice.options,a) table.insert(opts,b) copt = self:peek() if copt[3]~=OPTN then break end end local s = ((#opts-1)*2-1) local c = s for i=1,#opts do self:parseFNNR({copt[1],copt[2],FNNR,copt[4],opts[i]}) if i~=#opts then self:parseFNNR({copt[1],copt[2],FNNR,copt[4],"SKIP(".. c ..")"}) end c=c-2 end end function parser:parseFNNR(line) local fname, args = line[5]:match("(.-)%((.*)%)") if #args==0 then args = {} else args = self:split(args) end local cmd = Cmd:new(line,FNNR,{func=fname,args=args}) function cmd:tostring() if #args==0 then return fname else return table.concat({fname,table.concat(args,", ")},", ") end end self.current_chunk:addCmd(cmd) end function parser:parseFNWR(line) local vars, fname, args = line[5]:match("(.-)%s*=%s*(.-)%((.+)%)") vars = self:split(vars) if #args==0 then args = {} else args = self:split(args) end local cmd = Cmd:new(line,FNWR,{func=fname,args=args,vars=vars}) function cmd:tostring() return table.concat({fname,table.concat(args,", ")},", ").." -> ".. table.concat(vars,", ") end self.current_chunk:addCmd(cmd) end function parser:parseASGN(line) local vars,assigns = line[5]:match("(.-)%s*=%s*(.+)") vars = self:split(vars) assigns = self:split(assigns) local list = {} for i,v in ipairs(vars) do table.insert(list,Value:new(v,assigns[i])) end local cmd = Cmd:new(line,ASGN,list) function cmd:tostring() local str = "" for i,v in ipairs(self.args) do str = str .. "(" .. tostring(v) .. " -> ".. v.name ..")" .. ", " end return str:sub(1,-3) end self.current_chunk:addCmd(cmd) -- TODO: make dict lookups builtin end function parser:parseLABL(line) local label = line[5]:match("::(.+)::") if not label then self:error("Invalid Label Definition",line) end local cmd = Cmd:new(line,LABL,{label = label}) function cmd:tostring() return self.args.label end self.current_chunk:addCmd(cmd) end function parser:parseDialogue(line) local targ,text = line[5]:match("(%w*)%s*(.*)") if #targ == 0 then targ = nil end local cmd = Cmd:new(line,DISP,{text=text,target=targ}) function cmd:tostring() return table.concat({targ or "@",text},", ") end self.current_chunk:addCmd(cmd) end function parser:peek() return self.lines[self.pos + 1] end function parser:next() self.pos = self.pos + 1 return self.lines[self.pos] end return parser