summary refs log tree commit diff stats
path: root/compiler.lua
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2019-07-30 21:12:16 -0300
committerSoniEx2 <endermoneymod@gmail.com>2019-07-30 21:12:16 -0300
commit4b365cdab1296bc88509c6f8610318adefb0ef0e (patch)
tree528741562b21cc0d81d7c71aca3eaee5e8433934 /compiler.lua
parentaf3acfbb80bca7447af9fe0d4a34cf860163b218 (diff)
It... kinda works?
Diffstat (limited to 'compiler.lua')
-rw-r--r--compiler.lua844
1 files changed, 687 insertions, 157 deletions
diff --git a/compiler.lua b/compiler.lua
index d67f9ec..0e852c0 100644
--- a/compiler.lua
+++ b/compiler.lua
@@ -70,169 +70,699 @@
 ******************************************************************************/
 --]]
 
--- a parser.lua-based cratera compiler
--- a few notes:
--- * all "next" should be tables. as well as all "super" (which should be "next").
---   (it wouldn't work properly without this)
--- * when calling into a deeper level, remember to use the second return value "retry"
---   (i.e. set it to true)
+-- this is basically just a straight translation of the lparser.c
+-- main difference is we don't care about lua_State *L
 
 local parser = require "parser"
 local selfify = parser.selfify
 local STATE = parser.STATE
-local l = require "luatokens".tokens
-local assert, type, setmetatable = assert, type, setmetatable
-
-local function tostring__name(self)
-    return getmetatable(self).__name
-end
-
-local function Upvaldesc() return {
-    name = nil, -- TString -- upvalue name (for debug information)
-    instack = false, -- lu_byte -- whether it is in stack (register)
-    idx = 0, -- lu_byte -- index of upvalue (in stack or in outer function's list)
-} end
-local function LocVar() return {
-    varname = nil, -- TString
-    startpc = 0, -- int -- first point where variable is active
-    endpc = 0, -- int -- first point where variable is dead
-} end
-local function Proto() return { -- is a GC object
-    numparams = 0, -- lu_byte -- number of fixed parameters
-    is_vararg = false, -- lu_byte but boolean
-    maxstacksize = 0, -- lu_byte -- number of registers needed by this function
-    k = {}, -- TValue * -- constants used by the function
-    code = {}, -- Instruction * -- opcodes
-    p = {}, -- Proto ** -- functions defined inside the function
-    lineinfo = {}, -- int * -- map from opcodes to source lines (debug information)
-    locvars = {}, -- LocVar * -- information about local variables (debug information)
-    uvalues = {}, -- Upvaldesc * -- upvalue information
-} end
-local function FuncState() return {
-    f = nil, -- Proto -- current function header
-    prev = nil, -- FuncState -- enclosing function
-    ls = nil, -- LexState -- lexical state
-    bl = nil, -- BlockCnt -- chain of current blocks
-    pc = 0, -- int -- next position to code (equivalent to 'ncode')
-    lasttarget = 0, -- int -- 'label' of last 'jump label'
-    jpc = 0, -- int -- number of pending jumps to 'pc'
-    --nk = 0, -- int -- number of elements in 'k'
-    --np = 0, -- int -- number of elements in 'p'
-    firstlocal = 0, -- int -- index of first local var (in Dyndata array)
-    nlocvars = 0, -- short -- number of elements in 'f->locvars'
-    nactvar = 0, -- lu_byte -- number of active local variables
-    nups = 0, -- lu_byte -- number of upvalues
-    freereg = 0, -- lu_byte -- first free register
-} end
-local function Labeldesc() return {
-    name = nil, -- TString -- label identifier
-    pc = nil, -- int -- position in code
-    line = nil, -- int -- line where it appeared
-    nactvar = nil, -- lu_byte -- local level where it appears in current block
-} end
-local function Dyndata() return {
-    actvar = {}, -- ArrayList of Vardesc (short) -- list of active local variables
-    gt = {}, -- Labellist (ArrayList of Labeldesc) -- list of pending gotos
-    label = {}, -- Labellist (ArrayList of Labeldesc) -- list of active labels
-} end
-local function ParserState() return { -- LexState
-    fs = nil, -- FuncState *
-    dyd = nil, -- Dyndata *
-} end
-
-local gotostatname = {[parser.EOZ] = false}
-local gotostatnamemt = {__index=gotostatname, __name="gotostatname", __tostring=tostring__name}
-gotostatname[parser.FALLBACK] = function(state, token)
-    assert(type(token) == "string")
-    state[#state+1] = "goto"
-    state[#state+1] = token
-    return state[STATE].next
-end
-
-local gotostat = {[parser.EOZ] = false}
-local gotostatmt = {__index=gotostat, __name="gotostat", __tostring=tostring__name}
-gotostat[l.TK_NAME] = function(state, token)
-    return setmetatable({next = state[STATE].next}, gotostatnamemt)
-end
-
-local singlevar = {[parser.EOZ] = false}
-local singlevarmt = {__index=singlevar, __name="singlevar", __tostring=tostring__name}
-singlevar[parser.FALLBACK] = function(state, token)
-    assert(type(token) == "string")
-    state[#state+1] = token
-    return state[STATE].next
-end
-
-local primaryexp = {[parser.EOZ] = false}
-local primaryexpmt = {__name="primaryexp", __tostring=tostring__name}
-primaryexp['('] = function(state, token) end
-primaryexp[l.TK_NAME] = function(state, token)
-    return setmetatable({next=state[STATE].next}, singlevarmt)
-end
-
-local suffixedexp = {}
-local suffixedexpmt = {__name="suffixedexp", __tostring=tostring__name}
-suffixedexp.next = function() end
-
-local exprstat = {}
-local exprstatmt = {__index=exprstat, __name="exprstat", __tostring=tostring__name}
-exprstat.next = {}
-
-local statementt = {[parser.EOZ] = false}
-local statementmt = {__index=statementt, __name="statement", __tostring=tostring__name}
-local function statement(state, token)
-    local cur = state[STATE]
-    return setmetatable({next = cur.next}, statementmt), true
-end
-statementt[";"] = function(state, token)
-    state[#state+1] = token
-    return "next"
-end
-statementt[l.TK_IF] = function(state, token) end
-statementt[l.TK_WHILE] = function(state, token) end
-statementt[l.TK_DO] = function(state, token) end
-statementt[l.TK_FOR] = function(state, token) end
-statementt[l.TK_REPEAT] = function(state, token) end
-statementt[l.TK_FUNCTION] = function(state, token) end
-statementt[l.TK_LOCAL] = function(state, token) end
-statementt[l.TK_DBCOLON] = function(state, token) end
-statementt[l.TK_RETURN] = function(state, token) end
-statementt[l.TK_BREAK] = function(state, token)
-    state[#state+1] = "break"
-    return "next"
-end
-statementt[l.TK_GOTO] = function(state, token)
-    return setmetatable({next = state[STATE].next}, gotostatmt)
-end
-statementt[parser.FALLBACK] = function(state, token)
-    return setmetatable({super = state[STATE].next}, exprstatmt), true
-end
-
-local statlistt = {}
-local statlistmt = {__index=statlistt, __name="statlist", __tostring=tostring__name}
-local function statlist(state, token)
-    local cur = state[STATE]
-    return setmetatable(selfify({super = cur.next, withuntil = cur.withuntil}, "next"), statlistmt), true
-end
-statlistt[l.TK_ELSE] = function() return "super", true end
-statlistt[l.TK_ELSEIF] = function() return "super", true end
-statlistt[l.TK_END] = function() return "super", true end
-statlistt[parser.EOZ] = function() return "super", true end
-statlistt[l.TK_UNTIL] = function() return "withuntil", true end
-statlistt[parser.FALLBACK] = statement
-
-local mainfunc = setmetatable({}, {__name="mainfunc", __tostring=tostring__name})
-mainfunc.withuntil = "super"
-mainfunc[parser.EOZ] = parser.FALLBACK
-mainfunc[parser.FALLBACK] = statlist
-mainfunc.next = {
-    [parser.EOZ] = {}
-}
+local TK = require "luatokens".TK
+local error, assert = error, assert
+-- try to avoid making too many locals because Lua has a limit to how many locals you can have
+local coroutine = {create = coroutine.create,
+                   resume = coroutine.resume,
+                   yield  = coroutine.yield}
+local math = {huge  = math.huge,
+              floor = math.floor}
+local string = {format = string.format}
 
-local defs = setmetatable({}, {__name="defs", __tostring=tostring__name})
-defs[parser.EOZ] = parser.FALLBACK
-defs[parser.FALLBACK] = function(state, token) return mainfunc, true end
+local luaX = {} -- lexer
+local luaK = {} -- code generator
+
+luaK.ret = function() end -- FIXME
+
+luaX.next = (function()
+    local extra_tokens = {[TK.NAME] = true, [TK.INT] = true, [TK.FLT] = true, [TK.STRING] = true}
+    return function(ls)
+        ls.lastline = ls.linenumber
+        if ls.lookahead_token then
+            ls.t_token = ls.lookahead_token
+            ls.lookahead_token = nil
+            ls.t_seminfo = ls.lookahead_seminfo
+        end
+        local token = coroutine.yield()
+        ls.t_token = token
+        if extra_tokens[token] then
+            ls.t_seminfo = coroutine.yield()
+        end
+    end
+end)()
+
+local function save_token(ls)
+    local tk = ls.t_token
+    local seminfo = ls.t_seminfo
+    local c = ls[parser.COLLECT] or ls
+    if tk == TK.FLOAT then
+        local token = seminfo
+        local extra, num, den = 1, token, 1
+        assert(token == token and token >= 0, "NYI") -- the tokenizer should never output NaNs or negative values
+        if token == math.huge then
+            num, den = 1, 0
+        else
+            while num ~= math.floor(num) do
+                num = num * 2 -- always safe (I think)
+                local oldden = den
+                den = den * 2
+                if den == math.huge then -- subnormals or something?
+                    extra = oldden
+                    den = 2
+                end
+            end
+        end
+        c[#c+1] = string.format('((%d/%d)/%d)', num, den, extra)
+    elseif tk == TK.INT then
+        c[#c+1] = string.format('%d', seminfo)
+    elseif tk == TK.STRING then
+        c[#c+1] = string.format('%q', seminfo)
+    elseif tk == TK.NAME then
+        c[#c+1] = seminfo
+    else
+        c[#c+1] = tostring(tk)
+    end
+end
+
+function luaX.syntaxerror(ls, msg)
+    error("NYI")
+end
+
+-- maximum number of local variables per function (must be smaller
+-- than 250, due to the bytecode format)
+local MAXVARS = 200
+
+-- hasmultret TODO
+-- eqstr TODO
+
+-- prototypes for recursive non-terminal functions
+local statement, expr
+
+-- semantic error
+local function semerror(ls, msg)
+    ls.t_token = nil -- remove "near <token>" from final message
+    luaX.syntaxerror(ls, msg)
+end
+
+local function error_expected(ls, token)
+    luaX.syntaxerror(ls, string.format("%s expected", tostring(token)))
+end
+
+-- errorlimit TODO
+-- checklimit TODO
+
+local function testnext(ls, c)
+    if ls.t_token == c then
+        save_token(ls)
+        luaX.next(ls)
+        return true
+    end
+    return false
+end
+
+local function check(ls, c)
+    if ls.t_token ~= c then
+        error_expected(ls, c)
+    end
+end
+
+local function checknext(ls, c)
+    check(ls, c)
+    save_token(ls)
+    luaX.next(ls)
+end
+
+local function check_condition(ls, c, msg) if not c then luaX.syntaxerror(ls, msg) end end
+
+local function check_match(ls, what, who, where)
+    if not testnext(ls, what) then
+        if where == ls.linenumber then
+            error_expected(ls, what)
+        else
+            luaX.syntaxerror(ls, string.format("%s expected (to close %s at line %d)", tostring(what), tostring(who), where))
+        end
+    end
+end
+
+local function str_checkname(ls)
+    check(ls, TK.NAME)
+    local ts = ls.t_seminfo
+    save_token(ls)
+    luaX.next(ls)
+    return ts
+end
+
+local function init_exp(expdesc, expkind, i)
+    expdesc.t = NO_JUMP
+    expdesc.f = expdesc.t
+    expdesc.k = expkind
+    expdesc.val = i
+end
+
+local function codestring(ls, e, s)
+    init_exp(e, VK, luaK.stringK(ls.fs, s))
+end
+
+-- checkname TODO
+-- registerlocalvar TODO
+-- new_localvar TODO
+-- new_localvarliteral_ TODO
+-- new_localvarliteral TODO
+-- getlocvar TODO
+-- adjustlocalvars TODO
+-- removevars TODO
+-- searchupvalue TODO
+-- newupvalue TODO
+-- searchvar TODO
+-- markupval TODO
+-- singlevaraux TODO
+-- singlevar TODO
+-- adjust_assign TODO
+
+local function enterlevel(ls)
+    -- don't bother
+    --local L = ls.L
+    --L.nCcalls = L.nCcalls + 1
+    --checklimit(ls.fs, L.nCcalls, LUAI_MAXCCALLS, "C levels")
+end
+
+local function leavelevel(ls)
+    --ls.L.nCcalls = ls.L.nCcalls - 1
+end
+
+-- closegoto TODO
+-- findlabel TODO
+-- newlabelentry TODO
+-- findgotos TODO
+-- movegotosout TODO
+
+local function enterblock(fs, bl, isloop)
+    bl.isloop = isloop
+    bl.nactvar = fs.nactvar
+    bl.firstlabel = #fs.ls.dyd.label
+    bl.firstgoto = #fs.ls.dyd.gt
+    bl.upval = 0
+    bl.previous = fs.bl
+    fs.bl = bl
+    --lua_assert(fs.freereg == fs.nactvar)
+end
+
+-- breaklabel TODO
+-- undefgoto TODO
+
+local function leaveblock(fs)
+    local bl = fs.bl
+    local ls = fs.ls
+    if bl.previous and bl.upval then
+        -- create a 'jump to here' to close upvalues
+        local j = luaK.jump(fs)
+        luaK.patchclose(fs, j, bl.nactvar)
+        luaK.patchtohere(fs, j)
+    end
+    if bl.isloop then
+        breaklabel(ls) -- close pending breaks
+    end
+    fs.bl = bl.previous
+    removevars(fs, bl.nactvar)
+    --lua_assert(bl.nactvar == fs.nactvar)
+    fs.freereg = fs.nactvar -- free registers
+    for i=bl.firstlabel,#ls.dyd.label do ls.dyd.label[i]=nil end -- remove local labels
+    if bl.previous then
+        movegotosout(fs, bl)
+    elseif bl.firstgoto < #ls.dyd.gt then
+        undefgoto(ls, ls.dyd.gt[bl.firstgoto])
+    end
+end
+
+-- addprototype TODO
 
+-- codes instruction to create new closure in parent function.
+-- The OP_CLOSURe instruction must use the last available register,
+-- so that, if it invokes the GC, the GC knows which registers
+-- are in use at that time.
+local function codeclosure(ls, v)
+    local fs = ls.fs.prev
+    init_exp(v, VRELOCABLE, luaK.codeABx(fs, OP_CLOSURE, 0, #fs.f.p - 1))
+    luaK.exp2nextreg(fs, v) -- fix it at the last register
+end
+
+local function open_func(ls, fs, bl)
+    fs.prev = ls.fs
+    fs.ls = ls
+    ls.fs = fs
+    fs.pc = 0
+    fs.lasttarget = 0
+    fs.jpc = NO_JUMP
+    fs.freereg = 0
+    fs.nactvar = 0
+    fs.firstlocal = #ls.dyd.actvar
+    fs.bl = nil
+    local f = fs.f
+    f.source = ls.source
+    f.maxstacksize = 2 -- registers 0/1 are always valid
+    enterblock(fs, bl, false)
+end
+
+local function close_func(ls)
+    local fs = ls.fs
+    local f = fs.f
+    luaK.ret(fs, 0, 0) -- final return
+    leaveblock(fs)
+    -- don't need to worry about reallocating vectors
+    --lua_assert(fs.bl == nil)
+    ls.fs = fs.prev
+end
+
+local block_follow = (function()
+    local tokens = {[TK.ELSE] = true, [TK.ELSEIF] = true, [TK.END] = true, [parser.EOZ] = true}
+    return function(ls, withuntil)
+        local tk = ls.t_token
+        return tokens[tk] or (withuntil and tk == TK.UNTIL)
+    end
+end)()
+
+local function statlist(ls)
+    -- statlist -> { stat [';'] }
+    while not block_follow(ls, true) do
+        if ls.t_token == TK_RETURN then
+            statement(ls)
+            return -- 'return' must be last statement
+        end
+        statement(ls)
+    end
+end
+
+-- fieldsel TODO
+
+local function yindex(ls, v)
+    -- index -> '[' expr ']'
+    save_token(ls)
+    luaX.next(ls) -- skip the '['
+    expr(ls, v)
+    luaK.exp2val(ls.fs, v)
+    checknext(ls, ']')
+end
+
+-- recfield TODO
+-- closelistfield TODO
+-- lastlistfield TODO
+-- listfield TODO
+-- field TODO
+-- constructor TODO
+-- parlist TODO
+
+local function body(ls, e, ismethod, line)
+    -- body -> '(' parlist ')' block END
+    -- TODO
+    error("NYI")
+end
+
+local function explist(ls, v)
+    -- explist -> expr { ',' expr }
+    local n = 1 -- at least one expression
+    expr(ls, v)
+    while testnext(ls, ',') do
+        luaK.exp2nextreg(ls.fs, v)
+        expr(ls, v)
+        n = n + 1
+    end
+    return n
+end
+
+local function funcargs(ls, f, line)
+    local fs = ls.fs
+    local args = {}
+    local base, nparams
+    local tk = ls.t_token
+    if tk == '(' then -- funcargs -> '(' [ explist ] ')'
+        save_token(ls)
+        luaX.next(ls)
+        if ls.t_token == ')' then -- arg list is empty?
+            args.k = VVOID
+        else
+            explist(ls, args)
+            luaK.setmultret(fs, args)
+        end
+        check_match(ls, ')', '(', line)
+    elseif tk == '{' then -- funcargs -> constructor
+        constructor(ls, args)
+    elseif tk == TK.STRING then -- funcargs -> STRING
+        codestring(ls, args, ls.t_seminfo)
+        save_token(ls)
+        luaX.next(ls) -- must use 'seminfo' before 'next'
+    else
+        luaX.syntaxerror(ls, "function arguments expected")
+    end
+    --lua_assert(f.k == VNONRELOC)
+    base = f.val -- base register for call
+    if hasmultret(args.k) then
+        nparams = LUA_MULTRET -- open call
+    else
+        if args.k ~= VVOID then
+            luaK.exp2nextreg(fs, args) -- close last argument
+        end
+        nparams = fs.freereg - (base+1)
+    end
+    init_exp(f, VCALL, luaK.codeABC(fs, OP_CALL, base, nparams+1, 2))
+    luaK.fixline(fs, line)
+    fs.freereg = base+1 -- call remove function and arguments and leaves
+                        -- (unless changed) one result
+end
+
+local suffixedexp -- hm.
+
+;(function() -- avoid issues with 200 locals or w/e
+    local function primaryexp(ls, v)
+        local tk = ls.t_token
+        if tk == '(' then
+            local line = ls.linenumber
+            save_token(ls)
+            luaX.next(ls)
+            expr(ls, v)
+            check_match(ls, ')', '(', line)
+            luaK.dischargevars(ls.fs, v)
+        elseif tk == TK.NAME then
+            singlevar(ls, v)
+        else
+            luaX.syntaxerror(ls, "unexpected symbol")
+        end
+    end
+
+    function suffixedexp(ls, v)
+        -- suffixedexp ->
+        --   primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs }
+        local fs = ls.fs
+        local line = ls.linenumber
+        primaryexp(ls, v)
+        repeat
+            local tk = ls.t_token
+            if tk == '.' then -- fieldsel
+                fieldsel(ls, v)
+            elseif tk == '[' then -- '[' exp1 ']'
+                local key = {}
+                luaK.exp2anyregup(fs, v)
+                yindex(ls, key)
+                luaK.indexed(fs, v, key)
+            elseif tk == ':' then -- ':' NAME funcargs
+                local key = {}
+                save_token(ls)
+                luaX.next(ls)
+                checkname(ls, key)
+                luaK.self(fs, v, key)
+                funcargs(ls, v, line)
+            elseif tk == '(' or tk == TK.STRING or tk == '{' then -- funcargs
+                luaK.exp2nextreg(fs, v)
+                funcargs(ls, v, line)
+            else
+                return
+            end
+        until nil
+    end
+
+    local function simpleexp(ls, v)
+        -- simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... |
+        --              constructor | FUNCTION body | suffixedexp
+        local tk = ls.t_token
+        if tk == TK.FLT then
+            init_exp(v, VKFLT, 0)
+            v.val = ls.t_seminfo
+        elseif tk == TK.INT then
+            init_exp(v, VKINT, 0)
+            v.val = ls.t_seminfo
+        elseif tk == TK.STRING then
+            codestring(ls, v, ls.t_seminfo)
+        elseif tk == TK.NIL then
+            init_exp(v, VNIL, 0)
+        elseif tk == TK.TRUE then
+            init_exp(v, VTRUE, 0)
+        elseif tk == TK.FALSE then
+            init_exp(v, VFALSE, 0)
+        elseif tk == TK.DOTS then -- vararg
+            local fs = ls.fs
+            check_condition(ls, fs.f.is_vararg,
+                            "cannot use '...' outside a vararg function")
+            init_exp(v, VVARARG, luaK.codeABC(fs, OP.VARARG, 0, 1, 0))
+        elseif tk == '{' then
+            constructor(ls, v)
+        elseif tk == TK.FUNCTION then
+            save_token(ls)
+            luaX.next(ls)
+            body(ls, v, 0, ls.linenumber)
+        else
+            suffixedexp(ls, v)
+        end
+        save_token(ls)
+        luaX.next(ls)
+    end
+
+    local function getunopr(op)
+        if op == TK.NOT or
+           op == '-' or
+           op == '~' or
+           op == '#' then
+            return op
+        end
+    end
+
+    -- order intentionally swapped
+    local priority = {
+        ['+'] =       {left=10, right=10},
+        ['-'] =       {left=10, right=10},
+        ['*'] =       {left=11, right=11},
+        ['%'] =       {left=11, right=11},
+        ['^'] =       {left=14, right=13},
+        ['/'] =       {left=11, right=11},
+        [TK.IDIV] =   {left=11, right=11},
+        ['&'] =       {left=6,  right=6},
+        ['|'] =       {left=4,  right=4},
+        ['~'] =       {left=5,  right=5},
+        [TK.SHL] =    {left=7,  right=7},
+        [TK.SHR] =    {left=7,  right=7},
+        [TK.CONCAT] = {left=9,  right=8},
+        [TK.EQ] =     {left=3,  right=3},
+        ['<'] =       {left=3,  right=3},
+        [TK.LE] =     {left=3,  right=3},
+        [TK.NE] =     {left=3,  right=3},
+        ['>'] =       {left=3,  right=3},
+        [TK.GE] =     {left=3,  right=3},
+        [TK.AND] =    {left=2,  right=2},
+        [TK.OR] =     {left=1,  right=1},
+    }
+
+    -- order intentionally swapped
+    local function getbinopr(op)
+        if priority[op] then
+            return op
+        end
+    end
+
+    local UNARY_PRIORITY = 12
+
+    -- subexpr -> (simpleexp | unop subexpr) { binop subexpr }
+    -- where 'binop' is any binary operator with a priority higher than 'limit'
+    local function subexpr(ls, v, limit)
+        enterlevel(ls)
+        local uop = getunopr(ls.t_token)
+        if uop then
+            local line = ls.linenumber
+            save_token(ls)
+            luaX.next(ls)
+            subexpr(ls, v, UNARY_PRIORITY)
+            luaK.prefix(ls.fs, uop, v, line)
+        else
+            simpleexp(ls, v)
+        end
+        -- expand while operators have priorities higher than 'limit'
+        local op = getbinopr(ls.t_token)
+        while op and priority[op].left > limit do
+            local line = ls.linenumber
+            save_token(ls)
+            luaX.next(ls)
+            luaK.infix(ls.fs, op, v)
+            -- read sub-expression with higher priority
+            local nextop = subexpr(ls, v2, priority[op].right)
+            luaK_posfix(ls.fs, op, v, v2, line)
+            op = nextop
+        end
+        leavelevel(ls)
+        return op -- return first untreated operator
+    end
+
+    function expr(ls, v)
+        subexpr(ls, v, 0)
+    end
+end)()
+
+;(function() -- avoid issues with 200 locals or w/e
+    -- block TODO
+    -- check_conflict TODO
+    -- assignment TODO
+    -- cond TODO
+
+    local function gotostat(ls, pc)
+        local line = ls.linenumber
+        local label
+        if testnext(ls, TK.GOTO) then
+            label = str_checkname(ls)
+        else
+            save_token(ls)
+            luaX.next(ls) -- skip break
+            label = "break" -- ?
+        end
+        local g = newlabelentry(ls, ls.dyd.gt, label, line, pc)
+        findlabel(ls, g) -- close it if label already defined
+    end
+
+    -- checkrepeated TODO
+
+    local function skipnoopstat(ls)
+        while ls.t_token == ';' or ls.t_token == TK.DBCOLON do
+            statement(ls)
+        end
+    end
+
+    -- labelstat TODO
+    -- whilestat TODO
+    -- repeatstat TODO
+    -- exp1 TODO
+    -- forbody TODO
+    -- fornum TODO
+    -- forlist TODO
+    -- forstat TODO
+    -- test_then_block TODO
+    -- ifstat TODO
+    -- localfunc TODO
+    -- localstat TODO
+    -- funcname TODO
+    -- funcstat TODO
+    -- exprstat TODO
+
+    local function retstat(ls)
+        local fs = ls.fs
+        local e = {}
+        local first, nret
+        if block_follow(ls, true) or ls.t_token == ';' then
+            first, nret = 0, 0
+        else
+            nret = explist(ls, e)
+            if hasmultret(e.k) then
+                luaK.setmultret(fs, e)
+                if e.k == VCALL and nret == 1 then -- tail call?
+                    --SET_OPCODE(getinstruction(fs,e), OP_TAILCALL)
+                    --lua_assert(GETARG_A(getinstruction(fs,e)) == fs.nactvar)
+                end
+                first = fs.nactvar
+                nret = LUA_MULTRET
+            else
+                if nret == 1 then
+                    first = luaK.exp2anyreg(fs, e)
+                else
+                    luaK.exp2nextreg(fs, e)
+                    first = fs.nactvar
+                    --lua_assert(nret == fs.freereg - first)
+                end
+            end
+        end
+        luaK.ret(fs, first, nret)
+        testnext(ls, ';') -- skip optional semicolon
+    end
+
+    function statement(ls)
+        local line = ls.linenumber
+        enterlevel(ls)
+        local tk = ls.t_token
+        if tk == ';' then -- stat -> ';' (empty statement)
+            save_token(ls)
+            luaX.next(ls) -- skip ';'
+        elseif tk == TK.IF then -- stat -> ifstat
+            ifstat(ls, line)
+        elseif tk == TK.WHILE then -- stat -> whilestat
+            whilestat(ls, line)
+        elseif tk == TK.DO then --> stat -> DO block END
+            save_token(ls)
+            luaX.next(ls) -- skip DO
+            block(ls)
+            check_match(ls, TK_END, TK_DO, line)
+        elseif tk == TK.FOR then -- stat -> forstat
+            forstat(ls, line)
+        elseif tk == TK.REPEAT then -- stat -> repeatstat
+            repeatstat(ls, line)
+        elseif tk == TK.FUNCTION then -- stat -> funcstat
+            funcstat(ls, line)
+        elseif tk == TK.LOCAL then -- stat -> localstat
+            save_token(ls)
+            luaX.next(ls) -- skip LOCAL
+            if testnext(ls, TK.FUNCTION) then -- local function?
+                localfunc(ls)
+            else
+                localstat(ls)
+            end
+        elseif tk == TK.DBCOLON then -- stat -> label
+            save_token(ls)
+            luaX.next(ls) -- skip double colon
+            labelstat(ls, str_checkname(ls), line)
+        elseif tk == TK.RETURN then -- stat -> retstat
+            save_token(ls)
+            luaX.next(ls) -- skip RETURN
+            retstat(ls)
+        elseif tk == TK.BREAK     -- stat -> breakstat
+            or tk == TK.GOTO then -- stat -> 'goto' NAME
+            gotostat(ls, luaK.jump(ls.fs))
+        else
+            exprstat(ls)
+        end
+        --lua_assert(ls.fs.f.maxstacksize >= ls.fs.freereg and
+        --           ls.fs.freereg >= ls.fs.nactvar)
+        ls.fs.freereg = ls.fs.nactvar -- free registers
+        leavelevel(ls)
+    end
+end)()
+
+local function mainfunc(ls, fs)
+    local bl = {}
+    open_func(ls, fs, bl)
+    fs.f.is_vararg = true
+    -- we don't worry about these:
+    --local v = {}
+    --init_exp(v, VLOCAL, 0)
+    --newupvalue(fs, ls.envn, &v)
+    luaX.next(ls)
+    statlist(ls)
+    check(ls, parser.EOZ)
+    close_func(ls)
+end
+
+local function worst_cratera_parser(ls) -- luaY.parser
+    local lexstate, funcstate, cl
+    lexstate = ls
+    funcstate = {}
+    cl = {}
+    lexstate.h = {}
+    cl.p = {}
+    funcstate.f = cl.p
+    funcstate.f.source = lexstate.source
+    --lua_assert(iswhite(funcstate.f))
+    --lexstate.buff = {} -- ???
+    lexstate.dyd = {actvar = {}, gt = {}, label = {}} -- ???
+    if not lexstate.linenumber then lexstate.linenumber = 1 end -- not managed by us
+    lexstate.lastline = 1
+    mainfunc(lexstate, funcstate)
+    --lua_assert(!funcstate.prev and funcstate.nups == 1 and !lexstate.fs)
+    --lua_assert(#dyd.actvar == 0 and #dyd.gt == 0 and #dyd.label == 0)
+    return cl -- close enough
+end
+
+local defs = selfify({})
+defs[parser.EOZ] = parser.FALLBACK
+defs[parser.FALLBACK] = function(state, token)
+    local coro = state.coro
+    if not coro then
+        coro = coroutine.create(worst_cratera_parser)
+        state.coro = coro
+        state.t = {} -- token
+        assert(coroutine.resume(coro, state))
+    end
+    local _, override = assert(coroutine.resume(coro, token))
+    if override then return override end
+    return "self"
+end
 
 return {
     defs = defs,