Initial commit
This commit is contained in:
commit
c847863034
7
bon.lua
Normal file
7
bon.lua
Normal file
@ -0,0 +1,7 @@
|
||||
local next_unique_id = 0
|
||||
local function unique_name()
|
||||
next_unique_id = next_unique_id + 1
|
||||
return "__" .. "KY" .. next_unique_id
|
||||
end
|
||||
|
||||
return {unique_name = unique_name}
|
81
des.lua
Normal file
81
des.lua
Normal file
@ -0,0 +1,81 @@
|
||||
local unique_name = require"bon".unique_name
|
||||
|
||||
local function pass_destandardify(chu)
|
||||
local function is_builtin_method(c)
|
||||
if c.type ~= 'dot' then
|
||||
return false
|
||||
end
|
||||
|
||||
if c.a.et and c.a.et.type == 'list' and c.b == 'pop' then
|
||||
return {change_stmt = function(stmt)
|
||||
assert(stmt.type == 'call')
|
||||
|
||||
local temp = {et = stmt.what.a.et, name = unique_name()}
|
||||
|
||||
return {
|
||||
{
|
||||
type = 'let',
|
||||
var = temp,
|
||||
expr = stmt.what.a
|
||||
},
|
||||
{
|
||||
type = 'assign',
|
||||
dest = {
|
||||
type = 'index',
|
||||
what = {type = 'var', which = temp, et = temp.et},
|
||||
idx = {type = 'lengthof', sub = {type = 'var', which = temp, et = temp.et}, et = {type = 'integer'}}
|
||||
},
|
||||
src = {type = 'null', et = {type = 'null'}}
|
||||
},
|
||||
}
|
||||
end}
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if chu.type == "chunk" then
|
||||
for k, t in pairs(chu.types) do
|
||||
for _, m in pairs(t.members) do
|
||||
if m.type == "field" then
|
||||
-- Do nothing.
|
||||
elseif m.type == "static" then
|
||||
pass_destandardify(m.value)
|
||||
else
|
||||
error("Invalid AST")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local stmtIdx = 1
|
||||
while stmtIdx <= #chu.stmts do
|
||||
local stmt = chu.stmts[stmtIdx]
|
||||
|
||||
local builtinmethod = stmt.type == 'call' and is_builtin_method(stmt.what)
|
||||
if builtinmethod then
|
||||
table.remove(chu.stmts, stmtIdx)
|
||||
for p, o in ipairs(builtinmethod.change_stmt(stmt)) do
|
||||
table.insert(chu.stmts, stmtIdx + p - 1, o)
|
||||
end
|
||||
else
|
||||
pass_destandardify(stmt)
|
||||
stmtIdx = stmtIdx + 1
|
||||
end
|
||||
end
|
||||
elseif chu.type == 'function' then
|
||||
pass_destandardify(chu.chunk)
|
||||
elseif chu.type == 'if' then
|
||||
for i = 1, #chu do
|
||||
pass_destandardify(chu[i].chu)
|
||||
end
|
||||
if chu.elsa then
|
||||
pass_destandardify(chu.elsa)
|
||||
end
|
||||
elseif chu.type == 'fori' then
|
||||
pass_destandardify(chu.chu)
|
||||
elseif chu.type == 'loop' then
|
||||
pass_destandardify(chu.chu)
|
||||
end
|
||||
end
|
||||
|
||||
return pass_destandardify
|
224
gen.lua
Normal file
224
gen.lua
Normal file
@ -0,0 +1,224 @@
|
||||
return function(ast, out)
|
||||
local function compile(chu)
|
||||
if chu.type == "chunk" then
|
||||
if chu.toplevel then
|
||||
for k, import in pairs(chu.imports) do
|
||||
out("local %s = require(%q)\n", import[#import], table.concat(import, "."))
|
||||
end
|
||||
for k, var in pairs(chu.vars) do
|
||||
out("local %s\n", k)
|
||||
end
|
||||
for k, expr in pairs(chu.vars) do
|
||||
if expr then
|
||||
out("%s=", k)
|
||||
compile(expr)
|
||||
out(";")
|
||||
end
|
||||
end
|
||||
for k, t in pairs(chu.types) do
|
||||
out("local %s={}%s.__index=%s;setmetatable(%s, %s)", k, k, k, k, k)
|
||||
for _, m in pairs(t.members) do
|
||||
if m.type == "field" then
|
||||
-- Do nothing.
|
||||
elseif m.type == "static" then
|
||||
out("%s.%s=", k, m.name)
|
||||
compile(m.value)
|
||||
out(";")
|
||||
else
|
||||
error("Invalid AST")
|
||||
end
|
||||
end
|
||||
end
|
||||
out("return{")
|
||||
for k,v in pairs(chu.vars) do
|
||||
out("%s=%s,", k, k)
|
||||
end
|
||||
for k,v in pairs(chu.types) do
|
||||
out("%s=%s,", k, k)
|
||||
end
|
||||
out("}")
|
||||
else
|
||||
for k, stmt in ipairs(chu.stmts) do
|
||||
compile(stmt)
|
||||
|
||||
if stmt.type == 'call' then
|
||||
out(";") --Safety delimiter.
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif chu.type == "function" then
|
||||
out("function(%s%s)", chu.is_constructor and "_," or "", table.concat(chu.args, ","))
|
||||
|
||||
if SAFE_MODE then
|
||||
for k, argname in pairs(chu.args) do
|
||||
local et = chu.et.args[k]
|
||||
if et then
|
||||
local checks = {}
|
||||
if et.type == 'integer' or et.type == 'number' or et.type == 'string' or et.type == 'boolean' then
|
||||
if et.type == 'integer' then
|
||||
table.insert(checks, 'type(' .. argname .. ')=="number"')
|
||||
table.insert(checks, argname .. '%1==0')
|
||||
else
|
||||
table.insert(checks, 'type(' .. argname .. ')=="'..et.type..'"')
|
||||
end
|
||||
elseif et.type == 'list' then
|
||||
table.insert(checks, 'type(' .. argname .. ')=="table"')
|
||||
end
|
||||
if #checks > 0 then
|
||||
out("assert(%s, %q);", table.concat(checks, " and "), string.format("Invalid argument %q", argname))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if chu.is_constructor then
|
||||
out("local self=setmetatable({}, %s);", chu.et.ret.name)
|
||||
end
|
||||
|
||||
compile(chu.chunk)
|
||||
|
||||
if chu.is_constructor then
|
||||
out("return self;")
|
||||
end
|
||||
|
||||
out("end")
|
||||
elseif chu.type == "let" then
|
||||
out("local %s", chu.var.name)
|
||||
|
||||
if chu.expr then
|
||||
out("=")
|
||||
compile(chu.expr)
|
||||
end
|
||||
|
||||
out(";")
|
||||
elseif chu.type == "num" then
|
||||
out("%g", chu.value)
|
||||
elseif chu.type == "var" then
|
||||
out("%s", chu.which.name)
|
||||
elseif chu.type == "binop" then
|
||||
local paren
|
||||
|
||||
paren = chu.a.type == "binop" and chu.a.level > chu.level
|
||||
if paren then out("(") end
|
||||
compile(chu.a)
|
||||
if paren then out(")") end
|
||||
|
||||
local op = chu.op
|
||||
|
||||
if op == "**" then
|
||||
op = "^"
|
||||
elseif op == "/" and (not chu.a.et or chu.a.et.type == 'integer') and (not chu.b.et or chu.b.et.type == 'integer') then
|
||||
op = "//"
|
||||
end
|
||||
|
||||
out("%s", op)
|
||||
|
||||
paren = chu.b.type == "binop" and chu.b.level > chu.level
|
||||
if paren then out("(") end
|
||||
compile(chu.b)
|
||||
if paren then out(")") end
|
||||
elseif chu.type == "lengthof" then
|
||||
out("#(")
|
||||
compile(chu.sub)
|
||||
out(")")
|
||||
elseif chu.type == "dot" then
|
||||
compile(chu.a)
|
||||
out("%s%s", chu.colon and ":" or ".", chu.b)
|
||||
elseif chu.type == "index" then
|
||||
out("(")
|
||||
compile(chu.what)
|
||||
out(")[")
|
||||
compile(chu.idx)
|
||||
out("]")
|
||||
elseif chu.type == "call" then
|
||||
local guard = chu.type == 'binop'
|
||||
if guard then
|
||||
out("(")
|
||||
end
|
||||
compile(chu.what)
|
||||
if guard then
|
||||
out(")")
|
||||
end
|
||||
out("(")
|
||||
for k, v in pairs(chu.args) do
|
||||
compile(v)
|
||||
|
||||
if k < #chu.args then
|
||||
out(",")
|
||||
end
|
||||
end
|
||||
out(")")
|
||||
elseif chu.type == "dict" then
|
||||
out("{")
|
||||
for k, v in pairs(chu.mappings) do
|
||||
out("[")
|
||||
compile(k)
|
||||
out("]=")
|
||||
compile(v)
|
||||
out(",")
|
||||
end
|
||||
out("}")
|
||||
elseif chu.type == "list" then
|
||||
out("{")
|
||||
for k, v in pairs(chu.values) do
|
||||
out("[%i]=", k)
|
||||
compile(v)
|
||||
out(",")
|
||||
end
|
||||
out("}")
|
||||
elseif chu.type == "string" then
|
||||
out("%q", chu.value)
|
||||
elseif chu.type == "null" then
|
||||
out("nil")
|
||||
elseif chu.type == "return" then
|
||||
out("return ")
|
||||
compile(chu.expr)
|
||||
out(";")
|
||||
elseif chu.type == "if" then
|
||||
out("if ")
|
||||
compile(chu[1].pred)
|
||||
out(" then ")
|
||||
compile(chu[1].chu)
|
||||
|
||||
for i = 2, #chu do
|
||||
out("elseif ")
|
||||
compile(chu[i].pred)
|
||||
out("then ")
|
||||
compile(chu[i].chu)
|
||||
end
|
||||
|
||||
if chu.elsa then
|
||||
out("else ")
|
||||
compile(chu.elsa)
|
||||
end
|
||||
|
||||
out("end;")
|
||||
elseif chu.type == "fori" then
|
||||
out("for " .. chu.varname .. "=")
|
||||
compile(chu.from)
|
||||
out(",")
|
||||
compile(chu.to)
|
||||
out("-1 do ")
|
||||
compile(chu.chu)
|
||||
out("end ")
|
||||
elseif chu.type == "loop" then
|
||||
out("while true do ")
|
||||
compile(chu.chu)
|
||||
out("end ")
|
||||
elseif chu.type == "break" then
|
||||
out("break ")
|
||||
elseif chu.type == "noop" then
|
||||
elseif chu.type == "exprstat" then
|
||||
compile(chu.expr)
|
||||
out(";")
|
||||
elseif chu.type == "assign" then
|
||||
compile(chu.dest)
|
||||
out("=")
|
||||
compile(chu.src)
|
||||
out(";")
|
||||
else
|
||||
error(string.format("Invalid AST node (type %q)", chu.type))
|
||||
end
|
||||
end
|
||||
return compile(ast)
|
||||
end
|
215
lex.lua
Normal file
215
lex.lua
Normal file
@ -0,0 +1,215 @@
|
||||
--[[
|
||||
Titty has two main design goals:
|
||||
1. Should look natural from the outside, Lua code's PoV, and
|
||||
2. Should compile to Lua code that also appears natural from within
|
||||
The second is less important than the first.
|
||||
]]
|
||||
|
||||
local inspect = require"inspect".inspect
|
||||
|
||||
local function codepoints(str)
|
||||
local it, state, v1, v2 = utf8.codes(str)
|
||||
local idx = 0
|
||||
return function()
|
||||
if not it then return nil end
|
||||
|
||||
v1, v2 = it(state, v1)
|
||||
if v1 then
|
||||
local r = idx
|
||||
idx = idx + 1
|
||||
return r, v2
|
||||
else
|
||||
it = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function is_digit(cp)
|
||||
return cp and (cp >= 48 and cp <= 57)
|
||||
end
|
||||
|
||||
local function is_ident_start(cp)
|
||||
return cp and ((cp >= 65 and cp <= 90) or (cp >= 97 and cp <= 122) or cp == 95)
|
||||
end
|
||||
|
||||
local function is_ident_nonstart(cp)
|
||||
return cp and (is_ident_start(cp) or is_digit(cp))
|
||||
end
|
||||
|
||||
local function forall(tbl, f, startI)
|
||||
startI = startI or 1
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
if not f(v) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function is_whitespace(cp)
|
||||
return cp and (cp == 32 or cp == 10 or cp == 9)
|
||||
end
|
||||
|
||||
local function clear(tbl)
|
||||
for k in pairs(tbl) do
|
||||
tbl[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function lex(cpgetraw)
|
||||
local row, column, idx = 0, 0, 0
|
||||
local pull
|
||||
local function cpget()
|
||||
if pull then
|
||||
local ret = pull
|
||||
pull = nil
|
||||
return ret
|
||||
else
|
||||
local cp
|
||||
idx, cp = cpgetraw()
|
||||
column = column + 1
|
||||
if cp == 10 then
|
||||
column = 0
|
||||
row = row + 1
|
||||
end
|
||||
return cp
|
||||
end
|
||||
end
|
||||
|
||||
local buf = {}
|
||||
|
||||
return function()
|
||||
local cp
|
||||
|
||||
while true do
|
||||
cp = cpget()
|
||||
|
||||
if not is_whitespace(cp) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not cp then
|
||||
return nil
|
||||
end
|
||||
|
||||
local rowStart, columnStart = row, column
|
||||
|
||||
if cp == 40 then
|
||||
return "(", "(", rowStart, columnStart
|
||||
elseif cp == 41 then
|
||||
return ")", ")", rowStart, columnStart
|
||||
elseif cp == 123 then
|
||||
return "{", "{", rowStart, columnStart
|
||||
elseif cp == 125 then
|
||||
return "}", "}", rowStart, columnStart
|
||||
elseif cp == 44 then
|
||||
return ",", ",", rowStart, columnStart
|
||||
elseif cp == 46 then
|
||||
return ".", ".", rowStart, columnStart
|
||||
elseif cp == 58 then
|
||||
return ":", ":", rowStart, columnStart
|
||||
elseif cp == 61 then
|
||||
local after = cpget()
|
||||
if after == 61 then
|
||||
return "==", "==", rowStart, columnStart
|
||||
else
|
||||
pull = after
|
||||
end
|
||||
return "=", "=", rowStart, columnStart
|
||||
elseif cp == 43 then
|
||||
return "+", "+", rowStart, columnStart
|
||||
elseif cp == 45 then
|
||||
return "-", "-", rowStart, columnStart
|
||||
elseif cp == 42 then
|
||||
local after = cpget()
|
||||
if after == 42 then
|
||||
return "**", "**", rowStart, columnStart
|
||||
else
|
||||
pull = after
|
||||
end
|
||||
return "*", "*", rowStart, columnStart
|
||||
elseif cp == 47 then
|
||||
return "/", "/", rowStart, columnStart
|
||||
elseif cp == 37 then
|
||||
return "%", "%", rowStart, columnStart
|
||||
elseif cp == 35 then
|
||||
return "#", "#", rowStart, columnStart
|
||||
elseif cp == 91 then
|
||||
return "[", "[", rowStart, columnStart
|
||||
elseif cp == 93 then
|
||||
return "]", "]", rowStart, columnStart
|
||||
elseif cp == 39 then
|
||||
while true do
|
||||
cp = cpget()
|
||||
|
||||
if cp ~= 39 then
|
||||
buf[#buf + 1] = cp
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local ret = utf8.char(table.unpack(buf))
|
||||
|
||||
clear(buf)
|
||||
|
||||
return "string", ret, rowStart, columnStart
|
||||
elseif is_ident_start(cp) then
|
||||
buf[1] = cp
|
||||
|
||||
while true do
|
||||
cp = cpget()
|
||||
|
||||
if is_ident_nonstart(cp) then
|
||||
buf[#buf + 1] = cp
|
||||
else
|
||||
pull = cp
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local ret = utf8.char(table.unpack(buf))
|
||||
|
||||
clear(buf)
|
||||
|
||||
if ret == "func" or ret == "end" or ret == "if"
|
||||
or ret == "while" or ret == "do" or ret == "then"
|
||||
or ret == "elseif" or ret == "type" or ret == "interf"
|
||||
or ret == "let" or ret == "return" or ret == "else"
|
||||
or ret == "for" or ret == "import" or ret == "constr"
|
||||
or ret == "loop" or ret == "break" or ret == "nil" then
|
||||
|
||||
return ret, ret, rowStart, columnStart
|
||||
end
|
||||
|
||||
return "id", ret, rowStart, columnStart
|
||||
elseif is_digit(cp) then
|
||||
buf[1] = cp
|
||||
|
||||
local dotFound = false
|
||||
|
||||
while true do
|
||||
cp = cpget()
|
||||
if is_digit(cp) then
|
||||
buf[#buf + 1] = cp
|
||||
elseif cp == 46 and not dotFound then
|
||||
dotFound = true
|
||||
buf[#buf + 1] = cp
|
||||
else
|
||||
pull = cp
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local ret = utf8.char(table.unpack(buf))
|
||||
clear(buf)
|
||||
return "num", ret, rowStart, columnStart
|
||||
else
|
||||
error(string.format("%i:%i unknown character %q (code point %i)", row, column, utf8.char(cp), cp))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {lex = lex, codepoints = codepoints}
|
16
main.lua
Normal file
16
main.lua
Normal file
@ -0,0 +1,16 @@
|
||||
local Lexer = require"lex"
|
||||
local Parser = require"parse"
|
||||
local Des = require"des"
|
||||
local Gen = require"gen"
|
||||
|
||||
local tokens = {}
|
||||
|
||||
for tt, ts, tr, tc in Lexer.lex(Lexer.codepoints(io.open(arg[1], "r"):read("*a"))) do
|
||||
tokens[#tokens + 1] = {tt, ts, tr, tc}
|
||||
end
|
||||
|
||||
local ast = Parser(tokens, false)
|
||||
|
||||
Des(ast)
|
||||
|
||||
Gen(ast, function(str, ...) io.write(string.format(str, ...)) end)
|
696
parse.lua
Normal file
696
parse.lua
Normal file
@ -0,0 +1,696 @@
|
||||
local unique_name = require"bon".unique_name
|
||||
|
||||
local function do_everything(Tokens, Skeleton)
|
||||
local TokensIdx = 1
|
||||
local IdxStack = {}
|
||||
|
||||
local function gettoken()
|
||||
local r = Tokens[TokensIdx]
|
||||
if r then
|
||||
TokensIdx = TokensIdx + 1
|
||||
end
|
||||
return table.unpack(r)
|
||||
end
|
||||
local function peektoken()
|
||||
local r = Tokens[TokensIdx]
|
||||
if r then
|
||||
return table.unpack(Tokens[TokensIdx])
|
||||
end
|
||||
end
|
||||
local function maybe(t)
|
||||
local a, b, c, d = peektoken()
|
||||
if a == t then return gettoken() end
|
||||
return nil
|
||||
end
|
||||
local function pushlexi()
|
||||
table.insert(IdxStack, TokensIdx)
|
||||
end
|
||||
local function poplexi()
|
||||
TokensIdx = table.remove(IdxStack)
|
||||
end
|
||||
local function forgetlexi()
|
||||
table.remove(IdxStack)
|
||||
end
|
||||
|
||||
local function expect(t)
|
||||
local a, b, c, d = gettoken()
|
||||
if t ~= a then
|
||||
error(string.format("%i:%i expected %s, got %s", c, d, t, a))
|
||||
end
|
||||
return a, b, c, d
|
||||
end
|
||||
|
||||
local parse_chunk
|
||||
|
||||
local function new_parse_context(parent)
|
||||
return setmetatable({["the parent"] = parent}, {
|
||||
__index = function(self, key)
|
||||
while true do
|
||||
local r = rawget(self, key)
|
||||
if r then
|
||||
return r
|
||||
end
|
||||
self = rawget(self, "the parent")
|
||||
if not self then
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
__newindex = function(self, key, value)
|
||||
local z = self
|
||||
local r = nil
|
||||
while true do
|
||||
r = rawget(z, key)
|
||||
if r then break end
|
||||
z = rawget(z, "the parent")
|
||||
if not z then break end
|
||||
end
|
||||
rawset(z or self, key, value)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local ParseContext, ParseContextTypes
|
||||
|
||||
local function push_parse_context()
|
||||
ParseContext = new_parse_context(ParseContext)
|
||||
ParseContextTypes = new_parse_context(ParseContextTypes)
|
||||
end
|
||||
|
||||
local function pop_parse_context()
|
||||
ParseContext = rawget(ParseContext, "the parent")
|
||||
ParseContextTypes = rawget(ParseContextTypes, "the parent")
|
||||
end
|
||||
|
||||
local Types = {}
|
||||
function Types.castable(this, to)
|
||||
return not to or (this and this.type == to.type)
|
||||
end
|
||||
function Types.is_number(this)
|
||||
return not this or this.type == 'number' or this.type == 'integer'
|
||||
end
|
||||
function Types.can_do_arith(this)
|
||||
return Types.is_number(this)
|
||||
end
|
||||
function Types.is_callable(this)
|
||||
return not this or this.type == 'function'
|
||||
end
|
||||
function Types.is_indexable(this)
|
||||
return not this or this.type == 'list'
|
||||
end
|
||||
function Types.has_field(this, field)
|
||||
if not this then
|
||||
return true, nil
|
||||
end
|
||||
|
||||
if this.type == 'list' and field == 'pop' then
|
||||
return true, {type = 'function', ret = false, args = {this}}
|
||||
end
|
||||
|
||||
if this.type ~= 'structure' then
|
||||
return false, nil
|
||||
end
|
||||
|
||||
for k, v in ipairs(this.members) do
|
||||
if v.name == field then
|
||||
return true, v.et
|
||||
end
|
||||
end
|
||||
|
||||
return false, nil
|
||||
end
|
||||
|
||||
local function parse_et()
|
||||
local A
|
||||
|
||||
if peektoken() == "id" then
|
||||
local name = select(2, gettoken())
|
||||
if name == "number" then
|
||||
A = {type = 'number'}
|
||||
elseif name == "integer" then
|
||||
A = {type = 'integer'}
|
||||
elseif name == "string" then
|
||||
A = {type = 'string'}
|
||||
elseif name == "boolean" then
|
||||
A = {type = 'boolean'}
|
||||
elseif name == "list" then
|
||||
A = {type = 'list'}
|
||||
elseif name == "dict" then
|
||||
A = {type = 'dict'}
|
||||
else
|
||||
A = ParseContextTypes[name]
|
||||
end
|
||||
end
|
||||
|
||||
if not A then
|
||||
error("Invalid type description")
|
||||
end
|
||||
|
||||
if maybe("[") then
|
||||
A.parametrization = {parse_et()}
|
||||
|
||||
expect("]")
|
||||
end
|
||||
|
||||
return A
|
||||
end
|
||||
|
||||
local parse_function
|
||||
|
||||
local function parse_function_arg()
|
||||
local name = select(2, expect("id"))
|
||||
local et
|
||||
if maybe(":") then
|
||||
et = parse_et()
|
||||
end
|
||||
return name, et
|
||||
end
|
||||
|
||||
local function parse_type()
|
||||
expect("type")
|
||||
|
||||
local name = select(2, expect("id"))
|
||||
|
||||
local ret = {type = 'structure', members = {}, name = name}
|
||||
|
||||
while true do
|
||||
if maybe("end") then
|
||||
break
|
||||
end
|
||||
|
||||
if peektoken() == "constr" or peektoken() == "func" then
|
||||
local isConstr = gettoken() == "constr"
|
||||
local fname
|
||||
|
||||
if isConstr then
|
||||
fname = "__call"
|
||||
else
|
||||
fname = select(2, expect("id"))
|
||||
end
|
||||
|
||||
local argnames = {}
|
||||
local argets = {}
|
||||
|
||||
if not isConstr then
|
||||
table.insert(argnames, "self")
|
||||
table.insert(argets, ret)
|
||||
end
|
||||
|
||||
push_parse_context()
|
||||
rawset(ParseContext, "self", {et = ret, name = "self"})
|
||||
|
||||
if maybe("(") then
|
||||
if not maybe(")") then
|
||||
|
||||
while true do
|
||||
local name, et = parse_function_arg()
|
||||
|
||||
table.insert(argnames, name)
|
||||
argets[#argnames] = et
|
||||
|
||||
rawset(ParseContext, name, {et = et, name = name})
|
||||
|
||||
if maybe(")") then
|
||||
break
|
||||
else
|
||||
expect(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local fret
|
||||
if isConstr then
|
||||
fret = ret
|
||||
else
|
||||
if maybe(":") then
|
||||
fret = parse_et()
|
||||
end
|
||||
end
|
||||
|
||||
local chu = parse_chunk(false)
|
||||
|
||||
local et = {type = "function", args = argets, ret = fret}
|
||||
|
||||
table.insert(ret.members, {
|
||||
type = "static",
|
||||
name = fname,
|
||||
et = et,
|
||||
value = {type = "function", et = et, args = argnames, chunk = chu, is_constructor = isConstr},
|
||||
})
|
||||
|
||||
pop_parse_context()
|
||||
|
||||
expect("end")
|
||||
else
|
||||
local membname = select(2, expect("id"))
|
||||
|
||||
expect(":")
|
||||
|
||||
local et = parse_et()
|
||||
|
||||
table.insert(ret.members, {type = "field", name = membname, et = et})
|
||||
end
|
||||
end
|
||||
|
||||
return name, ret
|
||||
end
|
||||
|
||||
parse_function = function()
|
||||
expect("func")
|
||||
|
||||
local name = select(2, maybe("id"))
|
||||
|
||||
push_parse_context()
|
||||
|
||||
local argnames = {}
|
||||
local argets = {}
|
||||
|
||||
if maybe("(") then
|
||||
if not maybe(")") then
|
||||
while true do
|
||||
local name, et = parse_function_arg()
|
||||
|
||||
table.insert(argnames, name)
|
||||
argets[#argnames] = et
|
||||
|
||||
rawset(ParseContext, name, {et = et, name = name})
|
||||
|
||||
if maybe(")") then
|
||||
break
|
||||
else
|
||||
expect(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local retet
|
||||
if maybe(":") then
|
||||
retet = parse_et()
|
||||
end
|
||||
|
||||
rawset(ParseContext, "the function name", name)
|
||||
rawset(ParseContextTypes, "the function return type", retet)
|
||||
|
||||
local ret = {
|
||||
type = "function",
|
||||
et = {type = "function", args = argets, ret = retet},
|
||||
chunk = parse_chunk(false),
|
||||
args = argnames
|
||||
}
|
||||
|
||||
pop_parse_context()
|
||||
|
||||
expect("end")
|
||||
|
||||
return ret, name
|
||||
end
|
||||
|
||||
local active_stmt_list_stack = {}
|
||||
|
||||
BINOP_LEVELS = {["+"] = 3, ["-"] = 3, ["=="] = 4, ["/"] = 2, ["*"] = 2, ["%"] = 2, ["**"] = 1}
|
||||
local function parse_expr(level)
|
||||
level = level or 4
|
||||
|
||||
if level == 4 then
|
||||
local A = parse_expr(level - 1)
|
||||
|
||||
if peektoken() == "==" then
|
||||
local op = gettoken()
|
||||
|
||||
local B = parse_expr(level - 1)
|
||||
|
||||
A = {type = "binop", level = level, et = {type = 'boolean'}, op = op, a = A, b = B}
|
||||
end
|
||||
|
||||
return A
|
||||
elseif level == 3 then
|
||||
local A = parse_expr(level - 1)
|
||||
|
||||
if peektoken() == "+" or peektoken() == "-" then
|
||||
local op = gettoken()
|
||||
|
||||
local B = parse_expr(level - 1)
|
||||
|
||||
if not Types.can_do_arith(A.et) or not Types.can_do_arith(B.et) then
|
||||
error(op .. " operator not supported for non-number types")
|
||||
end
|
||||
|
||||
A = {type = "binop", level = level, et = A.et, op = op, a = A, b = B}
|
||||
end
|
||||
|
||||
return A
|
||||
elseif level == 2 then
|
||||
local A = parse_expr(level - 1)
|
||||
|
||||
if peektoken() == "*" or peektoken() == "/" or peektoken() == "%" then
|
||||
local op = gettoken()
|
||||
|
||||
local B = parse_expr(level - 1)
|
||||
|
||||
if not Types.can_do_arith(A.et) or not Types.can_do_arith(B.et) then
|
||||
error(op .. " operator not supported for non-number types")
|
||||
end
|
||||
|
||||
A = {type = "binop", level = level, et = A.et, op = op, a = A, b = B}
|
||||
end
|
||||
|
||||
return A
|
||||
elseif level == 1 then
|
||||
local A = parse_expr(level - 1)
|
||||
|
||||
if peektoken() == "**" then
|
||||
local op = gettoken()
|
||||
|
||||
local B = parse_expr(level - 1)
|
||||
|
||||
if not Types.can_do_arith(A.et) or not Types.can_do_arith(B.et) then
|
||||
error(op .. " operator not supported for non-number types")
|
||||
end
|
||||
|
||||
A = {type = "binop", level = level, et = A.et, op = op, a = A, b = B}
|
||||
end
|
||||
|
||||
return A
|
||||
elseif level == 0 then
|
||||
local A
|
||||
|
||||
if maybe("#") then
|
||||
A = {type = 'lengthof', et = {type = 'integer'}, sub = parse_expr(level)}
|
||||
|
||||
if A.sub.et and A.sub.et.type ~= 'list' then
|
||||
error("# requires a list")
|
||||
end
|
||||
elseif maybe("(") then
|
||||
A = parse_expr()
|
||||
expect(")")
|
||||
elseif peektoken() == "func" then
|
||||
A = parse_function()
|
||||
elseif peektoken() == "num" then
|
||||
local lit = select(2, gettoken())
|
||||
A = {type = "num", et = lit:find("%.") and {type = 'number'} or {type = 'integer'}, value = tonumber(lit)}
|
||||
elseif peektoken() == "id" then
|
||||
local name = select(2, gettoken())
|
||||
local v = ParseContext[name]
|
||||
if not v then
|
||||
error(string.format("No value with name %s exists.", name))
|
||||
end
|
||||
A = {type = "var", which = v, et = v.et}
|
||||
elseif maybe("{") then
|
||||
A = {type = "dict", et = {type = "dict", parametrization = {}}, mappings = {}}
|
||||
expect("}")
|
||||
elseif maybe("[") then
|
||||
A = {type = "list", et = {type = "list", parametrization = {}}, values = {}}
|
||||
expect("]")
|
||||
elseif peektoken() == "string" then
|
||||
A = {type = "string", et = {type = 'string'}, value = select(2, gettoken())}
|
||||
elseif maybe("nil") then
|
||||
A = {type = 'null', et = {type = 'null'}}
|
||||
else
|
||||
error("Unexpected token " .. select(2, peektoken()))
|
||||
end
|
||||
|
||||
while peektoken() == "[" or peektoken() == "(" or peektoken() == "." do
|
||||
if maybe("[") then
|
||||
if A.et and A.et.type ~= 'list' and A.et.type ~= 'dict' and A.et.type ~= 'string' then
|
||||
error("Indexed value is not a list nor dict.")
|
||||
end
|
||||
|
||||
local newet
|
||||
if A.et.type == 'list' then
|
||||
newet = A.et.parametrization and A.et.parametrization[1]
|
||||
elseif A.et.type == 'dict' then
|
||||
newet = A.et.parametrization and A.et.parametrization[2]
|
||||
elseif A.et.type == 'string' then
|
||||
newet = A.et
|
||||
else
|
||||
error()
|
||||
end
|
||||
|
||||
local idx = parse_expr()
|
||||
|
||||
if A.et.type == 'string' then
|
||||
local temp = {name = unique_name(), et = nil}
|
||||
|
||||
table.insert(active_stmt_list_stack[#active_stmt_list_stack], {
|
||||
type = 'let',
|
||||
var = temp,
|
||||
expr = {type = 'binop', level = BINOP_LEVELS['+'], op = '+', a = idx, b = {type = 'num', value = 1, et = {type = 'integer'}}}
|
||||
})
|
||||
|
||||
A = {
|
||||
type = 'call',
|
||||
what = {type = 'dot', colon = true, a = A, b = 'sub', et = nil},
|
||||
args = {
|
||||
{type = 'var', which = temp, et = temp.et},
|
||||
{type = 'var', which = temp, et = temp.et}
|
||||
}
|
||||
}
|
||||
else
|
||||
if A.et.type == 'list' then
|
||||
-- Switch to 1-based indexing
|
||||
idx = {type = 'binop', level = BINOP_LEVELS['+'], op = '+', a = idx, b = {type = 'num', value = 1, et = {type = 'integer'}}}
|
||||
end
|
||||
|
||||
A = {type = 'index', what = A, idx = idx, et = newet}
|
||||
end
|
||||
|
||||
expect("]")
|
||||
elseif maybe("(") then
|
||||
if not Types.is_callable(A.et) then
|
||||
error("Called value is not a function.")
|
||||
end
|
||||
|
||||
local args = {}
|
||||
|
||||
if not maybe(")") then
|
||||
while true do
|
||||
table.insert(args, parse_expr())
|
||||
|
||||
if maybe(")") then break end
|
||||
|
||||
expect(",")
|
||||
end
|
||||
end
|
||||
|
||||
A = {type = 'call', what = A, et = A.et and A.et.ret or nil, args = args}
|
||||
elseif maybe(".") then
|
||||
local B = select(2, expect("id"))
|
||||
|
||||
local has_field, field_type = Types.has_field(A.et, B)
|
||||
|
||||
if not has_field then
|
||||
error(string.format("No such field %s", B))
|
||||
end
|
||||
|
||||
A = {type = 'dot', a = A, b = B, et = field_type}
|
||||
end
|
||||
end
|
||||
|
||||
return A
|
||||
end
|
||||
|
||||
error("Invalid expression")
|
||||
end
|
||||
|
||||
local function parse_stmt()
|
||||
if maybe("let") then
|
||||
local name = select(2, expect("id"))
|
||||
|
||||
local et
|
||||
if maybe(":") then
|
||||
et = parse_et()
|
||||
end
|
||||
|
||||
local expr
|
||||
if maybe("=") then
|
||||
expr = parse_expr()
|
||||
end
|
||||
|
||||
if not et and not expr then
|
||||
error(string.format("Cannot create name %s without a given type", name))
|
||||
end
|
||||
|
||||
if not et then
|
||||
et = expr.et
|
||||
elseif expr and not Types.castable(expr.et, et) then
|
||||
error(string.format("Cannot cast expression to type %s", et.type))
|
||||
end
|
||||
|
||||
local newvar = {et = et, name = name}
|
||||
|
||||
ParseContext[name] = newvar
|
||||
|
||||
return {type = "let", var = newvar, expr = expr}
|
||||
elseif maybe("if") then
|
||||
local ret = {type = "if"}
|
||||
|
||||
while true do
|
||||
local expr = parse_expr()
|
||||
if not et and not Types.castable(expr.et, {type = 'boolean'}) then
|
||||
error("If predicate must be castable to boolean")
|
||||
end
|
||||
|
||||
expect("then")
|
||||
|
||||
local chu = parse_chunk(false)
|
||||
|
||||
table.insert(ret, {pred = expr, chu = chu})
|
||||
|
||||
if maybe("else") then
|
||||
ret.elsa = parse_chunk(false)
|
||||
|
||||
expect("end")
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
if maybe("end") then
|
||||
break
|
||||
end
|
||||
|
||||
expect("elseif")
|
||||
end
|
||||
|
||||
return ret
|
||||
elseif maybe("for") then
|
||||
local varname = select(2, expect("id"))
|
||||
|
||||
expect("=")
|
||||
|
||||
local from = parse_expr()
|
||||
|
||||
expect(",")
|
||||
|
||||
local to = parse_expr()
|
||||
|
||||
expect("do")
|
||||
|
||||
push_parse_context()
|
||||
|
||||
rawset(ParseContext, varname, {et = {type = 'integer'}, name = varname})
|
||||
|
||||
local chu = parse_chunk(false)
|
||||
|
||||
pop_parse_context()
|
||||
|
||||
expect("end")
|
||||
|
||||
return {type = "fori", varname = varname, from = from, to = to, chu = chu}
|
||||
elseif maybe("loop") then
|
||||
push_parse_context()
|
||||
local chu = parse_chunk(false)
|
||||
pop_parse_context()
|
||||
expect("end")
|
||||
|
||||
return {type = "loop", chu = chu}
|
||||
elseif maybe("break") then
|
||||
return {type = "break"}
|
||||
elseif maybe("return") then
|
||||
local expr = parse_expr()
|
||||
|
||||
if expr.et and not Types.castable(expr.et, ParseContextTypes["the function return type"]) then
|
||||
error(string.format("Function %s returns different type.", ParseContext["the function name"]))
|
||||
end
|
||||
|
||||
return {type = "return", expr = expr}
|
||||
else
|
||||
local e = parse_expr()
|
||||
|
||||
if maybe("=") then
|
||||
local src = parse_expr()
|
||||
|
||||
if not Types.castable(src.et, e.et) then
|
||||
error("Cannot cast for assignment")
|
||||
end
|
||||
|
||||
return {type = "assign", dest = e, src = src}
|
||||
else
|
||||
if e.type ~= "call" then
|
||||
error("Expected statement.")
|
||||
end
|
||||
|
||||
return e
|
||||
end
|
||||
end
|
||||
|
||||
error(string.format("Unexpected token %s", (select(2, peektoken()))))
|
||||
end
|
||||
|
||||
local SAFE_MODE = true
|
||||
|
||||
parse_chunk = function(toplevel)
|
||||
push_parse_context()
|
||||
|
||||
local ast = {type = "chunk", types = {}, vars = {}, stmts = {}, imports = {}, toplevel = toplevel}
|
||||
|
||||
table.insert(active_stmt_list_stack, ast.stmts)
|
||||
|
||||
while true do
|
||||
local tt, ts, tr, tc = peektoken()
|
||||
|
||||
if tt == "end" or tt == "else" or tt == "elseif" then
|
||||
if toplevel then
|
||||
error("Unexpected end in top-level chunk")
|
||||
end
|
||||
|
||||
break
|
||||
elseif tt == nil then
|
||||
if not toplevel then
|
||||
error("Unexpected EOF in non top-level chunk")
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
if toplevel and tt == "import" then
|
||||
gettoken()
|
||||
|
||||
local path = {}
|
||||
|
||||
while true do
|
||||
local part = select(2, expect("id"))
|
||||
table.insert(path, part)
|
||||
|
||||
if not maybe(".") then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
rawset(ParseContext, path[#path], {et = nil, name = path[#path]})
|
||||
|
||||
table.insert(ast.imports, path)
|
||||
elseif tt == "type" then
|
||||
local name, t = parse_type()
|
||||
ast.types[name] = t
|
||||
rawset(ParseContextTypes, name, t)
|
||||
elseif tt == "func" then
|
||||
local expr, name = parse_function()
|
||||
ast.vars[name] = expr
|
||||
rawset(ParseContext, name, {et = expr.et, name = name})
|
||||
elseif not toplevel then
|
||||
table.insert(ast.stmts, parse_stmt())
|
||||
else
|
||||
error(string.format("Unexpected %s in top-level chunk", tt))
|
||||
end
|
||||
end
|
||||
|
||||
table.remove(active_stmt_list_stack)
|
||||
|
||||
pop_parse_context()
|
||||
|
||||
return ast
|
||||
end
|
||||
|
||||
return parse_chunk(true)
|
||||
end
|
||||
|
||||
--local chu = parse_chunk(true)
|
||||
--pass_destandardify(chu)
|
||||
--compile(chu)
|
||||
--print()
|
||||
|
||||
return do_everything
|
Loading…
Reference in New Issue
Block a user