aboutsummaryrefslogtreecommitdiff
path: root/nomsu.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-12-04 17:35:47 -0800
committerBruce Hill <bitbucket@bruce-hill.com>2017-12-04 17:35:47 -0800
commitb3b8c4d731b0983d5b12c56fc245a8d7c1d631f4 (patch)
tree21c1bf182440b26edb621e76cf8e730d7dc6849e /nomsu.moon
parent8c0816995afaaf34cbfe903e6da68d8b6d8e8c39 (diff)
Some stuff changed to allow escaped args and some other ports from the
two_defs branch.
Diffstat (limited to 'nomsu.moon')
-rwxr-xr-xnomsu.moon224
1 files changed, 158 insertions, 66 deletions
diff --git a/nomsu.moon b/nomsu.moon
index 899b1fc..676f9b8 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -19,6 +19,12 @@ colors = setmetatable({}, {__index:->""})
colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..msg..colors.reset)})
{:insert, :remove, :concat} = table
--pcall = (fn,...)-> true, fn(...)
+if _VERSION == "Lua 5.1"
+ xp = xpcall
+ xpcall = (f, errhandler, ...)->
+ args = {n:select("#", ...), ...}
+ return xp((...)-> f(unpack(args,1,args.n))), errhandler
+--pcall = (fn, ...) -> xpcall(fn, debug.traceback, ...)
-- TODO:
-- Maybe get GOTOs working at file scope.
@@ -28,7 +34,6 @@ colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..msg..col
-- better scoping?
-- better error reporting
-- fix propagation of filename for error reporting
--- add line numbers of function calls
-- type checking?
-- Fix compiler bug that breaks when file ends with a block comment
-- Add compiler options for optimization level (compile-fast vs. run-fast, etc.)
@@ -162,7 +167,8 @@ defs =
for _ in src\sub(1,pos)\gmatch("\n") do line_no += 1
return pos, "#{CURRENT_FILE}:#{line_no}"
FunctionCall: (src, line_no, value, errors)->
- {type: "FunctionCall", :src, :line_no, :value, :errors}
+ stub = concat([(t.type == "Word" and t.value or "%") for t in *value], " ")
+ {type: "FunctionCall", :src, :line_no, :value, :errors, :stub}
error: (src,pos,errors,err_msg)->
line_no = 1
for _ in src\sub(1,-#errors)\gmatch("\n") do line_no += 1
@@ -190,17 +196,21 @@ setmetatable(defs, {
nomsu = re.compile(nomsu, defs)
class NomsuCompiler
+ @def_number: 0
new:(parent)=>
@write = (...)=> io.write(...)
@write_err = (...)=> io.stderr\write(...)
- @defs = setmetatable({}, {__index:parent and parent.defs})
- @def_number = 0
+ -- Use # to prevent someone from defining a function that has a namespace collision.
+ @defs = {["#vars"]:{}, ["#loaded_files"]:{}}
+ if parent
+ setmetatable(@defs, {__index:parent.defs})
+ setmetatable(@defs["#vars"], {__index:parent["#vars"]})
+ setmetatable(@defs["#loaded_files"], {__index:parent["#loaded_files"]})
@callstack = {}
@debug = false
@utils = utils
@repr = (...)=> repr(...)
@stringify = (...)=> utils.stringify(...)
- @loaded_files = setmetatable({}, {__index:parent and parent.loaded_files})
if not parent
@initialize_core!
@@ -219,9 +229,12 @@ class NomsuCompiler
signature = @get_stubs signature
assert type(thunk) == 'function', "Bad thunk: #{repr thunk}"
canonical_args = nil
+ canonical_escaped_args = nil
aliases = {}
- @def_number += 1
- for {stub, arg_names} in *signature
+ @@def_number += 1
+ def = {:thunk, :src, :is_macro, aliases:{}, def_number:@@def_number, defs:@defs}
+ where_defs_go = ((getmetatable(@defs) or {}).__newindex) or @defs
+ for {stub, arg_names, escaped_args} in *signature
assert stub, "NO STUB FOUND: #{repr signature}"
if @debug then @writeln "#{colored.bright "DEFINING RULE:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(arg_names)}"
for i=1,#arg_names-1 do for j=i+1,#arg_names
@@ -229,22 +242,58 @@ class NomsuCompiler
if canonical_args
assert utils.equivalent(utils.set(arg_names), canonical_args), "Mismatched args"
else canonical_args = utils.set(arg_names)
- insert aliases, stub
- @defs[stub] = {:thunk, :stub, :arg_names, :src, :is_macro, :aliases, def_number:@def_number}
+ if canonical_escaped_args
+ assert utils.equivalent(escaped_args, canonical_escaped_args), "Mismatched escaped args"
+ else
+ canonical_escaped_args = escaped_args
+ def.escaped_args = escaped_args
+ insert def.aliases, stub
+ stub_def = setmetatable({:stub, :arg_names, :escaped_args}, {__index:def})
+ rawset(where_defs_go, stub, stub_def)
defmacro: (signature, thunk, src)=>
@def(signature, thunk, src, true)
-
- serialize_defs: (after=0)=>
- defs = {}
- for _, def in pairs(@defs)
- defs[def.def_number] = def.src or ""
- keys = utils.keys(defs)
+
+ scoped: (thunk)=>
+ old_defs = @defs
+ @defs = setmetatable({}, {__index:old_defs})
+ ok, ret1, ret2 = pcall thunk, @
+ @defs = old_defs
+ if not ok then @error(ret1)
+ return ret1, ret2
+
+ serialize_defs: (scope=nil, after=0)=>
+ scope or= @defs
+ defs_by_num = {}
+ for stub, def in pairs(scope)
+ if def and stub\sub(1,1) != "#"
+ defs_by_num[def.def_number] = def
+ keys = [k for k,v in pairs(defs_by_num)]
table.sort(keys)
+
buff = {}
- for i in *keys
- if i > after and #defs[i] > 0
- insert buff, defs[i]
+ k_i = 1
+ _using = nil
+ _using_do = {}
+ for k_i,i in ipairs(keys)
+ if i <= after then continue
+ def = defs_by_num[i]
+ if def.defs == scope
+ if def.src
+ insert buff, def.src
+ continue
+ if _using == def.defs
+ if def.src
+ insert _using_do, def.src
+ else
+ _using = def.defs
+ _using_do = {def.src}
+ if k_i == #keys or defs_by_num[keys[k_i+1]].defs != _using
+ insert buff, "using:\n #{@indent @serialize_defs(_using)}\n..do:\n #{@indent concat(_using_do, "\n")}"
+
+ for k,v in pairs(scope["#vars"] or {})
+ insert buff, "<@#{k}> = #{@value_to_nomsu v}"
+
return concat buff, "\n"
call: (stub,line_no,...)=>
@@ -254,7 +303,7 @@ class NomsuCompiler
if def and def.is_macro and @callstack[#@callstack] != "#macro"
@error "Attempt to call macro at runtime: #{stub}\nThis can be caused by using a macro in a function that is defined before the macro."
insert @callstack, {stub, line_no}
- if def == nil
+ unless def
@error "Attempt to call undefined function: #{stub}"
unless def.is_macro
@assert_permission(stub)
@@ -263,22 +312,41 @@ class NomsuCompiler
if @debug
@write "#{colored.bright "CALLING"} #{colored.magenta(colored.underscore stub)} "
@writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr(args)}"
- -- TODO: optimize, but still allow multiple return values?
+ old_defs, @defs = @defs, def.defs
rets = {thunk(self,args)}
+ @defs = old_defs
remove @callstack
return unpack(rets)
run_macro: (tree)=>
- stub = @get_stub tree
args = [arg for arg in *tree.value when arg.type != "Word"]
if @debug
- @write "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(stub)} "
+ @write "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(tree.stub)} "
@writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr args}"
insert @callstack, "#macro"
- expr, statement = @call(stub, tree.line_no, unpack(args))
+ expr, statement = @call(tree.stub, tree.line_no, unpack(args))
remove @callstack
return expr, statement
+ dedent: (code)=>
+ unless code\find("\n")
+ return code
+ spaces, indent_spaces = math.huge, math.huge
+ for line in code\gmatch("\n([^\n]*)")
+ if line\match("^%s*#.*")
+ continue
+ elseif s = line\match("^(%s*)%.%..*")
+ spaces = math.min(spaces, #s)
+ elseif s = line\match("^(%s*)%S.*")
+ indent_spaces = math.min(indent_spaces, #s)
+ if spaces != math.huge and spaces < indent_spaces
+ return (code\gsub("\n"..(" ")\rep(spaces), "\n"))
+ else
+ return (code\gsub("\n"..(" ")\rep(indent_spaces), "\n "))
+
+ indent: (code)=>
+ (code\gsub("\n","\n "))
+
assert_permission: (stub)=>
fn_def = @defs[stub]
unless fn_def
@@ -343,7 +411,7 @@ class NomsuCompiler
ok,expr,statements = pcall(@tree_to_lua, self, statement)
if not ok
@errorln "#{colored.red "Error occurred in statement:"}\n#{colored.bright colored.yellow statement.src}"
- @error(expr)
+ error(expr)
code_for_statement = ([[
return (function(nomsu, vars)
%s
@@ -365,7 +433,7 @@ end);]])\format(statements or "", expr or "ret")
if not ok
@errorln "#{colored.red "Error occurred in statement:"}\n#{colored.yellow statement.src}"
@errorln debug.traceback!
- @error(ret)
+ error(ret)
if statements
insert buffer, statements
if expr
@@ -392,8 +460,6 @@ end);]])\format(concat(buffer, "\n"))
tree_to_nomsu: (tree, force_inline=false)=>
-- Return <nomsu code>, <is safe for inline use>
- indent = (s)->
- s\gsub("\n","\n ")
assert tree, "No tree provided."
if not tree.type
@errorln debug.traceback()
@@ -410,27 +476,31 @@ end);]])\format(concat(buffer, "\n"))
if force_inline
return "{#{concat([@tree_to_nomsu(v, true) for v in *tree.value], "; ")}", true
else
- return ":"..indent("\n"..concat([@tree_to_nomsu v for v in *tree.value], "\n")), false
+ return ":"..@indent("\n"..concat([@tree_to_nomsu v for v in *tree.value], "\n")), false
when "FunctionCall"
buff = ""
sep = ""
inline = true
- do_arg = (arg)->
-
+ line_len = 0
for arg in *tree.value
nomsu, arg_inline = @tree_to_nomsu(arg, force_inline)
- buff ..= sep
+ if sep == " " and line_len + #nomsu > 80
+ sep = "\n.."
+ unless sep == " " and not arg_inline and nomsu\sub(1,1) == ":"
+ buff ..= sep
if arg_inline
sep = " "
+ line_len += 1 + #nomsu
else
+ line_len = 0
inline = false
sep = "\n.."
if arg.type == 'FunctionCall'
if arg_inline
buff ..= "(#{nomsu})"
else
- buff ..= "(..)\n #{indent nomsu}"
+ buff ..= "(..)\n #{@indent nomsu}"
else
buff ..= nomsu
return buff, inline
@@ -491,6 +561,22 @@ end);]])\format(concat(buffer, "\n"))
else
@error("Unknown/unimplemented thingy: #{tree.type}")
+ value_to_nomsu: (value)=>
+ switch type(value)
+ when "nil"
+ return "(nil)"
+ when "bool"
+ return value and "(yes)" or "(no)"
+ when "number"
+ return repr(value)
+ when "table"
+ if utils.is_list(value)
+ return "[#{concat [@value_to_nomsu(v) for v in *value], ", "}]"
+ else
+ return "(d{#{concat ["#{@value_to_nomsu(k)}=#{@value_to_nomsu(v)}" for k,v in pairs(value)], "; "}})"
+ else
+ error("Unsupported value_to_nomsu type: #{type(value)}")
+
tree_to_lua: (tree)=>
-- Return <lua code for value>, <additional lua code>
assert tree, "No tree provided."
@@ -502,7 +588,7 @@ end);]])\format(concat(buffer, "\n"))
error("Should not be converting File to lua through this function.")
when "Nomsu"
- return "nomsu:parse(#{repr tree.value.src}).value[1]", nil
+ return "nomsu:parse(#{repr tree.value.src}, #{repr CURRENT_FILE}).value[1]", nil
when "Thunk"
lua_bits = {}
@@ -518,23 +604,31 @@ return ret;
end)]])\format(concat(lua_bits, "\n"))
when "FunctionCall"
- stub = @get_stub(tree)
- def = @defs[stub]
+ def = @defs[tree.stub]
if def and def.is_macro
expr, statement = @run_macro(tree)
if def.whiteset
if expr
- expr = "(nomsu:assert_permission(#{repr stub}) and #{expr})"
+ expr = "(nomsu:assert_permission(#{repr tree.stub}) and #{expr})"
if statement
- statement = "nomsu:assert_permission(#{repr stub});\n"..statement
+ statement = "nomsu:assert_permission(#{repr tree.stub});\n"..statement
return expr, statement
- args = {repr(stub), repr(tree.line_no)}
+ args = {repr(tree.stub), repr(tree.line_no)}
+ local arg_names, escaped_args
+ if def
+ arg_names, escaped_args = def.arg_names, def.escaped_args
+ else
+ arg_names, escaped_args = [w.value for w in *tree.value when w.type == "Word"], {}
+ arg_num = 1
for arg in *tree.value
if arg.type == 'Word' then continue
+ if escaped_args[arg_names[arg_num]]
+ arg = {type:"Nomsu", value:arg}
expr,statement = @tree_to_lua arg
if statement
@error "Cannot use [[#{arg.src}]] as a function argument, since it's not an expression."
insert args, expr
+ arg_num += 1
return @@comma_separated_items("nomsu:call(", args, ")"), nil
when "String"
@@ -658,27 +752,22 @@ end)]])\format(concat(lua_bits, "\n"))
get_stub: (x)=>
if not x
@error "Nothing to get stub from"
- -- Returns a single stub ("say %"), and list of arg names ({"msg"}) from a single rule def
+ -- Returns a single stub ("say %"), list of arg names ({"msg"}), and set of arg
+ -- names that should not be evaluated from a single rule def
-- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg")))
if type(x) == 'string'
- stub = x\gsub("([#{wordbreaker}]+)"," %1 ")\gsub("%%%S+","%%")\gsub("%s+"," ")\gsub("^%s*","")\gsub("%s*$","")
- arg_names = [arg for arg in x\gmatch("%%([^%s']*)")]
- return stub, arg_names
+ -- Standardize format to stuff separated by spaces
+ x = x\gsub("\n%s*%.%.", " ")\gsub("([#{wordbreaker}]+)", " %1 ")\gsub("%s+"," ")
+ x = x\gsub("^%s*","")\gsub("%s*$","")
+ stub = x\gsub("%%%S+","%%")\gsub("\\","")
+ arg_names = [arg for arg in x\gmatch("%%([^%s]*)")]
+ escaped_args = utils.set [arg for arg in x\gmatch("\\%%([^%s]*)")]
+ return stub, arg_names, escaped_args
+ if type(x) != 'table'
+ @error "Invalid type for getting stub: #{type(x)} for:\n#{repr x}"
switch x.type
when "String" then return @get_stub(x.value)
- when "FunctionCall"
- stub, arg_names = {}, {}, {}
- for token in *x.value
- switch token.type
- when "Word"
- insert stub, token.value
- when "Var"
- insert stub, "%"
- if arg_names then insert arg_names, token.value
- else
- insert stub, "%"
- arg_names = nil
- return concat(stub," "), arg_names
+ when "FunctionCall" then return @get_stub(x.src)
else @error "Unsupported get stub type: #{x.type} for #{repr x}"
get_stubs: (x)=>
@@ -699,31 +788,33 @@ end)]])\format(concat(lua_bits, "\n"))
if verboten == "_" then "__" else ("_%x")\format(verboten\byte!))
error: (msg)=>
- @errorln (colored.red "ERROR!")
+ error_msg = colored.red "ERROR!"
if msg
- @errorln(colored.bright colored.yellow colored.onred msg)
- @errorln("Callstack:")
+ error_msg ..= "\n" .. (colored.bright colored.yellow colored.onred msg)
+ error_msg ..= "\nCallstack:"
maxlen = utils.max([#c[2] for c in *@callstack when c != "#macro"])
for i=#@callstack,1,-1
if @callstack[i] != "#macro"
- @errorln " #{"%-#{maxlen}s"\format @callstack[i][2]}| #{@callstack[i][1]}"
- @errorln " <top level>"
+ error_msg ..= "\n #{"%-#{maxlen}s"\format @callstack[i][2]}| #{@callstack[i][1]}"
+ error_msg ..= "\n <top level>"
@callstack = {}
- error!
+ error error_msg, 3
typecheck: (vars, varname, desired_type)=>
x = vars[varname]
if type(x) == desired_type then return x
if type(x) == 'table' and x.type == desired_type then return x
- @error "Invalid type for %#{varname}. Expected #{desired_type}, but got #{x.type}."
+ @error "Invalid type for %#{varname}. Expected #{desired_type}, but got #{repr x}."
initialize_core: =>
-- Sets up some core functionality
- nomsu_string_as_lua = (code)=>
+ nomsu_string_as_lua = (code, tree)=>
concat_parts = {}
for bit in *code.value
if type(bit) == "string"
insert concat_parts, bit
+ elseif type(bit) == "table" and bit.type == "FunctionCall" and bit.src == "__src__"
+ insert concat_parts, repr(tree.src)
else
expr, statement = @tree_to_lua bit
if statement
@@ -763,9 +854,10 @@ end)]])\format(concat(lua_bits, "\n"))
@def "run file %filename", run_file
_require = (vars)=>
- if not @loaded_files[vars.filename]
- @loaded_files[vars.filename] = run_file(self, {filename:vars.filename}) or true
- return @loaded_files[vars.filename]
+ loaded = @defs["#loaded_files"]
+ if not loaded[vars.filename]
+ loaded[vars.filename] = run_file(self, {filename:vars.filename}) or true
+ return loaded[vars.filename]
@def "require %filename", _require