From f2048235f5cc7ff02db39a0e2fe5c79c7f390e0b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Sep 2018 00:30:28 -0700 Subject: [PATCH] Incremental checkin, currently not working, just saving progress. --- containers.moon | 10 +- core/control_flow.nom | 31 +++- core/metaprogramming.nom | 34 +++- lib/object.nom | 42 ++--- nomnom/ast.nom | 88 ++++++++++ nomnom/code_obj.nom | 180 +++++++++++++++++++++ nomnom/compile.nom | 207 ++++++++++++++++++++++++ nomnom/decompile.nom | 339 +++++++++++++++++++++++++++++++++++++++ nomnom/files.nom | 123 ++++++++++++++ nomnom/parser.nom | 81 ++++++++++ nomnom/pretty_errors.nom | 75 +++++++++ nomnom/source.nom | 49 ++++++ nomsu_compiler.moon | 2 +- string2.moon | 27 ++-- 14 files changed, 1242 insertions(+), 46 deletions(-) create mode 100644 nomnom/ast.nom create mode 100644 nomnom/code_obj.nom create mode 100644 nomnom/compile.nom create mode 100644 nomnom/decompile.nom create mode 100644 nomnom/files.nom create mode 100644 nomnom/parser.nom create mode 100644 nomnom/pretty_errors.nom create mode 100644 nomnom/source.nom diff --git a/containers.moon b/containers.moon index 70a9f70..039a4ea 100644 --- a/containers.moon +++ b/containers.moon @@ -133,9 +133,11 @@ for i,entry in ipairs(Dict({x:99})) do {:reverse, :upper, :lower, :find, :byte, :match, :gmatch, :gsub, :sub, :format, :rep} = string string2 = require 'string2' - {:lines, :line, :line_at, :as_lua_id} = string2 + {:lines, :line, :line_at, :as_lua_id, :is_lua_id} = string2 text_methods = formatted_with_1:format, byte_1:byte, position_of_1:find, position_of_1_after_2:find, + as_a_lua_identifier: as_lua_id, is_a_lua_identifier: is_lua_id, + as_a_lua_id: as_lua_id, is_a_lua_id: is_lua_id, bytes_1_to_2: (start, stop)=> List{byte(tostring(@), start, stop)} [as_lua_id "with 1 -> 2"]: gsub bytes: => List{byte(tostring(@), 1, -1)}, @@ -157,8 +159,10 @@ do line_number_of_1: (i)=> select(2, line_at(@, i)) line_position_of_1: (i)=> select(3, line_at(@, i)) matches_1: (patt)=> match(@, patt) and true or false + matching_1: (patt)=> (match(@, patt)) + matching_groups_1: (patt)=> {match(@, patt)} [as_lua_id "* 1"]: (n)=> rep(@, n) - matching_1: (patt)=> + all_matches_of_1: (patt)=> result = {} stepper,x,i = gmatch(@, patt) while true @@ -176,4 +180,6 @@ do elseif type(i) == 'table' then return sub(@, i[1], i[2]) else return text_methods[i] + getmetatable("").__add = (x)=> tostring(@)..tostring(x) + return {:List, :Dict} diff --git a/core/control_flow.nom b/core/control_flow.nom index a9e0ae0..c496d18 100644 --- a/core/control_flow.nom +++ b/core/control_flow.nom @@ -79,6 +79,10 @@ test: %i += 1 unless (%i == 10): go to %loop assume (%i == 10) + === (Loop) === + %i -= 1 + unless (%i == 0): go to (Loop) + assume (%i == 0) compile [=== %label ===, --- %label ---, *** %label ***] to (..) Lua "::label_\(%label as lua identifier)::" @@ -240,10 +244,8 @@ test: # For-each loop (lua's "ipairs()") compile [for %var in %iterable %body] to: - # This uses Lua's approach of only allowing loop-scoped variables in a loop - unless (%var.type is "Var"): - compile error at %var "Expected a variable here, not a \(%var.type)." define mangler + # This uses Lua's approach of only allowing loop-scoped variables in a loop %lua = (..) Lua "\ ..for \(mangle "i"),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do @@ -264,6 +266,29 @@ compile [for %var in %iterable %body] to: return %lua +# TODO: reduce code duplication +compile [for %var in %iterable at %i %body] to: + # This uses Lua's approach of only allowing loop-scoped variables in a loop + %lua = (..) + Lua "\ + ..for \(%i as lua identifier),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do + \(%body as lua statements)" + + if (%body has subtree \(do next)): + %lua::append "\n ::continue::" + if (%body has subtree \(do next %var)): + %lua::append (Lua "\n\(compile as (===next %var ===))") + %lua::append "\nend --foreach-loop" + if (%body has subtree \(stop %var)): + %lua = (..) + Lua "\ + ..do -- scope for stopping for-loop + \%lua + \(compile as (===stop %var ===)) + end -- end of scope for stopping for-loop" + + return %lua + test: %d = {a:10, b:20, c:30, d:40, e:50} %result = [] diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom index 51831ec..1503905 100644 --- a/core/metaprogramming.nom +++ b/core/metaprogramming.nom @@ -145,7 +145,7 @@ compile [action %actions %body] to (..) test: assume ((action (say %)) == (=lua "say_1")) -compile [action %action] to (Lua value (%action as lua id)) +compile [action %action] to (Lua value (%action.stub as lua id)) test: parse [swap %x and %y] as (..) @@ -233,7 +233,12 @@ action [%var as lua identifier, %var as lua id] (..) lua> "\ ..if type(\%var) == 'string' then return \%var:as_lua_id() elseif AST.is_syntax_tree(\%var, 'Var') then return \%var[1]:as_lua_id() - elseif AST.is_syntax_tree(\%var, 'Action') then return \%var.stub:as_lua_id() + elseif AST.is_syntax_tree(\%var) then + local lua = \(%var as lua expr) + if not tostring(lua):match("^[_a-zA-Z][_a-zA-Z0-9]*$") then + \(compile error at %var "This is not a valid Lua identifier.") + end + return lua else error("Unknown type: "..tostring(\%var)) end" @@ -317,8 +322,29 @@ test: compile [quote %s] to (Lua value "tostring(\(%s as lua expr)):as_lua()") test: - assume ((type of {}) == "table") or barf "type of failed." -compile [type of %obj] to (Lua value "type(\(%obj as lua expr))") + assume (lua type of {}) == "Lua table" + assume (type of {}) == "Dict" + assume ({} is a "Dict") + assume ("" is text) + assume ("" isn't a "Dict") + +%dict_mt = (=lua "getmetatable(\{})") +%list_mt = (=lua "getmetatable(\[])") + +action [% is text] (=lua "\(lua type of %) == 'string'") +action [% is not text, % isn't text] (=lua "\(lua type of %) ~= 'string'") +action [type of %]: + lua> "\ + local lua_type = \(lua type of %) + if lua_type == 'string' then return 'Text' + elseif lua_type == 'table' then + local mt = getmetatable(\%) + if mt and mt.__type then return mt.__type end + return 'Lua table' + else return lua_type end" +parse [% is a %type, % is an %type] as ((type of %) == %type) +parse [% isn't a %type, % isn't an %type, % is not a %type, % is not an %type] +..as ((type of %) == %type) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/object.nom b/lib/object.nom index 600ecf6..65da2a1 100644 --- a/lib/object.nom +++ b/lib/object.nom @@ -3,7 +3,7 @@ This file contains the implementation of an Object-Oriented programming system. test: - object "Dog": + object (Dog): (Dog).genus = "Canus" my action [set up]: %me.barks or= 0 my action [bark, woof]: @@ -12,8 +12,10 @@ test: my action [get pissed off]: %me.barks += 1 - %d = (new Dog {barks:2}) - assume (%d.barks == 2) + %d = (Dog {barks:2}) + assume (type of %d) == "Dog" + assume (%d is a "Dog") + assume %d.barks == 2 assume ((%d::bark) == "Bark! Bark!") assume ((%d::woof) == "Bark! Bark!") %d::get pissed off @@ -23,24 +25,24 @@ test: assume ("\(%d.class)" == "Dog") assume (%d.genus == "Canus") assume (%d.barks == 3) - %d2 = (new Dog) + %d2 = (Dog {}) assume (%d2.barks == 0) or barf "Default initializer failed" - with {%d:new Dog {barks:1}}: + with {%d:Dog {barks:1}}: assume ((%d::bark) == "Bark!") - object "Corgi" extends (Dog): + object (Corgi) extends (Dog): my action [sploot] "splooted" my action [bark, woof]: %barks = ("Yip!" for % in 1 to %me.barks) return (%barks::joined with " ") - %corg = (new Corgi) + %corg = (Corgi {}) assume (%corg.barks == 0) - with {%d:new Corgi {barks:1}}: + with {%d:Corgi {barks:1}}: assume ((%d::sploot) == "splooted") or barf "subclass method failed" assume ((%d::bark) == "Yip!") or barf "inheritance failed" assume ((%d::woof) == "Yip!") - with {%d:new Dog {barks:2}}: + with {%d:Dog {barks:2}}: assume ((%d::bark) == "Bark! Bark!") compile [my action %actions %body] to: lua> "\ @@ -69,12 +71,18 @@ compile [my action %actions %body] to: return lua" compile [object %classname extends %parent %class_body] to: + unless (%classname.type == "Action"): + compile error at %classname "Expected this to be an action, not a \(%classname.type)" + for % in %classname: + unless (% is text): + compile error at % "Class names should not have arguments." return (..) Lua "\ ..do - local class = {name=\(%classname as lua expr)} + local class = {name=\(quote %classname.stub)} setmetatable(class, { __index=\(%parent as lua expr), + __type=class.name, __tostring=function(cls) return cls.name end, __call=function(cls, inst) inst = setmetatable(inst or {}, cls) @@ -84,24 +92,21 @@ compile [object %classname extends %parent %class_body] to: return inst end, }) - nomsu.environment[("new "..class.name):as_lua_id()] = class - nomsu.environment[("new "..class.name.." 1"):as_lua_id()] = class + nomsu.environment[(class.name.." 1"):as_lua_id()] = class nomsu.environment[class.name:as_lua_id()] = function() return class end class.__index = class class.class = class class.__tostring = function(inst) return inst.name..getmetatable(_Dict{}).__tostring(inst) end - \(%class_body as lua statements) - local metamethod_map = {["as text"]="__tostring", ["clean up"]="__gc", ["+ 1"]="__add", ["- 1"]="__sub", ["* 1"]="__mul", ["/ 1"]="__div", ["-"]="__unm", ["// 1"]="__idiv", ["mod 1"]="__mod", ["^ 1"]="__pow", ["& 1"]="__band", ["| 1"]="__bor", ["~ 1"]="__bxor", ["~"]="__bnot", ["<< 1"]="__bshl", [">> 1"]="__bshr", ["== 1"]="__eq", ["< 1"]="__lt", - ["<= 1"]="__le", ["set 1 = 2"]="__newindex", ["length"]="__len", - ["__ipairs"]="__ipairs", ["__pairs"]="__pairs", + ["<= 1"]="__le", ["set 1 = 2"]="__newindex", ["size"]="__len", + ["iterate"]="__ipairs", ["iterate all"]="__pairs", } for stub,metamethod in pairs(metamethod_map) do class[metamethod] = class[stub:as_lua_id()] @@ -111,8 +116,3 @@ compile [object %classname extends %parent %class_body] to: parse [object %classname %class_body] as (..) object %classname extends (nil) %class_body -parse [%obj is a %class] as (..) - all of [..] - (type of %obj) == "table" - %obj's metatable - (%obj's metatable).__index == %class 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}) diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index 0718bca..34c2ab0 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -99,7 +99,7 @@ with NomsuCompiler :next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall, :error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module, :print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :rawlen, - :table, :assert, :dofile, :loadstring, :type, :select, :math, :io, :load, + :table, :assert, :dofile, :loadstring, lua_type_of_1:type, :select, :math, :io, :load, :pairs, :ipairs, -- Nomsu types: _List:List, _Dict:Dict, diff --git a/string2.moon b/string2.moon index 662cd0d..1b0037f 100644 --- a/string2.moon +++ b/string2.moon @@ -13,8 +13,10 @@ isplit = (sep='%s+')=> return step, {str:@, pos:1, :sep}, 0 lua_keywords = { - "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", - "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" + ["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true, ["elseif"]=true, ["end"]=true, + ["false"]=true, ["for"]=true, ["function"]=true, ["goto"]=true, ["if"]=true, + ["in"]=true, ["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true, ["repeat"]=true, + ["return"]=true, ["then"]=true, ["true"]=true, ["until"]=true, ["while"]=true } string2 = { @@ -76,24 +78,19 @@ string2 = { str = gsub str, "%W", (c)-> if c == ' ' then '_' else format("x%02X", byte(c)) - -- Lua IDs can't start with numbers, so map "1" -> "_1", "_1" -> "__1", etc. - str = gsub str, "^_*%d", "_%1" - -- This pattern is guaranteed to match all keywords, but also matches some other stuff. - if match str, "^_*[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$" - for kw in *lua_keywords - if match str, ("^_*"..kw.."$") - str = "_"..str + + unless string2.is_lua_id(str\match("^_*(.*)$")) + str = "_"..str return str + is_lua_id: (str)-> + match(str, "^[_a-zA-Z][_a-zA-Z0-9]*$") and not lua_keywords[str] + -- from_lua_id(as_lua_id(str)) == str, but behavior is unspecified for inputs that -- did not come from as_lua_id() from_lua_id: (str)-> - -- This pattern is guaranteed to match all keywords, but also matches some other stuff. - if match str, "^_+[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$" - for kw in *lua_keywords - if match str, ("^_+"..kw.."$") - str = str\sub(2,-1) - str = gsub(str, "^_(_*%d.*)", "%1") + unless string2.is_lua_id("^_+(.*)$") + str = str\sub(2,-1) str = gsub(str, "_", " ") str = gsub(str, "x([0-9A-F][0-9A-F])", (hex)-> char(tonumber(hex, 16))) str = gsub(str, "^ ([ ]*)$", "%1")