refactor apart transfromers

This commit is contained in:
leaf corcoran 2015-12-05 23:07:42 -08:00
parent 53ef9fe504
commit 76af9964fe
20 changed files with 2760 additions and 2576 deletions

View File

@ -65,6 +65,9 @@ do
end
end,
lint_check_unused = function(self)
do
return
end
if not (self.lint_unused_names and next(self.lint_unused_names)) then
return
end

View File

@ -105,6 +105,7 @@ class LinterBlock extends Block
@parent\lint_mark_used name
lint_check_unused: =>
do return
return unless @lint_unused_names and next @lint_unused_names
names_by_position = {}

View File

@ -27,10 +27,10 @@ Num = Space * (Num / function(v)
v
}
end)
local Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, flatten_string_chain, wrap_decorator, check_lua_string, self_assign
local Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign
do
local _obj_0 = require("moonscript.parse.util")
Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, flatten_string_chain, wrap_decorator, check_lua_string, self_assign = _obj_0.Indent, _obj_0.Cut, _obj_0.ensure, _obj_0.extract_line, _obj_0.mark, _obj_0.pos, _obj_0.flatten_or_mark, _obj_0.is_assignable, _obj_0.check_assignable, _obj_0.format_assign, _obj_0.format_single_assign, _obj_0.sym, _obj_0.symx, _obj_0.simple_string, _obj_0.wrap_func_arg, _obj_0.join_chain, _obj_0.flatten_string_chain, _obj_0.wrap_decorator, _obj_0.check_lua_string, _obj_0.self_assign
Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign = _obj_0.Indent, _obj_0.Cut, _obj_0.ensure, _obj_0.extract_line, _obj_0.mark, _obj_0.pos, _obj_0.flatten_or_mark, _obj_0.is_assignable, _obj_0.check_assignable, _obj_0.format_assign, _obj_0.format_single_assign, _obj_0.sym, _obj_0.symx, _obj_0.simple_string, _obj_0.wrap_func_arg, _obj_0.join_chain, _obj_0.wrap_decorator, _obj_0.check_lua_string, _obj_0.self_assign
end
local build_grammar = wrap_env(debug_grammar, function(root)
local _indent = Stack(0)

View File

@ -29,7 +29,7 @@ Num = Space * (Num / (v) -> {"number", v})
:Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark,
:is_assignable, :check_assignable, :format_assign, :format_single_assign,
:sym, :symx, :simple_string, :wrap_func_arg, :join_chain,
:flatten_string_chain, :wrap_decorator, :check_lua_string, :self_assign
:wrap_decorator, :check_lua_string, :self_assign
} = require "moonscript.parse.util"

View File

@ -197,17 +197,6 @@ join_chain = function(callee, args)
args
}
end
local flatten_string_chain
flatten_string_chain = function(str, chain, args)
if not (chain) then
return str
end
return flatten_chain({
"chain",
str,
unpack(chain)
}, args)
end
local wrap_decorator
wrap_decorator = function(stm, dec)
if not (dec) then
@ -254,7 +243,6 @@ return {
simple_string = simple_string,
wrap_func_arg = wrap_func_arg,
join_chain = join_chain,
flatten_string_chain = flatten_string_chain,
wrap_decorator = wrap_decorator,
check_lua_string = check_lua_string,
self_assign = self_assign

View File

@ -139,10 +139,6 @@ join_chain = (callee, args) ->
{"chain", callee, args}
flatten_string_chain = (str, chain, args) ->
return str unless chain
flatten_chain {"chain", str, unpack chain}, args
-- constructor for decorator node
wrap_decorator = (stm, dec) ->
return stm unless dec
@ -157,5 +153,5 @@ self_assign = (name, pos) ->
{ :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark,
:is_assignable, :check_assignable, :format_assign, :format_single_assign,
:sym, :symx, :simple_string, :wrap_func_arg, :join_chain,
:flatten_string_chain, :wrap_decorator, :check_lua_string, :self_assign }
:sym, :symx, :simple_string, :wrap_func_arg, :join_chain, :wrap_decorator,
:check_lua_string, :self_assign }

File diff suppressed because it is too large Load Diff

View File

@ -1,977 +1,5 @@
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"
import Run, transform_last_stm, last_stm, chain_is_stub from require "moonscript.transform.statements"
import Transformer from require "moonscript.transform.transformer"
destructure = require "moonscript.transform.destructure"
NOOP = {"noop"}
local *
-- 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]
if ntype(name) == "ref"
insert out, name
elseif type(name) == "string"
-- TODO: don't use string literal as ref
insert out, name
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
last = last_stm body
enclose_lines = types.terminating[last and ntype(last)]
@put_name continue_name, nil
@splice (lines) ->
lines = {"do", {lines}} if enclose_lines
{
{"assign", {continue_name}, {"false"}}
{"repeat", "true", {
lines
{"assign", {continue_name}, {"true"}}
}}
{"if", {"not", continue_name}, {
{"break"}
}}
}
}
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) =>
transform_last_stm body, implicitly_return @
return: (node) =>
ret_val = node[2]
ret_val_type = ntype ret_val
if ret_val_type == "explist" and #ret_val == 2
ret_val = ret_val[2]
ret_val_type = ntype ret_val
if types.cascading[ret_val_type]
return implicitly_return(@) ret_val
-- flatten things that create block exp
if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension"
ret_val = Value\transform_once @, ret_val
if ntype(ret_val) == "block_exp"
return build.group transform_last_stm ret_val[2], (stm)->
{"return", stm}
node[2] = ret_val
node
declare_glob: (node) =>
names = extract_declarations @
if node[2] == "^"
names = for name in *names
continue unless name[2]\match "^%u"
name
{"declare", names}
assign: (node) =>
names, values = unpack node, 2
num_values = #values
num_names = #values
-- special code simplifications for single assigns
if num_names == 1 and num_values == 1
first_value = values[1]
first_name = names[1]
first_type = ntype first_value
-- reduce colon stub chain to block exp
if first_type == "chain"
first_value = Value\transform_once @, first_value
first_type = ntype first_value
switch ntype first_value
when "block_exp"
block_body = first_value[2]
idx = #block_body
block_body[idx] = build.assign_one first_name, block_body[idx]
return build.group {
{"declare", {first_name}}
{"do", block_body}
}
when "comprehension", "tblcomprehension", "foreach", "for", "while"
return build.assign_one first_name, Value\transform_once @, first_value
else
values[1] = first_value
-- bubble cascading assigns
transformed = if num_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
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
-- pull out vawlues and assign them after the export
build.group {
{ "export", node[2] }
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
table_values = for name in *names
dest_name = if ntype(name) == "colon"
name[2]
else
name
{{"key_literal", name}, dest_name}
dest = { "table", table_values }
{ "assign", {dest}, {source}, [-1]: node[-1] }
comprehension: (node, action) =>
_, exp, clauses = unpack node
action = action or (exp) -> {exp}
construct_comprehension action(exp), clauses
do: (node, ret) =>
node[2] = transform_last_stm 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 ntype(name) == "ref"]
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'] = transform_last_stm node['then'], ret
for i = 4, #node
case = node[i]
body_idx = #node[i]
case[body_idx] = transform_last_stm case[body_idx], ret
node
with: (node, ret) =>
exp, block = unpack node, 2
copy_scope = true
local scope_name, named_assign
if last = last_stm block
ret = false if types.terminating[ntype(last)]
if ntype(exp) == "assign"
names, values = unpack exp, 2
first_name = names[1]
if ntype(first_name) == "ref"
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"
out = build.do {
copy_scope and build.assign_one(scope_name, exp) or NOOP
named_assign or NOOP
Run => @set "scope_var", scope_name
unpack block
}
if ret
table.insert out[2], ret scope_name
out
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
list_name = list if @is_local list
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 = transform_last_stm 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
parent_val = nil if parent_val == ""
-- 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 = if parent_val
build.fndef {
args: {{"..."}}
arrow: "fat"
body: {
build.chain { base: "super", {"call", {"..."}} }
}
}
else
build.fndef!
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
name_t = type real_name
-- TODO: don't use string literal as ref
flattened_name = if name_t == "string"
real_name
elseif name_t == "table" and real_name[1] == "ref"
real_name[2]
else
error "don't know how to extract name from #{name_t}"
{"string", '"', flattened_name}
cls = build.table {
{"__init", constructor}
{"__base", base_name}
{"__name", real_name} -- "quote the string"
parent_val and {"__parent", parent_cls_name} or nil
}
-- looking up a name in the class object
class_index = if parent_val
class_lookup = build["if"] {
cond: { "exp", {"ref", "val"}, "==", "nil" }
then: {
build.assign_one LocalName"parent", build.chain {
base: "rawget"
{
"call", {
{"ref", "cls"}
{"string", '"', "__parent"}
}
}
}
build.if {
cond: LocalName "parent"
then: {
build.chain {
base: LocalName "parent"
{"index", "name"}
}
}
}
}
}
insert class_lookup, {"else", {"val"}}
build.fndef {
args: {{"cls"}, {"name"}}
body: {
build.assign_one LocalName"val", build.chain {
base: "rawget", {"call", {base_name, {"ref", "name"}}}
}
class_lookup
}
}
else
base_name
cls_mt = build.table {
{"__index", class_index}
{"__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) ->
relative_parent = {
"chain",
cls_name
{"dot", "__parent"}
}
return relative_parent unless chain
chain_tail = { unpack chain, 3 }
head = chain_tail[1]
if head == nil
return relative_parent
new_chain = relative_parent
switch head[1]
-- calling super, inject calling name and self into chain
when "call"
calling_name = block\get"current_block"
assert calling_name, "missing calling name"
chain_tail[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 = chain_tail[2]
-- calling chain tail
if call and call[1] == "call"
chain_tail[1] = {
"dot"
head[2]
}
chain_tail[2] = {
"call"
{
"self"
unpack call[2]
}
}
insert new_chain, item for item in *chain_tail
new_chain
{"declare", { cls_name }}
{"declare_glob", "*"}
parent_val and .assign_one(parent_cls_name, parent_val) or NOOP
.assign_one base_name, {"table", properties}
.assign_one base_name\chain"__index", base_name
parent_val and .chain({
base: "setmetatable"
{"call", {
base_name,
.chain { base: parent_cls_name, {"dot", "__base"}}
}}
}) or NOOP
.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
parent_val and .if({
cond: {"exp", parent_cls_name\chain "__inherited" }
then: {
parent_cls_name\chain "__inherited", {"call", {
parent_cls_name, cls_name
}}
}
}) or NOOP
.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
{
Statement: require "moonscript.transform.statement"
Value: require "moonscript.transform.value"
}
class Accumulator
body_idx: { for: 4, while: 3, foreach: 4 }
new: (accum_name) =>
@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, group_type="block_exp") =>
build[group_type] {
build.assign_one @accum_name, build.table!
build.assign_one @len_name, 1
node
group_type == "block_exp" and @accum_name or NOOP
}
-- 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 = transform_last_stm 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 NameProxy.index(@accum_name, @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 = transform_last_stm 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) =>
-- escape lua keywords used in dot accessors
for i=2,#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] }
if chain_is_stub node
base_name = NameProxy "base"
fn_name = NameProxy "fn"
colon = table.remove node
is_super = ntype(node[2]) == "ref" and node[2][2] == "super"
build.block_exp {
build.assign {
names: {base_name}
values: {node}
}
build.assign {
names: {fn_name}
values: {
build.chain { base: base_name, {"dot", colon[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 }

View File

@ -0,0 +1,110 @@
local types = require("moonscript.types")
local build, ntype, NOOP
build, ntype, NOOP = types.build, types.ntype, types.NOOP
local NameProxy
NameProxy = require("moonscript.transform.names").NameProxy
local insert
insert = table.insert
local is_singular
is_singular = function(body)
if #body ~= 1 then
return false
end
if "group" == ntype(body) then
return is_singular(body[2])
else
return body[1]
end
end
local transform_last_stm
transform_last_stm = require("moonscript.transform.statements").transform_last_stm
local Accumulator
do
local _class_0
local _base_0 = {
body_idx = {
["for"] = 4,
["while"] = 3,
foreach = 4
},
convert = function(self, node)
local index = self.body_idx[ntype(node)]
node[index] = self:mutate_body(node[index])
return self:wrap(node)
end,
wrap = function(self, node, group_type)
if group_type == nil then
group_type = "block_exp"
end
return build[group_type]({
build.assign_one(self.accum_name, build.table()),
build.assign_one(self.len_name, 1),
node,
group_type == "block_exp" and self.accum_name or NOOP
})
end,
mutate_body = function(self, body)
local single_stm = is_singular(body)
local val
if single_stm and types.is_value(single_stm) then
body = { }
val = single_stm
else
body = transform_last_stm(body, function(n)
if types.is_value(n) then
return build.assign_one(self.value_name, n)
else
return build.group({
{
"declare",
{
self.value_name
}
},
n
})
end
end)
val = self.value_name
end
local update = {
build.assign_one(NameProxy.index(self.accum_name, self.len_name), val),
{
"update",
self.len_name,
"+=",
1
}
}
insert(body, build.group(update))
return body
end
}
_base_0.__index = _base_0
_class_0 = setmetatable({
__init = function(self, accum_name)
self.accum_name = NameProxy("accum")
self.value_name = NameProxy("value")
self.len_name = NameProxy("len")
end,
__base = _base_0,
__name = "Accumulator"
}, {
__index = _base_0,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
_base_0.__class = _class_0
Accumulator = _class_0
end
local default_accumulator
default_accumulator = function(self, node)
return Accumulator():convert(node)
end
return {
Accumulator = Accumulator,
default_accumulator = default_accumulator
}

View File

@ -0,0 +1,71 @@
types = require "moonscript.types"
import build, ntype, NOOP from types
import NameProxy from require "moonscript.transform.names"
import insert from table
-- is a body a single expression/statement
is_singular = (body) ->
return false if #body != 1
if "group" == ntype body
is_singular body[2]
else
body[1]
import transform_last_stm from require "moonscript.transform.statements"
class Accumulator
body_idx: { for: 4, while: 3, foreach: 4 }
new: (accum_name) =>
@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, group_type="block_exp") =>
build[group_type] {
build.assign_one @accum_name, build.table!
build.assign_one @len_name, 1
node
group_type == "block_exp" and @accum_name or NOOP
}
-- 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 = transform_last_stm 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 NameProxy.index(@accum_name, @len_name), val
{"update", @len_name, "+=", 1}
}
insert body, build.group update
body
default_accumulator = (node) =>
Accumulator!\convert node
{ :Accumulator, :default_accumulator }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,720 @@
import Transformer from require "moonscript.transform.transformer"
import NameProxy, LocalName from require "moonscript.transform.names"
import Run, transform_last_stm, implicitly_return, construct_comprehension,
last_stm from require "moonscript.transform.statements"
types = require "moonscript.types"
import build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP
from types
import insert from table
destructure = require "moonscript.transform.destructure"
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
last = last_stm body
enclose_lines = types.terminating[last and ntype(last)]
@put_name continue_name, nil
@splice (lines) ->
lines = {"do", {lines}} if enclose_lines
{
{"assign", {continue_name}, {"false"}}
{"repeat", "true", {
lines
{"assign", {continue_name}, {"true"}}
}}
{"if", {"not", continue_name}, {
{"break"}
}}
}
}
-- 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]
if ntype(name) == "ref"
insert out, name
elseif type(name) == "string"
-- TODO: don't use string literal as ref
insert out, name
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
Transformer {
transform: (tuple) =>
{_, node, fn} = tuple
fn node
root_stms: (body) =>
transform_last_stm body, implicitly_return @
return: (node) =>
ret_val = node[2]
ret_val_type = ntype ret_val
if ret_val_type == "explist" and #ret_val == 2
ret_val = ret_val[2]
ret_val_type = ntype ret_val
if types.cascading[ret_val_type]
return implicitly_return(@) ret_val
-- flatten things that create block exp
if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension"
-- TODO: clean this up
Value = require "moonscript.transform.value"
ret_val = Value\transform_once @, ret_val
if ntype(ret_val) == "block_exp"
return build.group transform_last_stm ret_val[2], (stm)->
{"return", stm}
node[2] = ret_val
node
declare_glob: (node) =>
names = extract_declarations @
if node[2] == "^"
names = for name in *names
continue unless name[2]\match "^%u"
name
{"declare", names}
assign: (node) =>
names, values = unpack node, 2
num_values = #values
num_names = #values
-- special code simplifications for single assigns
if num_names == 1 and num_values == 1
first_value = values[1]
first_name = names[1]
first_type = ntype first_value
-- reduce colon stub chain to block exp
if first_type == "chain"
-- TODO: clean this up
Value = require "moonscript.transform.value"
first_value = Value\transform_once @, first_value
first_type = ntype first_value
switch ntype first_value
when "block_exp"
block_body = first_value[2]
idx = #block_body
block_body[idx] = build.assign_one first_name, block_body[idx]
return build.group {
{"declare", {first_name}}
{"do", block_body}
}
when "comprehension", "tblcomprehension", "foreach", "for", "while"
-- TODO: clean this up
Value = require "moonscript.transform.value"
return build.assign_one first_name, Value\transform_once @, first_value
else
values[1] = first_value
-- bubble cascading assigns
transformed = if num_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 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
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
-- pull out vawlues and assign them after the export
build.group {
{ "export", node[2] }
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
table_values = for name in *names
dest_name = if ntype(name) == "colon"
name[2]
else
name
{{"key_literal", name}, dest_name}
dest = { "table", table_values }
{ "assign", {dest}, {source}, [-1]: node[-1] }
comprehension: (node, action) =>
_, exp, clauses = unpack node
action = action or (exp) -> {exp}
construct_comprehension action(exp), clauses
do: (node, ret) =>
node[2] = transform_last_stm 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 ntype(name) == "ref"]
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'] = transform_last_stm node['then'], ret
for i = 4, #node
case = node[i]
body_idx = #node[i]
case[body_idx] = transform_last_stm case[body_idx], ret
node
with: (node, ret) =>
exp, block = unpack node, 2
copy_scope = true
local scope_name, named_assign
if last = last_stm block
ret = false if types.terminating[ntype(last)]
if ntype(exp) == "assign"
names, values = unpack exp, 2
first_name = names[1]
if ntype(first_name) == "ref"
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"
out = build.do {
copy_scope and build.assign_one(scope_name, exp) or NOOP
named_assign or NOOP
Run => @set "scope_var", scope_name
unpack block
}
if ret
table.insert out[2], ret scope_name
out
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
list_name = list if @is_local list
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 = transform_last_stm 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
parent_val = nil if parent_val == ""
-- 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 = if parent_val
build.fndef {
args: {{"..."}}
arrow: "fat"
body: {
build.chain { base: "super", {"call", {"..."}} }
}
}
else
build.fndef!
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
name_t = type real_name
-- TODO: don't use string literal as ref
flattened_name = if name_t == "string"
real_name
elseif name_t == "table" and real_name[1] == "ref"
real_name[2]
else
error "don't know how to extract name from #{name_t}"
{"string", '"', flattened_name}
cls = build.table {
{"__init", constructor}
{"__base", base_name}
{"__name", real_name} -- "quote the string"
parent_val and {"__parent", parent_cls_name} or nil
}
-- looking up a name in the class object
class_index = if parent_val
class_lookup = build["if"] {
cond: { "exp", {"ref", "val"}, "==", "nil" }
then: {
build.assign_one LocalName"parent", build.chain {
base: "rawget"
{
"call", {
{"ref", "cls"}
{"string", '"', "__parent"}
}
}
}
build.if {
cond: LocalName "parent"
then: {
build.chain {
base: LocalName "parent"
{"index", "name"}
}
}
}
}
}
insert class_lookup, {"else", {"val"}}
build.fndef {
args: {{"cls"}, {"name"}}
body: {
build.assign_one LocalName"val", build.chain {
base: "rawget", {"call", {base_name, {"ref", "name"}}}
}
class_lookup
}
}
else
base_name
cls_mt = build.table {
{"__index", class_index}
{"__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) ->
relative_parent = {
"chain",
cls_name
{"dot", "__parent"}
}
return relative_parent unless chain
chain_tail = { unpack chain, 3 }
head = chain_tail[1]
if head == nil
return relative_parent
new_chain = relative_parent
switch head[1]
-- calling super, inject calling name and self into chain
when "call"
calling_name = block\get"current_block"
assert calling_name, "missing calling name"
chain_tail[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 = chain_tail[2]
-- calling chain tail
if call and call[1] == "call"
chain_tail[1] = {
"dot"
head[2]
}
chain_tail[2] = {
"call"
{
"self"
unpack call[2]
}
}
insert new_chain, item for item in *chain_tail
new_chain
{"declare", { cls_name }}
{"declare_glob", "*"}
parent_val and .assign_one(parent_cls_name, parent_val) or NOOP
.assign_one base_name, {"table", properties}
.assign_one base_name\chain"__index", base_name
parent_val and .chain({
base: "setmetatable"
{"call", {
base_name,
.chain { base: parent_cls_name, {"dot", "__base"}}
}}
}) or NOOP
.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
parent_val and .if({
cond: {"exp", parent_cls_name\chain "__inherited" }
then: {
parent_cls_name\chain "__inherited", {"call", {
parent_cls_name, cls_name
}}
}
}) or NOOP
.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
}

View File

@ -1,8 +1,6 @@
local ntype, mtype
do
local _obj_0 = require("moonscript.types")
ntype, mtype = _obj_0.ntype, _obj_0.mtype
end
local types = require("moonscript.types")
local ntype, mtype, is_value, NOOP
ntype, mtype, is_value, NOOP = types.ntype, types.mtype, types.is_value, types.NOOP
local Run
do
local _class_0
@ -74,9 +72,88 @@ chain_is_stub = function(chain)
local stub = chain[#chain]
return stub and ntype(stub) == "colon"
end
local implicitly_return
implicitly_return = function(scope)
local is_top = true
local fn
fn = function(stm)
local t = ntype(stm)
if t == "decorated" then
stm = scope.transform.statement(stm)
t = ntype(stm)
end
if types.cascading[t] then
is_top = false
return scope.transform.statement(stm, fn)
elseif types.manual_return[t] or not is_value(stm) then
if is_top and t == "return" and stm[2] == "" then
return NOOP
else
return stm
end
else
if t == "comprehension" and not types.comprehension_has_value(stm) then
return stm
else
return {
"return",
stm
}
end
end
end
return fn
end
local reversed
reversed = require("moonscript.util").reversed
local construct_comprehension
construct_comprehension = function(inner, clauses)
local current_stms = inner
for _, clause in reversed(clauses) do
local t = clause[1]
local _exp_0 = t
if "for" == _exp_0 then
local name, bounds
_, name, bounds = clause[1], clause[2], clause[3]
current_stms = {
"for",
name,
bounds,
current_stms
}
elseif "foreach" == _exp_0 then
local names, iter
_, names, iter = clause[1], clause[2], clause[3]
current_stms = {
"foreach",
names,
{
iter
},
current_stms
}
elseif "when" == _exp_0 then
local cond
_, cond = clause[1], clause[2]
current_stms = {
"if",
cond,
current_stms
}
else
current_stms = error("Unknown comprehension clause: " .. t)
end
current_stms = {
current_stms
}
end
return current_stms[1]
end
return {
Run = Run,
last_stm = last_stm,
transform_last_stm = transform_last_stm,
chain_is_stub = chain_is_stub
chain_is_stub = chain_is_stub,
implicitly_return = implicitly_return,
construct_comprehension = construct_comprehension
}

View File

@ -1,5 +1,6 @@
import ntype, mtype from require "moonscript.types"
types = require "moonscript.types"
import ntype, mtype, is_value, NOOP from types
-- A Run is a special statement node that lets a function run and mutate the
-- state of the compiler
@ -44,5 +45,56 @@ chain_is_stub = (chain) ->
stub = chain[#chain]
stub and ntype(stub) == "colon"
{:Run, :last_stm, :transform_last_stm, :chain_is_stub}
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 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
-- TODO: reversed unecessary
import reversed from require "moonscript.util"
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]
{:Run, :last_stm, :transform_last_stm, :chain_is_stub, :implicitly_return,
:construct_comprehension}

View File

@ -0,0 +1,74 @@
local ntype
ntype = require("moonscript.types").ntype
local Transformer
do
local _class_0
local _base_0 = {
transform_once = function(self, scope, node, ...)
if self.seen_nodes[node] then
return node
end
self.seen_nodes[node] = true
local transformer = self.transformers[ntype(node)]
if transformer then
return transformer(scope, node, ...) or node
else
return node
end
end,
transform = function(self, scope, node, ...)
if self.seen_nodes[node] then
return node
end
self.seen_nodes[node] = true
while true do
local transformer = self.transformers[ntype(node)]
local res
if transformer then
res = transformer(scope, node, ...) or node
else
res = node
end
if res == node then
return node
end
node = res
end
return node
end,
bind = function(self, scope)
return function(...)
return self:transform(scope, ...)
end
end,
__call = function(self, ...)
return self:transform(...)
end,
can_transform = function(self, node)
return self.transformers[ntype(node)] ~= nil
end
}
_base_0.__index = _base_0
_class_0 = setmetatable({
__init = function(self, transformers)
self.transformers = transformers
self.seen_nodes = setmetatable({ }, {
__mode = "k"
})
end,
__base = _base_0,
__name = "Transformer"
}, {
__index = _base_0,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
_base_0.__class = _class_0
Transformer = _class_0
end
return {
Transformer = Transformer
}

View File

@ -0,0 +1,42 @@
import ntype from require "moonscript.types"
class Transformer
new: (@transformers) =>
@seen_nodes = setmetatable {}, __mode: "k"
transform_once: (scope, node, ...) =>
return node if @seen_nodes[node]
@seen_nodes[node] = true
transformer = @transformers[ntype node]
if transformer
transformer(scope, node, ...) or node
else
node
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
{ :Transformer }

View File

@ -0,0 +1,261 @@
local Transformer
Transformer = require("moonscript.transform.transformer").Transformer
local build, ntype, smart_node
do
local _obj_0 = require("moonscript.types")
build, ntype, smart_node = _obj_0.build, _obj_0.ntype, _obj_0.smart_node
end
local NameProxy
NameProxy = require("moonscript.transform.names").NameProxy
local Accumulator, default_accumulator
do
local _obj_0 = require("moonscript.transform.accumulator")
Accumulator, default_accumulator = _obj_0.Accumulator, _obj_0.default_accumulator
end
local lua_keywords
lua_keywords = require("moonscript.data").lua_keywords
local Run, transform_last_stm, implicitly_return, chain_is_stub, construct_comprehension
do
local _obj_0 = require("moonscript.transform.statements")
Run, transform_last_stm, implicitly_return, chain_is_stub, construct_comprehension = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.chain_is_stub, _obj_0.construct_comprehension
end
local insert
insert = table.insert
return Transformer({
["for"] = default_accumulator,
["while"] = default_accumulator,
foreach = default_accumulator,
["do"] = function(self, node)
return build.block_exp(node[2])
end,
decorated = function(self, node)
return self.transform.statement(node)
end,
class = function(self, node)
return build.block_exp({
node
})
end,
string = function(self, node)
local delim = node[2]
local convert_part
convert_part = function(part)
if type(part) == "string" or part == nil then
return {
"string",
delim,
part or ""
}
else
return build.chain({
base = "tostring",
{
"call",
{
part[2]
}
}
})
end
end
if #node <= 3 then
if type(node[3]) == "string" then
return node
else
return convert_part(node[3])
end
end
local e = {
"exp",
convert_part(node[3])
}
for i = 4, #node do
insert(e, "..")
insert(e, convert_part(node[i]))
end
return e
end,
comprehension = function(self, node)
local a = Accumulator()
node = self.transform.statement(node, function(exp)
return a:mutate_body({
exp
})
end)
return a:wrap(node)
end,
tblcomprehension = function(self, node)
local _, explist, clauses = unpack(node)
local key_exp, value_exp = unpack(explist)
local accum = NameProxy("tbl")
local inner
if value_exp then
local dest = build.chain({
base = accum,
{
"index",
key_exp
}
})
inner = {
build.assign_one(dest, value_exp)
}
else
local key_name, val_name = NameProxy("key"), NameProxy("val")
local dest = build.chain({
base = accum,
{
"index",
key_name
}
})
inner = {
build.assign({
names = {
key_name,
val_name
},
values = {
key_exp
}
}),
build.assign_one(dest, val_name)
}
end
return build.block_exp({
build.assign_one(accum, build.table()),
construct_comprehension(inner, clauses),
accum
})
end,
fndef = function(self, node)
smart_node(node)
node.body = transform_last_stm(node.body, implicitly_return(self))
node.body = {
Run(function(self)
return self:listen("varargs", function() end)
end),
unpack(node.body)
}
return node
end,
["if"] = function(self, node)
return build.block_exp({
node
})
end,
unless = function(self, node)
return build.block_exp({
node
})
end,
with = function(self, node)
return build.block_exp({
node
})
end,
switch = function(self, node)
return build.block_exp({
node
})
end,
chain = function(self, node)
for i = 2, #node do
local part = node[i]
if ntype(part) == "dot" and lua_keywords[part[2]] then
node[i] = {
"index",
{
"string",
'"',
part[2]
}
}
end
end
if ntype(node[2]) == "string" then
node[2] = {
"parens",
node[2]
}
end
if chain_is_stub(node) then
local base_name = NameProxy("base")
local fn_name = NameProxy("fn")
local colon = table.remove(node)
local is_super = ntype(node[2]) == "ref" and node[2][2] == "super"
return build.block_exp({
build.assign({
names = {
base_name
},
values = {
node
}
}),
build.assign({
names = {
fn_name
},
values = {
build.chain({
base = base_name,
{
"dot",
colon[2]
}
})
}
}),
build.fndef({
args = {
{
"..."
}
},
body = {
build.chain({
base = fn_name,
{
"call",
{
is_super and "self" or base_name,
"..."
}
}
})
}
})
})
end
end,
block_exp = function(self, node)
local _, body = unpack(node)
local fn = nil
local arg_list = { }
fn = smart_node(build.fndef({
body = {
Run(function(self)
return self:listen("varargs", function()
insert(arg_list, "...")
insert(fn.args, {
"..."
})
return self:unlisten("varargs")
end)
end),
unpack(body)
}
}))
return build.chain({
base = {
"parens",
fn
},
{
"call",
arg_list
}
})
end
})

View File

@ -0,0 +1,162 @@
import Transformer from require "moonscript.transform.transformer"
import build, ntype, smart_node from require "moonscript.types"
import NameProxy from require "moonscript.transform.names"
import Accumulator, default_accumulator from require "moonscript.transform.accumulator"
import lua_keywords from require "moonscript.data"
import Run, transform_last_stm, implicitly_return, chain_is_stub,
construct_comprehension from require "moonscript.transform.statements"
import insert from table
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 = transform_last_stm 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) =>
-- escape lua keywords used in dot accessors
for i=2,#node
part = node[i]
if ntype(part) == "dot" and 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] }
if chain_is_stub node
base_name = NameProxy "base"
fn_name = NameProxy "fn"
colon = table.remove node
is_super = ntype(node[2]) == "ref" and node[2][2] == "super"
build.block_exp {
build.assign {
names: {base_name}
values: {node}
}
build.assign {
names: {fn_name}
values: {
build.chain { base: base_name, {"dot", colon[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} }
}

View File

@ -308,6 +308,9 @@ local smart_node
smart_node = function(node)
return setmetatable(node, smart_node_mt[ntype(node)])
end
local NOOP = {
"noop"
}
return {
ntype = ntype,
smart_node = smart_node,
@ -320,5 +323,6 @@ return {
comprehension_has_value = comprehension_has_value,
value_can_be_statement = value_can_be_statement,
mtype = mtype,
terminating = terminating
terminating = terminating,
NOOP = NOOP
}

View File

@ -189,9 +189,12 @@ smart_node_mt = setmetatable {}, {
smart_node = (node) ->
setmetatable node, smart_node_mt[ntype node]
NOOP = {"noop"}
{
:ntype, :smart_node, :build, :is_value, :is_slice, :manual_return,
:cascading, :value_is_singular, :comprehension_has_value,
:value_can_be_statement, :mtype, :terminating
:NOOP
}