mirror of
https://github.com/Reuh/candran.git
synced 2025-10-27 09:59:29 +00:00
827 lines
24 KiB
Text
827 lines
24 KiB
Text
local targetName = "Lua 5.3"
|
|
|
|
return function(code, ast, options)
|
|
--- Line mapping
|
|
local lastInputPos = 1 -- last token position in the input code
|
|
local prevLinePos = 1 -- last token position in the previous line of code in the input code
|
|
local lastSource = options.chunkname or "nil" -- last found code source name (from the original file)
|
|
local lastLine = 1 -- last found line number (from the original file)
|
|
|
|
--- Newline management
|
|
local indentLevel = 0
|
|
-- Returns a newline.
|
|
local function newline()
|
|
local r = options.newline..string.rep(options.indentation, indentLevel)
|
|
if options.mapLines then
|
|
local sub = code:sub(lastInputPos)
|
|
local source, line = sub:sub(1, sub:find("\n")):match(".*%-%- (.-)%:(%d+)\n")
|
|
|
|
if source and line then
|
|
lastSource = source
|
|
lastLine = tonumber(line)
|
|
else
|
|
for _ in code:sub(prevLinePos, lastInputPos):gmatch("\n") do
|
|
lastLine += 1
|
|
end
|
|
end
|
|
|
|
prevLinePos = lastInputPos
|
|
|
|
r = " -- "..lastSource..":"..lastLine..r
|
|
end
|
|
return r
|
|
end
|
|
-- Returns a newline and add one level of indentation.
|
|
local function indent()
|
|
indentLevel += 1
|
|
return newline()
|
|
end
|
|
-- Returns a newline and remove one level of indentation.
|
|
local function unindent()
|
|
indentLevel -= 1
|
|
return newline()
|
|
end
|
|
|
|
--- State stacks
|
|
-- Used for context-sensitive syntax.
|
|
local states = {
|
|
push = {}, -- push stack variable names
|
|
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...}
|
|
scope = {} -- list of variables defined in the current scope
|
|
}
|
|
-- Push a new value on top of the stack "name". Returns an empty string for chaining.
|
|
local function push(name, state)
|
|
table.insert(states[name], state)
|
|
return ""
|
|
end
|
|
-- Remove the value on top of the stack "name". Returns an empty string for chaining.
|
|
local function pop(name)
|
|
table.remove(states[name])
|
|
return ""
|
|
end
|
|
-- Set the value on top of the stack "name". Returns an empty string for chaining.
|
|
local function set(name, state)
|
|
states[name][#states[name]] = state
|
|
return ""
|
|
end
|
|
-- Returns the value on top of the stack "name".
|
|
local function peek(name)
|
|
return states[name][#states[name]]
|
|
end
|
|
|
|
--- Variable management
|
|
-- Returns the prefixed variable name.
|
|
local function var(name)
|
|
return options.variablePrefix..name
|
|
end
|
|
|
|
-- Returns the prefixed temporary variable name.
|
|
local function tmp()
|
|
local scope = peek("scope")
|
|
local var = "%s_%s":format(options.variablePrefix, #scope)
|
|
table.insert(scope, var)
|
|
return var
|
|
end
|
|
|
|
--- Module management
|
|
local required = {} -- { ["full require expression"] = true, ... }
|
|
local requireStr = ""
|
|
-- Add the module "mod" to the list of modules to require, and load its field "field" (or the whole module if nil) into the variable "name".
|
|
local function addRequire(mod, name, field)
|
|
local req = "require(%q)%s":format(mod, field and "."..field or "")
|
|
if not required[req] then
|
|
requireStr ..= "local %s = %s%s":format(var(name), req, options.newline)
|
|
required[req] = true
|
|
end
|
|
end
|
|
|
|
--- AST traversal helpers
|
|
local loop = { "While", "Repeat", "Fornum", "Forin", "WhileExpr", "RepeatExpr", "FornumExpr", "ForinExpr" } -- loops tags (can contain continue)
|
|
local func = { "Function", "TableCompr", "DoExpr", "WhileExpr", "RepeatExpr", "IfExpr", "FornumExpr", "ForinExpr" } -- function scope tags (can contain push)
|
|
|
|
-- Returns the first node or subnode from the list "list" which tag is in the list "tags", or nil if there were none.
|
|
-- Won't recursively follow nodes which have a tag in "nofollow".
|
|
local function any(list, tags, nofollow={})
|
|
local tagsCheck = {}
|
|
for _, tag in ipairs(tags) do
|
|
tagsCheck[tag] = true
|
|
end
|
|
local nofollowCheck = {}
|
|
for _, tag in ipairs(nofollow) do
|
|
nofollowCheck[tag] = true
|
|
end
|
|
for _, node in ipairs(list) do
|
|
if type(node) == "table" then
|
|
if tagsCheck[node.tag] then
|
|
return node
|
|
end
|
|
if not nofollowCheck[node.tag] then
|
|
local r = any(node, tags, nofollow)
|
|
if r then return r end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Like any, but returns a list of every node found.
|
|
-- Order: in the order of the list, from the deepest to the nearest
|
|
local function search(list, tags, nofollow={})
|
|
local tagsCheck = {}
|
|
for _, tag in ipairs(tags) do
|
|
tagsCheck[tag] = true
|
|
end
|
|
local nofollowCheck = {}
|
|
for _, tag in ipairs(nofollow) do
|
|
nofollowCheck[tag] = true
|
|
end
|
|
local found = {}
|
|
for _, node in ipairs(list) do
|
|
if type(node) == "table" then
|
|
if not nofollowCheck[node.tag] then
|
|
for _, n in ipairs(search(node, tags, nofollow)) do
|
|
table.insert(found, n)
|
|
end
|
|
end
|
|
if tagsCheck[node.tag] then
|
|
table.insert(found, node)
|
|
end
|
|
end
|
|
end
|
|
return found
|
|
end
|
|
|
|
-- Returns true if the all the nodes in list have their type in tags.
|
|
local function all(list, tags)
|
|
for _, node in ipairs(list) do
|
|
local ok = false
|
|
for _, tag in ipairs(tags) do
|
|
if node.tag == tag then
|
|
ok = true
|
|
break
|
|
end
|
|
end
|
|
if not ok then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Lua compiler
|
|
local tags
|
|
-- Recursively returns the compiled AST Lua code, set "forceTag" to override the tag type and pass additional arguments to the tag constructor if needed.
|
|
local function lua(ast, forceTag, ...)
|
|
if options.mapLines and ast.pos then
|
|
lastInputPos = ast.pos
|
|
end
|
|
return tags[forceTag or ast.tag](ast, ...)
|
|
end
|
|
|
|
--- Lua function calls writer
|
|
local UNPACK = (list, i, j) -- table.unpack
|
|
return "table.unpack("..list..(i and (", "..i..(j and (", "..j) or "")) or "")..")"
|
|
end
|
|
local APPEND = (t, toAppend) -- append values "toAppend" (multiple values possible) to t
|
|
return "do"..indent().."local a = table.pack("..toAppend..")"..newline().."table.move(a, 1, a.n, #"..t.."+1, "..t..")"..unindent().."end"
|
|
end
|
|
local CONTINUE_START = () -- at the start of loops using continue
|
|
return "do"..indent()
|
|
end
|
|
local CONTINUE_STOP = () -- at the start of loops using continue
|
|
return unindent().."end"..newline().."::"..var"continue".."::"
|
|
end
|
|
local DESTRUCTURING_ASSIGN = (destructured, newlineAfter=false, noLocal=false) -- to define values from a destructuring assignement
|
|
local vars = {}
|
|
local values = {}
|
|
for _, list in ipairs(destructured) do
|
|
for _, v in ipairs(list) do
|
|
local var, val
|
|
if v.tag == "Id" then
|
|
var = v
|
|
val = { tag = "Index", { tag = "Id", list.id }, { tag = "String", v[1] } }
|
|
elseif v.tag == "Pair" then
|
|
var = v[2]
|
|
val = { tag = "Index", { tag = "Id", list.id }, v[1] }
|
|
else
|
|
error("unknown destructuring element type: "..tostring(v.tag))
|
|
end
|
|
if destructured.rightOp and destructured.leftOp then
|
|
val = { tag = "Op", destructured.rightOp, var, { tag = "Op", destructured.leftOp, val, var } }
|
|
elseif destructured.rightOp then
|
|
val = { tag = "Op", destructured.rightOp, var, val }
|
|
elseif destructured.leftOp then
|
|
val = { tag = "Op", destructured.leftOp, val, var }
|
|
end
|
|
table.insert(vars, lua(var))
|
|
table.insert(values, lua(val))
|
|
end
|
|
end
|
|
if #vars > 0 then
|
|
local decl = noLocal and "" or "local "
|
|
if newlineAfter then
|
|
return decl..table.concat(vars, ", ").." = "..table.concat(values, ", ")..newline()
|
|
else
|
|
return newline()..decl..table.concat(vars, ", ").." = "..table.concat(values, ", ")
|
|
end
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
|
|
--- Tag constructors
|
|
tags = setmetatable({
|
|
-- block: { stat* } --
|
|
Block = (t)
|
|
local hasPush = peek("push") == nil and any(t, { "Push" }, func) -- push in block and push context not yet defined
|
|
if hasPush and hasPush == t[#t] then -- if the first push is the last statement, it's just a return
|
|
hasPush.tag = "Return"
|
|
hasPush = false
|
|
end
|
|
local r = push("scope", {})
|
|
if hasPush then
|
|
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
|
end
|
|
for i=1, #t-1, 1 do
|
|
r ..= lua(t[i])..newline()
|
|
end
|
|
if t[#t] then
|
|
r ..= lua(t[#t])
|
|
end
|
|
if hasPush and (t[#t] and t[#t].tag ~= "Return") then -- add return only if needed
|
|
r ..= newline().."return "..UNPACK(var"push")..pop("push")
|
|
end
|
|
return r..pop("scope")
|
|
end,
|
|
|
|
-- stat --
|
|
|
|
-- Do{ stat* }
|
|
Do = (t)
|
|
return "do"..indent()..lua(t, "Block")..unindent().."end"
|
|
end,
|
|
-- Set{ {lhs+} (opid? = opid?)? {expr+} }
|
|
Set = (t)
|
|
-- extract vars and values
|
|
local expr = t[#t]
|
|
local vars, values = {}, {}
|
|
local destructuringVars, destructuringValues = {}, {}
|
|
for i, n in ipairs(t[1]) do
|
|
if n.tag == "DestructuringId" then
|
|
table.insert(destructuringVars, n)
|
|
table.insert(destructuringValues, expr[i])
|
|
else
|
|
table.insert(vars, n)
|
|
table.insert(values, expr[i])
|
|
end
|
|
end
|
|
--
|
|
if #t == 2 or #t == 3 then
|
|
local r = ""
|
|
if #vars > 0 then
|
|
r = lua(vars, "_lhs").." = "..lua(values, "_lhs")
|
|
end
|
|
if #destructuringVars > 0 then
|
|
local destructured = {}
|
|
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
|
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
|
end
|
|
return r
|
|
elseif #t == 4 then
|
|
if t[3] == "=" then
|
|
local r = ""
|
|
if #vars > 0 then
|
|
r ..= lua(vars, "_lhs").." = "..lua({ t[2], vars[1], { tag = "Paren", values[1] } }, "Op")
|
|
for i=2, math.min(#t[4], #vars), 1 do
|
|
r ..= ", "..lua({ t[2], vars[i], { tag = "Paren", values[i] } }, "Op")
|
|
end
|
|
end
|
|
if #destructuringVars > 0 then
|
|
local destructured = { rightOp = t[2] }
|
|
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
|
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
|
end
|
|
return r
|
|
else
|
|
local r = ""
|
|
if #vars > 0 then
|
|
r ..= lua(vars, "_lhs").." = "..lua({ t[3], { tag = "Paren", values[1] }, vars[1] }, "Op")
|
|
for i=2, math.min(#t[4], #t[1]), 1 do
|
|
r ..= ", "..lua({ t[3], { tag = "Paren", values[i] }, vars[i] }, "Op")
|
|
end
|
|
end
|
|
if #destructuringVars > 0 then
|
|
local destructured = { leftOp = t[3] }
|
|
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
|
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
|
end
|
|
return r
|
|
end
|
|
else -- You are mad.
|
|
local r = ""
|
|
if #vars > 0 then
|
|
r ..= lua(vars, "_lhs").." = "..lua({ t[2], vars[1], { tag = "Op", t[4], { tag = "Paren", values[1] }, vars[1] } }, "Op")
|
|
for i=2, math.min(#t[5], #t[1]), 1 do
|
|
r ..= ", "..lua({ t[2], vars[i], { tag = "Op", t[4], { tag = "Paren", values[i] }, vars[i] } }, "Op")
|
|
end
|
|
end
|
|
if #destructuringVars > 0 then
|
|
local destructured = { rightOp = t[2], leftOp = t[4] }
|
|
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
|
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
|
end
|
|
return r
|
|
end
|
|
end,
|
|
-- While{ expr block }
|
|
While = (t)
|
|
local r = ""
|
|
local hasContinue = any(t[2], { "Continue" }, loop)
|
|
local lets = search({ t[1] }, { "LetExpr" })
|
|
if #lets > 0 then
|
|
r ..= "do"..indent()
|
|
for _, l in ipairs(lets) do
|
|
r ..= lua(l, "Let")..newline()
|
|
end
|
|
end
|
|
r ..= "while "..lua(t[1]).." do"..indent()
|
|
if #lets > 0 then
|
|
r ..= "do"..indent()
|
|
end
|
|
if hasContinue then
|
|
r ..= CONTINUE_START()
|
|
end
|
|
r ..= lua(t[2])
|
|
if hasContinue then
|
|
r ..= CONTINUE_STOP()
|
|
end
|
|
r ..= unindent().."end"
|
|
if #lets > 0 then
|
|
for _, l in ipairs(lets) do
|
|
r ..= newline()..lua(l, "Set")
|
|
end
|
|
r ..= unindent().."end"..unindent().."end"
|
|
end
|
|
return r
|
|
end,
|
|
-- Repeat{ block expr }
|
|
Repeat = (t)
|
|
local hasContinue = any(t[1], { "Continue" }, loop)
|
|
local r = "repeat"..indent()
|
|
if hasContinue then
|
|
r ..= CONTINUE_START()
|
|
end
|
|
r ..= lua(t[1])
|
|
if hasContinue then
|
|
r ..= CONTINUE_STOP()
|
|
end
|
|
r ..= unindent().."until "..lua(t[2])
|
|
return r
|
|
end,
|
|
-- If{ (lexpr block)+ block? }
|
|
If = (t)
|
|
local r = ""
|
|
local toClose = 0 -- blocks that need to be closed at the end of the if
|
|
local lets = search({ t[1] }, { "LetExpr" })
|
|
if #lets > 0 then
|
|
r ..= "do"..indent()
|
|
toClose += 1
|
|
for _, l in ipairs(lets) do
|
|
r ..= lua(l, "Let")..newline()
|
|
end
|
|
end
|
|
r ..= "if "..lua(t[1]).." then"..indent()..lua(t[2])..unindent()
|
|
for i=3, #t-1, 2 do
|
|
lets = search({ t[i] }, { "LetExpr" })
|
|
if #lets > 0 then
|
|
r ..= "else"..indent()
|
|
toClose += 1
|
|
for _, l in ipairs(lets) do
|
|
r ..= lua(l, "Let")..newline()
|
|
end
|
|
else
|
|
r ..= "else"
|
|
end
|
|
r ..= "if "..lua(t[i]).." then"..indent()..lua(t[i+1])..unindent()
|
|
end
|
|
if #t % 2 == 1 then
|
|
r ..= "else"..indent()..lua(t[#t])..unindent()
|
|
end
|
|
r ..= "end"
|
|
for i=1, toClose do
|
|
r ..= unindent().."end"
|
|
end
|
|
return r
|
|
end,
|
|
-- Fornum{ ident expr expr expr? block }
|
|
Fornum = (t)
|
|
local r = "for "..lua(t[1]).." = "..lua(t[2])..", "..lua(t[3])
|
|
if #t == 5 then
|
|
local hasContinue = any(t[5], { "Continue" }, loop)
|
|
r ..= ", "..lua(t[4]).." do"..indent()
|
|
if hasContinue then
|
|
r ..= CONTINUE_START()
|
|
end
|
|
r ..= lua(t[5])
|
|
if hasContinue then
|
|
r ..= CONTINUE_STOP()
|
|
end
|
|
return r..unindent().."end"
|
|
else
|
|
local hasContinue = any(t[4], { "Continue" }, loop)
|
|
r ..= " do"..indent()
|
|
if hasContinue then
|
|
r ..= CONTINUE_START()
|
|
end
|
|
r ..= lua(t[4])
|
|
if hasContinue then
|
|
r ..= CONTINUE_STOP()
|
|
end
|
|
return r..unindent().."end"
|
|
end
|
|
end,
|
|
-- Forin{ {ident+} {expr+} block }
|
|
Forin = (t)
|
|
local destructured = {}
|
|
local hasContinue = any(t[3], { "Continue" }, loop)
|
|
local r = "for "..push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring").." in "..lua(t[2], "_lhs").." do"..indent()
|
|
if hasContinue then
|
|
r ..= CONTINUE_START()
|
|
end
|
|
r ..= DESTRUCTURING_ASSIGN(destructured, true)..lua(t[3])
|
|
if hasContinue then
|
|
r ..= CONTINUE_STOP()
|
|
end
|
|
return r..unindent().."end"
|
|
end,
|
|
-- Local{ {ident+} {expr+}? }
|
|
Local = (t)
|
|
local destructured = {}
|
|
local r = "local "..push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring")
|
|
if t[2][1] then
|
|
r ..= " = "..lua(t[2], "_lhs")
|
|
end
|
|
return r..DESTRUCTURING_ASSIGN(destructured)
|
|
end,
|
|
-- Let{ {ident+} {expr+}? }
|
|
Let = (t)
|
|
local destructured = {}
|
|
local nameList = push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring")
|
|
local r = "local "..nameList
|
|
if t[2][1] then
|
|
if all(t[2], { "Nil", "Dots", "Boolean", "Number", "String" }) then -- predeclaration doesn't matter here
|
|
r ..= " = "..lua(t[2], "_lhs")
|
|
else
|
|
r ..= newline()..nameList.." = "..lua(t[2], "_lhs")
|
|
end
|
|
end
|
|
return r..DESTRUCTURING_ASSIGN(destructured)
|
|
end,
|
|
-- Localrec{ {ident} {expr} }
|
|
Localrec = (t)
|
|
return "local function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword")
|
|
end,
|
|
-- Goto{ <string> }
|
|
Goto = (t)
|
|
return "goto "..lua(t, "Id")
|
|
end,
|
|
-- Label{ <string> }
|
|
Label = (t)
|
|
return "::"..lua(t, "Id").."::"
|
|
end,
|
|
-- Return{ <expr*> }
|
|
Return = (t)
|
|
local push = peek("push")
|
|
if push then
|
|
local r = ""
|
|
for _, val in ipairs(t) do
|
|
r ..= push.."[#"..push.."+1] = "..lua(val)..newline()
|
|
end
|
|
return r.."return "..UNPACK(push)
|
|
else
|
|
return "return "..lua(t, "_lhs")
|
|
end
|
|
end,
|
|
-- Push{ <expr*> }
|
|
Push = (t)
|
|
local var = assert(peek("push"), "no context given for push")
|
|
r = ""
|
|
for i=1, #t-1, 1 do
|
|
r ..= var.."[#"..var.."+1] = "..lua(t[i])..newline()
|
|
end
|
|
if t[#t] then
|
|
if t[#t].tag == "Call" then
|
|
r ..= APPEND(var, lua(t[#t]))
|
|
else
|
|
r ..= var.."[#"..var.."+1] = "..lua(t[#t])
|
|
end
|
|
end
|
|
return r
|
|
end,
|
|
-- Break
|
|
Break = ()
|
|
return "break"
|
|
end,
|
|
-- Continue
|
|
Continue = ()
|
|
return "goto "..var"continue"
|
|
end,
|
|
-- apply (below)
|
|
|
|
-- expr --
|
|
|
|
-- Nil
|
|
Nil = ()
|
|
return "nil"
|
|
end,
|
|
-- Dots
|
|
Dots = ()
|
|
return "..."
|
|
end,
|
|
-- Boolean{ <boolean> }
|
|
Boolean = (t)
|
|
return tostring(t[1])
|
|
end,
|
|
-- Number{ <string> }
|
|
Number = (t)
|
|
return tostring(t[1])
|
|
end,
|
|
-- String{ <string> }
|
|
String = (t)
|
|
return "%q":format(t[1])
|
|
end,
|
|
-- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
|
|
_functionWithoutKeyword = (t)
|
|
local r = "("
|
|
local decl = {}
|
|
if t[1][1] then
|
|
if t[1][1].tag == "ParPair" then
|
|
local id = lua(t[1][1][1])
|
|
indentLevel += 1
|
|
table.insert(decl, "if "..id.." == nil then "..id.." = "..lua(t[1][1][2]).." end")
|
|
indentLevel -= 1
|
|
r ..= id
|
|
else
|
|
r ..= lua(t[1][1])
|
|
end
|
|
for i=2, #t[1], 1 do
|
|
if t[1][i].tag == "ParPair" then
|
|
local id = lua(t[1][i][1])
|
|
indentLevel += 1
|
|
table.insert(decl, "if "..id.." == nil then "..id.." = "..lua(t[1][i][2]).." end")
|
|
indentLevel -= 1
|
|
r ..= ", " ..id
|
|
else
|
|
r ..= ", "..lua(t[1][i])
|
|
end
|
|
end
|
|
end
|
|
r ..= ")"..indent()
|
|
for _, d in ipairs(decl) do
|
|
r ..= d..newline()
|
|
end
|
|
if t[2][#t[2]] and t[2][#t[2]].tag == "Push" then -- convert final push to return
|
|
t[2][#t[2]].tag = "Return"
|
|
end
|
|
local hasPush = any(t[2], { "Push" }, func)
|
|
if hasPush then
|
|
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
|
else
|
|
push("push", false) -- no push here (make sure higher push doesn't affect us)
|
|
end
|
|
r ..= lua(t[2])
|
|
if hasPush and (t[2][#t[2]] and t[2][#t[2]].tag ~= "Return") then -- add return only if needed
|
|
r ..= newline().."return "..UNPACK(var"push")
|
|
end
|
|
pop("push")
|
|
return r..unindent().."end"
|
|
end,
|
|
Function = (t)
|
|
return "function"..lua(t, "_functionWithoutKeyword")
|
|
end,
|
|
-- Table{ ( `Pair{ expr expr } | expr )* }
|
|
Pair = (t)
|
|
return "["..lua(t[1]).."] = "..lua(t[2])
|
|
end,
|
|
Table = (t)
|
|
if #t == 0 then
|
|
return "{}"
|
|
elseif #t == 1 then
|
|
return "{ "..lua(t, "_lhs").." }"
|
|
else
|
|
return "{"..indent()..lua(t, "_lhs", nil, true)..unindent().."}"
|
|
end
|
|
end,
|
|
-- TableCompr{ block }
|
|
TableCompr = (t)
|
|
return push("push", "self").."(function()"..indent().."local self = {}"..newline()..lua(t[1])..newline().."return self"..unindent().."end)()"..pop("push")
|
|
end,
|
|
-- Op{ opid expr expr? }
|
|
Op = (t)
|
|
local r
|
|
if #t == 2 then
|
|
if type(tags._opid[t[1]]) == "string" then
|
|
r = tags._opid[t[1]].." "..lua(t[2])
|
|
else
|
|
r = tags._opid[t[1]](t[2])
|
|
end
|
|
else
|
|
if type(tags._opid[t[1]]) == "string" then
|
|
r = lua(t[2]).." "..tags._opid[t[1]].." "..lua(t[3])
|
|
else
|
|
r = tags._opid[t[1]](t[2], t[3])
|
|
end
|
|
end
|
|
return r
|
|
end,
|
|
-- Paren{ expr }
|
|
Paren = (t)
|
|
return "("..lua(t[1])..")"
|
|
end,
|
|
-- MethodStub{ expr expr }
|
|
MethodStub = (t)
|
|
return "(function()"..indent() ..
|
|
"local "..var"object".." = "..lua(t[1])..newline()..
|
|
"local "..var"method".." = "..var"object".."."..lua(t[2], "Id")..newline() ..
|
|
"if "..var"method".." == nil then return nil end"..newline()..
|
|
"return function(...) return "..var"method".."("..var"object"..", ...) end"..unindent()..
|
|
"end)()"
|
|
end,
|
|
-- SafeMethodStub{ expr expr }
|
|
SafeMethodStub = (t)
|
|
return "(function()"..indent() ..
|
|
"local "..var"object".." = "..lua(t[1])..newline()..
|
|
"if "..var"object".." == nil then return nil end"..newline()..
|
|
"local "..var"method".." = "..var"object".."."..lua(t[2], "Id")..newline() ..
|
|
"if "..var"method".." == nil then return nil end"..newline()..
|
|
"return function(...) return "..var"method".."("..var"object"..", ...) end"..unindent()..
|
|
"end)()"
|
|
end,
|
|
-- statexpr (below)
|
|
-- apply (below)
|
|
-- lhs (below)
|
|
|
|
-- lexpr --
|
|
LetExpr = (t)
|
|
return lua(t[1][1])
|
|
end,
|
|
|
|
-- statexpr --
|
|
_statexpr = (t, stat)
|
|
local hasPush = any(t, { "Push" }, func)
|
|
local r = "(function()"..indent()
|
|
if hasPush then
|
|
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
|
else
|
|
push("push", false) -- no push here (make sure higher push don't affect us)
|
|
end
|
|
r ..= lua(t, stat)
|
|
if hasPush then
|
|
r ..= newline().."return "..UNPACK(var"push")
|
|
end
|
|
pop("push")
|
|
r ..= unindent().."end)()"
|
|
return r
|
|
end,
|
|
-- DoExpr{ stat* }
|
|
DoExpr = (t)
|
|
if t[#t].tag == "Push" then -- convert final push to return
|
|
t[#t].tag = "Return"
|
|
end
|
|
return lua(t, "_statexpr", "Do")
|
|
end,
|
|
-- WhileExpr{ expr block }
|
|
WhileExpr = (t)
|
|
return lua(t, "_statexpr", "While")
|
|
end,
|
|
-- RepeatExpr{ block expr }
|
|
RepeatExpr = (t)
|
|
return lua(t, "_statexpr", "Repeat")
|
|
end,
|
|
-- IfExpr{ (expr block)+ block? }
|
|
IfExpr = (t)
|
|
for i=2, #t do -- convert final pushes to returns
|
|
local block = t[i]
|
|
if block[#block] and block[#block].tag == "Push" then
|
|
block[#block].tag = "Return"
|
|
end
|
|
end
|
|
return lua(t, "_statexpr", "If")
|
|
end,
|
|
-- FornumExpr{ ident expr expr expr? block }
|
|
FornumExpr = (t)
|
|
return lua(t, "_statexpr", "Fornum")
|
|
end,
|
|
-- ForinExpr{ {ident+} {expr+} block }
|
|
ForinExpr = (t)
|
|
return lua(t, "_statexpr", "Forin")
|
|
end,
|
|
|
|
-- apply --
|
|
|
|
-- Call{ expr expr* }
|
|
Call = (t)
|
|
if t[1].tag == "String" or t[1].tag == "Table" then
|
|
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
|
|
elseif t[1].tag == "MethodStub" then -- method call
|
|
if t[1][1].tag == "String" or t[1][1].tag == "Table" then
|
|
return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
|
else
|
|
return lua(t[1][1])..":"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
|
end
|
|
else
|
|
return lua(t[1]).."("..lua(t, "_lhs", 2)..")"
|
|
end
|
|
end,
|
|
-- SafeCall{ expr expr* }
|
|
SafeCall = (t)
|
|
if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
|
|
return lua(t, "SafeIndex")
|
|
else -- no side effects possible
|
|
return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."("..lua(t, "_lhs", 2)..") or nil)"
|
|
end
|
|
end,
|
|
|
|
-- lhs --
|
|
_lhs = (t, start=1, newlines)
|
|
local r
|
|
if t[start] then
|
|
r = lua(t[start])
|
|
for i=start+1, #t, 1 do
|
|
r ..= ","..(newlines and newline() or " ")..lua(t[i])
|
|
end
|
|
else
|
|
r = ""
|
|
end
|
|
return r
|
|
end,
|
|
-- Id{ <string> }
|
|
Id = (t)
|
|
return t[1]
|
|
end,
|
|
-- DestructuringId{ Id | Pair+ }
|
|
DestructuringId = (t)
|
|
if t.id then -- destructing already done before, use parent variable as id
|
|
return t.id
|
|
else
|
|
local d = assert(peek("destructuring"), "DestructuringId not in a destructurable assignement")
|
|
local vars = { id = tmp() }
|
|
for j=1, #t, 1 do
|
|
table.insert(vars, t[j])
|
|
end
|
|
table.insert(d, vars)
|
|
t.id = vars.id
|
|
return vars.id
|
|
end
|
|
end,
|
|
-- Index{ expr expr }
|
|
Index = (t)
|
|
if t[1].tag == "String" or t[1].tag == "Table" then
|
|
return "("..lua(t[1])..")["..lua(t[2]).."]"
|
|
else
|
|
return lua(t[1]).."["..lua(t[2]).."]"
|
|
end
|
|
end,
|
|
-- SafeIndex{ expr expr }
|
|
SafeIndex = (t)
|
|
if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
|
|
local l = {} -- list of immediately chained safeindex, from deepest to nearest (to simply generated code)
|
|
while t.tag == "SafeIndex" or t.tag == "SafeCall" do
|
|
table.insert(l, 1, t)
|
|
t = t[1]
|
|
end
|
|
local r = "(function()"..indent().."local "..var"safe".." = "..lua(l[1][1])..newline() -- base expr
|
|
for _, e in ipairs(l) do
|
|
r ..= "if "..var"safe".." == nil then return nil end"..newline()
|
|
if e.tag == "SafeIndex" then
|
|
r ..= var"safe".." = "..var"safe".."["..lua(e[2]).."]"..newline()
|
|
else
|
|
r ..= var"safe".." = "..var"safe".."("..lua(e, "_lhs", 2)..")"..newline()
|
|
end
|
|
end
|
|
r ..= "return "..var"safe"..unindent().."end)()"
|
|
return r
|
|
else -- no side effects possible
|
|
return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."["..lua(t[2]).."] or nil)"
|
|
end
|
|
end,
|
|
|
|
-- opid --
|
|
_opid = {
|
|
add = "+", sub = "-", mul = "*", div = "/",
|
|
idiv = "//", mod = "%", pow = "^", concat = "..",
|
|
band = "&", bor = "|", bxor = "~", shl = "<<", shr = ">>",
|
|
eq = "==", ne = "~=", lt = "<", gt = ">", le = "<=", ge = ">=",
|
|
["and"] = "and", ["or"] = "or", unm = "-", len = "#", bnot = "~", ["not"] = "not"
|
|
}
|
|
}, {
|
|
__index = (self, key)
|
|
error("don't know how to compile a "..tostring(key).." to "..targetName)
|
|
end
|
|
})
|
|
|
|
#placeholder("patch")
|
|
|
|
local code = lua(ast)..newline()
|
|
return requireStr..code
|
|
end
|