Overhaul of indentations. Now strictly requiring exactly 4 spaces

everywhere, and supporting indented string interpolations.
This commit is contained in:
Bruce Hill 2018-01-30 15:10:37 -08:00
parent d7abcba34d
commit 0563571625
4 changed files with 126 additions and 105 deletions

View File

@ -98,13 +98,13 @@ immediately
action [remove action %stub] action [remove action %stub]
lua> ".." lua> ".."
local fn = ACTIONS[\%stub]; local fn = ACTION[\%stub];
local metadata = ACTION_METADATA[fn]; local metadata = ACTION_METADATA[fn];
for i=#metadata.aliases,1,-1 do for i=#metadata.aliases,1,-1 do
metadata.arg_orders[metadata.aliases[i]] = nil; metadata.arg_orders[metadata.aliases[i]] = nil;
table.remove(metadata.aliases, i); table.remove(metadata.aliases, i);
end end
ACTIONS[\%stub] = nil; ACTION[\%stub] = nil;
immediately immediately
action [%tree as lua] 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))"} compile [%var as lua identifier] to {expr:"nomsu:var_to_lua_identifier(\(%var as lua expr))"}
action [action %names metadata] action [action %names metadata]
=lua "ACTION_METADATA[ACTIONS[\%names]]" =lua "ACTION_METADATA[ACTION[\%names]]"
# Get the source code for a function # Get the source code for a function
action [help %action] action [help %action]

View File

@ -6,26 +6,26 @@ compile [@%var] to
local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'")
or 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 if key_attr then
return "_me."..key_attr; return {expr="_me."..key_attr};
elseif key_lua:sub(1,1) == "[" then elseif key_lua:sub(1,1) == "[" then
key_lua = " "..key_lua.." "; key_lua = " "..key_lua.." ";
end end
return "_me["..key_lua.."]"; return {expr="_me["..key_lua.."]"};
compile [@%var <- %val] to code compile [@%var <- %val] to
lua> ".." lua> ".."
local val_lua = \(%val as lua); local val_lua = \(%val as lua expr);
local key_lua = repr(\%var.value); local key_lua = repr(\%var.value);
local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'")
or 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 if key_attr then
return "_me."..key_attr.." = "..val_lua..";"; return {statements="_me."..key_attr.." = "..val_lua..";"};
elseif key_lua:sub(1,1) == "[" then elseif key_lua:sub(1,1) == "[" then
key_lua = " "..key_lua.." "; key_lua = " "..key_lua.." ";
end 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)") %class_identifier <- (=lua "nomsu:var_to_lua_identifier(\(%classname as value)):sub(2,-1)")
if: %class_identifier is "" if: %class_identifier is ""
%class_identifier <- "class" %class_identifier <- "class"
@ -34,71 +34,91 @@ compile [object %classname %class_body] to
if: (%line's "type") is "Comment" if: (%line's "type") is "Comment"
do next %line do next %line
assume (((%line's "type") == "FunctionCall") and ((%line's "stub") == "action % %")) 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> ".." lua> ".."
assert(\%line.value[3].type == "Block", local signature = {};
"Invalid type for action definition body. Expected Block, but got: "..tostring(\%line.value[3].type)); for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
local names, args = nomsu:parse_spec(\%line.value[2]); local stubs = nomsu:get_stubs_from_signature(signature);
local stub = nomsu:get_stub(names[1]); local stub_args = nomsu:get_args_from_signature(signature);
local body_lua = nomsu:tree_to_lua(\%line.value[3]); local arg_set = {};
body_lua = body_lua.statements or ("return "..body_lua.expr..";"); for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
local arg_nomsus = {}; local body_lua = nomsu:tree_to_lua(\%body);
for i, arg in ipairs(args) do arg_nomsus[i] = "nomsu:tree_to_lua("..arg..").expr"; end 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, ([==[ table.insert(\%methods, ([==[
%s[ %s] = function(%s) %s[ %s] = function(%s)
%s %s
end end
nomsu:define_compile_action(%s, %s, function(%s) nomsu:define_compile_action(%s, %s, function(%s)
return {expr="("..nomsu:tree_to_lua(_me).expr..")[ "..%s.."]("..%s..")"}; return {expr="("..nomsu:tree_to_lua(_me).expr..")[ "..%s.."]("..%s..")"};
end, %s); end);
ACTION_METADATA[%s[ %s]] = ACTION_METADATA[ACTIONS[ %s]]; ACTION_METADATA[%s[ %s]] = ACTION_METADATA[ACTION[ %s]];
]==]):format( ]==]):format(
\%class_identifier, repr(stub), table.concat(args, ", "), \%class_identifier, repr(stubs[1]), lua_fn_args,
body_lua, body_code,
repr(names), repr(\%line:get_line_no()), table.concat(args, ", "), repr(signature), repr(code_location), lua_fn_args,
repr(repr(stub)), table.concat(arg_nomsus, ".."), repr(repr(stubs[1])), compiled_args,
repr(nomsu:dedent(\%line:get_src())), \%class_identifier, repr(stubs[1]), repr(stubs[1])));
\%class_identifier, repr(stub), repr(stub)));
return ".." return {..}
do -- \%class_identifier statements:".."
-- Create the class object: do -- \%class_identifier
local \%class_identifier = setmetatable({ -- Create the class object:
name=\(%classname as lua), instances=setmetatable({}, {__mode="k"}), local \%class_identifier = setmetatable({
}, { name=\(%classname as lua expr), instances=setmetatable({}, {__mode="k"}),
__tostring=function(c) return c.name; end, }, {
__call=function(cls, inst) __tostring=function(c) return c.name; end,
inst = inst or {}; __call=function(cls, inst)
inst.id = tostring(inst):match('table: (.*)'); inst = inst or {};
setmetatable(inst, cls.instance_metatable); inst.id = tostring(inst):match('table: (.*)');
cls.instances[inst] = true; setmetatable(inst, cls.instance_metatable);
if inst['set % up'] then cls.instances[inst] = true;
inst['set % up'](inst); if inst['set % up'] then
end inst['set % up'](inst);
return inst; end
end, return inst;
}); end,
\%class_identifier.class = \%class_identifier; });
\%class_identifier.class = \%class_identifier;
-- Define the methods: -- Define the methods:
\(%methods joined with "\n") \(%methods joined with "\n")
-- Define class methods for instantiating and accessing instances: -- Define class methods for instantiating and accessing instances:
\%class_identifier.instance_metatable = { \%class_identifier.instance_metatable = {
__index=\%class_identifier, __index=\%class_identifier,
__tostring=\%class_identifier['% as text'] or function(inst) __tostring=\%class_identifier['% as text'] or function(inst)
return "<"..inst.class.name..": "..inst.id..">"; return "<"..inst.class.name..": "..inst.id..">";
end, end,
}; };
nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function() nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function()
return utils.keys(\%class_identifier.instances); return utils.keys(\%class_identifier.instances);
end, ""); end, "");
nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance) nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance)
return \%class_identifier(_instance); return \%class_identifier(_instance);
end, ""); end, "");
nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function() nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function()
return \%class_identifier({}); return \%class_identifier({});
end, ""); end, "");
end -- End of definition of \%class_identifier end -- End of definition of \%class_identifier
\("\n\n") \("\n\n")

View File

@ -66,33 +66,28 @@ NOMSU_DEFS = with {}
R("\240\244")*R("\128\191")*R("\128\191")*R("\128\191")) R("\240\244")*R("\128\191")*R("\128\191")*R("\128\191"))
.ident_char = R("az","AZ","09") + P("_") + .utf8_char .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 -- If the line begins with #indent+4 spaces, the pattern matches *those* spaces
-- stack, this pattern matches and pushes the number onto the stack. -- and adds them to the stack (not any more).
.indent = P (start)=> .indent = P (start)=>
spaces = @match("[ \t]*", start) nodent = lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack]
if #spaces > lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] indented = nodent.." "
insert(lpeg.userdata.indent_stack, #spaces) if @sub(start, start+#indented-1) == indented
return start + #spaces insert(lpeg.userdata.indent_stack, indented)
-- If the number of leading space characters is less than number in the top of the return start + #indented
-- stack, this pattern matches and pops off the top of the stack exactly once. -- 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)=> .dedent = P (start)=>
spaces = @match("[ \t]*", start) nodent = lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack]
if #spaces < lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] spaces = @match("[ ]*", start)
if #spaces <= #nodent-4
remove(lpeg.userdata.indent_stack) remove(lpeg.userdata.indent_stack)
return start 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. -- stack, this pattern matches and does not modify the stack.
.nodent = P (start)=> .nodent = P (start)=>
spaces = @match("[ \t]*", start) nodent = lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack]
if #spaces == lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] if @sub(start, start+#nodent-1) == nodent
return start + #spaces return start + #nodent
-- 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
.error = (src,pos,err_msg)-> .error = (src,pos,err_msg)->
if lpeg.userdata.source_code\sub(pos,pos)\match("[\r\n]") if lpeg.userdata.source_code\sub(pos,pos)\match("[\r\n]")
@ -146,7 +141,6 @@ NOMSU = do
re.compile(nomsu_peg, NOMSU_DEFS) re.compile(nomsu_peg, NOMSU_DEFS)
class NomsuCompiler class NomsuCompiler
@def_number: 0
new:()=> new:()=>
-- Weak-key mapping from objects to randomly generated unique IDs -- Weak-key mapping from objects to randomly generated unique IDs
NaN_surrogate = {} NaN_surrogate = {}
@ -173,7 +167,7 @@ class NomsuCompiler
:table, :assert, :dofile, :loadstring, :type, :select, :debug, :math, :io, :pairs, :table, :assert, :dofile, :loadstring, :type, :select, :debug, :math, :io, :pairs,
:load, :ipairs, :load, :ipairs,
} }
@environment.ACTIONS = setmetatable({}, {__index:(key)=> @environment.ACTION = setmetatable({}, {__index:(key)=>
error("Attempt to run undefined action: #{key}", 0) error("Attempt to run undefined action: #{key}", 0)
}) })
@action_metadata = setmetatable({}, {__mode:"k"}) @action_metadata = setmetatable({}, {__mode:"k"})
@ -192,7 +186,6 @@ class NomsuCompiler
error("Invalid signature, expected list of strings, but got: #{repr signature}", 0) error("Invalid signature, expected list of strings, but got: #{repr signature}", 0)
stubs = @get_stubs_from_signature signature stubs = @get_stubs_from_signature signature
stub_args = @get_args_from_signature signature stub_args = @get_args_from_signature signature
@@def_number += 1
fn_info = debug.getinfo(fn, "u") fn_info = debug.getinfo(fn, "u")
local fn_arg_positions, arg_orders local fn_arg_positions, arg_orders
@ -202,9 +195,9 @@ class NomsuCompiler
for sig_i=1,#stubs for sig_i=1,#stubs
stub, args = stubs[sig_i], stub_args[sig_i] stub, args = stubs[sig_i], stub_args[sig_i]
if @debug if @debug
print "#{colored.bright "ALIAS:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(args)} ON: #{@environment.ACTIONS}" 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.ACTIONS? -- TODO: use debug.getupvalue instead of @environment.ACTION?
@environment.ACTIONS[stub] = fn @environment.ACTION[stub] = fn
unless fn_info.isvararg unless fn_info.isvararg
arg_positions = [fn_arg_positions[a] for a in *args] arg_positions = [fn_arg_positions[a] for a in *args]
-- TODO: better error checking? -- TODO: better error checking?
@ -255,7 +248,7 @@ class NomsuCompiler
if @debug if @debug
print "#{colored.bright "PARSING:"}\n#{colored.yellow nomsu_code}" 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) .get_src = => nomsu_code\sub(@start, @stop-1)
.line_starts = line_counter\match(.source_code) .line_starts = line_counter\match(.source_code)
.get_line_no = => .get_line_no = =>
@ -650,7 +643,7 @@ class NomsuCompiler
insert @compilestack, tree insert @compilestack, tree
-- Rawget here to avoid triggering an error for accessing an undefined action -- 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] metadata = @environment.ACTION_METADATA[fn]
if metadata and metadata.compile_time if metadata and metadata.compile_time
args = [arg for arg in *tree.value when arg.type != "Word"] args = [arg for arg in *tree.value when arg.type != "Word"]
@ -691,7 +684,7 @@ class NomsuCompiler
args = new_args args = new_args
remove @compilestack remove @compilestack
return expr:@@comma_separated_items("ACTIONS[#{repr tree.stub}](", args, ")") return expr:@@comma_separated_items("ACTION[#{repr tree.stub}](", args, ")")
when "Text" when "Text"
concat_parts = {} concat_parts = {}

View File

@ -10,7 +10,7 @@ shebang: "#!" [^%nl]* %nl
statement: functioncall / expression statement: functioncall / expression
indented_block (Block): indented_block (Block):
{| indent {| ("(..)")? indent
statement (nodent statement)* statement (nodent statement)*
(dedent / (("" -> "Error while parsing block") => error)) (dedent / (("" -> "Error while parsing block") => error))
|} |}
@ -36,20 +36,28 @@ functioncall (FunctionCall):
word (Word): { %operator / (!number plain_word) } word (Word): { %operator / (!number plain_word) }
inline_text (Text): inline_text (Text):
!('".."' %ws* line_comment? %nl (%ws* %nl)* %gt_nodent) !('".."' eol)
'"' {| '"' {|
({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~} ({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~}
/ text_interpolation)* / text_interpolation)*
|} '"' |} '"'
-- Have to use "%indent" instead of "indent" etc. to avoid messing up text lines that start with "#"
indented_text (Text): indented_text (Text):
'".."' %ws* line_comment? %nl %gt_nodent? {| '".."' eol %nl {|
{~ (%nl*) (%indent -> "") ~}
({~ ({~
(("\\" -> "\") / (("\" eol %nl %gt_nodent "..") -> "") (("\\" -> "\") / (("\" eol %nl+ %nodent "..") -> "")
/ (%nl+ {~ %gt_nodent -> "" ~}) / [^%nl\])+ / (%nl+ {~ %nodent -> "" ~}) / [^%nl\])+
~} / text_interpolation)* ~} / text_interpolation)*
|} ((!.) / (&(%nl+ !%gt_nodent)) / (("" -> "Error while parsing Text") => error)) |} (((!.) &%dedent) / (&(%nl %dedent)) / (("" -> "Error while parsing Text") => error))
text_interpolation: 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) number (Number): (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber)
@ -84,7 +92,7 @@ dict_line:
inline_dict_item: inline_dict_item:
{| {:dict_key: inline_expression / word :} %ws* ":" %ws* {:dict_value: inline_functioncall / inline_expression :} |} {| {: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]* } line_comment(Comment): "#" { [^%nl]* }
eol: %ws* line_comment? (!. / &%nl) eol: %ws* line_comment? (!. / &%nl)