aboutsummaryrefslogtreecommitdiff
path: root/nomsu.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2018-01-12 19:27:59 -0800
committerBruce Hill <bitbucket@bruce-hill.com>2018-01-12 19:28:19 -0800
commit5a526675db93ea95efc82af7fc6c8f80b0275e8a (patch)
tree1127798d9caa3512d175217edb1f96bd56205332 /nomsu.moon
parent268a636157bb1fea0e90068a1c14d3c5f492134e (diff)
Better usage of ACTION_METADATA and deprecating nomsu.defs.
Diffstat (limited to 'nomsu.moon')
-rwxr-xr-xnomsu.moon169
1 files changed, 77 insertions, 92 deletions
diff --git a/nomsu.moon b/nomsu.moon
index 23ecede..054d907 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -33,7 +33,6 @@ if _VERSION == "Lua 5.1"
-- Fix compiler bug that breaks when file ends with a block comment
-- Add compiler options for optimization level (compile-fast vs. run-fast, etc.)
-- Do a pass on all actions to enforce parameters-are-nouns heuristic
--- Put function defs into a separate table so we can do nomsu.defs["foo"](nomsu, ...) directly without a ".fn"
-- Maybe do some sort of lazy definitions of actions that defer until they're used in code
-- Do automatic "local" detection of new variables and declare them as locals like moonscript does
@@ -148,7 +147,6 @@ class NomsuCompiler
@write = (...)=> io.write(...)
@write_err = (...)=> io.stderr\write(...)
-- Use # to prevent someone from defining a function that has a namespace collision.
- @defs = {["#vars"]:{}, ["#loaded_files"]:{}}
@ids = setmetatable({}, {
__mode: "k"
__index: (key)=>
@@ -157,15 +155,16 @@ class NomsuCompiler
return id
})
if parent
- setmetatable(@defs, {__index:parent.defs})
- setmetatable(@defs["#vars"], {__index:parent["#vars"]})
- setmetatable(@defs["#loaded_files"], {__index:parent["#loaded_files"]})
+ -- TODO: Implement
+ error("Not implemented")
@compilestack = {}
@debug = false
+ @action_metadata = setmetatable({}, {__mode:"k"})
@environment = {
-- Discretionary/convenience stuff
nomsu:self, repr:repr, stringify:stringify, utils:utils, lpeg:lpeg, re:re,
+ ACTIONS:{}, ACTION_METADATA:@action_metadata, LOADED:{},
-- Lua stuff:
:next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall,
:error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module,
@@ -189,38 +188,37 @@ class NomsuCompiler
signature = @get_stubs {signature}
elseif type(signature) == 'table' and type(signature[1]) == 'string'
signature = @get_stubs signature
- @assert type(fn) == 'function', "Bad fn: #{repr fn}"
+ assert type(fn) == 'function', "Bad fn: #{repr fn}"
aliases = {}
@@def_number += 1
- def = {:fn, :src, :line_no, :compile_time, aliases:{}, def_number:@@def_number, defs:@defs}
- where_defs_go = (getmetatable(@defs) or {}).__newindex or @defs
+
+ fn_info = debug.getinfo(fn, "u")
+ fn_arg_positions = {debug.getlocal(fn, i), i for i=1,fn_info.nparams}
+ arg_orders = {} -- Map from stub -> index where each arg in the stub goes in the function call
for sig_i=1,#signature
- stub, arg_names, escaped_args = unpack(signature[sig_i])
- arg_positions = {}
- @assert stub, "NO STUB FOUND: #{repr signature}"
- if @debug then @writeln "#{colored.bright "DEFINING ACTION:"} #{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
- if arg_names[i] == arg_names[j] then @error "Duplicate argument in function #{stub}: '#{arg_names[i]}'"
-
- if sig_i == 1
- arg_positions = [i for i=1,#arg_names]
- def.args = arg_names
- def.escaped_args = escaped_args
- else
- @assert equivalent(set(def.args), set(arg_names)), "Mismatched args"
- @assert equivalent(def.escaped_args, escaped_args), "Mismatched escaped args"
- for j,a in ipairs(arg_names)
- for i,c_a in ipairs(def.args)
- if a == c_a
- arg_positions[j] = i
- insert def.aliases, stub
- stub_def = setmetatable({:stub, :arg_names, :arg_positions}, {__index:def})
- rawset(where_defs_go, stub, stub_def)
+ stub, arg_names = unpack(signature[sig_i])
+ arg_positions = [fn_arg_positions[@var_to_lua_identifier(a)] for a in *arg_names]
+ -- TODO: better error checking?
+ assert(#arg_positions == #arg_names,
+ "Mismatch in args between lua function's #{repr fn_arg_positions} and stub's #{repr arg_names}")
+ -- TODO: use debug.getupvalue instead of @environment.ACTIONS?
+ @environment.ACTIONS[stub] = fn
+ assert stub, "NO STUB FOUND: #{repr signature}"
+ if @debug
+ @writeln "#{colored.bright "DEFINING ACTION:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(arg_names)}"
+ arg_orders[stub] = arg_positions
+
+ @action_metadata[fn] = {
+ :fn, :src, :line_no, :aliases, :arg_orders, def_number:@@def_number,
+ }
define_compile_action: (signature, line_no, fn, src)=>
@define_action(signature, line_no, fn, src, true)
+ @action_metadata[fn].compile_time = true
serialize_defs: (scope=nil, after=nil)=>
+ -- TODO: repair
+ error("Not currently functional.")
after or= @core_defs or 0
scope or= @defs
defs_by_num = {}
@@ -275,12 +273,12 @@ class NomsuCompiler
return code\gsub("\n","\n"..(" ")\rep(levels))
parse: (str, filename)=>
- @assert type(filename) == "string", "Bad filename type: #{type filename}"
+ assert type(filename) == "string", "Bad filename type: #{type filename}"
if @debug
@writeln("#{colored.bright "PARSING:"}\n#{colored.yellow str}")
str = str\gsub("\r","")
tree = parse(str, filename)
- @assert tree, "In file #{colored.blue filename} failed to parse:\n#{colored.onyellow colored.black str}"
+ assert tree, "In file #{colored.blue filename} failed to parse:\n#{colored.onyellow colored.black str}"
if @debug
@writeln "PARSE TREE:"
@print_tree tree, " "
@@ -291,11 +289,11 @@ class NomsuCompiler
if max_operations
timeout = ->
debug.sethook!
- @error "Execution quota exceeded. Your code took too long."
+ error "Execution quota exceeded. Your code took too long."
debug.sethook timeout, "", max_operations
tree = @parse(src, filename)
- @assert tree, "Failed to parse: #{src}"
- @assert tree.type == "File", "Attempt to run non-file: #{tree.type}"
+ assert tree, "Failed to parse: #{src}"
+ assert tree.type == "File", "Attempt to run non-file: #{tree.type}"
lua = @tree_to_lua(tree)
lua_code = lua.statements or (lua.expr..";")
@@ -322,15 +320,15 @@ class NomsuCompiler
return @run_lua(lua_code)
file = file or io.open(filename)
if not file
- @error "File does not exist: #{filename}"
+ error "File does not exist: #{filename}"
nomsu_code = file\read('*a')
file\close!
return @run(nomsu_code, filename)
else
- @error "Invalid filetype for #{filename}"
+ error "Invalid filetype for #{filename}"
require_file: (filename)=>
- loaded = @defs["#loaded_files"]
+ loaded = @environment.LOADED
if not loaded[filename]
loaded[filename] = @run_file(filename) or true
return loaded[filename]
@@ -345,7 +343,7 @@ class NomsuCompiler
n = n + 1
("\n%-3d|")\format(n)
code = "1 |"..lua_code\gsub("\n", fn)
- @error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{err}")
+ error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{err}")
return run_lua_fn!
tree_to_value: (tree, filename)=>
@@ -354,15 +352,14 @@ class NomsuCompiler
@writeln "#{colored.bright "RUNNING LUA TO GET VALUE:"}\n#{colored.blue colored.bright(code)}"
lua_thunk, err = load(code, nil, nil, @environment)
if not lua_thunk
- @error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{colored.red err}")
+ error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{colored.red err}")
return lua_thunk!
tree_to_nomsu: (tree, force_inline=false)=>
-- Return <nomsu code>, <is safe for inline use>
- @assert tree, "No tree provided."
+ assert tree, "No tree provided."
if not tree.type
- --@errorln debug.traceback()
- @error "Invalid tree: #{repr(tree)}"
+ error "Invalid tree: #{repr(tree)}"
switch tree.type
when "File"
return concat([@tree_to_nomsu(v, force_inline) for v in *tree.value], "\n"), false
@@ -450,7 +447,7 @@ class NomsuCompiler
when "Dict"
-- TODO: Implement
- @error("Sorry, not yet implemented.")
+ error("Sorry, not yet implemented.")
when "Number"
return repr(tree.value), true
@@ -462,7 +459,7 @@ class NomsuCompiler
return tree.value, true
else
- @error("Unknown/unimplemented thingy: #{tree.type}")
+ error("Unknown/unimplemented thingy: #{tree.type}")
value_to_nomsu: (value)=>
switch type(value)
@@ -491,10 +488,9 @@ class NomsuCompiler
@math_patt: re.compile [[ "%" (" " [*/^+-] " %")+ ]]
tree_to_lua: (tree)=>
-- Return <lua code for value>, <additional lua code>
- @assert tree, "No tree provided."
+ assert tree, "No tree provided."
if not tree.type
- --@errorln debug.traceback()
- @error "Invalid tree: #{repr(tree)}"
+ error "Invalid tree: #{repr(tree)}"
switch tree.type
when "File"
if #tree.value == 1
@@ -503,7 +499,7 @@ class NomsuCompiler
for line in *tree.value
lua = @tree_to_lua line
if not lua
- @error "No lua produced by #{repr line}"
+ error "No lua produced by #{repr line}"
if lua.statements then insert lua_bits, lua.statements
if lua.expr then insert lua_bits, "#{lua.expr};"
return statements:concat(lua_bits, "\n")
@@ -524,16 +520,20 @@ class NomsuCompiler
when "FunctionCall"
insert @compilestack, tree
- def = @defs[tree.stub]
- if def and def.compile_time
+ fn = @environment.ACTIONS[tree.stub]
+ metadata = @environment.ACTION_METADATA[fn]
+ if metadata and metadata.compile_time
args = [arg for arg in *tree.value when arg.type != "Word"]
+ if metadata
+ new_args = [args[p] for p in *metadata.arg_orders[tree.stub]]
+ args = new_args
if @debug
@write "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(tree.stub)} "
@writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr [(repr a)\sub(1,50) for a in *args]}"
- lua = @defs[tree.stub].fn(unpack(args))
+ lua = fn(unpack(args))
remove @compilestack
return lua
- elseif not def and @@math_patt\match(tree.stub)
+ elseif not metadata and @@math_patt\match(tree.stub)
-- This is a bit of a hack, but this code handles arbitrarily complex
-- math expressions like 2*x + 3^2 without having to define a single
-- action for every possibility.
@@ -543,25 +543,24 @@ class NomsuCompiler
insert bits, tok.value
else
lua = @tree_to_lua(tok)
- @assert(lua.statements == nil, "non-expression value inside math expression")
+ assert(lua.statements == nil, "non-expression value inside math expression")
insert bits, lua.expr
remove @compilestack
return expr:"(#{concat bits, " "})"
- arg_positions = def and def.arg_positions or {}
args = {}
for tok in *tree.value
if tok.type == "Word" then continue
lua = @tree_to_lua(tok)
- @assert(lua.expr, "Cannot use #{tok.src} as an argument, since it's not an expression.")
+ assert(lua.expr, "Cannot use #{tok.src} as an argument, since it's not an expression.")
insert args, lua.expr
- if def
- new_args = [args[p] for p in *def.arg_positions]
+ if metadata
+ new_args = [args[p] for p in *metadata.arg_orders[tree.stub]]
args = new_args
remove @compilestack
- return expr:@@comma_separated_items("nomsu.defs[#{repr tree.stub}].fn(", args, ")")
+ return expr:@@comma_separated_items("ACTIONS[#{repr tree.stub}](", args, ")")
when "Text"
concat_parts = {}
@@ -579,7 +578,7 @@ class NomsuCompiler
@print_tree bit
@writeln "#{colored.bright "EXPR:"} #{lua.expr}, #{colored.bright "STATEMENT:"} #{lua.statements}"
if lua.statements
- @error "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression."
+ error "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression."
insert concat_parts, "stringify(#{lua.expr})"
if string_buffer ~= ""
@@ -596,7 +595,7 @@ class NomsuCompiler
for item in *tree.value
lua = @tree_to_lua item
if lua.statements
- @error "Cannot use [[#{item.src}]] as a list item, since it's not an expression."
+ error "Cannot use [[#{item.src}]] as a list item, since it's not an expression."
insert items, lua.expr
return expr:@@comma_separated_items("{", items, "}")
@@ -608,10 +607,10 @@ class NomsuCompiler
else
@tree_to_lua entry.dict_key
if key_lua.statements
- @error "Cannot use [[#{entry.dict_key.src}]] as a dict key, since it's not an expression."
+ error "Cannot use [[#{entry.dict_key.src}]] as a dict key, since it's not an expression."
value_lua = @tree_to_lua entry.dict_value
if value_lua.statements
- @error "Cannot use [[#{entry.dict_value.src}]] as a dict value, since it's not an expression."
+ error "Cannot use [[#{entry.dict_value.src}]] as a dict value, since it's not an expression."
key_str = key_lua.expr\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=])
if key_str
insert items, "#{key_str}=#{value_lua.expr}"
@@ -623,10 +622,10 @@ class NomsuCompiler
return expr:repr(tree.value)
when "Var"
- return expr:("_"..@var_to_lua_identifier(tree.value))
+ return expr:@var_to_lua_identifier(tree.value)
else
- @error("Unknown/unimplemented thingy: #{tree.type}")
+ error("Unknown/unimplemented thingy: #{tree.type}")
walk_tree: (tree, depth=0)=>
coroutine.yield(tree, depth)
@@ -709,27 +708,28 @@ class NomsuCompiler
tree = new_values
return tree
- @stub_patt: re.compile "{|(' '+ / '\n..' / {'\\'? '%' %id*} / {%id+} / {%op})*|}",
+ @stub_patt: re.compile "{|(' '+ / '\n..' / {'%' %id*} / {%id+} / {%op})*|}",
id:IDENT_CHAR, op:OPERATOR_CHAR
get_stub: (x)=>
if not x
- @error "Nothing to get stub from"
+ error "Nothing to get stub from"
-- Returns a single stub ("say %"), list of arg names ({"msg"}), and set of arg
-- names that should not be evaluated from a single action def
-- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg")))
if type(x) == 'string'
-- Standardize format to stuff separated by spaces
spec = concat @@stub_patt\match(x), " "
- stub = spec\gsub("%%%S+","%%")\gsub("\\","")
- arg_names = [arg for arg in spec\gmatch("%%(%S*)")]
- escaped_args = {arg, true for arg in spec\gmatch("\\%%(%S*)")}
- return stub, arg_names, escaped_args
+ arg_names = {}
+ stub = spec\gsub "%%(%S+)", (arg)->
+ insert(arg_names, arg)
+ return "%"
+ return stub, arg_names
if type(x) != 'table'
- @error "Invalid type for getting stub: #{type(x)} for:\n#{repr x}"
+ error "Invalid type for getting stub: #{type(x)} for:\n#{repr x}"
switch x.type
when "Text" then return @get_stub(x.value)
when "FunctionCall" then return @get_stub(x.src)
- else @error "Unsupported get stub type: #{x.type} for #{repr x}"
+ else error "Unsupported get stub type: #{x.type} for #{repr x}"
get_stubs: (x)=>
if type(x) != 'table' then return {{@get_stub(x)}}
@@ -745,17 +745,9 @@ class NomsuCompiler
-- characters with escape sequences
if type(var) == 'table' and var.type == "Var"
var = var.value
- (var\gsub "%W", (verboten)->
+ "_"..(var\gsub "%W", (verboten)->
if verboten == "_" then "__" else ("_%x")\format(verboten\byte!))
- assert: (condition, msg='')=>
- if not condition
- @error("Assertion failed: "..msg)
- return condition
-
- error: (msg)=>
- error msg, 0
-
source_code: (level=0)=>
@dedent @compilestack[#@compilestack-level].src
@@ -773,13 +765,6 @@ class NomsuCompiler
error "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression."
insert concat_parts, lua.expr
return concat(concat_parts)
-
- @define_compile_action "do %block", "nomsu.moon", (_block)->
- make_line = (lua)-> lua.expr and (lua.expr..";") or lua.statements
- if _block.type == "Block"
- return nomsu\tree_to_lua(_block)
- else
- return expr:"#{nomsu\tree_to_lua _block}(nomsu)"
@define_compile_action "immediately %block", "nomsu.moon", (_block)->
lua = nomsu\tree_to_lua(_block)
@@ -876,6 +861,7 @@ if arg
print "= "..repr(ret)
err_hand = (error_message)->
+ -- TODO: write properly to stderr
print("#{colored.red "ERROR:"} #{colored.bright colored.yellow colored.onred (error_message or "")}")
print("stack traceback:")
@@ -885,7 +871,6 @@ if arg
_, line_table = to_lua(nomsu_source)
nomsu_file\close!
- function_defs = {def.fn, def for _,def in pairs(nomsu.defs) when def.fn}
level = 2
while true
calling_fn = debug.getinfo(level)
@@ -895,9 +880,9 @@ if arg
name = calling_fn.name
if name == "run_lua_fn" then continue
line = nil
- if def = function_defs[calling_fn.func]
- line = colored.yellow(def.line_no)
- name = colored.bright(colored.yellow(def.aliases[1]))
+ if metadata = nomsu.action_metadata[calling_fn.func]
+ line = colored.yellow(metadata.line_no)
+ name = colored.bright(colored.yellow(metadata.aliases[1]))
else
if calling_fn.istailcall and not name
name = "<tail call>"