From e0546bcda57720f09378cdae554beed0c9544a2a Mon Sep 17 00:00:00 2001 From: Blendi Date: Sat, 25 Apr 2026 16:59:45 +0200 Subject: [PATCH] bootstrap compiler --- main.lua | 48 ++ src/cli.lua | 171 +++++ src/lexer.lua | 321 ++++++++++ src/lower.lua | 129 ++++ src/methods/debug.lua | 676 ++++++++++++++++++++ src/methods/small.lua | 719 +++++++++++++++++++++ src/output.lua | 46 ++ src/parser.lua | 1373 +++++++++++++++++++++++++++++++++++++++++ src/types.lua | 85 +++ src/util.lua | 64 ++ 10 files changed, 3632 insertions(+) create mode 100644 main.lua create mode 100644 src/cli.lua create mode 100644 src/lexer.lua create mode 100644 src/lower.lua create mode 100644 src/methods/debug.lua create mode 100644 src/methods/small.lua create mode 100644 src/output.lua create mode 100644 src/parser.lua create mode 100644 src/types.lua create mode 100644 src/util.lua diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..4a5e339 --- /dev/null +++ b/main.lua @@ -0,0 +1,48 @@ +require('src.util') + +require("src.types") +require("src.lexer") +require("src.parser") +require("src.lower") +require("src.output") +require("src.methods.debug") +require("src.methods.small") + +require("src.cli") + +-- local file = assert(io.open("code.lake", "r")) +-- local code = file:read("*all") + +-- local lexer = Lexer:new(code, "test.lake") + +-- local lexer2 = Lexer:new(code, "test.lake") +-- while true do +-- local tok, err = lexer2:consume() +-- if not tok then +-- error("oh no " .. tostring(err.message)) +-- end + +-- print(tok.kind, tok.content) + +-- if tok.kind == "eof" then break end +-- end + +-- print'\n\n' + +-- local parser = Parser:new(lexer) + +-- local program, err = parser:parse() + +-- if not program then +-- error(err.message) +-- end + +-- ERGON = require("ERGON") + +-- print(ERGON.encodeIndented(program, 1, " ")) + +-- local output = Output:new("lua54", "debug") + +-- output:output(program) +-- print'\n\n' +-- print(table.concat(output.lines, "\n")) \ No newline at end of file diff --git a/src/cli.lua b/src/cli.lua new file mode 100644 index 0000000..8422a0c --- /dev/null +++ b/src/cli.lua @@ -0,0 +1,171 @@ + +---@param args string[] +---@param argConfig table +function ParseArgData(args, argConfig) + local i = 1 + + local knorps = {} + local flarples = {} + + for k,v in pairs(argConfig) do + if v[1] == "flag" then + flarples[v[2]] = false + elseif v[1] == "list" then + flarples[v[2]] = {} + elseif v[1] == "input" then + -- no + end + end + + while i <= #args do + local argument = args[i] + + if argConfig[argument] then + local argconf = argConfig[argument] + + local argtype = argconf[1] + local storageplace = argconf[2] + + if argtype == "flag" then + flarples[storageplace] = true + elseif argtype == "input" then + local next = args[i+1] + i = i + 1 -- don't get it checked as a flag because that'd be rerarded + + if not next then + error("gdsiohjubhoupidsgohupigdshoupigsdhpoid") + end + + flarples[storageplace] = next + elseif argtype == "list" then + local next = args[i+1] + i = i + 1 -- don't get it checked as a flag because that'd be rerarded + + if not next then + error("gdsiohjubhoupidsgohupigdshoupigsdhpoid") + end + + table.insert(flarples[storageplace], next) + end + else + if argument:sub(1,1) == "-" then + error("fuck you") + end + + knorps[#knorps+1] = argument + end + + i = i + 1 + end + + return knorps, flarples +end + +if #arg == 0 then + while true do + io.write("\x1b[36minput>\x1b[0m ") + io.flush() + local line = io.read("L") + if not line then break end + + local lexer = Lexer:new(line, "stdin") + local parser = Parser:new(lexer) + + local ast, err = parser:parse() + + if err then + local msg = string.format("%s:%d %s", err.location.file, err.location.line, err.message) + print("\x1b[31m" .. msg .. "\x1b[0m") + else + assert(ast, "incredibly bad no good state very bad crash now") + + -- this is the best code ever made + local output = Output:new("lua54", "small") + output:output(ast) + local outdata = table.concat(output.lines, '\n') + print(outdata) + end + end + return +end + +if arg[1] == "compile" then + table.remove(arg, 1) + + local options = { + ['-v'] = {'flag', 'verbose'}, + ['-o'] = {'input', 'output'}, + ['-s'] = {'input', 'sourcemap'}, + -- ['-d'] = {'list', 'dump'}, + -- ['-D'] = {'input', 'dependency'}, + ['-m'] = {'input', 'method'}, + ['-t'] = {'input', 'target'} + } + + + local knorps, flarples = ParseArgData(arg, options) + flarples.target = flarples.target or "lua54" + flarples.method = flarples.method or "debug" + + if not flarples.output then + print("\x1b[31mno output file given\x1b[0m") + os.exit(1) + end + + local outfile = io.open(flarples.output, "w") + if not outfile then + print("\x1b[31moutput file not writable\x1b[0m") + os.exit(1) + end + + local SMfile + if flarples.sourcemap then + SMfile = io.open(flarples.sourcemap, 'w') + if not SMfile then + print("\x1b[31msourcemap file not writable\x1b[0m") + os.exit(1) + end + end + + for i = 1,#knorps do + local file = io.open(knorps[i],"r") + if not file then + print("\x1b[31mcould not read file " .. tostring(knorps[i]) .."\x1b[0m") + os.exit(1) + end + + local code = file:read("*all") + file:close() + + local lexer = Lexer:new(code, knorps[i]) + + local parser = Parser:new(lexer) + + local ast,err = parser:parse() + if not ast then + if err then + err:print() + else + print("\x1b[31m!!! an unknown error occured !!!\x1b[0m") + end + os.exit(1) + end + + local output = Output:new(flarples.target, flarples.method) + + output:output(ast) + + local outdata = table.concat(output.lines, '\n') + local smdata = table.concat(output.debug, '\n') + + outfile:write(outdata); + if SMfile then + SMfile:write(smdata) + end + end + + outfile:close() + if SMfile then + SMfile:close() + end +end diff --git a/src/lexer.lua b/src/lexer.lua new file mode 100644 index 0000000..b58ca8e --- /dev/null +++ b/src/lexer.lua @@ -0,0 +1,321 @@ +---@class Lexer +---@field cache Token? +---@field cursor integer +---@field location Location +---@field code string +Lexer = {} + +Lexer.keywords = { + "var", + "return", + "fn", + "true", + "false", + "null", + "if", + "var", + "else", + "elseif", + "while", + "for", + "do", + "break", + "foreach" +} + +Lexer.symbols = { + "!=", + "==", + "<=", + ">=", + "&&=", + "||=", + "&&", + "||", + "+=", + "-=", + "*=", + "/=", + "%=", + "^=", + "..=", + "..", + ">", + "<", + "+", + "-", + "*", + "/", + "^", + "=", + ";", + "#", + "!", + "[", + "]", + ".", + "{", + "}", + "(", + ")", + ",", + "%", + ':' +} + +function Lexer:new(code, fname) + return setmetatable({ + cache = nil, + cursor = 1, + location = Location:new(fname), + code = code, + }, {__index = self}) +end + +function Lexer:next(n) + n = n or 1 + for i = 1,n do + self.location:skip(self.code:sub(self.cursor,self.cursor)) + self.cursor = self.cursor + 1 + end +end + +function Lexer:get(n) + n = n or 1 + return self.code:sub(self.cursor, self.cursor+n-1) +end + +function Lexer:isWhitespace(ch) + return ch == "\r" or ch == "\n" or ch == "\t" or ch == " " +end + +function Lexer:isDigit(ch) + local n = string.byte(ch) + if not n then return false end + return n >= 48 and n <= 57 +end + +function Lexer:isHex(ch) + return (ch:match("%x") ~= nil) +end + +function Lexer:isIdentifierValid(ch) + return (ch:match("%a") ~= nil) or (Lexer:isDigit(ch)) or (ch == '_') +end + +function Lexer:run() + while true do -- skip skippables + if self:isWhitespace(self:get()) then + self:next() + elseif self:get(2) == "//" then -- comment yay wooo i love it i'm so happy yayyyy + while (self:get() ~= "\n") and (self:get() ~= "") do + self:next() + end + elseif self:get() == '' then + return Token:new("eof", "eof", self.location:clone()) + else + break + end + end + + if self:isDigit(self:get()) then + -- number!!! + + local stpos = self.location:clone() + local st = "" + while self:isDigit(self:get()) do + st = st .. self:get() + self:next() + end + + if self:get() == '.' then + -- omg number + + self:next() + st = st .. "." + + while self:isDigit(self:get()) do + st = st .. self:get() + self:next() + end + end + + return Token:new("number", st, stpos) + end + + if self:get() == "'" then -- rawer string + local d = "'" + local stpos = self.location:clone() + self:next() + + while self:get() ~= "'" do + local ch = self:get() + if ch == '' then break end + d = d .. ch + self:next() + end + + if self:get() ~= "'" then + return nil, Error:new("unfinished string", "lexer", self.location:clone()) + end + d = d .. "'" + self:next() + + return Token:new("string", d, stpos) + end + + if self:get() == '"' then -- not raw string + local d = '"' + local stpos = self.location:clone() + self:next() + + while true do + local ch = self:get() + if ch == '\\' then + d = d .. ch + self:next() + + local nch = self:get() + if nch == '\\' then + d = d .. '\\' + self:next() + elseif nch == 'n' then + d = d .. 'n' + self:next() + elseif nch == 'r' then + d = d .. 'r' + self:next() + elseif nch == 'v' then + d = d .. 'v' + self:next() + elseif nch == '"' then + d = d .. '"' + self:next() + elseif nch == 'f' then + d = d .. 'f' + self:next() + elseif nch == 't' then + d = d .. "t" + self:next() + elseif nch == 'x' then + d = d .. 'x' + self:next() + + local hexdata = self:get(2) + + if (not self:isHex(hexdata:sub(1,1))) or (not self:isHex(hexdata:sub(2,2))) then + return nil, Error:new("invalid hex digits after x escape code", 'lexer', self.location:clone()); + end + + d = d .. hexdata + self:next(2) + elseif nch == "u" then + d = d .. "u" + self:next() + + if self:get() ~= '{' then + return nil, Error:new('expected { after u escape', 'lexer', self.location:clone()); + end + d = d .. "{" + self:next() + + local i = 0 + while self:isHex(self:get()) and (self:get() ~= '') do -- TODO: check validity + d = d .. self:get() + self:next() + i = i + 1 + end + + if i > 0 then + local hexpart = d:sub(-i) + + local value = HexDecode(hexpart) + + if value >= 2^31 then + return nil, Error:new("invalid utf8 codepoint in u escape", 'lexer', self.location:clone()) + end + end + + if self:get() ~= '}' then + return nil, Error:new('expected } to close { in u escape', 'lexer', self.location:clone()) + end + d = d .. "}" + self:next() + end + elseif ch == '' then + break; + elseif ch == '"' then + break; + else + d = d .. ch; -- assume it's just something you can have in there + self:next() + end + end + + if self:get() ~= '"' then + return nil, Error:new('unfinished double-quote string', 'lexer', self.location:clone()) + end + d = d .. '"' + self:next() + + return Token:new("string", d, stpos) + end + + do + local stloc = self.location:clone() + for i = 1,#self.symbols do + local symbol = self.symbols[i] + + if self:get(#symbol) == symbol then + -- ladies and gentlemen, we got him. + + self:next(#symbol) + + return Token:new("symbol", symbol, stloc) + end + end + end + + -- ident! + if self:isIdentifierValid(self:get()) then + local ident = "" + local stpos = self.location:clone() + + while self:isIdentifierValid(self:get()) do + ident = ident .. self:get() + self:next() + end + + for i = 1,#self.keywords do + local kw = self.keywords[i] + + if ident == kw then + return Token:new("keyword", kw, stpos) + end + end + + return Token:new("identifier", ident, stpos) + end + + return nil, Error:new("tried to lex invalid code", "lexer", self.location:clone()) +end + +function Lexer:peek() + if self.cache ~= nil then + return self.cache, nil + end + + local token, err = self:run() + self.cache = token + return token, err +end + +function Lexer:consume() + if self.cache == nil then + return self:run() + end + + local token = self.cache + self.cache = nil + return token, nil +end diff --git a/src/lower.lua b/src/lower.lua new file mode 100644 index 0000000..4ec711d --- /dev/null +++ b/src/lower.lua @@ -0,0 +1,129 @@ +Lower = {} + +function Lower.makeUTF8Char(decimal) + if decimal < 128 then + return string.char(decimal) + elseif decimal < 2048 then + local byte2 = (128 + (decimal % 64)) + local byte1 = (192 + math.floor(decimal / 64)) + return string.char(byte1, byte2) + elseif decimal < 65536 then + local byte3 = (128 + (decimal % 64)) + decimal = math.floor(decimal / 64) + local byte2 = (128 + (decimal % 64)) + local byte1 = (224 + math.floor(decimal / 64)) + return string.char(byte1, byte2, byte3) + elseif decimal < 1114112 then + local byte4 = (128 + (decimal % 64)) + decimal = math.floor(decimal / 64) + local byte3 = (128 + (decimal % 64)) + decimal = math.floor(decimal / 64) + local byte2 = (128 + (decimal % 64)) + local byte1 = (240 + math.floor(decimal / 64)) + return string.char(byte1, byte2, byte3, byte4) + else + return nil -- Invalid Unicode code point + end +end + +function Lower.decodeString(lakeString) + if lakeString:sub(1,1) == "'" then + -- easy + return lakeString:sub(2,-2) -- bad way to do it but it works lol lmfao rofl + end + if lakeString:sub(1,1) == '"' then + local i = 2 + local out = "" + while true do + local ch = lakeString:sub(i,i) + + if ch == '\\' then + i = i + 1 + local nextch = lakeString:sub(i,i) + + if nextch == '\\' then + out = out .. '\\' + i = i + 1; + elseif nextch == 'n' then + out = out .. '\n' + i = i + 1; + elseif nextch == 'r' then + out = out .. '\r' + i = i + 1; + elseif nextch == 'v' then + out = out .. '\v' + i = i + 1; + elseif nextch == '"' then + out = out .. '"' + i = i + 1; + elseif nextch == 'f' then + out = out .. '\f' + i = i + 1; + elseif nextch == "t" then + out = out .. "\t" + i = i + 1 + elseif nextch == 'x' then + i = i + 1; + local hex = lakeString:sub(i, i + 1) + i = i + 2; + + local decoded = HexDecode(hex); + + out = out .. string.char(decoded) + elseif nextch == "u" then + i = i + 2; -- skip u and {, if the { wasn't there that's the lexer's fault and their bug. + + local hexpart = "" + + while lakeString:sub(i,i) ~= '}' do + hexpart = hexpart .. lakeString:sub(i,i) + i = i + 1 + end + -- on the }, skip it: + i = i + 1 -- if we were not on it, welp + + local decoded = HexDecode(hexpart) + + out = out .. Lower.makeUTF8Char(decoded); + end + elseif ch == '"' then + break; + else + i = i + 1 + out = out .. ch + end + end + + return out; + end +end + +function Lower.decodeNumber(num) + local numeric = 0 + local decimal = false + for i = 1,#num do + local ch = num:sub(i,i) + + if ch == "." then + decimal = i + 1 + break + end + + numeric = numeric * 10 + + numeric = numeric + (string.byte(ch) - string.byte'0') + end + + if decimal then + local n = 0.1 + for j = decimal, #num do + local ch = num:sub(j,j) + + numeric = numeric + (string.byte(ch) - string.byte'0')*n + + n = n * 0.1 + end + end + + return numeric +end \ No newline at end of file diff --git a/src/methods/debug.lua b/src/methods/debug.lua new file mode 100644 index 0000000..9a69cc2 --- /dev/null +++ b/src/methods/debug.lua @@ -0,0 +1,676 @@ +Debug = {} + +-- very basic, but should always be correct and doesn't try any optimization shenanigans +function Debug.encodeString(raw, islua51) + local out = '"' + + for i = 1,#raw do + local ch = raw:sub(i,i) + + local code = string.byte(ch) + if ch == '\\' then + out = out .. "\\\\" + elseif ch == '"' then + out = out .. '\\"' + elseif code >= 32 and code <= 126 then + out = out .. ch + else + if islua51 then + out = out .. "\\" .. DecEncode(code, 3) + else + out = out .. "\\x" .. HexEncode(code, 2) + end + end + end + + out = out .. '"' + + return out +end + +function Debug.encodeNumber(n) + local num = '' + local isneg = false + + if n < 0 then + n = n * -1 + isneg = true; + end + + local whole = math.floor(n) + + if whole == 0 then + num = num .. '0' + end + + while whole > 0 do + local digit = whole % 10 + num = string.char(string.byte'0' + digit) .. num + + whole = math.floor(whole / 10) + end + + local dec = n % 1 + + if dec > 0 then + num = num .. "." + + while dec > 0 do + -- aw nah + dec = dec * 10 + + local digit = math.floor(dec) + + num = num .. string.char(string.byte'0' + digit) + + dec = dec % 1 + end + end + + if isneg then + num = '-' .. num + end + + return num +end + +function Debug.convertBinaryOperator(lakeop) + if lakeop == "+" then + return "+" + elseif lakeop == "-" then + return "-" + elseif lakeop == "*" then + return "*" + elseif lakeop == "/" then + return "/" + elseif lakeop == "%" then + return "%" + elseif lakeop == "^" then + return "^" + elseif lakeop == ".." then + return ".." + elseif lakeop == "<" then + return "<" + elseif lakeop == ">" then + return ">" + elseif lakeop == "<=" then + return "<=" + elseif lakeop == ">=" then + return ">=" + elseif lakeop == "!=" then + return "~=" + elseif lakeop == "==" then + return "==" + elseif lakeop == "&&" then + return "and" + elseif lakeop == "||" then + return "or" + end +end + +function Debug.convertUnaryOperator(op) + if op == "#" then + return "#" + elseif op == "-" then + return "-" + elseif op == "!" then + return "not" + end +end + +function Debug.output(outp, ast) + Debug.outputStatement(outp, ast) +end + +---@param outp Output +---@param node Node +function Debug.outputStatement(outp, node) + if node.kind == "Program" then + outp:addLine("do", node.location:format()) + + for i = 1,#node.content do + local stmt = node.content[i] + + Debug.outputStatement(outp, stmt) + end + + outp:addLine("end;", node.location:format()) + elseif node.kind == "VariableDeclaration" then + outp:addLine("local", node.location:format()) + local varnames = node.content[1] + + for i = 1,#varnames do + local vname = varnames[i] + + outp:addLine(vname[1], vname[2]:format()) + + if i ~= #varnames then + outp:addLine(",", node.location:format()) + end + end + + local values = node.content[2] + if #values > 0 then + outp:addLine("=", node.location:format()) + + for i = 1,#values do + local val = values[i] + + Debug.outputExpression(outp, val) + + if i ~= #values then + outp:addLine(",", node.location:format()) + end + end + + end + + outp:addLine(";", node.location:format()) + elseif node.kind == "Block" then + outp:addLine("do", node.location:format()) + + for i = 1,#node.content do + local stmt = node.content[i] + + Debug.outputStatement(outp, stmt) + end + + outp:addLine("end;", node.location:format()) + elseif node.kind == "IfStatement" then + outp:addLine("if", node.location:format()) + + Debug.outputExpression(outp, node.content[1][1][1]) + + outp:addLine("then", node.location:format()) + + Debug.outputStatement(outp, node.content[1][1][2]) + + for i = 2, #node.content[1] do + local item = node.content[1][i] + -- print(item, item[1], item[2]) + outp:addLine("elseif", node.location:format()) + + Debug.outputExpression(outp,item[1]); + + outp:addLine("then", node.location:format()) + + Debug.outputStatement(outp,item[2]); + end + + if node.content[2] then + outp:addLine("else", node.location:format()) + + Debug.outputStatement(outp, node.content[2]) + end + + outp:addLine("end;", node.location:format()) + elseif node.kind == "FunctionDeclaration" then + outp:addLine("function", node.location:format()) + + Debug.outputFunctionName(outp, node.content[1]); + + outp:addLine("(", node.location:format()) + + for i = 1,#node.content[2] do + local part = node.content[2][i] + + outp:addLine(part, node.location:format()) + + if i ~= #node.content[2] then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + + Debug.outputStatement(outp, node.content[3]) + + outp:addLine("end;", node.location:format()) + elseif node.kind == "LocalFunctionDeclaration" then + outp:addLine("local function", node.location:format()) + + outp:addLine(node.content[1].content, node.content[1].location:format()); + + outp:addLine("(", node.location:format()) + + for i = 1,#node.content[2] do + local part = node.content[2][i] + + outp:addLine(part, node.location:format()) + + if i ~= #node.content[2] then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + + Debug.outputStatement(outp, node.content[3]) + + outp:addLine("end;", node.location:format()) + elseif node.kind == "Return" then + outp:addLine("return", node.location:format()) + + for i = 1,#node.content do + local expr = node.content[i] + + Debug.outputExpression(outp, expr) + + if i ~= #node.content then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(";", node.location:format()) + elseif node.kind == "Assignment" then + local vars = node.content[1] + local vals = node.content[2] + + for i = 1,#vars do + local var = vars[i] + + Debug.outputVariable(outp, var) + + if i ~= #vars then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine("=", node.location:format()) + + for i = 1,#vals do + local val = vals[i] + + Debug.outputExpression(outp, val) + + if i ~= #vals then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(";", node.location:format()) + elseif node.kind == "Call" then + outp:addLine("(", node.location:format()) + Debug.outputExpression(outp, node.content[1]) + outp:addLine(")(", node.location:format()) + + local args = node.content[2] + for i = 1,#args do + local arg = args[i] + + Debug.outputExpression(outp, arg) + + if i ~= #args then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + outp:addLine(";", node.location:format()) + elseif node.kind == "MethodCall" then + outp:addLine("(", node.location:format()) + Debug.outputExpression(outp, node.content[1]) + outp:addLine("):", node.location:format()) + outp:addLine(node.content[2] .. '(', node.location:format()) + + local args = node.content[3] + for i = 1,#args do + local arg = args[i] + + Debug.outputExpression(outp, arg) + + if i ~= #args then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + outp:addLine(";", node.location:format()) + elseif node.kind == "While" then + outp:addLine("while", node.location:format()) + + Debug.outputExpression(outp, node.content[1]) + + outp:addLine("do", node.location:format()) + + Debug.outputStatement(outp, node.content[2]) + + outp:addLine("end;", node.location:format()) + elseif node.kind == "For" then + outp:addLine("do", node.location:format()) + Debug.outputStatement(outp,node.content[1]); + + outp:addLine("while", node.location:format()) + Debug.outputExpression(outp,node.content[2]); + outp:addLine("do", node.location:format()) + + outp:addLine("do", node.location:format()) + Debug.outputStatement(outp,node.content[4]); + outp:addLine("end;", node.location:format()) + + Debug.outputStatement(outp,node.content[3]); + + outp:addLine("end;", node.location:format()) + + outp:addLine("end;", node.location:format()) + elseif node.kind == "OperatorAssignment" then + local lake_op = node.content[2] + local op = Debug.convertBinaryOperator(lake_op) + + local lhs = node.content[1] + local rhs = node.content[3] + + if lhs.kind == "Variable" then + local varname = lhs.content + + outp:addLine(varname, node.location:format()) + outp:addLine('=', node.location:format()) + + outp:addLine('(', node.location:format()) + outp:addLine(varname, node.location:format()) + outp:addLine(op, node.location:format()) + outp:addLine('(', node.location:format()) + Debug.outputExpression(outp, rhs) + outp:addLine('));', node.location:format()) + elseif lhs.kind == "Field" then + outp:addLine('do', node.location:format()) + + outp:addLine('local _lake_operator_assignment_tmp =', node.location:format()) + Debug.outputExpression(outp, lhs.content[1]) + outp:addLine(';', node.location:format()); + + outp:addLine('(_lake_operator_assignment_tmp).', node.location:format()) + outp:addLine(lhs.content[2], node.location:format()) + + outp:addLine('=', node.location:format()) + + outp:addLine('(', node.location:format()) + outp:addLine('(_lake_operator_assignment_tmp).', node.location:format()) + outp:addLine(lhs.content[2], node.location:format()) + outp:addLine(op, node.location:format()) + outp:addLine('(', node.location:format()) + Debug.outputExpression(outp, rhs) + outp:addLine('));', node.location:format()) + + outp:addLine('end;', node.location:format()) + elseif lhs.kind == "Index" then + outp:addLine('do', node.location:format()) + + outp:addLine('local _lake_operator_assignment_tmp =', node.location:format()) + Debug.outputExpression(outp, lhs.content[1]) + outp:addLine(';', node.location:format()); + + outp:addLine('(_lake_operator_assignment_tmp)[', node.location:format()) + Debug.outputExpression(outp, lhs.content[2]) + outp:addLine(']', node.location:format()) + + outp:addLine('=', node.location:format()) + + outp:addLine('(', node.location:format()) + outp:addLine('(_lake_operator_assignment_tmp)[', node.location:format()) + Debug.outputExpression(outp, lhs.content[2]) + outp:addLine(']', node.location:format()) + outp:addLine(op, node.location:format()) + outp:addLine('(', node.location:format()) + Debug.outputExpression(outp, rhs) + outp:addLine('));', node.location:format()) + + outp:addLine('end;', node.location:format()) + end + elseif node.kind == "Break" then + outp:addLine('break;', node.location:format()) + elseif node.kind == "Foreach" then + outp:addLine("for", node.location:format()) + + local varlist,exprlist,block = node.content[1],node.content[2],node.content[3] + + for i = 1,#varlist do + outp:addLine(varlist[i], node.location:format()) + if i ~= #varlist then + outp:addLine(',', node.location:format()) + end + end + + outp:addLine("in", node.location:format()) + + for i = 1,#exprlist do + Debug.outputExpression(outp, exprlist[i]) + + if i ~= #exprlist then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine('do', node.location:format()) + + Debug.outputStatement(outp, block); + + outp:addLine('end;', node.location:format()) + end +end + +---@param outp Output +---@param node Node +function Debug.outputExpression(outp, node) + if node.kind == "StringLiteral" then + local lakestr = node.content + + local raw = Lower.decodeString(lakestr) + + local enc = Debug.encodeString(raw, outp.target == "lua51") + + outp:addLine(enc, node.location:format()) + elseif node.kind == "ParenthesizedExpression" then + outp:addLine("(", node.location:format()) + Debug.outputExpression(outp, node.content) + outp:addLine(")", node.location:format()) + elseif node.kind == "NumberLiteral" then + local lakenum = node.content + + local raw = Lower.decodeNumber(lakenum) + + -- print(raw, type(raw)) + + local enc = Debug.encodeNumber(raw) + + outp:addLine("(", node.location:format()) + outp:addLine(enc, node.location:format()) + outp:addLine(")", node.location:format()) + elseif node.kind == "TableLiteral" then + outp:addLine('{', node.location:format()) + local list_part = node.content[1] + local table_part = node.content[2] + + for i = 1,#list_part do + local item = list_part[i] + + Debug.outputExpression(outp, item); + outp:addLine(',', node.location:format()) + end + + for i = 1,#table_part do + local item = table_part[i] + + outp:addLine('[', node.location:format()) + Debug.outputExpression(outp, item[1]) + outp:addLine(']', node.location:format()) + outp:addLine('=', node.location:format()) + Debug.outputExpression(outp, item[2]) + outp:addLine(',', node.location:format()) + end + outp:addLine('}', node.location:format()) + elseif node.kind == "LambdaFunctionLiteral" then + outp:addLine('function(', node.location:format()) + + for i = 1,#node.content[1] do + local part = node.content[1][i] + + outp:addLine(part, node.location:format()) + + if i ~= #node.content[1] then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + + Debug.outputStatement(outp, node.content[2]) + + outp:addLine("end ", node.location:format()) + elseif node.kind == "BinaryOperator" then + local op = node.content[1] + local lhs = node.content[2] + local rhs = node.content[3] + + outp:addLine("((", node.location:format()) + + Debug.outputExpression(outp,lhs) + + outp:addLine(")", node.location:format()) + + local luaop = Debug.convertBinaryOperator(op) + + outp:addLine(luaop, node.location:format()) + + outp:addLine("(", node.location:format()) + + Debug.outputExpression(outp,rhs) + + outp:addLine("))", node.location:format()) + elseif node.kind == "UnaryOperator" then + local op = node.content[1] + local val = node.content[2] + + outp:addLine("(", node.location:format()) + + local luaop = Debug.convertUnaryOperator(op) + outp:addLine(luaop, node.location:format()) + + + outp:addLine("(", node.location:format()) + + Debug.outputExpression(outp,val) + + outp:addLine("))", node.location:format()) + elseif node.kind == "Field" then + local left = node.content[1] + local right = node.content[2] + + outp:addLine("((", node.location:format()) + Debug.outputExpression(outp, left) + outp:addLine(").", node.location:format()) + outp:addLine(right, node.location:format()) + outp:addLine(")", node.location:format()) + elseif node.kind == "Index" then + local left = node.content[1] + local right = node.content[2] + + outp:addLine("((", node.location:format()) + Debug.outputExpression(outp, left) + outp:addLine(")", node.location:format()) + outp:addLine("[", node.location:format()) + Debug.outputExpression(outp, right) + outp:addLine("]", node.location:format()) + outp:addLine(")", node.location:format()) + elseif node.kind == "Variable" then + outp:addLine("(", node.location:format()) + outp:addLine(node.content, node.location:format()) + outp:addLine(")", node.location:format()) + elseif node.kind == "BooleanLiteral" then + outp:addLine("(", node.location:format()) + outp:addLine(node.content, node.location:format()) + outp:addLine(")", node.location:format()) + elseif node.kind == "NullLiteral" then + outp:addLine("(nil)", node.location:format()) + elseif node.kind == "Call" then + outp:addLine("(", node.location:format()) + Debug.outputExpression(outp, node.content[1]) + outp:addLine(")(", node.location:format()) + + local args = node.content[2] + for i = 1,#args do + local arg = args[i] + + Debug.outputExpression(outp, arg) + + if i ~= #args then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + elseif node.kind == "MethodCall" then + outp:addLine("(", node.location:format()) + Debug.outputExpression(outp, node.content[1]) + outp:addLine("):", node.location:format()) + outp:addLine(node.content[2] .. "(", node.location:format()) + + local args = node.content[3] + for i = 1,#args do + local arg = args[i] + + Debug.outputExpression(outp, arg) + + if i ~= #args then + outp:addLine(",", node.location:format()) + end + end + + outp:addLine(")", node.location:format()) + end +end + +---@param outp Output +---@param node Node +function Debug.outputFunctionName(outp, node) + if node.kind == "Field" then + local left = node.content[1] + local right = node.content[2] + + Debug.outputFunctionName(outp, left) + outp:addLine(".", node.location:format()) + outp:addLine(right, node.location:format()) + elseif node.kind == "Index" then + local left = node.content[1] + local right = node.content[2] + + Debug.outputFunctionName(outp, left) + outp:addLine("[", node.location:format()) + Debug.outputExpression(outp, right) + outp:addLine("]", node.location:format()) + elseif node.kind == "Variable" then + outp:addLine(node.content, node.location:format()) + elseif node.kind == "Method" then + local left = node.content[1] + local right = node.content[2] + + Debug.outputFunctionName(outp, left) + outp:addLine(":", node.location:format()) + outp:addLine(right, node.location:format()) + end +end + +---@param outp Output +---@param node Node +function Debug.outputVariable(outp, node) + if node.kind == "Field" then + local left = node.content[1] + local right = node.content[2] + + Debug.outputExpression(outp, left) + outp:addLine(".", node.location:format()) + outp:addLine(right, node.location:format()) + elseif node.kind == "Index" then + local left = node.content[1] + local right = node.content[2] + + Debug.outputExpression(outp, left) + outp:addLine("[", node.location:format()) + Debug.outputExpression(outp, right) + outp:addLine("]", node.location:format()) + elseif node.kind == "Variable" then + outp:addLine(node.content, node.location:format()) + end +end + +RegisterOutMethod("debug", Debug) diff --git a/src/methods/small.lua b/src/methods/small.lua new file mode 100644 index 0000000..51eaf63 --- /dev/null +++ b/src/methods/small.lua @@ -0,0 +1,719 @@ +Small = {} + +function Small.isDigit(ch) + local n = string.byte(ch) + if not n then return false end + return n >= 48 and n <= 57 +end + +function Small.encodeString(raw, islua51) + local out = '"' + + for i = 1,#raw do + local ch = raw:sub(i,i) + + local code = string.byte(ch) + if ch == '\\' then + out = out .. "\\\\" + elseif ch == '"' then + out = out .. '\\"' + elseif ch == "\n" then + out = out .. "\\n" + elseif ch == '\r' then + out = out .. "\\r" + elseif ch == "\a" then + out = out .. "\\a" + elseif ch == "\b" then + out = out .. "\\b" + elseif ch == '\f' then + out = out .. "\\f" + elseif ch == "\t" then + out = out .. "\\t" + elseif code >= 32 and code <= 126 then + out = out .. ch + else + local nextchar = raw:sub(i+1,i+1) + if Small.isDigit(nextchar) then + out = out .. "\\" .. DecEncode(code, 3) + else + out = out .. "\\" .. DecEncode(code, 1) + end + end + end + + out = out .. '"' + + return out +end + +function Small.encodeNumber(n) + local num = '' + local isneg = false + + if n < 0 then + n = n * -1 + isneg = true; + end + + local whole = math.floor(n) + + if whole == 0 then + num = num .. '0' + end + + while whole > 0 do + local digit = whole % 10 + num = string.char(string.byte'0' + digit) .. num + + whole = math.floor(whole / 10) + end + + local dec = n % 1 + + if dec > 0 then + num = num .. "." + + while dec > 0 do + -- aw nah + dec = dec * 10 + + local digit = math.floor(dec) + + num = num .. string.char(string.byte'0' + digit) + + dec = dec % 1 + end + end + + if isneg then + num = '-' .. num + end + + return num +end + +function Small.convertBinaryOperator(lakeop, spacer) + if lakeop == "+" then + return "+" + elseif lakeop == "-" then + return "-" + elseif lakeop == "*" then + return "*" + elseif lakeop == "/" then + return "/" + elseif lakeop == "%" then + return "%" + elseif lakeop == "^" then + return "^" + elseif lakeop == ".." then + return ".." + elseif lakeop == "<" then + return "<" + elseif lakeop == ">" then + return ">" + elseif lakeop == "<=" then + return "<=" + elseif lakeop == ">=" then + return ">=" + elseif lakeop == "!=" then + return "~=" + elseif lakeop == "==" then + return "==" + elseif lakeop == "&&" then + return spacer and ' and ' or "and" + elseif lakeop == "||" then + return spacer and ' or ' or "or" + end +end + +function Small.convertUnaryOperator(op) + if op == "#" then + return "#" + elseif op == "-" then + return "-" + elseif op == "!" then + return "not" + end +end + +function Small.output(outp, ast) + Small.outputStatement(outp, ast) +end + +---@param outp Output +---@param node Node +function Small.outputStatement(outp, node, noblock) + if node.kind == "Program" then + outp:appendLine("do ", '') + + for i = 1,#node.content do + local stmt = node.content[i] + + Small.outputStatement(outp, stmt) + end + + outp:appendLine("end;", '') + elseif node.kind == "VariableDeclaration" then + outp:appendLine("local ", '') + local varnames = node.content[1] + + for i = 1,#varnames do + local vname = varnames[i] + + outp:appendLine(vname[1], '') + + if i ~= #varnames then + outp:appendLine(",", '') + end + end + + local values = node.content[2] + if #values > 0 then + outp:appendLine("=", '') + + for i = 1,#values do + local val = values[i] + + Small.outputExpression(outp, val, false) + + if i ~= #values then + outp:appendLine(",", '') + end + end + end + + outp:appendLine(";", '') + elseif node.kind == "Block" then + if not noblock then + outp:appendLine("do ", '') + end + + for i = 1,#node.content do + local stmt = node.content[i] + + Small.outputStatement(outp, stmt, #node.content == 1) + end + + if not noblock then + outp:appendLine("end;", '') + end + elseif node.kind == "IfStatement" then + outp:appendLine("if ", '') + + Small.outputExpression(outp, node.content[1][1][1]) + + outp:appendLine("then ", '') + + Small.outputStatement(outp, node.content[1][1][2], true) + + for i = 2, #node.content[1] do + local item = node.content[1][i] + -- print(item, item[1], item[2]) + outp:appendLine("elseif ", '') + + Small.outputExpression(outp,item[1]); + + outp:appendLine("then ", '') + + Small.outputStatement(outp,item[2], true); + end + + if node.content[2] then + outp:appendLine("else ", '') + + Small.outputStatement(outp, node.content[2], true) + end + + outp:appendLine("end;", '') + elseif node.kind == "FunctionDeclaration" then + outp:appendLine("function ", '') + + Small.outputFunctionName(outp, node.content[1]); + + outp:appendLine("(", '') + + for i = 1,#node.content[2] do + local part = node.content[2][i] + + outp:appendLine(part, '') + + if i ~= #node.content[2] then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + + Small.outputStatement(outp, node.content[3], true) + + outp:appendLine("end;", '') + elseif node.kind == "LocalFunctionDeclaration" then + outp:appendLine("local function ", '') + + outp:appendLine(node.content[1].content, ''); + + outp:appendLine("(", '') + + for i = 1,#node.content[2] do + local part = node.content[2][i] + + outp:appendLine(part, '') + + if i ~= #node.content[2] then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + + Small.outputStatement(outp, node.content[3], true) + + outp:appendLine("end;", '') + elseif node.kind == "Return" then + if #node.content > 0 then + outp:appendLine("return ", '') + else + outp:appendLine('return', '') + end + + for i = 1,#node.content do + local expr = node.content[i] + + Small.outputExpression(outp, expr, false) + + if i ~= #node.content then + outp:appendLine(",", '') + end + end + + outp:appendLine(";", '') + elseif node.kind == "Assignment" then + local vars = node.content[1] + local vals = node.content[2] + + for i = 1,#vars do + local var = vars[i] + + Small.outputVariable(outp, var) + + if i ~= #vars then + outp:appendLine(",", '') + end + end + + outp:appendLine("=", '') + + for i = 1,#vals do + local val = vals[i] + + Small.outputExpression(outp, val, false) + + if i ~= #vals then + outp:appendLine(",", '') + end + end + + outp:appendLine(";", '') + elseif node.kind == "Call" then + outp:appendLine("(", '') + Small.outputExpression(outp, node.content[1], false) + outp:appendLine(")", '') + + outp:appendLine("(", '') + + local args = node.content[2] + for i = 1,#args do + local arg = args[i] + + Small.outputExpression(outp, arg, false) + + if i ~= #args then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + outp:appendLine(";", '') + elseif node.kind == "MethodCall" then + outp:appendLine("(", '') + Small.outputExpression(outp, node.content[1], false) + outp:appendLine("):", '') + outp:appendLine(node.content[2], '') + outp:appendLine("(", '') + + local args = node.content[3] + for i = 1,#args do + local arg = args[i] + + Small.outputExpression(outp, arg, false) + + if i ~= #args then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + outp:appendLine(";", '') + elseif node.kind == "While" then + outp:appendLine("while ", '') + + Small.outputExpression(outp, node.content[1]) + + outp:appendLine("do ", '') + + Small.outputStatement(outp, node.content[2], true) + + outp:appendLine("end;", '') + elseif node.kind == "For" then + outp:appendLine("do ", '') + Small.outputStatement(outp,node.content[1]); + + outp:appendLine("while ", '') + Small.outputExpression(outp,node.content[2]); + outp:appendLine("do ", '') + + outp:appendLine("do ", '') + Small.outputStatement(outp,node.content[4], true); + outp:appendLine("end;", '') + + Small.outputStatement(outp,node.content[3], true); + + outp:appendLine("end;", '') + + outp:appendLine("end;", '') + elseif node.kind == "OperatorAssignment" then + local lake_op = node.content[2] + local op = Small.convertBinaryOperator(lake_op, true) + + local lhs = node.content[1] + local rhs = node.content[3] + + if lhs.kind == "Variable" then + local varname = lhs.content + + outp:appendLine(varname, '') + outp:appendLine('=', '') + outp:appendLine(varname, '') + outp:appendLine(op, '') + outp:appendLine('(', '') + Small.outputExpression(outp, rhs, false) + outp:appendLine(');', '') + elseif lhs.kind == "Field" then + outp:appendLine('do ', '') + + outp:appendLine('local _lake_oat=', '') + Small.outputExpression(outp, lhs.content[1], false) + outp:appendLine(';', ''); + + outp:appendLine('_lake_oat.', '') + outp:appendLine(lhs.content[2], '') + + outp:appendLine('=', '') + + outp:appendLine('_lake_oat.', '') + outp:appendLine(lhs.content[2], '') + outp:appendLine(op, '') + outp:appendLine('(', '') + Small.outputExpression(outp, rhs, false) + outp:appendLine(');', '') + + outp:appendLine('end;', '') + elseif lhs.kind == "Index" then + outp:appendLine('do ', '') + + outp:appendLine('local _lake_oat=', '') + Small.outputExpression(outp, lhs.content[1], false) + outp:appendLine(';', ''); + + outp:appendLine('_lake_oat[', '') + Small.outputExpression(outp, lhs.content[2], false) + outp:appendLine(']', '') + + outp:appendLine('=', '') + + outp:appendLine('_lake_oat[', '') + Small.outputExpression(outp, lhs.content[2], false) + outp:appendLine(']', '') + outp:appendLine(op, '') + outp:appendLine('(', '') + Small.outputExpression(outp, rhs, false) + outp:appendLine(');', '') + + outp:appendLine('end;', '') + end + elseif node.kind == "Break" then + outp:appendLine('break;', '') + elseif node.kind == "Foreach" then + outp:appendLine("for ", '') + + local varlist,exprlist,block = node.content[1],node.content[2],node.content[3] + + for i = 1,#varlist do + outp:appendLine(varlist[i], '') + if i ~= #varlist then + outp:appendLine(',', '') + end + end + + outp:appendLine(" in ", '') + + for i = 1,#exprlist do + Small.outputExpression(outp, exprlist[i], i == #exprlist) + + if i ~= #exprlist then + outp:appendLine(",", '') + end + end + + outp:appendLine('do ', '') + + Small.outputStatement(outp, block, true); + + outp:appendLine('end;', '') + end +end + +---@param outp Output +---@param node Node +function Small.outputExpression(outp, node, needsp) + if needsp == nil then needsp = true end + if node.kind == "StringLiteral" then + local lakestr = node.content + + local raw = Lower.decodeString(lakestr) + + local enc = Small.encodeString(raw, outp.target == "lua51") + + outp:appendLine(enc, '') + elseif node.kind == "ParenthesizedExpression" then + outp:appendLine("(", '') + Small.outputExpression(outp, node.content, false) + outp:appendLine(")", '') + elseif node.kind == "NumberLiteral" then + local lakenum = node.content + + local raw = Lower.decodeNumber(lakenum) + + -- print(raw, type(raw)) + + local enc = Small.encodeNumber(raw) + + outp:appendLine(enc, '') + if needsp then + outp:appendLine(' ', '') + end + elseif node.kind == "TableLiteral" then + outp:appendLine('{', '') + local list_part = node.content[1] + local table_part = node.content[2] + + for i = 1,#list_part do + local item = list_part[i] + + Small.outputExpression(outp, item, false); + + if (#table_part > 0) or (i < #list_part) then + outp:appendLine(',', '') + end + end + + for i = 1,#table_part do + local item = table_part[i] + + outp:appendLine('[', '') + Small.outputExpression(outp, item[1], false) + outp:appendLine(']', '') + outp:appendLine('=', '') + Small.outputExpression(outp, item[2], false) + if (i < #table_part) then + outp:appendLine(',', '') + end + end + outp:appendLine('}', '') + elseif node.kind == "LambdaFunctionLiteral" then + outp:appendLine('function(', '') + + for i = 1,#node.content[1] do + local part = node.content[1][i] + + outp:appendLine(part, '') + + if i ~= #node.content[1] then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + + Small.outputStatement(outp, node.content[2], true) + + outp:appendLine("end", '') + if needsp then + outp:appendLine(' ', '') + end + elseif node.kind == "BinaryOperator" then + local op = node.content[1] + local lhs = node.content[2] + local rhs = node.content[3] + + outp:appendLine("((", '') + + Small.outputExpression(outp,lhs, false) + + outp:appendLine(")", '') + + local luaop = Small.convertBinaryOperator(op) + + outp:appendLine(luaop, '') + + outp:appendLine("(", '') + + Small.outputExpression(outp,rhs,false) + + outp:appendLine("))", '') + elseif node.kind == "UnaryOperator" then + local op = node.content[1] + local val = node.content[2] + + outp:appendLine("(", '') + + local luaop = Small.convertUnaryOperator(op) + outp:appendLine(luaop, '') + + + outp:appendLine("(", '') + + Small.outputExpression(outp,val,false) + + outp:appendLine("))", '') + elseif node.kind == "Field" then + local left = node.content[1] + local right = node.content[2] + + outp:appendLine("(", '') + Small.outputExpression(outp, left, false) + outp:appendLine(").", '') + outp:appendLine(right, '') + if needsp then + outp:appendLine(' ', '') + end + elseif node.kind == "Index" then + local left = node.content[1] + local right = node.content[2] + + outp:appendLine("(", '') + Small.outputExpression(outp, left, false) + outp:appendLine(")", '') + outp:appendLine("[", '') + Small.outputExpression(outp, right, false) + outp:appendLine("]", '') + elseif node.kind == "Variable" then + outp:appendLine(node.content, '') + if needsp then + outp:appendLine(' ', '') + end + elseif node.kind == "BooleanLiteral" then + outp:appendLine(node.content, '') + if needsp then + outp:appendLine(' ', '') + end + elseif node.kind == "NullLiteral" then + outp:appendLine("nil", '') + if needsp then + outp:appendLine(' ', '') + end + elseif node.kind == "Call" then + outp:appendLine("(", '') + Small.outputExpression(outp, node.content[1], false) + outp:appendLine(")", '') + + outp:appendLine("(", '') + + local args = node.content[2] + for i = 1,#args do + local arg = args[i] + + Small.outputExpression(outp, arg, false) + + if i ~= #args then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + elseif node.kind == "MethodCall" then + outp:appendLine("(", '') + Small.outputExpression(outp, node.content[1], false) + outp:appendLine("):", '') + outp:appendLine(node.content[2], '') + outp:appendLine("(", '') + + local args = node.content[3] + for i = 1,#args do + local arg = args[i] + + Small.outputExpression(outp, arg, false) + + if i ~= #args then + outp:appendLine(",", '') + end + end + + outp:appendLine(")", '') + end +end + +---@param outp Output +---@param node Node +function Small.outputFunctionName(outp, node) + if node.kind == "Field" then + local left = node.content[1] + local right = node.content[2] + + Small.outputFunctionName(outp, left) + outp:appendLine(".", '') + outp:appendLine(right, '') + elseif node.kind == "Index" then + local left = node.content[1] + local right = node.content[2] + + Small.outputFunctionName(outp, left) + outp:appendLine("[", '') + Small.outputExpression(outp, right, false) + outp:appendLine("]", '') + elseif node.kind == "Variable" then + outp:appendLine(node.content, '') + elseif node.kind == "Method" then + local left = node.content[1] + local right = node.content[2] + + Small.outputFunctionName(outp, left) + outp:appendLine(":", '') + outp:appendLine(right, '') + end +end + +---@param outp Output +---@param node Node +function Small.outputVariable(outp, node) + if node.kind == "Field" then + local left = node.content[1] + local right = node.content[2] + + Small.outputExpression(outp, left, false) + outp:appendLine(".", '') + outp:appendLine(right, '') + elseif node.kind == "Index" then + local left = node.content[1] + local right = node.content[2] + + Small.outputExpression(outp, left, false) + outp:appendLine("[", '') + Small.outputExpression(outp, right, false) + outp:appendLine("]", '') + elseif node.kind == "Variable" then + outp:appendLine(node.content, '') + end +end + +RegisterOutMethod("small", Small) diff --git a/src/output.lua b/src/output.lua new file mode 100644 index 0000000..f2cb509 --- /dev/null +++ b/src/output.lua @@ -0,0 +1,46 @@ +OutputMethods = {} + +function RegisterOutMethod(name, data) + OutputMethods[name] = data +end + +---@class Output +---@field target string +---@field method string +---@field lines string[] +---@field debug string[] +Output = {} + +function Output:new(target, method) + return setmetatable({ + target = target, + method = method, + lines = {}, + debug = {} + }, {__index = self}) +end + +function Output:output(ast) + local method = OutputMethods[self.method] + assert(method) + + method.output(self, ast) +end + +function Output:addLine(line, debuginfo) + self.lines[#self.lines+1] = line + self.debug[#self.debug+1] = debuginfo +end + +function Output:appendLine(data, debuginfo) + if not self.lines[#self.lines] then + self.lines[#self.lines+1] = "" + end + self.lines[#self.lines] = self.lines[#self.lines] .. data + + if not debuginfo then print(data) end + if not self.debug[#self.debug] then + self.debug[#self.debug+1] = "" + end + self.debug[#self.debug] = self.debug[#self.debug] .. debuginfo +end \ No newline at end of file diff --git a/src/parser.lua b/src/parser.lua new file mode 100644 index 0000000..59d51f4 --- /dev/null +++ b/src/parser.lua @@ -0,0 +1,1373 @@ +---@class Parser +---@field lexer Lexer +Parser = {} + +function Parser:new(lexer) + return setmetatable({ + lexer = lexer + }, {__index = self}) +end + +function Parser:parse() + local parts = {} + local start + + while true do + local tok, err = self.lexer:peek() + if not tok then + return nil, err + end + + if not start then + start = tok.location:clone() + end + + if tok.kind == "eof" then + break + end + + local node, nerr = self:parseStatement() + if not node then + return nil, nerr + end + + parts[#parts+1] = node + end + + return Node:new("Program", parts, start) +end + +function Parser:parseBlockOrStatement() + local checktok, checkerr = self.lexer:peek() + if not checktok then + return nil, checkerr + end + + if checktok.kind == "symbol" and checktok.content == "{" then + local block, berr = self:parseBlock() + if not block then + return nil, berr + end + + return block + end + + local stmt, sterr = self:parseStatement() + if not stmt then + return nil, sterr + end + + return stmt; +end + +function Parser:parseBlock() + local starttok, sterr = self.lexer:peek() + if not starttok then + return nil, sterr + end + + if starttok.kind ~= "symbol" or starttok.content ~= "{" then + return nil, Error:new("expected { to start block", "parser", starttok.location:clone()) + end + self.lexer:consume() + + local parts = {} + + while true do + local detect, deterr = self.lexer:peek() + if not detect then + return nil, deterr + end + + if detect.kind == "symbol" and detect.content == "}" then + break; + end + + if detect.kind == "eof" and detect.content == "eof" then + return nil, Error:new("unexpected eof in a block", "parser", detect.location:clone()) + end + + local stmt, stmterr = self:parseStatement() + if not stmt then + return nil, stmterr + end + + parts[#parts+1] = stmt + end + + local check, cherr = self.lexer:peek() + if not check then + return nil, cherr + end + + if check.kind ~= "symbol" or check.content ~= "}" then + return nil, Error:new("block ended in an invalid way", "parser", check.location:clone()) + end + self.lexer:consume() + + return Node:new("Block", parts, starttok.location:clone()) +end + +function Parser:parseFunctionName() + local tok,terr = self.lexer:peek() + if not tok then + return nil, terr + end + + if tok.kind ~= "identifier" then + return nil, Error:new("expected function name to start with identifier", "parser", tok.location:clone()) + end + self.lexer:consume() + + local node = Node:new('Variable', tok.content, tok.location:clone()) + + while true do -- woohoo more shit + local check, checkerr = self.lexer:peek() + if not check then + return nil, checkerr + end + + if check.kind == "symbol" and check.content == "." then + self.lexer:consume() + + local idx,idxerr = self.lexer:peek() + if not idx then + return nil, idxerr + end + + if idx.kind ~= "identifier" then + return nil, Error:new("expected identifier after . in function name", 'parser', idx.location:clone()) + end + self.lexer:consume() + + node = Node:new('Field', {node, idx.content}, check.location:clone()); + elseif check.kind == "symbol" and check.content == "[" then + self.lexer:consume() + + local expr,err = self:parseExpression() + if not expr then + return nil, err + end + + local endcheck, endcheckerr = self.lexer:peek() + if not endcheck then + return nil, endcheckerr + end + + if endcheck.kind ~= "symbol" or endcheck.content ~= "]" then + return nil, Error:new("expected ] to close indexing in function name", 'parser', endcheck.location:clone()) + end + self.lexer:consume() + + node = Node:new('Index', {node, expr}, check.location:clone()) + elseif check.kind == "symbol" and check.content == ":" then + self.lexer:consume() + + local methodname, methoderr = self.lexer:peek() + if not methodname then + return nil, methoderr + end + + if methodname.kind ~= "identifier" then + return nil, Error:new("expected identifier for method name in function declaration", 'parser', methodname.location:clone()) + end + self.lexer:consume() + + node = Node:new("Method", {node, methodname.content}, check.location:clone()) + break; -- can't have more afterward + else + break; + end + end + + return node; +end + +function Parser:parseStatement() + local tok, err = self.lexer:peek() + if not tok then + return nil, err + end + + if tok.kind == "keyword" and tok.content == "var" then + -- var decl + + -- get rid of keyw + self.lexer:consume() + + local checker,checkererr = self.lexer:peek() + if not checker then + return nil, checkererr + end + + if checker.kind == "keyword" and checker.content == "fn" then + self.lexer:consume() + + local fnname, fnnerr = self.lexer:peek() + if not fnname then + return nil, fnnerr + end + + if fnname.kind ~= "identifier" then + return nil, Error:new("expected identifier function name after var fn", "parser", fnname.location:clone()); + end + self.lexer:consume() + + local paren, parerr = self.lexer:peek() + if not paren then + return nil, parerr + end + + if paren.kind ~= "symbol" or paren.content ~= "(" then + return nil, Error:new("expected ( to open parameter list for local function") + end + self.lexer:consume() + + local params = {} + + while true do + local checktok, checkerr = self.lexer:peek() + if not checktok then + return nil, checkerr + end + + if checktok.kind == "symbol" and checktok.content == ")" then + break; + elseif checktok.kind == "identifier" then + params[#params+1] = checktok.content + self.lexer:consume() + elseif checktok.kind == "eof" and checktok.content == "eof" then + break; + end + + local nexttok, nexterr = self.lexer:peek() + if not nexttok then + return nil, nexterr + end + + if nexttok.kind ~= "symbol" or nexttok.content ~= "," then + break; + end + self.lexer:consume() + end + + local clostok, closerr = self.lexer:peek() + if not clostok then + return nil, closerr + end + + if clostok.kind ~= "symbol" or clostok.content ~= ")" then + return nil, Error:new("no closing parenthesis after local function parameters", "parser", clostok.location:clone()) + end + self.lexer:consume() + + local block, berr = self:parseBlockOrStatement() + if not block then + return nil, berr + end + + return Node:new("LocalFunctionDeclaration", {fnname, params, block}, tok.location:clone()) + end + + -- varnames + local varnames = {} + while true do + local vntok, vnerr = self.lexer:peek() + if not vntok then + return nil, vnerr + end + + if vntok.kind == "identifier" then + self.lexer:consume() + varnames[#varnames+1] = {vntok.content, vntok.location:clone()} + else + return nil, Error:new("not an identifier in varname list in declaration", "parser", vntok.location:clone()) + end + + local commatok, commaerr = self.lexer:peek() + if not commatok then + return nil, commaerr + end + + if commatok.kind == "symbol" and commatok.content == "," then + self.lexer:consume() + else + break + end + end + + local splittok, spliterr = self.lexer:peek() + if not splittok then + return nil, spliterr + end + + if splittok.kind ~= "symbol" then + return nil, Error:new("expected = or ; after variable names in declaration", "parser", splittok.location:clone()) + end + + if splittok.content == ";" then + -- get rid + self.lexer:consume() + + return Node:new("VariableDeclaration", {varnames, {}}, tok.location:clone()) + end + + if splittok.content == "=" then + -- get rid + self.lexer:consume() + + local values = {} + + while true do + local expr, exerr = self:parseExpression() + if not expr then + return nil, exerr + end + + values[#values+1] = expr + + local commatok, commaerr = self.lexer:peek() + if not commatok then + return nil, commaerr + end + + if commatok.kind == "symbol" and commatok.content == "," then + self.lexer:consume() + else + break + end + end + + local smc, smcerr = self.lexer:peek() + if not smc then + return nil, smcerr + end + + if smc.kind ~= "symbol" or smc.content ~= ";" then + return nil, Error:new("expected semicolon after variable declaration", "parser", smc.location:clone()) + end + + self.lexer:consume() + + return Node:new("VariableDeclaration", {varnames, values}, tok.location:clone()) + end + + return nil, Error:new("expected = or ; after variable names in declaration", "parser", splittok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "if" then + self.lexer:consume() + + local checks = {} + + local partok, parerr = self.lexer:peek() + if not partok then + return nil,parerr + end + + if partok.kind ~= "symbol" or partok.content ~= "(" then + return nil, Error:new("expected ( after if keyword", "parser", partok.location:clone()) + end + self.lexer:consume() + + local condition,conderr = self:parseExpression() + if not condition then + return nil, conderr + end + + local closepart, closeparerr = self.lexer:peek() + if not closepart then + return nil, closeparerr + end + + if closepart.kind ~= "symbol" or closepart.content ~= ")" then + return nil, Error:new("expected ) to close if statement condition", 'parser', closepart.location:clone()) + end + self.lexer:consume() + + local body, bodyerr = self:parseBlockOrStatement() + if not body then + return nil, bodyerr + end + + checks[#checks+1] = {condition,body} + + -- elseifs + + while true do + local checkt, checkerr = self.lexer:peek() + if not checkt then + return nil, checkerr + end + + if checkt.kind == "keyword" and checkt.content == "elseif" then + self.lexer:consume() + + local partok2, parerr2 = self.lexer:peek() + if not partok2 then + return nil,parerr2 + end + + if partok2.kind ~= "symbol" or partok2.content ~= "(" then + return nil, Error:new("expected ( after elseif keyword", "parser", partok2.location:clone()) + end + self.lexer:consume() + + local condition2,conderr2 = self:parseExpression() + if not condition2 then + return nil, conderr2 + end + + local closepart2, closeparerr2 = self.lexer:peek() + if not closepart2 then + return nil, closeparerr2 + end + + if closepart2.kind ~= "symbol" or closepart2.content ~= ")" then + return nil, Error:new("expected ) to close elseif statement condition", closepart2.location:clone()) + end + self.lexer:consume() + + local block,berr = self:parseBlockOrStatement(); + if not block then + return nil, berr + end + + checks[#checks+1] = {condition2, block} + else + break; + end + end + + local elset, elseerr = self.lexer:peek() + if not elset then + return nil, elseerr + end + + local elseblock; + if elset.kind == "keyword" and elset.content == "else" then + self.lexer:consume() + + local elseb, elseberr = self:parseBlockOrStatement(); + if not elseb then + return nil, elseberr + end + + elseblock = elseb + end + + return Node:new("IfStatement", {checks, elseblock}, tok.location:clone()); + elseif tok.kind == "keyword" and tok.content == "while" then + self.lexer:consume() + + local paren, perr = self.lexer:peek() + if not paren then + return nil, perr + end + + if paren.kind ~= "symbol" or paren.content ~= "(" then + return nil, Error:new("expected ( after start of while", "parser", paren.location:clone()) + end + self.lexer:consume() + + local cond, conderr = self:parseExpression() + if not cond then + return nil, conderr + end + + local closep, closeperr = self.lexer:peek() + if not closep then + return nil, closeperr + end + + if closep.kind ~= "symbol" or closep.content ~= ")" then + return nil, Error:new("expected ) to close condition in while loop", 'parser', closep.location:clone()) + end + self.lexer:consume() + + local block,berr = self:parseBlockOrStatement() + if not block then + return nil, berr + end + + return Node:new("While", {cond, block}, tok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "for" then + self.lexer:consume() + + local paren, perr = self.lexer:peek() + if not paren then + return nil, perr + end + + if paren.kind ~= "symbol" or paren.content ~= "(" then + return nil, Error:new("expected ( after start of while", "parser", paren.location:clone()) + end + self.lexer:consume() + + local decl, declerr = self:parseStatement(); -- auto removes semi!! + if not decl then + return nil, declerr + end + + local check, checkerr = self:parseExpression(); + if not check then + return nil, checkerr + end + + local semi, semierr = self.lexer:peek() + if not semi then + return nil, semierr + end + + if semi.kind ~= "symbol" or semi.content ~= ";" then + return nil, Error:new('expected semicolon after condition in for loop', 'parser', semi.location:clone()) + end + self.lexer:consume() + + local modifier, moderr = self:parseStatement(); -- also removes a semi -> third semi is required! + if not modifier then + return nil, moderr + end + + local close, closeerr = self.lexer:peek(); + if not close then + return nil, closeerr + end + + if close.kind ~= "symbol" or close.content ~= ")" then + return nil, Error:new("expected ) to close condition part of for loop", 'parser', close.location:clone()) + end + self.lexer:consume() + + local block, berr = self:parseBlockOrStatement() + if not block then + return nil, berr + end + + return Node:new("For", {decl, check, modifier, block}, tok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "fn" then + self.lexer:consume() + + local fnname, fnnerr = self:parseFunctionName() + if not fnname then + return nil, fnnerr + end + + local paren, parerr = self.lexer:peek() + if not paren then + return nil, parerr + end + + if paren.kind ~= "symbol" or paren.content ~= "(" then + return nil, Error:new("expected ( to open parameter list for function", 'lexer', paren.location:clone()) + end + self.lexer:consume() + + local params = {} + + while true do + local checktok, checkerr = self.lexer:peek() + if not checktok then + return nil, checkerr + end + + if checktok.kind == "symbol" and checktok.content == ")" then + break; + elseif checktok.kind == "identifier" then + params[#params+1] = checktok.content + self.lexer:consume() + elseif checktok.kind == "eof" and checktok.content == "eof" then + break; + end + + local nexttok, nexterr = self.lexer:peek() + if not nexttok then + return nil, nexterr + end + + if nexttok.kind ~= "symbol" or nexttok.content ~= "," then + break; + end + self.lexer:consume() + end + + local clostok, closerr = self.lexer:peek() + if not clostok then + return nil, closerr + end + + if clostok.kind ~= "symbol" or clostok.content ~= ")" then + return nil, Error:new("no closing parenthesis after function parameters", "parser", clostok.location:clone()) + end + self.lexer:consume() + + local block, berr = self:parseBlockOrStatement() + if not block then + return nil, berr + end + + return Node:new("FunctionDeclaration", {fnname, params, block}, tok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "return" then + self.lexer:consume() + + local parts = {} + + while true do + local chtok, cherr = self.lexer:peek() + if not chtok then + return nil, cherr + end + + if chtok.kind == "symbol" and chtok.content == ";" then + break; + end + + local expr, exprerr = self:parseExpression() + if not expr then + return nil, exprerr + end + + parts[#parts+1] = expr + + local checkt, checkerr = self.lexer:peek() + if not checkt then + return nil, checkerr + end + + if checkt.kind ~= "symbol" or checkt.content ~= "," then + break; + end + + self.lexer:consume() + end + + local semi, semierr = self.lexer:peek() + if not semi then + return nil, semierr + end + + if semi.kind ~= 'symbol' or semi.content ~= ';' then + return nil, Error:new("expected ; after return statement", "parser", semi.location:clone()) + end + self.lexer:consume() + + return Node:new('Return', parts, tok.location:clone()); + elseif tok.kind == "keyword" and tok.content == "do" then + self.lexer:consume() + + local block, berr = self:parseBlockOrStatement() + if not block then + return nil, berr + end + + if block.kind == "Block" then + return block; + else + return Node:new("Block", {block}, tok.location:clone()) + end + elseif (tok.kind == "symbol" and tok.content == "(") or tok.kind == "identifier" then + local expr, exerr = self:parseExpression() + if not expr then + return nil, exerr + end + + local nextok, nexerr = self.lexer:peek() + if not nextok then + return nil, nexerr + end + + if nextok.kind == "symbol" and (nextok.content == "," or nextok.content == "=") then + -- assignment + + local vars = {expr} + + while true do + local tkn, tknerr = self.lexer:peek() + if not tkn then + return nil, tknerr + end + + if tkn.kind == "symbol" and tkn.content == "," then + self.lexer:consume() + + local checkt, checkerr = self.lexer:peek() + if not checkt then + return nil, checkerr + end + + if (checkt.kind == "symbol" and checkt.content == "(") or checkt.kind == "identifier" then + local nexpr, nerr = self:parseExpression() + if not nexpr then + return nil, nerr + end + + vars[#vars+1] = nexpr + else + return nil, Error:new("expected expression starting with ( or an identifier after comma in assigment!", 'parser', checkt.location:clone()) + end + else + break; + end + end + + local equaltok, equalerr = self.lexer:peek() + + if not equaltok then + return nil, equalerr + end + + if equaltok.kind ~= "symbol" or equaltok.content ~= "=" then + return nil, Error:new("expected = in assignment", 'parser', equaltok.location:clone()) + end + self.lexer:consume() -- get rid of = + + local values = {} + + while true do + local assexpr, asserr = self:parseExpression() + if not assexpr then + return nil, asserr + end + + values[#values+1] = assexpr; + + local commatok, commaerr = self.lexer:peek() + if not commatok then + return nil, commaerr + end + + if commatok.kind == "symbol" and commatok.content == "," then + self.lexer:consume() + else + break; + end + end + + local semi,semierr = self.lexer:peek() + if not semi then + return nil, semierr + end + + if semi.kind ~= "symbol" or semi.content ~= ";" then + return nil, Error:new("expected semicolon after assignment", 'parser', semi.location:clone()) + end + self.lexer:consume() + + return Node:new('Assignment', {vars, values}, equaltok.location:clone()); + elseif nextok.kind == "symbol" and (nextok.content == '&&=' or nextok.content == "||=" or nextok.content == "+=" or nextok.content == "-=" or nextok.content == "*=" or nextok.content == "/=" or nextok.content == "%=" or nextok.content == "^=" or nextok.content == "..=") then + self.lexer:consume() + local operator = nextok.content:sub(1,-2) -- just the thing but without the =, whould work fine for all these cases + + if expr.kind ~= "Variable" and expr.kind ~= "Field" and expr.kind ~= "Index" then + return nil, Error:new("can only use operator assignment on variables, fields or indexes", "parser", expr.location:clone()) + end + + local rhs,rhserr = self:parseExpression() + if not rhs then + return nil, rhserr + end + + local semi,semierr = self.lexer:peek() + if not semi then + return nil, semierr + end + + if semi.kind ~= "symbol" or semi.content ~= ";" then + return nil, Error:new("expected semicolon after operator assignment", 'parser', semi.location:clone()) + end + self.lexer:consume() + + return Node:new("OperatorAssignment", {expr, operator, rhs}, nextok.location:clone()) + end + + if expr.kind ~= "Call" and expr.kind ~= "MethodCall" then + return nil, Error:new("loose expression must be a call or method call", 'parser', expr.location:clone()) + end + + local semi, semierr = self.lexer:peek() + if not semi then + return nil, semierr + end + + if semi.kind ~= "symbol" or semi.content ~= ";" then + return nil, Error:new("expected semicolon after call or method call", 'parser', semi.location:clone()) + end + self.lexer:consume() + + return expr + elseif tok.kind == "keyword" and tok.content == "break" then + self.lexer:consume() + + local semi,semierr = self.lexer:peek() + if not semi then + return nil, semierr + end + + if semi.kind ~= "symbol" or semi.content ~= ";" then + return nil, Error:new("expected semicolon after break", 'parser', semi.location:clone()) + end + self.lexer:consume() + + return Node:new("Break", {}, tok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "foreach" then + self.lexer:consume() + + local openpar,operr = self.lexer:peek() + if not openpar then + return nil, operr + end + + if openpar.kind ~= "symbol" or openpar.content ~= "(" then + return nil, Error:new("expected ( after foreach keyword", 'parser', openpar.location:clone()); + end + self.lexer:consume() + + local varnames = {} + + while true do + local vname,vnamerr = self.lexer:peek() + if not vname then + return nil, vnamerr + end + + if vname.kind ~= "identifier" then + return nil, Error:new("expected identifier in varname list in foreach loop", 'parser', vname.location:clone()) + end + self.lexer:consume() + + varnames[#varnames+1] = vname.content; + + local comma,commaerr = self.lexer:peek() + if not comma then + return nil, commaerr + end + + if comma.kind ~= "symbol" or comma.content ~= "," then + break + end + self.lexer:consume() + end + + local eqtok, eqerr = self.lexer:peek() + if not eqtok then + return nil, eqerr + end + + if eqtok.kind ~= "symbol" or eqtok.content ~= "=" then + return nil, Error:new("expected = after varname list in foreach loop", 'parser', eqtok.location:clone()) + end + self.lexer:consume() + + local exprs = {} + + while true do + local expr, exerr = self:parseExpression() + if not expr then + return nil, exerr + end + + exprs[#exprs+1] = expr + + local commatok, commaerr = self.lexer:peek() + if not commatok then + return nil, commaerr + end + + if commatok.kind ~= "symbol" or commatok.content ~= "," then + break; + end + self.lexer:consume() + end + + local closepar, clperr = self.lexer:peek() + if not closepar then + return nil, clperr + end + + if closepar.kind ~= "symbol" or closepar.content ~= ")" then + return nil, Error:new("expected ) to close expression list for foreach loop", 'parser', closepar.location:clone()) + end + self.lexer:consume() + + local block, berr = self:parseBlockOrStatement(); + if not block then + return nil, berr + end + + return Node:new("Foreach", {varnames, exprs, block}, tok.location:clone()) + end + + return nil, Error:new('invalid statement', 'parser', tok.location:clone()); +end + +function Parser:parseExpression() + return self:parseOperatorExpression() +end + +---@param token Token +---@return number? +function Parser:prefixOperatorBindingPower(token) + if token.kind == "symbol" then + if token.content == "-" then + return 45 + elseif token.content == "#" then + return 45 + elseif token.content == "!" then + return 45 + end + end + + return nil +end + +---@param token Token +---@return number?, number? +function Parser:infixOperatorBindingPower(token) + if token.kind == "symbol" then + if token.content == "+" or token.content == "-" then + return 10, 20 + elseif token.content == "*" or token.content == "/" or token.content == "%" then + return 30, 40 + elseif token.content == "^" then + return 60, 50 + elseif token.content == ".." then + return 0, -10 + elseif token.content == "<" or token.content == ">" or + token.content == "<=" or token.content == ">=" or + token.content == "!=" or token.content == "==" then + return -30, -20 + elseif token.content == "&&" then + return -50, -40 + elseif token.content == "||" then + return -70, -60 + end + end +end + +function Parser:parseOperatorExpression(min_bp, predlhs) + min_bp = min_bp or -math.huge + + local tok, err = self.lexer:peek() + + if not tok then + return nil, err + end + + if tok.kind == "eof" then + return nil, Error:new("expected expression, but found EOF", "parser", tok.location:clone()) + end + + local lhs = predlhs + + if (lhs == nil) and self:prefixOperatorBindingPower(tok) then + self.lexer:consume() -- eat the operator + + local prefix_bp = self:prefixOperatorBindingPower(tok) + + local rightthing, righterr = self:parseOperatorExpression(prefix_bp) + if not rightthing then + return nil, righterr + end + + lhs = Node:new("UnaryOperator", {tok.content, rightthing}, tok.location:clone()) + end + + if lhs == nil then + local newlhs, newerr = self:parseRawExpression() + if not newlhs then + return nil, newerr + end + lhs = newlhs + end + + while true do + local newtok, lexErr = self.lexer:peek() + + if not newtok then + return nil, lexErr + end + + local left_bp, right_bp = self:infixOperatorBindingPower(newtok) + + if not left_bp or not right_bp then break end -- ain't an operator!! + + if left_bp < min_bp then -- we don't + break + end + + self.lexer:consume() + + local rhs, rhserr = self:parseOperatorExpression(right_bp) + if not rhs then + return nil, rhserr + end + + lhs = Node:new("BinaryOperator", {newtok.content, lhs, rhs}, newtok.location:clone()) + end + + return lhs +end + +function Parser:parseTableLiteral() + local tok, tokerr = self.lexer:peek() + if not tok then + return nil, tokerr + end + + if tok.kind ~= "symbol" or tok.content ~= "{" then + return nil, Error:new("expected { to start table literal", "parser", tok.location:clone()) + end + self.lexer:consume() + + local list_part = {} + local table_entries = {} + + while true do + local check, checkerr = self.lexer:peek() + if not check then + return nil, checkerr + end + + if check.kind == "symbol" and check.content == "}" then + break; + end + + -- it hasn't ended, let us continue. + + local expr, exerr = self:parseExpression() + if not expr then + return nil, exerr + end + + local check2, check2err = self.lexer:peek() + if not check2 then + return nil, check2err + end + + if check2.kind == "symbol" and check2.content == '=' then + -- table entry! + self.lexer:consume() + + local set,seterr = self:parseExpression() + if not set then + return nil, seterr + end + + table_entries[#table_entries+1] = {expr, set} + else + list_part[#list_part+1] = expr + end + + local commatok, commaerr = self.lexer:peek() + if not commatok then + return nil, commaerr + end + + if commatok.kind ~= "symbol" or commatok.content ~= "," then + break; + end + self.lexer:consume() -- remove comma + end + + local closetok, closeerr = self.lexer:peek() + if not closetok then + return nil, closeerr; + end + + if closetok.kind ~= "symbol" or closetok.content ~= "}" then + return nil, Error:new('expected } to close table literal', 'parser', closetok.location:clone()) + end + self.lexer:consume() + + return Node:new("TableLiteral", {list_part, table_entries}, tok.location:clone()) +end + +function Parser:parseLambdaFunction() + local fn,fnerr = self.lexer:peek() + if not fn then + return nil, fnerr + end + + if fn.kind ~= 'keyword' or fn.content ~= 'fn' then + return nil, Error:new('expected fn to start lambda function', 'parser', fn.location:clone()) + end + self.lexer:consume() + + local paren, parerr = self.lexer:peek() + if not paren then + return nil, parerr + end + + if paren.kind ~= "symbol" or paren.content ~= "(" then + return nil, Error:new("expected ( to open parameter list for lambda function", 'parser', paren.location:clone()) + end + self.lexer:consume() + + local params = {} + + while true do + local checktok, checkerr = self.lexer:peek() + if not checktok then + return nil, checkerr + end + + if checktok.kind == "symbol" and checktok.content == ")" then + break; + elseif checktok.kind == "identifier" then + params[#params+1] = checktok.content + self.lexer:consume() + elseif checktok.kind == "eof" and checktok.content == "eof" then + break; + end + + local nexttok, nexterr = self.lexer:peek() + if not nexttok then + return nil, nexterr + end + + if nexttok.kind ~= "symbol" or nexttok.content ~= "," then + break; + end + self.lexer:consume() + end + + local clostok, closerr = self.lexer:peek() + if not clostok then + return nil, closerr + end + + if clostok.kind ~= "symbol" or clostok.content ~= ")" then + return nil, Error:new("no closing parenthesis after lambda function parameters", "parser", clostok.location:clone()) + end + self.lexer:consume() + + local block, berr = self:parseBlockOrStatement() + if not block then + return nil, berr + end + + return Node:new("LambdaFunctionLiteral", {params, block}, fn.location:clone()) +end + +function Parser:parseRawExpression() + local tok, err = self.lexer:peek() + if not tok then + return nil, err + end + + if tok.kind == "string" then + self.lexer:consume() + return Node:new("StringLiteral", tok.content, tok.location:clone()) + elseif tok.kind == "number" then + self.lexer:consume() + return Node:new("NumberLiteral", tok.content, tok.location:clone()) + elseif tok.kind == "identifier" then + self.lexer:consume() + local node = Node:new("Variable", tok.content, tok.location:clone()) + + return self:parseComplexExpression(node) + elseif tok.kind == "keyword" and tok.content == "true" then + self.lexer:consume() + return Node:new("BooleanLiteral", "true", tok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "false" then + self.lexer:consume() + return Node:new("BooleanLiteral", "false", tok.location:clone()) + elseif tok.kind == "keyword" and tok.content == "null" then + self.lexer:consume() + return Node:new("NullLiteral", nil, tok.location:clone()) + elseif tok.kind == "symbol" and tok.content == "{" then + return self:parseTableLiteral(); + elseif tok.kind == "keyword" and tok.content == "fn" then + return self:parseLambdaFunction(); + elseif tok.kind == "symbol" and tok.content == "(" then + self.lexer:consume() + + local expr, exerr = self:parseExpression() + if not expr then + return nil, exerr + end + + local closet, closerr = self.lexer:peek() + if not closet then + return nil, closerr + end + + if closet.kind ~= "symbol" or closet.content ~= ")" then + return nil, Error:new('expected ) to close parenthesized expression', 'parser', closet.location:clone()) + end + self.lexer:consume() + + local node = Node:new('ParenthesizedExpression', expr, tok.location:clone()) + + local newnode, nodeerr = self:parseComplexExpression(node) + if nodeerr then + return nil, nodeerr + end + + return newnode + else + return nil, Error:new('invalid expression', 'parser', tok.location:clone()) + end +end + +function Parser:parseComplexExpression(node) + while true do + local tok, err = self.lexer:peek() + if not tok then + return nil, err + end + + if tok.kind == "symbol" and tok.content == "." then + self.lexer:consume() -- bye . + + local field, ferr = self.lexer:peek() + if not field then + return nil, ferr + end + + if field.kind ~= "identifier" then + return nil, Error:new("expected identifier after . in complex expression", "parser", field.location:clone()) + end + + self.lexer:consume() + + node = Node:new("Field", {node, field.content}, tok.location:clone()) + elseif tok.kind == "symbol" and tok.content == "[" then + self.lexer:consume() + + -- now we have *some* expression + local expr, exerr = self:parseExpression() + if not expr then + return nil, exerr + end + + local close,closerr = self.lexer:peek() + if not close then + return nil, closerr + end + + if close.kind ~= "symbol" or close.content ~= "]" then + return nil, Error:new("expected ] to close indexing in complex expression", "parser", close.location:clone()) + end + self.lexer:consume() + + node = Node:new("Index", {node, expr}, tok.location:clone()) + elseif tok.kind == "symbol" and tok.content == ":" then + self.lexer:consume() + + local methodname, methoderr = self.lexer:peek() + if not methodname then + return nil, methoderr + end + + if methodname.kind ~= "identifier" then + return nil, Error:new('expected identifier for method name', 'parser', methodname.location:clone()) + end + self.lexer:consume() + + local parentok, parenerr = self.lexer:peek() + if not parentok then + return nil, parenerr + end + + if parentok.kind ~= "symbol" or parentok.content ~= '(' then + return nil, Error:new("expected ( to open method call argument list", 'parser', parentok.location:clone()) + end + self.lexer:consume() + + local parts = {} + while true do + local chtok, cherr = self.lexer:peek() + if not chtok then + return nil, cherr + end + + if chtok.kind == "symbol" and chtok.content == ")" then + -- self.lexer:consume() + break; + end + + local expr, experr = self:parseExpression() + if not expr then + return nil, experr + end + + parts[#parts+1] = expr; + + local ntok, nerr = self.lexer:peek() + if not ntok then + return nil, nerr + end + + if ntok.kind == "symbol" and ntok.content == ")" then + break; + elseif ntok.kind == "symbol" and ntok.content == "," then + self.lexer:consume() + -- let it go!1!! + else + return nil, Error:new("expected ) or , after argument in method call", 'parser', ntok.location:clone()) + end + end + + local closeparen, closeparenerr = self.lexer:peek() + if not closeparen then + return nil, closeparenerr + end + + if closeparen.kind ~= "symbol" or closeparen.content ~= ")" then + return nil, Error:new("expected ) to close arument list for method call", 'parser', closeparen.location:clone()) + end + self.lexer:consume() + + node = Node:new("MethodCall", {node, methodname.content, parts}, tok.location:clone()) + elseif tok.kind == "symbol" and tok.content == "(" then + self.lexer:consume() + + local parts = {} + while true do + local chtok, cherr = self.lexer:peek() + if not chtok then + return nil, cherr + end + + if chtok.kind == "symbol" and chtok.content == ")" then + -- self.lexer:consume() + break; + end + + local expr, experr = self:parseExpression() + if not expr then + return nil, experr + end + + parts[#parts+1] = expr; + + local ntok, nerr = self.lexer:peek() + if not ntok then + return nil, nerr + end + + if ntok.kind == "symbol" and ntok.content == ")" then + break; + elseif ntok.kind == "symbol" and ntok.content == "," then + self.lexer:consume() + -- let it go!1!! + else + return nil, Error:new("expected ) or , after argument in call", 'parser', ntok.location:clone()) + end + end + + local paren, parenerr = self.lexer:peek() + if not paren then + return nil, parenerr + end + + if paren.kind ~= "symbol" or paren.content ~= ")" then + return nil, Error:new("expected ) to close arument list for call", 'parser', paren.location:clone()) + end + self.lexer:consume() + + node = Node:new("Call", {node, parts}, tok.location:clone()); + else + return node + end + end +end diff --git a/src/types.lua b/src/types.lua new file mode 100644 index 0000000..60a4549 --- /dev/null +++ b/src/types.lua @@ -0,0 +1,85 @@ +---@class Location +---@field file string +---@field line integer +---@field column integer +Location = {} + +function Location:new(file) + return setmetatable({file = file, column = 1, line = 1},{__index = self}) +end + +---@return Location +function Location:clone() + return setmetatable({file = self.file, column = self.column, line = self.line}, getmetatable(self)) +end + +function Location:skip(chr) + for i=1,#chr do + self.column = self.column + 1 + local c = chr:sub(i, i) + if c == '\n' then + self.column = 1 + self.line = self.line + 1 + end + end +end + +function Location:format() + return self.file .. ":" .. self.line .. ":" .. self.column +end + +---@class Token +---@field kind string +---@field content any +---@field location Location +Token = {} + +function Token:new(kind, content, location) + return setmetatable({ + kind = kind, + content = content, + location = location + }, {__index = self}) +end + +---@class Error +---@field location Location +---@field message string +---@field origin string +Error = {} + +function Error:new(message,origin, location) + return setmetatable({ + message = message, + origin = origin, + location = location + }, {__index = self}) +end + +function Error:format() + local capitalizedOrigin = self.origin:sub(1,1):upper() .. self.origin:sub(2):lower() + local locationString = self.location:format() + + -- local msg = string.format("%s:%d %s", err.location.file, err.location.line, err.message) + + return capitalizedOrigin .. " error: " .. locationString .. ": " .. self.message; + -- print("\x1b[31m" .. msg .. "\x1b[0m") +end + +function Error:print() + print("\x1b[31m" .. self:format() .. "\x1b[0m") +end + +---@class Node +---@field kind string +---@field content any +---@field location Location +Node = {} + +function Node:new(kind,content,location) + return setmetatable({ + kind = kind, + content = content, + location = location, + }, {__index = self}) +end \ No newline at end of file diff --git a/src/util.lua b/src/util.lua new file mode 100644 index 0000000..4112e55 --- /dev/null +++ b/src/util.lua @@ -0,0 +1,64 @@ +local deckey = "0123456789" +function DecEncode(num, minchars) + local outp = "" + + while num > 0 do + local dig = num % (#deckey) + + local ch = deckey:sub(dig + 1, dig + 1) + + outp = ch .. outp + + num = math.floor(num / #deckey) + end + + if minchars then + + while #outp < minchars do + outp = deckey:sub(1,1) .. outp + end + + end + + return outp +end + +local hexkey = "0123456789abcdef" +function HexEncode(num, minchars) + local outp = "" + + while num > 0 do + local dig = num % (#hexkey) + + local ch = hexkey:sub(dig + 1, dig + 1) + + outp = ch .. outp + + num = math.floor(num / #hexkey) + end + + if minchars then + + while #outp < minchars do + outp = hexkey:sub(1,1) .. outp + end + + end + + return outp +end + +function HexDecode(hexstr) + local out = 0 + + for i = 1,#hexstr do + local char = hexstr:sub(i,i):lower() + + local pos = hexkey:find(char, nil, true) - 1 + + out = out * 16; + out = out + pos; + end + + return out; +end \ No newline at end of file