aboutsummaryrefslogtreecommitdiff
path: root/nomic.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-09-12 21:59:46 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-09-12 21:59:46 -0700
commit20a9fd8a3e81fdc264172564cf7405690974bfb6 (patch)
treebd99b1ac9fe7b6cfca06fb050f07f96bf6abf396 /nomic.moon
parent395e6c8e5ba5d8f19f7c8abd81b22cb34629f13d (diff)
Updated names.
Diffstat (limited to 'nomic.moon')
-rwxr-xr-xnomic.moon567
1 files changed, 0 insertions, 567 deletions
diff --git a/nomic.moon b/nomic.moon
deleted file mode 100755
index 812574c..0000000
--- a/nomic.moon
+++ /dev/null
@@ -1,567 +0,0 @@
-#!/usr/bin/env moon
-re = require 're'
-lpeg = require 'lpeg'
-utils = require 'utils'
-
--- TODO:
--- string interpolation
--- comprehensions?
--- dicts?
--- better scoping?
--- first-class functions
-
-INDENT = " "
-lpeg.setmaxstack 10000 -- whoa
-{:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg
-
-wordchar = P(1)-S(' \t\n\r%:;,.{}[]()"')
-comment = re.compile [[comment <- "(#" (comment / ((! "#)") .))* "#)"]]
-whitespace = (S(" \t") + comment)^1
-nl = P("\n")
-blank_line = whitespace^-1 * nl
-
-get_line_indentation = (line)->
- indent_amounts = {[" "]:1, ["\t"]:4}
- with sum = 0
- leading_space = line\gsub("([\t ]*).*", "%1")
- for c in leading_space\gmatch "[\t ]"
- sum += indent_amounts[c]
-
-make_parser = (lingo, extra_definitions)->
- indent_stack = {0}
- push = (n)-> table.insert indent_stack, n
- pop = ()-> table.remove indent_stack
- check_indent = (subject,end_pos,spaces)->
- num_spaces = get_line_indentation(spaces)
- if num_spaces <= indent_stack[#indent_stack] then return nil
- push num_spaces
- return end_pos
- check_dedent = (subject,end_pos,spaces)->
- num_spaces = get_line_indentation(spaces)
- if num_spaces >= indent_stack[#indent_stack] then return nil
- pop!
- return end_pos
- check_nodent = (subject,end_pos,spaces)->
- num_spaces = get_line_indentation(spaces)
- if num_spaces != indent_stack[#indent_stack] then return nil
- return end_pos
-
- defs =
- :wordchar, :nl, ws:whitespace, :comment
- eol: #nl + (P("")-P(1))
- word_boundary: whitespace + B(P("..")) + B(S("\";)]")) + #S("\":([") + #((whitespace + nl)^0 * P(".."))
- indent: #(nl * blank_line^0 * Cmt(whitespace^-1, check_indent))
- dedent: #(nl * blank_line^0 * Cmt(whitespace^-1, check_dedent))
- new_line: nl * blank_line^0 * Cmt(whitespace^-1, check_nodent)
- error_handler: (src,pos,errors)->
- line_no = 1
- for _ in src\sub(1,-#errors)\gmatch("\n") do line_no += 1
- err_pos = #src - #errors + 1
- if errors\sub(1,1) == "\n"
- -- Indentation error
- err_pos += #errors\match("[ \t]*", 2)
- start_of_err_line = err_pos
- while src\sub(start_of_err_line, start_of_err_line) != "\n" do start_of_err_line -= 1
- start_of_prev_line = start_of_err_line - 1
- while src\sub(start_of_prev_line, start_of_prev_line) != "\n" do start_of_prev_line -= 1
-
- prev_line,err_line,next_line = src\match("([^\n]*)\n([^\n]*)\n([^\n]*)", start_of_prev_line+1)
-
- pointer = ("-")\rep(err_pos - start_of_err_line + 0) .. "^"
- error("\nParse error on line #{line_no}:\n\n#{prev_line}\n#{err_line}\n#{pointer}\n#{next_line}\n")
-
- if extra_definitions
- for k,v in pairs(extra_definitions) do defs[k] = v
-
- setmetatable(defs, {
- __index: (t,key)->
- fn = (src, value, errors)->
- token = {type: key, :src, :value, :errors}
- return token
- t[key] = fn
- return fn
- })
- return re.compile lingo, defs
-
-class Compiler
- new:(parent)=>
- @defs = setmetatable({}, {__index:parent and parent.defs})
- @callstack = {}
- @debug = false
- @initialize_core!
-
- call: (fn_name,...)=>
- fn_info = @defs[fn_name]
- if fn_info == nil
- @error "Attempt to call undefined function: #{fn_name}"
- if fn_info.is_macro
- @error "Attempt to call macro at runtime: #{fn_name}"
- unless @check_permission(fn_name)
- @error "You do not have the authority to call: #{fn_name}"
- table.insert @callstack, fn_name
- {:fn, :arg_names} = fn_info
- args = {name, select(i,...) for i,name in ipairs(arg_names)}
- if @debug
- print "Calling #{fn_name} with args: #{utils.repr(args)}"
- ret = fn(self, args)
- table.remove @callstack
- return ret
-
- check_permission: (fn_name)=>
- fn_info = @defs[fn_name]
- if fn_info == nil
- @error "Undefined function: #{fn_name}"
- if fn_info.whiteset == nil then return true
- for caller in *@callstack
- if fn_info.whiteset[caller]
- return true
- return false
-
- def: (spec, fn)=>
- if @debug
- print "Defining rule: #{spec}"
- invocations,arg_names = @get_invocations spec
- fn_info = {:fn, :arg_names, :invocations, is_macro:false}
- for invocation in *invocations
- @defs[invocation] = fn_info
-
- get_invocations:(text)=>
- if type(text) == 'string' then text = {text}
- invocations = {}
- local arg_names
- for _text in *text
- invocation = _text\gsub("%%%S+","%%")
- _arg_names = [arg for arg in _text\gmatch("%%(%S+)")]
- table.insert(invocations, invocation)
- if arg_names
- if not utils.equivalent(utils.set(arg_names), utils.set(_arg_names))
- @error("Conflicting argument names #{utils.repr(arg_names)} and #{utils.repr(_arg_names)} for #{utils.repr(text)}")
- else arg_names = _arg_names
- return invocations, arg_names
-
- defmacro: (spec, lua_gen_fn)=>
- invocations,arg_names = @get_invocations spec
- fn_info = {fn:lua_gen_fn, :arg_names, :invocations, is_macro:true}
- for invocation in *invocations
- @defs[invocation] = fn_info
-
- run: (text)=>
- if @debug
- print "RUNNING TEXT:\n#{text}"
- -- This will execute each chunk as it goes along
- code = @compile(text)
- if @debug
- print "\nGENERATED LUA CODE:\n#{code}"
- return code
-
- parse: (str)=>
- if @debug
- print("PARSING:\n#{str}")
- lingo = [=[
- file <- ({ {| %ws? %new_line? {:body: block :} %new_line? %ws? (errors)? |} }) -> File
- errors <- (({.+}) => error_handler)
- block <- ({ {| statement (%new_line statement)* |} }) -> Block
- statement <- ({ (functioncall / expression) }) -> Statement
- one_liner <- ({ {|
- (({
- (({ {|
- (expression (%word_boundary fn_bit)+) / (word (%word_boundary fn_bit)*)
- |} }) -> FunctionCall)
- / (expression)
- }) -> Statement)
- |} }) -> Block
-
- functioncall <- ({ {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} }) -> FunctionCall
- fn_bit <- (expression / word)
- fn_bits <-
- ((".." %ws? (%indent %new_line indented_fn_bits %dedent) (%new_line ".." %ws? fn_bits)?)
- / (%new_line ".." fn_bit (%word_boundary fn_bits)?)
- / (fn_bit (%word_boundary fn_bits)?))
- indented_fn_bits <-
- fn_bit ((%new_line / %word_boundary) indented_fn_bits)?
-
- thunk <-
- ({ ":" %ws?
- ((%indent %new_line block ((%dedent (%new_line "..")?) / errors))
- / (one_liner (%ws? (%new_line? ".."))?)) }) -> Thunk
-
- word <- ({ !number {%wordchar+} }) -> Word
- expression <- ({ (longstring / string / number / variable / list / thunk / subexpression) }) -> Expression
-
- string <- ({ (!longstring) '"' {(("\" .) / [^"])*} '"' }) -> String
- longstring <- ({ '".."' %ws? %indent {(%new_line "|" [^%nl]*)+} ((%dedent (%new_line '..')?) / errors) }) -> Longstring
- number <- ({ {'-'? [0-9]+ ("." [0-9]+)?} }) -> Number
- variable <- ({ ("%" {%wordchar+}) }) -> Var
-
- subexpression <-
- (!%comment "(" %ws? (functioncall / expression) %ws? ")")
- / ("(..)" %ws? %indent %new_line ((({ {| indented_fn_bits |} }) -> FunctionCall) / expression) %dedent (%new_line "..")?)
-
- list <- ({ {|
- ("[..]" %ws? %indent %new_line indented_list ","? ((%dedent (%new_line "..")?) / errors))
- / ("[" %ws? (list_items ","?)? %ws?"]")
- |} }) -> List
- list_items <- ((functioncall / expression) (list_sep list_items)?)
- list_sep <- %ws? "," %ws?
- indented_list <-
- (functioncall / expression) (((list_sep %new_line?) / %new_line) indented_list)?
- ]=]
- lingo = make_parser lingo
-
- tree = lingo\match(str\gsub("\r","").."\n")
- if @debug
- print("\nPARSE TREE:")
- @print_tree(tree)
- assert tree, "Failed to parse: #{str}"
- return tree
-
- tree_to_value: (tree)=>
- code = "return (function(compiler, vars)\nreturn #{@tree_to_lua(tree)}\nend)"
- lua_thunk, err = load(code)
- if not lua_thunk
- error("Failed to compile generated code:\n#{code}\n\n#{err}")
- return (lua_thunk!)(self, {})
-
- tree_to_lua: (tree, kind="Expression")=>
- assert tree, "No tree provided."
- indent = ""
- buffer = {}
-
- to_lua = (t,kind)->
- ret = @tree_to_lua(t,kind)
- return ret
-
- add = (code)-> table.insert(buffer, code)
-
- switch tree.type
- when "File"
- add [[return (function(compiler, vars)
- local ret]]
- vars = {}
- for statement in *tree.value.body.value
- code = to_lua(statement)
- -- Run the fuckers as we go
- lua_thunk, err = load("return (function(compiler, vars)\n#{code}\nend)")
- if not lua_thunk
- error("Failed to compile generated code:\n#{code}\n\n#{err}")
- ok,err = pcall(lua_thunk)
- if not ok then error(err)
- ok,err = pcall(err, self, vars)
- if not ok then @error(err)
- add code
- add [[
- return ret
- end)
- ]]
-
- when "Block"
- for statement in *tree.value
- add to_lua(statement)
-
- when "Thunk"
- assert tree.value.type == "Block", "Non-block value in Thunk"
- add [[
- (function(compiler, vars)
- local ret]]
- add to_lua(tree.value)
- add [[
- return ret
- end)
- ]]
-
- when "Statement"
- -- This case here is to prevent "ret =" from getting prepended when the macro might not want it
- if tree.value.type == "FunctionCall"
- name = @fn_name_from_tree(tree.value)
- if @defs[name] and @defs[name].is_macro
- add @run_macro(tree.value, "Statement")
- else
- add "ret = "..(to_lua(tree.value)\match("%s*(.*)"))
- else
- add "ret = "..(to_lua(tree.value)\match("%s*(.*)"))
-
- when "Expression"
- add to_lua(tree.value)
-
- when "FunctionCall"
- name = @fn_name_from_tree(tree)
- if @defs[name] and @defs[name].is_macro
- add @run_macro(tree, "Expression")
- else
- args = [to_lua(a) for a in *tree.value when a.type != "Word"]
- table.insert args, 1, utils.repr(name, true)
- add @@comma_separated_items("compiler:call(", args, ")")
-
- when "String"
- escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
- unescaped = tree.value\gsub("\\(.)", ((c)-> escapes[c] or c))
- add utils.repr(unescaped, true)
-
- when "Longstring"
- -- TODO: handle comments here?
- result = [line for line in tree.value\gmatch("[ \t]*|([^\n]*)")]
- add utils.repr(table.concat(result, "\n"), true)
-
- when "Number"
- add tree.value
-
- when "List"
- if #tree.value == 0
- add "{}"
- elseif #tree.value == 1
- add "{#{to_lua(tree.value[1])}}"
- else
- add @@comma_separated_items("{", [to_lua(item) for item in *tree.value], "}")
-
- when "Var"
- add "vars[#{utils.repr(tree.value,true)}]"
-
- else
- error("Unknown/unimplemented thingy: #{tree.type}")
-
- -- TODO: make indentation clean
- buffer = table.concat(buffer, "\n")
- return buffer
-
- @comma_separated_items: (open, items, close)=>
- utils.accumulate "\n", ->
- buffer = open
- so_far = 0
- for i,item in ipairs(items)
- if i < #items then item ..= ", "
- if so_far + #item >= 80 and #buffer > 0
- coroutine.yield buffer
- so_far -= #buffer
- buffer = item
- else
- so_far += #item
- buffer ..= item
- buffer ..= close
- coroutine.yield buffer
-
- fn_name_from_tree: (tree)=>
- assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: #{tree.type}")
- name_bits = {}
- for token in *tree.value
- table.insert name_bits, if token.type == "Word" then token.value else "%"
- table.concat(name_bits, " ")
-
- run_macro: (tree, kind="Expression")=>
- name = @fn_name_from_tree(tree)
- unless @defs[name] and @defs[name].is_macro
- @error("Macro not found: #{name}")
- unless @check_permission(name)
- @error "You do not have the authority to call: #{name}"
- {:fn, :arg_names} = @defs[name]
- args = [a for a in *tree.value when a.type != "Word"]
- args = {name,args[i] for i,name in ipairs(arg_names)}
- table.insert @callstack, name
- ret, manual_mode = fn(self, args, kind)
- table.remove @callstack
- if not ret
- @error("No return value for macro: #{name}")
- if kind == "Statement" and not manual_mode
- ret = "ret = "..ret
- return ret
-
- _yield_tree: (tree, indent_level=0)=>
- ind = (s) -> INDENT\rep(indent_level)..s
- switch tree.type
- when "File"
- coroutine.yield(ind"File:")
- @_yield_tree(tree.value.body, indent_level+1)
-
- when "Errors"
- coroutine.yield(ind"Error:\n#{tree.value}")
-
- when "Block"
- for chunk in *tree.value
- @_yield_tree(chunk, indent_level)
-
- when "Thunk"
- coroutine.yield(ind"Thunk:")
- @_yield_tree(tree.value, indent_level+1)
-
- when "Statement"
- @_yield_tree(tree.value, indent_level)
-
- when "Expression"
- @_yield_tree(tree.value, indent_level)
-
- when "FunctionCall"
- name = @fn_name_from_tree(tree)
- args = [a for a in *tree.value when a.type != "Word"]
- if #args == 0
- coroutine.yield(ind"Call [#{name}]!")
- else
- coroutine.yield(ind"Call [#{name}]:")
- for a in *args
- @_yield_tree(a, indent_level+1)
-
- when "String"
- -- TODO: Better implement
- coroutine.yield(ind(utils.repr(tree.value, true)))
-
- when "Longstring"
- -- TODO: Better implement
- coroutine.yield(ind(utils.repr(tree.value, true)))
-
- when "Number"
- coroutine.yield(ind(tree.value))
-
- when "List"
- if #tree.value == 0
- coroutine.yield(ind("<Empty List>"))
- else
- coroutine.yield(ind"List:")
- for item in *tree.value
- @_yield_tree(item, indent_level+1)
-
- when "Var"
- coroutine.yield ind"Var[#{utils.repr(tree.value)}]"
-
- else
- error("Unknown/unimplemented thingy: #{tree.type}")
- return nil -- to prevent tail calls
-
- print_tree:(tree)=>
- for line in coroutine.wrap(-> @_yield_tree(tree))
- print(line)
-
- stringify_tree:(tree)=>
- result = {}
- for line in coroutine.wrap(-> @_yield_tree(tree))
- table.insert(result, line)
- return table.concat result, "\n"
-
- compile: (src, output_file=nil)=>
- if @debug
- print "COMPILING:\n#{src}"
- tree = @parse(src)
- assert tree, "Tree failed to compile: #{src}"
- code = @tree_to_lua(tree)
- if output_file
- output = io.open(output_file, "w")
- output\write(code)
- return code
-
- error: (...)=>
- print(...)
- print("Callstack:")
- for i=#@callstack,1,-1
- print " #{@callstack[i]}"
- error!
-
- test: (src, expected)=>
- i = 1
- while i != nil
- start,stop = src\find("\n\n", i)
-
- test = src\sub(i,start)
- i = stop
- start,stop = test\find"==="
- if not start or not stop then
- @error("WHERE'S THE ===? in:\n#{test}")
- test_src, expected = test\sub(1,start-1), test\sub(stop+1,-1)
- expected = expected\match'[\n]*(.*[^\n])'
- tree = @parse(test_src)
- got = @stringify_tree(tree.value.body)
- if got != expected
- @error"TEST FAILED!\nSource:\n#{test_src}\nExpected:\n#{expected}\n\nGot:\n#{got}"
-
-
- initialize_core: =>
- -- Sets up some core functionality
- as_lua_code = (str)=>
- switch str.type
- when "String"
- escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
- unescaped = str.value\gsub("\\(.)", ((c)-> escapes[c] or c))
- return unescaped
-
- when "Longstring"
- -- TODO: handle comments?
- result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")]
- return table.concat(result, "\n")
- else
- return @tree_to_lua(str)
-
- @defmacro [[lua block %lua_code]], (vars, kind)=>
- if kind == "Expression" then error("Expected to be in statement.")
- lua_code = vars.lua_code.value
- switch lua_code.type
- when "List"
- -- TODO: handle subexpressions
- return table.concat([as_lua_code(@, i.value) for i in *lua_code.value]), true
- else
- return as_lua_code(@, lua_code), true
-
- @defmacro [[lua expr %lua_code]], (vars, kind)=>
- lua_code = vars.lua_code.value
- switch lua_code.type
- when "List"
- -- TODO: handle subexpressions
- return table.concat([as_lua_code(@, i.value) for i in *lua_code.value])
- else
- return as_lua_code(@, lua_code)
-
- @def "rule %spec %body", (vars)=>
- @def vars.spec, vars.body
-
- @defmacro [[macro %spec %body]], (vars, kind)=>
- if kind == "Expression"
- error("Macro definitions cannot be used as expressions.")
- @defmacro @tree_to_value(vars.spec), @tree_to_value(vars.body)
- return "", true
-
- @defmacro [[macro block %spec %body]], (vars, kind)=>
- if kind == "Expression"
- error("Macro definitions cannot be used as expressions.")
- invocation = @tree_to_value(vars.spec)
- fn = @tree_to_value(vars.body)
- @defmacro invocation, ((vars,kind)=>
- if kind == "Expression"
- error("Macro: #{invocation} was defined to be a block, not an expression.")
- return fn(@,vars,kind), true)
- return "", true
-
- @def "run file %filename", (vars)=>
- file = io.open(vars.filename)
- return @run(file\read('*a'))
-
-
--- Run on the command line via "./nomic.moon input_file.nom" to execute
--- and "./nomic.moon input_file.nom output_file.lua" to compile (use "-" to compile to stdout)
-if arg[1]
- c = Compiler()
- input = io.open(arg[1])\read("*a")
- -- Kinda hacky, if run via "./nomic.moon file.nom -", then silence print and io.write
- -- during execution and re-enable them to print out the generated source code
- _print = print
- _io_write = io.write
- if arg[2] == "-"
- export print
- nop = ->
- print, io.write = nop, nop
- code = c\run(input)
- if arg[2]
- output = if arg[2] == "-"
- export print
- print, io.write = _print, _io_write
- io.output()
- else io.open(arg[2], 'w')
-
- output\write [[
- local load = function()
- ]]
- output\write(code)
- output\write [[
-
- end
- local utils = require('utils')
- local Compiler = require('nomic')
- local c = Compiler(require('core'))
- load()(c, {})
- ]]
-
-return Compiler