diff --git a/code_obj.lua b/code_obj.lua index 2e1ce5e..ab0f1ac 100644 --- a/code_obj.lua +++ b/code_obj.lua @@ -201,6 +201,9 @@ do local _parent_0 = Code local _base_0 = { add_free_vars = function(self, vars) + if not (#vars > 0) then + return + end local seen do local _tbl_0 = { } @@ -216,12 +219,7 @@ do end for _index_0 = 1, #vars do local var = vars[_index_0] - if type(var) == 'userdata' and var.type == "Var" then - var = tostring(var:as_lua()) - elseif type(var) ~= 'string' then - var = tostring(var) - end - assert(var:match("^[_a-zA-Z][_a-zA-Z0-9]*$")) + assert(type(var) == 'userdata' and var.type == "Var") if not (seen[var]) then self.free_vars[#self.free_vars + 1] = var seen[var] = true @@ -230,15 +228,13 @@ do self.__str = nil end, remove_free_vars = function(self, vars) + if not (#vars > 0) then + return + end local removals = { } for _index_0 = 1, #vars do local var = vars[_index_0] - if type(var) == 'userdata' and var.type == "Var" then - var = tostring(var:as_lua()) - elseif type(var) ~= 'string' then - var = tostring(var) - end - assert(var:match("^[_a-zA-Z][_a-zA-Z0-9]*$")) + assert(type(var) == 'userdata' and var.type == "Var") removals[var] = true end local stack = { @@ -296,7 +292,6 @@ do local var = _list_0[_index_0] if not (seen[var]) then seen[var] = true - assert(var:match("^[_a-zA-Z][_a-zA-Z0-9]*$")) to_declare[#to_declare + 1] = var end end @@ -312,7 +307,16 @@ do end if #to_declare > 0 then self:remove_free_vars(to_declare) - self:prepend("local " .. tostring(concat(to_declare, ", ")) .. ";\n") + self:prepend("local " .. tostring(concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #to_declare do + local v = to_declare[_index_0] + _accum_0[_len_0] = v:as_lua_id() + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ")) .. ";\n") end return to_declare end, diff --git a/code_obj.moon b/code_obj.moon index ec5bc46..fbe202c 100644 --- a/code_obj.moon +++ b/code_obj.moon @@ -134,26 +134,20 @@ class Lua extends Code return lua add_free_vars: (vars)=> + return unless #vars > 0 seen = {[v]:true for v in *@free_vars} for var in *vars - if type(var) == 'userdata' and var.type == "Var" - var = tostring(var\as_lua!) - elseif type(var) != 'string' - var = tostring(var) - assert(var\match("^[_a-zA-Z][_a-zA-Z0-9]*$")) + assert(type(var) == 'userdata' and var.type == "Var") unless seen[var] @free_vars[#@free_vars+1] = var seen[var] = true @__str = nil remove_free_vars: (vars)=> + return unless #vars > 0 removals = {} for var in *vars - if type(var) == 'userdata' and var.type == "Var" - var = tostring(var\as_lua!) - elseif type(var) != 'string' - var = tostring(var) - assert(var\match("^[_a-zA-Z][_a-zA-Z0-9]*$")) + assert(type(var) == 'userdata' and var.type == "Var") removals[var] = true stack = {self} @@ -185,7 +179,6 @@ class Lua extends Code for var in *@free_vars unless seen[var] seen[var] = true - assert(var\match("^[_a-zA-Z][_a-zA-Z0-9]*$")) to_declare[#to_declare+1] = var for bit in *@bits if bit.__class == Lua @@ -193,7 +186,7 @@ class Lua extends Code gather_from self if #to_declare > 0 @remove_free_vars to_declare - @prepend "local #{concat to_declare, ", "};\n" + @prepend "local #{concat [v\as_lua_id! for v in *to_declare], ", "};\n" return to_declare __tostring: => diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom index 41491c7..9d6e1e6 100644 --- a/core/metaprogramming.nom +++ b/core/metaprogramming.nom @@ -20,12 +20,12 @@ immediately lua:append("function(tree") local args = {} for i,tok in ipairs(\%actions[1]) do - if tok.type == "Var" then args[#args+1] = tok:as_lua(nomsu) end + if tok.type == "Var" then args[#args+1] = tok end end for i, arg in ipairs(args) do - lua:append(", ", arg) + lua:append(", ", nomsu:tree_to_lua(arg)) end - local body_lua = \%lua:as_lua(nomsu):as_statements("return ") + local body_lua = nomsu:tree_to_lua(\%lua):as_statements("return ") body_lua:remove_free_vars(args) body_lua:declare_locals() lua:append(")\n ", body_lua, "\nend);") @@ -50,13 +50,13 @@ immediately lua:append("function(") local args = {} for i,tok in ipairs(\%actions[1]) do - if tok.type == "Var" then args[#args+1] = tok:as_lua(nomsu) end + if tok.type == "Var" then args[#args+1] = tok end end for i, arg in ipairs(args) do - lua:append(arg) + lua:append(nomsu:tree_to_lua(arg)) if i < #args then lua:append(", ") end end - local body_lua = \%body:as_lua(nomsu):as_statements("return ") + local body_lua = nomsu:tree_to_lua(\%body):as_statements("return ") body_lua:remove_free_vars(args) body_lua:declare_locals() lua:append(")\n ", body_lua, "\nend);") @@ -81,7 +81,7 @@ immediately local replacements = {} for i,tok in ipairs(\%shorthand[1]) do if tok.type == "Var" then - local lua_var = tostring(tok:as_lua(nomsu)) + local lua_var = tostring(nomsu:tree_to_lua(tok)) replacements[tok] = lua_var lua:append(", ", lua_var) end @@ -104,7 +104,7 @@ immediately lua:append([[) local tree = ]], make_tree(\%longhand), [[ - return tree:as_lua(nomsu) + return nomsu:tree_to_lua(tree) end);]]) return lua @@ -119,27 +119,27 @@ action [remove action %stub] immediately action [%tree as nomsu] - =lua "\%tree:as_nomsu()" + =lua "nomsu:tree_to_nomsu(\%tree)" action [%tree as inline nomsu] - =lua "\%tree:as_nomsu(true)" + =lua "nomsu:tree_to_nomsu(\%tree, true)" action [%tree as lua] - =lua "\%tree:as_lua(nomsu)" + =lua "nomsu:tree_to_lua(\%tree)" action [%tree as lua expr] lua> ".." - local lua = \%tree:as_lua(nomsu) + local lua = nomsu:tree_to_lua(\%tree) if not lua.is_value then error("Invalid thing to convert to lua expr: "..\%tree) end return lua action [%tree as lua statements] - =lua "\%tree:as_lua(nomsu):as_statements()" + =lua "nomsu:tree_to_lua(\%tree):as_statements()" action [%tree with vars %vars] - =lua "nomsu:tree_with_replacements(\%tree, \%vars)" + =lua "\%tree:map(\%vars)" compile [declare locals in %code] to Lua value "\(%code as lua expr):declare_locals()" @@ -168,7 +168,7 @@ immediately immediately compile [nomsu] to: Lua value "nomsu" - compile [%var as lua identifier] to: Lua value "\(%var as lua expr):as_lua(nomsu)" + compile [%var as lua identifier] to: Lua value "nomsu:tree_to_lua(\(%var as lua expr))" # Compiler tools immediately @@ -178,7 +178,7 @@ immediately immediately compile [show lua %block] to lua> ".." - local \%lua = \%block:as_lua(nomsu); + local \%lua = nomsu:tree_to_lua(\%block); return Lua(nil, "print(", repr(tostring(\%lua)), ");"); immediately @@ -202,7 +202,7 @@ immediately compile [barf] to: Lua "error(nil, 0);" compile [barf %msg] to: Lua "error(\(%msg as lua expr), 0);" compile [assume %condition] to - lua> "local \%assumption = 'Assumption failed: '..tostring(\%condition:as_nomsu());" + lua> "local \%assumption = 'Assumption failed: '..tostring(nomsu:tree_to_nomsu(\%condition));" return Lua ".." if not \(%condition as lua expr) then diff --git a/core/operators.nom b/core/operators.nom index adfeeb5..62a3225 100644 --- a/core/operators.nom +++ b/core/operators.nom @@ -25,21 +25,21 @@ immediately # TODO: optimize case of [%x,%y] = [1,2] compile [%a is %b, %a = %b, %a == %b] to lua> ".." - local safe = {Text=true, Number=true}; - local a_lua, b_lua = \%a:as_lua(nomsu), \%b:as_lua(nomsu); + local safe = {Text=true, Number=true} + local a_lua, b_lua = \(%a as lua), \(%b as lua) if safe[\%a.type] or safe[\%b.type] then - return Lua.Value(nil, "(", a_lua, " == ", b_lua, ")"); + return Lua.Value(nil, "(", a_lua, " == ", b_lua, ")") else - return Lua.Value(nil, "utils.equivalent(", a_lua, ", ", b_lua, ")"); + return Lua.Value(nil, "utils.equivalent(", a_lua, ", ", b_lua, ")") end compile [%a isn't %b, %a is not %b, %a not= %b, %a != %b] to lua> ".." - local safe = {Text=true, Number=true}; - local a_lua, b_lua = \%a:as_lua(nomsu), \%b:as_lua(nomsu); + local safe = {Text=true, Number=true} + local a_lua, b_lua = \(%a as lua), \(%b as lua) if safe[\%a.type] or safe[\%b.type] then - return Lua.Value(nil, "(", a_lua, " ~= ", b_lua, ")"); + return Lua.Value(nil, "(", a_lua, " ~= ", b_lua, ")") else - return Lua.Value(nil, "(not utils.equivalent(", a_lua, ", ", b_lua, "))"); + return Lua.Value(nil, "(not utils.equivalent(", a_lua, ", ", b_lua, "))") end # For strict identity checking, use (%x's id) is (%y's id) compile [%'s id, id of %] to: Lua value "nomsu.ids[\(% as lua expr)]" @@ -47,14 +47,14 @@ immediately # Variable assignment operator immediately compile [%var <- %value] to - lua> "local \%var_lua = \%var:as_lua(nomsu);" + lua> "local \%var_lua = \(%var as lua);" assume %var_lua.is_value or barf "Invalid target for assignment: \%var" - lua> "local \%value_lua = \%value:as_lua(nomsu);" + lua> "local \%value_lua = \(%value as lua);" assume %value_lua.is_value or barf "Invalid value for assignment: \%value" lua> ".." - local lua = Lua(nil, \%var_lua, ' = ', \%value_lua, ';'); + local lua = Lua(nil, \%var_lua, ' = ', \%value_lua, ';') if \%var.type == 'Var' then - lua:add_free_vars({\%var}); + lua:add_free_vars({\%var}) end return lua; @@ -64,24 +64,24 @@ immediately assume ((%assignments' "type") is "Dict") or barf ".." Expected a Dict for the assignments part of '<- %' statement, not \%assignments lua> ".." - local lhs, rhs = Lua(), Lua(); + local lhs, rhs = Lua(), Lua() for i, item in ipairs(\%assignments) do - local target, value = item[1], item[2]; - local target_lua = target:as_lua(nomsu); - if not target_lua.is_value then error("Invalid target for assignment: "..target:get_src()); end - local value_lua = value:as_lua(nomsu); - if not value_lua.is_value then error("Invalid value for assignment: "..value:get_src()); end - if target.type == "Var" then - lhs:add_free_vars({target}); + local \%target, \%value = item[1], item[2] + local target_lua = \(%target as lua) + if not target_lua.is_value then error("Invalid target for assignment: "..\(%target as text)) end + local value_lua = \(%value as lua) + if not value_lua.is_value then error("Invalid value for assignment: "..\(%value as text)) end + if \%target.type == "Var" then + lhs:add_free_vars({\%target}) end if i > 1 then - lhs:append(", "); - rhs:append(", "); + lhs:append(", ") + rhs:append(", ") end - lhs:append(target_lua); - rhs:append(value_lua); + lhs:append(target_lua) + rhs:append(value_lua) end - return Lua(nil, lhs, " = ", rhs, ";"); + return Lua(nil, lhs, " = ", rhs, ";") immediately compile [external %var <- %value] to @@ -99,31 +99,31 @@ immediately compile [with %assignments %body] to %lua <- (%body as lua statements) lua> ".." - local lhs, rhs = Lua(), Lua(); - local vars = {}; + local lhs, rhs = Lua(), Lua() + local vars = {} for i, item in ipairs(\%assignments) do - local target, value = item[1], item[2]; - if not target.type == "Var" then - error("Invalid target for 'with' assignment: "..tostring(target)); + local \%target, \%value = item[1], item[2] + if not \%target.type == "Var" then + error("Invalid target for 'with' assignment: "..tostring(\%target)) end - local target_lua = target:as_lua(nomsu); - local value_lua = value:as_lua(nomsu); + local target_lua = \(%target as lua) + local value_lua = \(%value as lua) if not value_lua.is_value then - error("Invalid value for assignment: "..tostring(value)); + error("Invalid value for assignment: "..tostring(\%value)) end - if target.type == "Var" then - lhs:add_free_vars({target}); + if \%target.type == "Var" then + lhs:add_free_vars({\%target}) end if i > 1 then - lhs:append(", "); - rhs:append(", "); + lhs:append(", ") + rhs:append(", ") end - lhs:append(target_lua); - rhs:append(value_lua); - vars[i] = tostring(target_lua); + lhs:append(target_lua) + rhs:append(value_lua) + vars[i] = \%target end - \%lua:remove_free_vars(vars); - \%lua:prepend("local ", lhs, " = ", rhs, ";\n"); + \%lua:remove_free_vars(vars) + \%lua:prepend("local ", lhs, " = ", rhs, ";\n") return Lua ".." do diff --git a/core/text.nom b/core/text.nom index 7b945cd..b7fd2cf 100644 --- a/core/text.nom +++ b/core/text.nom @@ -44,7 +44,7 @@ lua> ".." local reset = "'"..colors["reset color"].."'"; nomsu:define_compile_action(name, function(tree) return Lua.Value(nil, color); end); nomsu:define_compile_action(name.." %", function(\%) - return Lua.Value(nil, color, "..", \%:as_lua(nomsu), "..", reset); + return Lua.Value(nil, color, "..", \(% as lua), "..", reset); end); end end diff --git a/nomsu.lua b/nomsu.lua index f4dcb7e..2b0b6dc 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -249,7 +249,7 @@ end local NomsuCompiler do local _class_0 - local stub_defs, stub_pattern, var_pattern, _nomsu_chunk_counter, _running_files + local stub_defs, stub_pattern, var_pattern, _nomsu_chunk_counter, _running_files, MAX_LINE, math_expression local _base_0 = { define_action = function(self, signature, fn, is_compile_action) if is_compile_action == nil then @@ -284,7 +284,7 @@ do local _len_0 = 1 for _index_1 = 1, #stub_args do local a = stub_args[_index_1] - _accum_0[_len_0] = fn_arg_positions[Types.Var.as_lua_id(a)] + _accum_0[_len_0] = fn_arg_positions[Types.Var(a):as_lua_id()] _len_0 = _len_0 + 1 end arg_orders[stub] = _accum_0 @@ -341,7 +341,7 @@ do end local tree = self:parse(nomsu_code) assert(tree, "Failed to parse: " .. tostring(nomsu_code)) - local lua = tree:as_lua(self):as_statements() + local lua = self:tree_to_lua(tree):as_statements() lua:declare_locals() lua:prepend("-- File: " .. tostring(nomsu_code.source or "") .. "\n") if compile_fn then @@ -432,11 +432,628 @@ do end return run_lua_fn() end, + tree_to_lua = function(self, tree) + local _exp_0 = tree.type + if "Action" == _exp_0 then + local stub = tree:get_stub() + local compile_action = self.environment.COMPILE_ACTIONS[stub] + if compile_action then + local args + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #tree do + local arg = tree[_index_0] + if arg.type ~= "Word" then + _accum_0[_len_0] = arg + _len_0 = _len_0 + 1 + end + end + args = _accum_0 + end + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.environment.ARG_ORDERS[compile_action][stub] + for _index_0 = 1, #_list_0 do + local p = _list_0[_index_0] + _accum_0[_len_0] = args[p - 1] + _len_0 = _len_0 + 1 + end + args = _accum_0 + end + local ret = compile_action(tree, unpack(args)) + if not ret then + error("Failed to produce any Lua") + end + return ret + end + local action = rawget(self.environment.ACTIONS, stub) + local lua = Lua.Value() + if not action and math_expression:match(stub) then + for i, tok in ipairs(tree) do + if tok.type == "Word" then + lua:append(tok.value) + else + local tok_lua = self:tree_to_lua(tok) + if not (tok_lua.is_value) then + error("non-expression value inside math expression: " .. tostring(colored.yellow(repr(tok)))) + end + if tok.type == "Action" then + tok_lua:parenthesize() + end + lua:append(tok_lua) + end + if i < #tree then + lua:append(" ") + end + end + return lua + end + local args = { } + for i, tok in ipairs(tree) do + local _continue_0 = false + repeat + if tok.type == "Word" then + _continue_0 = true + break + end + local arg_lua = self:tree_to_lua(tok) + if not (arg_lua.is_value) then + error("Cannot use:\n" .. tostring(colored.yellow(repr(tok))) .. "\nas an argument to " .. tostring(stub) .. ", since it's not an expression, it produces: " .. tostring(repr(arg_lua)), 0) + end + insert(args, arg_lua) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + if action then + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.environment.ARG_ORDERS[action][stub] + for _index_0 = 1, #_list_0 do + local p = _list_0[_index_0] + _accum_0[_len_0] = args[p] + _len_0 = _len_0 + 1 + end + args = _accum_0 + end + end + lua:append("ACTIONS[", repr(stub), "](") + for i, arg in ipairs(args) do + lua:append(arg) + if i < #args then + lua:append(", ") + end + end + lua:append(")") + return lua + elseif "EscapedNomsu" == _exp_0 then + local make_tree + make_tree = function(t) + if type(t) ~= 'userdata' then + return repr(t) + end + if t.is_multi then + local bits + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #t do + local bit = t[_index_0] + _accum_0[_len_0] = make_tree(bit) + _len_0 = _len_0 + 1 + end + bits = _accum_0 + end + return t.type .. "(" .. table.concat(bits, ", ") .. ")" + else + return t.type .. "(" .. make_tree(t.value) .. ")" + end + end + return Lua.Value(nil, make_tree(tree.value)) + elseif "Block" == _exp_0 then + local lua = Lua() + for i, line in ipairs(tree) do + local line_lua = self:tree_to_lua(line) + if i > 1 then + lua:append("\n") + end + lua:append(line_lua:as_statements()) + end + return lua + elseif "Text" == _exp_0 then + local lua = Lua.Value() + local string_buffer = "" + for _index_0 = 1, #tree do + local _continue_0 = false + repeat + local bit = tree[_index_0] + if type(bit) == "string" then + string_buffer = string_buffer .. bit + _continue_0 = true + break + end + if string_buffer ~= "" then + if #lua.bits > 0 then + lua:append("..") + end + lua:append(repr(string_buffer)) + string_buffer = "" + end + local bit_lua = self:tree_to_lua(bit) + if not (bit_lua.is_value) then + error("Cannot use " .. tostring(colored.yellow(repr(bit))) .. " as a string interpolation value, since it's not an expression.", 0) + end + if #lua.bits > 0 then + lua:append("..") + end + if bit.type ~= "Text" then + bit_lua = Lua.Value(nil, "stringify(", bit_lua, ")") + end + lua:append(bit_lua) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + if string_buffer ~= "" or #lua.bits == 0 then + if #lua.bits > 0 then + lua:append("..") + end + lua:append(repr(string_buffer)) + end + if #lua.bits > 1 then + lua:parenthesize() + end + return lua + elseif "List" == _exp_0 then + local lua = Lua.Value(nil, "{") + local line_length = 0 + for i, item in ipairs(tree) do + local item_lua = self:tree_to_lua(item) + if not (item_lua.is_value) then + error("Cannot use " .. tostring(colored.yellow(repr(item))) .. " as a list item, since it's not an expression.", 0) + end + lua:append(item_lua) + local item_string = tostring(item_lua) + local last_line = item_string:match("[^\n]*$") + if item_string:match("\n") then + line_length = #last_line + else + line_length = line_length + #last_line + end + if i < #tree then + if line_length >= MAX_LINE then + lua:append(",\n ") + line_length = 0 + else + lua:append(", ") + line_length = line_length + 2 + end + end + end + lua:append("}") + return lua + elseif "Dict" == _exp_0 then + local lua = Lua.Value(nil, "{") + local line_length = 0 + for i, entry in ipairs(tree) do + local entry_lua = self:tree_to_lua(entry) + lua:append(entry_lua) + local entry_lua_str = tostring(entry_lua) + local last_line = entry_lua_str:match("\n([^\n]*)$") + if last_line then + line_length = #last_line + else + line_length = line_length + #entry_lua_str + end + if i < #tree then + if line_length >= MAX_LINE then + lua:append(",\n ") + line_length = 0 + else + lua:append(", ") + line_length = line_length + 2 + end + end + end + lua:append("}") + return lua + elseif "DictEntry" == _exp_0 then + local key, value = tree[1], tree[2] + local key_lua = self:tree_to_lua(key) + if not (key_lua.is_value) then + error("Cannot use " .. tostring(colored.yellow(repr(key))) .. " as a dict key, since it's not an expression.", 0) + end + local value_lua = value and self:tree_to_lua(value) or Lua.Value(nil, "true") + if not (value_lua.is_value) then + error("Cannot use " .. tostring(colored.yellow(repr(value))) .. " as a dict value, since it's not an expression.", 0) + end + local key_str = tostring(key_lua):match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) + if key_str then + return Lua(nil, key_str, "=", value_lua) + elseif tostring(key_lua):sub(1, 1) == "[" then + return Lua(nil, "[ ", key_lua, "]=", value_lua) + else + return Lua(nil, "[", key_lua, "]=", value_lua) + end + elseif "IndexChain" == _exp_0 then + local lua = self:tree_to_lua(tree[1]) + if not (lua.is_value) then + error("Cannot index " .. tostring(colored.yellow(repr(tree[1]))) .. ", since it's not an expression.", 0) + end + local first_char = tostring(lua):sub(1, 1) + if first_char == "{" or first_char == '"' or first_char == "[" then + lua:parenthesize() + end + for i = 2, #tree do + local key = tree[i] + local key_lua = self:tree_to_lua(key) + if not (key_lua.is_value) then + error("Cannot use " .. tostring(colored.yellow(repr(key))) .. " as an index, since it's not an expression.", 0) + end + local key_lua_str = tostring(key_lua) + do + local lua_id = key_lua_str:match("^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if lua_id then + lua:append("." .. tostring(lua_id)) + elseif key_lua_str:sub(1, 1) == '[' then + lua:append("[ ", key_lua, " ]") + else + lua:append("[", key_lua, "]") + end + end + end + return lua + elseif "Number" == _exp_0 then + return Lua.Value(nil, tostring(tree.value)) + elseif "Var" == _exp_0 then + return Lua.Value(nil, tree:as_lua_id()) + elseif "Word" == _exp_0 then + return error("Cannot convert a Word to lua") + elseif "Comment" == _exp_0 then + return Lua(nil, "--" .. tree.value:gsub("\n", "\n--") .. "\n") + else + return error("Unknown type: " .. tostring(tree.type)) + end + end, + tree_to_nomsu = function(self, tree, inline, can_use_colon) + if inline == nil then + inline = false + end + if can_use_colon == nil then + can_use_colon = false + end + local _exp_0 = tree.type + if "Action" == _exp_0 then + if inline then + local nomsu = Nomsu() + for i, bit in ipairs(tree) do + if bit.type == "Word" then + if i > 1 then + nomsu:append(" ") + end + nomsu:append(bit.value) + else + local arg_nomsu = self:tree_to_nomsu(bit, true) + if not (arg_nomsu) then + return nil + end + if not (i == 1) then + nomsu:append(" ") + end + if bit.type == "Action" or bit.type == "Block" then + arg_nomsu:parenthesize() + end + nomsu:append(arg_nomsu) + end + end + return nomsu + else + local nomsu = Nomsu() + local next_space = "" + local last_colon = nil + for i, bit in ipairs(tree) do + if bit.type == "Word" then + nomsu:append(next_space, bit.value) + next_space = " " + else + local arg_nomsu + if last_colon == i - 1 and bit.type == "Action" then + arg_nomsu = nil + elseif bit.type == "Block" then + arg_nomsu = nil + else + arg_nomsu = self:tree_to_nomsu(bit, true) + end + if arg_nomsu and #arg_nomsu < MAX_LINE then + if bit.type == "Action" then + if can_use_colon and i > 1 then + nomsu:append(next_space:match("[^ ]*"), ": ", arg_nomsu) + next_space = "\n.." + last_colon = i + else + nomsu:append(next_space, "(", arg_nomsu, ")") + next_space = " " + end + else + nomsu:append(next_space, arg_nomsu) + next_space = " " + end + else + arg_nomsu = self:tree_to_nomsu(bit, nil, true) + if not (nomsu) then + return nil + end + if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then + if i == 1 then + arg_nomsu = Nomsu(nil, "(..)\n ", arg_nomsu) + else + arg_nomsu = Nomsu(nil, "\n ", arg_nomsu) + end + end + if last_colon == i - 1 and (bit.type == "Action" or bit.type == "Block") then + next_space = "" + end + nomsu:append(next_space, arg_nomsu) + next_space = "\n.." + end + if next_space == " " and #(tostring(nomsu):match("[^\n]*$")) > MAX_LINE then + next_space = "\n.." + end + end + end + return nomsu + end + elseif "EscapedNomsu" == _exp_0 then + local nomsu = self:tree_to_nomsu(tree.value, true) + if nomsu == nil and not inline then + nomsu = self:tree_to_nomsu(tree.value) + return nomsu and Nomsu(nil, "\\:\n ", nomsu) + end + return nomsu and Nomsu(nil, "\\(", nomsu, ")") + elseif "Block" == _exp_0 then + if inline then + local nomsu = Nomsu() + for i, line in ipairs(self) do + if i > 1 then + nomsu:append("; ") + end + local line_nomsu = self:tree_to_nomsu(line, true) + if not (line_nomsu) then + return nil + end + nomsu:append(line_nomsu) + end + return nomsu + end + local nomsu = Nomsu() + for i, line in ipairs(self) do + line = assert(self:tree_to_nomsu(line, nil, true), "Could not convert line to nomsu") + nomsu:append(line) + if i < #self then + nomsu:append("\n") + if tostring(line):match("\n") then + nomsu:append("\n") + end + end + end + return nomsu + elseif "Text" == _exp_0 then + if inline then + local nomsu = Nomsu(nil, '"') + for _index_0 = 1, #tree do + local bit = tree[_index_0] + if type(bit) == 'string' then + nomsu:append((bit:gsub("\\", "\\\\"):gsub("\n", "\\n"))) + else + local interp_nomsu = self:tree_to_nomsu(bit, true) + if interp_nomsu then + if bit.type ~= "Word" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then + interp_nomsu:parenthesize() + end + nomsu:append("\\", interp_nomsu) + else + return nil + end + end + end + nomsu:append('"') + return nomsu + else + local inline_version = self:tree_to_nomsu(tree, true) + if inline_version and #inline_version <= MAX_LINE then + return inline_version + end + local nomsu = Nomsu(nil, '".."\n ') + for i, bit in ipairs(self) do + if type(bit) == 'string' then + nomsu:append((bit:gsub("\\", "\\\\"):gsub("\n", "\n "))) + else + local interp_nomsu = self:tree_to_nomsu(bit, true) + if interp_nomsu then + if bit.type ~= "Word" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then + interp_nomsu:parenthesize() + end + nomsu:append("\\", interp_nomsu) + else + interp_nomsu = self:tree_to_nomsu(bit) + if not (interp_nomsu) then + return nil + end + nomsu:append("\\\n ", interp_nomsu) + if i < #self then + nomsu:append("\n ..") + end + end + end + end + return nomsu + end + elseif "List" == _exp_0 then + if inline then + local nomsu = Nomsu(nil, "[") + for i, item in ipairs(tree) do + local item_nomsu = self:tree_to_nomsu(item, true) + if not (item_nomsu) then + return nil + end + if i > 1 then + nomsu:append(", ") + end + nomsu:append(item_nomsu) + end + nomsu:append("]") + return nomsu + else + local inline_version = self:tree_to_nomsu(tree, true) + if inline_version and #inline_version <= MAX_LINE then + return inline_version + end + local nomsu = Nomsu(nil, "[..]") + local line = Nomsu(nil, "\n ") + for _index_0 = 1, #tree do + local item = tree[_index_0] + local item_nomsu = self:tree_to_nomsu(item, true) + if item_nomsu and #line + #", " + #item_nomsu <= MAX_LINE then + if #line.bits > 1 then + line:append(", ") + end + line:append(item_nomsu) + else + if not (item_nomsu) then + item_nomsu = self:tree_to_nomsu(item) + if not (item_nomsu) then + return nil + end + end + if #line.bits > 1 then + nomsu:append(line) + line = Nomsu(nil, "\n ") + end + line:append(item_nomsu) + end + end + if #line.bits > 1 then + nomsu:append(line) + end + return nomsu + end + elseif "Dict" == _exp_0 then + if inline then + local nomsu = Nomsu(nil, "{") + for i, entry in ipairs(tree) do + local entry_nomsu = self:tree_to_nomsu(entry, true) + if not (entry_nomsu) then + return nil + end + if i > 1 then + nomsu:append(", ") + end + nomsu:append(entry_nomsu) + end + nomsu:append("}") + return nomsu + else + local inline_version = self:tree_to_nomsu(tree, true) + if inline_version then + return inline_version + end + local nomsu = Nomsu(nil, "{..}") + local line = Nomsu(nil, "\n ") + for _index_0 = 1, #tree do + local entry = tree[_index_0] + local entry_nomsu = self:tree_to_nomsu(entry) + if not (entry_nomsu) then + return nil + end + if #line + #tostring(entry_nomsu) <= MAX_LINE then + if #line.bits > 1 then + line:append(", ") + end + line:append(entry_nomsu) + else + if #line.bits > 1 then + nomsu:append(line) + line = Nomsu(nil, "\n ") + end + line:append(entry_nomsu) + end + end + if #line.bits > 1 then + nomsu:append(line) + end + return nomsu + end + elseif "DictEntry" == _exp_0 then + local key, value = tree[1], tree[2] + local key_nomsu = self:tree_to_nomsu(key, true) + if not (key_nomsu) then + return nil + end + if key.type == "Action" or key.type == "Block" then + key_nomsu:parenthesize() + end + local value_nomsu + if value then + value_nomsu = self:tree_to_nomsu(value, true) + else + value_nomsu = Nomsu(nil, "") + end + if inline and not value_nomsu then + return nil + end + if not value_nomsu then + if inline then + return nil + end + value_nomsu = self:tree_to_nomsu(value) + if not (value_nomsu) then + return nil + end + end + return Nomsu(nil, key_nomsu, ":", value_nomsu) + elseif "IndexChain" == _exp_0 then + local nomsu = Nomsu() + for i, bit in ipairs(tree) do + if i > 1 then + nomsu:append(".") + end + local bit_nomsu = self:tree_to_nomsu(bit, true) + if not (bit_nomsu) then + return nil + end + if bit.type == "Action" or bit.type == "Block" then + bit_nomsu:parenthesize() + end + nomsu:append(bit_nomsu) + end + return nomsu + elseif "Number" == _exp_0 then + return Nomsu(nil, tostring(tree.value)) + elseif "Var" == _exp_0 then + return Nomsu(nil, "%", tree.value) + elseif "Word" == _exp_0 then + return Nomsu(nil, tree.value) + elseif "Comment" == _exp_0 then + if inline then + return nil + end + return Nomsu(nil, "#", tree.value:gsub("\n", "\n ")) + else + return error("Unknown type: " .. tostring(tree.type)) + end + end, tree_to_value = function(self, tree) if tree.type == 'Text' and #tree == 1 and type(tree[1]) == 'string' then return tree[1] end - local lua = Lua(nil, "return ", tree:as_lua(self), ";") + local lua = Lua(nil, "return ", self:tree_to_lua(tree), ";") return self:run_lua(lua) end, walk_tree = function(self, tree, depth) @@ -456,32 +1073,10 @@ do return self:walk_tree(v, depth + 1) end end, - tree_with_replacements = function(self, tree, replacements) - if not (next(replacements)) then - return tree - end - if next(replacements).type == "Var" then - do - local _tbl_0 = { } - for k, v in pairs(replacements) do - _tbl_0[tostring(k:as_lua(self))] = v - end - replacements = _tbl_0 - end - end - return tree:map(function(t) - if t.type == "Var" then - local id = tostring(t:as_lua(self)) - if replacements[id] ~= nil then - return replacements[id] - end - end - end) - end, initialize_core = function(self) local nomsu = self self:define_compile_action("immediately %block", function(self, _block) - local lua = _block:as_lua(nomsu):as_statements() + local lua = nomsu:tree_to_lua(_block):as_statements() lua:declare_locals() nomsu:run_lua(lua) return Lua(nil, "if IMMEDIATE then\n ", lua, "\nend") @@ -489,7 +1084,7 @@ do local add_lua_string_bits add_lua_string_bits = function(lua, code) if code.type ~= "Text" then - lua:append(", ", code:as_lua(nomsu)) + lua:append(", ", nomsu:tree_to_lua(code)) return end for _index_0 = 1, #code do @@ -498,7 +1093,7 @@ do if type(bit) == "string" then lua:append(repr(bit)) else - local bit_lua = bit:as_lua(nomsu) + local bit_lua = nomsu:tree_to_lua(bit) if not (bit_lua.is_value) then error("Cannot use " .. tostring(colored.yellow(repr(bit))) .. " as a string interpolation value, since it's not an expression.") end @@ -525,7 +1120,7 @@ do if type(bit) == "string" then lua:append(bit) else - local bit_lua = bit:as_lua(nomsu) + local bit_lua = nomsu:tree_to_lua(bit) if not (bit_lua.is_value) then error("Cannot use " .. tostring(colored.yellow(repr(bit))) .. " as a string interpolation value, since it's not an expression.", 0) end @@ -536,13 +1131,13 @@ do end self:define_compile_action("lua> %code", function(self, _code) if _code.type ~= "Text" then - return Lua(nil, "nomsu:run_lua(", _code:as_lua(nomsu), ");") + return Lua(nil, "nomsu:run_lua(", nomsu:tree_to_lua(_code), ");") end return add_lua_bits(Lua(), _code) end) self:define_compile_action("=lua %code", function(self, _code) if _code.type ~= "Text" then - return Lua.Value(nil, "nomsu:run_lua(", _code:as_lua(nomsu), ":as_statements('return '))") + return Lua.Value(nil, "nomsu:run_lua(", nomsu:tree_to_lua(_code), ":as_statements('return '))") end return add_lua_bits(Lua.Value(), _code) end) @@ -715,6 +1310,8 @@ do var_pattern = re.compile("{| %space ((('%' {%varname}) / %word) %space)+ |}", stub_defs) _nomsu_chunk_counter = 0 _running_files = { } + MAX_LINE = 80 + math_expression = re.compile([[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]]) NomsuCompiler = _class_0 end if arg and debug_getinfo(2).func ~= require then @@ -953,7 +1550,7 @@ OPTIONS elseif args.format then for input_file in all_files(input) do local tree = nomsu:parse(io.open(input_file):read("*a")) - local formatted = tostring(tree:as_nomsu()) + local formatted = tostring(self:tree_to_nomsu(tree)) if output_file then output_file:write(formatted, "\n") output_file:flush() diff --git a/nomsu.moon b/nomsu.moon index 7e60cfd..196bedf 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -306,7 +306,7 @@ class NomsuCompiler stub = assert(stub_pattern\match(alias)) stub_args = assert(var_pattern\match(alias)) (is_compile_action and @environment.COMPILE_ACTIONS or @environment.ACTIONS)[stub] = fn - arg_orders[stub] = [fn_arg_positions[Types.Var.as_lua_id(a)] for a in *stub_args] + arg_orders[stub] = [fn_arg_positions[Types.Var(a)\as_lua_id!] for a in *stub_args] @environment.ARG_ORDERS[fn] = arg_orders define_compile_action: (signature, fn)=> @@ -341,7 +341,7 @@ class NomsuCompiler if #tostring(nomsu_code) == 0 then return nil tree = @parse(nomsu_code) assert tree, "Failed to parse: #{nomsu_code}" - lua = tree\as_lua(@)\as_statements! + lua = @tree_to_lua(tree)\as_statements! lua\declare_locals! lua\prepend "-- File: #{nomsu_code.source or ""}\n" if compile_fn @@ -401,12 +401,461 @@ class NomsuCompiler line_numbered_lua = "1 |"..lua_string\gsub("\n", fn) error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack line_numbered_lua}\n\n#{err}", 0) return run_lua_fn! + + MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value + math_expression = re.compile [[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]] + tree_to_lua: (tree)=> + switch tree.type + when "Action" + stub = tree\get_stub! + compile_action = @environment.COMPILE_ACTIONS[stub] + if compile_action + args = [arg for arg in *tree when arg.type != "Word"] + -- Force all compile-time actions to take a tree location + args = [args[p-1] for p in *@environment.ARG_ORDERS[compile_action][stub]] + -- Force Lua to avoid tail call optimization for debugging purposes + ret = compile_action(tree, unpack(args)) + if not ret then error("Failed to produce any Lua") + return ret + action = rawget(@environment.ACTIONS, stub) + lua = Lua.Value! + if not action and math_expression\match(stub) + -- This is a bit of a hack, but this code handles arbitrarily complex + -- math expressions like 2*x + 3^2 without having to define a single + -- action for every possibility. + for i,tok in ipairs tree + if tok.type == "Word" + lua\append tok.value + else + tok_lua = @tree_to_lua(tok) + unless tok_lua.is_value + error("non-expression value inside math expression: #{colored.yellow repr(tok)}") + if tok.type == "Action" + tok_lua\parenthesize! + lua\append tok_lua + if i < #tree + lua\append " " + return lua + + args = {} + for i, tok in ipairs tree + if tok.type == "Word" then continue + arg_lua = @tree_to_lua(tok) + unless arg_lua.is_value + error "Cannot use:\n#{colored.yellow repr(tok)}\nas an argument to #{stub}, since it's not an expression, it produces: #{repr arg_lua}", 0 + insert args, arg_lua + + if action + args = [args[p] for p in *@environment.ARG_ORDERS[action][stub]] + + -- Not really worth bothering with ACTIONS.foo(...) style since almost every action + -- has arguments, so it won't work + lua\append "ACTIONS[",repr(stub),"](" + for i, arg in ipairs args + lua\append arg + if i < #args then lua\append ", " + lua\append ")" + return lua + + when "EscapedNomsu" + make_tree = (t)-> + if type(t) != 'userdata' + return repr(t) + if t.is_multi + bits = [make_tree(bit) for bit in *t] + return t.type.."("..table.concat(bits, ", ")..")" + else + return t.type.."("..make_tree(t.value)..")" + Lua.Value nil, make_tree(tree.value) + + when "Block" + lua = Lua! + for i,line in ipairs tree + line_lua = @tree_to_lua(line) + if i > 1 + lua\append "\n" + lua\append line_lua\as_statements! + return lua + + when "Text" + lua = Lua.Value! + string_buffer = "" + for bit in *tree + if type(bit) == "string" + string_buffer ..= bit + continue + if string_buffer ~= "" + if #lua.bits > 0 then lua\append ".." + lua\append repr(string_buffer) + string_buffer = "" + bit_lua = @tree_to_lua(bit) + unless bit_lua.is_value + error "Cannot use #{colored.yellow repr(bit)} as a string interpolation value, since it's not an expression.", 0 + if #lua.bits > 0 then lua\append ".." + if bit.type != "Text" + bit_lua = Lua.Value(nil, "stringify(",bit_lua,")") + lua\append bit_lua + + if string_buffer ~= "" or #lua.bits == 0 + if #lua.bits > 0 then lua\append ".." + lua\append repr(string_buffer) + + if #lua.bits > 1 + lua\parenthesize! + return lua + + when "List" + lua = Lua.Value nil, "{" + line_length = 0 + for i, item in ipairs tree + item_lua = @tree_to_lua(item) + unless item_lua.is_value + error "Cannot use #{colored.yellow repr(item)} as a list item, since it's not an expression.", 0 + lua\append item_lua + item_string = tostring(item_lua) + last_line = item_string\match("[^\n]*$") + if item_string\match("\n") + line_length = #last_line + else + line_length += #last_line + if i < #tree + if line_length >= MAX_LINE + lua\append ",\n " + line_length = 0 + else + lua\append ", " + line_length += 2 + lua\append "}" + return lua + + when "Dict" + lua = Lua.Value nil, "{" + line_length = 0 + for i, entry in ipairs tree + entry_lua = @tree_to_lua(entry) + lua\append entry_lua + entry_lua_str = tostring(entry_lua) + -- TODO: maybe make this more accurate? It's only a heuristic, so eh... + last_line = entry_lua_str\match("\n([^\n]*)$") + if last_line + line_length = #last_line + else + line_length += #entry_lua_str + if i < #tree + if line_length >= MAX_LINE + lua\append ",\n " + line_length = 0 + else + lua\append ", " + line_length += 2 + lua\append "}" + return lua + + when "DictEntry" + key, value = tree[1], tree[2] + key_lua = @tree_to_lua(key) + unless key_lua.is_value + error "Cannot use #{colored.yellow repr(key)} as a dict key, since it's not an expression.", 0 + value_lua = value and @tree_to_lua(value) or Lua.Value(nil, "true") + unless value_lua.is_value + error "Cannot use #{colored.yellow repr(value)} as a dict value, since it's not an expression.", 0 + key_str = tostring(key_lua)\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) + return if key_str + Lua nil, key_str,"=",value_lua + elseif tostring(key_lua)\sub(1,1) == "[" + -- NOTE: this *must* use a space after the [ to avoid freaking out + -- Lua's parser if the inner expression is a long string. Lua + -- parses x[[[y]]] as x("[y]"), not as x["y"] + Lua nil, "[ ",key_lua,"]=",value_lua + else + Lua nil, "[",key_lua,"]=",value_lua + + when "IndexChain" + lua = @tree_to_lua(tree[1]) + unless lua.is_value + error "Cannot index #{colored.yellow repr(tree[1])}, since it's not an expression.", 0 + first_char = tostring(lua)\sub(1,1) + if first_char == "{" or first_char == '"' or first_char == "[" + lua\parenthesize! + + for i=2,#tree + key = tree[i] + key_lua = @tree_to_lua(key) + unless key_lua.is_value + error "Cannot use #{colored.yellow repr(key)} as an index, since it's not an expression.", 0 + key_lua_str = tostring(key_lua) + if lua_id = key_lua_str\match("^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + lua\append ".#{lua_id}" + elseif key_lua_str\sub(1,1) == '[' + -- NOTE: this *must* use a space after the [ to avoid freaking out + -- Lua's parser if the inner expression is a long string. Lua + -- parses x[[[y]]] as x("[y]"), not as x["y"] + lua\append "[ ",key_lua," ]" + else + lua\append "[",key_lua,"]" + return lua + + when "Number" + Lua.Value(nil, tostring(tree.value)) + + when "Var" + Lua.Value(nil, tree\as_lua_id!) + + when "Word" + error("Cannot convert a Word to lua") + + when "Comment" + Lua(nil, "--"..tree.value\gsub("\n","\n--").."\n") + + else + error("Unknown type: #{tree.type}") + + + tree_to_nomsu: (tree, inline=false, can_use_colon=false)=> + switch tree.type + when "Action" + if inline + nomsu = Nomsu! + for i,bit in ipairs tree + if bit.type == "Word" + if i > 1 + nomsu\append " " + nomsu\append bit.value + else + arg_nomsu = @tree_to_nomsu(bit,true) + return nil unless arg_nomsu + unless i == 1 + nomsu\append " " + if bit.type == "Action" or bit.type == "Block" + arg_nomsu\parenthesize! + nomsu\append arg_nomsu + return nomsu + else + nomsu = Nomsu! + next_space = "" + -- TODO: track line length as we go and use 80-that instead of 80 for wrapping + last_colon = nil + for i,bit in ipairs tree + if bit.type == "Word" + nomsu\append next_space, bit.value + next_space = " " + else + arg_nomsu = if last_colon == i-1 and bit.type == "Action" then nil + elseif bit.type == "Block" then nil + else @tree_to_nomsu(bit,true) + + if arg_nomsu and #arg_nomsu < MAX_LINE + if bit.type == "Action" + if can_use_colon and i > 1 + nomsu\append next_space\match("[^ ]*"), ": ", arg_nomsu + next_space = "\n.." + last_colon = i + else + nomsu\append next_space, "(", arg_nomsu, ")" + next_space = " " + else + nomsu\append next_space, arg_nomsu + next_space = " " + else + arg_nomsu = @tree_to_nomsu(bit, nil, true) + return nil unless nomsu + -- These types carry their own indentation + if bit.type != "List" and bit.type != "Dict" and bit.type != "Text" + if i == 1 + arg_nomsu = Nomsu(nil, "(..)\n ", arg_nomsu) + else + arg_nomsu = Nomsu(nil, "\n ", arg_nomsu) + + if last_colon == i-1 and (bit.type == "Action" or bit.type == "Block") + next_space = "" + nomsu\append next_space, arg_nomsu + next_space = "\n.." + + if next_space == " " and #(tostring(nomsu)\match("[^\n]*$")) > MAX_LINE + next_space = "\n.." + return nomsu + + when "EscapedNomsu" + nomsu = @tree_to_nomsu(tree.value, true) + if nomsu == nil and not inline + nomsu = @tree_to_nomsu(tree.value) + return nomsu and Nomsu nil, "\\:\n ", nomsu + return nomsu and Nomsu nil, "\\(", nomsu, ")" + + when "Block" + if inline + nomsu = Nomsu! + for i,line in ipairs @ + if i > 1 + nomsu\append "; " + line_nomsu = @tree_to_nomsu(line,true) + return nil unless line_nomsu + nomsu\append line_nomsu + return nomsu + nomsu = Nomsu! + for i, line in ipairs @ + line = assert(@tree_to_nomsu(line, nil, true), "Could not convert line to nomsu") + nomsu\append line + if i < #@ + nomsu\append "\n" + if tostring(line)\match("\n") + nomsu\append "\n" + return nomsu + + when "Text" + if inline + nomsu = Nomsu(nil, '"') + for bit in *tree + if type(bit) == 'string' + -- TODO: unescape better? + nomsu\append (bit\gsub("\\","\\\\")\gsub("\n","\\n")) + else + interp_nomsu = @tree_to_nomsu(bit, true) + if interp_nomsu + if bit.type != "Word" and bit.type != "List" and bit.type != "Dict" and bit.type != "Text" + interp_nomsu\parenthesize! + nomsu\append "\\", interp_nomsu + else return nil + nomsu\append '"' + return nomsu + else + inline_version = @tree_to_nomsu(tree, true) + if inline_version and #inline_version <= MAX_LINE + return inline_version + nomsu = Nomsu(nil, '".."\n ') + for i, bit in ipairs @ + if type(bit) == 'string' + nomsu\append (bit\gsub("\\","\\\\")\gsub("\n","\n ")) + else + interp_nomsu = @tree_to_nomsu(bit, true) + if interp_nomsu + if bit.type != "Word" and bit.type != "List" and bit.type != "Dict" and bit.type != "Text" + interp_nomsu\parenthesize! + nomsu\append "\\", interp_nomsu + else + interp_nomsu = @tree_to_nomsu(bit) + return nil unless interp_nomsu + nomsu\append "\\\n ", interp_nomsu + if i < #@ + nomsu\append "\n .." + return nomsu + + when "List" + if inline + nomsu = Nomsu(nil, "[") + for i, item in ipairs tree + item_nomsu = @tree_to_nomsu(item, true) + return nil unless item_nomsu + if i > 1 + nomsu\append ", " + nomsu\append item_nomsu + nomsu\append "]" + return nomsu + else + inline_version = @tree_to_nomsu(tree, true) + if inline_version and #inline_version <= MAX_LINE + return inline_version + nomsu = Nomsu(nil, "[..]") + line = Nomsu(nil, "\n ") + for item in *tree + item_nomsu = @tree_to_nomsu(item, true) + if item_nomsu and #line + #", " + #item_nomsu <= MAX_LINE + if #line.bits > 1 + line\append ", " + line\append item_nomsu + else + unless item_nomsu + item_nomsu = @tree_to_nomsu(item) + return nil unless item_nomsu + if #line.bits > 1 + nomsu\append line + line = Nomsu(nil, "\n ") + line\append item_nomsu + if #line.bits > 1 + nomsu\append line + return nomsu + + when "Dict" + if inline + nomsu = Nomsu(nil, "{") + for i, entry in ipairs tree + entry_nomsu = @tree_to_nomsu(entry, true) + return nil unless entry_nomsu + if i > 1 + nomsu\append ", " + nomsu\append entry_nomsu + nomsu\append "}" + return nomsu + else + inline_version = @tree_to_nomsu(tree, true) + if inline_version then return inline_version + nomsu = Nomsu(nil, "{..}") + line = Nomsu(nil, "\n ") + for entry in *tree + entry_nomsu = @tree_to_nomsu(entry) + return nil unless entry_nomsu + if #line + #tostring(entry_nomsu) <= MAX_LINE + if #line.bits > 1 + line\append ", " + line\append entry_nomsu + else + if #line.bits > 1 + nomsu\append line + line = Nomsu(nil, "\n ") + line\append entry_nomsu + if #line.bits > 1 + nomsu\append line + return nomsu + + when "DictEntry" + key, value = tree[1], tree[2] + key_nomsu = @tree_to_nomsu(key, true) + return nil unless key_nomsu + if key.type == "Action" or key.type == "Block" + key_nomsu\parenthesize! + value_nomsu = if value + @tree_to_nomsu(value, true) + else Nomsu(nil, "") + if inline and not value_nomsu then return nil + if not value_nomsu + return nil if inline + value_nomsu = @tree_to_nomsu(value) + return nil unless value_nomsu + return Nomsu nil, key_nomsu, ":", value_nomsu + + when "IndexChain" + nomsu = Nomsu! + for i, bit in ipairs tree + if i > 1 + nomsu\append "." + bit_nomsu = @tree_to_nomsu(bit, true) + return nil unless bit_nomsu + if bit.type == "Action" or bit.type == "Block" + bit_nomsu\parenthesize! + nomsu\append bit_nomsu + return nomsu + + when "Number" + return Nomsu(nil, tostring(tree.value)) + + when "Var" + return Nomsu(nil, "%", tree.value) + + when "Word" + return Nomsu(nil, tree.value) + + when "Comment" + return nil if inline + return Nomsu(nil, "#", tree.value\gsub("\n", "\n ")) + + else + error("Unknown type: #{tree.type}") tree_to_value: (tree)=> -- Special case for text literals if tree.type == 'Text' and #tree == 1 and type(tree[1]) == 'string' return tree[1] - lua = Lua(nil, "return ",tree\as_lua(@),";") + lua = Lua(nil, "return ",@tree_to_lua(tree),";") return @run_lua(lua) walk_tree: (tree, depth=0)=> @@ -418,35 +867,25 @@ class NomsuCompiler else @walk_tree(v, depth+1) - tree_with_replacements: (tree, replacements)=> - return tree unless next(replacements) - if next(replacements).type == "Var" - replacements = {tostring(k\as_lua(@)),v for k,v in pairs(replacements)} - tree\map (t)-> - if t.type == "Var" - id = tostring(t\as_lua(self)) - if replacements[id] != nil - return replacements[id] - initialize_core: => -- Sets up some core functionality nomsu = self @define_compile_action "immediately %block", (_block)=> - lua = _block\as_lua(nomsu)\as_statements! + lua = nomsu\tree_to_lua(_block)\as_statements! lua\declare_locals! nomsu\run_lua(lua) return Lua(nil, "if IMMEDIATE then\n ", lua, "\nend") add_lua_string_bits = (lua, code)-> if code.type != "Text" - lua\append ", ", code\as_lua(nomsu) + lua\append ", ", nomsu\tree_to_lua(code) return for bit in *code lua\append ", " if type(bit) == "string" lua\append repr(bit) else - bit_lua = bit\as_lua(nomsu) + bit_lua = nomsu\tree_to_lua(bit) unless bit_lua.is_value error "Cannot use #{colored.yellow repr(bit)} as a string interpolation value, since it's not an expression." lua\append bit_lua @@ -468,7 +907,7 @@ class NomsuCompiler if type(bit) == "string" lua\append bit else - bit_lua = bit\as_lua(nomsu) + bit_lua = nomsu\tree_to_lua(bit) unless bit_lua.is_value error "Cannot use #{colored.yellow repr(bit)} as a string interpolation value, since it's not an expression.", 0 lua\append bit_lua @@ -476,12 +915,12 @@ class NomsuCompiler @define_compile_action "lua> %code", (_code)=> if _code.type != "Text" - return Lua nil, "nomsu:run_lua(", _code\as_lua(nomsu), ");" + return Lua nil, "nomsu:run_lua(", nomsu\tree_to_lua(_code), ");" return add_lua_bits(Lua!, _code) @define_compile_action "=lua %code", (_code)=> if _code.type != "Text" - return Lua.Value nil, "nomsu:run_lua(", _code\as_lua(nomsu), ":as_statements('return '))" + return Lua.Value nil, "nomsu:run_lua(", nomsu\tree_to_lua(_code), ":as_statements('return '))" return add_lua_bits(Lua.Value!, _code) @define_compile_action "use %path", (_path)=> @@ -669,7 +1108,7 @@ OPTIONS -- Auto-format for input_file in all_files(input) tree = nomsu\parse(io.open(input_file)\read("*a")) - formatted = tostring(tree\as_nomsu!) + formatted = tostring(@tree_to_nomsu(tree)) if output_file output_file\write(formatted, "\n") output_file\flush! diff --git a/nomsu_tree.lua b/nomsu_tree.lua index 4d30f38..f4f10b2 100644 --- a/nomsu_tree.lua +++ b/nomsu_tree.lua @@ -19,6 +19,7 @@ Types.is_node = function(n) end local Tree Tree = function(name, kind, methods) + methods = methods or { } assert((kind == 'single') or (kind == 'multi')) local is_multi = (kind == 'multi') do @@ -28,6 +29,21 @@ Tree = function(name, kind, methods) methods.type = name methods.name = name methods.is_multi = is_multi + methods.map = function(self, fn) + if type(fn) == 'table' then + if not (next(fn)) then + return self + end + if type(next(fn)) == 'string' then + error("SHIT") + end + local _replacements = fn + fn = function(k) + return _replacements[k] + end + end + return self:_map(fn) + end if is_multi then methods.__tostring = function(self) return tostring(self.name) .. "(" .. tostring(table.concat((function() @@ -79,191 +95,32 @@ Tree = function(name, kind, methods) }, methods) end end +Tree("Block", 'multi') +Tree("Text", 'multi') +Tree("List", 'multi') +Tree("Dict", 'multi') +Tree("DictEntry", 'multi') +Tree("IndexChain", 'multi') +Tree("Number", 'single') +Tree("Word", 'single') +Tree("Comment", 'single') Tree("EscapedNomsu", 'single', { - as_lua = function(self, nomsu) - local make_tree - make_tree = function(t) - if type(t) ~= 'userdata' then - return repr(t) - end - if t.is_multi then - local bits - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #t do - local bit = t[_index_0] - _accum_0[_len_0] = make_tree(bit) - _len_0 = _len_0 + 1 - end - bits = _accum_0 - end - return t.type .. "(" .. table.concat(bits, ", ") .. ")" - else - return t.type .. "(" .. make_tree(t.value) .. ")" - end - end - return Lua.Value(nil, make_tree(self.value)) - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - local nomsu = self.value:as_nomsu(true) - if nomsu == nil and not inline then - nomsu = self.value:as_nomsu() - return nomsu and Nomsu(nil, "\\:\n ", nomsu) - end - return nomsu and Nomsu(nil, "\\(", nomsu, ")") - end, map = function(self, fn) return fn(self) or self:map(fn) end }) -Tree("Block", 'multi', { - as_lua = function(self, nomsu) - local lua = Lua() - for i, line in ipairs(self) do - local line_lua = line:as_lua(nomsu) - if i > 1 then - lua:append("\n") +Tree("Var", 'single', { + as_lua_id = function(self) + return "_" .. (self.value:gsub("%W", function(c) + if c == "_" then + return "__" + else + return ("_%x"):format(c:byte()) end - lua:append(line_lua:as_statements()) - end - return lua - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - if inline then - local nomsu = Nomsu() - for i, line in ipairs(self) do - if i > 1 then - nomsu:append("; ") - end - local line_nomsu = line:as_nomsu(true) - if not (line_nomsu) then - return nil - end - nomsu:append(line_nomsu) - end - return nomsu - end - local nomsu = Nomsu() - for i, line in ipairs(self) do - line = assert(line:as_nomsu(nil, true), "Could not convert line to nomsu") - nomsu:append(line) - if i < #self then - nomsu:append("\n") - if tostring(line):match("\n") then - nomsu:append("\n") - end - end - end - return nomsu + end)) end }) -local math_expression = re.compile([[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]]) Tree("Action", 'multi', { - as_lua = function(self, nomsu) - local stub = self:get_stub() - local compile_action = nomsu.environment.COMPILE_ACTIONS[stub] - if compile_action then - local args - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #self do - local arg = self[_index_0] - if arg.type ~= "Word" then - _accum_0[_len_0] = arg - _len_0 = _len_0 + 1 - end - end - args = _accum_0 - end - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = nomsu.environment.ARG_ORDERS[compile_action][stub] - for _index_0 = 1, #_list_0 do - local p = _list_0[_index_0] - _accum_0[_len_0] = args[p - 1] - _len_0 = _len_0 + 1 - end - args = _accum_0 - end - local ret = compile_action(self, unpack(args)) - if not ret then - error("Failed to produce any Lua") - end - return ret - end - local action = rawget(nomsu.environment.ACTIONS, stub) - local lua = Lua.Value() - if not action and math_expression:match(stub) then - for i, tok in ipairs(self) do - if tok.type == "Word" then - lua:append(tok.value) - else - local tok_lua = tok:as_lua(nomsu) - if not (tok_lua.is_value) then - error("non-expression value inside math expression: " .. tostring(colored.yellow(repr(tok)))) - end - if tok.type == "Action" then - tok_lua:parenthesize() - end - lua:append(tok_lua) - end - if i < #self then - lua:append(" ") - end - end - return lua - end - local args = { } - for i, tok in ipairs(self) do - local _continue_0 = false - repeat - if tok.type == "Word" then - _continue_0 = true - break - end - local arg_lua = tok:as_lua(nomsu) - if not (arg_lua.is_value) then - error("Cannot use:\n" .. tostring(colored.yellow(repr(tok))) .. "\nas an argument to " .. tostring(stub) .. ", since it's not an expression, it produces: " .. tostring(repr(arg_lua)), 0) - end - insert(args, arg_lua) - _continue_0 = true - until true - if not _continue_0 then - break - end - end - if action then - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = nomsu.environment.ARG_ORDERS[action][stub] - for _index_0 = 1, #_list_0 do - local p = _list_0[_index_0] - _accum_0[_len_0] = args[p] - _len_0 = _len_0 + 1 - end - args = _accum_0 - end - end - lua:append("ACTIONS[", repr(stub), "](") - for i, arg in ipairs(args) do - lua:append(arg) - if i < #args then - lua:append(", ") - end - end - lua:append(")") - return lua - end, get_stub = function(self, include_names) if include_names == nil then include_names = false @@ -293,523 +150,6 @@ Tree("Action", 'multi', { end end return concat(bits, " ") - end, - as_nomsu = function(self, inline, can_use_colon) - if inline == nil then - inline = false - end - if can_use_colon == nil then - can_use_colon = false - end - if inline then - local nomsu = Nomsu() - for i, bit in ipairs(self) do - if bit.type == "Word" then - if i > 1 then - nomsu:append(" ") - end - nomsu:append(bit.value) - else - local arg_nomsu = bit:as_nomsu(true) - if not (arg_nomsu) then - return nil - end - if not (i == 1) then - nomsu:append(" ") - end - if bit.type == "Action" or bit.type == "Block" then - arg_nomsu:parenthesize() - end - nomsu:append(arg_nomsu) - end - end - return nomsu - else - local nomsu = Nomsu() - local next_space = "" - local last_colon = nil - for i, bit in ipairs(self) do - if bit.type == "Word" then - nomsu:append(next_space, bit.value) - next_space = " " - else - local arg_nomsu - if last_colon == i - 1 and bit.type == "Action" then - arg_nomsu = nil - elseif bit.type == "Block" then - arg_nomsu = nil - else - arg_nomsu = bit:as_nomsu(true) - end - if arg_nomsu and #arg_nomsu < MAX_LINE then - if bit.type == "Action" then - if can_use_colon and i > 1 then - nomsu:append(next_space:match("[^ ]*"), ": ", arg_nomsu) - next_space = "\n.." - last_colon = i - else - nomsu:append(next_space, "(", arg_nomsu, ")") - next_space = " " - end - else - nomsu:append(next_space, arg_nomsu) - next_space = " " - end - else - arg_nomsu = bit:as_nomsu(nil, true) - if not (nomsu) then - return nil - end - if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then - if i == 1 then - arg_nomsu = Nomsu(nil, "(..)\n ", arg_nomsu) - else - arg_nomsu = Nomsu(nil, "\n ", arg_nomsu) - end - end - if last_colon == i - 1 and (bit.type == "Action" or bit.type == "Block") then - next_space = "" - end - nomsu:append(next_space, arg_nomsu) - next_space = "\n.." - end - if next_space == " " and #(tostring(nomsu):match("[^\n]*$")) > MAX_LINE then - next_space = "\n.." - end - end - end - return nomsu - end - end -}) -Tree("Text", 'multi', { - as_lua = function(self, nomsu) - local lua = Lua.Value() - local string_buffer = "" - for _index_0 = 1, #self do - local _continue_0 = false - repeat - local bit = self[_index_0] - if type(bit) == "string" then - string_buffer = string_buffer .. bit - _continue_0 = true - break - end - if string_buffer ~= "" then - if #lua.bits > 0 then - lua:append("..") - end - lua:append(repr(string_buffer)) - string_buffer = "" - end - local bit_lua = bit:as_lua(nomsu) - if not (bit_lua.is_value) then - error("Cannot use " .. tostring(colored.yellow(repr(bit))) .. " as a string interpolation value, since it's not an expression.", 0) - end - if #lua.bits > 0 then - lua:append("..") - end - if bit.type ~= "Text" then - bit_lua = Lua.Value(nil, "stringify(", bit_lua, ")") - end - lua:append(bit_lua) - _continue_0 = true - until true - if not _continue_0 then - break - end - end - if string_buffer ~= "" or #lua.bits == 0 then - if #lua.bits > 0 then - lua:append("..") - end - lua:append(repr(string_buffer)) - end - if #lua.bits > 1 then - lua:parenthesize() - end - return lua - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - if inline then - local nomsu = Nomsu(nil, '"') - for _index_0 = 1, #self do - local bit = self[_index_0] - if type(bit) == 'string' then - nomsu:append((bit:gsub("\\", "\\\\"):gsub("\n", "\\n"))) - else - local interp_nomsu = bit:as_nomsu(true) - if interp_nomsu then - if bit.type ~= "Word" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then - interp_nomsu:parenthesize() - end - nomsu:append("\\", interp_nomsu) - else - return nil - end - end - end - nomsu:append('"') - return nomsu - else - local inline_version = self:as_nomsu(true) - if inline_version and #inline_version <= MAX_LINE then - return inline_version - end - local nomsu = Nomsu(nil, '".."\n ') - for i, bit in ipairs(self) do - if type(bit) == 'string' then - nomsu:append((bit:gsub("\\", "\\\\"):gsub("\n", "\n "))) - else - local interp_nomsu = bit:as_nomsu(true) - if interp_nomsu then - if bit.type ~= "Word" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then - interp_nomsu:parenthesize() - end - nomsu:append("\\", interp_nomsu) - else - interp_nomsu = bit:as_nomsu() - if not (interp_nomsu) then - return nil - end - nomsu:append("\\\n ", interp_nomsu) - if i < #self then - nomsu:append("\n ..") - end - end - end - end - return nomsu - end - end -}) -Tree("List", 'multi', { - as_lua = function(self, nomsu) - local lua = Lua.Value(nil, "{") - local line_length = 0 - for i, item in ipairs(self) do - local item_lua = item:as_lua(nomsu) - if not (item_lua.is_value) then - error("Cannot use " .. tostring(colored.yellow(repr(item))) .. " as a list item, since it's not an expression.", 0) - end - lua:append(item_lua) - local item_string = tostring(item_lua) - local last_line = item_string:match("[^\n]*$") - if item_string:match("\n") then - line_length = #last_line - else - line_length = line_length + #last_line - end - if i < #self then - if line_length >= MAX_LINE then - lua:append(",\n ") - line_length = 0 - else - lua:append(", ") - line_length = line_length + 2 - end - end - end - lua:append("}") - return lua - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - if inline then - local nomsu = Nomsu(nil, "[") - for i, item in ipairs(self) do - local item_nomsu = item:as_nomsu(true) - if not (item_nomsu) then - return nil - end - if i > 1 then - nomsu:append(", ") - end - nomsu:append(item_nomsu) - end - nomsu:append("]") - return nomsu - else - local inline_version = self:as_nomsu(true) - if inline_version and #inline_version <= MAX_LINE then - return inline_version - end - local nomsu = Nomsu(nil, "[..]") - local line = Nomsu(nil, "\n ") - for _index_0 = 1, #self do - local item = self[_index_0] - local item_nomsu = item:as_nomsu(true) - if item_nomsu and #line + #", " + #item_nomsu <= MAX_LINE then - if #line.bits > 1 then - line:append(", ") - end - line:append(item_nomsu) - else - if not (item_nomsu) then - item_nomsu = item:as_nomsu() - if not (item_nomsu) then - return nil - end - end - if #line.bits > 1 then - nomsu:append(line) - line = Nomsu(nil, "\n ") - end - line:append(item_nomsu) - end - end - if #line.bits > 1 then - nomsu:append(line) - end - return nomsu - end - end -}) -Tree("Dict", 'multi', { - as_lua = function(self, nomsu) - local lua = Lua.Value(nil, "{") - local line_length = 0 - for i, entry in ipairs(self) do - local entry_lua = entry:as_lua(nomsu) - lua:append(entry_lua) - local entry_lua_str = tostring(entry_lua) - local last_line = entry_lua_str:match("\n([^\n]*)$") - if last_line then - line_length = #last_line - else - line_length = line_length + #entry_lua_str - end - if i < #self then - if line_length >= MAX_LINE then - lua:append(",\n ") - line_length = 0 - else - lua:append(", ") - line_length = line_length + 2 - end - end - end - lua:append("}") - return lua - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - if inline then - local nomsu = Nomsu(nil, "{") - for i, entry in ipairs(self) do - local entry_nomsu = entry:as_nomsu(true) - if not (entry_nomsu) then - return nil - end - if i > 1 then - nomsu:append(", ") - end - nomsu:append(entry_nomsu) - end - nomsu:append("}") - return nomsu - else - local inline_version = self:as_nomsu(true) - if inline_version then - return inline_version - end - local nomsu = Nomsu(nil, "{..}") - local line = Nomsu(nil, "\n ") - for _index_0 = 1, #self do - local entry = self[_index_0] - local entry_nomsu = entry:as_nomsu() - if not (entry_nomsu) then - return nil - end - if #line + #tostring(entry_nomsu) <= MAX_LINE then - if #line.bits > 1 then - line:append(", ") - end - line:append(entry_nomsu) - else - if #line.bits > 1 then - nomsu:append(line) - line = Nomsu(nil, "\n ") - end - line:append(entry_nomsu) - end - end - if #line.bits > 1 then - nomsu:append(line) - end - return nomsu - end - end -}) -Tree("DictEntry", 'multi', { - as_lua = function(self, nomsu) - local key, value = self[1], self[2] - local key_lua = key:as_lua(nomsu) - if not (key_lua.is_value) then - error("Cannot use " .. tostring(colored.yellow(repr(key))) .. " as a dict key, since it's not an expression.", 0) - end - local value_lua = value and value:as_lua(nomsu) or Lua.Value(nil, "true") - if not (value_lua.is_value) then - error("Cannot use " .. tostring(colored.yellow(repr(value))) .. " as a dict value, since it's not an expression.", 0) - end - local key_str = tostring(key_lua):match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) - if key_str then - return Lua(nil, key_str, "=", value_lua) - elseif tostring(key_lua):sub(1, 1) == "[" then - return Lua(nil, "[ ", key_lua, "]=", value_lua) - else - return Lua(nil, "[", key_lua, "]=", value_lua) - end - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = true - end - local key, value = self[1], self[2] - local key_nomsu = key:as_nomsu(true) - if not (key_nomsu) then - return nil - end - if key.type == "Action" or key.type == "Block" then - key_nomsu:parenthesize() - end - local value_nomsu - if value then - value_nomsu = value:as_nomsu(true) - else - value_nomsu = Nomsu(nil, "") - end - if inline and not value_nomsu then - return nil - end - if not value_nomsu then - if inline then - return nil - end - value_nomsu = value:as_nomsu() - if not (value_nomsu) then - return nil - end - end - return Nomsu(nil, key_nomsu, ":", value_nomsu) - end -}) -Tree("IndexChain", 'multi', { - as_lua = function(self, nomsu) - local lua = self[1]:as_lua(nomsu) - if not (lua.is_value) then - error("Cannot index " .. tostring(colored.yellow(repr(self[1]))) .. ", since it's not an expression.", 0) - end - local first_char = tostring(lua):sub(1, 1) - if first_char == "{" or first_char == '"' or first_char == "[" then - lua:parenthesize() - end - for i = 2, #self do - local key = self[i] - local key_lua = key:as_lua(nomsu) - if not (key_lua.is_value) then - error("Cannot use " .. tostring(colored.yellow(repr(key))) .. " as an index, since it's not an expression.", 0) - end - local key_lua_str = tostring(key_lua) - do - local lua_id = key_lua_str:match("^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") - if lua_id then - lua:append("." .. tostring(lua_id)) - elseif key_lua_str:sub(1, 1) == '[' then - lua:append("[ ", key_lua, " ]") - else - lua:append("[", key_lua, "]") - end - end - end - return lua - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - local nomsu = Nomsu() - for i, bit in ipairs(self) do - if i > 1 then - nomsu:append(".") - end - local bit_nomsu = bit:as_nomsu(true) - if not (bit_nomsu) then - return nil - end - if bit.type == "Action" or bit.type == "Block" then - bit_nomsu:parenthesize() - end - nomsu:append(bit_nomsu) - end - return nomsu - end -}) -Tree("Number", 'single', { - as_lua = function(self, nomsu) - return Lua.Value(nil, tostring(self.value)) - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - return Nomsu(nil, tostring(self.value)) - end -}) -Tree("Var", 'single', { - as_lua_id = function(v) - return "_" .. (v:gsub("%W", function(c) - if c == "_" then - return "__" - else - return ("_%x"):format(c:byte()) - end - end)) - end, - as_lua = function(self, nomsu) - return Lua.Value(nil, self.as_lua_id(self.value)) - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - return Nomsu(nil, "%", self.value) - end -}) -Tree("Word", 'single', { - as_lua = function(self, nomsu) - return error("Attempt to convert Word to lua") - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - return Nomsu(nil, self.value) - end -}) -Tree("Comment", 'single', { - as_lua = function(self, nomsu) - return Lua(nil, "--" .. self.value:gsub("\n", "\n--") .. "\n") - end, - as_nomsu = function(self, inline) - if inline == nil then - inline = false - end - if inline then - return nil - end - if self.value:match("\n") then - return Nomsu(nil, "#..", self.value:gsub("\n", "\n ")) - else - return Nomsu(nil, "#", self.value) - end end }) return Types diff --git a/nomsu_tree.moon b/nomsu_tree.moon index 11fed94..c15d29c 100644 --- a/nomsu_tree.moon +++ b/nomsu_tree.moon @@ -14,6 +14,7 @@ Types.is_node = (n)-> -- Helper method: Tree = (name, kind, methods)-> + methods or= {} assert((kind == 'single') or (kind == 'multi')) is_multi = (kind == 'multi') with methods @@ -21,6 +22,14 @@ Tree = (name, kind, methods)-> .type = name .name = name .is_multi = is_multi + .map = (fn)=> + if type(fn) == 'table' + return @ unless next(fn) + if type(next(fn)) == 'string' + error("SHIT") + _replacements = fn + fn = (k)-> _replacements[k] + return @_map(fn) if is_multi .__tostring = => "#{@name}(#{table.concat [repr(v) for v in *@], ', '})" .map = (fn)=> @@ -31,476 +40,36 @@ Tree = (name, kind, methods)-> return ret else .__tostring = => "#{@name}(#{repr(@value)})" - .map = (fn)=> fn(@) or @ + .map = (fn)=> + fn(@) or @ if is_multi Types[name] = immutable nil, methods else Types[name] = immutable {"value"}, methods +Tree "Block", 'multi' +Tree "Text", 'multi' +Tree "List", 'multi' +Tree "Dict", 'multi' +Tree "DictEntry", 'multi' +Tree "IndexChain", 'multi' +Tree "Number", 'single' +Tree "Word", 'single' +Tree "Comment", 'single' Tree "EscapedNomsu", 'single', - as_lua: (nomsu)=> - make_tree = (t)-> - if type(t) != 'userdata' - return repr(t) - if t.is_multi - bits = [make_tree(bit) for bit in *t] - return t.type.."("..table.concat(bits, ", ")..")" - else - return t.type.."("..make_tree(t.value)..")" - Lua.Value nil, make_tree(@value) - - as_nomsu: (inline=false)=> - nomsu = @value\as_nomsu(true) - if nomsu == nil and not inline - nomsu = @value\as_nomsu! - return nomsu and Nomsu nil, "\\:\n ", nomsu - return nomsu and Nomsu nil, "\\(", nomsu, ")" - map: (fn)=> fn(@) or @\map(fn) -Tree "Block", 'multi', - as_lua: (nomsu)=> - lua = Lua! - for i,line in ipairs @ - line_lua = line\as_lua(nomsu) - if i > 1 - lua\append "\n" - lua\append line_lua\as_statements! - return lua +Tree "Var", 'single', + as_lua_id: => + "_"..(@value\gsub("%W", (c)-> if c == "_" then "__" else ("_%x")\format(c\byte!))) - as_nomsu: (inline=false)=> - if inline - nomsu = Nomsu! - for i,line in ipairs @ - if i > 1 - nomsu\append "; " - line_nomsu = line\as_nomsu(true) - return nil unless line_nomsu - nomsu\append line_nomsu - return nomsu - nomsu = Nomsu! - for i, line in ipairs @ - line = assert(line\as_nomsu(nil, true), "Could not convert line to nomsu") - nomsu\append line - if i < #@ - nomsu\append "\n" - if tostring(line)\match("\n") - nomsu\append "\n" - return nomsu - -math_expression = re.compile [[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]] Tree "Action", 'multi', - as_lua: (nomsu)=> - stub = @get_stub! - compile_action = nomsu.environment.COMPILE_ACTIONS[stub] - if compile_action - args = [arg for arg in *@ when arg.type != "Word"] - -- Force all compile-time actions to take a tree location - args = [args[p-1] for p in *nomsu.environment.ARG_ORDERS[compile_action][stub]] - -- Force Lua to avoid tail call optimization for debugging purposes - ret = compile_action(self, unpack(args)) - if not ret then error("Failed to produce any Lua") - return ret - action = rawget(nomsu.environment.ACTIONS, stub) - lua = Lua.Value! - if not action and math_expression\match(stub) - -- This is a bit of a hack, but this code handles arbitrarily complex - -- math expressions like 2*x + 3^2 without having to define a single - -- action for every possibility. - for i,tok in ipairs @ - if tok.type == "Word" - lua\append tok.value - else - tok_lua = tok\as_lua(nomsu) - unless tok_lua.is_value - error("non-expression value inside math expression: #{colored.yellow repr(tok)}") - if tok.type == "Action" - tok_lua\parenthesize! - lua\append tok_lua - if i < #@ - lua\append " " - return lua - - args = {} - for i, tok in ipairs @ - if tok.type == "Word" then continue - arg_lua = tok\as_lua(nomsu) - unless arg_lua.is_value - error "Cannot use:\n#{colored.yellow repr(tok)}\nas an argument to #{stub}, since it's not an expression, it produces: #{repr arg_lua}", 0 - insert args, arg_lua - - if action - args = [args[p] for p in *nomsu.environment.ARG_ORDERS[action][stub]] - - -- Not really worth bothering with ACTIONS.foo(...) style since almost every action - -- has arguments, so it won't work - lua\append "ACTIONS[",repr(stub),"](" - for i, arg in ipairs args - lua\append arg - if i < #args then lua\append ", " - lua\append ")" - return lua - get_stub: (include_names=false)=> bits = if include_names [(t.type == "Word" and t.value or "%#{t.value}") for t in *@] else [(t.type == "Word" and t.value or "%") for t in *@] return concat(bits, " ") - as_nomsu: (inline=false, can_use_colon=false)=> - if inline - nomsu = Nomsu! - for i,bit in ipairs @ - if bit.type == "Word" - if i > 1 - nomsu\append " " - nomsu\append bit.value - else - arg_nomsu = bit\as_nomsu(true) - return nil unless arg_nomsu - unless i == 1 - nomsu\append " " - if bit.type == "Action" or bit.type == "Block" - arg_nomsu\parenthesize! - nomsu\append arg_nomsu - return nomsu - else - nomsu = Nomsu! - next_space = "" - -- TODO: track line length as we go and use 80-that instead of 80 for wrapping - last_colon = nil - for i,bit in ipairs @ - if bit.type == "Word" - nomsu\append next_space, bit.value - next_space = " " - else - arg_nomsu = if last_colon == i-1 and bit.type == "Action" then nil - elseif bit.type == "Block" then nil - else bit\as_nomsu(true) - - if arg_nomsu and #arg_nomsu < MAX_LINE - if bit.type == "Action" - if can_use_colon and i > 1 - nomsu\append next_space\match("[^ ]*"), ": ", arg_nomsu - next_space = "\n.." - last_colon = i - else - nomsu\append next_space, "(", arg_nomsu, ")" - next_space = " " - else - nomsu\append next_space, arg_nomsu - next_space = " " - else - arg_nomsu = bit\as_nomsu(nil, true) - return nil unless nomsu - -- These types carry their own indentation - if bit.type != "List" and bit.type != "Dict" and bit.type != "Text" - if i == 1 - arg_nomsu = Nomsu(nil, "(..)\n ", arg_nomsu) - else - arg_nomsu = Nomsu(nil, "\n ", arg_nomsu) - - if last_colon == i-1 and (bit.type == "Action" or bit.type == "Block") - next_space = "" - nomsu\append next_space, arg_nomsu - next_space = "\n.." - - if next_space == " " and #(tostring(nomsu)\match("[^\n]*$")) > MAX_LINE - next_space = "\n.." - return nomsu - -Tree "Text", 'multi', - as_lua: (nomsu)=> - lua = Lua.Value! - string_buffer = "" - for bit in *@ - if type(bit) == "string" - string_buffer ..= bit - continue - if string_buffer ~= "" - if #lua.bits > 0 then lua\append ".." - lua\append repr(string_buffer) - string_buffer = "" - bit_lua = bit\as_lua(nomsu) - unless bit_lua.is_value - error "Cannot use #{colored.yellow repr(bit)} as a string interpolation value, since it's not an expression.", 0 - if #lua.bits > 0 then lua\append ".." - if bit.type != "Text" - bit_lua = Lua.Value(nil, "stringify(",bit_lua,")") - lua\append bit_lua - - if string_buffer ~= "" or #lua.bits == 0 - if #lua.bits > 0 then lua\append ".." - lua\append repr(string_buffer) - - if #lua.bits > 1 - lua\parenthesize! - return lua - - as_nomsu: (inline=false)=> - if inline - nomsu = Nomsu(nil, '"') - for bit in *@ - if type(bit) == 'string' - -- TODO: unescape better? - nomsu\append (bit\gsub("\\","\\\\")\gsub("\n","\\n")) - else - interp_nomsu = bit\as_nomsu(true) - if interp_nomsu - if bit.type != "Word" and bit.type != "List" and bit.type != "Dict" and bit.type != "Text" - interp_nomsu\parenthesize! - nomsu\append "\\", interp_nomsu - else return nil - nomsu\append '"' - return nomsu - else - inline_version = @as_nomsu(true) - if inline_version and #inline_version <= MAX_LINE - return inline_version - nomsu = Nomsu(nil, '".."\n ') - for i, bit in ipairs @ - if type(bit) == 'string' - nomsu\append (bit\gsub("\\","\\\\")\gsub("\n","\n ")) - else - interp_nomsu = bit\as_nomsu(true) - if interp_nomsu - if bit.type != "Word" and bit.type != "List" and bit.type != "Dict" and bit.type != "Text" - interp_nomsu\parenthesize! - nomsu\append "\\", interp_nomsu - else - interp_nomsu = bit\as_nomsu! - return nil unless interp_nomsu - nomsu\append "\\\n ", interp_nomsu - if i < #@ - nomsu\append "\n .." - return nomsu - -Tree "List", 'multi', - as_lua: (nomsu)=> - lua = Lua.Value nil, "{" - line_length = 0 - for i, item in ipairs @ - item_lua = item\as_lua(nomsu) - unless item_lua.is_value - error "Cannot use #{colored.yellow repr(item)} as a list item, since it's not an expression.", 0 - lua\append item_lua - item_string = tostring(item_lua) - last_line = item_string\match("[^\n]*$") - if item_string\match("\n") - line_length = #last_line - else - line_length += #last_line - if i < #@ - if line_length >= MAX_LINE - lua\append ",\n " - line_length = 0 - else - lua\append ", " - line_length += 2 - lua\append "}" - return lua - - as_nomsu: (inline=false)=> - if inline - nomsu = Nomsu(nil, "[") - for i, item in ipairs @ - item_nomsu = item\as_nomsu(true) - return nil unless item_nomsu - if i > 1 - nomsu\append ", " - nomsu\append item_nomsu - nomsu\append "]" - return nomsu - else - inline_version = @as_nomsu(true) - if inline_version and #inline_version <= MAX_LINE - return inline_version - nomsu = Nomsu(nil, "[..]") - line = Nomsu(nil, "\n ") - for item in *@ - item_nomsu = item\as_nomsu(true) - if item_nomsu and #line + #", " + #item_nomsu <= MAX_LINE - if #line.bits > 1 - line\append ", " - line\append item_nomsu - else - unless item_nomsu - item_nomsu = item\as_nomsu! - return nil unless item_nomsu - if #line.bits > 1 - nomsu\append line - line = Nomsu(nil, "\n ") - line\append item_nomsu - if #line.bits > 1 - nomsu\append line - return nomsu - -Tree "Dict", 'multi', - as_lua: (nomsu)=> - lua = Lua.Value nil, "{" - line_length = 0 - for i, entry in ipairs @ - entry_lua = entry\as_lua(nomsu) - lua\append entry_lua - entry_lua_str = tostring(entry_lua) - -- TODO: maybe make this more accurate? It's only a heuristic, so eh... - last_line = entry_lua_str\match("\n([^\n]*)$") - if last_line - line_length = #last_line - else - line_length += #entry_lua_str - if i < #@ - if line_length >= MAX_LINE - lua\append ",\n " - line_length = 0 - else - lua\append ", " - line_length += 2 - lua\append "}" - return lua - - as_nomsu: (inline=false)=> - if inline - nomsu = Nomsu(nil, "{") - for i, entry in ipairs @ - entry_nomsu = entry\as_nomsu(true) - return nil unless entry_nomsu - if i > 1 - nomsu\append ", " - nomsu\append entry_nomsu - nomsu\append "}" - return nomsu - else - inline_version = @as_nomsu(true) - if inline_version then return inline_version - nomsu = Nomsu(nil, "{..}") - line = Nomsu(nil, "\n ") - for entry in *@ - entry_nomsu = entry\as_nomsu! - return nil unless entry_nomsu - if #line + #tostring(entry_nomsu) <= MAX_LINE - if #line.bits > 1 - line\append ", " - line\append entry_nomsu - else - if #line.bits > 1 - nomsu\append line - line = Nomsu(nil, "\n ") - line\append entry_nomsu - if #line.bits > 1 - nomsu\append line - return nomsu - -Tree "DictEntry", 'multi', - as_lua: (nomsu)=> - key, value = @[1], @[2] - key_lua = key\as_lua(nomsu) - unless key_lua.is_value - error "Cannot use #{colored.yellow repr(key)} as a dict key, since it's not an expression.", 0 - value_lua = value and value\as_lua(nomsu) or Lua.Value(nil, "true") - unless value_lua.is_value - error "Cannot use #{colored.yellow repr(value)} as a dict value, since it's not an expression.", 0 - key_str = tostring(key_lua)\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) - return if key_str - Lua nil, key_str,"=",value_lua - elseif tostring(key_lua)\sub(1,1) == "[" - -- NOTE: this *must* use a space after the [ to avoid freaking out - -- Lua's parser if the inner expression is a long string. Lua - -- parses x[[[y]]] as x("[y]"), not as x["y"] - Lua nil, "[ ",key_lua,"]=",value_lua - else - Lua nil, "[",key_lua,"]=",value_lua - - as_nomsu: (inline=true)=> - key, value = @[1], @[2] - key_nomsu = key\as_nomsu(true) - return nil unless key_nomsu - if key.type == "Action" or key.type == "Block" - key_nomsu\parenthesize! - value_nomsu = if value - value\as_nomsu(true) - else Nomsu(nil, "") - if inline and not value_nomsu then return nil - if not value_nomsu - return nil if inline - value_nomsu = value\as_nomsu! - return nil unless value_nomsu - return Nomsu nil, key_nomsu, ":", value_nomsu - - -Tree "IndexChain", 'multi', - as_lua: (nomsu)=> - lua = @[1]\as_lua(nomsu) - unless lua.is_value - error "Cannot index #{colored.yellow repr(@[1])}, since it's not an expression.", 0 - first_char = tostring(lua)\sub(1,1) - if first_char == "{" or first_char == '"' or first_char == "[" - lua\parenthesize! - - for i=2,#@ - key = @[i] - key_lua = key\as_lua(nomsu) - unless key_lua.is_value - error "Cannot use #{colored.yellow repr(key)} as an index, since it's not an expression.", 0 - key_lua_str = tostring(key_lua) - if lua_id = key_lua_str\match("^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") - lua\append ".#{lua_id}" - elseif key_lua_str\sub(1,1) == '[' - -- NOTE: this *must* use a space after the [ to avoid freaking out - -- Lua's parser if the inner expression is a long string. Lua - -- parses x[[[y]]] as x("[y]"), not as x["y"] - lua\append "[ ",key_lua," ]" - else - lua\append "[",key_lua,"]" - return lua - - as_nomsu: (inline=false)=> - nomsu = Nomsu! - for i, bit in ipairs @ - if i > 1 - nomsu\append "." - bit_nomsu = bit\as_nomsu(true) - return nil unless bit_nomsu - if bit.type == "Action" or bit.type == "Block" - bit_nomsu\parenthesize! - nomsu\append bit_nomsu - return nomsu - -Tree "Number", 'single', - as_lua: (nomsu)=> - Lua.Value(nil, tostring(@value)) - - as_nomsu: (inline=false)=> - return Nomsu(nil, tostring(@value)) - -Tree "Var", 'single', - as_lua_id: (v)-> - "_"..(v\gsub("%W", (c)-> if c == "_" then "__" else ("_%x")\format(c\byte!))) - - as_lua: (nomsu)=> - Lua.Value(nil, self.as_lua_id(@value)) - - as_nomsu: (inline=false)=> - return Nomsu(nil, "%", @value) - -Tree "Word", 'single', - as_lua: (nomsu)=> - error("Attempt to convert Word to lua") - - as_nomsu: (inline=false)=> - return Nomsu(nil, @value) - -Tree "Comment", 'single', - as_lua: (nomsu)=> - Lua(nil, "--"..@value\gsub("\n","\n--").."\n") - - as_nomsu: (inline=false)=> - return nil if inline - if @value\match("\n") - return Nomsu(nil, "#..", @value\gsub("\n", "\n ")) - else - return Nomsu(nil, "#", @value) - return Types