697 lines
22 KiB
Lua
697 lines
22 KiB
Lua
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
|