Initial commit

This commit is contained in:
mid 2024-06-11 17:46:13 +03:00
commit c847863034
6 changed files with 1239 additions and 0 deletions

7
bon.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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