Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lua2c/lib/lua2c/ast2cast.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2268 lines (1949 sloc)
62.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- | |
| -- ast2cast.lua | |
| -- Converts Lua ASTs to C ASTs. | |
| -- | |
| -- Variable naming conventions: ast (Lua AST), cast (C AST). | |
| -- | |
| -- (c) 2008 David Manura. Licensed in the same terms as Lua (MIT license). | |
| -- See included LICENSE file for full licensing details. | |
| --luaanalyze checks | |
| --! local t_G = typeimport"luaanalyze.library.standard" | |
| --! t_G.add_field "mlc" | |
| --! t_G.add_field "mlp" | |
| --! local ast_t = require "luaanalyze.type.ast" | |
| --! ast_t.add_field "name" | |
| --! ast_t.add_field "upvalue" | |
| --! ast_t.add_field "idx" | |
| --! ast_t.add_field "type" | |
| --! typematch("[^c]ast$", ast_t) --FIX:match | |
| --! checkglobals() | |
| --! checktypes() | |
| local M = {} | |
| local _G = _G | |
| local assert = _G.assert | |
| local ipairs = _G.ipairs | |
| local math = _G.math | |
| local select = _G.select | |
| local setmetatable = _G.setmetatable | |
| local string = _G.string | |
| local tostring = _G.tostring | |
| local type = _G.type | |
| local table = _G.table | |
| local unpack = _G.unpack | |
| -- converts Metalua AST <ast> built from string | |
| -- <src> to C AST <cast>. | |
| -- returns(cast) | |
| local function ast_to_cast(src, ast) | |
| --##------------------------------------------------------------------ | |
| --## Note: this is a large function nesting many closures; | |
| --## indentation of its contents is omitted. | |
| --##------------------------------------------------------------------ | |
| -- info on current scope. includes | |
| -- table of local variables. | |
| -- Maps name -> stack index | |
| local _currentscope = {['.closurelevel'] = 0} | |
| -- topmost stack index | |
| local _idxtop = 0 | |
| local _names = {} | |
| local _varid = 0 | |
| -- note: _is_created maps function name to boolean indicated whether | |
| -- function has been generated. | |
| local _is_created = {} | |
| -- Information on function currently being compiled. | |
| local _funcinfo = {is_vararg=false, nformalparams=0, | |
| is_lc_nextra_used=false, is_lc_nactualargs_used=false, | |
| is_lc_nextra_used_debug=false, is_lc_nactualargs_used_debug=false, | |
| idxtopmax = 0 | |
| --old: outervars={} | |
| } | |
| -- LUA_MINSTACK value in luaconf.h | |
| local LUA_MINSTACK = 20 | |
| -- Mutators for _idxtop. | |
| local function idxtop_change(n) | |
| _idxtop = _idxtop + n | |
| _funcinfo.idxtopmax = math.max(_funcinfo.idxtopmax, _idxtop) | |
| end | |
| local function idxtop_restore(idx) | |
| assert(idx <= _idxtop) | |
| _idxtop = idx | |
| end | |
| -- Builds set from elements in array t. | |
| local function makeset(t) | |
| local set = {} | |
| for _,v in ipairs(t) do set[v] = true end | |
| return set | |
| end | |
| -- Set of identifiers of binary operators | |
| -- (metamethod name without "__" prefix). | |
| local is_binopid = makeset { | |
| "add", "sub", "mul", "div", | |
| "mod", "pow", "concat", "eq", | |
| "lt", "le", "and", "or" | |
| } | |
| -- Set of identifiers of unary operators. | |
| local is_unopid = makeset { | |
| "not", "len", "unm" | |
| } | |
| -- Set of binary ops with metamethod behavior like "+". | |
| local is_arithbinop = makeset { | |
| "add", "sub", "mul", "div", "mod", "pow" | |
| } | |
| -- Maps operator identifier to function | |
| -- implementing that identifier (without metamethods). | |
| local fops = { | |
| ["add"] =function(a,b) return a+b end, | |
| ["sub"] =function(a,b) return a-b end, | |
| ["mul"] =function(a,b) return a*b end, | |
| ["div"] =function(a,b) return a/b end, | |
| ["mod"] =function(a,b) return a%b end, | |
| ["pow"] =function(a,b) return a^b end, | |
| ["concat"]=function(a,b) return a..b end, | |
| ["eq"] =function(a,b) return a==b end, | |
| ["lt"] =function(a,b) return a<b end, | |
| ["le"] =function(a,b) return a<=b end, | |
| ["and"] =function(a,b) return a and b end, | |
| ["or"] =function(a,b) return a or b end, | |
| ["not"] =function(a) return not a end, | |
| ["len"] =function(a) return #a end, | |
| ["unm"] =function(a) return -a end | |
| } | |
| -- Maps operator identifier to function that returns | |
| -- a C code string implementing that identifier | |
| -- (without metamethods). | |
| -- Only for binary arthmetic ops. | |
| local opid_to_c = { | |
| ["add"]=function(a,b) return a .. ' + ' .. b end, | |
| ["sub"]=function(a,b) return a .. ' - ' .. b end, | |
| ["mul"]=function(a,b) return a .. ' * ' .. b end, | |
| ["div"]=function(a,b) return a .. ' / ' .. b end, | |
| ["mod"]=function(a,b) return | |
| a .. ' - floor(' .. a .. '/' .. b ..')*' .. b end, | |
| -- caution: a and b evaluate twice | |
| -- note: requies math.h | |
| ["pow"]=function(a,b) return 'pow(' .. a .. ',' .. b .. ')' end, | |
| -- note: requies math.h | |
| } | |
| -- Converts Lua object to Lua AST. | |
| local function obj_to_ast(obj) | |
| if obj == nil then | |
| return {tag='Nil'} | |
| elseif obj == true then | |
| return {tag='True'} | |
| elseif obj == false then | |
| return {tag='False'} | |
| elseif type(obj) == 'number' then | |
| return {tag='Number', obj} | |
| elseif type(obj) == 'string' then | |
| return {tag='String', obj} | |
| else | |
| assert(false, tostring(obj)) | |
| end | |
| end | |
| -- remove whitespace from front and end of string | |
| local function trim(s) | |
| return s:gsub('^%s+', ''):gsub('%s+$', '') | |
| end | |
| -- Prepends comment to comments above C AST node. | |
| local function prepend_comment(cast, comment) | |
| local s = cast.comment and comment .. '\n' .. cast.comment or comment | |
| cast.comment = s | |
| end | |
| -- Appends comment to comments above C AST node. | |
| local function append_comment(cast, comment) | |
| local s = cast.comment and cast.comment .. '\n' .. comment or comment | |
| cast.comment = s | |
| end | |
| -- Appends comment to comments after C AST node. | |
| local function append_comment_below(cast, comment) | |
| local s = cast.aftercomment and cast.aftercomment .. '\n' .. | |
| comment or comment | |
| cast.aftercomment = s | |
| end | |
| -- Appends elements to array t2 to array t1. | |
| local function append_array(t1,t2) | |
| for _,v in ipairs(t2) do t1[#t1+1] = v end | |
| end | |
| local function append_cast(cast1, cast2) | |
| if cast2.tag ~= nil then -- enclosing block omitted (convenience) | |
| -- :IMPROVE: the convenience possibly isn't | |
| -- worth the ambiguity. make separate function? | |
| assert(not cast2.pre) | |
| assert(not cast2.idx) | |
| cast2 = {cast2, pre=cast2.pre or {}, idx=nil} | |
| end | |
| append_array(cast1, cast2) | |
| if cast2.comment then | |
| if #cast2 > 0 then | |
| prepend_comment(cast2[1], cast2.comment) | |
| elseif #cast1 > 0 then | |
| append_comment_below(cast1[#cast1], cast2.comment) | |
| else | |
| assert(false) | |
| end | |
| end | |
| if cast2.aftercomment then | |
| if #cast2 > 0 then | |
| append_comment_below(cast2[#cast2], cast2.aftercomment) | |
| elseif #cast1 > 0 then | |
| append_comment_below(cast1[#cast1], cast2.aftercomment) | |
| else | |
| assert(false) | |
| end | |
| end | |
| append_array(cast1.pre, cast2.pre) | |
| return cast2 --FIX:improve style? | |
| end | |
| -- Constructor and type for C AST nodes. | |
| local cexpr_mt = {} | |
| cexpr_mt.__index = cexpr_mt | |
| function cexpr_mt:append(cast) | |
| return append_cast(self, cast) | |
| end | |
| local function cexpr(...) | |
| return setmetatable({idx=-1, pre={}, ...}, cexpr_mt) | |
| end | |
| local C_mt = {} | |
| function C_mt.__index(t,k,v) | |
| local f = function(...) return {tag=k, ...} end | |
| t.k = f -- cache | |
| return f | |
| end | |
| local C = setmetatable({}, C_mt) | |
| local function tag(o) | |
| return type(o) == 'table' and o.tag or nil | |
| end | |
| -- returns C AST of double for given C AST idx. | |
| local function todouble(idx) | |
| if type(idx) == 'string' then | |
| return C.Id(idx) | |
| else | |
| return C.lua_tonumber(idx) | |
| end | |
| end | |
| -- returns C AST of bool for given C AST idx. | |
| local function tobool(idx) | |
| if type(idx) == 'string' then | |
| return C.Id(idx) | |
| else | |
| return C.lua_toboolean(idx) | |
| end | |
| end | |
| -- Adjusts absolute index of local variable, | |
| -- given that in vararg (...) functions, base index depends on | |
| -- arguments passed in. Can return C AST. | |
| local function realidx(idx) | |
| -- pseudoindex | |
| if type(idx) == 'table' and idx.tag == 'C' and | |
| idx[1] == 'lua_upvalueindex(1)' | |
| then | |
| return idx | |
| end | |
| local idxreal = tag(idx) == 'Upval' and idx[1] or idx | |
| if _funcinfo.is_vararg and | |
| (tag(idxreal) == 'Id' or idxreal > _funcinfo.nformalparams) | |
| then | |
| if tag(idx) == 'Upval' then | |
| -- no adjustment | |
| else | |
| idx = C.Op('+', idx, C.Id'lc_nextra') | |
| _funcinfo.is_lc_nextra_used = true | |
| end | |
| end | |
| return idx | |
| end | |
| -- Gets real index of local var. | |
| local function localidx(name) | |
| local idx = _currentscope[name] | |
| if not idx then return end | |
| return realidx(idx) | |
| end | |
| --old: local function isupvalue(name) | |
| -- return not _currentscope[name] and _funcinfo.outervars[name] | |
| --end | |
| -- Adjusts stack index to relative (negative) position offset. note: | |
| -- use only when values inside offset are on the stack | |
| -- (non-psuedoindices). | |
| -- Note: allows C AST. | |
| local function adjustidx(offset, idx) | |
| if type(idx) ~= 'number' or idx > 0 then | |
| return idx | |
| else -- relative stack index or pseudoindex | |
| -- FIX:pseudoindices not supported? | |
| return idx + offset + 1 | |
| end | |
| end | |
| -- Adjusts relative stack indicies | |
| -- Note: allows C ASTs. | |
| local function adjustidxs(...) | |
| local nused = 0 | |
| local result = {...} | |
| for i=#result,1,-1 do | |
| local idx = result[i] | |
| if type(idx) == 'number' and idx < 0 then | |
| --FIX: and idx > LUA_REGISTRYINDEX ? | |
| result[i] = result[i] - nused -- adjust | |
| end | |
| if idx == -1 then | |
| nused = nused + 1 | |
| end | |
| end | |
| return unpack(result) | |
| end | |
| -- Counts number of temporary values on stack. | |
| local function countstack(...) | |
| local n = select('#', ...) | |
| local ncount = 0 | |
| for i=1,n do | |
| local idx = select(i, ...) | |
| if idx == -1 then ncount = ncount + 1 end | |
| end | |
| return ncount | |
| end | |
| -- Given AST ast taken from inside code string src, | |
| -- return code string representing AST. | |
| local function get_ast_string(src, ast, ...) | |
| local first = ast.lineinfo.first[3] | |
| local last = ast.lineinfo.last [3] | |
| local code = src:sub(first, last) | |
| return code | |
| end | |
| -- Gets next unique ID. Useful for generating unique variable names. | |
| -- orig_name is string of arbitrary text to base variable name | |
| -- on (optional and may be ignored) | |
| -- prefix is prefix string for variable (defaults to '') | |
| local MAX_IDENTIFIER_LENGTH = 60 -- note: not a hard limit | |
| local function nextid(orig_name, prefix) | |
| orig_name = orig_name or '' | |
| prefix = prefix or '' | |
| -- ensure will form a valid C identifier | |
| local name = orig_name:gsub('[^%w_]', '_') | |
| -- ensure uniqueness | |
| _names[name] = (_names[name] or 0) + 1 | |
| local id = | |
| 'lc' .. prefix .. _names[name] .. (name == '' and '' or '_') .. name | |
| return id | |
| end | |
| -- Gets next unique ID for lexical variable. | |
| local function nextvarid() | |
| _varid = _varid + 1 | |
| return _varid | |
| end | |
| local function newscope() | |
| local currentscope_old = _currentscope | |
| _currentscope = setmetatable({}, {__index = currentscope_old}) | |
| return currentscope_old | |
| end | |
| local function restore_scope(currentscope_save) | |
| _currentscope = currentscope_save | |
| end | |
| local function restore_stack_rel(cast, idx_old) | |
| local npushed = _idxtop - idx_old | |
| if npushed ~= 0 then | |
| cast:append(C.lua_pop(npushed)) | |
| append_comment(cast[#cast], 'internal: stack cleanup on scope exit') | |
| end | |
| idxtop_change(- npushed) | |
| end | |
| local function restore_stack_abs(cast, idx_old_cast, idx_old) | |
| cast:append(C.lua_settop(realidx(idx_old_cast))) | |
| idxtop_restore(idx_old) | |
| end | |
| -- Returns whether expression could possibly return a number of argument | |
| -- different from 1. | |
| -- note: expr_ast can be nil | |
| local function can_multi(expr_ast) | |
| return expr_ast and (expr_ast.tag == 'Call' or expr_ast.tag == 'Invoke' | |
| or expr_ast.tag == 'Dots') | |
| end | |
| -- Performs constant folding on AST. | |
| local function constant_fold(ast) | |
| if ast.tag == 'Op' then | |
| local opid = ast[1] | |
| local a_ast = constant_fold(ast[2]) | |
| local b_ast = ast[3] and constant_fold(ast[3]) | |
| if b_ast then -- binary op | |
| if a_ast.tag == 'Number' and b_ast.tag == 'Number' then | |
| return obj_to_ast(fops[opid](a_ast[1], b_ast[1])) | |
| end | |
| else -- unary op | |
| if a_ast.tag == 'Number' then | |
| return obj_to_ast(fops[opid](a_ast[1])) | |
| end | |
| end | |
| end | |
| return ast | |
| end | |
| local function get_closuretableidx_cast() | |
| return localidx('.closuretable') | |
| end | |
| -- Deduce names of functions by the variables they are assigned to. | |
| -- Given lists of LHS and RHS expressions, adds a "name" field | |
| -- to any applicable RHS expression. | |
| local function deduce_function_names(ls_ast, rs_ast) | |
| for i,r_ast in ipairs(rs_ast) do | |
| local l_ast = ls_ast[i] | |
| if ls_ast[i] and r_ast.tag == 'Function' then | |
| local name = get_ast_string(src, l_ast) | |
| r_ast.name = name | |
| end | |
| end | |
| end | |
| -- forward declarations | |
| local genexpr | |
| local genblock | |
| -- Converts any Lua arithmetic op AST to C AST. | |
| local function genarithbinop(ast, where) | |
| -- Returns C AST node definition arithmetic binary op with given | |
| -- identitifier. | |
| local function makebinop(opid) | |
| local ccode = string.format([[ | |
| /* __%s metamethod handler. | |
| * warning: assumes indices in range LUA_REGISTRYINDEX < x < 0 are relative. */ | |
| static void lc_%s(lua_State * L, int idxa, int idxb) { | |
| if (lua_isnumber(L,idxa) && lua_isnumber(L,idxb)) { | |
| lua_pushnumber(L,%s); | |
| } | |
| else { | |
| if (luaL_getmetafield(L,idxa,"__%s")||luaL_getmetafield(L,idxb,"__%s")) { | |
| lua_pushvalue(L,idxa < 0 && idxa > LUA_REGISTRYINDEX ? idxa-1 : idxa); | |
| lua_pushvalue(L,idxb < 0 && idxb > LUA_REGISTRYINDEX ? idxb-2 : idxb); | |
| lua_call(L,2,1); | |
| } | |
| else { | |
| luaL_error(L, "attempt to perform arithmetic"); | |
| } | |
| } | |
| } | |
| ]], opid, opid, | |
| assert(opid_to_c[opid])("lua_tonumber(L,idxa)", "lua_tonumber(L,idxb)"), | |
| opid, opid) | |
| return C.C(ccode) | |
| end | |
| local opid, a_ast, b_ast = ast[1], ast[2], ast[3] | |
| local cast = cexpr() | |
| if opid == 'pow' or opid == 'mod' then | |
| if not _is_created['math.h'] then | |
| append_array(cast.pre, {C.Include'<math.h>'}) | |
| _is_created['math.h'] = true | |
| end | |
| end | |
| local fname = "lc_" .. opid | |
| if not _is_created[fname] then | |
| append_array(cast.pre, {makebinop(opid)}) | |
| _is_created[fname] = true | |
| end | |
| local a_idx = cast:append(genexpr(a_ast, 'anywhere')).idx | |
| local b_idx = cast:append(genexpr(b_ast, 'anywhere')).idx | |
| local nstack = countstack(a_idx, b_idx) | |
| a_idx, b_idx = adjustidxs(a_idx, b_idx) | |
| cast:append(C.CallL('lc_'..opid, a_idx, b_idx)) | |
| for i=1,nstack do | |
| cast:append(C.lua_remove(-2)) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua equality op AST to C AST. | |
| local function geneqop(ast, where) | |
| local a_ast, b_ast = ast[2], ast[3] | |
| local cast = cexpr() | |
| local a_idx = cast:append(genexpr(a_ast, where)).idx | |
| local b_idx = cast:append(genexpr(b_ast, where)).idx | |
| local nstack = (a_idx == -1 and 1 or 0) + (b_idx == -1 and 1 or 0) | |
| a_idx, b_idx = adjustidxs(a_idx, b_idx) | |
| local id = nextid(); cast:append(C.Let(id, C.lua_equal(a_idx, b_idx))) | |
| if nstack ~= 0 then | |
| cast:append(C.lua_pop(nstack)) | |
| end | |
| cast:append(C.lua_pushboolean(C.Id(id))) | |
| return cast | |
| end | |
| -- Converts Lua less-than op AST to C AST. | |
| local function genltop(ast, where) | |
| local opid, a_ast, b_ast = ast[1], ast[2], ast[3] | |
| local cast = cexpr() | |
| local a_idx = cast:append(genexpr(a_ast, 'anywhere')).idx | |
| local b_idx = cast:append(genexpr(b_ast, 'anywhere')).idx | |
| local nstack = countstack(a_idx, b_idx) | |
| a_idx, b_idx = adjustidxs(a_idx, b_idx) | |
| local id = nextid(); cast:append(C.Let(id, C.lua_lessthan(a_idx, b_idx))) | |
| if nstack ~= 0 then | |
| cast:append(C.lua_pop(nstack)) | |
| end | |
| cast:append(C.lua_pushboolean(C.Id(id))) | |
| return cast | |
| end | |
| -- Converts Lua less-than-or-equal op AST to C AST. | |
| -- Why does the Lua C API implement lua_lessthan but not lua_lessequal? | |
| -- (note: lessqual is defined in lvm.c). | |
| local function genleop(ast, where) | |
| local function makeop() | |
| local cast = C.C [[ | |
| /* warning: assumes indices in range LUA_REGISTRYINDEX < x < 0 are relative. */ | |
| static int lc_le(lua_State * L, int idxa, int idxb) { | |
| if (lua_type(L,idxa) == LUA_TNUMBER && lua_type(L,idxb) == LUA_TNUMBER) { | |
| return lua_tonumber(L,idxa) <= lua_tonumber(L,idxb); | |
| } | |
| else if (lua_type(L,idxa) == LUA_TSTRING && lua_type(L,idxb) == LUA_TSTRING) { | |
| /* result similar to lvm.c l_strcmp */ | |
| return lua_lessthan(L,idxa,idxb) || lua_rawequal(L,idxa,idxb); | |
| } | |
| else if (luaL_getmetafield(L,idxa,"__le")||luaL_getmetafield(L,idxb,"__le")) { | |
| lua_pushvalue(L,idxa < 0 && idxa > LUA_REGISTRYINDEX ? idxa-1 : idxa); | |
| lua_pushvalue(L,idxb < 0 && idxb > LUA_REGISTRYINDEX ? idxb-2 : idxb); | |
| lua_call(L,2,1); | |
| const int result = lua_toboolean(L,-1); | |
| lua_pop(L,1); | |
| return result; | |
| } | |
| else if (luaL_getmetafield(L,idxa,"__lt")||luaL_getmetafield(L,idxb,"__lt")) { | |
| lua_pushvalue(L,idxb < 0 && idxb > LUA_REGISTRYINDEX ? idxb-1 : idxb); | |
| lua_pushvalue(L,idxa < 0 && idxa > LUA_REGISTRYINDEX ? idxa-2 : idxa); | |
| lua_call(L,2,1); | |
| const int result = ! lua_toboolean(L,-1); | |
| lua_pop(L,1); | |
| return result; | |
| } | |
| else { | |
| return luaL_error(L, "attempt to compare"); | |
| } | |
| } | |
| ]] | |
| return cast | |
| end | |
| local opid, a_ast, b_ast = ast[1], ast[2], ast[3] | |
| local cast = cexpr() | |
| local fname = "lc_le" | |
| if not _is_created[fname] then | |
| append_array(cast.pre, {makeop(opid)}) | |
| _is_created[fname] = true | |
| end | |
| local a_idx = cast:append(genexpr(a_ast, 'anywhere')).idx | |
| local b_idx = cast:append(genexpr(b_ast, 'anywhere')).idx | |
| local nstack = countstack(a_idx, b_idx) | |
| a_idx, b_idx = adjustidxs(a_idx, b_idx) | |
| if nstack == 0 then | |
| cast:append(C.lua_pushboolean(C.CallL('lc_le', a_idx, b_idx))) | |
| else | |
| local id = nextid() | |
| cast:append(C.Let(id, C.CallL('lc_le', a_idx, b_idx))) | |
| cast:append(C.lua_pop(nstack)) | |
| cast:append(C.lua_pushboolean(C.Id(id))) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua binary logical op AST to C AST. | |
| local function genlogbinop(ast, where) | |
| local opid, a_ast, b_ast = ast[1], ast[2], ast[3] | |
| local cast = cexpr() | |
| cast:append(genexpr(a_ast, 'onstack')) | |
| assert(opid == 'and' or opid == 'or') | |
| local expr_cast = C.lua_toboolean(-1) | |
| if opid == 'or' then expr_cast = C.Not(expr_cast) end | |
| local block_cast = cexpr() | |
| block_cast:append(C.lua_pop(1)) | |
| block_cast:append(genexpr(b_ast, 'onstack')) | |
| cast:append(C.If(expr_cast, block_cast)) | |
| append_array(cast.pre, block_cast.pre) | |
| return cast | |
| end | |
| -- Converts Lua unary logical (i.e. not) op AST to C AST. | |
| local function gennotop(ast, where) | |
| local opid, a_ast = ast[1], ast[2] | |
| local cast = cexpr() | |
| local a_idx = cast:append(genexpr(a_ast, 'anywhere')).idx | |
| cast:append(C.lua_pushboolean(C.Not(tobool(a_idx)))) | |
| if a_idx == -1 then | |
| cast:append(C.lua_remove(-2)) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua length op AST to C AST. | |
| local function genlenop(ast, where) | |
| local opid, a_ast = ast[1], ast[2] | |
| local cast = cexpr() | |
| local a_idx = cast:append(genexpr(a_ast, 'anywhere')).idx | |
| --FIX:call metatable __len for userdata? | |
| local id = nextid() | |
| cast:append(C.LetDouble(id, C.lua_objlen(a_idx))) | |
| if a_idx == -1 then | |
| cast:append(C.lua_pop(1)) | |
| end | |
| cast:append(C.lua_pushnumber(C.Id(id))) | |
| return cast | |
| end | |
| -- Converts Lua unary minus op AST to C AST. | |
| local function genunmop(ast, where) | |
| -- Returns C AST node definition of op. | |
| local function makeop() | |
| local ccode = [[ | |
| /* __unm metamethod handler. | |
| * warning: assumes indices in range LUA_REGISTRYINDEX < x < 0 are relative. */ | |
| static void lc_unm(lua_State * L, int idxa) { | |
| if (lua_isnumber(L,idxa)) { | |
| lua_pushnumber(L,- lua_tonumber(L, idxa)); | |
| } | |
| else { | |
| if (luaL_getmetafield(L,idxa,"__unm")) { | |
| lua_pushvalue(L,idxa < 0 && idxa > LUA_REGISTRYINDEX ? idxa-1 : idxa); | |
| lua_call(L,1,1); | |
| } | |
| else { | |
| luaL_error(L, "attempt to perform arithmetic"); | |
| } | |
| } | |
| } | |
| ]] | |
| return C.C(ccode) | |
| end | |
| local opid, a_ast = ast[1], ast[2] | |
| assert(opid == 'unm') | |
| local cast = cexpr() | |
| local fname = "lc_" .. opid | |
| if not _is_created[fname] then | |
| append_array(cast.pre, {makeop()}) | |
| _is_created[fname] = true | |
| end | |
| local a_idx = cast:append(genexpr(a_ast, 'anywhere')).idx | |
| local nstack = countstack(a_idx) | |
| a_idx = adjustidxs(a_idx) | |
| cast:append(C.CallL('lc_'..opid, a_idx)) | |
| for i=1,nstack do | |
| cast:append(C.lua_remove(-2)) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua concatenation op AST to C AST. | |
| local function genconcatop(ast, where) | |
| local a_ast, b_ast = ast[2], ast[3] | |
| local cast = cexpr() | |
| cast:append(genexpr(a_ast, 'onstack')) | |
| cast:append(genexpr(b_ast, 'onstack')) | |
| cast:append(C.lua_concat(2)) | |
| return cast | |
| end | |
| -- Creates C AST for table used to manage upvalues | |
| -- for a closure. | |
| -- note: call activate_closure_table() afterward. | |
| local function gennewclosuretable() | |
| local cast = cexpr() | |
| if not _is_created['lc_newclosuretable'] then | |
| -- note: index idx cannot be relative. | |
| -- note: key 0 points to parent table. | |
| local body_cast = cexpr() | |
| --IMPROVE: C.C usage | |
| body_cast:append(C.C[[ | |
| /* pushes new closure table onto the stack, using closure table at | |
| * given index as its parent */ | |
| static void lc_newclosuretable(lua_State * L, int idx) {]]) | |
| body_cast:append( cexpr( cexpr( | |
| C.lua_newtable(), | |
| C.lua_pushvalue(C.Id'idx'), | |
| C.lua_rawseti(-2,0) | |
| ))) | |
| body_cast:append(C.C'}') | |
| append_array(cast.pre, body_cast) | |
| _is_created['lc_newclosuretable'] = true | |
| end | |
| cast:append(C.CallL('lc_newclosuretable', get_closuretableidx_cast())) | |
| idxtop_change(1) | |
| local id = nextid() | |
| cast:append(C.Enum(id, _idxtop)) | |
| if not _is_created['assert.h'] then | |
| append_array(cast.pre, {C.Include'<assert.h>'}) | |
| _is_created['assert.h'] = true | |
| end | |
| cast:append(C.Call('assert', C.Op('==', C.lua_gettop(), realidx(C.Id(id))))) | |
| cast.idx = C.Id(id) | |
| return cast | |
| end | |
| local function activate_closure_table(idx) | |
| _currentscope['.closuretable'] = idx | |
| _currentscope['.closurelevel'] = _currentscope['.closurelevel'] + 1 | |
| end | |
| -- Convert Lua `Function AST to C AST. | |
| -- C AST defines the function's implementation. | |
| -- helper function to generate function definition | |
| -- is_main - whether this is the top-level "main" function. | |
| local function genfunctiondef(ast, ismain) | |
| local params_ast, body_ast = ast[1], ast[2] | |
| -- localize new function info. | |
| local funcinfo_old = _funcinfo | |
| local has_vararg = params_ast[#params_ast] | |
| and params_ast[#params_ast].tag == 'Dots' | |
| local nformalargs = #params_ast - (has_vararg and 1 or 0) | |
| _funcinfo = {is_vararg = has_vararg, nformalparams = nformalargs, | |
| is_lc_nextra_used = false, is_lc_nactualargs_used=false, | |
| is_lc_nextra_used_debug = false, is_lc_nactualargs_used_debug=false, | |
| idxtopmax = 0 | |
| --old: outervars = _currentscope | |
| } | |
| -- note: special usage of _idxtop | |
| local idxtop_old = _idxtop | |
| _idxtop = 0 | |
| -- localize code | |
| local currentscope_save = newscope() | |
| _currentscope['.closuretable'] = C.C'lua_upvalueindex(1)' | |
| -- define parameters as local variables. | |
| for i,var_ast in ipairs(params_ast) do | |
| if var_ast.tag ~= 'Dots' then | |
| assert(var_ast.tag == 'Id') | |
| local varname = var_ast[1] | |
| _currentscope[varname] = i | |
| idxtop_change(1) | |
| end | |
| end | |
| local body_cast = cexpr() | |
| local is_upvalue = false | |
| for i,params_ast in ipairs(params_ast) do | |
| is_upvalue = is_upvalue or params_ast.upvalue | |
| end | |
| if is_upvalue then | |
| local ct_idx = body_cast:append(gennewclosuretable()).idx | |
| activate_closure_table(ct_idx) | |
| for i=1,nformalargs do | |
| local param_ast = params_ast[i] | |
| if param_ast.upvalue then | |
| local name = param_ast[1] | |
| local varid = nextvarid() | |
| -- create local symbol | |
| _currentscope[name] = | |
| {tag='Upval', _idxtop, _currentscope['.closurelevel'], varid} | |
| body_cast:append(C.lua_pushvalue(i)) | |
| body_cast:append(C.lua_rawseti(-2, varid)) | |
| end | |
| end | |
| end | |
| -- generate body | |
| body_cast:append(genblock(body_ast)) | |
| local bodypre_cast = cexpr() | |
| -- Ensure stack space. | |
| local fudge = 10 -- allow some extra space, avoiding detailed | |
| -- accounting. FIX: is this always sufficient? | |
| if _funcinfo.idxtopmax + fudge >= LUA_MINSTACK then | |
| bodypre_cast:append(C.lua_checkstack(_funcinfo.idxtopmax + fudge)) | |
| end | |
| -- note: do after generating body do that _funcinfo params are set | |
| -- note: safety in case caller passes more or less arguments | |
| -- than parameters. IMPROVE-some of this is sometimes not necessary. | |
| bodypre_cast:append(C.Enum('lc_nformalargs', nformalargs)) | |
| if #params_ast > 0 and params_ast[#params_ast].tag == 'Dots' or | |
| ismain -- dots implicit in main chunk | |
| then | |
| if nformalargs > 0 then | |
| bodypre_cast:append( | |
| C.If(C.Op('<', C.lua_gettop(), C.Id'lc_nformalargs'), | |
| { C.lua_settop(C.Id'lc_nformalargs') } )) | |
| end | |
| -- note: measure nactualargs after adjustment above | |
| -- (important for genexpr of `Id) | |
| if _funcinfo.is_lc_nextra_used then | |
| _funcinfo.is_lc_nactualargs_used = true | |
| elseif _funcinfo.is_lc_nextra_used_debug then | |
| _funcinfo.is_lc_nactualargs_used_debug = true | |
| end | |
| if _funcinfo.is_lc_nactualargs_used then | |
| bodypre_cast:append(C.LetInt('lc_nactualargs', C.lua_gettop())) | |
| elseif _funcinfo.is_lc_nactualargs_used_debug then | |
| bodypre_cast:append(C.C'#ifndef NDEBUG') | |
| bodypre_cast:append(C.LetInt('lc_nactualargs', C.lua_gettop())) | |
| bodypre_cast:append(C.C'#endif') | |
| end | |
| if _funcinfo.is_lc_nextra_used then | |
| bodypre_cast:append(C.LetInt('lc_nextra', | |
| C.Op('-', C.Id'lc_nactualargs', C.Id'lc_nformalargs'))) | |
| elseif _funcinfo.is_lc_nextra_used_debug then | |
| bodypre_cast:append(C.C'#ifndef NDEBUG') | |
| bodypre_cast:append(C.LetInt('lc_nextra', | |
| C.Op('-', C.Id'lc_nactualargs', C.Id'lc_nformalargs'))) | |
| bodypre_cast:append(C.C'#endif') | |
| end | |
| else | |
| bodypre_cast:append(C.lua_settop(#params_ast)) | |
| end | |
| -- prepend bodypre_cast to body_cast (IMPROVE: add prepend method?) | |
| local body_old_cast = body_cast | |
| local body_cast = cexpr() | |
| body_cast:append(bodypre_cast) | |
| body_cast:append(body_old_cast) | |
| if #body_ast == 0 or body_ast[#body_ast].tag ~= 'Return' then | |
| body_cast:append(C.Return(0)) | |
| end | |
| local id = ast.is_main and 'lcf_main' | |
| or nextid(ast.name, 'f') | |
| local def_cast = cexpr() | |
| append_array(def_cast, body_cast.pre) | |
| local func_cast = C.Functiondef(id, body_cast) | |
| local comment = | |
| ast.is_main and '(...)' or | |
| trim(src:sub(ast.lineinfo.first[3], params_ast.lineinfo.last[3])) .. ')' | |
| if comment:sub(1,1) == '(' then | |
| comment = 'function' .. comment | |
| end | |
| if ast.name then | |
| comment = 'name: ' .. ast.name .. '\n' .. comment | |
| end | |
| func_cast.comment = comment | |
| func_cast.id = id | |
| append_array(def_cast, { func_cast }) | |
| restore_scope(currentscope_save) | |
| _idxtop = idxtop_old | |
| _funcinfo = funcinfo_old | |
| return def_cast | |
| end | |
| -- Converts Lua `Function AST to C AST. | |
| -- The C AST instantiates the function/closure (and includes code to define | |
| -- the function implementation also). | |
| local function genfunction(ast, where) | |
| local cast = cexpr() | |
| --## generate function definition | |
| local def_cast = genfunctiondef(ast) | |
| append_array(cast.pre, def_cast) | |
| --## generate function object | |
| if ast.uses_upvalue then | |
| cast:append(C.lua_pushvalue(get_closuretableidx_cast())) | |
| cast:append(C.lua_pushcclosure(C.Id(def_cast[#def_cast].id), 1)) | |
| else | |
| cast:append(C.lua_pushcfunction(C.Id(def_cast[#def_cast].id))) | |
| end | |
| return cast | |
| end | |
| -- Convert Lua top-level block AST (for top-level main function) | |
| -- to C AST. | |
| local function genmainchunk(ast) | |
| local astnew = {tag='Function', is_main=true, name='(main)', {{tag='Dots'}}, | |
| ast, lineinfo=ast.lineinfo} -- note: lineinfo not cloned. ok? | |
| return genfunctiondef(astnew, true) | |
| end | |
| -- Converts Lua `Table AST to C AST. | |
| local function gentable(ast, where) | |
| local cast = cexpr() | |
| local narr = 0 | |
| local nrec = 0 | |
| for _,e_ast in ipairs(ast) do | |
| if e_ast.tag == 'Pair' then -- Q: always true? | |
| nrec = nrec + 1 | |
| else | |
| narr = narr + 1 | |
| end | |
| end | |
| if narr + nrec == 0 then | |
| cast:append(C.lua_newtable()) | |
| else | |
| cast:append(C.lua_createtable(narr,nrec)) | |
| end | |
| local pos = 0 | |
| for i,e_ast in ipairs(ast) do | |
| if e_ast.tag == 'Pair' then | |
| local k_ast, v_ast = e_ast[1], e_ast[2] | |
| cast:append(genexpr(k_ast, 'onstack')) | |
| cast:append(genexpr(v_ast, 'onstack')) | |
| cast:append(C.lua_rawset(-3)) | |
| else | |
| local can_multret = i == #ast and can_multi(e_ast) | |
| if can_multret then | |
| -- table marker | |
| local id = nextid() | |
| cast:append(C.Let(id, C.lua_gettop())) | |
| -- get list of values | |
| cast:append(genexpr(e_ast, 'onstack', 'multret')) | |
| -- push list of values in table. | |
| -- note: Lua spec does not prohibit right-to-left insertion. | |
| -- IMPROVE? right-to-left insertion can cause needless placement | |
| -- of values into the hash part of the table. | |
| cast:append( | |
| C.While(C.Op('>', C.lua_gettop(), C.Id(id)), { | |
| C.lua_rawseti(C.Id(id), | |
| C.Op('+', pos, C.Op('-', C.lua_gettop(), C.Id(id)))) } )) | |
| else | |
| cast:append(genexpr(e_ast, 'onstack')) | |
| pos = pos + 1 | |
| cast:append(C.lua_rawseti(-2, pos)) | |
| end | |
| end | |
| end | |
| return cast | |
| end | |
| -- Converts Lua `Call or `Invoke AST to C AST. | |
| local function gencall(ast, where, nret) | |
| local isinvoke = ast.tag == 'Invoke' -- method call | |
| local cast = cexpr() | |
| -- whether last argument might possibly return multiple values | |
| local can_multret = can_multi(ast[#ast]) | |
| -- push function | |
| if isinvoke then | |
| local obj_ast = ast[1] | |
| cast:append(genexpr(obj_ast, 'onstack')) -- actually self | |
| else | |
| assert(ast.tag == 'Call') | |
| local f_ast = ast[1] | |
| cast:append(genexpr(f_ast, 'onstack')) | |
| end | |
| -- argument marker | |
| local id | |
| if can_multret then | |
| id = nextid(); cast:append(C.Let(id, C.lua_gettop())) | |
| end | |
| -- push arguments | |
| for i=2,#ast do | |
| if i == 2 and isinvoke then | |
| local methodname_ast = ast[i] | |
| cast:append(genexpr(methodname_ast, 'onstack')) | |
| cast:append(C.lua_gettable(-2)) | |
| cast:append(C.lua_insert(-2)) -- swap self and function | |
| else | |
| cast:append(genexpr(ast[i], 'onstack', i == #ast and 'multret' or false)) | |
| end | |
| end | |
| local narg = can_multret and C.Op('-', C.lua_gettop(), C.Id(id)) or #ast-1 | |
| -- call | |
| cast:append(C.lua_call(narg, | |
| nret == 'multret' and C.C'LUA_MULTRET' or nret or 1)) | |
| return cast | |
| end | |
| -- helper | |
| local function gen_lc_setupvalue(...) | |
| local cast = cexpr(C.CallL('lc_setupvalue', ...)) | |
| if not _is_created['lc_setupvalue'] then | |
| append_array(cast.pre, {C.C[[ | |
| static void lc_setupvalue(lua_State * L, int tidx, int level, int varid) { | |
| if (level == 0) { | |
| lua_rawseti(L,tidx,varid); | |
| } | |
| else { | |
| lua_pushvalue(L,tidx); | |
| while(--level >= 0) { | |
| lua_rawgeti(L,tidx,0); /* 0 links to parent table */ | |
| lua_remove(L,-2); | |
| tidx = -1; | |
| } | |
| lua_insert(L,-2); | |
| lua_rawseti(L,-2,varid); | |
| lua_pop(L,1); | |
| } | |
| } | |
| ]]}) | |
| _is_created['lc_setupvalue'] = true | |
| end | |
| return cast | |
| end | |
| -- helper | |
| local function gen_lc_getupvalue(tidx, level, varid) | |
| assert(tidx and level and varid) | |
| local cast = cexpr(C.CallL('lc_getupvalue', tidx, level, varid)) | |
| if not _is_created['lc_getupvalue'] then | |
| append_array(cast.pre, {C.C[[ | |
| /* gets upvalue with ID varid by consulting upvalue table at index | |
| * tidx for the upvalue table at given nesting level. */ | |
| static void lc_getupvalue(lua_State * L, int tidx, int level, int varid) { | |
| if (level == 0) { | |
| lua_rawgeti(L,tidx,varid); | |
| } | |
| else { | |
| lua_pushvalue(L,tidx); | |
| while (--level >= 0) { | |
| lua_rawgeti(L,tidx,0); /* 0 links to parent table */ | |
| lua_remove(L,-2); | |
| tidx = -1; | |
| } | |
| lua_rawgeti(L,-1,varid); | |
| lua_remove(L,-2); | |
| } | |
| } | |
| ]]}) | |
| _is_created['lc_getupvalue'] = true | |
| end | |
| return cast | |
| end | |
| -- Converts Lua `Number AST to C C AST | |
| local function gennumber(x, pre_cast) | |
| if x == math.huge or x == -math.huge then | |
| if not _is_created['math.h'] then | |
| append_array(pre_cast, {C.Include'<math.h>'}) | |
| _is_created['math.h'] = true | |
| end | |
| end | |
| return x | |
| end | |
| -- Converts any Lua expression AST to C AST. | |
| -- (local) | |
| function genexpr(ast, where, nret) | |
| if ast.tag == 'Op' then | |
| ast = constant_fold(ast) | |
| end | |
| if ast.tag == 'Nil' then | |
| return cexpr(C.lua_pushnil()) | |
| elseif ast.tag == 'Dots' then | |
| if nret == 'multret' then | |
| _funcinfo.is_lc_nactualargs_used = true | |
| return cexpr(C.C[[{int i; for (i=lc_nformalargs+1; i<=lc_nactualargs; i++) { lua_pushvalue(L, i); }}]]) | |
| elseif nret and nret > 1 then | |
| _funcinfo.is_lc_nextra_used = true | |
| return cexpr(C.C([[ | |
| /* ... */ | |
| { int i; | |
| const int idx = lua_gettop(L); | |
| const int npush = (]] .. nret .. [[ <= lc_nextra) ? ]] .. nret .. [[ : lc_nextra; | |
| for (i=lc_nformalargs+1; i<=lc_nformalargs+npush; i++) { lua_pushvalue(L, i); } | |
| lua_settop(L, idx + ]] .. nret .. [[); }]])) | |
| else | |
| return cexpr(C.lua_pushvalue(C.Op('+', C.Id'lc_nformalargs', 1))); | |
| end | |
| elseif ast.tag == 'True' then | |
| return cexpr(C.lua_pushboolean(1)) | |
| elseif ast.tag == 'False' then | |
| return cexpr(C.lua_pushboolean(0)) | |
| elseif ast.tag == 'Number' then | |
| local cast = cexpr() | |
| cast:append(C.lua_pushnumber(gennumber(ast[1], cast.pre))) | |
| return cast | |
| elseif ast.tag == 'String' then | |
| local s = ast[1] | |
| return cexpr(C.lua_pushliteral(s)) | |
| elseif ast.tag == 'Function' then | |
| return genfunction(ast, where) | |
| elseif ast.tag == 'Table' then | |
| return gentable(ast, where) | |
| elseif ast.tag == 'Op' then | |
| local opid = ast[1] | |
| if is_binopid[opid] then | |
| if is_arithbinop[opid] then | |
| return genarithbinop(ast, where) | |
| elseif opid == 'eq' then | |
| return geneqop(ast, where) | |
| elseif opid == 'lt' then | |
| return genltop(ast, where) | |
| elseif opid == 'le' then | |
| return genleop(ast, where) | |
| elseif opid == 'or' or opid == 'and' then | |
| return genlogbinop(ast, where) | |
| elseif opid == 'concat' then | |
| return genconcatop(ast, where) | |
| else | |
| assert(false, opid) | |
| end | |
| elseif is_unopid[opid] then | |
| if opid == 'not' then | |
| return gennotop(ast, where) | |
| elseif opid == 'len' then | |
| return genlenop(ast, where) | |
| elseif opid == 'unm' then | |
| return genunmop(ast, where) | |
| else | |
| assert(false, opid) | |
| end | |
| else | |
| assert(false, opid) | |
| end | |
| elseif ast.tag == 'Paren' then | |
| local ast2 = ast[1] | |
| return genexpr(ast2, where) | |
| -- note: metalua allows `Stat here too as an extension | |
| elseif ast.tag == 'Call' or ast.tag == 'Invoke' then | |
| return gencall(ast, where, nret) | |
| elseif ast.tag == 'Id' then | |
| local name = ast[1] | |
| local cast = cexpr() | |
| if _currentscope[name] then -- local | |
| local idx = localidx(name) | |
| if tag(idx) == 'Upval' then | |
| local closuretable_idx, closurelevel, varid = idx[1], idx[2], idx[3] | |
| cast:append(gen_lc_getupvalue( | |
| get_closuretableidx_cast(), | |
| _currentscope['.closurelevel'] - closurelevel, varid)) | |
| elseif where == 'anywhere' then | |
| cast.idx = idx | |
| return cast | |
| else | |
| cast:append(C.lua_pushvalue(idx)) | |
| end | |
| else -- global | |
| --old: cast:append(C.lua_getglobal(name)) | |
| cast:append(C.lua_getfield(C.C'LUA_ENVIRONINDEX', name)) | |
| end | |
| return cast | |
| elseif ast.tag == 'Index' then | |
| local cast = cexpr() | |
| local t_ast, k_ast = ast[1], ast[2] | |
| local t_idx = cast:append(genexpr(t_ast, 'anywhere')).idx | |
| cast:append(genexpr(k_ast, 'onstack')) | |
| cast:append(C.lua_gettable(adjustidx(-2, t_idx))) | |
| if t_idx == -1 then | |
| cast:append(C.lua_remove(-2)) | |
| end | |
| return cast | |
| else | |
| assert(false, ast.tag) | |
| end | |
| end | |
| -- Converts Lua l-value expression AST to C AST. | |
| local function genlvalueassign(l_ast) | |
| local cast = cexpr() | |
| if l_ast.tag == 'Id' then | |
| local name = l_ast[1] | |
| if _currentscope[name] then | |
| local idx = localidx(name) | |
| if tag(idx) == 'Upval' then | |
| local closuretable_idx, closurelevel, varid = idx[1], idx[2], idx[3] | |
| cast:append(gen_lc_setupvalue( | |
| get_closuretableidx_cast(), | |
| _currentscope['.closurelevel'] - closurelevel, varid)) | |
| else | |
| cast:append(C.lua_replace(idx)) | |
| end | |
| else -- global | |
| --old:cast:append(C.lua_setglobal(name)) | |
| cast:append(C.lua_setfield(C.C'LUA_ENVIRONINDEX', name)) | |
| end | |
| elseif l_ast.tag == 'Index' then | |
| local t_ast, k_ast = l_ast[1], l_ast[2] | |
| local t_idx = cast:append(genexpr(t_ast, 'anywhere')).idx | |
| if t_idx == -1 then cast:append(C.lua_insert(-2)) end | |
| cast:append(genexpr(k_ast, 'onstack')) | |
| cast:append(C.lua_insert(-2)) | |
| cast:append(C.lua_settable(adjustidx(-3, t_idx))) | |
| if t_idx == -1 then | |
| cast:append(C.lua_pop(1)) | |
| end | |
| else | |
| assert(false, l_ast.tag) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua `If AST to C AST. | |
| local function genif(ast, i) | |
| i = i or 1 -- i > 1 is index in AST node of elseif branch to generate | |
| local cast = cexpr() | |
| local if_args = {pre=cast.pre} | |
| local currentscope_save = _currentscope | |
| local base_idx = _idxtop | |
| local base_id = nextid() | |
| cast:append(C.Enum(base_id, base_idx)) | |
| do | |
| local expr_ast, body_ast = ast[i], ast[i+1] | |
| idxtop_restore(base_idx) | |
| -- expression | |
| local a_idx = cast:append(genexpr(expr_ast, 'anywhere')).idx | |
| if a_idx ~= -1 then | |
| if_args[#if_args+1] = tobool(a_idx) | |
| else | |
| local id = nextid(); | |
| cast:append(C.Let(id, tobool(-1))) | |
| cast:append(C.lua_pop(1)) | |
| if_args[#if_args+1] = C.Id(id) | |
| end | |
| -- block | |
| newscope() | |
| local block_cast = genblock(body_ast) | |
| table.insert(if_args, block_cast) | |
| append_array(cast.pre, block_cast.pre) | |
| restore_scope(currentscope_save) | |
| end | |
| if ast[i+2] then | |
| idxtop_restore(base_idx) | |
| local currentscope_save = newscope() | |
| local block_cast | |
| if not ast[i+3] then | |
| block_cast = genblock(ast[i+2]) | |
| if block_cast[1] then | |
| prepend_comment(block_cast[1], 'else') | |
| end | |
| else | |
| block_cast = genif(ast, i+2) | |
| end | |
| table.insert(if_args, block_cast) | |
| append_array(cast.pre, block_cast.pre) | |
| restore_scope(currentscope_save) | |
| end | |
| -- note: idx is (in general) indeterminant now | |
| -- due to the multiple branches. | |
| cast:append(C.If(unpack(if_args))) | |
| cast:append(C.lua_settop(realidx(C.Id(base_id)))) | |
| idxtop_restore(base_idx) | |
| local start = | |
| i == 1 and ast.lineinfo.first[3] or ast[i-1].lineinfo.last[3]+1 | |
| prepend_comment(cast, trim(src:sub(start, ast[i].lineinfo.last[3])) .. | |
| ' then') | |
| if i == 1 then | |
| append_comment_below(cast, 'end') | |
| end | |
| local comment = false | |
| return cast, comment | |
| end | |
| -- Converts Lua `Fornum AST to C AST. | |
| local function genfornum(ast) | |
| local name_ast, e1_ast, e2_ast, e3_ast, block_ast = | |
| ast[1], ast[2], ast[3], ast[5] and ast[4] or ast[5], ast[5] or ast[4] | |
| local name_id = name_ast[1]; assert(name_ast.tag == 'Id') | |
| local cast = cexpr() | |
| local var_id = nextid() .. '_var'; | |
| local limit_id = nextid() .. '_limit'; | |
| local step_id = nextid() .. '_step'; | |
| local var_idx = cast:append(genexpr(e1_ast, 'anywhere')).idx | |
| local limit_idx = cast:append(genexpr(e2_ast, 'anywhere')).idx | |
| local step_idx = e3_ast and cast:append(genexpr(e3_ast, 'anywhere')).idx | |
| local nstack = (var_idx == -1 and 1 or 0) + | |
| (limit_idx == -1 and 1 or 0) + | |
| (step_idx == -1 and 1 or 0) | |
| var_idx, limit_idx, step_idx = adjustidxs(var_idx, limit_idx, step_idx) | |
| local expr_cast = | |
| C.Op('&&', C.lua_isnumber(var_idx), C.lua_isnumber(limit_idx)) | |
| if step_idx then | |
| expr_cast = C.Op('&&', expr_cast, C.lua_isnumber(step_idx)) | |
| end | |
| cast:append( | |
| C.If(C.Not(expr_cast), | |
| {C.CallL('luaL_error', "'for' limit must be a number")})) | |
| cast:append(C.LetMutableDouble(var_id, todouble(var_idx))) | |
| cast:append(C.LetDouble(limit_id, todouble(limit_idx))) | |
| if e3_ast then | |
| cast:append(C.LetDouble(step_id, todouble(step_idx))) | |
| else | |
| cast:append(C.LetDouble(step_id, 1.0)) | |
| end | |
| cast:append(C.lua_pop(nstack)) | |
| local while_expr_cast = | |
| C.Op('||', | |
| C.Op('&&', | |
| C.Op('>', C.Id(step_id), 0), | |
| C.Op('<=', C.Id(var_id), C.Id(limit_id))), | |
| C.Op('&&', | |
| C.Op('<=', C.Id(step_id), 0), | |
| C.Op('>=', C.Id(var_id), C.Id(limit_id)))) | |
| -- local scope to evaluate block | |
| local currentscope_save = newscope() | |
| local block_cast = cexpr() | |
| local base_idx = _idxtop | |
| local base_id = nextid() | |
| cast:append(C.Enum(base_id, base_idx)) | |
| -- create local | |
| if name_ast.upvalue then | |
| local ct_idx = block_cast:append(gennewclosuretable()).idx | |
| activate_closure_table(ct_idx) | |
| local varid = nextvarid() | |
| block_cast:append(C.lua_pushnumber(C.Id(var_id))) | |
| block_cast:append(C.lua_rawseti(-2, varid)) | |
| _currentscope[name_id] = | |
| {tag='Upval', ct_idx, _currentscope['.closurelevel'], varid} | |
| else | |
| block_cast:append(C.lua_pushnumber(C.Id(var_id))) | |
| idxtop_change(1) | |
| _currentscope[name_id] = _idxtop | |
| end | |
| block_cast[1].comment = | |
| string.format("internal: local %s at index %d", name_id, _idxtop) | |
| block_cast:append(genblock(block_ast)) | |
| restore_stack_rel(block_cast, base_idx) | |
| restore_scope(currentscope_save) | |
| block_cast:append(C.C(var_id .. ' += ' .. step_id .. ';')) --IMPROVE? | |
| cast:append(C.While(while_expr_cast, block_cast)) | |
| append_array(cast.pre, block_cast.pre) | |
| -- note: mixed breaks and locals can leave the stack at an | |
| -- unknown size, so absolute adjust here. | |
| restore_stack_abs(cast, C.Id(base_id), base_idx) | |
| prepend_comment(cast, | |
| trim(src:sub(ast.lineinfo.first[3], block_ast.lineinfo.first[3]-1))) | |
| append_comment_below(cast, 'end') | |
| local comment = false | |
| return cast, comment | |
| end | |
| -- Converts Lua `Forin AST to C AST. | |
| local function genforin(ast) | |
| local names_ast, exprs_ast, block_ast = ast[1], ast[2], ast[3] | |
| local cast = cexpr() | |
| local multi = can_multi(exprs_ast[1]) | |
| local base_idx = _idxtop | |
| local base_id = nextid() | |
| cast:append(C.Enum(base_id, base_idx)) | |
| -- loop invisible variables: var, limit, step | |
| local nlast = multi and 3 + 1 - #exprs_ast or 1 | |
| for i,expr_ast in ipairs(exprs_ast) do | |
| cast:append(genexpr(expr_ast, 'onstack', | |
| i==#exprs_ast and math.max(0,nlast))) | |
| end | |
| if nlast < 0 then | |
| cast:append(C.lua_pop(-nlast)) | |
| end | |
| idxtop_change(3) | |
| append_comment(cast[1], 'internal: local f, s, var = explist') | |
| local base2_idx = _idxtop | |
| local block_cast = cexpr(); do | |
| -- local scope to evaluate block | |
| local currentscope_save = newscope() | |
| local extra = 0 | |
| local ct_idx | |
| local is_upvalue = false | |
| for i,name_ast in ipairs(names_ast) do | |
| if name_ast.upvalue then is_upvalue = true end | |
| end | |
| if is_upvalue then | |
| ct_idx = block_cast:append(gennewclosuretable()).idx | |
| activate_closure_table(ct_idx) | |
| extra = 1 | |
| end | |
| -- loop variables and control | |
| block_cast:append(C.lua_pushvalue(-3 - extra)) | |
| append_comment(block_cast[#block_cast], | |
| 'internal: local var_1, ..., var_n = f(s, var)\n' .. | |
| ' if var_1 == nil then break end\n' .. | |
| ' var = var_1') | |
| block_cast:append(C.lua_pushvalue(-3 - extra)) | |
| block_cast:append(C.lua_pushvalue(-3 - extra)) | |
| block_cast:append(C.lua_call(2,#names_ast)) | |
| idxtop_change(#names_ast) | |
| block_cast:append(C.If(C.lua_isnil(- #names_ast), {C.Break()})) | |
| block_cast:append(C.lua_pushvalue(- #names_ast)) | |
| block_cast:append(C.lua_replace(- #names_ast - 2 - extra)) | |
| -- loop variables | |
| local pos1 = #block_cast | |
| local idx1 = _idxtop - #names_ast | |
| do -- locals used as upvalues | |
| local varids = {} | |
| local nlocals = 0 -- number of non-up-value-enabled locals found | |
| for i=#names_ast,1,-1 do | |
| local name_ast = names_ast[i] | |
| if name_ast.upvalue then | |
| local name_id = name_ast[1]; assert(name_ast.tag == 'Id') | |
| varids[i] = nextvarid() | |
| if nlocals == 0 then | |
| block_cast:append(C.lua_rawseti(-1 - i, varids[i])) | |
| else | |
| block_cast:append(C.lua_pushvalue(-1 - nlocals)) | |
| block_cast:append(C.lua_rawseti(realidx(ct_idx), varids[i])) | |
| block_cast:append(C.lua_remove(-1 - nlocals)) | |
| end | |
| idxtop_change(- 1) | |
| _currentscope[name_id] = | |
| {tag='Upval', ct_idx, _currentscope['.closurelevel'], varids[i]} | |
| else | |
| nlocals = nlocals + 1 | |
| end | |
| end | |
| for i,name_ast in ipairs(names_ast) do if varids[i] then | |
| append_comment(block_cast[pos1+1], | |
| string.format("internal: upvalue-enabled local %s with id %d", | |
| name_ast[1], varids[i])) | |
| end end | |
| end | |
| if pos1 == #block_cast then | |
| -- hack: ensure AST node exists in which to place comment. | |
| block_cast:append(C.C'') | |
| end | |
| do -- locals | |
| local count = 0 | |
| for i,name_ast in ipairs(names_ast) do | |
| if not name_ast.upvalue then | |
| local name_id = name_ast[1]; assert(name_ast.tag == 'Id') | |
| count = count + 1 | |
| _currentscope[name_id] = idx1 + count | |
| append_comment(block_cast[pos1+1], | |
| string.format("internal: local %s with idx %d", | |
| name_id, _currentscope[name_id])) | |
| end | |
| end | |
| end | |
| -- block | |
| block_cast:append(genblock(block_ast)) | |
| -- end scope | |
| restore_stack_rel(block_cast, base2_idx) | |
| restore_scope(currentscope_save) | |
| end | |
| cast:append(C.While(1, block_cast)) | |
| append_array(cast.pre, block_cast.pre) | |
| restore_stack_abs(cast, C.Id(base_id), base_idx) | |
| prepend_comment(cast, | |
| trim(src:sub(ast.lineinfo.first[3], block_ast.lineinfo.first[3]-1))) | |
| append_comment_below(cast, 'end') | |
| local comment = false | |
| return cast, comment | |
| end | |
| -- Converts Lua `While AST to C AST. | |
| local function genwhile(ast) | |
| local expr_ast, block_ast = ast[1], ast[2] | |
| local cast = cexpr() | |
| local base_idx = _idxtop | |
| local base_id = nextid() | |
| cast:append(C.Enum(base_id, base_idx)) | |
| do | |
| -- expression | |
| local block_cast = cexpr(); do | |
| local expr_idx = block_cast:append(genexpr(expr_ast, 'anywhere')).idx | |
| block_cast:append( | |
| C.If(C.Not(tobool(expr_idx)), {C.Break()})) | |
| if expr_idx == -1 then | |
| block_cast:append(C.lua_pop(1)) | |
| end | |
| -- local scope to evaluate block | |
| local currentscope_save = newscope() | |
| -- block | |
| block_cast:append(genblock(block_ast)) | |
| restore_stack_rel(block_cast, base_idx) | |
| restore_scope(currentscope_save) | |
| end | |
| cast:append(C.While(1, block_cast)) | |
| append_array(cast.pre, block_cast.pre) | |
| end | |
| -- note: mixed breaks and locals can leave the stack at an | |
| -- unknown size, so absolute adjust here. | |
| restore_stack_abs(cast, C.Id(base_id), base_idx) | |
| prepend_comment(cast, | |
| trim(src:sub(ast.lineinfo.first[3], block_ast.lineinfo.first[3]-1))) | |
| append_comment_below(cast, 'end') | |
| local comment = false | |
| return cast, comment | |
| end | |
| -- Converts Lua `Repeat AST to C AST. | |
| local function genrepeat(ast) | |
| local block_ast, expr_ast = ast[1], ast[2] | |
| local cast = cexpr() | |
| local base_idx = _idxtop | |
| local base_id = nextid() | |
| cast:append(C.Enum(base_id, base_idx)) | |
| do | |
| -- body | |
| local block_cast = cexpr(); do | |
| -- local scope to evaluate block and expression | |
| local currentscope_save = newscope() | |
| -- block | |
| block_cast:append(genblock(block_ast)) | |
| -- expression | |
| local expr_idx = block_cast:append(genexpr(expr_ast, 'anywhere')).idx | |
| idxtop_change(1) | |
| block_cast:append( | |
| C.If(tobool(expr_idx), {C.Break()})) | |
| restore_stack_rel(block_cast, base_idx) | |
| restore_scope(currentscope_save) | |
| end | |
| cast:append(C.While(1, block_cast)) | |
| append_array(cast.pre, block_cast.pre) | |
| end | |
| -- note: mixed breaks and locals can leave the stack at an | |
| -- unknown size, so absolute adjust here. | |
| restore_stack_abs(cast, C.Id(base_id), base_idx) | |
| prepend_comment(cast, 'repeat') | |
| append_comment_below(cast, | |
| trim(src:sub(block_ast.lineinfo.last[3]+1, ast.lineinfo.last[3]))) | |
| local comment = false | |
| return cast, comment | |
| end | |
| -- Converts Lua `Do AST to C AST. | |
| local function gendo(ast) | |
| local cast = cexpr() | |
| local base_idx = _idxtop | |
| -- local scope to evaluate block | |
| local currentscope_save = newscope() | |
| cast:append(genblock(ast)) | |
| restore_scope(currentscope_save) | |
| restore_stack_rel(cast, base_idx) | |
| prepend_comment(cast, 'do') | |
| append_comment_below(cast, 'end') | |
| local comment = false | |
| return cast, comment | |
| end | |
| -- Converts Lua `Local AST to C AST. | |
| local function genlocalstat(ast) | |
| local names_ast, vals_ast = ast[1], ast[2] | |
| local cast = cexpr() | |
| local ct_idx | |
| local is_upvalue = false | |
| for i,name_ast in ipairs(names_ast) do | |
| is_upvalue = is_upvalue or name_ast.upvalue | |
| end | |
| if is_upvalue then | |
| ct_idx = cast:append(gennewclosuretable()).idx | |
| end | |
| deduce_function_names(names_ast, vals_ast) | |
| -- create values | |
| for i,val_ast in ipairs(vals_ast) do | |
| cast:append(genexpr(val_ast, 'onstack', | |
| #names_ast > #vals_ast and i == #vals_ast and can_multi(val_ast) and | |
| (#names_ast - #vals_ast + 1))) | |
| end | |
| -- cleanup if LHS and RHS lengths don't match | |
| if #names_ast > #vals_ast and not can_multi(vals_ast[#vals_ast]) then | |
| cast:append( | |
| C.lua_settop( | |
| C.Op('+', C.lua_gettop(), #names_ast - #vals_ast))) | |
| elseif #names_ast < #vals_ast then | |
| cast:append(C.lua_pop(#vals_ast - #names_ast)) | |
| end | |
| if ct_idx then | |
| -- activate closure scope (after having generated values) | |
| activate_closure_table(ct_idx) | |
| end | |
| -- store values in closure table and create local symbols | |
| for i=#names_ast,1,-1 do local name_ast = names_ast[i] | |
| local name = name_ast[1] | |
| if name_ast.upvalue then | |
| local varid = nextvarid() | |
| cast:append(C.lua_rawseti(realidx(ct_idx), varid)) | |
| -- create local symbol | |
| _currentscope[name] = | |
| {tag='Upval', ct_idx, _currentscope['.closurelevel'], varid} | |
| end | |
| end | |
| -- create local symbols | |
| for i,name_ast in ipairs(names_ast) do | |
| local name = name_ast[1]; assert(name_ast.tag == 'Id') | |
| if not name_ast.upvalue then | |
| idxtop_change(1) | |
| _currentscope[name] = _idxtop | |
| end | |
| end | |
| return cast | |
| end | |
| -- Converts Lua `Set AST to C AST. | |
| local function genset(stat_ast) | |
| -- note: supports x,y=y,x | |
| local ls_ast, rs_ast = stat_ast[1], stat_ast[2] | |
| local cast = cexpr() | |
| deduce_function_names(ls_ast, rs_ast) | |
| -- create values (r_ast) | |
| for i,r_ast in ipairs(rs_ast) do | |
| cast:append(genexpr(r_ast, 'onstack', | |
| #ls_ast > #rs_ast and i == #rs_ast and can_multi(r_ast) and | |
| (#ls_ast - #rs_ast + 1))) | |
| end | |
| -- cleanup if LHS and RHS lengths don't match | |
| if #ls_ast > #rs_ast and not can_multi(rs_ast[#rs_ast]) then | |
| cast:append( | |
| C.lua_settop( | |
| C.Op('+', C.lua_gettop(), #ls_ast - #rs_ast))) | |
| elseif #ls_ast < #rs_ast then | |
| cast:append(C.lua_pop(#rs_ast - #ls_ast)) | |
| end | |
| for i=#ls_ast,1,-1 do | |
| cast:append(genlvalueassign(ls_ast[i])) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua `Localrec AST to C AST. | |
| local function genlocalrec(ast) | |
| local names_ast, vals_ast = ast[1], ast[2] | |
| assert(#names_ast == 1) | |
| assert(#vals_ast == 1) | |
| local name_ast = names_ast[1] | |
| local val_ast = vals_ast[1] | |
| assert(val_ast.tag == 'Function') | |
| local cast = cexpr() | |
| -- activate scope and symbol (prior to generating value) | |
| local ct_idx | |
| local varid | |
| local name = name_ast[1] | |
| if name_ast.upvalue then | |
| ct_idx = cast:append(gennewclosuretable()).idx | |
| activate_closure_table(ct_idx) | |
| -- create local symbol | |
| varid = nextvarid() | |
| _currentscope[name] = | |
| {tag='Upval', ct_idx, _currentscope['.closurelevel'], varid} | |
| else | |
| _currentscope[name] = _idxtop + 1 | |
| end | |
| deduce_function_names(names_ast, vals_ast) | |
| -- create value | |
| cast:append(genexpr(val_ast, 'onstack')) | |
| -- store value | |
| if name_ast.upvalue then | |
| cast:append(C.lua_rawseti(realidx(ct_idx), varid)) | |
| else | |
| idxtop_change(1) | |
| end | |
| return cast | |
| end | |
| -- Converts any Lua statement AST to C AST. | |
| local function genstatement(stat_ast) | |
| local cast | |
| local comment | |
| if stat_ast.tag == 'Set' then | |
| cast = genset(stat_ast) | |
| elseif stat_ast.tag == 'Return' then | |
| cast = cexpr() | |
| local can_multi = #stat_ast >= 1 and can_multi(stat_ast[#stat_ast]) | |
| local id | |
| if can_multi then | |
| id = nextid(); cast:append(C.Let(id, C.lua_gettop())) | |
| end | |
| for i,e_ast in ipairs(stat_ast) do | |
| cast:append(genexpr(e_ast, 'onstack', | |
| i==#stat_ast and can_multi and 'multret' or 1)) | |
| end | |
| if id then | |
| cast:append(C.Return(C.Op('-', C.lua_gettop(), C.Id(id)))) | |
| else | |
| cast:append(C.Return(#stat_ast)) | |
| end | |
| elseif stat_ast.tag == 'Fornum' then | |
| cast, comment = genfornum(stat_ast) | |
| elseif stat_ast.tag == 'Forin' then | |
| cast, comment = genforin(stat_ast) | |
| elseif stat_ast.tag == 'While' then | |
| cast, comment = genwhile(stat_ast) | |
| elseif stat_ast.tag == 'Repeat' then | |
| cast, comment = genrepeat(stat_ast) | |
| elseif stat_ast.tag == 'Do' then | |
| cast, comment = gendo(stat_ast) | |
| elseif stat_ast.tag == 'If' then | |
| cast, comment = genif(stat_ast) | |
| elseif stat_ast.tag == 'Call' or stat_ast.tag == 'Invoke' then | |
| cast = genexpr(stat_ast, 'onstack', 0) | |
| elseif stat_ast.tag == 'Local' then | |
| cast = genlocalstat(stat_ast) | |
| elseif stat_ast.tag == 'Localrec' then | |
| cast = genlocalrec(stat_ast) | |
| elseif stat_ast.tag == 'Break' then | |
| cast = cexpr(C.Break()) | |
| else | |
| assert(false, stat_ast.tag) | |
| end | |
| if comment ~= false then | |
| comment = comment or get_ast_string(src, stat_ast) | |
| prepend_comment(cast, comment) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua block AST to C AST. | |
| -- (local) | |
| function genblock(ast) | |
| local cast = cexpr() | |
| for _,stat_ast in ipairs(ast) do | |
| local stat_cast = genstatement(stat_ast) | |
| local comments = stat_ast.lineinfo.first.comments | |
| if comments then | |
| for i=#comments,1,-1 do | |
| local comment = src:sub(comments[i][2], comments[i][3]):gsub('\n$','') | |
| prepend_comment(stat_cast, comment) | |
| end | |
| end | |
| cast:append(stat_cast) | |
| -- DEBUG | |
| if true then | |
| if _funcinfo.is_vararg then | |
| _funcinfo.is_lc_nextra_used_debug = true | |
| end | |
| if not _is_created['assert.h'] then | |
| append_array(cast.pre, {C.Include'<assert.h>'}) | |
| _is_created['assert.h'] = true | |
| end | |
| cast:append(C.C(string.format([[assert(lua_gettop(L) %s== %d);]], | |
| _funcinfo.is_lc_nextra_used_debug and "- lc_nextra " or "", _idxtop))) | |
| end | |
| end | |
| if #ast > 0 then | |
| local stat_ast = ast[#ast] | |
| local comments = stat_ast.lineinfo.last.comments | |
| if comments then | |
| for i=1,#comments do | |
| local comment = src:sub(comments[i][2], comments[i][3]):gsub('\n$','') | |
| append_comment_below(cast[#cast], comment) | |
| end | |
| end | |
| else | |
| local comments = ast.lineinfo.first.comments | |
| if comments then | |
| for i=1,#comments do | |
| local comment = src:sub(comments[i][2], comments[i][3]):gsub('\n$','') | |
| append_comment_below(cast, comment) | |
| end | |
| end | |
| end | |
| return cast | |
| end | |
| local function gendefinitions(ast) | |
| local cast = C.Def() | |
| for _,def_ast in ipairs(ast) do | |
| cast:append(genstatement(def_ast)) | |
| end | |
| return cast | |
| end | |
| -- Converts Lua top-level function to C AST, | |
| -- including prelude. | |
| local function genfull(ast) | |
| -- support LUA_INIT environment variable | |
| local enable_lua_init = true | |
| local cast = cexpr(); cast.tag = 'Def' | |
| cast:append(C.C [[ | |
| /* WARNING: This file was automatically generated by lua2c. */ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| #include <lua.h> | |
| #include <lauxlib.h> | |
| #include <lualib.h> | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| ]]) | |
| if enable_lua_init then | |
| cast:append(C.C [[ | |
| #include <string.h> | |
| ]]) | |
| end | |
| cast:append(genmainchunk(ast)) | |
| cast:append(C.C [[ | |
| /* from lua.c */ | |
| static int traceback (lua_State *L) { | |
| if (!lua_isstring(L, 1)) /* 'message' not a string? */ | |
| return 1; /* keep it intact */ | |
| lua_getfield(L, LUA_GLOBALSINDEX, "debug"); | |
| if (!lua_istable(L, -1)) { | |
| lua_pop(L, 1); | |
| return 1; | |
| } | |
| lua_getfield(L, -1, "traceback"); | |
| if (!lua_isfunction(L, -1)) { | |
| lua_pop(L, 2); | |
| return 1; | |
| } | |
| lua_pushvalue(L, 1); /* pass error message */ | |
| lua_pushinteger(L, 2); /* skip this function and traceback */ | |
| lua_call(L, 2, 1); /* call debug.traceback */ | |
| return 1; | |
| } | |
| ]]) | |
| if enable_lua_init then | |
| cast:append(C.C [[ | |
| static void lc_l_message (const char *pname, const char *msg) { | |
| if (pname) fprintf(stderr, "%s: ", pname); | |
| fprintf(stderr, "%s\n", msg); | |
| fflush(stderr); | |
| } | |
| static int lc_report (lua_State *L, int status) { | |
| if (status && !lua_isnil(L, -1)) { | |
| const char *msg = lua_tostring(L, -1); | |
| if (msg == NULL) msg = "(error object is not a string)"; | |
| /*FIX-IMROVE:progname*/ | |
| lc_l_message("lua", msg); | |
| lua_pop(L, 1); | |
| } | |
| return status; | |
| } | |
| static int lc_docall (lua_State *L, int narg, int clear) { | |
| int status; | |
| int base = lua_gettop(L) - narg; /* function index */ | |
| lua_pushcfunction(L, traceback); /* push traceback function */ | |
| lua_insert(L, base); /* put it under chunk and args */ | |
| /*FIX? signal(SIGINT, laction); */ | |
| status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base); | |
| /*FIX? signal(SIGINT, SIG_DFL); */ | |
| lua_remove(L, base); /* remove traceback function */ | |
| /* force a complete garbage collection in case of errors */ | |
| if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); | |
| return status; | |
| } | |
| static int lc_dofile (lua_State *L, const char *name) { | |
| int status = luaL_loadfile(L, name) || lc_docall(L, 0, 1); | |
| return lc_report(L, status); | |
| } | |
| static int lc_dostring (lua_State *L, const char *s, const char *name) { | |
| int status = luaL_loadbuffer(L, s, strlen(s), name) || lc_docall(L, 0, 1); | |
| return lc_report(L, status); | |
| } | |
| static int lc_handle_luainit (lua_State *L) { | |
| const char *init = getenv(LUA_INIT); | |
| if (init == NULL) return 0; /* status OK */ | |
| else if (init[0] == '@') | |
| return lc_dofile(L, init+1); | |
| else | |
| return lc_dostring(L, init, "=" LUA_INIT); | |
| } | |
| ]]) | |
| end | |
| cast:append(C.C [[ | |
| typedef struct { | |
| int c; | |
| const char ** v; | |
| } lc_args_t; | |
| ]]) | |
| cast:append(C.C [[ | |
| /* create global arg table */ | |
| static void lc_createarg(lua_State * L, const lc_args_t * const args) { | |
| int i; | |
| lua_newtable(L); | |
| for (i=0; i < args->c; i++) { | |
| lua_pushstring(L, args->v[i]); | |
| lua_rawseti(L, -2, i); | |
| } | |
| lua_setglobal(L, "arg"); | |
| } | |
| ]]) | |
| cast:append(C.C([[ | |
| static int lc_pmain(lua_State * L) { | |
| luaL_openlibs(L); | |
| const lc_args_t * const args = (lc_args_t*)lua_touserdata(L, 1); | |
| lc_createarg(L, args); | |
| lua_pushcfunction(L, traceback); | |
| ]] .. ( | |
| enable_lua_init and [[ | |
| const int status1 = lc_handle_luainit(L); | |
| if (status1 != 0) return 0; | |
| ]] or '') .. | |
| [[ | |
| /* note: IMPROVE: closure not always needed here */ | |
| lua_newtable(L); /* closure table */ | |
| lua_pushcclosure(L, lcf_main, 1); | |
| int i; | |
| for (i=1; i < args->c; i++) { | |
| lua_pushstring(L, args->v[i]); | |
| } | |
| int status2 = lua_pcall(L, args->c-1, 0, -2); | |
| if (status2 != 0) { | |
| const char * msg = lua_tostring(L,-1); | |
| if (msg == NULL) msg = "(error object is not a string)"; | |
| fputs(msg, stderr); | |
| } | |
| return 0; | |
| } | |
| ]])) | |
| cast:append(C.C [[ | |
| int main(int argc, const char ** argv) { | |
| lc_args_t args = {argc, argv}; | |
| lua_State * L = luaL_newstate(); | |
| if (! L) { fputs("Failed creating Lua state.", stderr); exit(1); } | |
| int status = lua_cpcall(L, lc_pmain, &args); | |
| if (status != 0) { | |
| fputs(lua_tostring(L,-1), stderr); | |
| } | |
| lua_close(L); | |
| return 0; | |
| } | |
| ]]) | |
| return cast | |
| end | |
| M.genfull = genfull | |
| -- First pass through AST <ast>. | |
| -- Each `Id node is marked with field <upvalue=true> if it is used as an | |
| -- upvalue. | |
| -- Each `Function node is marked with field <uses_upvalue=true> if it uses | |
| -- at least one upvalue. | |
| local function first_pass(ast) | |
| -- Represents current lexical scope. | |
| -- Maps variable name to variable info (see newvar). | |
| local scope = {} | |
| local functions = {} | |
| local function newscope() | |
| local saved_scope = scope | |
| scope = setmetatable({}, {__index=scope}) | |
| return saved_scope | |
| end | |
| local function endscope(saved_scope) | |
| scope = assert(saved_scope) | |
| end | |
| local function newvar(id_ast) | |
| assert(id_ast.tag == 'Id', id_ast) | |
| local name = id_ast[1] | |
| scope[name] = {function_level = #functions, ast = id_ast} | |
| end | |
| local function usevar(name) | |
| local varinfo = scope[name] | |
| if varinfo and varinfo.function_level < #functions then | |
| --DEBUG('upval:', varinfo.ast) | |
| varinfo.ast.upvalue = true | |
| for i=varinfo.function_level+1,#functions do | |
| functions[i].uses_upvalue = true | |
| end | |
| end | |
| end | |
| local function process(ast) | |
| if ast.tag == nil or ast.tag == 'Do' then -- block | |
| local saved_scope = newscope() | |
| for _,stat_ast in ipairs(ast) do | |
| process(stat_ast) | |
| end | |
| endscope(saved_scope) | |
| elseif ast.tag == 'Set' then | |
| for i=1,#ast[1] do process(ast[1][i]) end | |
| for i=1,#ast[2] do process(ast[2][i]) end | |
| elseif ast.tag == 'While' then | |
| process(ast[1]) | |
| local saved_scope = newscope() | |
| process(ast[2]) | |
| endscope(saved_scope) | |
| elseif ast.tag == 'Repeat' then | |
| local saved_scope = newscope() | |
| process(ast[1]) | |
| process(ast[2]) | |
| endscope(saved_scope) | |
| elseif ast.tag == 'If' then | |
| for i=1,#ast do | |
| if i % 2 == 0 or i == #ast then | |
| local saved_scope = newscope() | |
| process(ast[i]) | |
| endscope(saved_scope) | |
| else | |
| process(ast[i]) | |
| end | |
| end | |
| elseif ast.tag == 'Fornum' then | |
| local saved_scope = newscope() | |
| newvar(ast[1]) | |
| for i=2,#ast do process(ast[i]) end | |
| endscope(saved_scope) | |
| elseif ast.tag == 'Forin' then | |
| local saved_scope = newscope() | |
| for i=1,#ast[1] do newvar(ast[1][i]) end | |
| for i=1,#ast[2] do process(ast[2][i]) end | |
| process(ast[#ast]) | |
| endscope(saved_scope) | |
| elseif ast.tag == 'Local' then | |
| if ast[2] then | |
| for i=1,#ast[2] do process(ast[2][i]) end | |
| end | |
| for i=1,#ast[1] do newvar(ast[1][i]) end | |
| elseif ast.tag == 'Localrec' then | |
| for i=1,#ast[1] do newvar(ast[1][i]) end | |
| if ast[2] then | |
| for i=1,#ast[2] do process(ast[2][i]) end | |
| end | |
| --metalua: elseif ast.tag == 'Goto' or ast.tag == 'Label' then | |
| elseif ast.tag == 'Return' then | |
| for i=1,#ast do process(ast[i]) end | |
| elseif ast.tag == 'Break' then | |
| elseif ast.tag == 'Nil' or ast.tag == 'Dots' or ast.tag == 'True' | |
| or ast.tag == 'False' or ast.tag == 'Number' or ast.tag == 'String' | |
| then | |
| elseif ast.tag == 'Function' then | |
| local saved_scope = newscope() | |
| table.insert(functions, ast) | |
| for i=1,#ast[1] do | |
| if ast[1][i].tag ~= 'Dots' then newvar(ast[1][i]) end | |
| end | |
| process(ast[2]) | |
| table.remove(functions) | |
| endscope(saved_scope) | |
| elseif ast.tag == 'Table' then | |
| for i=1,#ast do process(ast[i]) end | |
| elseif ast.tag == 'Pair' then | |
| for i=1,2 do process(ast[i]) end | |
| elseif ast.tag == 'Op' then | |
| for i=2,#ast do process(ast[i]) end | |
| elseif ast.tag == 'Paren' then | |
| process(ast[1]) | |
| -- metalua: elseif ast.tag == 'Stat' then | |
| elseif ast.tag == 'Call' then | |
| for i=1,#ast do process(ast[i]) end | |
| elseif ast.tag == 'Invoke' then | |
| process(ast[1]) | |
| for i=3,#ast do process(ast[i]) end | |
| elseif ast.tag == 'Index' then | |
| for i=1,2 do process(ast[i]) end | |
| elseif ast.tag == 'Id' then | |
| usevar(ast[1]) | |
| else | |
| assert(false, ast.tag) | |
| end | |
| end | |
| process(ast) | |
| end | |
| first_pass(ast) | |
| local cast = genfull(ast) | |
| return cast | |
| --##------------------------------------------------------------------ | |
| --## Note: this is a large function nesting many closures; | |
| --## indentation of its contents is omitted. | |
| --##------------------------------------------------------------------ | |
| end | |
| -- end of ast_to_cast | |
| M.ast_to_cast = ast_to_cast | |
| return M |