initial commit

This commit is contained in:
2026-04-25 16:50:59 +02:00
commit 47c84ae3c8
11 changed files with 3484 additions and 0 deletions

11
main.lake Normal file
View File

@@ -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');

168
src/cli.lake Normal file
View File

@@ -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();
}

326
src/lexer.lake Normal file
View File

@@ -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;
}

155
src/lower.lake Normal file
View File

@@ -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;
}

667
src/methods/debug.lake Normal file
View File

@@ -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);

689
src/methods/small.lake Normal file
View File

@@ -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);

259
src/optimizer.lake Normal file
View File

@@ -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);
}
}
}

40
src/output.lake Normal file
View File

@@ -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;
}

1035
src/parser.lake Normal file

File diff suppressed because it is too large Load Diff

76
src/types.lake Normal file
View File

@@ -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});
}

58
src/util.lake Normal file
View File

@@ -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;
}