aboutsummaryrefslogtreecommitdiff
path: root/nomnom
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2018-09-21 00:30:28 -0700
committerBruce Hill <bruce@bruce-hill.com>2018-09-21 00:30:44 -0700
commitf2048235f5cc7ff02db39a0e2fe5c79c7f390e0b (patch)
tree738faa0d4692e53d0fe2deb61399b6d7a9eedc9f /nomnom
parent79d4bd5125de7ff220fbf8a8a5493d437ed16963 (diff)
Incremental checkin, currently not working, just saving progress.
Diffstat (limited to 'nomnom')
-rw-r--r--nomnom/ast.nom88
-rw-r--r--nomnom/code_obj.nom180
-rw-r--r--nomnom/compile.nom207
-rw-r--r--nomnom/decompile.nom339
-rw-r--r--nomnom/files.nom123
-rw-r--r--nomnom/parser.nom81
-rw-r--r--nomnom/pretty_errors.nom75
-rw-r--r--nomnom/source.nom49
8 files changed, 1142 insertions, 0 deletions
diff --git a/nomnom/ast.nom b/nomnom/ast.nom
new file mode 100644
index 0000000..2d5a894
--- /dev/null
+++ b/nomnom/ast.nom
@@ -0,0 +1,88 @@
+use "lib/object.nom"
+
+#%types = [..]
+ "Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry",
+ "IndexChain", "Action", "FileChunks", "Error", "Comment"
+
+object (Syntax Tree):
+ my action [set up]:
+ if (%me.type == "Action"):
+ %stub_bits = []
+ %argnum = 1
+ for %bit in %me:
+ if:
+ (%bit is text): %stub_bits::add %bit
+ (%bit.type != "Comment"):
+ %stub_bits::add "\%argnum"
+ %argnum += 1
+ %me.stub = (%stub_bits::joined with " ")
+
+ (Syntax Tree).source_code_for_tree = (..)
+ {} with fallback % -> (read file %.source.filename)
+
+ my action [children]:
+ %children = []
+ for % in %me:
+ if ((% is a "Syntax Tree") and (%.type != "Comment")):
+ %children::add %
+ if ((%me.type == "Action") and %me.target):
+ %children::add %me.target
+ return %children
+
+ my action [as lua] (..)
+ "Syntax_Tree(\(call ({}'s metatable).as_lua with [%me]))"
+
+ my action [as nomsu] (..)
+ "(Syntax Tree \(call ({}'s metatable).as_nomsu with [%me]))"
+
+ my action [as text] (..)
+ "(Syntax Tree \(call ({}'s metatable).__tostring with [%me]))"
+
+ my action [get source code] (..)
+ (Syntax Tree).source_code_for_tree.%me
+
+ my action [map %fn]:
+ %replacement = (call %fn with [%me])
+ if %replacement:
+ if (%replacement is a "Syntax Tree"):
+ %replacement = (%k = %v for %k = %v in %replacement)
+ %replacement.source = %me.source
+ return (Syntax Tree %replacement)
+ return %replacement
+ ..else:
+ %replacement = {}
+ %changes = (no)
+ for %k = %v in %me:
+ %replacement.%k = %v
+ if (%v is a "Syntax Tree"):
+ %r = (%v::map %fn)
+ if ((%r == %v) or (%r == (nil))):
+ do next %k
+ %changes = (yes)
+ %replacement.%k = %r
+ unless %changes: return %me
+ return (Syntax Tree %replacement)
+
+ my action [== %other]:
+ unless (..)
+ all of [..]
+ (type of %me) == (type of %other)
+ (%me's metatable) == (%other's metatable)
+ (size of %me) == (size of %other)
+ %me.type == %other.type
+ ..: return (no)
+
+ for %item in %me at %i:
+ if (%other.%i != %item): return (no)
+ if (%me.type == "Action"):
+ if (%me.target != %other.target): return (no)
+ return (yes)
+
+ my action [get args]:
+ assume (%me.type == "Action") or barf "Only actions have arguments, not \(%me.type)"
+ %args = []
+ for % in %me:
+ unless ((% is text) or (%.type == "Comment")):
+ %args::add %
+ return %args
+
diff --git a/nomnom/code_obj.nom b/nomnom/code_obj.nom
new file mode 100644
index 0000000..4df312e
--- /dev/null
+++ b/nomnom/code_obj.nom
@@ -0,0 +1,180 @@
+# This file contains objects that are used to track code positions and incrementally
+ build up generated code, while keeping track of where it came from, and managing
+ indentation levels.
+
+use "lib/object.nom"
+
+object (Code):
+ my action [set up]:
+ %old_bits = %me.bits
+ %me.bits = []
+ if (%me.source is text):
+ %me.source = (Source from text %me.source)
+ for % in %old_bits:
+ %me::add %
+
+ my action [as text]:
+ if (%me.__str == (nil)):
+ set {%buff:[], %indent:0}
+ for % in %me.bits:
+ if (% is text):
+ %spaces = (%::matching "\n([ ]*)[^\n]*$")
+ if %spaces.1: %indent = (size of %spaces)
+ ..else:
+ % = "\%"
+ if (%indent > 0):
+ % = (%::with "\n" -> "\n\(" "::* %indent)")
+ %buff::add %
+ %me.__str = (%buff::joined)
+ return %me.__str
+
+ my action [as lua] (..)
+ "\(%me.class.name::as lua id)_1_2(\(%me.source::as lua), \(%me.bits::as lua))"
+
+ my action [as nomsu] (..)
+ "(\(%me.class.name) \(%me.source::as nomsu) \(%me.bits::as nomsu))"
+
+ my action [size] (size of "\%me")
+
+ my action [mark as dirty]:
+ %me.__str = (nil)
+ %me._trailing_line_len = (nil)
+ %me._num_lines = (nil)
+
+ my action [add %new_bits]:
+ unless (%new_bits is a "List"):
+ %new_bits = [%new_bits]
+ for % in %new_bits:
+ if (% == ""): do next %
+ if ((% isn't text) and (% isn't a (Code))):
+ % = (%::as lua)
+ %me.bits::add %
+ %me::mark as dirty
+
+ my action [trailing line length]:
+ if (%me._trailing_line_len == (nil)):
+ %me._trailing_line_len = (size of ("\%me"::matching "[^\n]*$"))
+ return %me._trailing_line_len
+
+ my action [number of lines]:
+ unless %me._num_lines:
+ %num_lines = 1
+ for % in %me:
+ if (% is text):
+ %num_lines += (size of (%::all matches of "\n"))
+ ..else:
+ %num_lines += ((%::number of lines) - 1)
+ %me._num_lines = %num_lines
+ return %me._num_lines
+
+ my action [is multiline, is multi-line] ((%me::number of lines) > 1)
+ my action [is one line, is single line] ((%me::number of lines) == 1)
+
+ my action [add %values joined with %joiner]:
+ %me::add %values joined with %joiner or %joiner
+
+ my action [add %values joined with %joiner or %wrapping_joiner]:
+ %line_len = 0
+ %bits = %me.bits
+ for %i = % in %values:
+ if (%i > 1):
+ if (%line_len > 80):
+ %bits::add %wrapping_joiner
+ %line_len = 0
+ ..else:
+ %bits::add %joiner
+ %bits::add %
+ %line = ("\%"::matching "\n([^\n]*)$")
+ if %line:
+ %line_len = (size of %line)
+ ..else:
+ %line_len += (size of %)
+ %me::mark as dirty
+
+ my action [prepend %]:
+ if ((% isn't text) and (% isn't a %me.__type)):
+ % = (%::as lua)
+ %me.bits::add % at index 1
+ %me::mark as dirty
+
+ my action [parenthesize]:
+ %me.bits::add "(" at index 1
+ %me.bits::add ")"
+ %me::mark as dirty
+
+
+object (Lua Code) extends (Code):
+ my action [add free vars %vars]:
+ if ((size of %vars) == 0): return
+ %seen = (%v = (yes) for %v in %me.free_vars)
+ for %var in %vars:
+ assume (%var is text)
+ unless %seen.%var:
+ %me.free_vars::add %var
+ %seen.%var = (yes)
+ %me::mark as dirty
+
+ my action [remove free vars %vars]:
+ if ((size of %vars) == 0): return
+ %removals = {}
+ for %var in %vars:
+ assume (%var is text)
+ %removals.%var = (yes)
+
+ %stack = [%me]
+ while ((size of %stack) > 0):
+ %lua = (%stack::pop)
+ for %i in (size of %lua.free_vars) to 1 by -1:
+ if %removals.(%lua.%free_vars.%i):
+ %lua.free_vars::remove index %i
+ for % in %lua.bits:
+ if (% is a "Lua Code"):
+ %stack::add %
+ %me::mark as dirty
+
+ my action [declare locals]:
+ set {%to_declare:[], %seen:{}}
+ for %lua in recursive %me:
+ for %var in %lua.free_vars:
+ unless %seen.%var:
+ %seen.%var = (yes)
+ %to_declare::add %var
+ for % in %lua.bits:
+ if (% is a "Lua Code"):
+ recurse %lua on %
+ return (%me::declare locals %to_declare)
+
+ my action [declare locals %to_declare]:
+ if ((size of %to_declare) > 0):
+ %me::remove free vars %to_declare
+ %me::prepend "local \(%to_declare::joined with ", ");\n"
+ return %to_declare
+
+ my action [as statements] (%me::as statements "" ";")
+ my action [as statements %prefix] (%me::as statements %prefix ";")
+ my action [as statements %prefix %suffix]:
+ unless %me.is_value:
+ return %me
+ %statements = (Lua Code %me.source [])
+ if (%prefix != ""):
+ %statements::add %prefix
+ %statements::add %me
+ if (%suffix != ""):
+ %statements::add %suffix
+ return %statements
+
+ action [Lua Code from %tree] (..)
+ Lua Code {source:%tree.source, bits:[], is_value:(no), free_vars:[]}
+ action [Lua Code from %tree %bits] (..)
+ Lua Code {source:%tree.source, bits:%bits, is_value:(no), free_vars:[]}
+
+ action [Lua Value from %tree] (..)
+ Lua Code {source:%tree.source, bits:[], is_value:(yes), free_vars:[]}
+ action [Lua Value from %tree %bits] (..)
+ Lua Code {source:%tree.source, bits:%bits, is_value:(yes), free_vars:[]}
+
+object (Nomsu Code) extends (Code):
+ action [Nomsu Code from %tree] (..)
+ Nomsu Code {source:%tree.source, bits:[]}
+ action [Nomsu Code from %tree %bits] (..)
+ Nomsu Code {source:%tree.source, bits:%bits}
diff --git a/nomnom/compile.nom b/nomnom/compile.nom
new file mode 100644
index 0000000..ec48f05
--- /dev/null
+++ b/nomnom/compile.nom
@@ -0,0 +1,207 @@
+# This file contains the code to convert syntax trees to Lua code
+
+use "nomnom/code_obj.nom"
+
+action [compile %tree using %compile_actions]:
+ assume (%tree is a "Syntax Tree")
+ if (..)
+ all of [..]
+ %tree.version, action (Nomsu version)
+ %tree.version != (Nomsu version)
+ action (1 upgraded from 2 to 3)
+ ..: %tree = (upgrade %tree from %tree.version to (Nomsu version))
+ if %tree.type is:
+ "Action":
+ %stub = %tree.stub
+ %compile_action = %compile_actions.%stub
+ if %compile_action:
+ %args = [%tree, %compile_actions]
+ for % in (%tree::get args): %args::add %
+ %result = (call %compile_action with %args)
+ if (%result == (nil)):
+ compile error at %tree.source "\
+ ..The compile-time action here (\(%tree.stub)) failed to return any value."
+ ..hint "\
+ ..Look at the implementation of (\(%tree.stub)) and make sure it's returning something."
+ if (%result is a "Syntax Tree"):
+ if (%result == %tree):
+ compile error at %tree.source "\
+ ..The compile-time action here (\(%tree.stub)) is producing an endless loop."
+ ..hint "\
+ ..Look at the implementation of (\(%tree.stub)) and make sure it's not just returning the original tree."
+ return (compile %result using %compile_actions)
+ return %result
+
+ %lua = (new Lua Value from %tree)
+ if %tree.target: # Method call
+ %target_lua = (compile %tree.target using %compile_actions)
+ if (("\%target_lua"::matches "^%(.*%)$") or ("\%target_lua"::matches "^[_a-zA-Z][_a-zA-Z0-9]*$")):
+ %lua::add [%target_lua, ":"]
+ ..else:
+ %lua::add ["(", %target_lua, "):"]
+ %lua:add [%stub as lua id, "("]
+ %args = []
+ for %tok in %tree:
+ if (%tok is text): do next %tok
+ # TODO: maybe translate Lua comments
+ if (%tok.type == "Comment"): do next %tok
+ if (%tok.type == "Block"):
+ %values = (..)
+ (compile %line using %compile_actions) for %line in %tok
+ ..unless (%line.type == "Comment")
+ if (all of (%.is_value for % in %values)):
+ if ((size of %values) == 1):
+ %arg_lua = %values.1
+ ..else:
+ %arg_lua = (new Lua Value from %tok ["("])
+ %arg_lua::add %values joined with " and nil or "
+ %arg_lua::add ")"
+ ..else:
+ %arg_lua = (new Lua Value from %tok ["((function()"])
+ for %v in %values at %i:
+ if %v.is_value:
+ %v = (%v::as statements ("return " if (%i == (size of %values) else "")))
+ %arg_lua::add ["\n ", %v]
+ %arg_lua::add "\nend)())"
+ ..else:
+ %arg_lua = (compile %tok using %compile_actions)
+ unless %arg_lua.is_value:
+ if (%tok.type == "Action"):
+ compile error at %tok "\
+ ..Can't use this as an argument to (\%stub), since it's not \
+ ..an expression, it produces: \%arg_lua"
+ ..hint "\
+ ..Check the implementation of (\(%tok.stub)) to see if it \
+ ..is actually meant to produce an expression."
+ ..else:
+ compile error at %tok "\
+ ..Can't use this as an argument to (\%stub), since it's \
+ ..not an expression, it produces: \%arg_lua"
+ %args::add %arg_lua
+ %lua::add %args joined with ", "
+ %lua::add ")"
+ return %lua
+
+ "EscapedNomsu":
+ return (new Lua Value from %tree ((%tree.1)::as nomsu))
+
+ "Block":
+ %lua = (new Lua Code from %tree)
+ %lua::add (..)
+ ((compile %line using %compile_actions)::as statements)
+ ..for %line in %tree
+ ..joined with "\n"
+ return %lua
+
+ "Text":
+ %lua = (new Lua Code from %tree)
+ %lua_bits = []
+ %string_buffer = ""
+ for % in %tree:
+ if (% is text):
+ %string_buffer = "\%string_buffer\%"
+ do next %
+ if (%string_buffer != ""):
+ %lua_bits::add (%string_buffer::as lua)
+ %string_buffer = ""
+ %bit_lua = (compile %bit using %compile_actions)
+ unless %bit_lua.is_value:
+ compile error at %bit "\
+ ..Can't use this as a string interpolation value, since it doesn't have a value."
+ if (%bit.type != "Text"):
+ %bit_lua = (new Lua Value from %bit ["tostring(",%bit_lua,")"])
+ %lua_bits::add %bit_lua
+
+ if ((%string_buffer != "") or ((size of %lua_bits) == 0)):
+ %lua_bits::add (%string_buffer::as lua)
+
+ %lua::add %lua_bits joined with ("..")
+ if ((size of %lua_bits) > 1):
+ %lua::parenthesize
+ return %lua
+
+ "List":
+ %lua = (new Lua Value from %tree ["_List{"])
+ %lua::add ((compile % using %compile_actions) for % in %tree) joined with ", " or ",\n "
+ %lua::add "}"
+ return %lua
+
+ "Dict":
+ %lua = (new Lua Value from %tree ["_Dict{"])
+ %lua::add ((compile % using %compile_actions) for % in %tree) joined with ", " or ",\n "
+ %lua::add "}"
+ return %lua
+
+ "DictEntry":
+ set {%key:%tree.1, %value:%tree.2}
+ %key_lua = (compile %key using %compile_actions)
+ unless %key_lua.is_value:
+ compile error at %tree.1 "\
+ ..Can't use this as a dict key, since it's not an expression."
+ %value_lua = (..)
+ (compile %value using %compile_actions) if %value
+ ..else (new Lua Value from %key ["true"]))
+ unless %value_lua.is_value:
+ compile error at %tree.2 "\
+ ..Can't use this as a dict value, since it's not an expression."
+ %key_str = ("\%key_lua"::matching "^[\"']([a-zA-Z_][a-zA-Z0-9_]*)['\"]$")
+ if:
+ %key_str:
+ return (new Lua Code from %tree [%key_str, "=", %value_lua])
+ ("\%key_lua".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"]
+ return (new Lua Code from %tree ["[ ", %key_lua, "]=", %value_lua])
+ else:
+ return (new Lua Code from %tree ["[", %key_lua, "]=", %value_lua])
+
+ "IndexChain":
+ %lua = (compile %tree.1 using %compile_actions)
+ unless %lua.is_value:
+ compile error at %tree.1 "\
+ ..Can't index into this, since it's not an expression."
+ %first_char = "\%lua".1
+ if (any of [%first_char == "{", %first_char == "\"", %first_char == "["]):
+ %lua::parenthesize
+
+ for %i in 2 to (size of %tree):
+ %key = %tree.%i
+ %key_lua = (compile %key using %compile_actions)
+ unless %key_lua.is_value:
+ compile error at %key "\
+ ..Can't use this as an index, since it's not an expression."
+ %key_lua_str = "\%key_lua"
+ %lua_id = (%key_lua_str::matching "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$")
+ if:
+ %lua_id:
+ %lua::add [".", %lua_id]
+ (%key_lua_str.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::add ["[ ", %key_lua, " ]"]
+ else:
+ %lua::add ["[", %key_lua, "]"]
+ return %lua
+
+ "Number":
+ return (new Lua Value from %tree ["\(%tree.1)"])
+
+ "Var":
+ return (new Lua Value from %tree [%tree.1::as lua id])
+
+ "FileChunks":
+ barf "\
+ ..Can't convert FileChunks to a single block of lua, since each chunk's \
+ ..compilation depends on the earlier chunks"
+
+ "Comment":
+ # TODO: implement?
+ return (new Lua Code from %tree)
+
+ "Error":
+ barf "Can't compile errors"
+
+ else:
+ barf "Unknown type: \(%tree.type)"
diff --git a/nomnom/decompile.nom b/nomnom/decompile.nom
new file mode 100644
index 0000000..27ef406
--- /dev/null
+++ b/nomnom/decompile.nom
@@ -0,0 +1,339 @@
+# This file contains the code to convert syntax trees to Nomsu code
+use "nomnom/code_obj.nom"
+use "nomnom/parser.nom"
+
+# TODO: maybe re-implement the fancy coroutine checker that aborts early if nomsu gets too long
+action [decompile %tree inline]:
+ assume (%tree is a "Syntax Tree")
+ if %tree.type is:
+ "Action":
+ %nomsu = (Nomsu Code from %tree)
+ if %tree.target:
+ %target_nomsu = (decompile %tree.target inline)
+ if %tree.target.type is:
+ ("Action", "Block"):
+ %target_nomsu::parenthesize
+ %nomsu::add [%target_nomsu, "::"]
+
+ for %bit in %tree at %i:
+ if (%bit is text):
+ unless (..)
+ any of [..]
+ %i == 1,
+ %tree.(%i - 1) isn't text,
+ (%bit::is a nomsu operator) == (%tree.(%i - 1)::is a nomsu operator)
+ ..: %nomsu::add " "
+ %nomsu::add %bit
+ ..else:
+ %arg_nomsu = (decompile %bit inline)
+ unless ((%i == (size of %tree)) and (%bit.type == "Block")):
+ %nomsu::add " "
+ if ((%bit.type == "Action") or (%bit.type == "Block")):
+ %arg_nomsu::parenthesize
+ %nomsu::add %arg_nomsu
+ return %nomsu
+
+ "EscapedNomsu":
+ %inner_nomsu = (decompile %tree.1 inline)
+ unless (..)
+ any of [..]
+ %tree.1.type == "List", %tree.1.type == "Dict",
+ %tree.1.type == "Var"
+ ..: %inner_nomsu::parenthesize
+ %nomsu = (Nomsu Code from %tree ["\\", %inner_nomsu])
+ return %nomsu
+
+ "Block":
+ %nomsu = (Nomsu Code from %tree [":"])
+ for i,line in ipairs tree
+ %nomsu::add(i == 1 and " " or "; ")
+ %nomsu::add recurse(line, nomsu, i == 1 or i < #tree)
+ return %nomsu
+
+ "Text":
+ %nomsu = (Nomsu Code from %tree ["\""])
+ for %text in recursive %tree:
+ for %bit in %text at %i:
+ if:
+ (%bit is text):
+ %nomsu::add %bit
+ if %bit.type is:
+ "Text":
+ recurse %text on %bit
+ "Var":
+ %interp_nomsu = (decompile %bit inline)
+ # Make sure "...\(%x)y..." isn't confused with "...\(%xy)..."
+ # TODO: make this more robust against "...\%x\("y").."
+ if (..)
+ (%tree.(%i + 1) is text) and (..)
+ not (%tree.(%i + 1)::matches "^[ \n\t,.:;#(){}%[%]]")
+ ..: %interp_nomsu::parenthesize
+ %nomsu::add ["\\", %interp_nomsu]
+ ("List", "Dict"):
+ %nomsu::add ["\\", decompile %bit inline]
+ else:
+ %nomsu::add ["\\(", decompile %bit inline, ")"]
+ return (Nomsu Code from %tree ["\"", %nomsu, "\""])
+
+ ("List", "Dict"):
+ %nomsu = (Nomsu Code from %tree ["[" if (%tree.type == "List") else "{"])
+ for %item in %tree at %i:
+ if (%i > 1): %nomsu::add ", "
+ %nomsu::add (decompile %item inline)
+ %nomsu::add ("]" if (%tree.type == "List") else "}")
+ return %nomsu
+
+ "DictEntry":
+ set {%key:%tree.1, %value:%tree.2}
+ if (all of [%key.type == "Text", (size of %key) == 1, %key.1::is a nomsu identifier]):
+ %nomsu = (Nomsu Code from %key [key.1])
+ ..else:
+ %nomsu = (decompile %key inline)
+
+ if (%key.type == "Action"):
+ %nomsu::parenthesize
+ %nomsu::add ":"
+ if %value:
+ %nomsu::add (decompile %value inline)
+ return %nomsu
+
+ "IndexChain":
+ %nomsu = (Nomsu Code from %tree)
+ for %bit in %tree at %i:
+ if (%i > 1): nomsu::add "."
+ if (..)
+ all of [..]
+ %i > 1, %bit.type == "Text", (size of %bit) == 1, %bit.1 is text,
+ %bit.1::is a nomsu identifier
+ %nomsu::add %bit.1
+ ..else:
+ %bit_nomsu = (decompile %bit inline)
+ if (..)
+ any of [..]
+ %bit.type == "Action"
+ %bit.type == "Block"
+ %bit.type == "IndexChain"
+ (%bit.type == "Number") and (%i < (size of %tree))
+ ..: %bit_nomsu::parenthesize
+ %nomsu::add %bit_nomsu
+ return %nomsu
+
+ "Number":
+ return (Nomsu Code from %tree [(%tree.1 as hex) if %tree.hex else "\(%tree.1)"])
+
+ "Var":
+ return (Nomsu Code from %tree ["%\(%tree.1)"])
+
+ "Comment":
+ return (nil)
+
+ "FileChunks":
+ barf "Can't inline a FileChunks"
+
+ "Error":
+ barf "Can't compile errors"
+
+ else:
+ barf "Unknown type: \(%tree.type)"
+
+%MAX_LINE = 90
+action [decompile %tree]:
+ %nomsu = (Nomsu Code from %tree)
+ # For concision:
+ local action [recurse on %t]:
+ %space = (%MAX_LINE - (%nomsu::trailing line length))
+ if (%space <= 0): go to (Indented)
+ for %subtree in recursive %tree:
+ if %subtree.type is:
+ "Block":
+ if ((size of %subtree) > 1):
+ go to (Use Indented)
+ if ((size of "\(decompile %subtree inline)") > 20):
+ go to (Use Indented)
+ for %k = %v in %subtree:
+ if (%v is a "Syntax Tree"):
+ recurse %subtree on %v
+
+ %inline_nomsu = (decompile %t inline)
+ if (%inline_nomsu and ((size of "\%inline_nomsu") <= %space)):
+ return %inline_nomsu
+
+ === (Use Indented) ===
+ %indented = (decompile %t)
+ if (%t.type == "Action"):
+ %indented = (Nomsu Code from %t ["(..)\n ", %indented])
+ return %indented
+
+ if %tree.type is:
+ "FileChunks":
+ local action [%1 and %2 should clump]:
+ if ((%1.type == "Action") and (%2.type == "Action")):
+ if (%1.stub == "use 1"): return (%2.stub == "use 1")
+ if (%1.stub == "test 1"): return (yes)
+ if (%2.stub == "test 1"): return (no)
+ return (not ((recurse on %1)::is multi-line))
+ for %chunk in %tree at %chunk_no:
+ if (%chunk_no > 1):
+ %nomsu::add "\n\n\("~"::* 80)\n\n"
+ %nomsu::add (pop comments at %chunk.source.start)
+ if (%chunk.type == "Block"):
+ for %line in %chunk at %line_no:
+ if (%line_no > 1):
+ if (%chunk.(%line_no - 1) and %line should clump):
+ %nomsu::add ["\n", pop comments at %line.source.start "\n"]
+ ..else:
+ %nomsu::add ["\n\n", pop comments at %line.source.start]
+ %nomsu::add (decompile %line %pop_comments)
+ %nomsu::add (pop comments at %chunk.source.stop "\n")
+ ..else:
+ %nomsu::add (decompile %chunk %pop_comments)
+ %nomsu::add (pop comments at %tree.source.stop "\n")
+ unless ("\%nomsu"::matches "\n$"):
+ %nomsu::add "\n"
+ return %nomsu
+
+ "Action":
+ %pos = %tree.source.start
+ %next_space = ""
+ if %tree.target:
+ %target_nomsu = (recurse on %tree.target)
+ if ((%tree.target.type == "Action") and (%target_nomsu::is one line)):
+ %target_nomsu::parenthesize
+ %nomsu::add %target_nomsu
+ %pos = %tree.target.source.stop
+ %next_space = ("\n..::" if (%target_nomsu::is multi-line) else "::")
+
+ for %bit in %tree at %i:
+ if ((%next_space == " ") and ((%nomsu::trailing line length) > %MAX_LINE)):
+ %next_space = " \\\n"
+
+ %nomsu::add %next_space
+
+ if (%bit is text):
+ unless (..)
+ all of [..]
+ %tree.(%i - 1) is text
+ (%tree.(%i - 1)::is a nomsu operator) != (%bit::is a nomsu operator)
+ ..: %nomsu::add %next_space
+ %nomsu::add %bit
+ %next_space = " "
+ do next %bit
+
+ %bit_nomsu = (recurse on %bit)
+ if (%bit.type == "Comment"):
+ %next_space = "\n"
+ ..else:
+ %next_space = (" " if (%bit_nomsu::is one line) else "\n..")
+ if (%bit.type == "Action"):
+ %bit_nomsu::parenthesize
+
+ return %nomsu
+
+ "EscapedNomsu":
+ %nomsu::add "\\"
+ %val_nomsu = (recurse on %tree.1)
+ if ((%tree.(1).type == "Action") and (%val_nomsu::is one line)):
+ %val_nomsu::parenthesize
+ %nomsu::add %val_nomsu
+ return %nomsu
+
+ "Block":
+ for %line in %tree at %i:
+ if ((%i > 1) and (%line.type == "Comment")):
+ %nomsu::add "\n"
+ %line_nomsu = (recurse on %line)
+ %nomsu::add
+ if (%i < (size of %tree)):
+ if ((%line_nomsu::number of lines) > 2):
+ %nomsu::add "\n\n"
+ ..else:
+ %nomsu::add "\n"
+ return (Nomsu Code from %tree [":\n ", %nomsu])
+
+ "Text":
+ # Multi-line text has more generous wrap margins
+ %max_line = ((1.5 * %MAX_LINE) rounded down)
+ %nomsu = (Nomsu Code from %tree)
+ local action [add text from %tree]:
+ for %bit in %tree at %i:
+ if (%bit is text):
+ # TODO: escape properly?
+ %bit = (escape text %bit)
+ for %line in (%bit::lines) at %j:
+ if:
+ (%j > 1): nomsu::add "\n"
+ (((size of %line) > 10) and ((%nomsu::trailing line length) > %max_line)):
+ %nomsu::add "\\\n.."
+
+ while ((size of %line) > 0):
+ %space = (%max_line - (%nomsu::trailing line length))
+ %split = (%line::position of "[%p%s]" after %space)
+ if ((not %split) or (%split > %space + 10)):
+ %split = (%space + 10)
+ if ((%line - %split) < 10):
+ %split = (size of %line)
+ set {%bite:%line.[1, %split], %line:%line.[%split + 1, -1]}
+ %nomsu::add %bite
+ if ((size of %line) > 0):
+ %nomsu::add "\\\n.."
+ if (%bit.type == "Text"):
+ add text from %bit
+ ..else:
+ %nomsu::add "\\"
+ %interp_nomsu = (recurse on %bit)
+ unless (%interp_nomsu::is multi-line):
+ if %bit.type is:
+ "Var":
+ if ((%tree.(%i+1) is text) and (not (%tree.(%i+1)::matches "^[ \n\t,.:#(){}[%]]"))):
+ %interp_nomsu::parenthesize
+ ("List", "Dict"):
+ %interp_nomsu::parenthesize
+ %nomsu::add %interp_nomsu
+ if (%interp_nomsu::is multi-line):
+ %nomsu::add "\n.."
+ add text from %tree
+ return (Nomsu Code from %tree ["\"\\\n ..", %nomsu, "\""])
+
+ ("List", "Dict"):
+ if ((size of %tree) == 0):
+ %nomsu::add ("[]" if (%tree.type == "List") else "{}")
+ return %nomsu
+ for %item in %tree at %i:
+ %item_nomsu = (decompile %item inline)
+ if ((not %item_nomsu) or ((size of "\%item_nomsu") > %MAX_LINE)):
+ %item_nomsu = (recurse on %item_nomsu)
+ %nomsu::add %item_nomsu
+ if (%i < (size of %tree)):
+ if (..)
+ (%item_nomsu::is multi-line) or (..)
+ (%nomsu::trailing line length) + (size of "\%item_nomsu")) >= %MAX_LINE
+ ..: %nomsu::add "\n"
+ ..else: %nomsu::add ", "
+ return (..)
+ Nomsu Code from %tree [..]
+ "[..]\n " if tree.type == "List" else "{..}\n "
+ %nomsu
+
+ "DictEntry":
+ set {%key:%tree.1, %value:%tree.2}
+ if (all of [%key.type == "Text", (size of %key) == 1, %key.1::is a nomsu identifier]):
+ %nomsu::add %key.1
+ ..else:
+ %nomsu::add (decompile %key inline)
+ if ((%key.type == "Action") or (%key.type == "Block")):
+ %nomsu::parenthesize
+ %nomsu::add [": ", recurse on %value]
+ return %nomsu
+
+ "Comment":
+ %nomsu::add ["#", %tree.1::with "\n" -> "\n "]
+ return %nomsu
+
+ ("IndexChain", "Number", "Var"):
+ return (decompile %tree inline)
+
+ "Error":
+ barf "Cannot decompile an error"
+
+ else:
+ barf "Unknown type: \(%tree.type)"
diff --git a/nomnom/files.nom b/nomnom/files.nom
new file mode 100644
index 0000000..8fd8f24
--- /dev/null
+++ b/nomnom/files.nom
@@ -0,0 +1,123 @@
+# Some file utilities for searching for files recursively and using package.nomsupath
+use "lib/os.nom"
+
+%_SPOOFED_FILES = {}
+%_FILE_CACHE = ({} with fallback %_SPOOFED_FILES)
+%_BROWSE_CACHE = {}
+
+# Create a fake file and put it in the cache
+action [spoof file %filename %contents]:
+ %_SPOOFED_FILES.%filename = %contents
+ return %contents
+
+# Read a file's contents
+action [read file %filename]:
+ %contents = %_FILE_CACHE.%filename
+ if %contents: return %contents
+ if (%filename == "stdin"):
+ return (spoof file "stdin" (=lua "io.read('*a')"))
+ %file = (=lua "io.open(\%filename)")
+ unless %file: return (nil)
+ %contents = (call %file.read with [%file, "*a"])
+ %file::close
+ %_FILE_CACHE.%filename = %contents
+ return %contents
+
+action [%path sanitized]:
+ %path = (%path::with "\\" -> "\\\\")
+ %path = (%path::with "`" -> "")
+ %path = (%path::with "\"" -> "\\\"")
+ %path = (%path::with "$" -> "")
+ %path = (%path::with "%.%." -> "\\..")
+ return %path
+
+try:
+ %lfs = (=lua "require('lfs')")
+..and if it succeeds:
+ local action [filesystem has %filename]:
+ %mode = (call %lfs.attributes with [%filename, "mode"])
+ if %mode is:
+ ("file", "directory", "link", "char device"):
+ return (yes)
+ else: return (no)
+
+ action [file %path exists]:
+ if (..)
+ any of [..]
+ %_SPOOFED_FILES.%path
+ %path == "stdin"
+ filesystem has %path
+ ..: return (yes)
+ for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
+ if (filesystem has "\%nomsupath/\%path"):
+ return (yes)
+ return (no)
+
+ action [files in %path]:
+ unless %_BROWSE_CACHE.%path:
+ if (%_SPOOFED_FILES.%path or (%filename == "stdin")):
+ %_BROWSE_CACHE.%path = [%path]
+ ..else:
+ if (call %lfs.attributes with [%filename, "mode"]) is:
+ ("file", "char device"):
+ %_BROWSE_CACHE.%path = [%filename]
+ ("directory", "link"):
+ for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
+ %files = []
+ for %member in (call %lfs.dir with ["\%nomsupath/\%filename"]):
+ if ((%member == ".") or (%member == "..")):
+ do next %member
+ for % in (files in %member): %files::add %
+ if ((size of %files) > 0):
+ %_BROWSE_CACHE.%path = %files
+ go to (Found Files)
+
+ %_BROWSE_CACHE.%path = []
+ else:
+ %_BROWSE_CACHE.%path = []
+
+ === (Found Files) ===
+ return %_BROWSE_CACHE.%filename
+
+..or if it barfs:
+ # LFS not found! Fall back to shell commands, if available.
+ unless (sh> "find . -maxdepth 0"):
+ url = if jit
+ 'https://github.com/spacewander/luafilesystem'
+ else
+ barf "\
+ ..Could not find 'luafilesystem' module and couldn't run system command 'find' \
+ ..(this might happen on Windows). Please install 'luafilesystem' (which can be \
+ ..found at \(..)
+ "https://github.com/spacewander/luafilesystem"
+ ..if %jit else "https://github.com/keplerproject/luafilesystem"
+ .. or obtained through `luarocks install luafilesystem`)"
+
+
+ action [file %path exists]:
+ if (..)
+ any of [..]
+ %_SPOOFED_FILES.%path
+ %path == "stdin"
+ sh> "ls \(%path sanitized)"
+ ..: return (yes)
+ for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
+ if (sh> "ls \(%nomsupath)/\(%path)"):
+ return (yes)
+ return (no)
+
+ action [files in %path]:
+ unless %_BROWSE_CACHE.%path:
+ if %_SPOOFED_FILES.%path:
+ %_BROWSE_CACHE.%path = [%_SPOOFED_FILES.%path]
+ ..else:
+ for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
+ %files = (sh> "find -L '\%path' -not -path '*/\\.*' -type f'")
+ if %files:
+ %_BROWSE_CACHE.%path = (%files::lines)
+ go to (Found Files)
+ %_BROWSE_CACHE.%path = []
+
+ === (Found Files) ===
+ return %_BROWSE_CACHE.%path
+
diff --git a/nomnom/parser.nom b/nomnom/parser.nom
new file mode 100644
index 0000000..60c356f
--- /dev/null
+++ b/nomnom/parser.nom
@@ -0,0 +1,81 @@
+# This file contains the parser, which converts text into abstract syntax trees
+use "nomonom/ast.nom"
+
+%lpeg = (=lua "require('lpeg')")
+%re = (=lua "require('re')")
+call %lpeg.setmaxstack with [20_000]
+set {..}
+ (action (P 1)): %lpeg.P, (action (R 1)): %lpeg.R, (action (Carg 1)): %lpeg.Carg,
+ (action (Cc 1)): %lpeg.Cc, (action (lpeg re pattern 1)): %re.compile,
+ (action (lpeg re pattern 1 using 2)): %re.compile
+
+%source_code_for_tree = {}
+%defs = (..)
+ {..}
+ nl: (P "\r")^(-1) * (P "\n")
+ tab: P "\t"
+ tonumber: %tonumber
+ tochar: %string.char
+ unpack: %unpack
+ nil: Cc (nil)
+ userdata: Carg 1
+ utf8_char: (..)
+ (R "\194\223")*(R "\128\191") +
+ (R "\224\239")*(R "\128\191")*(R "\128\191") +
+ (R "\240\244")*(R "\128\191")*(R "\128\191")*(R "\128\191")
+
+ Tree: [%t, %userdata] ->:
+ %source = (..)
+ Source {filename:%userdata.filename, start:%tree.start, stop:%tree.stop}
+ set {%t.start: nil, %t.stop: nil}
+ %t = (Syntax Tree %t)
+ (Syntax Tree).source_code_for_tree.%t = %userdata.source
+ return %t
+
+ ..with fallback %key ->:
+ if:
+ (%key::matches "^ascii_(%d+)$"):
+ %i = (%key::matching "^ascii_(%d+)$")
+ return (call %string.char with [%i as a number])
+ (%key::matches "^number_(%d+)$"):
+ %i = (%key::matching "^number_(%d+)$")
+ return (Cc (%i as a number))
+
+%id_patt = (((P "") - (R "09")) * ((%defs.utf8_char + (R "az") + (R "AZ") + (P "_") + (R "09"))^1 * -1))
+%operator_patt = ((S "'`~!@$^&*+=|<>?/-")^1 * -1)
+%text_methods = (""'s metatable).__index
+%text_methods.(action (is a nomsu identifier)) = (..)
+ [%str] -> (call %id_patt.match with [%id_patt, %str])
+%text_methods.(action (is a nomsu id)) = %text_methods.(action (is a nomsu identifier))
+%text_methods.(action (is a nomsu operator)) = (..)
+ [%str] -> (call %operator_patt.match with [%operator_patt, %str])
+
+%peg_tidier = (..)
+ lpeg re pattern "\
+ file <- %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~}
+ def <- anon_def / captured_def
+ anon_def <-
+ ({ident} (" "*) ":" {[^%nl]* (%nl+ " "+ [^%nl]*)*})
+ -> "%1 <- %2"
+ captured_def <-
+ ({ident} (" "*) "(" {ident} ")" (" "*) ":" {[^%nl]* (%nl+ " "+ [^%nl]*)*})
+ -> "%1 <- ({| {:start:{}:} %3 {:stop:{}:} {:type: (''->'%2') :} |} %%userdata) -> Tree"
+ ident <- [a-zA-Z_][a-zA-Z0-9_]*
+ comment <- "--" [^%nl]*
+ "
+
+action [make parser from %peg] (make parser from %peg using (nil))
+
+action [make parser from %peg using %make_tree]:
+ %peg = (call %peg_tidier.match with [%peg_tidier, %peg])
+ %peg = (lpeg re pattern %peg using %defs)
+ local action [parse %input from %filename]:
+ %input = "\%input"
+ %tree_mt = {__index: {source:%input, filename:%filename}}
+ %userdata = {..}
+ make_tree: %make_tree or ([%]-> (: set %'s metatable to %tree_mt; return %))
+ filename:%filename, source:%input
+ %tree = (call %peg.match with [%peg, %input, (nil), %userdata])
+ assume %tree or barf "File \%filename failed to parse:\n\%input"
+ return %tree
+ return (action (parse 1 from 2))
diff --git a/nomnom/pretty_errors.nom b/nomnom/pretty_errors.nom
new file mode 100644
index 0000000..732f3b9
--- /dev/null
+++ b/nomnom/pretty_errors.nom
@@ -0,0 +1,75 @@
+# This file has code for converting errors to user-friendly format, with colors,
+ line numbers, code excerpts, and so on.
+
+local action [visible size of %text]:
+ return (size of (%text::with "\027%[[0-9;]*m" -> ""))
+
+local action [boxed %text]:
+ %max_line = (..)
+ max of ((visible size of %line) for %line in (%text::lines))
+ %ret = (..)
+ "\n\%text"::with "\n([^\n]*)" as % -> (..)
+ "\n\%\(" "::* (%max_line - (visible size of %))) \027[0m"
+ return %ret.[2,-1]
+
+action [%err as a pretty error]:
+ %context = 2
+ %err_code = (%err::get source code)
+ %err_line = (%err_code::line at %err.source.start)
+ %err_linenum = (%err_code::line number at %err.source.start)
+ %err_linepos = (%err_code::line position at %err.source.start)
+ # TODO: better handle multi-line errors
+ %err_size = (..)
+ min of [..]
+ %err.source.stop - %err.source.start
+ (size of %err_line) - %err_linepos + 1
+ %nl_indicator = (" " if (%err_linepos > (size of %err_line)) else "")
+ %fmt_str = " %\(size of "\(%err_linenum + %context)")d|"
+ local action [num %i] (%fmt_str::formatted with 0)
+ %linenum_size = (size of (num 0))
+
+ %pointer = "\(" "::* (%err_linepos + %linenum_size - 1))"
+ if (%err_size >= 2):
+ %pointer += "\(%pointer)╚\("═"::* (%err_size - 2))╝"
+ ..else:
+ %pointer += "\(%pointer)⬆"
+
+ %err_msg = "\027[33;41;1m\(%err.title or "Error") at \(%err.source.filename or "???"):\(%err_linenum)\027[0m"
+ for %i in (%err_linenum - %context) to (%err_linenum - 1):
+ %line = (%err_code::line %i)
+ if %line:
+ %err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m"
+ if %err_line:
+ %before = %err_line.[1, %err_linepos - 1]
+ %during = %err_line.[%err_linepos, %err_linepos + %err_size - 1]
+ %after = %err_line.[%err_linepos + %err_size, -1]
+ %err_line = "\027[0m\(%before)\027[41;30m\(%during)\(%nl_indicator)\027[0m\(%after)"
+ %err_msg += "\n\027[2m\(%fmt_str::formated with %err_linenum)\(%err_line)\027[0m"
+ %err_linenum_end = (%err_code::line number at %err.source.stop)
+ %err_linepos_end = (%err_code::line position at %err.source.stop)
+ %err_linenum_end or= %err_linenum
+ if (%err_linenum_end == %err_linenum):
+ %err_msg += "\n\(%pointer)"
+ ..else:
+ for %i in (%err_linenum + 1) to %err_linenum_end:
+ %line = (%err_code::line %i)
+ if %line:
+ if (%i == %err_linenum_end):
+ %during = %line.[1, %err_linepos_end - 1]
+ %after = %line.[%err_linepos_end, -1]
+ %err_msg += "\n\027[2m\(num %i)\027[0;41;30m\(%during)\027[0m\(%after)"
+ ..else:
+ %err_msg += "\n\027[2m\(num %i)\027[0;41;30m\(%line)\027[0m"
+
+ %box_width = 70
+ %err_text = "\
+ ..\027[47;31;1m\((" \(%err.error)"::wrapped to %box_width)::with "\n" -> "\n\027[47;31;1m ")"
+ if %err.hint:
+ err_text += "\n\027[47;30m\((" Suggestion: \(%err.hint)"::wrapped to %box_width)::with "\n" -> "\n\027[47;30m ")"
+ %err_msg += "\n\027[33;1m \((%err_text boxed) with "\n" -> "\n ")"
+
+ for %i in (%err_linenum_end + 1) to (%err_linenum_end + %context):
+ %line = (%err_code::line %i)
+ if %line:
+ %err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m"
+ return %err_msg
diff --git a/nomnom/source.nom b/nomnom/source.nom
new file mode 100644
index 0000000..e05b314
--- /dev/null
+++ b/nomnom/source.nom
@@ -0,0 +1,49 @@
+use "lib/object.nom"
+
+object (Source):
+ action [Source from text %text]:
+ %match = (%text::matching groups "^@(.-)%[(%d+):(%d+)%]$")
+ set {%filename:%match.1, %start:%match.2, %stop:%match.3}
+ unless %filename:
+ %match = (%text::matching groups "^@(.-)%[(%d+)%]$")
+ set {%filename:%match.1, %start:%match.2}
+ return (Source {filename:%filename, start:(%start or 1) as number, stop: %stop as number})
+
+ my action [as text] "\
+ ..@\(%me.filename)[\(%me.start)\(":\(%me.stop)" if %me.stop else "")]"
+
+ my action [as lua] "\
+ ..Source{filename=\(%me.filename::as lua), start=\(%me.start)\
+ ..\(", stop=\(%me.stop)" if %stop else "")}"
+
+ my action [as nomsu] "\
+ ..(Source {filename:\(%me.filename::as nomsu), start:\(%me.start)\
+ ..\(", stop:\(%me.stop)" if %stop else "")})"
+
+ my action [== %other] (..)
+ all of [..]
+ (%me's metatable) == (%other's metatable)
+ %me.filename == %other.filename
+ %me.start == %other.start
+ %me.stop == %other.stop
+
+ my action [< %other]:
+ assume %me.filename == %other.filename
+ if (%start == %other.start):
+ return ((%me.stop or %me.start) < (%other.stop or %other.start))
+ ..else:
+ return (%me.start < %other.start)
+
+ my action [<= %other]:
+ assume %me.filename == %other.filename
+ if (%start == %other.start):
+ return ((%me.stop or %me.start) <= (%other.stop or %other.start))
+ ..else:
+ return (%me.start <= %other.start)
+
+ my action [+ %offset]:
+ if ((type of %me) == "number"):
+ set {%me:%offset, %offset:%me}
+ ..else:
+ assume (type of %offset) == "number"
+ return (Source {filename:%me.filename, start:%me.start + %offset, stop:%me.stop})