diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2018-11-02 15:17:48 -0700 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2018-11-02 15:17:49 -0700 |
| commit | 0f17c5eb9ac4660f2f969bd1e67af42713e45eac (patch) | |
| tree | 279ca7da2de0efe2f363684f3c84a540635f11a8 /nomnom | |
| parent | acd9c2acd4688f2301b091daad910c04e402bd6a (diff) | |
| parent | dc41f30c73c9686685e3a4183c1213fb4ba55c90 (diff) | |
Merge branch 'master' into working
Diffstat (limited to 'nomnom')
| -rw-r--r-- | nomnom/ast.nom | 88 | ||||
| -rw-r--r-- | nomnom/code_obj.nom | 210 | ||||
| -rw-r--r-- | nomnom/compile.nom | 276 | ||||
| -rw-r--r-- | nomnom/decompile.nom | 347 | ||||
| -rw-r--r-- | nomnom/files.nom | 108 | ||||
| -rw-r--r-- | nomnom/parser.nom | 91 | ||||
| -rw-r--r-- | nomnom/pretty_errors.nom | 85 | ||||
| -rw-r--r-- | nomnom/source.nom | 54 |
8 files changed, 1259 insertions, 0 deletions
diff --git a/nomnom/ast.nom b/nomnom/ast.nom new file mode 100644 index 0000000..ef41b26 --- /dev/null +++ b/nomnom/ast.nom @@ -0,0 +1,88 @@ +#!/usr/bin/env nomsu -V4.8.10 +use "lib/object.nom" + +# The types are [..] + "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 " ") + if (%me.stub == "Lua Code 1 2"): + lua> "require('ldt').breakpoint()" + + (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] "\ + ..a_Syntax_Tree_with(\(call ({} 's metatable).as_lua with [%me]))" + my action [as nomsu] "\ + ..(a Syntax Tree with \(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 [with %overrides]: + %new = (%k = %v for %k = %v in %me) + for %k = %v in %overrides: %new.%k = %v + return (Syntax Tree %new) + + 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..c8d2784 --- /dev/null +++ b/nomnom/code_obj.nom @@ -0,0 +1,210 @@ +#!/usr/bin/env nomsu -V4.8.10 +# 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/things.nom" + +a (Code Buffer) is a thing: + that can (set up) by: + assume %its.source + %old_bits = (%its.bits if (%its.bits is a "List") else [%its.bits]) + %its.bits = [] + if (type of %its.source) is: + "Text": + %its.source = (Source from text %its.source) + "Syntax Tree": + %its.source = %its.source.source + + for % in %old_bits: %its::add % + + whose (text) means: + if (%its._text == (nil)): + %buff = [] + %indent = 0 + for %bit in %its.bits: + if (%bit is text): + %spaces = (%bit::matching "\n([ ]*)[^\n]*$") + if %spaces: %indent = (size of %spaces.1) + ..else: + %bit = (%bit::text) + if (%indent > 0): + %bit = (%bit::with "\n" -> "\n\(" "::* %indent)") + %buff::add %bit + %its._text = (%buff::joined) + return %its._text + + whose (lua code) means "\ + ..a_\(%its.class.name::as lua id)_with{source=\(..) + (%its.source::as lua) if %its.source else "nil" + .., \(%its.bits::as lua)}" + + whose (nomsu code) means "\ + ..(a \(%its.class.name) with {source: \((%its.source::as nomsu) if %its.source else "(nil)"), bits: \(..) + %its.bits::as nomsu + ..})" + + whose (size) means (size of (%its::text)) + + that can (mark as dirty) by: + %its._text = (nil) + %its._trailing_line_len = (nil) + %its._num_lines = (nil) + + that can (add %new_bits) by: + 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) + %its.bits::add % + %its::mark as dirty + + whose (trailing line length) means: + if (%its._trailing_line_len == (nil)): + %its._trailing_line_len = (size of ((%its::text)::matching "[^\n]*$")) + return %its._trailing_line_len + + whose (number of lines) means: + unless %its._num_lines: + %num_lines = 1 + for % in %its: + if (% is text): + %num_lines += (size of (%::all matches of "\n")) + ..else: + %num_lines += ((%::number of lines) - 1) + + %its._num_lines = %num_lines + + return %its._num_lines + + whose [is multiline, is multi-line] all mean ((%its::number of lines) > 1) + whose [is one line, is single line] all mean ((%its::number of lines) == 1) + that can (add %values joined with %joiner) by: + %its::add %values joined with %joiner or %joiner + that can [add %values joined with %joiner or %wrapping_joiner] by: + %line_len = 0 + %bits = %its.bits + for %value in %values at %i: + if (%i > 1): + if (%line_len > 80): + %bits::add %wrapping_joiner + %line_len = 0 + ..else: %bits::add %joiner + + %bits::add %value + unless (%value is text): + %value = (%value::text) + %line = (%value::matching "\n([^\n]*)$") + if %line: + %line_len = (size of %line) + ..else: + %line_len += (size of %value) + %its::mark as dirty + + that can (prepend %) by: + #if ((% isn't text) and (% isn't a %its.__type)): + % = (%::as lua) + %its.bits::add % at index 1 + %its::mark as dirty + + that can (parenthesize) by: + %its.bits::add "(" at index 1 + %its.bits::add ")" + %its::mark as dirty + +a (Lua Buffer) is a thing: + that has [..] + text, lua code, nomsu code, trailing line length, size, number of lines, + is multiline, is multi-line, is one line, is single line, + ..like a (Code Buffer) + that can [..] + set up, mark as dirty, add %, prepend %, parenthesize, + add % joined with %, add % joined with % or %, + ..like a (Code Buffer) + + that can (add free vars %vars) by: + if ((size of %vars) == 0): return + %seen = (%v = (yes) for %v in %its.free_vars) + for %var in %vars: + assume (%var is text) + unless %seen.%var: + %its.free_vars::add %var + %seen.%var = (yes) + + %its::mark as dirty + + that can (remove free vars %vars) by: + if ((size of %vars) == 0): return + %removals = {} + for %var in %vars: + assume (%var is text) + %removals.%var = (yes) + + %stack = [%its] + repeat 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 at index %i + + for % in %lua.bits: + unless (% is text): %stack::add % + + %its::mark as dirty + + that can (declare locals) by (%its::declare locals (nil)) + that can (declare locals %to_declare) by: + unless %to_declare: + %to_declare = [] + %seen = {} + for %lua in recursive %its: + for %var in %lua.free_vars: + unless %seen.%var: + %seen.%var = (yes) + %to_declare::add %var + + for % in %lua.bits: + unless (% is text): recurse %lua on % + + if ((size of %to_declare) > 0): + %its::remove free vars %to_declare + %its::prepend "local \(%to_declare::joined with ", ");\n" + return %to_declare + + whose (as statements) means (%its::as statements with "") + whose (as statements with %prefix) means: + unless %its.is_value: return %its + %statements = (a Lua Buffer with {source:%its.source}) + if ((%prefix or "") != ""): + %statements::add %prefix + %statements::add %its + %statements::add ";" + return %statements + + that can (mark as value) by: + %its.is_value = (yes) + + that can (mark as variable) by: + %its.is_variable = (yes) + %its.is_value = (yes) + + that can (variables) by: + %vars = [] + for %code in recursive %its: + if %code.is_variable: + %vars::add (%code::text) + for % in %code.bits: + unless (% is text): recurse %code on % + + return %vars + +a (Nomsu Buffer) is a thing: + that has [..] + text, lua code, nomsu code, trailing line length, size, number of lines, + is multiline, is multi-line, is one line, is single line, + ..like a (Code Buffer) + that can [..] + set up, mark as dirty, add %, prepend %, parenthesize, + add % joined with %, add % joined with % or %, + ..like a (Code Buffer) diff --git a/nomnom/compile.nom b/nomnom/compile.nom new file mode 100644 index 0000000..8241ef2 --- /dev/null +++ b/nomnom/compile.nom @@ -0,0 +1,276 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file contains the code to convert syntax trees to Lua code +use "nomnom/code_obj.nom" +use "nomnom/parser.nom" +use "nomnom/pretty_errors.nom" + +externally (report compile error at %tree %err) means: + barf (pretty "Compile Error" error at %tree %err) + +externally (report compile error at %pos %err hint %hint) means: + barf (pretty "Compile Error" error at %tree %err hint %hint) + +externally (barf any errors in %t) means: + assume (%t is a "Syntax Tree") + %errs = [] + for % in recursive %t: + if (%.type == "Error"): %errs::add % + for %k = %v in %: + if (%v is a "Syntax Tree"): recurse % on %v + + sort %errs by % -> %.source + %errs = ((% as a pretty error) for % in %errs) + if ((size of %errs) > 0): + if ((size of %errs) > 3): + %n = ((size of %errs) - 3) + for %i in 4 to (size of %errs): %errs.%i = (nil) + %errs::add "\027[31;1m +\%n additional errors...\027[0m\n" + barf (%errs::joined with "\n\n") + +externally (%tree compiled with %compile_actions) means: + assume (%tree is a "Syntax Tree") + if all of [..] + %tree.version, ((Nomsu version)'s meaning) != (nil), %tree.version != (Nomsu version) + ((1 upgraded from 2 to 3)'s meaning) != (nil) + ..then: + %tree = (upgrade %tree from %tree.version to (Nomsu version)) + + if %tree.type is: + "Action": + %stub = %tree.stub + %compile_action = %compile_actions.%stub + + # Don't apply compiler actions to methods + if (%compile_action and (not %tree.target)): + # TODO: restore this: + #%args = [%tree, %compile_actions] + %args = [%nomsu, %tree] + for % in (%tree::arguments): %args::add % + %result = (call %compile_action with %args) + if (%result == (nil)): + report compile error at %tree "\ + ..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): + report compile error at %tree "\ + ..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 (%result compiled with %compile_actions) + + return %result + + %lua = (a Lua Buffer with {source:%tree}) + if %tree.target: + # Method call + %target_lua = (%tree.target compiled with %compile_actions) + if (..) + ((%target_lua::text)::matches "^%(.*%)$") or (..) + (%target_lua::text)::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 at %i: + if (%tok is text): do next %tok + + # TODO: maybe don't translate Lua comments + #if (%tok.type == "Comment"): do next %tok + if (%tok.type == "Block"): + %values = [] + for %line in %tok: + #unless (%line.type == "Comment"): + %values::add (%line compiled with %compile_actions) + + if all of (%.is_value for % in %values): + if ((size of %values) == 1): + %arg_lua = %values.1 + ..else: + %arg_lua = (a Lua Buffer with {source:%tok, is_value:yes, bits:["("]}) + %arg_lua::add %values joined with " and nil or " + %arg_lua::add ")" + ..else: + %arg_lua = (a Lua Buffer with {source:%tok, is_value:yes, bits:["((function()"]}) + for %v in %values at %i: + if %v.is_value: + %v = (%v::as statements with ("return " if (%i == (size of %values) else ""))) + %arg_lua::add ["\n ", %v] + + %arg_lua::add "\nend)())" + ..else: + %arg_lua = (%tok compiled with %compile_actions) + unless %arg_lua.is_value: + if (%tok.type == "Action"): + report 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: + report compile error at %tok "\ + ..Can't use this as an argument to (\%stub), since it's not an expression, it produces: \%arg_lua" + + assume (%arg_lua != %lua) or barf "Huh? \%tree .\%i = \%tok -> \%arg_lua" + %args::add %arg_lua + + %lua::add %args joined with ", " + %lua::add ")" + return %lua + + "EscapedNomsu": + %lua = (a Lua Buffer with {source:%tree, is_value:yes, bits:["a_Syntax_Tree_with{type=", quote %tree.(1).type]}) + set {%needs_comma:no, %i:1} + (% as shmua) means: + if (% is a "Lua number"): return "\%" + if (% is a "Syntax Tree"): + return (% compiled with %compile_actions) + if (% is text): return (quote %) + return (%::as lua) + + for %k = %v in (((%tree.(1).type == "EscapedNomsu") and %tree) or %tree.1): + %lua::add ", " + if: + (%k == %i): %i += 1 + ((%k is text) and (%k::is a lua identifier)): + %lua::add [%k, "= "] + else: + %lua::add ["[", % as shmua, "]= "] + + if (%k == "source"): + %lua::add (quote "\%v") + ..else: + %lua::add (%v as shmua) + + %lua::add "}" + return %lua + + "Block": + %lua = (a Lua Buffer with {source:%tree}) + %lua::add (..) + ((%line compiled with %compile_actions)::as statements) for %line in %tree + ..joined with "\n" + + return %lua + + "Text": + %lua = (a Lua Buffer with {source:%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 = (% compiled with %compile_actions) + unless %bit_lua.is_value: + report compile error at % "\ + ..Can't use this as a string interpolation value, since it doesn't have a value." + + if (%.type != "Text"): + %bit_lua = (a Lua Buffer with {source:%, is_value:yes, bits:["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 = (a Lua Buffer with {source:%tree, is_value:yes, bits:["List{"]}) + %lua::add ((% compiled with %compile_actions) for % in %tree) joined with ", " or ",\n " + %lua::add "}" + return %lua + + "Dict": + %lua = (a Lua Buffer with {source:%tree, is_value:yes, bits:["Dict{"]}) + %lua::add ((% compiled with %compile_actions) for % in %tree) joined with ", " or ",\n " + %lua::add "}" + return %lua + + "DictEntry": + set {%key:%tree.1, %value:%tree.2} + %key_lua = (%key compiled with %compile_actions) + unless %key_lua.is_value: + report compile error at %tree.1 "\ + ..Can't use this as a dict key, since it's not an expression." + + %value_lua = (..) + (%value compiled with %compile_actions) if %value else (..) + a Lua Buffer with {source:%key, is_value:yes, bits:["true"]} + + unless %value_lua.is_value: + report compile error at %tree.2 "\ + ..Can't use this as a dict value, since it's not an expression." + + %key_str = ((%key_lua::text)::matching "^[\"']([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if: + %key_str: + return (a Lua Buffer with {source:%tree, bits:[%key_str, "=", %value_lua]}) + ((%key_lua::text).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 (a Lua Buffer with {source:%tree, bits:["[ ", %key_lua, "]=", %value_lua]}) + + else: + return (a Lua Buffer with {source:%tree, bits:["[", %key_lua, "]=", %value_lua]}) + + "IndexChain": + %lua = (%tree.1 compiled with %compile_actions) + unless %lua.is_value: + report compile error at %tree.1 "\ + ..Can't index into this, since it's not an expression." + + %first_char = (%lua::text).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 = (%key compiled with %compile_actions) + unless %key_lua.is_value: + report compile error at %key "\ + ..Can't use this as an index, since it's not an expression." + + %key_lua_str = (%key_lua::text) + %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 (a Lua Buffer with {source:%tree, is_value:yes, bits:["\(%tree.1)"]}) + "Var": + return (a Lua Buffer with {source:%tree, is_value:yes, is_variable:yes, bits:[%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: de-implement? + return (a Lua Buffer with {source:%tree, bits:["-- \(%tree.1::with "\n" -> "\n-- ")"]}) + + "Error": + barf (%tree as a pretty error) + else: + barf "Unknown type: \(%tree.type)" diff --git a/nomnom/decompile.nom b/nomnom/decompile.nom new file mode 100644 index 0000000..11ec233 --- /dev/null +++ b/nomnom/decompile.nom @@ -0,0 +1,347 @@ +#!/usr/bin/env nomsu -V4.8.10 +# 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 +externally (%tree decompiled inline) means: + assume (%tree is a "Syntax Tree") + if %tree.type is: + "Action": + %nomsu = (a Nomsu Buffer with {source: %tree}) + if %tree.target: + %target_nomsu = (%tree.target decompiled 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 = (%bit decompiled 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 = (%tree.1 decompiled inline) + unless (..) + any of [..] + %tree.(1).type == "List", %tree.(1).type == "Dict", %tree.(1).type == "Var" + ..: + %inner_nomsu::parenthesize + + %nomsu = (a Nomsu Buffer with {source: %tree, bits: ["\\", %inner_nomsu]}) + return %nomsu + + "Block": + %nomsu = (a Nomsu Buffer with {source: %tree, bits: [":"]}) + for %line in %tree at %i: + %nomsu::add [" " if (%i == 1) else "; ", %line decompiled inline] + return %nomsu + + "Text": + %nomsu = (a Nomsu Buffer with {source: %tree, bits: []}) + for %text in recursive %tree: + for %bit in %text at %i: + if (%bit is text): %nomsu::add %bit + ..else: + if %bit.type is: + "Text": + recurse %text on %bit + "Var": + %interp_nomsu = (%bit decompiled 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 ["\\", %bit decompiled inline] + else: + %nomsu::add ["\\(", %bit decompiled inline, ")"] + + return (a Nomsu Buffer with {source: %tree, bits: ["\"", %nomsu, "\""]}) + + "List" "Dict": + %nomsu = (a Nomsu Buffer with {source: %tree, bits: ["[" if (%tree.type == "List") else "{"]}) + for %item in %tree at %i: + if (%i > 1): %nomsu::add ", " + %nomsu::add (%item decompiled 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 = (a Nomsu Buffer with {source: %key, bits: [%key.1]}) + ..else: + %nomsu = (%key decompiled inline) + + if (%key.type == "Action"): + %nomsu::parenthesize + if %value: + %nomsu::add ":" + %nomsu::add (%value decompiled inline) + + return %nomsu + + "IndexChain": + %nomsu = (a Nomsu Buffer with {source: %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 = (%bit decompiled 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 (a Nomsu Buffer with {source: %tree, bits: [(%tree.1 as hex) if %tree.hex else "\(%tree.1)"]}) + "Var": + return (a Nomsu Buffer with {source: %tree, bits: ["%\(%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 +externally (%tree decompiled) means: + %nomsu = (a Nomsu Buffer with {source: %tree}) + + # For concision: + (recurse on %t) means: + %space = (%MAX_LINE - (%nomsu::trailing line length)) + if (%space <= 0): + go to (Use Indented) + for %subtree in recursive %tree: + if %subtree.type is: + "Block": + if ((size of %subtree) > 1): + go to (Use Indented) + if ((size of "\(%subtree decompiled inline)") > 20): + go to (Use Indented) + + for %k = %v in %subtree: + if (%v is a "Syntax Tree"): + recurse %subtree on %v + + %inline_nomsu = (%t decompiled inline) + if (%inline_nomsu and ((size of "\%inline_nomsu") <= %space)): + return %inline_nomsu + === (Use Indented) === + %indented = (%t decompiled) + if (%t.type == "Action"): + %indented = (..) + a Nomsu Buffer with {source: %t, bits: ["(..)\n ", %indented]} + + return %indented + + if %tree.type is: + "FileChunks": + (%1 and %2 should clump) means: + 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" + 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" + ..else: %nomsu::add "\n\n" + + %nomsu::add (%line decompiled) + ..else: + %nomsu::add (%chunk decompiled) + + 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 (..) + a Nomsu Buffer with {source: %tree, bits: [":\n ", %nomsu]} + + "Text": + # Multi-line text has more generous wrap margins + %max_line = ((1.5 * %MAX_LINE) rounded down) + %nomsu = (a Nomsu Buffer with {source: %tree}) + (add text from %tree) means: + 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.." + + repeat 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 (..) + a Nomsu Buffer with {source: %tree, bits: ["\"\\\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 = (%item decompiled 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 any of [..] + %item_nomsu::is multi-line, ((%nomsu::trailing line length) + (size of "\%item_nomsu")) >= %MAX_LINE + ..: %nomsu::add "\n" + ..else: %nomsu::add ", " + + return (..) + a Nomsu Buffer with {..} + source: %tree, bits: [..] + "[..]\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 (%key decompiled 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 (%tree decompiled 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..352ddfa --- /dev/null +++ b/nomnom/files.nom @@ -0,0 +1,108 @@ +#!/usr/bin/env nomsu -V4.8.10 +# 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 +externally (spoof file %filename %contents) means: + %_SPOOFED_FILES.%filename = %contents + return %contents + +# Read a file's contents +externally (read file %filename) means: + %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 + +externally (%path sanitized) means: + %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: + (filesystem has %filename) means: + %mode = (call %lfs.attributes with [%filename, "mode"]) + if %mode is: + "file" "directory" "link" "char device": return (yes) + else: return (no) + + externally (file %path exists) means: + 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) + + externally (files in %path) means: + 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"): + 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`)" + + externally (file %path exists) means: + 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) + + externally (files in %path) means: + 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..fe237e8 --- /dev/null +++ b/nomnom/parser.nom @@ -0,0 +1,91 @@ +#!/usr/bin/env nomsu -V4.8.10 +# 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 [20000] +set {..} + ((P 1)'s meaning):%lpeg.P, ((R 1)'s meaning):%lpeg.R + ((Carg 1)'s meaning):%lpeg.Carg, ((S 1)'s meaning):%lpeg.S + ((Cc 1)'s meaning):%lpeg.Cc, ((lpeg re pattern 1)'s meaning):%re.compile + ((lpeg re pattern 1 using 2)'s meaning):%re.compile + ((lpeg pattern 1's match of 2)'s meaning):%lpeg.match + ((lpeg pattern 1's match of 2 with 3)'s meaning): (..) + [%1, %2, %3] -> (call %lpeg.match with [%1, %2, nil, %3]) + +%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:%t.start, stop:%t.stop} + set {%t.start: nil, %t.stop: nil, %t.source: %source} + %t = (a Syntax Tree with %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) +externally [%text is a nomsu id, %text is a nomsu identifier] all mean (..) + lpeg pattern %id_patt's match of %text + +externally (%text is a nomsu operator) means (..) + lpeg pattern %operator_patt's match of %text + +%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]* + " + +externally (make parser from %peg) means (make parser from %peg using (nil)) +externally (make parser from %peg using %make_tree) means: + %peg = (lpeg pattern %peg_tidier's match of %peg) + %peg = (lpeg re pattern %peg using %defs) + (%input from %filename parsed) means: + %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 = (lpeg pattern %peg's match of %input with %userdata) + assume %tree or barf "\ + ..File \%filename failed to parse: + \%input" + + return %tree + + return ((1 from 2 parsed)'s meaning) diff --git a/nomnom/pretty_errors.nom b/nomnom/pretty_errors.nom new file mode 100644 index 0000000..db4be5a --- /dev/null +++ b/nomnom/pretty_errors.nom @@ -0,0 +1,85 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file has code for converting errors to user-friendly format, with colors, + line numbers, code excerpts, and so on. +(visible size of %text) means: + return (size of (%text::with "\027%[[0-9;]*m" -> "")) + +(%text boxed) means: + %max_line = (max of ((visible size of %line) for %line in (%text::lines))) + %ret = (..) + "\n\%text"::with "\n([^\n]*)" -> (..) + [%] -> (..) + "\n\%\(" "::* (%max_line - (visible size of %))) \027[0m" + return %ret.[2,-1] + +%CONTEXT = 2 +externally (pretty %title error at %tree %err hint %hint) means: + %source_code = (%tree::get source code) + %start = %tree.source.start + %stop = %tree.source.stop + %filename = (%tree.source.filename or "???") + %err_line = (%source_code::line at %start) + %err_linenum = (%source_code::line number at %start) + %err_linepos = (%source_code::line position at %start) + + # TODO: better handle multi-line errors + %err_size = (min of [%stop - %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|" + (num %i) means (%fmt_str::formatted with %i) + %linenum_size = (size of (num 0)) + %pointer = "\(" "::* (%err_linepos + %linenum_size - 1))" + if (%err_size >= 2): + %pointer += "╚\("═"::* (%err_size - 2))╝" + ..else: %pointer += "⬆" + + %err_msg = "\ + ..\027[33;41;1m\(%title or "Error") at \%filename:\%err_linenum\027[0m" + for %i in (%err_linenum - %CONTEXT) to (%err_linenum - 1): + %line = (%source_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\(num %err_linenum)\(%err_line)\027[0m" + + %err_linenum_end = (%source_code::line number at %stop) + %err_linepos_end = (%source_code::line position at %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 = (%source_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"::wrapped to %box_width)::with "\n" -> "\n\027[47;31;1m ")" + if %hint: + %err_text += "\n\027[47;30m\((" Suggestion: \(%hint)"::wrapped to %box_width)::with "\n" -> "\n\027[47;30m ")" + %err_msg += "\n\027[33;1m \((%err_text boxed)::with "\n" -> "\n ")" + + %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 = (%source_code::line %i) + if %line: + %err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m" + + return %err_msg + +externally (pretty %title error at %tree %err) means (..) + pretty %title error at %tree %err (nil) + +externally (%err_tree as a pretty error) means (..) + pretty %err_tree.title error at %err_tree %err_tree.error hint %err_tree.hint diff --git a/nomnom/source.nom b/nomnom/source.nom new file mode 100644 index 0000000..c36216f --- /dev/null +++ b/nomnom/source.nom @@ -0,0 +1,54 @@ +#!/usr/bin/env nomsu -V4.8.10 +use "lib/object.nom" + +object (Source): + externally (Source from text %text) means: + %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}) |
