h = object.new
eb = { s | s.sub("\\", "\\\\") }
native_ops = ["_percent", "_plus", "_minus", "_forward", "_star", "_up"]
compare_ops = ["_less", "_greater", "_equal_equal", "_less_equal", "_greater_equal"]
h.callable? = { name |
true? my.env.get_type(name)
{
true? my.env.get_type(name) == :function
{ "true" }
{ "false" }
}
{
"object._is_callable(#{name})"
}
}
h.is_number? = { name |
true? my.env.get_type(name)
{
true? my.env.get_type(name) == :number
{ "true" }
{ "false" }
}
{
"_type(#{name}) == 'number'"
}
}
h.get_action = { res_var |
true? res_var == '_return_'
{ "return " }
{ true? res_var
{ "#{res_var} = " }
{ "_dummy_ = " }
}
}
h.invoke = { node, target, invoke_meth, var |
res = set_result var
w = my
target_var = null
true? target
{
res.out << target.out; target_var = target.var
}
args = (node.args || {[]}).map { n | w.process n }
avars = args.map({ a | a.var })
res.out << args.map({ a | a.out }).join("\n") << "\n"
res.out << invoke_meth target_var, node.method, avars, res.var
avars.each { v | w.env.unset v }
false? node.name == :invoke_function || { node.name == :invoke_self }
{ env.unset target_var }
res
}
h.invoke_local = { t_, name, args, res_var |
args = (["_self"] + args).join(", ")
temp = my.env[name]
action = get_action res_var
call_it = "#{action} #{temp}(#{args})\n"
nonmethod_error = "_error(exception:new(\"Tried to invoke non-method: '#{name}' (\" .. object.__type(#{temp}) .. \")\"))"
t = my.env.get_type temp
true? t
{
true? t == :function
{ call_it }
{
nonmethod_error
}
}
{
"
if #{callable? temp} then
#{call_it}
elseif #{temp} then
#{action} #{temp}(#{args})
else
#{nonmethod_error}
end
"
}
}
h.invoke_self = { t_, name, args, res_var |
args_array = (["_self"] + args).join(", ")
action = get_action res_var
call_it = "#{action} #{name}(#{args_array})\n"
"
if #{name} then
#{call_it}
else
#{invoke_method_helper '_self', name, args, res_var}
end
"
}
h.invoke_number_method = { target, name, args, res_var |
"
local _n = number:new(#{target})
#{invoke_method_helper '_n', name, args, res_var}"
}
standard_invoke = { target, name, args, res_var |
"if #{is_number? target} then
#{target} = number:new(#{target})
elseif #{callable? target} then
#{target} = brat_function:new(#{target})
end
#{invoke_method_helper target, name, args, res_var}"
}
h.invoke_method = { target, name, args, res_var |
true? (target.number? || { h.number? target })
{ invoke_number_method target, name, args, res_var }
{
true? args.length == 1 && { native_ops.include?(name) || { compare_ops.include? name }}
{
rhs = args[0]
"if #{is_number? target} and #{is_number? rhs} and number._unchanged('#{name}') then
#{op_helper target, name, rhs, res_var}
else
#{standard_invoke target, name, args, res_var}
end
"
}
{
standard_invoke target, name, args, res_var
}
}
}
h.invoke_index_get = { target, n_, args, res_var |
args_array = (['_self'] + args).join(", ")
action = get_action res_var
"
if #{callable? target} then
#{action} #{target}(#{args_array})
else
_error(exception:new(\"Tried to invoke non-method: '#{target}' (\" .. object.__type(#{target}) .. \")\"))
end
"
}
h.invoke_index_get_direct = { node, target, var |
res = set_result var
res_var = res.var
action = get_action res_var
target_var = target.var
arg = node.args.first
res.out << target.out
res.out << true? my.env.get_type(target_var) == :hash
{
"
if #{target_var}._unchanged('get') then
#{action} #{target_var}:get('#{escape_string arg.value}')
else
#{invoke_method(target_var, :get, ["string:new('#{escape_string arg.value}')"], res_var) }
end
"
}
{
"
if #{target_var}._lua_hash and #{target.var}._unchanged('get') then
#{action} #{target_var}:get('#{escape_string arg.value}')
else
#{invoke_method(target_var, :get, ["string:new('#{escape_string arg.value}')"], res_var) }
end
"
}
res
}
h.invoke_index_set_direct = { node, target, var |
res = set_result var
res_var = res.var
action = get_action res_var
target_var = target.var
index = node.args.first
value = process node.args.last
res.out << target.out
res.out << value.out
res.out << true? my.env.get_type(target_var) == :hash
{
"
if #{target_var}._unchanged('set') then
#{action} #{target_var}:set('#{escape_string index.value}', #{value.var})
else
#{invoke_method(target_var, :set, ["string:new('#{escape_string index.value}')", value.var], res_var) }
end
"
}
{
"
if #{target_var}._lua_hash and #{target.var}._unchanged('set') then
#{action} #{target_var}:set('#{escape_string index.value}', #{value.var})
else
#{invoke_method(target_var, :set, ["string:new('#{escape_string index.value}')", value.var], res_var) }
end
"
}
res
}
h.invoke_function = { target, n_, args, res_var |
args_array = (['_self'] + args).join(", ")
action = get_action res_var
"
#{action} #{target}(#{args_array})
"
}
h.invoke_method_helper = { target, name, args, res_var |
action = get_action res_var
true? args.empty?
{
"
local _m_#{target}_#{name} = #{target}.#{name}
if #{callable? "_m_#{target}_#{name}"} then
#{action} _m_#{target}_#{name}(#{target})
elseif _m_#{target}_#{name} ~= nil then
#{action} _m_#{target}_#{name}
elseif #{target}.no_undermethod then
#{action} #{target}:no_undermethod(string:new('#{eb unescape_identifier name}'))
else
_error(exception:method_error(#{target}, '#{name}'))
end
_m_#{target}_#{name} = nil
"
}
{
arg_list = ([target] + args).join(', ')
"
local _m_#{target}_#{name} = #{target}.#{name}
if #{callable? "_m_#{target}_#{name}"} then
#{action} _m_#{target}_#{name}(#{arg_list})
elseif _m_#{target}_#{name} ~= nil then
_error(exception:argument_error('function', 0, #{args.length - 1 }))
elseif #{target}.no_undermethod then
#{action} #{target}:no_undermethod(string:new('#{eb unescape_identifier name}'), #{args.join(', ')})
else
_error(exception:method_error(#{target}, '#{name}'))
end
_m_#{target}_#{name} = nil
"
}
}
h.op_helper = { lhs, op, rhs, res_var |
action = get_action res_var
true? native_ops.include?(op)
{
"#{action} #{lhs} #{unescape_op op} #{rhs}"
}
{
"if #{lhs} #{unescape_op op} #{rhs} then
#{action} object.__true
else
#{action} object.__false
end"
}
}
h.invoke_numbers = { lhs, op, rhs, res_var |
invoked = invoke_method lhs, op, [rhs], res_var
true? native_ops.include?(op) || { compare_ops.include?(op) }
{
"if number._unchanged('#{op}') then
#{op_helper lhs, op, rhs, res_var}
else
#{invoked}
end
"
}
{
invoked
}
}
# When LHS is number literal
h.invoke_number_lhs = { lhs, op, rhs, res_var |
"
if #{is_number? rhs} then
#{invoke_numbers lhs, op, rhs, res_var}
else
#{invoke_method lhs, op, [rhs], res_var}
end
"
}
h.invoke_number_rhs = { lhs, op, rhs, res_var |
"
if #{is_number? lhs} then
#{invoke_numbers lhs, op, rhs, res_var}
else
#{invoke_method lhs, op, [rhs], res_var}
end
"
}
h.get_a_value = { node, var = null |
res = set_result var
res.out << get_local_value(node.value, res.var)
res
}
h.get_local_value = { name, res_var |
temp = my.env[name]
action = get_action res_var
call_it = "#{action} #{temp}(_self)\n"
t = my.env.get_type temp
true? t
{
true? t == :function
{ call_it }
{
true? res_var
{ my.env.set_type res_var, t }
"#{action} #{temp}\n"
}
}
{
"
if #{callable? temp} then
#{call_it}
elseif #{temp} then
#{action} #{temp}
else
_error(exception:name_error(\"#{name}\"))
end
"
}
}
h.get_value = { name, res_var |
true? res_var == '_return_'
{ res_var = null }
action = true? res_var
{ "#{res_var} =" }
{ "return" }
call_it = "#{action} #{name}(_self)\n"
"
local _m_#{name}
if #{name} then
_m_#{name} = #{name}
else
_m_#{name} = _self[\"#{name}\"]
end
if #{callable? "_m_#{name}"} then
#{action} _m_#{name}(_self)
elseif _m_#{name} then
#{action} _m_#{name}
elseif _self.no_undermethod then
#{action} _self:no_undermethod(string:new('#{eb unescape_identifier name}'))
else
_error(exception:name_error(\"#{name}\"))
end
_m_#{name} = nil
"
}
h.get_method_local = { name, res_var |
action = get_action res_var
temp = my.env[name]
"
if #{temp} then
#{action} #{temp}
else
_error(exception:null_error(\"#{name}\", \"access it\"))
end
"
}
h.get_method_self = { name, res_var |
action = get_action res_var
"
if _self[\"#{name}\"] then
#{action} _self[\"#{name}\"]
else
_error(exception:null_error(\"#{name}\", \"access it\"))
end
"
}
h.get_method = { target, name, res_var |
action = get_action res_var
"
if #{target}[\"#{name}\"] then
#{action} #{target}[\"#{name}\"]
else
_error(exception:method_error(\"#{target}\", \"#{name|}\"))
end
"
}
# Converts function to do/end
h.convert_func_to_do_end = { node, var, arg_values = null |
w = my
my.env.new_scope
res = set_result var
res.out << "do\n"
action = get_action(res.var)
# Allow passing values to the function as arguments
true? arg_values && { node.args.any? }
{
false? arg_values.length == node.args.length
{
throw "Number of arguments and argument values in function do not match"
}
node.args.each_with_index { arg, index |
temp = w.env.new_var arg.id
res.out << "#{temp} = #{arg_values[index]}\n"
}
}
body = node.body.copy
last = body.pop
body_res = body.map({ n | w.process(n, '_dummy').out }).join("\n")
res.out << body_res
true? last
{
last_res = my.process last
res.out << last_res.out << "\n" << "#{action} #{last_res.var}\n"
}
{
true? body.empty?
{ res.out << "#{action} object:null()" }
}
my.env.pop_scope
res.out << "\nend\n"
res
}
h.process_if_branch = { node, var, condition_var = null |
true? (node.name == :function || {node.name == :liftable_function})
{ convert_func_to_do_end node, var, condition_var }
{ process node, var }
}
h.true_if = { node, ignored |
res = set_result
condition = process node.args[0]
action = get_action(res.var)
true_branch = true? node.args[1]
{ process_if_branch(node.args[1], res.var, [condition.var]) }
{ set_result(res.var, "object.__true") }
false_branch = true? node.args[2]
{ process_if_branch(node.args[2], res.var, [condition.var]) }
{ set_result(res.var, "object.__false") }
#TODO: yeck
call_cond = true? node.args[0].has_method?(:name) && {node.args[0].name == :number}
{ '' }
{ "if object._is_callable(#{condition.var}) then
#{condition.var} = #{condition.var}(_self)
end"
}
regular = invoke(node, null, ->invoke_self, res.var)
res.out << "
if (_self == object or _rawget(_self, 'true_question') == nil) and true_question == nil and object._unchanged('#{node.method}') then
-- yay if my var is #{res.var}
#{condition.out}
#{call_cond}
-- end condition
if object._is_true(#{condition.var}) then
#{true_branch.out}
#{action} #{true_branch.var}
else
#{false_branch.out}
#{action} #{false_branch.var}
end
-- end yay if
else
-- fallback if
#{regular.out}
#{action} #{regular.var}
-- end fallback if
end
"
res
}
h.false_if = { node, ignored |
res = set_result
condition = process node.args[0]
action = get_action(res.var)
false_branch = true? node.args[1]
{ process_if_branch(node.args[1], res.var, [condition.var]) }
{ set_result(res.var, "object.__true") }
true_branch = true? node.args[2]
{ process_if_branch(node.args[2], res.var, [condition.var]) }
{ set_result(res.var, "object.__false") }
#TODO: yeck
call_cond = true? node.args[0].has_method?(:name) && {node.args[0].name == :number}
{ '' }
{ "if object._is_callable(#{condition.var}) then
#{condition.var} = #{condition.var}(_self)
end"
}
regular = invoke(node, null, ->invoke_self, res.var)
res.out << "
if (_self == object or _rawget(_self, 'false_question') == nil) and false_question == nil and object._unchanged('false_question') then
-- yay if my var is #{res.var}
#{condition.out}
#{call_cond}
-- end condition
if object._is_true(#{condition.var}) then
#{true_branch.out}
#{action} #{true_branch.var}
else
#{false_branch.out}
#{action} #{false_branch.var}
end
-- end yay if
else
-- fallback false?
#{regular.out}
#{action} #{regular.var}
-- end fallback false?
end
"
res
}
h.null_if = { node, ignored |
res = set_result
condition = process node.args[0]
action = get_action(res.var)
true_branch = true? node.args[1]
{ process_if_branch(node.args[1], res.var, [condition.var]) }
{ set_result(res.var, "object.__true") }
false_branch = true? node.args[2]
{ process_if_branch(node.args[2], res.var, [condition.var]) }
{ set_result(res.var, "object.__false") }
#TODO: yeck
call_cond = true? node.args[0].has_method?(:name) && {node.args[0].name == :number}
{ '' }
{ "if object._is_callable(#{condition.var}) then
#{condition.var} = #{condition.var}(_self)
end"
}
regular = invoke(node, null, ->invoke_self, res.var)
res.out << "
if (_self == object or _rawget(_self, 'null_question') == nil) and null_question == nil and object._unchanged('null_question') then
-- yay if my var is #{res.var}
#{condition.out}
#{call_cond}
-- end condition
if #{condition.var} == object.__null or #{condition.var} == nil then
#{true_branch.out}
#{action} #{true_branch.var}
else
#{false_branch.out}
#{action} #{false_branch.var}
end
-- end yay if
else
-- fallback null?
#{regular.out}
#{action} #{regular.var}
-- end fallback null?
end
"
res
}
h.inline_when = { node, var |
res = set_result
action = get_action(res.var)
regular = invoke(node, null, ->invoke_self, res.var)
w = my
res.out << "--when\ndo\n" <<
"if (_self == object or _rawget(_self, 'when') == nil) and null_question == nil and object._unchanged('when') then"
node.args.each_slice 2, { cond, branch |
cond_res = w.process_if_branch(cond, null)
branch_res = w.process_if_branch(branch, null, [cond_res.var])
res.out << "
#{cond_res.out}
if object._is_true(#{cond_res.var}) then
#{branch_res.out}
#{action} #{branch_res.var}
else
"
}
res.out << "
-- no branches matched
#{action} object.__null
"
ends = (node.args.length / 2).of("end").join(";")
res.out << ends
res.out << "
else
--when fallback
#{regular.out}
#{action} #{regular.var}
end
end
"
res
}
export h, :invoke_helper