aboutsummaryrefslogtreecommitdiff
path: root/nomsu.moon
diff options
context:
space:
mode:
Diffstat (limited to 'nomsu.moon')
-rwxr-xr-xnomsu.moon479
1 files changed, 459 insertions, 20 deletions
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!