mirror of
https://github.com/leafo/moonscript.git
synced 2025-01-09 00:04:22 +00:00
925 lines
22 KiB
Plaintext
925 lines
22 KiB
Plaintext
|
|
types = require "moonscript.types"
|
|
util = require "moonscript.util"
|
|
data = require "moonscript.data"
|
|
|
|
import reversed, unpack from util
|
|
import ntype, mtype, build, smart_node, is_slice, value_is_singular from types
|
|
import insert from table
|
|
import NameProxy, LocalName from require "moonscript.transform.names"
|
|
|
|
destructure = require "moonscript.transform.destructure"
|
|
NOOP = {"noop"}
|
|
|
|
local implicitly_return
|
|
|
|
class Run
|
|
new: (@fn) =>
|
|
self[1] = "run"
|
|
|
|
call: (state) =>
|
|
self.fn state
|
|
|
|
-- transform the last stm is a list of stms
|
|
-- will puke on group
|
|
apply_to_last = (stms, fn) ->
|
|
-- find last (real) exp
|
|
last_exp_id = 0
|
|
for i = #stms, 1, -1
|
|
stm = stms[i]
|
|
if stm and mtype(stm) != Run
|
|
last_exp_id = i
|
|
break
|
|
|
|
return for i, stm in ipairs stms
|
|
if i == last_exp_id
|
|
{"transform", stm, fn}
|
|
else
|
|
stm
|
|
|
|
-- is a body a sindle expression/statement
|
|
is_singular = (body) ->
|
|
return false if #body != 1
|
|
if "group" == ntype body
|
|
is_singular body[2]
|
|
else
|
|
body[1]
|
|
|
|
-- this mutates body searching for assigns
|
|
extract_declarations = (body=@current_stms, start=@current_stm_i + 1, out={}) =>
|
|
for i=start,#body
|
|
stm = body[i]
|
|
continue if stm == nil
|
|
stm = @transform.statement stm
|
|
body[i] = stm
|
|
switch stm[1]
|
|
when "assign", "declare"
|
|
for name in *stm[2]
|
|
insert out, name if type(name) == "string"
|
|
when "group"
|
|
extract_declarations @, stm[2], 1, out
|
|
out
|
|
|
|
expand_elseif_assign = (ifstm) ->
|
|
for i = 4, #ifstm
|
|
case = ifstm[i]
|
|
if ntype(case) == "elseif" and ntype(case[2]) == "assign"
|
|
split = { unpack ifstm, 1, i - 1 }
|
|
insert split, {
|
|
"else", {
|
|
{"if", case[2], case[3], unpack ifstm, i + 1}
|
|
}
|
|
}
|
|
return split
|
|
|
|
ifstm
|
|
|
|
constructor_name = "new"
|
|
|
|
with_continue_listener = (body) ->
|
|
continue_name = nil
|
|
{
|
|
Run =>
|
|
@listen "continue", ->
|
|
unless continue_name
|
|
continue_name = NameProxy"continue"
|
|
@put_name continue_name
|
|
continue_name
|
|
|
|
build.group body
|
|
|
|
Run =>
|
|
return unless continue_name
|
|
@put_name continue_name, nil
|
|
@splice (lines) -> {
|
|
{"assign", {continue_name}, {"false"}}
|
|
{"repeat", "true", {
|
|
lines
|
|
{"assign", {continue_name}, {"true"}}
|
|
}}
|
|
{"if", {"not", continue_name}, {
|
|
{"break"}
|
|
}}
|
|
}
|
|
}
|
|
|
|
|
|
class Transformer
|
|
new: (@transformers) =>
|
|
@seen_nodes = setmetatable {}, __mode: "k"
|
|
|
|
transform: (scope, node, ...) =>
|
|
return node if @seen_nodes[node]
|
|
@seen_nodes[node] = true
|
|
while true
|
|
transformer = @transformers[ntype node]
|
|
res = if transformer
|
|
transformer(scope, node, ...) or node
|
|
else
|
|
node
|
|
return node if res == node
|
|
node = res
|
|
node
|
|
|
|
bind: (scope) =>
|
|
(...) -> @transform scope, ...
|
|
|
|
__call: (...) => @transform ...
|
|
|
|
can_transform: (node) =>
|
|
@transformers[ntype node] != nil
|
|
|
|
construct_comprehension = (inner, clauses) ->
|
|
current_stms = inner
|
|
for _, clause in reversed clauses
|
|
t = clause[1]
|
|
current_stms = switch t
|
|
when "for"
|
|
{_, name, bounds} = clause
|
|
{"for", name, bounds, current_stms}
|
|
when "foreach"
|
|
{_, names, iter} = clause
|
|
{"foreach", names, {iter}, current_stms}
|
|
when "when"
|
|
{_, cond} = clause
|
|
{"if", cond, current_stms}
|
|
else
|
|
error "Unknown comprehension clause: "..t
|
|
|
|
current_stms = {current_stms}
|
|
|
|
current_stms[1]
|
|
|
|
Statement = Transformer {
|
|
transform: (tuple) =>
|
|
{_, node, fn} = tuple
|
|
fn node
|
|
|
|
root_stms: (body) =>
|
|
apply_to_last body, implicitly_return @
|
|
|
|
declare_glob: (node) =>
|
|
names = extract_declarations @
|
|
|
|
if node[2] == "^"
|
|
names = for name in *names
|
|
continue unless name\match "^%u"
|
|
name
|
|
|
|
{"declare", names}
|
|
|
|
assign: (node) =>
|
|
names, values = unpack node, 2
|
|
|
|
-- bubble cascading assigns
|
|
transformed = if #values == 1
|
|
value = values[1]
|
|
t = ntype value
|
|
|
|
if t == "decorated"
|
|
value = @transform.statement value
|
|
t = ntype value
|
|
|
|
if types.cascading[t]
|
|
ret = (stm) ->
|
|
if types.is_value stm
|
|
{"assign", names, {stm}}
|
|
else
|
|
stm
|
|
|
|
build.group {
|
|
{"declare", names}
|
|
@transform.statement value, ret, node
|
|
}
|
|
|
|
node = transformed or node
|
|
|
|
|
|
if destructure.has_destructure names
|
|
return destructure.split_assign @, node
|
|
|
|
-- print util.dump node
|
|
node
|
|
|
|
continue: (node) =>
|
|
continue_name = @send "continue"
|
|
error "continue must be inside of a loop" unless continue_name
|
|
build.group {
|
|
build.assign_one continue_name, "true"
|
|
{"break"}
|
|
}
|
|
|
|
export: (node) =>
|
|
-- assign values if they are included
|
|
if #node > 2
|
|
if node[2] == "class"
|
|
cls = smart_node node[3]
|
|
build.group {
|
|
{"export", {cls.name}}
|
|
cls
|
|
}
|
|
else
|
|
build.group {
|
|
node
|
|
build.assign {
|
|
names: node[2]
|
|
values: node[3]
|
|
}
|
|
}
|
|
else
|
|
nil
|
|
|
|
update: (node) =>
|
|
_, name, op, exp = unpack node
|
|
op_final = op\match "^(.+)=$"
|
|
error "Unknown op: "..op if not op_final
|
|
exp = {"parens", exp} unless value_is_singular exp
|
|
build.assign_one name, {"exp", name, op_final, exp}
|
|
|
|
import: (node) =>
|
|
_, names, source = unpack node
|
|
|
|
stubs = for name in *names
|
|
if type(name) == "table"
|
|
name
|
|
else
|
|
{"dot", name}
|
|
|
|
real_names = for name in *names
|
|
type(name) == "table" and name[2] or name
|
|
|
|
if type(source) == "string"
|
|
build.assign {
|
|
names: real_names
|
|
values: [build.chain { base: source, stub} for stub in *stubs]
|
|
}
|
|
else
|
|
source_name = NameProxy "table"
|
|
build.group {
|
|
{"declare", real_names}
|
|
build["do"] {
|
|
build.assign_one source_name, source
|
|
build.assign {
|
|
names: real_names
|
|
values: [build.chain { base: source_name, stub} for stub in *stubs]
|
|
}
|
|
}
|
|
}
|
|
|
|
comprehension: (node, action) =>
|
|
_, exp, clauses = unpack node
|
|
|
|
action = action or (exp) -> {exp}
|
|
construct_comprehension action(exp), clauses
|
|
|
|
do: (node, ret) =>
|
|
node[2] = apply_to_last node[2], ret if ret
|
|
node
|
|
|
|
decorated: (node) =>
|
|
stm, dec = unpack node, 2
|
|
|
|
wrapped = switch dec[1]
|
|
when "if"
|
|
cond, fail = unpack dec, 2
|
|
fail = { "else", { fail } } if fail
|
|
{ "if", cond, { stm }, fail }
|
|
when "unless"
|
|
{ "unless", dec[2], { stm } }
|
|
when "comprehension"
|
|
{ "comprehension", stm, dec[2] }
|
|
else
|
|
error "Unknown decorator " .. dec[1]
|
|
|
|
if ntype(stm) == "assign"
|
|
wrapped = build.group {
|
|
build.declare names: [name for name in *stm[2] when type(name) == "string"]
|
|
wrapped
|
|
}
|
|
|
|
wrapped
|
|
|
|
unless: (node) =>
|
|
{ "if", {"not", {"parens", node[2]}}, unpack node, 3 }
|
|
|
|
if: (node, ret) =>
|
|
-- expand assign in cond
|
|
if ntype(node[2]) == "assign"
|
|
_, assign, body = unpack node
|
|
if destructure.has_destructure assign[2]
|
|
name = NameProxy "des"
|
|
|
|
body = {
|
|
destructure.build_assign @, assign[2][1], name
|
|
build.group node[3]
|
|
}
|
|
|
|
return build.do {
|
|
build.assign_one name, assign[3][1]
|
|
{"if", name, body, unpack node, 4}
|
|
}
|
|
else
|
|
name = assign[2][1]
|
|
return build["do"] {
|
|
assign
|
|
{"if", name, unpack node, 3}
|
|
}
|
|
|
|
node = expand_elseif_assign node
|
|
|
|
-- apply cascading return decorator
|
|
if ret
|
|
smart_node node
|
|
-- mutate all the bodies
|
|
node['then'] = apply_to_last node['then'], ret
|
|
for i = 4, #node
|
|
case = node[i]
|
|
body_idx = #node[i]
|
|
case[body_idx] = apply_to_last case[body_idx], ret
|
|
|
|
node
|
|
|
|
with: (node, ret) =>
|
|
exp, block = unpack node, 2
|
|
|
|
copy_scope = true
|
|
local scope_name, named_assign
|
|
|
|
if ntype(exp) == "assign"
|
|
names, values = unpack exp, 2
|
|
first_name = names[1]
|
|
|
|
if ntype(first_name) == "value"
|
|
scope_name = first_name
|
|
named_assign = exp
|
|
exp = values[1]
|
|
copy_scope = false
|
|
else
|
|
scope_name = NameProxy "with"
|
|
exp = values[1]
|
|
values[1] = scope_name
|
|
named_assign = {"assign", names, values}
|
|
|
|
elseif @is_local exp
|
|
scope_name = exp
|
|
copy_scope = false
|
|
|
|
scope_name or= NameProxy "with"
|
|
|
|
build.do {
|
|
Run => @set "scope_var", scope_name
|
|
copy_scope and build.assign_one(scope_name, exp) or NOOP
|
|
named_assign or NOOP
|
|
build.group block
|
|
|
|
if ret
|
|
ret scope_name
|
|
}
|
|
|
|
foreach: (node) =>
|
|
smart_node node
|
|
source = unpack node.iter
|
|
|
|
destructures = {}
|
|
node.names = for i, name in ipairs node.names
|
|
if ntype(name) == "table"
|
|
with proxy = NameProxy "des"
|
|
insert destructures, destructure.build_assign @, name, proxy
|
|
else
|
|
name
|
|
|
|
if next destructures
|
|
insert destructures, build.group node.body
|
|
node.body = destructures
|
|
|
|
if ntype(source) == "unpack"
|
|
list = source[2]
|
|
|
|
index_name = NameProxy "index"
|
|
list_name = @is_local(list) and list or NameProxy "list"
|
|
|
|
slice_var = nil
|
|
bounds = if is_slice list
|
|
slice = list[#list]
|
|
table.remove list
|
|
table.remove slice, 1
|
|
|
|
slice[2] = if slice[2] and slice[2] != ""
|
|
max_tmp_name = NameProxy "max"
|
|
slice_var = build.assign_one max_tmp_name, slice[2]
|
|
{"exp", max_tmp_name, "<", 0
|
|
"and", {"length", list_name}, "+", max_tmp_name
|
|
"or", max_tmp_name }
|
|
else
|
|
{"length", list_name}
|
|
|
|
slice
|
|
else
|
|
{1, {"length", list_name}}
|
|
|
|
return build.group {
|
|
list_name != list and build.assign_one(list_name, list) or NOOP
|
|
slice_var or NOOP
|
|
build["for"] {
|
|
name: index_name
|
|
bounds: bounds
|
|
body: {
|
|
{"assign", node.names, { NameProxy.index list_name, index_name }}
|
|
build.group node.body
|
|
}
|
|
}
|
|
}
|
|
|
|
node.body = with_continue_listener node.body
|
|
|
|
while: (node) =>
|
|
smart_node node
|
|
node.body = with_continue_listener node.body
|
|
|
|
for: (node) =>
|
|
smart_node node
|
|
node.body = with_continue_listener node.body
|
|
|
|
switch: (node, ret) =>
|
|
_, exp, conds = unpack node
|
|
exp_name = NameProxy "exp"
|
|
|
|
-- convert switch conds into if statment conds
|
|
convert_cond = (cond) ->
|
|
t, case_exps, body = unpack cond
|
|
out = {}
|
|
insert out, t == "case" and "elseif" or "else"
|
|
if t != "else"
|
|
cond_exp = {}
|
|
for i, case in ipairs case_exps
|
|
if i == 1
|
|
insert cond_exp, "exp"
|
|
else
|
|
insert cond_exp, "or"
|
|
|
|
case = {"parens", case} unless value_is_singular case
|
|
insert cond_exp, {"exp", case, "==", exp_name}
|
|
|
|
insert out, cond_exp
|
|
else
|
|
body = case_exps
|
|
|
|
if ret
|
|
body = apply_to_last body, ret
|
|
|
|
insert out, body
|
|
|
|
out
|
|
|
|
first = true
|
|
if_stm = {"if"}
|
|
for cond in *conds
|
|
if_cond = convert_cond cond
|
|
if first
|
|
first = false
|
|
insert if_stm, if_cond[2]
|
|
insert if_stm, if_cond[3]
|
|
else
|
|
insert if_stm, if_cond
|
|
|
|
build.group {
|
|
build.assign_one exp_name, exp
|
|
if_stm
|
|
}
|
|
|
|
class: (node, ret, parent_assign) =>
|
|
_, name, parent_val, body = unpack node
|
|
|
|
-- split apart properties and statements
|
|
statements = {}
|
|
properties = {}
|
|
for item in *body
|
|
switch item[1]
|
|
when "stm"
|
|
insert statements, item[2]
|
|
when "props"
|
|
for tuple in *item[2,]
|
|
if ntype(tuple[1]) == "self"
|
|
insert statements, build.assign_one unpack tuple
|
|
else
|
|
insert properties, tuple
|
|
|
|
-- find constructor
|
|
local constructor
|
|
properties = for tuple in *properties
|
|
key = tuple[1]
|
|
if key[1] == "key_literal" and key[2] == constructor_name
|
|
constructor = tuple[2]
|
|
continue
|
|
else
|
|
tuple
|
|
|
|
parent_cls_name = NameProxy "parent"
|
|
base_name = NameProxy "base"
|
|
self_name = NameProxy "self"
|
|
cls_name = NameProxy "class"
|
|
|
|
unless constructor
|
|
constructor = build.fndef {
|
|
args: {{"..."}}
|
|
arrow: "fat"
|
|
body: {
|
|
build["if"] {
|
|
cond: parent_cls_name
|
|
then: {
|
|
build.chain { base: "super", {"call", {"..."}} }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
real_name = name or parent_assign and parent_assign[2][1]
|
|
real_name = switch ntype real_name
|
|
when "chain"
|
|
last = real_name[#real_name]
|
|
switch ntype last
|
|
when "dot"
|
|
{"string", '"', last[2]}
|
|
when "index"
|
|
last[2]
|
|
else
|
|
"nil"
|
|
when "nil"
|
|
"nil"
|
|
else
|
|
{"string", '"', real_name}
|
|
|
|
cls = build.table {
|
|
{"__init", constructor}
|
|
{"__base", base_name}
|
|
{"__name", real_name} -- "quote the string"
|
|
{"__parent", parent_cls_name}
|
|
}
|
|
|
|
-- look up a name in the class object
|
|
class_lookup = build["if"] {
|
|
cond: {"exp", "val", "==", "nil", "and", parent_cls_name}
|
|
then: {
|
|
parent_cls_name\index"name"
|
|
}
|
|
}
|
|
insert class_lookup, {"else", {"val"}}
|
|
|
|
cls_mt = build.table {
|
|
{"__index", build.fndef {
|
|
args: {{"cls"}, {"name"}}
|
|
body: {
|
|
build.assign_one LocalName"val", build.chain {
|
|
base: "rawget", {"call", {base_name, "name"}}
|
|
}
|
|
class_lookup
|
|
}
|
|
}}
|
|
{"__call", build.fndef {
|
|
args: {{"cls"}, {"..."}}
|
|
body: {
|
|
build.assign_one self_name, build.chain {
|
|
base: "setmetatable"
|
|
{"call", {"{}", base_name}}
|
|
}
|
|
build.chain {
|
|
base: "cls.__init"
|
|
{"call", {self_name, "..."}}
|
|
}
|
|
self_name
|
|
}
|
|
}}
|
|
}
|
|
|
|
cls = build.chain {
|
|
base: "setmetatable"
|
|
{"call", {cls, cls_mt}}
|
|
}
|
|
|
|
value = nil
|
|
with build
|
|
out_body = {
|
|
Run =>
|
|
-- make sure we don't assign the class to a local inside the do
|
|
@put_name name if name
|
|
|
|
@set "super", (block, chain) ->
|
|
if chain
|
|
slice = [item for item in *chain[3,]]
|
|
new_chain = {"chain", parent_cls_name}
|
|
|
|
head = slice[1]
|
|
|
|
if head == nil
|
|
return parent_cls_name
|
|
|
|
switch head[1]
|
|
-- calling super, inject calling name and self into chain
|
|
when "call"
|
|
calling_name = block\get"current_block"
|
|
slice[1] = {"call", {"self", unpack head[2]}}
|
|
|
|
if ntype(calling_name) == "key_literal"
|
|
insert new_chain, {"dot", calling_name[2]}
|
|
else
|
|
insert new_chain, {"index", calling_name}
|
|
|
|
-- colon call on super, replace class with self as first arg
|
|
when "colon"
|
|
call = head[3]
|
|
insert new_chain, {"dot", head[2]}
|
|
slice[1] = { "call", { "self", unpack call[2] } }
|
|
|
|
insert new_chain, item for item in *slice
|
|
|
|
new_chain
|
|
else
|
|
parent_cls_name
|
|
|
|
{"declare_glob", "*"}
|
|
|
|
.assign_one parent_cls_name, parent_val == "" and "nil" or parent_val
|
|
.assign_one base_name, {"table", properties}
|
|
.assign_one base_name\chain"__index", base_name
|
|
|
|
.if {
|
|
cond: parent_cls_name
|
|
then: {
|
|
.chain {
|
|
base: "setmetatable"
|
|
{"call", {
|
|
base_name,
|
|
.chain { base: parent_cls_name, {"dot", "__base"}}
|
|
}}
|
|
}
|
|
}
|
|
}
|
|
|
|
.assign_one cls_name, cls
|
|
.assign_one base_name\chain"__class", cls_name
|
|
|
|
.group if #statements > 0 then {
|
|
.assign_one LocalName"self", cls_name
|
|
.group statements
|
|
}
|
|
|
|
-- run the inherited callback
|
|
.if {
|
|
cond: {"exp",
|
|
parent_cls_name, "and", parent_cls_name\chain "__inherited"
|
|
}
|
|
then: {
|
|
parent_cls_name\chain "__inherited", {"call", {
|
|
parent_cls_name, cls_name
|
|
}}
|
|
}
|
|
}
|
|
|
|
.group if name then {
|
|
.assign_one name, cls_name
|
|
}
|
|
|
|
if ret
|
|
ret cls_name
|
|
}
|
|
|
|
value = .group {
|
|
.group if ntype(name) == "value" then {
|
|
.declare names: {name}
|
|
}
|
|
|
|
.do out_body
|
|
}
|
|
|
|
value
|
|
}
|
|
|
|
class Accumulator
|
|
body_idx: { for: 4, while: 3, foreach: 4 }
|
|
|
|
new: =>
|
|
@accum_name = NameProxy "accum"
|
|
@value_name = NameProxy "value"
|
|
@len_name = NameProxy "len"
|
|
|
|
-- wraps node and mutates body
|
|
convert: (node) =>
|
|
index = @body_idx[ntype node]
|
|
node[index] = @mutate_body node[index]
|
|
@wrap node
|
|
|
|
-- wrap the node into a block_exp
|
|
wrap: (node) =>
|
|
build.block_exp {
|
|
build.assign_one @accum_name, build.table!
|
|
build.assign_one @len_name, 1
|
|
node
|
|
@accum_name
|
|
}
|
|
|
|
-- mutates the body of a loop construct to save last value into accumulator
|
|
mutate_body: (body) =>
|
|
-- shortcut to write simpler code if body is a single expression
|
|
single_stm = is_singular body
|
|
val = if single_stm and types.is_value single_stm
|
|
body = {}
|
|
single_stm
|
|
else
|
|
body = apply_to_last body, (n) ->
|
|
if types.is_value n
|
|
build.assign_one @value_name, n
|
|
else
|
|
-- just ignore it
|
|
build.group {
|
|
{"declare", {@value_name}}
|
|
n
|
|
}
|
|
@value_name
|
|
|
|
update = {
|
|
build.assign_one @accum_name\index(@len_name), val
|
|
{"update", @len_name, "+=", 1}
|
|
}
|
|
|
|
insert body, build.group update
|
|
body
|
|
|
|
default_accumulator = (node) =>
|
|
Accumulator!\convert node
|
|
|
|
implicitly_return = (scope) ->
|
|
is_top = true
|
|
fn = (stm) ->
|
|
t = ntype stm
|
|
|
|
-- expand decorated
|
|
if t == "decorated"
|
|
stm = scope.transform.statement stm
|
|
t = ntype stm
|
|
|
|
if types.cascading[t]
|
|
is_top = false
|
|
scope.transform.statement stm, fn
|
|
elseif types.manual_return[t] or not types.is_value stm
|
|
-- remove blank return statement
|
|
if is_top and t == "return" and stm[2] == ""
|
|
NOOP
|
|
else
|
|
stm
|
|
else
|
|
if t == "comprehension" and not types.comprehension_has_value stm
|
|
stm
|
|
else
|
|
{"return", stm}
|
|
|
|
fn
|
|
|
|
Value = Transformer {
|
|
for: default_accumulator
|
|
while: default_accumulator
|
|
foreach: default_accumulator
|
|
|
|
do: (node) =>
|
|
build.block_exp node[2]
|
|
|
|
decorated: (node) =>
|
|
@transform.statement node
|
|
|
|
class: (node) =>
|
|
build.block_exp { node }
|
|
|
|
string: (node) =>
|
|
delim = node[2]
|
|
|
|
convert_part = (part) ->
|
|
if type(part) == "string" or part == nil
|
|
{"string", delim, part or ""}
|
|
else
|
|
build.chain { base: "tostring", {"call", {part[2]}} }
|
|
|
|
-- reduced to single item
|
|
if #node <= 3
|
|
return if type(node[3]) == "string"
|
|
node
|
|
else
|
|
convert_part node[3]
|
|
|
|
e = {"exp", convert_part node[3]}
|
|
|
|
for i=4, #node
|
|
insert e, ".."
|
|
insert e, convert_part node[i]
|
|
e
|
|
|
|
comprehension: (node) =>
|
|
a = Accumulator!
|
|
node = @transform.statement node, (exp) ->
|
|
a\mutate_body {exp}
|
|
a\wrap node
|
|
|
|
tblcomprehension: (node) =>
|
|
_, explist, clauses = unpack node
|
|
key_exp, value_exp = unpack explist
|
|
|
|
accum = NameProxy "tbl"
|
|
|
|
inner = if value_exp
|
|
dest = build.chain { base: accum, {"index", key_exp} }
|
|
{ build.assign_one dest, value_exp }
|
|
else
|
|
-- If we only have single expression then
|
|
-- unpack the result into key and value
|
|
key_name, val_name = NameProxy"key", NameProxy"val"
|
|
dest = build.chain { base: accum, {"index", key_name} }
|
|
{
|
|
build.assign names: {key_name, val_name}, values: {key_exp}
|
|
build.assign_one dest, val_name
|
|
}
|
|
|
|
build.block_exp {
|
|
build.assign_one accum, build.table!
|
|
construct_comprehension inner, clauses
|
|
accum
|
|
}
|
|
|
|
fndef: (node) =>
|
|
smart_node node
|
|
node.body = apply_to_last node.body, implicitly_return self
|
|
node.body = {
|
|
Run => @listen "varargs", -> -- capture event
|
|
unpack node.body
|
|
}
|
|
|
|
node
|
|
|
|
if: (node) => build.block_exp { node }
|
|
unless: (node) =>build.block_exp { node }
|
|
with: (node) => build.block_exp { node }
|
|
switch: (node) =>
|
|
build.block_exp { node }
|
|
|
|
-- pull out colon chain
|
|
chain: (node) =>
|
|
stub = node[#node]
|
|
|
|
-- escape lua keywords used in dot accessors
|
|
for i=3,#node
|
|
part = node[i]
|
|
if ntype(part) == "dot" and data.lua_keywords[part[2]]
|
|
node[i] = { "index", {"string", '"', part[2]} }
|
|
|
|
if ntype(node[2]) == "string"
|
|
-- add parens if callee is raw string
|
|
node[2] = {"parens", node[2] }
|
|
elseif type(stub) == "table" and stub[1] == "colon_stub"
|
|
-- convert colon stub into code
|
|
table.remove node, #node
|
|
|
|
base_name = NameProxy "base"
|
|
fn_name = NameProxy "fn"
|
|
|
|
is_super = node[2] == "super"
|
|
@transform.value build.block_exp {
|
|
build.assign {
|
|
names: {base_name}
|
|
values: {node}
|
|
}
|
|
|
|
build.assign {
|
|
names: {fn_name}
|
|
values: {
|
|
build.chain { base: base_name, {"dot", stub[2]} }
|
|
}
|
|
}
|
|
|
|
build.fndef {
|
|
args: {{"..."}}
|
|
body: {
|
|
build.chain {
|
|
base: fn_name, {"call", {is_super and "self" or base_name, "..."}}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
block_exp: (node) =>
|
|
_, body = unpack node
|
|
|
|
fn = nil
|
|
arg_list = {}
|
|
|
|
fn = smart_node build.fndef body: {
|
|
Run =>
|
|
@listen "varargs", ->
|
|
insert arg_list, "..."
|
|
insert fn.args, {"..."}
|
|
@unlisten "varargs"
|
|
|
|
unpack body
|
|
}
|
|
|
|
build.chain { base: {"parens", fn}, {"call", arg_list} }
|
|
}
|
|
|
|
{ :Statement, :Value, :Run }
|