From 056357162551993e3e7d3a0ac778ed46aafdc083 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 30 Jan 2018 15:10:37 -0800 Subject: [PATCH] Overhaul of indentations. Now strictly requiring exactly 4 spaces everywhere, and supporting indented string interpolations. --- lib/metaprogramming.nom | 6 +- lib/object.nom | 148 +++++++++++++++++++++++----------------- nomsu.moon | 53 +++++++------- nomsu.peg | 24 ++++--- 4 files changed, 126 insertions(+), 105 deletions(-) diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index 8f2bde9..c6bf491 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -98,13 +98,13 @@ immediately action [remove action %stub] lua> ".." - local fn = ACTIONS[\%stub]; + local fn = ACTION[\%stub]; local metadata = ACTION_METADATA[fn]; for i=#metadata.aliases,1,-1 do metadata.arg_orders[metadata.aliases[i]] = nil; table.remove(metadata.aliases, i); end - ACTIONS[\%stub] = nil; + ACTION[\%stub] = nil; immediately action [%tree as lua] @@ -141,7 +141,7 @@ immediately compile [%var as lua identifier] to {expr:"nomsu:var_to_lua_identifier(\(%var as lua expr))"} action [action %names metadata] - =lua "ACTION_METADATA[ACTIONS[\%names]]" + =lua "ACTION_METADATA[ACTION[\%names]]" # Get the source code for a function action [help %action] diff --git a/lib/object.nom b/lib/object.nom index b7506f3..f3fcd86 100644 --- a/lib/object.nom +++ b/lib/object.nom @@ -6,26 +6,26 @@ compile [@%var] to local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"')); if key_attr then - return "_me."..key_attr; + return {expr="_me."..key_attr}; elseif key_lua:sub(1,1) == "[" then key_lua = " "..key_lua.." "; end - return "_me["..key_lua.."]"; + return {expr="_me["..key_lua.."]"}; -compile [@%var <- %val] to code +compile [@%var <- %val] to lua> ".." - local val_lua = \(%val as lua); + local val_lua = \(%val as lua expr); local key_lua = repr(\%var.value); local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"')); if key_attr then - return "_me."..key_attr.." = "..val_lua..";"; + return {statements="_me."..key_attr.." = "..val_lua..";"}; elseif key_lua:sub(1,1) == "[" then key_lua = " "..key_lua.." "; end - return "_me["..key_lua.."] = "..val_lua..";"; + return {statements="_me["..key_lua.."] = "..val_lua..";"}; -compile [object %classname %class_body] to +compile [define object %classname %class_body] to %class_identifier <- (=lua "nomsu:var_to_lua_identifier(\(%classname as value)):sub(2,-1)") if: %class_identifier is "" %class_identifier <- "class" @@ -34,71 +34,91 @@ compile [object %classname %class_body] to if: (%line's "type") is "Comment" do next %line assume (((%line's "type") == "FunctionCall") and ((%line's "stub") == "action % %")) - ..or barf "Only action definitions are supported inside 'object % %', not \(%line's "src")" + ..or barf "Only action definitions are supported inside 'define object % %', not \(%line's "src")" + %actions <- (2nd in (%line's "value")) + %body <- (3rd in (%line's "value")) lua> ".." - assert(\%line.value[3].type == "Block", - "Invalid type for action definition body. Expected Block, but got: "..tostring(\%line.value[3].type)); - local names, args = nomsu:parse_spec(\%line.value[2]); - local stub = nomsu:get_stub(names[1]); - local body_lua = nomsu:tree_to_lua(\%line.value[3]); - body_lua = body_lua.statements or ("return "..body_lua.expr..";"); - local arg_nomsus = {}; - for i, arg in ipairs(args) do arg_nomsus[i] = "nomsu:tree_to_lua("..arg..").expr"; end + local signature = {}; + for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end + local stubs = nomsu:get_stubs_from_signature(signature); + local stub_args = nomsu:get_args_from_signature(signature); + local arg_set = {}; + for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end + local body_lua = nomsu:tree_to_lua(\%body); + local body_code = body_lua.statements or ("return "..body_lua.expr..";"); + local undeclared_locals = {}; + for i, body_local in ipairs(body_lua.locals or {}) do + if not arg_set[body_local] then + table.insert(undeclared_locals, body_local); + end + end + if #undeclared_locals > 0 then + body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code; + end + local lua_fn_args = table.concat(stub_args[1], ", "); + local def_tree = nomsu.compilestack[#nomsu.compilestack]; + local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop); + + local compiled_args = {}; + for i, arg in ipairs(stub_args[1]) do + compiled_args[i] = "nomsu:tree_to_lua("..arg..").expr"; + end + compiled_args = table.concat(compiled_args, "..', '.."); table.insert(\%methods, ([==[ %s[ %s] = function(%s) %s end nomsu:define_compile_action(%s, %s, function(%s) return {expr="("..nomsu:tree_to_lua(_me).expr..")[ "..%s.."]("..%s..")"}; - end, %s); - ACTION_METADATA[%s[ %s]] = ACTION_METADATA[ACTIONS[ %s]]; + end); + ACTION_METADATA[%s[ %s]] = ACTION_METADATA[ACTION[ %s]]; ]==]):format( - \%class_identifier, repr(stub), table.concat(args, ", "), - body_lua, - repr(names), repr(\%line:get_line_no()), table.concat(args, ", "), - repr(repr(stub)), table.concat(arg_nomsus, ".."), - repr(nomsu:dedent(\%line:get_src())), - \%class_identifier, repr(stub), repr(stub))); + \%class_identifier, repr(stubs[1]), lua_fn_args, + body_code, + repr(signature), repr(code_location), lua_fn_args, + repr(repr(stubs[1])), compiled_args, + \%class_identifier, repr(stubs[1]), repr(stubs[1]))); - return ".." - do -- \%class_identifier - -- Create the class object: - local \%class_identifier = setmetatable({ - name=\(%classname as lua), instances=setmetatable({}, {__mode="k"}), - }, { - __tostring=function(c) return c.name; end, - __call=function(cls, inst) - inst = inst or {}; - inst.id = tostring(inst):match('table: (.*)'); - setmetatable(inst, cls.instance_metatable); - cls.instances[inst] = true; - if inst['set % up'] then - inst['set % up'](inst); - end - return inst; - end, - }); - \%class_identifier.class = \%class_identifier; + return {..} + statements:".." + do -- \%class_identifier + -- Create the class object: + local \%class_identifier = setmetatable({ + name=\(%classname as lua expr), instances=setmetatable({}, {__mode="k"}), + }, { + __tostring=function(c) return c.name; end, + __call=function(cls, inst) + inst = inst or {}; + inst.id = tostring(inst):match('table: (.*)'); + setmetatable(inst, cls.instance_metatable); + cls.instances[inst] = true; + if inst['set % up'] then + inst['set % up'](inst); + end + return inst; + end, + }); + \%class_identifier.class = \%class_identifier; - -- Define the methods: - \(%methods joined with "\n") + -- Define the methods: + \(%methods joined with "\n") - -- Define class methods for instantiating and accessing instances: - \%class_identifier.instance_metatable = { - __index=\%class_identifier, - __tostring=\%class_identifier['% as text'] or function(inst) - return "<"..inst.class.name..": "..inst.id..">"; - end, - }; - nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function() - return utils.keys(\%class_identifier.instances); - end, ""); - nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance) - return \%class_identifier(_instance); - end, ""); - nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function() - return \%class_identifier({}); - end, ""); - end -- End of definition of \%class_identifier - \("\n\n") - + -- Define class methods for instantiating and accessing instances: + \%class_identifier.instance_metatable = { + __index=\%class_identifier, + __tostring=\%class_identifier['% as text'] or function(inst) + return "<"..inst.class.name..": "..inst.id..">"; + end, + }; + nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function() + return utils.keys(\%class_identifier.instances); + end, ""); + nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance) + return \%class_identifier(_instance); + end, ""); + nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function() + return \%class_identifier({}); + end, ""); + end -- End of definition of \%class_identifier + \("\n\n") + diff --git a/nomsu.moon b/nomsu.moon index a42e2e9..0f9ebbf 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -66,33 +66,28 @@ NOMSU_DEFS = with {} R("\240\244")*R("\128\191")*R("\128\191")*R("\128\191")) .ident_char = R("az","AZ","09") + P("_") + .utf8_char - -- If the number of leading space characters is greater than number in the top of the - -- stack, this pattern matches and pushes the number onto the stack. + -- If the line begins with #indent+4 spaces, the pattern matches *those* spaces + -- and adds them to the stack (not any more). .indent = P (start)=> - spaces = @match("[ \t]*", start) - if #spaces > lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] - insert(lpeg.userdata.indent_stack, #spaces) - return start + #spaces - -- If the number of leading space characters is less than number in the top of the - -- stack, this pattern matches and pops off the top of the stack exactly once. + nodent = lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + indented = nodent.." " + if @sub(start, start+#indented-1) == indented + insert(lpeg.userdata.indent_stack, indented) + return start + #indented + -- If the number of leading space characters is <= the number of space on the top of the + -- stack minus 4, this pattern matches and pops off the top of the stack exactly once. .dedent = P (start)=> - spaces = @match("[ \t]*", start) - if #spaces < lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + nodent = lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + spaces = @match("[ ]*", start) + if #spaces <= #nodent-4 remove(lpeg.userdata.indent_stack) return start - -- If the number of leading space characters is equal to the number on the top of the + -- If the number of leading space characters is >= the number on the top of the -- stack, this pattern matches and does not modify the stack. .nodent = P (start)=> - spaces = @match("[ \t]*", start) - if #spaces == lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] - return start + #spaces - -- If the number of leading space characters is 4+ more than the number on the top of the - -- stack, this pattern matches the first n+4 spaces and does not modify the stack. - .gt_nodent = P (start)=> - -- Note! This assumes indent is exactly 4 spaces!!! - spaces = @match("[ \t]*", start) - if #spaces >= lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + 4 - return start + lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + 4 + nodent = lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + if @sub(start, start+#nodent-1) == nodent + return start + #nodent .error = (src,pos,err_msg)-> if lpeg.userdata.source_code\sub(pos,pos)\match("[\r\n]") @@ -146,7 +141,6 @@ NOMSU = do re.compile(nomsu_peg, NOMSU_DEFS) class NomsuCompiler - @def_number: 0 new:()=> -- Weak-key mapping from objects to randomly generated unique IDs NaN_surrogate = {} @@ -173,7 +167,7 @@ class NomsuCompiler :table, :assert, :dofile, :loadstring, :type, :select, :debug, :math, :io, :pairs, :load, :ipairs, } - @environment.ACTIONS = setmetatable({}, {__index:(key)=> + @environment.ACTION = setmetatable({}, {__index:(key)=> error("Attempt to run undefined action: #{key}", 0) }) @action_metadata = setmetatable({}, {__mode:"k"}) @@ -192,7 +186,6 @@ class NomsuCompiler error("Invalid signature, expected list of strings, but got: #{repr signature}", 0) stubs = @get_stubs_from_signature signature stub_args = @get_args_from_signature signature - @@def_number += 1 fn_info = debug.getinfo(fn, "u") local fn_arg_positions, arg_orders @@ -202,9 +195,9 @@ class NomsuCompiler for sig_i=1,#stubs stub, args = stubs[sig_i], stub_args[sig_i] if @debug - print "#{colored.bright "ALIAS:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(args)} ON: #{@environment.ACTIONS}" - -- TODO: use debug.getupvalue instead of @environment.ACTIONS? - @environment.ACTIONS[stub] = fn + print "#{colored.bright "ALIAS:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(args)} ON: #{@environment.ACTION}" + -- TODO: use debug.getupvalue instead of @environment.ACTION? + @environment.ACTION[stub] = fn unless fn_info.isvararg arg_positions = [fn_arg_positions[a] for a in *args] -- TODO: better error checking? @@ -255,7 +248,7 @@ class NomsuCompiler if @debug print "#{colored.bright "PARSING:"}\n#{colored.yellow nomsu_code}" - userdata = with {source_code:nomsu_code, :filename, indent_stack: {0}} + userdata = with {source_code:nomsu_code, :filename, indent_stack: {""}} .get_src = => nomsu_code\sub(@start, @stop-1) .line_starts = line_counter\match(.source_code) .get_line_no = => @@ -650,7 +643,7 @@ class NomsuCompiler insert @compilestack, tree -- Rawget here to avoid triggering an error for accessing an undefined action - fn = rawget(@environment.ACTIONS, tree.stub) + fn = rawget(@environment.ACTION, tree.stub) metadata = @environment.ACTION_METADATA[fn] if metadata and metadata.compile_time args = [arg for arg in *tree.value when arg.type != "Word"] @@ -691,7 +684,7 @@ class NomsuCompiler args = new_args remove @compilestack - return expr:@@comma_separated_items("ACTIONS[#{repr tree.stub}](", args, ")") + return expr:@@comma_separated_items("ACTION[#{repr tree.stub}](", args, ")") when "Text" concat_parts = {} diff --git a/nomsu.peg b/nomsu.peg index e7f34a3..ae3e488 100644 --- a/nomsu.peg +++ b/nomsu.peg @@ -10,7 +10,7 @@ shebang: "#!" [^%nl]* %nl statement: functioncall / expression indented_block (Block): - {| indent + {| ("(..)")? indent statement (nodent statement)* (dedent / (("" -> "Error while parsing block") => error)) |} @@ -36,20 +36,28 @@ functioncall (FunctionCall): word (Word): { %operator / (!number plain_word) } inline_text (Text): - !('".."' %ws* line_comment? %nl (%ws* %nl)* %gt_nodent) + !('".."' eol) '"' {| ({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~} / text_interpolation)* |} '"' +-- Have to use "%indent" instead of "indent" etc. to avoid messing up text lines that start with "#" indented_text (Text): - '".."' %ws* line_comment? %nl %gt_nodent? {| + '".."' eol %nl {| + {~ (%nl*) (%indent -> "") ~} ({~ - (("\\" -> "\") / (("\" eol %nl %gt_nodent "..") -> "") - / (%nl+ {~ %gt_nodent -> "" ~}) / [^%nl\])+ + (("\\" -> "\") / (("\" eol %nl+ %nodent "..") -> "") + / (%nl+ {~ %nodent -> "" ~}) / [^%nl\])+ ~} / text_interpolation)* - |} ((!.) / (&(%nl+ !%gt_nodent)) / (("" -> "Error while parsing Text") => error)) + |} (((!.) &%dedent) / (&(%nl %dedent)) / (("" -> "Error while parsing Text") => error)) text_interpolation: - "\" (variable / inline_list / inline_dict / inline_text / ("(" %ws* (inline_functioncall / inline_expression) %ws* ")")) + "\" ( + variable / inline_list / inline_dict / inline_text + / ("(" %ws* (inline_functioncall / inline_expression) %ws* ")") + / (%ws* (block_comment / line_comment)? nodent "..") + / (indented_text %nl+ %nodent "..") + / ((indented_list / indented_block) nodent "..") + ) number (Number): (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber) @@ -84,7 +92,7 @@ dict_line: inline_dict_item: {| {:dict_key: inline_expression / word :} %ws* ":" %ws* {:dict_value: inline_functioncall / inline_expression :} |} -block_comment(Comment): "#.." { [^%nl]* (%nl %gt_nodent [^%nl]*)* } +block_comment(Comment): "#.." { [^%nl]* (%nl+ %indent [^%nl]* (%nl+ %nodent [^%nl]*)* %dedent)? } line_comment(Comment): "#" { [^%nl]* } eol: %ws* line_comment? (!. / &%nl)