commit 47c84ae3c8102b20f92e5dffb917bd929faa9b2a Author: Blendi Date: Sat Apr 25 16:50:59 2026 +0200 initial commit diff --git a/main.lake b/main.lake new file mode 100644 index 0000000..9fc4ed7 --- /dev/null +++ b/main.lake @@ -0,0 +1,11 @@ +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'); diff --git a/src/cli.lake b/src/cli.lake new file mode 100644 index 0000000..7d087ca --- /dev/null +++ b/src/cli.lake @@ -0,0 +1,168 @@ +fn ParseArgData(args, argConfig) { + var i = 1; + + var knorps = {}; + var flarples = {}; + + foreach (k,v = pairs(argConfig)) { + if (v[1] == 'flag') { + flarples[v[2]] = false; + } elseif (v[1] == 'list') { + flarples[v[2]] = {}; + } elseif (v[1] == 'input') { + // can't initialize to anything. + } + } + + while (i <= #args) { + var argument = args[i]; + + if (argConfig[argument]) { + var argconf = argConfig[argument]; + + var argtype = argconf[1]; + var storageplace = argconf[2]; + + if (argtype == 'flag') { + flarples[storageplace] = true; + } elseif (argtype == 'input') { + var next = args[i+1]; + i += 1; + + if (!next) { + error('no value given to an input flag!'); + } + + flarples[storageplace] = next; + } elseif (argtype == 'list') { + var next = args[i+1]; + i += 1; + + if (!next) { + error('no value given to a list flag!'); + } + + table.insert(flarples[storageplace], next); + } + } else { + if (argument:sub(1,1) == '-') { + error('knorp starts with a dash!'); + } + + knorps[#knorps+1] = argument; + } + + i += 1; + } + + return knorps, flarples; +} + +if (#arg == 0) { + while (true) { + io.write("\x1b[36minput>\x1b[0m "); + io.flush(); + + var line = io.read('L'); + if (!line) break; + + var lexer = Lexer:new(line, "stdin"); + var parser = Parser:new(lexer); + + var ast,err = parser:parse(); + if (err) { + err:print(); + } else { + if (!ast) { + print("\x1b[31m!!! an unknown error occured !!!\x1b[0m"); + os.exit(1); + } + + var output = Output:new('lua54', 'small'); + output:output(ast); + + var outdata = table.concat(output.lines, "\n"); + print(outdata); + } + } + + return; +} + +if (arg[1] == 'compile') { + table.remove(arg, 1); + + var options = { + '-v' = {'flag', 'verbose'}, + '-o' = {'input', 'output'}, + '-s' = {'input', 'sourcemap'}, + '-m' = {'input', 'method'}, + '-t' = {'input', 'target'} + }; + + var knorps, flarples = ParseArgData(arg, options); + flarples.target ||= 'lua54'; + flarples.method ||= 'debug'; + + if (!flarples.output) { + print("\x1b[31mno output file given\x1b[0m"); + os.exit(1); + } + + var outfile = io.open(flarples.output, 'w'); + if (!outfile) { + print("\x1b[31moutput file not writable\x1b[0m"); + os.exit(1); + } + + var SMfile; + if (flarples.sourcemap) { + SMfile = io.open(flarples.sourcemap, 'w'); + if (!SMfile) { + print("\x1b[31msourcemap file not writable\x1b[0m"); + os.exit(1); + } + } + + for (var i = 1; i <= #knorps; i += 1;) { + var file = io.open(knorps[i], 'r'); + if (!file) { + print("\x1b[31mcould not read file " .. tostring(knorps[i]) .. "\x1b[0m"); + os.exit(1); + } + + var code = file:read('*all'); + file:close(); + + var lexer = Lexer:new(code, knorps[i]); + + var parser = Parser:new(lexer); + + var ast,err = parser:parse(); + if (!ast) { + if (err) + err:print(); + else + print("\x1b[31m!!! an unknown error occured !!!\x1b[0m"); + os.exit(1); + } + + var optimizer = Optimizer:new(); + + optimizer:run(ast); + + var output = Output:new(flarples.target, flarples.method); + + output:output(ast); + + var outdata = table.concat(output.lines, "\n"); + var smdata = table.concat(output.debug, "\n"); + + outfile:write(outdata); + if (SMfile) + SMfile:write(smdata); + } + + outfile:close(); + if (SMfile) SMfile:close(); +} diff --git a/src/lexer.lake b/src/lexer.lake new file mode 100644 index 0000000..6a8f90b --- /dev/null +++ b/src/lexer.lake @@ -0,0 +1,326 @@ +Lexer = {}; + +Lexer.keywords = { + 'var', + 'return', + 'fn', + 'true', + 'false', + 'null', + 'if', + 'var', + 'else', + 'elseif', + 'while', + 'for', + 'do', + 'break', + 'foreach' +}; + +Lexer.symbols = { + "!=", + "==", + "<=", + ">=", + "&&=", + "||=", + "&&", + "||", + "+=", + "-=", + "*=", + "/=", + "%=", + "^=", + "..=", + "..", + ">", + "<", + "+", + "-", + "*", + "/", + "^", + "=", + ";", + "#", + "!", + "[", + "]", + ".", + "{", + "}", + "(", + ")", + ",", + "%", + ':' +}; + +fn Lexer:new(code, fname) { + return setmetatable({ + 'cache' = null, + 'cursor' = 1, + 'location' = Location:new(fname), + 'code' = code + }, {'__index' = self}); +} + +fn Lexer:next(n) { + n ||= 1; + + for (var i = 1; i <= n; i += 1;) { + self.location:skip(self.code:sub(self.cursor, self.cursor)); + self.cursor += 1; + } +} + +fn Lexer:get(n) { + n ||= 1; + return self.code:sub(self.cursor, self.cursor+n-1); +} + +fn Lexer:isWhitespace(ch) { + return ch == "\r" || ch == "\n" || ch == "\t" || ch == ' '; +} + +fn Lexer:isDigit(ch) { + var n = string.byte(ch); + if (!n) return false; + return n >= 48 && n <= 57; +} + +fn Lexer:isHex(ch) { + return (ch:match('%x') != null); +} + +fn Lexer:isIdentifierValid(ch) { + return (ch:match('%a') != null) || (Lexer:isDigit(ch)) || ch == '_'; +} + +fn Lexer:run() { + while (true) { + if (self:isWhitespace(self:get())) { + self:next(); + } elseif (self:get(2) == '//') { + while ((self:get() != "\n") && (self:get() != '')) { + self:next(); + } + } elseif (self:get() == '') { + return Token:new('eof', 'eof', self.location:clone()); + } else { + break; + } + } + + if (self:isDigit(self:get())) { + // number + + var stpos = self.location:clone(); + var st = ''; + + while (self:isDigit(self:get())) { + st ..= self:get(); + self:next(); + } + + if (self:get() == '.') { + // decimal point real + + self:next(); + st ..= '.'; + + while (self:isDigit(self:get())) { + st ..= self:get(); + self:next(); + } + } + + return Token:new('number', st, stpos); + } + + if (self:get() == "'") { + var d = "'"; + var stpos = self.location:clone(); + self:next(); + + while (self:get() != "'") { + var ch = self:get(); + if (ch == '') break; + d ..= ch; + self:next(); + } + + if (self:get() != "'") { + return null, Error:new('unfinished string', 'lexer', self.location:clone()); + } + d ..= "'"; + self:next(); + + return Token:new('string', d, stpos); + } + + if (self:get() == '"') { + var d = '"'; + var stpos = self.location:clone(); + self:next(); + + while (true) { + var ch = self:get(); + + if (ch == '\') { + d ..= ch; + self:next(); + + var nch = self:get(); + if (nch == '\') { + d ..= '\'; + self:next(); + } elseif (nch == 'n') { + d ..= 'n'; + self:next(); + } elseif (nch == 'r') { + d ..= 'r'; + self:next(); + } elseif (nch == 'v') { + d ..= 'v'; + self:next(); + } elseif (nch == '"') { + d ..= '"'; + self:next(); + } elseif (nch == 'f') { + d ..= 'f'; + self:next(); + } elseif (nch == 't') { + d ..= 't'; + self:next(); + } elseif (nch == 'a') { + d ..= 'a'; + self:next(); + } elseif (nch == 'b') { + d ..= 'b'; + self:next(); + } elseif (nch == 'x') { + d ..= 'x'; + self:next(); + + var hexdata = self:get(2); + + if ((!self:isHex(hexdata:sub(1,1))) || (!self:isHex(hexdata:sub(2,2)))) { + return null, Error:new('invalid hex digits after x escape code', 'lexer', self.location:clone()); + } + + d ..= hexdata; + self:next(2); + } elseif (nch == 'u') { + d ..= 'u'; + self:next(); + + if (self:get() != '{') { + return null, Error:new('expected { after u escape', 'lexer', self.location:clone()); + } + d ..= '{'; + self:next(); + + var i = 0; + while (self:isHex(self:get()) && (self:get() != '')) { + d ..= self:get(); + self:next(); + i += 1; + } + + if (i > 0) { + var hexpart = d:sub(-i); + var value = HexDecode(hexpart); + + if (value >= 2^31) { + return null, Error:new('invalid utf8 codepoint in u escape', 'lexer', self.location:clone()); + } + } + + if (self:get() != '}') { + return null, Error:new('expected } to close u escape', 'lexer', self.location:clone()); + } + + d ..= '}'; + self:next(); + } else { + return null, Error:new('unknown escape in string', 'lexer', self.location:clone()); + } + } elseif (ch == '') { + break; + } elseif (ch == '"') { + break; + } else { + d ..= ch; + self:next(); + } + } + + if (self:get() != '"') { + return null, Error:new('unfinished double-quoted string', 'lexer', self.location:clone()); + } + d ..= '"'; + self:next(); + + return Token:new('string', d, stpos); + } + + do { + var stloc = self.location:clone(); + for (var i = 1; i <= #self.symbols; i += 1;) { + var symbol = self.symbols[i]; + + if (self:get(#symbol) == symbol) { + // ladies and gentlemen, we got him. + + self:next(#symbol); + + return Token:new('symbol', symbol, stloc); + } + } + } + + if (self:isIdentifierValid(self:get())) { + var ident = ''; + var stpos = self.location:clone(); + + while (self:isIdentifierValid(self:get())) { + ident ..= self:get(); + self:next(); + } + + for (var i = 1; i <= #self.keywords; i += 1;) { + var kw = self.keywords[i]; + + if (ident == kw) { + return Token:new('keyword', kw, stpos); + } + } + + return Token:new('identifier', ident, stpos); + } + + return null, Error:new('tried to lex invalid code', 'lexer', self.location:clone()); +} + +fn Lexer:peek() { + if (self.cache != null) { + return self.cache, null; + } + + var token, err = self:run(); + self.cache = token; + return token, err; +} + +fn Lexer:consume() { + if (self.cache == null) { + return self:run(); + } + + var token = self.cache; + self.cache = null; + return token, null; +} diff --git a/src/lower.lake b/src/lower.lake new file mode 100644 index 0000000..7ea08ba --- /dev/null +++ b/src/lower.lake @@ -0,0 +1,155 @@ +Lower = {}; + +fn Lower.makeUTF8Char(decimal) { + if (decimal < 128) { + return string.char(decimal); + } elseif (decimal < 2048) { + var byte2 = (128 + (decimal % 64)); + var byte1 = (192 + math.floor(decimal / 64)); + + return string.char(byte1, byte2); + } elseif (decimal < 65536) { + var byte3 = (128 + (decimal % 64)); + decimal = math.floor(decimal / 64); + var byte2 = (128 + (decimal % 64)); + var byte1 = (224 + math.floor(decimal / 64)); + + return string.char(byte1, byte2, byte3); + } elseif (decimal < 2097152) { + var byte4 = (128 + (decimal % 64)); + decimal = math.floor(decimal / 64); + var byte3 = (128 + (decimal % 64)); + decimal = math.floor(decimal / 64); + var byte2 = (128 + (decimal % 64)); + var byte1 = (240 + math.floor(decimal / 64)); + + return string.char(byte1,byte2,byte3,byte4); + } elseif (decimal < 67108864) { + // different code style cause new lol + var b1 = 248 + (math.floor(decimal / 16777216)); // my fav power of two!! + var b2 = 128 + (math.floor(decimal / 262144) % 64); + var b3 = 128 + (math.floor(decimal / 4096) % 64); + var b4 = 128 + (math.floor(decimal / 64) % 64); + var b5 = 128 + (decimal % 64); + + return string.char(b1,b2,b3,b4,b5); + } elseif (decimal < 2147483648) { + var b1 = 252 + math.floor(decimal / 1073741824); + var b2 = 128 + (math.floor(decimal / 16777216) % 64); + var b3 = 128 + (math.floor(decimal / 262144) % 64); + var b4 = 128 + (math.floor(decimal / 4096) % 64); + var b5 = 128 + (math.floor(decimal / 64) % 64); + var b6 = 128 + (decimal % 64); + + return string.char(b1, b2, b3, b4, b5, b6); + } +} + +fn Lower.decodeString(lakeString) { + if (lakeString:sub(1,1) == "'") { + return lakeString:sub(2,-2); + } + + if (lakeString:sub(1,1) == '"') { + var i = 2; + var out = ''; + + while (true) { + var ch = lakeString:sub(i,i); + + if (ch == '\') { + i += 1; + var nextch = lakeString:sub(i,i); + + if (nextch == '\') { + out ..= '\'; + i += 1; + } elseif (nextch == 'n') { + out ..= "\n"; + i += 1; + } elseif (nextch == 'r') { + out ..= "\r"; + i += 1; + } elseif (nextch == 'v') { + out ..= "\v"; + i += 1; + } elseif (nextch == '"') { + out ..= '"'; + i += 1; + } elseif (nextch == 'f') { + out ..= "\f"; + i += 1; + } elseif (nextch == 't') { + out ..= "\t"; + i += 1; + } elseif (nextch == 'a') { + out ..= "\x07"; + i += 1; + } elseif (nextch == 'b') { + out ..= "\x08"; + i += 1; + } elseif (nextch == 'x') { + i += 1; + + var hex = lakeString:sub(i, i+1); + i += 2; + + var decoded = HexDecode(hex); + + out ..= string.char(decoded); + } elseif (nextch == 'u') { + i += 2; + + var hexpart = ''; + + while (lakeString:sub(i,i) != '}') { + hexpart ..= lakeString:sub(i,i); + i += 1; + } + + i += 1; + + var decoded = HexDecode(hexpart); + + out ..= Lower.makeUTF8Char(decoded); + } + } elseif (ch == '"') { + break; + } else { + i += 1; + out ..= ch; + } + } + + return out; + } +} + +fn Lower.decodeNumber(num) { + var numeric = 0; + var decimal = false; + + for (var i = 1; i <= #num; i += 1;) { + var ch = num:sub(i,i); + + if (ch == '.') { + decimal = i + 1; + break; + } + + numeric *= 10; + numeric += string.byte(ch) - string.byte('0'); + } + + if (decimal) { + var n = 0.1; + for (var j = decimal; j <= #num; j += 1;) { + var ch = num:sub(j,j); + + numeric += (string.byte(ch) - string.byte('0')) * n; + n *= 0.1; + } + } + + return numeric; +} diff --git a/src/methods/debug.lake b/src/methods/debug.lake new file mode 100644 index 0000000..d1723b6 --- /dev/null +++ b/src/methods/debug.lake @@ -0,0 +1,667 @@ +Debug = {}; + +fn Debug.encodeString(raw, islua51) { + var out = '"'; + + for (var i = 1; i <= #raw; i += 1;) { + var ch = raw:sub(i,i); + + var code = string.byte(ch); + if (ch == '\') { + out ..= '\\'; + } elseif (ch == '"') { + out ..= '\"'; + } elseif (code >= 32 && code <= 126) { + out ..= ch; + } else { + if (islua51) + out ..= '\' .. DecEncode(code, 3); + else + out ..= '\x' .. HexEncode(code, 2); + } + } + + out ..= '"'; + return out; +} + +fn Debug.encodeNumber(n) { + var num = ''; + var isneg = false; + + if (n < 0) { + n *= -1; + isneg = true; + } + + var whole = math.floor(n); + + if (whole == 0) { + num ..= '0'; + } + + while (whole > 0) { + var digit = whole % 10; + num = string.char(string.byte('0') + digit) .. num; + whole = math.floor(whole / 10); + } + + var dec = n % 1; + + if (dec > 0) { + num ..= '.'; + + while (dec > 0) { + dec *= 10; + + var digit = math.floor(dec); + num ..= string.char(string.byte('0') + digit); + + dec %= 1; + } + } + + if (isneg) num = '-' .. num; + + return num; +} + +fn Debug.convertBinaryOperator(lakeop) { + if (lakeop == '+') { + return '+'; + } elseif (lakeop == "-" ) { + return "-"; + } elseif ( lakeop == "*" ) { + return "*"; + } elseif ( lakeop == "/" ) { + return "/"; + } elseif ( lakeop == "%" ) { + return "%"; + } elseif ( lakeop == "^" ) { + return "^"; + } elseif ( lakeop == ".." ) { + return ".."; + } elseif ( lakeop == "<" ) { + return "<"; + } elseif ( lakeop == ">" ) { + return ">"; + } elseif ( lakeop == "<=" ) { + return "<="; + } elseif ( lakeop == ">=" ) { + return ">="; + } elseif ( lakeop == "!=" ) { + return "~="; + } elseif ( lakeop == "==" ) { + return "=="; + } elseif ( lakeop == "&&" ) { + return "and"; + } elseif ( lakeop == "||" ) { + return "or"; + } +} + +fn Debug.convertUnaryOperator(op) { + if (op == '#') return '#'; + elseif (op == '-') return '-'; + elseif (op == '!') return 'not'; +} + +fn Debug.output(outp, ast) { + Debug.outputStatement(outp, ast); +} + +fn Debug.outputStatement(outp, node) { + if (node.kind == 'Program') { + outp:addLine('do', node.location:format()); + + for (var i = 1; i <= #node.content; i += 1;) { + var stmt = node.content[i]; + + Debug.outputStatement(outp, stmt); + } + + outp:addLine('end;', node.location:format()); + } elseif (node.kind == 'VariableDeclaration') { + outp:addLine('local', node.location:format()); + var varnames = node.content[1]; + + for (var i = 1; i <= #varnames; i += 1;) { + var vname = varnames[i]; + + outp:addLine(vname[1], vname[2]:format()); + + if (i != #varnames) { + outp:addLine(',', node.location:format()); + } + } + + var values = node.content[2]; + if (#values > 0) { + outp:addLine('=', node.location:format()); + + for (var i = 1; i <= #values; i += 1;) { + var val = values[i]; + + Debug.outputExpression(outp, val); + + if (i != #values) { + outp:addLine(',', node.location:format()); + } + } + } + + outp:addLine(';', node.location:format()); + } elseif (node.kind == "Block") { + outp:addLine('do', node.location:format()); + + for (var i = 1; i <= #node.content; i += 1;) { + var stmt = node.content[i]; + + Debug.outputStatement(outp, stmt); + } + + outp:addLine('end;', node.location:format()); + } elseif (node.kind == 'IfStatement') { + 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 (var i = 2; i <= #node.content[1]; i += 1;) { + var item = node.content[1][i]; + + outp:addLine('elseif', node.location:format()); + + Debug.outputExpression(outp, item[1]); + + outp:addLine('then', node.location:format()); + + Debug.outputStatement(outp, item[2]); + } + + if (node.content[2]) { + outp:addLine('else', node.location:format()); + + Debug.outputStatement(outp, node.content[2]); + } + + outp:addLine('end;', node.location:format()); + } elseif (node.kind == "FunctionDeclaration") { + outp:addLine('function', node.location:format()); + + Debug.outputFunctionName(outp, node.content[1]); + + outp:addLine('(', node.location:format()); + + for (var i = 1; i <= #node.content[2]; i += 1;) { + var part = node.content[2][i]; + + outp:addLine(part, node.location:format()); + + if (i != #node.content[2]) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine(')', node.location:format()); + + Debug.outputStatement(outp, node.content[3]); + + outp:addLine('end;', node.location:format()); + } elseif (node.kind == 'LocalFunctionDeclaration') { + outp:addLine('local function', node.location:format()); + + outp:addLine(node.content[1].content, node.content[1].location:format()); + + outp:addLine('(', node.location:format()); + + for (var i = 1; i <= #node.content[2]; i += 1;) { + var part = node.content[2][i]; + + outp:addLine(part, node.location:format()); + + if (i != #node.content[2]) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine(')', node.location:format()); + + Debug.outputStatement(outp, node.content[3]); + + outp:addLine('end;', node.location:format()); + } elseif (node.kind == 'Return') { + outp:addLine('return', node.location:format()); // if true because it has to be at the end of a block in lua, but not here. + + for (var i = 1; i <= #node.content; i += 1;) { + var expr = node.content[i]; + + Debug.outputExpression(outp, expr); + + if (i != #node.content) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine(';', node.location:format()); + } elseif (node.kind == 'Assignment') { + var vars = node.content[1]; + var vals = node.content[2]; + + for (var i = 1; i <= #vars; i += 1;) { + var vari = vars[i]; + + Debug.outputVariable(outp, vari); + + if (i != #vars) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine('=', node.location:format()); + + for (var i = 1; i <= #vals; i += 1;) { + var val = vals[i]; + + Debug.outputExpression(outp, val); + + if (i != #vals) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine(';', node.location:format()); + } elseif (node.kind == 'Call') { + outp:addLine('(', node.location:format()); + Debug.outputExpression(outp, node.content[1]); + outp:addLine(')(', node.location:format()); + + var args = node.content[2]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Debug.outputExpression(outp, arg); + + if (i != #args) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine(');', node.location:format()); + } elseif (node.kind == 'MethodCall') { + outp:addLine('(', node.location:format()); + Debug.outputExpression(outp, node.content[1]); + outp:addLine('):', node.location:format()); + outp:addLine(node.content[2] .. '(', node.location:format()); + + var args = node.content[3]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Debug.outputExpression(outp, arg); + + if (i != #args) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine(');', node.location:format()); + } elseif (node.kind == 'While') { + 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') { + 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') { + var lakeop = node.content[2]; + var op = Debug.convertBinaryOperator(lakeop); + + var lhs = node.content[1]; + var rhs = node.content[3]; + + if (lhs.kind == 'Variable') { + var 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') { + 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') { + 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()); + } + } elseif (node.kind == 'Break') { + outp:addLine('break;', node.location:format()); + } elseif (node.kind == 'Foreach') { + outp:addLine("for", node.location:format()); + + var varlist,exprlist,block = node.content[1],node.content[2],node.content[3]; + + for (var i = 1; i <= #varlist; i += 1;) { + outp:addLine(varlist[i], node.location:format()); + if (i != #varlist) { + outp:addLine(',', node.location:format()); + } + } + + outp:addLine("in", node.location:format()); + + for (var i = 1; i <= #exprlist; i += 1;) { + Debug.outputExpression(outp, exprlist[i]); + + if (i != #exprlist) { + outp:addLine(",", node.location:format()); + } + } + + outp:addLine('do', node.location:format()); + + Debug.outputStatement(outp, block); + + outp:addLine('end;', node.location:format()); + } +} + + + +fn Debug.outputExpression(outp, node) { + if (node.kind == 'StringLiteral') { + var lakestr = node.content; + var raw = Lower.decodeString(lakestr); + var enc = Debug.encodeString(raw, outp.target == 'lua51'); + + outp:addLine(enc, node.location:format()); + } elseif (node.kind == 'CompiledStringLiteral') { + var raw = node.content; + var enc = Debug.encodeString(raw, outp.target == 'lua51'); + + outp:addLine(enc, node.location:format()); + } elseif (node.kind == 'ParenthesizedExpression') { + outp:addLine("(", node.location:format()); + Debug.outputExpression(outp, node.content); + outp:addLine(")", node.location:format()); + } elseif (node.kind == 'NumberLiteral') { + var lakenum = node.content; + + var raw = Lower.decodeNumber(lakenum); + + var enc = Debug.encodeNumber(raw); + + outp:addLine("(", node.location:format()); + outp:addLine(enc, node.location:format()); + outp:addLine(")", node.location:format()); + } elseif (node.kind == 'CompiledNumberLiteral') { // usually generated by optimizer doing math + var raw = node.content; + + var enc = Debug.encodeNumber(raw); + + outp:addLine("(", node.location:format()); + outp:addLine(enc, node.location:format()); + outp:addLine(")", node.location:format()); + } elseif (node.kind == 'TableLiteral') { + outp:addLine('{', node.location:format()); + var listpart = node.content[1]; + var tablepart = node.content[2]; + + for (var i = 1; i <= #listpart; i += 1;) { + var item = listpart[i]; + + Debug.outputExpression(outp, item); + outp:addLine(',', node.location:format()); + } + + for (var i = 1; i <= #tablepart; i += 1;) { + var item = tablepart[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()); + } + outp:addLine('}', node.location:format()); + } elseif (node.kind == 'LambdaFunctionLiteral') { + outp:addLine('function(', node.location:format()); + + for (var i = 1; i <= #node.content[1]; i += 1;) { + var part = node.content[1][i]; + + outp:addLine(part, node.location:format()); + + if (i != #node.content[1]) { + outp:addLine(",", node.location:format()); + } + } + + outp:addLine(")", node.location:format()); + + Debug.outputStatement(outp, node.content[2]); + + outp:addLine("end", node.location:format()); + } elseif (node.kind == 'BinaryOperator') { + var op = node.content[1]; + var lhs = node.content[2]; + var rhs = node.content[3]; + + outp:addLine("((", node.location:format()); + + Debug.outputExpression(outp,lhs); + + outp:addLine(")", node.location:format()); + + var 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') { + var op = node.content[1]; + var val = node.content[2]; + + outp:addLine("(", node.location:format()); + + var 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') { + var left = node.content[1]; + var 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') { + var left = node.content[1]; + var 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') { + outp:addLine("(", node.location:format()); + outp:addLine(node.content, node.location:format()); + outp:addLine(")", node.location:format()); + } elseif (node.kind == 'BooleanLiteral') { + outp:addLine("(", node.location:format()); + outp:addLine(node.content, node.location:format()); + outp:addLine(")", node.location:format()); + } elseif (node.kind == 'NullLiteral') { + outp:addLine("(nil)", node.location:format()); + } elseif (node.kind == 'Call') { + outp:addLine("(", node.location:format()); + Debug.outputExpression(outp, node.content[1]); + outp:addLine(")(", node.location:format()); + + var args = node.content[2]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Debug.outputExpression(outp, arg); + + if (i != #args) { + outp:addLine(",", node.location:format()); + } + } + + outp:addLine(")", node.location:format()); + } elseif (node.kind == 'MethodCall') { + outp:addLine("(", node.location:format()); + Debug.outputExpression(outp, node.content[1]); + outp:addLine("):", node.location:format()); + outp:addLine(node.content[2] .. "(", node.location:format()); + + var args = node.content[3]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Debug.outputExpression(outp, arg); + + if (i != #args) { + outp:addLine(",", node.location:format()); + } + } + + outp:addLine(")", node.location:format()); + } +} + + + +fn Debug.outputFunctionName(outp, node) { + if (node.kind == 'Field') { + var left = node.content[1]; + var right = node.content[2]; + + Debug.outputFunctionName(outp, left); + outp:addLine(".", node.location:format()); + outp:addLine(right, node.location:format()); + } elseif (node.kind == 'Index') { + var left = node.content[1]; + var 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') { + outp:addLine(node.content, node.location:format()); + } elseif (node.kind == 'Method') { + var left = node.content[1]; + var right = node.content[2]; + + Debug.outputFunctionName(outp, left); + outp:addLine(":", node.location:format()); + outp:addLine(right, node.location:format()); + } +} + + + +fn Debug.outputVariable(outp, node) { + if (node.kind == 'Field') { + var left = node.content[1]; + var right = node.content[2]; + + Debug.outputExpression(outp, left); + outp:addLine(".", node.location:format()); + outp:addLine(right, node.location:format()); + } elseif (node.kind == 'Index') { + var left = node.content[1]; + var 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') { + outp:addLine(node.content, node.location:format()); + } +} + +RegisterOutMethod("debug", Debug); diff --git a/src/methods/small.lake b/src/methods/small.lake new file mode 100644 index 0000000..6d9d6d4 --- /dev/null +++ b/src/methods/small.lake @@ -0,0 +1,689 @@ +Small = {}; + +fn Small.isDigit(ch) { + var n = string.byte(ch); + if (!n) return false; + return n >= 48 && n <= 57; +} + +fn Small.encodeString(raw, islua51) { + var out = '"'; + + for (var i = 1; i <= #raw; i += 1;) { + var ch = raw:sub(i,i); + var code = string.byte(ch); + + if (ch == '\') out ..= '\\'; + elseif (ch == '"') out ..= '\"'; + elseif (ch == "\n") out ..= '\n'; + elseif (ch == "\r") out ..= '\r'; + elseif (ch == "\x07") out ..= '\a'; + elseif (ch == "\x08") out ..= '\b'; + elseif (ch == "\x0c") out ..= '\f'; + elseif (ch == "\t") out ..= '\t'; + elseif (code >= 32 && code <= 126) out ..= ch; + else { + var nextchar = raw:sub(i+1,i+1); + if (Small.isDigit(nextchar)) + out ..= '\' .. DecEncode(code, 3); + else + out ..= '\' .. DecEncode(code, 1); + } + } + + out ..= '"'; + + return out; +} + +fn Small.encodeNumber(n) { + var num = ''; + var isneg = false; + + if (n < 0) { + n *= -1; + isneg = true; + } + + var whole = math.floor(n); + + if (whole == 0) { + num ..= '0'; + } + + while (whole > 0) { + var digit = whole % 10; + num = string.char(string.byte('0') + digit) .. num; + whole = math.floor(whole / 10); + } + + var dec = n % 1; + + if (dec > 0) { + num ..= '.'; + + while (dec > 0) { + dec *= 10; + + var digit = math.floor(dec); + num ..= string.char(string.byte('0') + digit); + + dec %= 1; + } + } + + if (isneg) num = '-' .. num; + + return num; +} + +fn Small.convertBinaryOperator(lakeop, spacer) { + if (lakeop == '+') return '+'; + elseif ( lakeop == "-" ) return "-"; + elseif ( lakeop == "*" ) return "*"; + elseif ( lakeop == "/" ) return "/"; + elseif ( lakeop == "%" ) return "%"; + elseif ( lakeop == "^" ) return "^"; + elseif ( lakeop == ".." ) return ".."; + elseif ( lakeop == "<" ) return "<"; + elseif ( lakeop == ">" ) return ">"; + elseif ( lakeop == "<=" ) return "<="; + elseif ( lakeop == ">=" ) return ">="; + elseif ( lakeop == "!=" ) return "~="; + elseif ( lakeop == "==" ) return "=="; + elseif ( lakeop == "&&" ) { + if (spacer) return " and "; + else return "and"; + } elseif ( lakeop == "||" ) { + if (spacer) return " or "; + else return 'or'; + } +} + +fn Small.convertUnaryOperator(op) { + if (op == '#') return '#'; + elseif (op == '-') return '-'; + elseif (op == '!') return 'not'; +} + +fn Small.output(outp, ast) { + return Small.outputStatement(outp, ast); +} + + + +fn Small.outputStatement(outp, node, noblock) { + if (node.kind == 'Program') { + outp:appendLine('do ', ''); + + for (var i = 1; i <= #node.content; i += 1;) { + var stmt = node.content[i]; + + Small.outputStatement(outp, stmt, #node.content == 1); + } + + outp:appendLine('end;', ''); + } elseif (node.kind == 'VariableDeclaration') { + outp:appendLine('local ', ''); + var varnames = node.content[1]; + + for (var i = 1; i <= #varnames; i += 1;) { + var vname = varnames[i]; + + outp:appendLine(vname[1], ''); + + if (i != #varnames) { + outp:appendLine(',', ''); + } + } + + var values = node.content[2]; + if (#values > 0) { + outp:appendLine('=', ''); + + for (var i = 1; i <= #values; i += 1;) { + var val = values[i]; + + Small.outputExpression(outp, val, false); + + if (i != #values) { + outp:appendLine(',', ''); + } + } + } + + outp:appendLine(';', ''); + } elseif (node.kind == 'Block') { + if (!noblock) outp:appendLine('do ', ''); + + for (var i = 1; i <= #node.content; i += 1;) { + var stmt = node.content[i]; + + Small.outputStatement(outp, stmt, i == #node.content); + } + + if (!noblock) outp:appendLine('end;', ''); + } elseif (node.kind == 'IfStatement') { + outp:appendLine("if ", ''); + + Small.outputExpression(outp, node.content[1][1][1]); + + outp:appendLine("then ", ''); + + Small.outputStatement(outp, node.content[1][1][2], true); + + for (var i = 2; i <= #node.content[1]; i += 1;) { + var item = node.content[1][i]; + + outp:appendLine("elseif ", ''); + + Small.outputExpression(outp,item[1]); + + outp:appendLine("then ", ''); + + Small.outputStatement(outp,item[2], true); + } + + if (node.content[2]) { + outp:appendLine("else ", ''); + + Small.outputStatement(outp, node.content[2], true); + } + + outp:appendLine("end;", ''); + } elseif (node.kind == 'FunctionDeclaration') { + outp:appendLine("function ", ''); + + Small.outputFunctionName(outp, node.content[1]); + + outp:appendLine("(", ''); + + for (var i = 1; i <= #node.content[2]; i += 1;) { + var part = node.content[2][i]; + + outp:appendLine(part, ''); + + if (i != #node.content[2]) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + + Small.outputStatement(outp, node.content[3], true); + + outp:appendLine("end;", ''); + } elseif (node.kind == 'LocalFunctionDeclaration') { + outp:appendLine("local function ", ''); + + outp:appendLine(node.content[1].content, ''); + + outp:appendLine("(", ''); + + for (var i = 1; i <= #node.content[2]; i += 1;) { + var part = node.content[2][i]; + + outp:appendLine(part, ''); + + if (i != #node.content[2]) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + + Small.outputStatement(outp, node.content[3], true); + + outp:appendLine("end;", ''); + } elseif (node.kind == 'Return') { + if (!noblock) { + outp:appendLine("if''then "); + } + + if (#node.content > 0) { + outp:appendLine("return ", ''); + } else { + outp:appendLine('return', ''); + } + + for (var i = 1; i <= #node.content; i += 1;) { + var expr = node.content[i]; + + Small.outputExpression(outp, expr, false); + + if (i != #node.content) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(";", ''); + if (!noblock) { + outp:appendLine("end;"); + } + } elseif (node.kind == 'Assignment') { + var vars = node.content[1]; + var vals = node.content[2]; + + for (var i = 1; i <= #vars; i += 1;) { + var vari = vars[i]; + + Small.outputVariable(outp, vari); + + if (i != #vars) { + outp:appendLine(",", ''); + } + } + + outp:appendLine("=", ''); + + for (var i = 1; i <= #vals; i += 1;) { + var val = vals[i]; + + Small.outputExpression(outp, val, false); + + if (i != #vals) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(";", ''); + } elseif (node.kind == 'Call') { + outp:appendLine("(", ''); + Small.outputExpression(outp, node.content[1], false); + outp:appendLine(")", ''); + + outp:appendLine("(", ''); + + var args = node.content[2]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Small.outputExpression(outp, arg, false); + + if (i != #args) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + outp:appendLine(";", ''); + } elseif (node.kind == 'MethodCall') { + outp:appendLine("(", ''); + Small.outputExpression(outp, node.content[1], false); + outp:appendLine("):", ''); + outp:appendLine(node.content[2], ''); + outp:appendLine("(", ''); + + var args = node.content[3]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Small.outputExpression(outp, arg, false); + + if (i != #args) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + outp:appendLine(";", ''); + } elseif (node.kind == 'While') { + 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') { + 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') { + var lakeop = node.content[2]; + var op = Small.convertBinaryOperator(lakeop, true); + + var lhs = node.content[1]; + var rhs = node.content[3]; + + if (lhs.kind == "Variable") { + var 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") { + 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") { + 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;', ''); + } + } elseif (node.kind == 'Break') { + outp:appendLine('break;', ''); + } elseif (node.kind == 'Foreach') { + outp:appendLine("for ", ''); + + var varlist,exprlist,block = node.content[1],node.content[2],node.content[3]; + + for (var i = 1; i <= #varlist; i += 1;) { + outp:appendLine(varlist[i], ''); + if (i != #varlist) { + outp:appendLine(',', ''); + } + } + + outp:appendLine(" in ", ''); + + for (var i = 1; i <= #exprlist; i += 1;) { + Small.outputExpression(outp, exprlist[i], i == #exprlist); + + if (i != #exprlist) { + outp:appendLine(",", ''); + } + } + + outp:appendLine('do ', ''); + + Small.outputStatement(outp, block, true); + + outp:appendLine('end;', ''); + } +} + + + +fn Small.outputExpression(outp, node, needsp) { + if (needsp == null) needsp = true; + if (node.kind == 'StringLiteral') { + var lakestr = node.content; + + var raw = Lower.decodeString(lakestr); + + var enc = Small.encodeString(raw, outp.target == "lua51"); + + outp:appendLine(enc, ''); + } elseif (node.kind == 'CompiledStringLiteral') { + var raw = node.content; + + var enc = Small.encodeString(raw, outp.target == "lua51"); + + outp:appendLine(enc, ''); + } elseif (node.kind == 'ParenthesizedExpression') { + outp:appendLine("(", ''); + Small.outputExpression(outp, node.content, false); + outp:appendLine(")", ''); + } elseif (node.kind == 'NumberLiteral') { + var lakenum = node.content; + var raw = Lower.decodeNumber(lakenum); + var enc = Small.encodeNumber(raw); + + outp:appendLine(enc, ''); + + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'CompiledNumberLiteral') { + var raw = node.content; + var enc = Small.encodeNumber(raw); + + outp:appendLine(enc, ''); + + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'TableLiteral') { + outp:appendLine('{', ''); + var listpart = node.content[1]; + var tablepart = node.content[2]; + + for (var i = 1; i <= #listpart; i += 1;) { + var item = listpart[i]; + + Small.outputExpression(outp, item, false); + + if ((#tablepart > 0) || (i < #listpart)) { + outp:appendLine(',', ''); + } + } + + for (var i = 1; i <= #tablepart; i += 1;) { + var item = tablepart[i]; + + outp:appendLine('[', ''); + Small.outputExpression(outp, item[1], false); + outp:appendLine(']', ''); + outp:appendLine('=', ''); + Small.outputExpression(outp, item[2], false); + if (i < #tablepart) { + outp:appendLine(',', ''); + } + } + outp:appendLine('}', ''); + } elseif (node.kind == 'LambdaFunctionLiteral') { + outp:appendLine('function(', ''); + + for (var i = 1; i <= #node.content[1]; i += 1;) { + var part = node.content[1][i]; + + outp:appendLine(part, ''); + + if (i != #node.content[1]) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + + Small.outputStatement(outp, node.content[2], true); + + outp:appendLine("end", ''); + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'BinaryOperator') { + var op = node.content[1]; + var lhs = node.content[2]; + var rhs = node.content[3]; + + outp:appendLine("((", ''); + + Small.outputExpression(outp,lhs, false); + + outp:appendLine(")", ''); + + var luaop = Small.convertBinaryOperator(op); + + outp:appendLine(luaop, ''); + + outp:appendLine("(", ''); + + Small.outputExpression(outp,rhs,false); + + outp:appendLine("))", ''); + } elseif (node.kind == 'UnaryOperator') { + var op = node.content[1]; + var val = node.content[2]; + + outp:appendLine("(", ''); + + var luaop = Small.convertUnaryOperator(op); + outp:appendLine(luaop, ''); + + outp:appendLine("(", ''); + + Small.outputExpression(outp,val,false); + + outp:appendLine("))", ''); + } elseif (node.kind == 'Field') { + var left = node.content[1]; + var right = node.content[2]; + + outp:appendLine("(", ''); + Small.outputExpression(outp, left, false); + outp:appendLine(").", ''); + outp:appendLine(right, ''); + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'Index') { + var left = node.content[1]; + var 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') { + outp:appendLine(node.content, ''); + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'BooleanLiteral') { + outp:appendLine(node.content, ''); + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'NullLiteral') { + outp:appendLine("nil", ''); + if (needsp) outp:appendLine(' ', ''); + } elseif (node.kind == 'Call') { + outp:appendLine("(", ''); + Small.outputExpression(outp, node.content[1], false); + outp:appendLine(")", ''); + + outp:appendLine("(", ''); + + var args = node.content[2]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Small.outputExpression(outp, arg, false); + + if (i != #args) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + } elseif (node.kind == 'MethodCall') { + outp:appendLine("(", ''); + Small.outputExpression(outp, node.content[1], false); + outp:appendLine("):", ''); + outp:appendLine(node.content[2], ''); + outp:appendLine("(", ''); + + var args = node.content[3]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + Small.outputExpression(outp, arg, false); + + if (i != #args) { + outp:appendLine(",", ''); + } + } + + outp:appendLine(")", ''); + } +} + + + +fn Small.outputFunctionName(outp, node) { + if (node.kind == "Field") { + var left = node.content[1]; + var right = node.content[2]; + + Small.outputFunctionName(outp, left); + outp:appendLine(".", ''); + outp:appendLine(right, ''); + } elseif (node.kind == "Index") { + var left = node.content[1]; + var right = node.content[2]; + + Small.outputFunctionName(outp, left); + outp:appendLine("[", ''); + Small.outputExpression(outp, right, false); + outp:appendLine("]", ''); + } elseif (node.kind == "Variable") { + outp:appendLine(node.content, ''); + } elseif (node.kind == "Method") { + var left = node.content[1]; + var right = node.content[2]; + + Small.outputFunctionName(outp, left); + outp:appendLine(":", ''); + outp:appendLine(right, ''); + } +} + + + +fn Small.outputVariable(outp, node) { + if (node.kind == "Field") { + var left = node.content[1]; + var right = node.content[2]; + + Small.outputExpression(outp, left, false); + outp:appendLine(".", ''); + outp:appendLine(right, ''); + } elseif (node.kind == "Index") { + var left = node.content[1]; + var right = node.content[2]; + + Small.outputExpression(outp, left, false); + outp:appendLine("[", ''); + Small.outputExpression(outp, right, false); + outp:appendLine("]", ''); + } elseif (node.kind == "Variable") { + outp:appendLine(node.content, ''); + } +} + +RegisterOutMethod('small', Small); diff --git a/src/optimizer.lake b/src/optimizer.lake new file mode 100644 index 0000000..dd36a63 --- /dev/null +++ b/src/optimizer.lake @@ -0,0 +1,259 @@ +Optimizer = {}; + +fn Optimizer:new() { + return setmetatable({}, {'__index' = self}); +} + +fn Optimizer:run(ast) { // in-place optimization!! + self:optimizeStatement(ast); +} + +fn Optimizer:optimizeStatement(node, justBlock) { + if (node.kind == 'Program') { + for (var i = 1; i <= #node.content; i += 1;) { + var substmt = node.content[i]; + + self:optimizeStatement(substmt); + } + } elseif (node.kind == 'VariableDeclaration') { + // var varnames = node.content[1]; + + // for (var i = 1; i <= #varnames; i += 1;) { + // var vname = varnames[i]; + // } + + var values = node.content[2]; + for (var i = 1; i <= #values; i += 1;) { + var val = values[i]; + + self:optimizeExpression(val); + } + } elseif (node.kind == 'Block') { + for (var i = 1; i <= #node.content; i += 1;) { + var stmt = node.content[i]; + + self:optimizeStatement(stmt, i == #node.content); // TODO: make sure i can do this + } + + if (#node.content == 1 && justBlock) { // eat the block + var subn = node.content[1]; + + node.kind = subn.kind; // should work fine to do this + node.content = subn.content; + node.location = subn.location; + } + } elseif (node.kind == 'IfStatement') { + self:optimizeExpression(node.content[1][1][1]); // condition + + self:optimizeStatement(node.content[1][1][2], true); // base body + + // elseif chain: + for (var i = 2; i <= #node.content[1]; i += 1;) { + var item = node.content[1][i]; + + self:optimizeExpression(item[1]); // condition + + self:optimizeStatement(item[2], true); + } + + // else + if (node.content[2]) { + self:optimizeStatement(node.content[2], true); + } + } elseif (node.kind == 'FunctionDeclaration') { + // TODO: function name? + + self:optimizeStatement(node.content[3], true); + } elseif (node.kind == 'LocalFunctionDeclaration') { + self:optimizeStatement(node.content[3], true); + } elseif (node.kind == 'Return') { + for (var i = 1; i <= #node.content; i += 1;) { + var expr = node.content[i]; + + self:optimizeExpression(expr); + } + } elseif (node.kind == 'Assignment') { + // var vars = node.content[1]; + var vals = node.content[2]; + + for (var i = 1; i <= #vals; i += 1;) { + var val = vals[i]; + + self:optimizeExpression(val); + } + } elseif (node.kind == 'Call') { + self:optimizeExpression(node.content[1]); // function name + + var args = node.content[2]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + self:optimizeExpression(arg); + } + } elseif (node.kind == 'MethodCall') { + self:optimizeExpression(node.content[1]); + + var args = node.content[3]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + self:optimizeExpression(arg); + } + } elseif (node.kind == 'While') { + self:optimizeExpression(node.content[1]); + + self:optimizeStatement(node.content[2], true); + } elseif (node.kind == 'For') { + self:optimizeStatement(node.content[1]); + + self:optimizeExpression(node.content[2]); + + self:optimizeStatement(node.content[4], true); + + self:optimizeStatement(node.content[3], true); + } elseif (node.kind == 'OperatorAssignment') { + self:optimizeExpression(node.content[3]); // rhs + } elseif (node.kind == 'Break') { + // nothing + } elseif (node.kind == 'Foreach') { + // exprs!! + var exprlist = node.content[2]; + + for (var i = 1; i <= #exprlist; i += 1;) { + self:optimizeExpression(exprlist[i]); + } + + var block = node.content[3]; + + self:optimizeStatement(block, true); + } +} + +fn Optimizer:optimizeExpression(node) { + if (node.kind == 'StringLiteral') { + // nothing + } elseif (node.kind == 'ParenthesizedExpression') { + // todo: check if parentheses required + var subn = node.content; + + self:optimizeExpression(subn); + + if (subn.kind == 'ParenthesizedExpression' || subn.kind == 'Variable' || subn.kind == 'NumberLiteral' || subn.kind == 'CompiledNumberLiteral' || subn.kind == 'StringLiteral' || subn.kind == 'CompiledStringLiteral' || subn.kind == 'BooleanLiteral' || subn.kind == 'NullLiteral' || subn.kind == 'TableLiteral') { + node.kind = subn.kind; + node.content = subn.content; + node.location = subn.location; + } + } elseif (node.kind == 'NumberLiteral') { + // nothing + } elseif (node.kind == 'TableLiteral') { + // todo: maybe remove duplicate entries or something? + var listpart = node.content[1]; + var tablepart = node.content[2]; + + for (var i = 1; i <= #listpart; i += 1;) { + var item = listpart[i]; + + self:optimizeExpression(item); + } + + for (var i = 1; i <= #tablepart; i += 1;) { + var item = tablepart[i]; + + self:optimizeExpression(item[1]); + self:optimizeExpression(item[2]); + } + } elseif (node.kind == 'LambdaFunctionLiteral') { + self:optimizeStatement(node.content[2], true); + } elseif (node.kind == 'BinaryOperator') { + // todo: do calculations with constants ahead of time + + var lhs = node.content[2]; + var rhs = node.content[3]; + + self:optimizeExpression(lhs); + self:optimizeExpression(rhs); + + var op = node.content[1]; + if ((lhs.kind == 'NumberLiteral' || lhs.kind == 'CompiledNumberLiteral') && (rhs.kind == 'NumberLiteral' || rhs.kind == 'CompiledNumberLiteral')) { // we can optimize, presumably + var a,b = lhs.content, rhs.content; + if (lhs.kind == 'NumberLiteral') a = Lower.decodeNumber(a); + if (rhs.kind == 'NumberLiteral') b = Lower.decodeNumber(b); + + if (op == '+') { + node.location = lhs.location; + node.kind = 'CompiledNumberLiteral'; + node.content = a+b; + } elseif (op == '-') { + node.location = lhs.location; + node.kind = 'CompiledNumberLiteral'; + node.content = a-b; + } elseif (op == '*') { + node.location = lhs.location; + node.kind = 'CompiledNumberLiteral'; + node.content = a*b; + } elseif (op == '/') { + node.location = lhs.location; + node.kind = 'CompiledNumberLiteral'; + node.content = a/b; + } elseif (op == '%') { + node.location = lhs.location; + node.kind = 'CompiledNumberLiteral'; + node.content = a%b; + } elseif (op == '^') { + node.location = lhs.location; + node.kind = 'CompiledNumberLiteral'; + node.content = a^b; + } + // print(lhs.location:format(), rhs.location:format()); + } elseif ((lhs.kind == 'StringLiteral' || lhs.kind == 'CompiledStringLiteral') && (rhs.kind == 'StringLiteral' || rhs.kind == 'CompiledStringLiteral')) { + var a,b = lhs.content, rhs.content; + if (lhs.kind == 'StringLiteral') a = Lower.decodeString(a); + if (rhs.kind == 'StringLiteral') b = Lower.decodeString(b); + + if (op == '..') { + node.location = lhs.location; + node.kind = 'CompiledStringLiteral'; + node.content = a..b; + } + } + } elseif (node.kind == 'UnaryOperator') { + // todo: again, constants, blah blah blah + var val = node.content[2]; + + self:optimizeExpression(val); + } elseif (node.kind == 'Field') { + var left = node.content[1]; + + self:optimizeExpression(left); + } elseif (node.kind == 'Index') { + var left = node.content[1]; + var right = node.content[2]; + + self:optimizeExpression(left); + self:optimizeExpression(right); + } elseif (node.kind == 'Variable') { + // nothing + } elseif (node.kind == 'BooleanLiteral') { + // nothing + } elseif (node.kind == 'NullLiteral') { + // nun + } elseif (node.kind == 'Call') { + self:optimizeExpression(node.content[1]); // function name + + var args = node.content[2]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + self:optimizeExpression(arg); + } + } elseif (node.kind == 'MethodCall') { + self:optimizeExpression(node.content[1]); + + var args = node.content[3]; + for (var i = 1; i <= #args; i += 1;) { + var arg = args[i]; + + self:optimizeExpression(arg); + } + } +} diff --git a/src/output.lake b/src/output.lake new file mode 100644 index 0000000..662a620 --- /dev/null +++ b/src/output.lake @@ -0,0 +1,40 @@ +OutputMethods = {}; + +fn RegisterOutMethod(name, data) { + OutputMethods[name] = data; +} + +Output = {}; + +fn Output:new(target, method) { + return setmetatable({ + 'target' = target, + 'method' = method, + 'lines' = {}, + 'debug' = {} + }, {'__index' = self}); +} + +fn Output:output(ast) { + var method = OutputMethods[self.method]; + assert(method, 'invalid output method!'); + + method.output(self, ast); +} + +fn Output:addLine(line, debug) { + self.lines[#self.lines+1] = line; + self.debug[#self.debug+1] = debug; +} + +fn Output:appendLine(data, debuginfo) { + if (!self.lines[#self.lines]) { + self.lines[#self.lines+1] = ''; + } + self.lines[#self.lines] = self.lines[#self.lines] .. data; + + if (!self.debug[#self.debug]) { + self.debug[#self.debug+1] = ''; + } + self.debug[#self.debug] = self.debug[#self.debug] .. debuginfo; +} diff --git a/src/parser.lake b/src/parser.lake new file mode 100644 index 0000000..6000082 --- /dev/null +++ b/src/parser.lake @@ -0,0 +1,1035 @@ +Parser = {}; + +fn Parser:new(lexer) { + return setmetatable({ + 'lexer' = lexer + }, {'__index' = self}); +} + +fn Parser:parse() { + var parts = {}; + var start; + + while (true) { + var tok, err = self.lexer:peek(); + if (!tok) return null, err; + if (!start) start = tok.location:clone(); + + if (tok.kind == 'eof') break; + + var node, nerr = self:parseStatement(); + if (!node) return null, nerr; + + parts[#parts+1] = node; + } + + return Node:new('Program', parts, start); +} + +fn Parser:parseBlockOrStatement() { + var checktok, checkerr = self.lexer:peek(); + if (!checktok) return null, checkerr; + + if (checktok.kind == 'symbol' && checktok.content == '{') { + var block, berr = self:parseBlock(); + if (!block) return null, berr; + + return block; + } + + var stmt, sterr = self:parseStatement(); + if (!stmt) return null, sterr; + + return stmt; +} + +fn Parser:parseBlock() { + var starttok, sterr = self.lexer:peek(); + if (!starttok) return null, sterr; + + if (starttok.kind != 'symbol' || starttok.content != '{') + return null, Error:new('expected { to start block', 'parser', starttok.location:clone()); + self.lexer:consume(); + + var parts = {}; + + while (true) { + var detect, deterr = self.lexer:peek(); + if (!detect) return null, deterr; + + if (detect.kind == 'symbol' && detect.content == '}') + break; + + if (detect.kind == 'eof' && detect.content == 'eof') + return null, Error:new('unexpected eof in a block', 'parser', detect.location:clone()); + + var stmt, stmterr = self:parseStatement(); + if (!stmt) return null, stmterr; + + parts[#parts+1] = stmt; + } + + var check, cherr = self.lexer:peek(); + if (!check) return null, cherr; + + if (check.kind != 'symbol' || check.content != '}') + return null, Error:new('block ended in an invalid way', 'parser', check.location:clone()); + self.lexer:consume(); + + return Node:new('Block', parts, starttok.location:clone()); +} + +fn Parser:parseFunctionName() { + var tok, terr = self.lexer:peek(); + if (!tok) return null, terr; + + if (tok.kind != 'identifier') + return null, Error:new('expected function name to start with identifier', 'parser', tok.location:clone()); + self.lexer:consume(); + + var node = Node:new('Variable', tok.content, tok.location:clone()); + + while (true) { + var check, checkerr = self.lexer:peek(); + if (!check) return null, checkerr; + + if (check.kind == 'symbol' && check.content == '.') { + self.lexer:consume(); + + var idx, idxerr = self.lexer:peek(); + if (!idx) return null, idxerr; + + if (idx.kind != 'identifier') + return null, Error:new('expected identifier after . in function name', 'parser', idx.location:clone()); + self.lexer:consume(); + + node = Node:new('Field', {node, idx.content}, check.location:clone()); + } elseif (check.kind == 'symbol' && check.content == '[') { + self.lexer:consume(); + + var expr, err = self:parseExpression(); + if (!expr) return null, err; + + var endcheck, endcheckerr = self.lexer:peek(); + if (!endcheck) return null, endcheckerr; + + if (endcheck.kind != 'symbol' || endcheck.content != ']') + return null, Error:new('expected ] to close indexing in function name', 'parser', endcheck.location:clone()); + self.lexer:consume(); + + node = Node:new('Index', {node, expr}, check.location:clone()); + } elseif (check.kind == 'symbol' && check.content == ':') { + self.lexer:consume(); + + var methodname, methoderr = self.lexer:peek(); + if (!methodname) return null, methoderr; + + if (methodname.kind != 'identifier') + return null, Error:new('expected identifier for method name in function name', 'parser', methodname.location:clone()); + self.lexer:consume(); + + node = Node:new('Method', {node, methodname.content}, check.location:clone()); + + break; + } else { + break; // summin else; it's the end + } + } + + return node; +} + +fn Parser:parseStatement() { + var tok, err = self.lexer:peek(); + if (!tok) return null, error; + + if (tok.kind == 'keyword' && tok.content == 'var') { + self.lexer:consume(); + + var checker,checkererr = self.lexer:peek(); + if (!checker) return null, checkererr; + + if (checker.kind == 'keyword' && checker.content == 'fn') { + self.lexer:consume(); + + var fnname, fnnerr = self.lexer:peek(); + if (!fnname) return null, fnnerr; + + if (fnname.kind != 'identifier') + return null, Error:new('expected identifier as function name after var fn', 'parser', fnname.location:clone()); + self.lexer:consume(); + + var paren, parerr = self.lexer:peek(); + if (!paren) return null, parerr; + + if (paren.kind != 'symbol' || paren.content != '(') + return null, Error:new('expected ( to start parameter list for local function declaration', 'parser', paren.location:clone()); + self.lexer:consume(); + + var params = {}; + + while (true) { + var checktok, checkerr = self.lexer:peek(); + if (!checktok) return null, checkerr; + + if (checktok.kind == 'symbol' && checktok.content == ')') { + break; + } elseif (checktok.kind == 'identifier') { + params[#params+1] = checktok.content; + self.lexer:consume(); + } elseif (checktok.kind == 'eof' && checktok.content == 'eof') { + break; + } + + var nexttok, nexterr = self.lexer:peek(); + if (!nexttok) return null, nexterr; + + if (nexttok.kind != 'symbol' || nexttok.content != ',') { + break; + } + self.lexer:consume(); + } + + var clostok, closerr = self.lexer:peek(); + if (!clostok) return null, closerr; + + if (clostok.kind != 'symbol' || clostok.content != ')') { + return null, Error:new('expected closing parenthesis after location function parameters', 'parser', clostok.location:clone()); + } + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + return Node:new('LocalFunctionDeclaration', {fnname, params, block}, tok.location:clone()); + } + + // just a basic ol var a = b; + var varnames = {}; + + while (true) { + var vntok, vnerr = self.lexer:peek(); + if (!vntok) return null, vnerr; + + if (vntok.kind == 'identifier') { + self.lexer:consume(); + varnames[#varnames+1] = {vntok.content, vntok.location:clone()}; + } else { + return null, Error:new('not an identifier in varname list in variable declaration', 'parser', vntok.location:clone()); + } + + var commatok, commaerr = self.lexer:peek(); + if (!commatok) return null, commaerr; + + if (commatok.kind == 'symbol' && commatok.content == ',') { + self.lexer:consume(); + } else { + break; + } + } + + var splittok, spliterr = self.lexer:peek(); + if (!splittok) return null, spliterr; + + if (splittok.kind != 'symbol') + return null, Error:new('expected = or ; after variable names in variable declaration', 'parser', splittok.location:clone()); + + if (splittok.content == ';') { + self.lexer:consume(); + return Node:new('VariableDeclaration', {varnames, {}}, tok.location:clone()); + } + + if (splittok.content == '=') { + self.lexer:consume(); + + var values = {}; + + while (true) { + var expr, exerr = self:parseExpression(); + if (!expr) return null, exerr; + + values[#values + 1] = expr; + + var commatok, commaerr = self.lexer:peek(); + if (!commatok) return null, commaerr; + + if (commatok.kind == 'symbol' && commatok.content == ',') { + self.lexer:consume(); + } else { + break; + } + } + + var smc, smcerr = self.lexer:peek(); + if (!smc) return null, smcerr; + + if (smc.kind != 'symbol' || smc.content != ';') + return null, Error:new('expected semicolon after variable declaration', 'parser', smc.location:clone()); + self.lexer:consume(); + + return Node:new('VariableDeclaration', {varnames, values}, tok.location:clone()); + } + + return null, Error:new('expected = or ; after variable names in declaration', 'parser', splittok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'if') { + self.lexer:consume(); + + var checks = {}; + + var partok, parerr = self.lexer:peek(); + if (!partok) return null, parerr; + + if (partok.kind != 'symbol' || partok.content != '(') + return null, Error:new('expected ( after if keyword', 'parser', partok.location:clone()); + self.lexer:consume(); + + var condition, conderr = self:parseExpression(); + if (!condition) return null, conderr; + + var closepart, closeparerr = self.lexer:peek(); + if (!closepart) return null, closeparerr; + + if (closepart.kind != 'symbol' || closepart.content != ')') + return null, Error:new('expected ) to close if statement condition', 'parser', closepart.location:clone()); + self.lexer:consume(); + + var body, bodyerr = self:parseBlockOrStatement(); + if (!body) return null, bodyerr; + + checks[#checks+1] = {condition, body}; + + while (true) { + var checkt, checkerr = self.lexer:peek(); + if (!checkt) return null, checkerr; + + if (checkt.kind == 'keyword' && checkt.content == 'elseif') { + self.lexer:consume(); + + var partok2, parerr2 = self.lexer:peek(); + if (!partok2) return null, parerr2; + + if (partok2.kind != 'symbol' || partok2.content != '(') + return null, Error:new('expected ( after elseif keyword', 'parser', partok2.location:clone()); + self.lexer:consume(); + + var condition2, conderr2 = self:parseExpression(); + if (!condition2) return null, conderr2; + + var closepart2, closeparerr2 = self.lexer:peek(); + if (!closepart2) return null, closeparerr2; + + if (closepart2.kind != 'symbol' || closepart2.content != ')') + return null, Error:new('expected ) to close elseif condition', 'parser', closepart2.location:clone()); + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + checks[#checks+1] = {condition2, block}; + } else { + break; + } + } + + var elset, elseerr = self.lexer:peek(); + if (!elset) return null, elseerr; + + var elseblock; + if (elset.kind == 'keyword' && elset.content == 'else') { + self.lexer:consume(); + + var elseb, elseberr = self:parseBlockOrStatement(); + if (!elseb) return null, elseberr; + + elseblock = elseb; + } + + return Node:new('IfStatement', {checks, elseblock}, tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'while') { + self.lexer:consume(); + + var paren, perr = self.lexer:peek(); + if (!paren) { + return nil, perr; + } + + if (paren.kind != 'symbol' || paren.content != '(') + return null, Error:new('expected ( after start of while', 'parser', paren.location:clone()); + self.lexer:consume(); + + var cond, conderr = self:parseExpression(); + if (!cond) return null, conderr; + + var closep, closeperr = self.lexer:peek(); + if (!closep) return null, closeperr; + + if (closep.kind != 'symbol' || closep.content != ')') + return null, Error:new('expected ) to close while loop condition', 'parser', closep.location:clone()); + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + return Node:new('While', {cond, block}, tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'for') { + self.lexer:consume(); + + var paren, perr = self.lexer:peek(); + if (!paren) return null, perr; + + if (paren.kind != 'symbol' || paren.content != '(') + return null, Error:new('expected ( after start of while', 'parser', paren.location:clone()); + self.lexer:consume(); + + var decl, declerr = self:parseStatement(); + if (!decl) return null, declerr; + + var check, checkerr = self:parseExpression(); + if (!check) return null, checkerr; + + var semi, semierr = self.lexer:peek(); + if (!semi) return null, semierr; + + if (semi.kind != 'symbol' || semi.content != ';') + return null, Error:new('expected semicolon after for loop condition', 'parser', semi.location:clone()); + self.lexer:consume(); + + var modifier, moderr = self:parseStatement(); + if (!modifier) return null, moderr; + + var close, closeerr = self.lexer:peek(); + if (!close) return null, closeerr; + + if (close.kind != 'symbol' || close.content != ')') + return null, Error:new('expected ) to close for loop condition part', 'parser', close.location:clone()); + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + return Node:new('For', {decl, check, modifier, block}, tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'fn') { + self.lexer:consume(); + + var fnname, fnnerr = self:parseFunctionName(); + if (!fnname) return null, fnnerr; + + var paren, parerr = self.lexer:peek(); + if (!paren) return null, parerr; + + if (paren.kind != 'symbol' || paren.content != '(') + return null, Error:new('expected ( to open function parameter list', 'parser', paren.location:clone()); + self.lexer:consume(); + + var params = {}; + + while (true) { + var checktok, checkerr = self.lexer:peek(); + if (!checktok) return null, checkerr; + + if (checktok.kind == 'symbol' && checktok.content == ')') { + break; + } elseif (checktok.kind == 'identifier') { + params[#params+1] = checktok.content; + self.lexer:consume(); + } elseif (checktok.kind == 'eof' && checktok.content == 'eof') { + break; + } + + var nexttok, nexterr = self.lexer:peek(); + if (!nexttok) return null, nexterr; + + if (nexttok.kind != 'symbol' || nexttok.content != ',') { + break; + } + self.lexer:consume(); + } + + var clostok, closerr = self.lexer:peek(); + if (!clostok) return null, closerr; + + if (clostok.kind != 'symbol' || clostok.content != ')') + return null, Error:new('expected closing parenthesis after function parameters', 'parser', clostok.location:clone()); + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + return Node:new('FunctionDeclaration', {fnname, params, block}, tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'return') { + self.lexer:consume(); + + var parts = {}; + + while (true) { + var chtok, cherr = self.lexer:peek(); + if (!chtok) return null, cherr; + + if (chtok.kind == 'symbol' && chtok.content == ';') { + break; + } + + var expr, exprerr = self:parseExpression(); + if (!expr) return null, exprerr; + + parts[#parts+1] = expr; + + var checkt, checkerr = self.lexer:peek(); + if (!checkt) return null, checkerr; + + if (checkt.kind != 'symbol' || checkt.content != ',') { + break; + } + + self.lexer:consume(); + } + + var semi, semierr = self.lexer:peek(); + if (!semi) return null, semierr; + + if (semi.kind != 'symbol' || semi.content != ';') + return null, Error:new('expected ; after return statement', 'parser', semi.location:clone()); + self.lexer:consume(); + + return Node:new('Return', parts, tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'do') { + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + if (block.kind == 'Block') { + return block; + } else { + return Node:new('Block', {block}, tok.location:clone()); + } + } elseif ((tok.kind == 'symbol' && tok.content == '(') || (tok.kind == 'identifier')) { + var expr, exerr = self:parseExpression(); + if (!expr) return null, exerr; + + var nextok, nexerr = self.lexer:peek(); + if (!nextok) return null, nexerr; + + if (nextok.kind == 'symbol' && (nextok.content == ',' || nextok.content == '=')) { + var vars = {expr}; + + while (true) { + var tkn, tknerr = self.lexer:peek(); + if (!tkn) return null, tknerr; + + if (tkn.kind == 'symbol' && tkn.content == ',') { + self.lexer:consume(); + + var checkt, checkerr = self.lexer:peek(); + if (!checkt) return null, checkerr; + + if ((checkt.kind == 'symbol' && checkt.content == '(') || checkt.kind == 'identifier') { + var nexpr, nerr = self:parseExpression(); + if (!nexpr) return null, nerr; + + vars[#vars+1] = nexpr; + } else { + return null, Error:new('expected expression starting with ( or an identifier after comma in assignment', 'parser', checkt.location:clone()); + } + } else { + break; + } + } + + var equaltok, equalerr = self.lexer:peek(); + if (!equaltok) return null, equalerr; + + if (equaltok.kind != 'symbol' || equaltok.content != '=') + return nil, Error:new('expected = in assignment', 'parser', equaltok.location:clone()); + self.lexer:consume(); + + var values = {}; + + while (true) { + var assexpr, asserr = self:parseExpression(); + if (!assexpr) return null, asserr; + + values[#values + 1] = assexpr; + + var commatok, commaerr = self.lexer:peek(); + if (!commatok) return null, commaerr; + + if (commatok.kind == 'symbol' && commatok.content == ',') { + self.lexer:consume(); + } else { + break; + } + } + + var semi, semierr = self.lexer:peek(); + if (!semi) return null, semierr; + + if (semi.kind != 'symbol' || semi.content != ';') + return null, Error:new('expected semicolon after assignment', 'parser', semi.location:clone()); + self.lexer:consume(); + + return Node:new('Assignment', {vars, values}, equaltok.location:clone()); + } elseif (nextok.kind == 'symbol' && (nextok.content == '&&=' || nextok.content == "||=" || nextok.content == "+=" || nextok.content == "-=" || nextok.content == "*=" || nextok.content == "/=" || nextok.content == "%=" || nextok.content == "^=" || nextok.content == "..=")) { + self.lexer:consume(); + + var operator = nextok.content:sub(1,-2); + + if (expr.kind != 'Variable' && expr.kind != 'Field' && expr.kind != 'Index') + return null, Error:new('can only use operator assignments on variables, fields or indexes', 'parser', expr.location:clone()); + + var rhs, rhserr = self:parseExpression(); + if (!rhs) return null, rhserr; + + var semi, semierr = self.lexer:peek(); + if (!semi) return null, semierr; + + if (semi.kind != 'symbol' || semi.content != ';') + return nil, Error:new('expected semicolon after operator assignment', 'parser', semi.location:clone()); + self.lexer:consume(); + + return Node:new('OperatorAssignment', {expr, operator, rhs}, nextok.location:clone()); + } + + if (expr.kind != 'Call' && expr.kind != 'MethodCall') + return null, Error:new('loose expression must be a call or a method call', 'parser', expr.location:clone()); + + var semi, semierr = self.lexer:peek(); + if (!semi) return null, semierr; + + if (semi.kind != 'symbol' || semi.content != ';') + return null, Error:new('expected semicolon after loose expression', 'parser', semi.location:clone()); + self.lexer:consume(); + + return expr; + } elseif (tok.kind == 'keyword' && tok.content == 'break') { + self.lexer:consume(); + + var semi, semierr = self.lexer:peek(); + if (!semi) return null, semierr; + + if (semi.kind != 'symbol' || semi.content != ';') + return null, Error:new('expected semicolon after break', 'parser', semi.location:clone()); + self.lexer:consume(); + + return Node:new('Break', {}, tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'foreach') { + self.lexer:consume(); + + var openpar, operr = self.lexer:peek(); + if (!openpar) return null, operr; + + if (openpar.kind != 'symbol' || openpar.content != '(') + return null, Error:new('expected ( after foreach keyword', 'parser', openpar.location:clone()); + self.lexer:consume(); + + var varnames = {}; + + while (true) { + var vname, vnamerr = self.lexer:peek(); + if (!vname) return null, vnamerr; + + if (vname.kind != 'identifier') + return null, Error:new('expected identifier in varname list in foreach loop', 'parser', vname.location:clone()); + self.lexer:consume(); + + varnames[#varnames+1] = vname.content; + + var comma, commaerr = self.lexer:peek(); + if (!comma) return null, commaerr; + + if (comma.kind != 'symbol' || comma.content != ',') + break; + self.lexer:consume(); + } + + var eqtok, eqerr = self.lexer:peek(); + if (!eqtok) return null, eqerr; + + if (eqtok.kind != 'symbol' || eqtok.content != '=') + return null, Error:new('expected = after varname list in foreach loop', 'parser', eqtok.location:clone()); + self.lexer:consume(); + + var exprs = {}; + + while (true) { + var expr, exerr = self:parseExpression(); + if (!expr) return null, exerr; + + exprs[#exprs+1] = expr; + + var commatok, commaerr = self.lexer:peek(); + if (!commatok) return null, commaerr; + + if (commatok.kind != 'symbol' || commatok.content != ',') + break; + self.lexer:consume(); + } + + var closepar, clperr = self.lexer:peek(); + if (!closepar) return null, clperr; + + if (closepar.kind != 'symbol' || closepar.content != ')') + return null, Error:new('expected ) to close expression list for foreach loop', 'parser', closepar.location:clone()); + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + return Node:new('Foreach', {varnames, exprs, block}, tok.location:clone()); + } + + return null, Error:new('invalid statement', 'parser', tok.location:clone()); +} + +fn Parser:parseExpression() { + return self:parseOperatorExpression(); +} + +fn Parser:prefixOperatorBindingPower(token) { + if (token.kind == 'symbol') + if (token.content == '-') { + return 45; + } elseif (token.content == '#') { + return 45; + } elseif (token.content == '!') { + return 45; + } + + return null; +} + +fn Parser:infixOperatorBindingPower(token) { + if (token.kind == 'symbol') + if (token.content == '+' || token.content == '-') return 10, 20; + elseif (token.content == '*' || token.content == '/' || token.content == '%') return 30, 40; + elseif (token.content == '^') return 60, 50; + elseif (token.content == '..') return 0, -10; + elseif (token.content == '<' || token.content == '>' || token.content == '<=' || + token.content == '>=' || token.content == '!=' || token.content == '==') return -30, -20; + elseif (token.content == '&&') return -50, -40; + elseif (token.content == '||') return -70, -60; +} + +fn Parser:parseOperatorExpression(minbp, predlhs) { + minbp ||= -math.huge; + + var tok, err = self.lexer:peek(); + if (!tok) return null, err; + + if (tok.kind == 'eof') + return null, Error:new('expected expression but found eof', 'parser', tok.location:clone()); + + var lhs = predlhs; + + if ((lhs == null) && self:prefixOperatorBindingPower(tok)) { + self.lexer:consume(); + + var prefixbp = self:prefixOperatorBindingPower(tok); + + var rightthing, righterr = self:parseOperatorExpression(prefixbp); + if (!rightthing) return null, righterr; + + lhs = Node:new('UnaryOperator', {tok.content, rightthing}, tok.location:clone()); + } + + if (lhs == null) { + var newlhs, newerr = self:parseRawExpression(); + if (!newlhs) return null, newerr; + lhs = newlhs; + } + + while (true) { + var newtok, lexerr = self.lexer:peek(); + if (!newtok) return null, lexerr; + + var leftbp, rightbp = self:infixOperatorBindingPower(newtok); + + if (!leftbp || !rightbp) break; + if (leftbp < minbp) break; + + self.lexer:consume(); + + var rhs, rhserr = self:parseOperatorExpression(rightbp); + if (!rhs) return null, rhserr; + + lhs = Node:new('BinaryOperator', {newtok.content, lhs, rhs}, newtok.location:clone()); + } + + return lhs; +} + +fn Parser:parseTableLiteral() { + var tok, tokerr = self.lexer:peek(); + if (!tok) return null, tokerr; + + if (tok.kind != 'symbol' || tok.content != '{') + return null, Error:new('expected { to start table literal', 'parser', tok.location:clone()); + self.lexer:consume(); + + var listpart = {}; + var tableentries = {}; + + while (true) { + var check, checkerr = self.lexer:peek(); + if (!check) return null, checkerr; + + if (check.kind == 'symbol' && check.content == '}') + break; + + var expr, exerr = self:parseExpression(); + if (!expr) return null, exerr; + + var check2, check2err = self.lexer:peek(); + if (!check2) return null, check2err; + + if (check2.kind == 'symbol' && check2.content == '=') { + self.lexer:consume(); + + var set, seterr = self:parseExpression(); + if (!set) return null, seterr; + + tableentries[#tableentries+1] = {expr, set}; + } else { + listpart[#listpart+1] = expr; + } + + var commatok, commaerr = self.lexer:peek(); + if (!commatok) return null, commaerr; + + if (commatok.kind != 'symbol' || commatok.content != ',') + break; + self.lexer:consume(); + } + + var closetok, closeerr = self.lexer:peek(); + if (!closetok) return null, closeerr; + + if (closetok.kind != 'symbol' || closetok.content != '}') + return null, Error:new('expected } to close table literal', 'parser', closetok.location:clone()); + self.lexer:consume(); + + return Node:new('TableLiteral', {listpart, tableentries}, tok.location:clone()); +} + +fn Parser:parseLambdaFunction() { + var fnt, fnerr = self.lexer:peek(); + if (!fnt) return null, fnerr; + + if (fnt.kind != 'keyword' || fnt.content != 'fn') + return null, Error:new('expected fn to start lambda function', 'parser', fnt.location:clone()); + self.lexer:consume(); + + var paren, parerr = self.lexer:peek(); + if (!paren) return null, parerr; + + if (paren.kind != 'symbol' || paren.content != '(') + return null, Error:new('expected ( to open parameter list for lambda function', 'parser', paren.location:clone()); + self.lexer:consume(); + + var params = {}; + + while (true) { + var checktok, checkerr = self.lexer:peek(); + if (!checktok) return null, checkerr; + + if (checktok.kind == 'symbol' && checktok.content == ')') { + break; + } elseif (checktok.kind == 'identifier') { + params[#params+1] = checktok.content; + self.lexer:consume(); + } elseif (checktok.kind == 'eof' && checktok.content == 'eof') { + break; + } + + var nexttok, nexterr = self.lexer:peek(); + if (!nexttok) return null, nexterr; + + if (nexttok.kind != 'symbol' || nexttok.content != ',') + break; + self.lexer:consume(); + } + + var clostok, closerr = self.lexer:peek(); + if (!clostok) return null, closerr; + + if (clostok.kind != 'symbol' || clostok.content != ')') + return null, Error:new('expected closing parenthesis after lambda function parameters', 'parser', clostok.location:clone()); + self.lexer:consume(); + + var block, berr = self:parseBlockOrStatement(); + if (!block) return null, berr; + + return Node:new('LambdaFunctionLiteral', {params, block}, fnt.location:clone()); +} + +fn Parser:parseRawExpression() { + var tok, err = self.lexer:peek(); + if (!tok) return null, err; + + if (tok.kind == 'string') { + self.lexer:consume(); + return Node:new('StringLiteral', tok.content, tok.location:clone()); + } elseif (tok.kind == 'number') { + self.lexer:consume(); + return Node:new('NumberLiteral', tok.content, tok.location:clone()); + } elseif (tok.kind == 'identifier') { + self.lexer:consume(); + var node = Node:new('Variable', tok.content, tok.location:clone()); + + return self:parseComplexExpression(node); + } elseif (tok.kind == 'keyword' && tok.content == 'true') { + self.lexer:consume(); + return Node:new('BooleanLiteral', 'true', tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'false') { + self.lexer:consume(); + return Node:new('BooleanLiteral', 'false', tok.location:clone()); + } elseif (tok.kind == 'keyword' && tok.content == 'null') { + self.lexer:consume(); + return Node:new('NullLiteral', null, tok.location:clone()); + } elseif (tok.kind == 'symbol' && tok.content == '{') { + return self:parseTableLiteral(); + } elseif (tok.kind == 'keyword' && tok.content == 'fn') { + return self:parseLambdaFunction(); + } elseif (tok.kind == 'symbol' && tok.content == '(') { + self.lexer:consume(); + + var expr, exerr = self:parseExpression(); + if (!expr) return null, exerr; + + var closet, closeerr = self.lexer:peek(); + if (!closet) return null, closeerr; + + if (closet.kind != 'symbol' || closet.content != ')') + return null, Error:new('expected ) to close parenthesized expression', 'parser', closet.location:clone()); + self.lexer:consume(); + + var node = Node:new('ParenthesizedExpression', expr, tok.location:clone()); + + var newnode, nodeerr = self:parseComplexExpression(node); + if (!newnode) return null, nodeerr; + + return newnode; + } else { + return null, Error:new('invalid expression', 'parser', tok.location:clone()); + } +} + +fn Parser:parseComplexExpression(node) { + while (true) { + var tok, err = self.lexer:peek(); + if (!tok) return null, err; + + if (tok.kind == 'symbol' && tok.content == '.') { + self.lexer:consume(); + + var field,ferr = self.lexer:peek(); + if (!field) return null, ferr; + + if (field.kind != 'identifier') + return null, Error:new('expected identifier after . in complex expression', 'parser', field.location:clone()); + self.lexer:consume(); + + node = Node:new('Field', {node, field.content}, tok.location:clone()); + } elseif (tok.kind == 'symbol' && tok.content == '[') { + self.lexer:consume(); + + var expr, exerr = self:parseExpression(); + if (!expr) return null, exerr; + + var close, closerr = self.lexer:peek(); + if (!close) return null, closerr; + + if (close.kind != 'symbol' || close.content != ']') + return null, Error:new('expected ] to close indexing in complex expression', 'parser', close.location:clone()); + self.lexer:consume(); + + node = Node:new('Index', {node, expr}, tok.location:clone()); + } elseif (tok.kind == 'symbol' && tok.content == ':') { + self.lexer:consume(); + + var methodname, methoderr = self.lexer:peek(); + if (!methodname) return null, methoderr; + + if (methodname.kind != 'identifier') + return null, Error:new('expected identifier for method name', 'parser', methodname.location:clone()); + self.lexer:consume(); + + var parentok, parenerr = self.lexer:peek(); + if (!parentok) return null, parenerr; + + if (parentok.kind != 'symbol' || parentok.content != '(') + return null, Error:new('expected ( to open method call argument list', 'parser', parentok.location:clone()); + self.lexer:consume(); + + var parts = {}; + while (true) { + var chtok, cherr = self.lexer:peek(); + if (!chtok) return null, cherr; + + if (chtok.kind == 'symbol' && chtok.content == ')') break; + + var expr, experr = self:parseExpression(); + if (!expr) return null, experr; + + parts[#parts+1] = expr; + + var ntok, nerr = self.lexer:peek(); + if (!ntok) return null, nerr; + + if (ntok.kind == 'symbol' && ntok.content == ')') + break; + elseif (ntok.kind == 'symbol' && ntok.content == ',') + self.lexer:consume(); + else + return null, Error:new('expected ) or , after argument in method call argument list', 'parser', ntok.location:clone()); + } + + var closeparen, closeparenerr = self.lexer:peek(); + if (!closeparen) return null, closeparenerr; + + if (closeparen.kind != 'symbol' || closeparen.content != ')') + return null, Error:new('expected ) to close argument list for method call', 'parser', closeparen.location:clone()); + self.lexer:consume(); + + node = Node:new('MethodCall', {node, methodname.content, parts}, tok.location:clone()); + } elseif (tok.kind == 'symbol' && tok.content == '(') { + self.lexer:consume(); + + var parts = {}; + + while (true) { + var chtok, cherr = self.lexer:peek(); + if (!chtok) return null, cherr; + + if (chtok.kind == 'symbol' && chtok.content == ')') + break; + + var expr, experr = self:parseExpression(); + if (!expr) return null, experr; + + parts[#parts+1] = expr; + + var ntok, nerr = self.lexer:peek(); + if (!ntok) return null, nerr; + + if (ntok.kind == 'symbol' && ntok.content == ')') + break; + elseif (ntok.kind == 'symbol' && ntok.content == ',') + self.lexer:consume(); + else + return null, Error:new('expected ) or , after call argument list', 'parser', ntok.location:clone()); + } + + var paren, parenerr = self.lexer:peek(); + if (!paren) return null, parenerr; + + if (paren.kind != 'symbol' || paren.content != ')') + return null, Error:new('expected ) to close argument list for call', 'parser', paren.location:clone()); + self.lexer:consume(); + + node = Node:new('Call', {node, parts}, tok.location:clone()); + } else { + return node; + } + } +} diff --git a/src/types.lake b/src/types.lake new file mode 100644 index 0000000..f3c1403 --- /dev/null +++ b/src/types.lake @@ -0,0 +1,76 @@ +Location = {}; + +fn Location:new(file) { + return setmetatable({'file' = file, 'column' = 1, 'line' = 1}, {'__index' = self}); +} + +fn Location:clone() { + return setmetatable({ + 'file' = self.file, + 'column' = self.column, + 'line' = self.line + }, getmetatable(self)); +} + +fn Location:skip(chr) { + for (var i = 1; i <= #chr; i += 1;) { + self.column += 1; + var c = chr:sub(i,i); + + if (c == "\n") { + self.column = 1; + self.line += 1; + } + } +} + +fn Location:format() { + return self.file .. ':' .. self.line .. ':' .. self.column; +} + + + +Token = {}; + +fn Token:new(kind, content, location) { + return setmetatable({ + 'kind' = kind, + 'content' = content, + 'location' = location + }, {'__index' = self}); +} + + + +Error = {}; + +fn Error:new(message, origin, location) { + return setmetatable({ + 'message' = message, + 'origin' = origin, + 'location' = location + }, {'__index' = self}); +} + +fn Error:format() { + var capitalizedOrigin = self.origin:sub(1,1):upper() .. self.origin:sub(2):lower(); + var locationString = self.location:format(); + + return capitalizedOrigin .. ' error: ' .. locationString .. ': ' .. self.message; +} + +fn Error:print() { + print("\x1b[31m" .. self:format() .. "\x1b[0m"); +} + + + +Node = {}; + +fn Node:new(kind, content, location) { + return setmetatable({ + 'kind' = kind, + 'content' = content, + 'location' = location, + }, {'__index' = self}); +} diff --git a/src/util.lake b/src/util.lake new file mode 100644 index 0000000..44af9ce --- /dev/null +++ b/src/util.lake @@ -0,0 +1,58 @@ +var deckey = '0123456789'; +fn DecEncode(num, minchars) { + var outp = ''; + + while (num > 0) { + var dig = num % (#deckey); + var ch = deckey:sub(dig + 1, dig + 1); + + outp = ch .. outp; + + num = math.floor(num / (#deckey)); + } + + if (minchars) { + while (#outp < minchars) { + outp = deckey:sub(1,1) .. outp; + } + } + + return outp; +} + +var hexkey = '0123456789abcdef'; +fn HexEncode(num, minchars) { + var outp = ''; + + while (num > 0) { + var dig = num % (#hexkey); + var ch = hexkey:sub(dig + 1, dig + 1); + + outp = ch .. outp; + + num = math.floor(num / (#hexkey)); + } + + if (minchars) { + while (#outp < minchars) { + outp = hexkey:sub(1,1) .. outp; + } + } + + return outp; +} + +fn HexDecode(hexstr) { + var out = 0; + + for (var i = 1; i <= #hexstr; i += 1;) { + var char = hexstr:sub(i,i):lower(); + + var pos = hexkey:find(char, null, true) - 1; + + out *= 16; + out += pos; + } + + return out; +}