lua.cratera/compiler.lua

770 lines
23 KiB
Lua

--[[
This file is part of cratera.lua - pure-Lua Cratera-to-Lua transpiler
Copyright (C) 2019 Soni L.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
--]]
--[[
This software is based on Lua 5.1 and Lua 5.3
Lua 5.1 license:
/******************************************************************************
* Copyright (C) 1994-2012 Lua.org, PUC-Rio. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/
Lua 5.3 license:
/******************************************************************************
* Copyright (C) 1994-2018 Lua.org, PUC-Rio.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/
--]]
-- 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 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 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,
}