From 57268d8c04355d0897222c2df48388df08e45a87 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 12 Sep 2017 22:30:41 -0700 Subject: Renamed language again. --- README.md | 8 +- nomsu.lua | 839 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ nomsu.moon | 567 ++++++++++++++++++++++++++++++++++++++++ nomsub.lua | 839 ------------------------------------------------------------ nomsub.moon | 567 ---------------------------------------- 5 files changed, 1410 insertions(+), 1410 deletions(-) create mode 100644 nomsu.lua create mode 100755 nomsu.moon delete mode 100644 nomsub.lua delete mode 100755 nomsub.moon diff --git a/README.md b/README.md index e371e49..0d1e8e2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Nomsub (named after **Nom**ic, and its creator, Peter **Sub**er) is a programming language +Nomsu (named after **Nom**ic, and its creator, Peter **Su**ber) is a programming language designed to be used for playing games of Nomic, or engaging in other similar activities revolving around natural language rule-making and self modification. @@ -7,9 +7,9 @@ The language compiler was written in [Moonscript](http://moonscript.org/), using so you need to install it in order to run the compiler. All of the moon files have been compiled into lua for convenience, so Moonscript is not a dependency. -In order to run a .nom file, run `lua nomsub.lua your_file.nom`. Code can also be compiled -into lua code directly, which still requires nomsub.lua as a dependency, but bypasses the -compilation phase when it runs. To compile, run `lua nomsub.lua your_file.nom output_file.lua` +In order to run a .nom file, run `lua nomsu.lua your_file.nom`. Code can also be compiled +into lua code directly, which still requires nomsu.lua as a dependency, but bypasses the +compilation phase when it runs. To compile, run `lua nomsu.lua your_file.nom output_file.lua` which produces an output file which can be run with the command `lua output_file.lua`. Example code can be found in the examples folder. diff --git a/nomsu.lua b/nomsu.lua new file mode 100644 index 0000000..f111fc7 --- /dev/null +++ b/nomsu.lua @@ -0,0 +1,839 @@ +local re = require('re') +local lpeg = require('lpeg') +local utils = require('utils') +local INDENT = " " +lpeg.setmaxstack(10000) +local P, V, S, Cg, C, Cp, B, Cmt +P, V, S, Cg, C, Cp, B, Cmt = lpeg.P, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, lpeg.Cp, lpeg.B, lpeg.Cmt +local wordchar = P(1) - S(' \t\n\r%:;,.{}[]()"') +local comment = re.compile([[comment <- "(#" (comment / ((! "#)") .))* "#)"]]) +local whitespace = (S(" \t") + comment) ^ 1 +local nl = P("\n") +local blank_line = whitespace ^ -1 * nl +local get_line_indentation +get_line_indentation = function(line) + local indent_amounts = { + [" "] = 1, + ["\t"] = 4 + } + do + local sum = 0 + local leading_space = line:gsub("([\t ]*).*", "%1") + for c in leading_space:gmatch("[\t ]") do + sum = sum + indent_amounts[c] + end + return sum + end +end +local make_parser +make_parser = function(lingo, extra_definitions) + local indent_stack = { + 0 + } + local push + push = function(n) + return table.insert(indent_stack, n) + end + local pop + pop = function() + return table.remove(indent_stack) + end + local check_indent + check_indent = function(subject, end_pos, spaces) + local num_spaces = get_line_indentation(spaces) + if num_spaces <= indent_stack[#indent_stack] then + return nil + end + push(num_spaces) + return end_pos + end + local check_dedent + check_dedent = function(subject, end_pos, spaces) + local num_spaces = get_line_indentation(spaces) + if num_spaces >= indent_stack[#indent_stack] then + return nil + end + pop() + return end_pos + end + local check_nodent + check_nodent = function(subject, end_pos, spaces) + local num_spaces = get_line_indentation(spaces) + if num_spaces ~= indent_stack[#indent_stack] then + return nil + end + return end_pos + end + local defs = { + wordchar = wordchar, + nl = nl, + ws = whitespace, + comment = 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 = function(src, pos, errors) + local line_no = 1 + for _ in src:sub(1, -#errors):gmatch("\n") do + line_no = line_no + 1 + end + local err_pos = #src - #errors + 1 + if errors:sub(1, 1) == "\n" then + err_pos = err_pos + #errors:match("[ \t]*", 2) + end + local start_of_err_line = err_pos + while src:sub(start_of_err_line, start_of_err_line) ~= "\n" do + start_of_err_line = start_of_err_line - 1 + end + local 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 = start_of_prev_line - 1 + end + local prev_line, err_line, next_line = src:match("([^\n]*)\n([^\n]*)\n([^\n]*)", start_of_prev_line + 1) + local pointer = ("-"):rep(err_pos - start_of_err_line + 0) .. "^" + return error("\nParse error on line " .. tostring(line_no) .. ":\n\n" .. tostring(prev_line) .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer) .. "\n" .. tostring(next_line) .. "\n") + end + } + if extra_definitions then + for k, v in pairs(extra_definitions) do + defs[k] = v + end + end + setmetatable(defs, { + __index = function(t, key) + local fn + fn = function(src, value, errors) + local token = { + type = key, + src = src, + value = value, + errors = errors + } + return token + end + t[key] = fn + return fn + end + }) + return re.compile(lingo, defs) +end +local Compiler +do + local _class_0 + local _base_0 = { + call = function(self, fn_name, ...) + local fn_info = self.defs[fn_name] + if fn_info == nil then + self:error("Attempt to call undefined function: " .. tostring(fn_name)) + end + if fn_info.is_macro then + self:error("Attempt to call macro at runtime: " .. tostring(fn_name)) + end + if not (self:check_permission(fn_name)) then + self:error("You do not have the authority to call: " .. tostring(fn_name)) + end + table.insert(self.callstack, fn_name) + local fn, arg_names + fn, arg_names = fn_info.fn, fn_info.arg_names + local args + do + local _tbl_0 = { } + for i, name in ipairs(arg_names) do + _tbl_0[name] = select(i, ...) + end + args = _tbl_0 + end + if self.debug then + print("Calling " .. tostring(fn_name) .. " with args: " .. tostring(utils.repr(args))) + end + local ret = fn(self, args) + table.remove(self.callstack) + return ret + end, + check_permission = function(self, fn_name) + local fn_info = self.defs[fn_name] + if fn_info == nil then + self:error("Undefined function: " .. tostring(fn_name)) + end + if fn_info.whiteset == nil then + return true + end + local _list_0 = self.callstack + for _index_0 = 1, #_list_0 do + local caller = _list_0[_index_0] + if fn_info.whiteset[caller] then + return true + end + end + return false + end, + def = function(self, spec, fn) + if self.debug then + print("Defining rule: " .. tostring(spec)) + end + local invocations, arg_names = self:get_invocations(spec) + local fn_info = { + fn = fn, + arg_names = arg_names, + invocations = invocations, + is_macro = false + } + for _index_0 = 1, #invocations do + local invocation = invocations[_index_0] + self.defs[invocation] = fn_info + end + end, + get_invocations = function(self, text) + if type(text) == 'string' then + text = { + text + } + end + local invocations = { } + local arg_names + for _index_0 = 1, #text do + local _text = text[_index_0] + local invocation = _text:gsub("%%%S+", "%%") + local _arg_names + do + local _accum_0 = { } + local _len_0 = 1 + for arg in _text:gmatch("%%(%S+)") do + _accum_0[_len_0] = arg + _len_0 = _len_0 + 1 + end + _arg_names = _accum_0 + end + table.insert(invocations, invocation) + if arg_names then + if not utils.equivalent(utils.set(arg_names), utils.set(_arg_names)) then + self:error("Conflicting argument names " .. tostring(utils.repr(arg_names)) .. " and " .. tostring(utils.repr(_arg_names)) .. " for " .. tostring(utils.repr(text))) + end + else + arg_names = _arg_names + end + end + return invocations, arg_names + end, + defmacro = function(self, spec, lua_gen_fn) + local invocations, arg_names = self:get_invocations(spec) + local fn_info = { + fn = lua_gen_fn, + arg_names = arg_names, + invocations = invocations, + is_macro = true + } + for _index_0 = 1, #invocations do + local invocation = invocations[_index_0] + self.defs[invocation] = fn_info + end + end, + run = function(self, text) + if self.debug then + print("RUNNING TEXT:\n" .. tostring(text)) + end + local code = self:compile(text) + if self.debug then + print("\nGENERATED LUA CODE:\n" .. tostring(code)) + end + return code + end, + parse = function(self, str) + if self.debug then + print("PARSING:\n" .. tostring(str)) + end + local 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) + local tree = lingo:match(str:gsub("\r", "") .. "\n") + if self.debug then + print("\nPARSE TREE:") + self:print_tree(tree) + end + assert(tree, "Failed to parse: " .. tostring(str)) + return tree + end, + tree_to_value = function(self, tree) + local code = "return (function(compiler, vars)\nreturn " .. tostring(self:tree_to_lua(tree)) .. "\nend)" + local lua_thunk, err = load(code) + if not lua_thunk then + error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err)) + end + return (lua_thunk())(self, { }) + end, + tree_to_lua = function(self, tree, kind) + if kind == nil then + kind = "Expression" + end + assert(tree, "No tree provided.") + local indent = "" + local buffer = { } + local to_lua + to_lua = function(t, kind) + local ret = self:tree_to_lua(t, kind) + return ret + end + local add + add = function(code) + return table.insert(buffer, code) + end + local _exp_0 = tree.type + if "File" == _exp_0 then + add([[return (function(compiler, vars) + local ret]]) + local vars = { } + local _list_0 = tree.value.body.value + for _index_0 = 1, #_list_0 do + local statement = _list_0[_index_0] + local code = to_lua(statement) + local lua_thunk, err = load("return (function(compiler, vars)\n" .. tostring(code) .. "\nend)") + if not lua_thunk then + error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err)) + end + local ok + ok, err = pcall(lua_thunk) + if not ok then + error(err) + end + ok, err = pcall(err, self, vars) + if not ok then + self:error(err) + end + add(code) + end + add([[ return ret + end) + ]]) + elseif "Block" == _exp_0 then + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local statement = _list_0[_index_0] + add(to_lua(statement)) + end + elseif "Thunk" == _exp_0 then + 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) + ]]) + elseif "Statement" == _exp_0 then + if tree.value.type == "FunctionCall" then + local name = self:fn_name_from_tree(tree.value) + if self.defs[name] and self.defs[name].is_macro then + add(self:run_macro(tree.value, "Statement")) + else + add("ret = " .. (to_lua(tree.value):match("%s*(.*)"))) + end + else + add("ret = " .. (to_lua(tree.value):match("%s*(.*)"))) + end + elseif "Expression" == _exp_0 then + add(to_lua(tree.value)) + elseif "FunctionCall" == _exp_0 then + local name = self:fn_name_from_tree(tree) + if self.defs[name] and self.defs[name].is_macro then + add(self:run_macro(tree, "Expression")) + else + local args + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local a = _list_0[_index_0] + if a.type ~= "Word" then + _accum_0[_len_0] = to_lua(a) + _len_0 = _len_0 + 1 + end + end + args = _accum_0 + end + table.insert(args, 1, utils.repr(name, true)) + add(self.__class:comma_separated_items("compiler:call(", args, ")")) + end + elseif "String" == _exp_0 then + local escapes = { + n = "\n", + t = "\t", + b = "\b", + a = "\a", + v = "\v", + f = "\f", + r = "\r" + } + local unescaped = tree.value:gsub("\\(.)", (function(c) + return escapes[c] or c + end)) + add(utils.repr(unescaped, true)) + elseif "Longstring" == _exp_0 then + local result + do + local _accum_0 = { } + local _len_0 = 1 + for line in tree.value:gmatch("[ \t]*|([^\n]*)") do + _accum_0[_len_0] = line + _len_0 = _len_0 + 1 + end + result = _accum_0 + end + add(utils.repr(table.concat(result, "\n"), true)) + elseif "Number" == _exp_0 then + add(tree.value) + elseif "List" == _exp_0 then + if #tree.value == 0 then + add("{}") + elseif #tree.value == 1 then + add("{" .. tostring(to_lua(tree.value[1])) .. "}") + else + add(self.__class:comma_separated_items("{", (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local item = _list_0[_index_0] + _accum_0[_len_0] = to_lua(item) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), "}")) + end + elseif "Var" == _exp_0 then + add("vars[" .. tostring(utils.repr(tree.value, true)) .. "]") + else + error("Unknown/unimplemented thingy: " .. tostring(tree.type)) + end + buffer = table.concat(buffer, "\n") + return buffer + end, + fn_name_from_tree = function(self, tree) + assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: " .. tostring(tree.type)) + local name_bits = { } + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local token = _list_0[_index_0] + table.insert(name_bits, (function() + if token.type == "Word" then + return token.value + else + return "%" + end + end)()) + end + return table.concat(name_bits, " ") + end, + run_macro = function(self, tree, kind) + if kind == nil then + kind = "Expression" + end + local name = self:fn_name_from_tree(tree) + if not (self.defs[name] and self.defs[name].is_macro) then + self:error("Macro not found: " .. tostring(name)) + end + if not (self:check_permission(name)) then + self:error("You do not have the authority to call: " .. tostring(name)) + end + local fn, arg_names + do + local _obj_0 = self.defs[name] + fn, arg_names = _obj_0.fn, _obj_0.arg_names + end + local args + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local a = _list_0[_index_0] + if a.type ~= "Word" then + _accum_0[_len_0] = a + _len_0 = _len_0 + 1 + end + end + args = _accum_0 + end + do + local _tbl_0 = { } + for i, name in ipairs(arg_names) do + _tbl_0[name] = args[i] + end + args = _tbl_0 + end + table.insert(self.callstack, name) + local ret, manual_mode = fn(self, args, kind) + table.remove(self.callstack) + if not ret then + self:error("No return value for macro: " .. tostring(name)) + end + if kind == "Statement" and not manual_mode then + ret = "ret = " .. ret + end + return ret + end, + _yield_tree = function(self, tree, indent_level) + if indent_level == nil then + indent_level = 0 + end + local ind + ind = function(s) + return INDENT:rep(indent_level) .. s + end + local _exp_0 = tree.type + if "File" == _exp_0 then + coroutine.yield(ind("File:")) + self:_yield_tree(tree.value.body, indent_level + 1) + elseif "Errors" == _exp_0 then + coroutine.yield(ind("Error:\n" .. tostring(tree.value))) + elseif "Block" == _exp_0 then + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local chunk = _list_0[_index_0] + self:_yield_tree(chunk, indent_level) + end + elseif "Thunk" == _exp_0 then + coroutine.yield(ind("Thunk:")) + self:_yield_tree(tree.value, indent_level + 1) + elseif "Statement" == _exp_0 then + self:_yield_tree(tree.value, indent_level) + elseif "Expression" == _exp_0 then + self:_yield_tree(tree.value, indent_level) + elseif "FunctionCall" == _exp_0 then + local name = self:fn_name_from_tree(tree) + local args + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local a = _list_0[_index_0] + if a.type ~= "Word" then + _accum_0[_len_0] = a + _len_0 = _len_0 + 1 + end + end + args = _accum_0 + end + if #args == 0 then + coroutine.yield(ind("Call [" .. tostring(name) .. "]!")) + else + coroutine.yield(ind("Call [" .. tostring(name) .. "]:")) + for _index_0 = 1, #args do + local a = args[_index_0] + self:_yield_tree(a, indent_level + 1) + end + end + elseif "String" == _exp_0 then + coroutine.yield(ind(utils.repr(tree.value, true))) + elseif "Longstring" == _exp_0 then + coroutine.yield(ind(utils.repr(tree.value, true))) + elseif "Number" == _exp_0 then + coroutine.yield(ind(tree.value)) + elseif "List" == _exp_0 then + if #tree.value == 0 then + coroutine.yield(ind("")) + else + coroutine.yield(ind("List:")) + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local item = _list_0[_index_0] + self:_yield_tree(item, indent_level + 1) + end + end + elseif "Var" == _exp_0 then + coroutine.yield(ind("Var[" .. tostring(utils.repr(tree.value)) .. "]")) + else + error("Unknown/unimplemented thingy: " .. tostring(tree.type)) + end + return nil + end, + print_tree = function(self, tree) + for line in coroutine.wrap(function() + return self:_yield_tree(tree) + end) do + print(line) + end + end, + stringify_tree = function(self, tree) + local result = { } + for line in coroutine.wrap(function() + return self:_yield_tree(tree) + end) do + table.insert(result, line) + end + return table.concat(result, "\n") + end, + compile = function(self, src, output_file) + if output_file == nil then + output_file = nil + end + if self.debug then + print("COMPILING:\n" .. tostring(src)) + end + local tree = self:parse(src) + assert(tree, "Tree failed to compile: " .. tostring(src)) + local code = self:tree_to_lua(tree) + if output_file then + local output = io.open(output_file, "w") + output:write(code) + end + return code + end, + error = function(self, ...) + print(...) + print("Callstack:") + for i = #self.callstack, 1, -1 do + print(" " .. tostring(self.callstack[i])) + end + return error() + end, + test = function(self, src, expected) + local i = 1 + while i ~= nil do + local start, stop = src:find("\n\n", i) + local test = src:sub(i, start) + i = stop + start, stop = test:find("===") + if not start or not stop then + self:error("WHERE'S THE ===? in:\n" .. tostring(test)) + end + local test_src + test_src, expected = test:sub(1, start - 1), test:sub(stop + 1, -1) + expected = expected:match('[\n]*(.*[^\n])') + local tree = self:parse(test_src) + local got = self:stringify_tree(tree.value.body) + if got ~= expected then + self:error("TEST FAILED!\nSource:\n" .. tostring(test_src) .. "\nExpected:\n" .. tostring(expected) .. "\n\nGot:\n" .. tostring(got)) + end + end + end, + initialize_core = function(self) + local as_lua_code + as_lua_code = function(self, str) + local _exp_0 = str.type + if "String" == _exp_0 then + local escapes = { + n = "\n", + t = "\t", + b = "\b", + a = "\a", + v = "\v", + f = "\f", + r = "\r" + } + local unescaped = str.value:gsub("\\(.)", (function(c) + return escapes[c] or c + end)) + return unescaped + elseif "Longstring" == _exp_0 then + local result + do + local _accum_0 = { } + local _len_0 = 1 + for line in str.value:gmatch("[ \t]*|([^\n]*)") do + _accum_0[_len_0] = line + _len_0 = _len_0 + 1 + end + result = _accum_0 + end + return table.concat(result, "\n") + else + return self:tree_to_lua(str) + end + end + self:defmacro([[lua block %lua_code]], function(self, vars, kind) + if kind == "Expression" then + error("Expected to be in statement.") + end + local lua_code = vars.lua_code.value + local _exp_0 = lua_code.type + if "List" == _exp_0 then + return table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = lua_code.value + for _index_0 = 1, #_list_0 do + local i = _list_0[_index_0] + _accum_0[_len_0] = as_lua_code(self, i.value) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)()), true + else + return as_lua_code(self, lua_code), true + end + end) + self:defmacro([[lua expr %lua_code]], function(self, vars, kind) + local lua_code = vars.lua_code.value + local _exp_0 = lua_code.type + if "List" == _exp_0 then + return table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = lua_code.value + for _index_0 = 1, #_list_0 do + local i = _list_0[_index_0] + _accum_0[_len_0] = as_lua_code(self, i.value) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)()) + else + return as_lua_code(self, lua_code) + end + end) + self:def("rule %spec %body", function(self, vars) + return self:def(vars.spec, vars.body) + end) + self:defmacro([[macro %spec %body]], function(self, vars, kind) + if kind == "Expression" then + error("Macro definitions cannot be used as expressions.") + end + self:defmacro(self:tree_to_value(vars.spec), self:tree_to_value(vars.body)) + return "", true + end) + self:defmacro([[macro block %spec %body]], function(self, vars, kind) + if kind == "Expression" then + error("Macro definitions cannot be used as expressions.") + end + local invocation = self:tree_to_value(vars.spec) + local fn = self:tree_to_value(vars.body) + self:defmacro(invocation, (function(self, vars, kind) + if kind == "Expression" then + error("Macro: " .. tostring(invocation) .. " was defined to be a block, not an expression.") + end + return fn(self, vars, kind), true + end)) + return "", true + end) + return self:def("run file %filename", function(self, vars) + local file = io.open(vars.filename) + return self:run(file:read('*a')) + end) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, parent) + self.defs = setmetatable({ }, { + __index = parent and parent.defs + }) + self.callstack = { } + self.debug = false + return self:initialize_core() + end, + __base = _base_0, + __name = "Compiler" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.comma_separated_items = function(self, open, items, close) + return utils.accumulate("\n", function() + local buffer = open + local so_far = 0 + for i, item in ipairs(items) do + if i < #items then + item = item .. ", " + end + if so_far + #item >= 80 and #buffer > 0 then + coroutine.yield(buffer) + so_far = so_far - #buffer + buffer = item + else + so_far = so_far + #item + buffer = buffer .. item + end + end + buffer = buffer .. close + return coroutine.yield(buffer) + end) + end + Compiler = _class_0 +end +if arg[1] then + local c = Compiler() + local input = io.open(arg[1]):read("*a") + local _print = print + local _io_write = io.write + if arg[2] == "-" then + local nop + nop = function() end + print, io.write = nop, nop + end + local code = c:run(input) + if arg[2] then + local output + if arg[2] == "-" then + print, io.write = _print, _io_write + output = io.output() + else + output = io.open(arg[2], 'w') + end + 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, {}) + ]]) + end +end +return Compiler diff --git a/nomsu.moon b/nomsu.moon new file mode 100755 index 0000000..243dd64 --- /dev/null +++ b/nomsu.moon @@ -0,0 +1,567 @@ +#!/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("")) + 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 "./nomsu.moon input_file.nom" to execute +-- and "./nomsu.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 "./nomsu.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('nomsu') + local c = Compiler(require('core')) + load()(c, {}) + ]] + +return Compiler diff --git a/nomsub.lua b/nomsub.lua deleted file mode 100644 index f111fc7..0000000 --- a/nomsub.lua +++ /dev/null @@ -1,839 +0,0 @@ -local re = require('re') -local lpeg = require('lpeg') -local utils = require('utils') -local INDENT = " " -lpeg.setmaxstack(10000) -local P, V, S, Cg, C, Cp, B, Cmt -P, V, S, Cg, C, Cp, B, Cmt = lpeg.P, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, lpeg.Cp, lpeg.B, lpeg.Cmt -local wordchar = P(1) - S(' \t\n\r%:;,.{}[]()"') -local comment = re.compile([[comment <- "(#" (comment / ((! "#)") .))* "#)"]]) -local whitespace = (S(" \t") + comment) ^ 1 -local nl = P("\n") -local blank_line = whitespace ^ -1 * nl -local get_line_indentation -get_line_indentation = function(line) - local indent_amounts = { - [" "] = 1, - ["\t"] = 4 - } - do - local sum = 0 - local leading_space = line:gsub("([\t ]*).*", "%1") - for c in leading_space:gmatch("[\t ]") do - sum = sum + indent_amounts[c] - end - return sum - end -end -local make_parser -make_parser = function(lingo, extra_definitions) - local indent_stack = { - 0 - } - local push - push = function(n) - return table.insert(indent_stack, n) - end - local pop - pop = function() - return table.remove(indent_stack) - end - local check_indent - check_indent = function(subject, end_pos, spaces) - local num_spaces = get_line_indentation(spaces) - if num_spaces <= indent_stack[#indent_stack] then - return nil - end - push(num_spaces) - return end_pos - end - local check_dedent - check_dedent = function(subject, end_pos, spaces) - local num_spaces = get_line_indentation(spaces) - if num_spaces >= indent_stack[#indent_stack] then - return nil - end - pop() - return end_pos - end - local check_nodent - check_nodent = function(subject, end_pos, spaces) - local num_spaces = get_line_indentation(spaces) - if num_spaces ~= indent_stack[#indent_stack] then - return nil - end - return end_pos - end - local defs = { - wordchar = wordchar, - nl = nl, - ws = whitespace, - comment = 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 = function(src, pos, errors) - local line_no = 1 - for _ in src:sub(1, -#errors):gmatch("\n") do - line_no = line_no + 1 - end - local err_pos = #src - #errors + 1 - if errors:sub(1, 1) == "\n" then - err_pos = err_pos + #errors:match("[ \t]*", 2) - end - local start_of_err_line = err_pos - while src:sub(start_of_err_line, start_of_err_line) ~= "\n" do - start_of_err_line = start_of_err_line - 1 - end - local 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 = start_of_prev_line - 1 - end - local prev_line, err_line, next_line = src:match("([^\n]*)\n([^\n]*)\n([^\n]*)", start_of_prev_line + 1) - local pointer = ("-"):rep(err_pos - start_of_err_line + 0) .. "^" - return error("\nParse error on line " .. tostring(line_no) .. ":\n\n" .. tostring(prev_line) .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer) .. "\n" .. tostring(next_line) .. "\n") - end - } - if extra_definitions then - for k, v in pairs(extra_definitions) do - defs[k] = v - end - end - setmetatable(defs, { - __index = function(t, key) - local fn - fn = function(src, value, errors) - local token = { - type = key, - src = src, - value = value, - errors = errors - } - return token - end - t[key] = fn - return fn - end - }) - return re.compile(lingo, defs) -end -local Compiler -do - local _class_0 - local _base_0 = { - call = function(self, fn_name, ...) - local fn_info = self.defs[fn_name] - if fn_info == nil then - self:error("Attempt to call undefined function: " .. tostring(fn_name)) - end - if fn_info.is_macro then - self:error("Attempt to call macro at runtime: " .. tostring(fn_name)) - end - if not (self:check_permission(fn_name)) then - self:error("You do not have the authority to call: " .. tostring(fn_name)) - end - table.insert(self.callstack, fn_name) - local fn, arg_names - fn, arg_names = fn_info.fn, fn_info.arg_names - local args - do - local _tbl_0 = { } - for i, name in ipairs(arg_names) do - _tbl_0[name] = select(i, ...) - end - args = _tbl_0 - end - if self.debug then - print("Calling " .. tostring(fn_name) .. " with args: " .. tostring(utils.repr(args))) - end - local ret = fn(self, args) - table.remove(self.callstack) - return ret - end, - check_permission = function(self, fn_name) - local fn_info = self.defs[fn_name] - if fn_info == nil then - self:error("Undefined function: " .. tostring(fn_name)) - end - if fn_info.whiteset == nil then - return true - end - local _list_0 = self.callstack - for _index_0 = 1, #_list_0 do - local caller = _list_0[_index_0] - if fn_info.whiteset[caller] then - return true - end - end - return false - end, - def = function(self, spec, fn) - if self.debug then - print("Defining rule: " .. tostring(spec)) - end - local invocations, arg_names = self:get_invocations(spec) - local fn_info = { - fn = fn, - arg_names = arg_names, - invocations = invocations, - is_macro = false - } - for _index_0 = 1, #invocations do - local invocation = invocations[_index_0] - self.defs[invocation] = fn_info - end - end, - get_invocations = function(self, text) - if type(text) == 'string' then - text = { - text - } - end - local invocations = { } - local arg_names - for _index_0 = 1, #text do - local _text = text[_index_0] - local invocation = _text:gsub("%%%S+", "%%") - local _arg_names - do - local _accum_0 = { } - local _len_0 = 1 - for arg in _text:gmatch("%%(%S+)") do - _accum_0[_len_0] = arg - _len_0 = _len_0 + 1 - end - _arg_names = _accum_0 - end - table.insert(invocations, invocation) - if arg_names then - if not utils.equivalent(utils.set(arg_names), utils.set(_arg_names)) then - self:error("Conflicting argument names " .. tostring(utils.repr(arg_names)) .. " and " .. tostring(utils.repr(_arg_names)) .. " for " .. tostring(utils.repr(text))) - end - else - arg_names = _arg_names - end - end - return invocations, arg_names - end, - defmacro = function(self, spec, lua_gen_fn) - local invocations, arg_names = self:get_invocations(spec) - local fn_info = { - fn = lua_gen_fn, - arg_names = arg_names, - invocations = invocations, - is_macro = true - } - for _index_0 = 1, #invocations do - local invocation = invocations[_index_0] - self.defs[invocation] = fn_info - end - end, - run = function(self, text) - if self.debug then - print("RUNNING TEXT:\n" .. tostring(text)) - end - local code = self:compile(text) - if self.debug then - print("\nGENERATED LUA CODE:\n" .. tostring(code)) - end - return code - end, - parse = function(self, str) - if self.debug then - print("PARSING:\n" .. tostring(str)) - end - local 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) - local tree = lingo:match(str:gsub("\r", "") .. "\n") - if self.debug then - print("\nPARSE TREE:") - self:print_tree(tree) - end - assert(tree, "Failed to parse: " .. tostring(str)) - return tree - end, - tree_to_value = function(self, tree) - local code = "return (function(compiler, vars)\nreturn " .. tostring(self:tree_to_lua(tree)) .. "\nend)" - local lua_thunk, err = load(code) - if not lua_thunk then - error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err)) - end - return (lua_thunk())(self, { }) - end, - tree_to_lua = function(self, tree, kind) - if kind == nil then - kind = "Expression" - end - assert(tree, "No tree provided.") - local indent = "" - local buffer = { } - local to_lua - to_lua = function(t, kind) - local ret = self:tree_to_lua(t, kind) - return ret - end - local add - add = function(code) - return table.insert(buffer, code) - end - local _exp_0 = tree.type - if "File" == _exp_0 then - add([[return (function(compiler, vars) - local ret]]) - local vars = { } - local _list_0 = tree.value.body.value - for _index_0 = 1, #_list_0 do - local statement = _list_0[_index_0] - local code = to_lua(statement) - local lua_thunk, err = load("return (function(compiler, vars)\n" .. tostring(code) .. "\nend)") - if not lua_thunk then - error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err)) - end - local ok - ok, err = pcall(lua_thunk) - if not ok then - error(err) - end - ok, err = pcall(err, self, vars) - if not ok then - self:error(err) - end - add(code) - end - add([[ return ret - end) - ]]) - elseif "Block" == _exp_0 then - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local statement = _list_0[_index_0] - add(to_lua(statement)) - end - elseif "Thunk" == _exp_0 then - 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) - ]]) - elseif "Statement" == _exp_0 then - if tree.value.type == "FunctionCall" then - local name = self:fn_name_from_tree(tree.value) - if self.defs[name] and self.defs[name].is_macro then - add(self:run_macro(tree.value, "Statement")) - else - add("ret = " .. (to_lua(tree.value):match("%s*(.*)"))) - end - else - add("ret = " .. (to_lua(tree.value):match("%s*(.*)"))) - end - elseif "Expression" == _exp_0 then - add(to_lua(tree.value)) - elseif "FunctionCall" == _exp_0 then - local name = self:fn_name_from_tree(tree) - if self.defs[name] and self.defs[name].is_macro then - add(self:run_macro(tree, "Expression")) - else - local args - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local a = _list_0[_index_0] - if a.type ~= "Word" then - _accum_0[_len_0] = to_lua(a) - _len_0 = _len_0 + 1 - end - end - args = _accum_0 - end - table.insert(args, 1, utils.repr(name, true)) - add(self.__class:comma_separated_items("compiler:call(", args, ")")) - end - elseif "String" == _exp_0 then - local escapes = { - n = "\n", - t = "\t", - b = "\b", - a = "\a", - v = "\v", - f = "\f", - r = "\r" - } - local unescaped = tree.value:gsub("\\(.)", (function(c) - return escapes[c] or c - end)) - add(utils.repr(unescaped, true)) - elseif "Longstring" == _exp_0 then - local result - do - local _accum_0 = { } - local _len_0 = 1 - for line in tree.value:gmatch("[ \t]*|([^\n]*)") do - _accum_0[_len_0] = line - _len_0 = _len_0 + 1 - end - result = _accum_0 - end - add(utils.repr(table.concat(result, "\n"), true)) - elseif "Number" == _exp_0 then - add(tree.value) - elseif "List" == _exp_0 then - if #tree.value == 0 then - add("{}") - elseif #tree.value == 1 then - add("{" .. tostring(to_lua(tree.value[1])) .. "}") - else - add(self.__class:comma_separated_items("{", (function() - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local item = _list_0[_index_0] - _accum_0[_len_0] = to_lua(item) - _len_0 = _len_0 + 1 - end - return _accum_0 - end)(), "}")) - end - elseif "Var" == _exp_0 then - add("vars[" .. tostring(utils.repr(tree.value, true)) .. "]") - else - error("Unknown/unimplemented thingy: " .. tostring(tree.type)) - end - buffer = table.concat(buffer, "\n") - return buffer - end, - fn_name_from_tree = function(self, tree) - assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: " .. tostring(tree.type)) - local name_bits = { } - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local token = _list_0[_index_0] - table.insert(name_bits, (function() - if token.type == "Word" then - return token.value - else - return "%" - end - end)()) - end - return table.concat(name_bits, " ") - end, - run_macro = function(self, tree, kind) - if kind == nil then - kind = "Expression" - end - local name = self:fn_name_from_tree(tree) - if not (self.defs[name] and self.defs[name].is_macro) then - self:error("Macro not found: " .. tostring(name)) - end - if not (self:check_permission(name)) then - self:error("You do not have the authority to call: " .. tostring(name)) - end - local fn, arg_names - do - local _obj_0 = self.defs[name] - fn, arg_names = _obj_0.fn, _obj_0.arg_names - end - local args - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local a = _list_0[_index_0] - if a.type ~= "Word" then - _accum_0[_len_0] = a - _len_0 = _len_0 + 1 - end - end - args = _accum_0 - end - do - local _tbl_0 = { } - for i, name in ipairs(arg_names) do - _tbl_0[name] = args[i] - end - args = _tbl_0 - end - table.insert(self.callstack, name) - local ret, manual_mode = fn(self, args, kind) - table.remove(self.callstack) - if not ret then - self:error("No return value for macro: " .. tostring(name)) - end - if kind == "Statement" and not manual_mode then - ret = "ret = " .. ret - end - return ret - end, - _yield_tree = function(self, tree, indent_level) - if indent_level == nil then - indent_level = 0 - end - local ind - ind = function(s) - return INDENT:rep(indent_level) .. s - end - local _exp_0 = tree.type - if "File" == _exp_0 then - coroutine.yield(ind("File:")) - self:_yield_tree(tree.value.body, indent_level + 1) - elseif "Errors" == _exp_0 then - coroutine.yield(ind("Error:\n" .. tostring(tree.value))) - elseif "Block" == _exp_0 then - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local chunk = _list_0[_index_0] - self:_yield_tree(chunk, indent_level) - end - elseif "Thunk" == _exp_0 then - coroutine.yield(ind("Thunk:")) - self:_yield_tree(tree.value, indent_level + 1) - elseif "Statement" == _exp_0 then - self:_yield_tree(tree.value, indent_level) - elseif "Expression" == _exp_0 then - self:_yield_tree(tree.value, indent_level) - elseif "FunctionCall" == _exp_0 then - local name = self:fn_name_from_tree(tree) - local args - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local a = _list_0[_index_0] - if a.type ~= "Word" then - _accum_0[_len_0] = a - _len_0 = _len_0 + 1 - end - end - args = _accum_0 - end - if #args == 0 then - coroutine.yield(ind("Call [" .. tostring(name) .. "]!")) - else - coroutine.yield(ind("Call [" .. tostring(name) .. "]:")) - for _index_0 = 1, #args do - local a = args[_index_0] - self:_yield_tree(a, indent_level + 1) - end - end - elseif "String" == _exp_0 then - coroutine.yield(ind(utils.repr(tree.value, true))) - elseif "Longstring" == _exp_0 then - coroutine.yield(ind(utils.repr(tree.value, true))) - elseif "Number" == _exp_0 then - coroutine.yield(ind(tree.value)) - elseif "List" == _exp_0 then - if #tree.value == 0 then - coroutine.yield(ind("")) - else - coroutine.yield(ind("List:")) - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local item = _list_0[_index_0] - self:_yield_tree(item, indent_level + 1) - end - end - elseif "Var" == _exp_0 then - coroutine.yield(ind("Var[" .. tostring(utils.repr(tree.value)) .. "]")) - else - error("Unknown/unimplemented thingy: " .. tostring(tree.type)) - end - return nil - end, - print_tree = function(self, tree) - for line in coroutine.wrap(function() - return self:_yield_tree(tree) - end) do - print(line) - end - end, - stringify_tree = function(self, tree) - local result = { } - for line in coroutine.wrap(function() - return self:_yield_tree(tree) - end) do - table.insert(result, line) - end - return table.concat(result, "\n") - end, - compile = function(self, src, output_file) - if output_file == nil then - output_file = nil - end - if self.debug then - print("COMPILING:\n" .. tostring(src)) - end - local tree = self:parse(src) - assert(tree, "Tree failed to compile: " .. tostring(src)) - local code = self:tree_to_lua(tree) - if output_file then - local output = io.open(output_file, "w") - output:write(code) - end - return code - end, - error = function(self, ...) - print(...) - print("Callstack:") - for i = #self.callstack, 1, -1 do - print(" " .. tostring(self.callstack[i])) - end - return error() - end, - test = function(self, src, expected) - local i = 1 - while i ~= nil do - local start, stop = src:find("\n\n", i) - local test = src:sub(i, start) - i = stop - start, stop = test:find("===") - if not start or not stop then - self:error("WHERE'S THE ===? in:\n" .. tostring(test)) - end - local test_src - test_src, expected = test:sub(1, start - 1), test:sub(stop + 1, -1) - expected = expected:match('[\n]*(.*[^\n])') - local tree = self:parse(test_src) - local got = self:stringify_tree(tree.value.body) - if got ~= expected then - self:error("TEST FAILED!\nSource:\n" .. tostring(test_src) .. "\nExpected:\n" .. tostring(expected) .. "\n\nGot:\n" .. tostring(got)) - end - end - end, - initialize_core = function(self) - local as_lua_code - as_lua_code = function(self, str) - local _exp_0 = str.type - if "String" == _exp_0 then - local escapes = { - n = "\n", - t = "\t", - b = "\b", - a = "\a", - v = "\v", - f = "\f", - r = "\r" - } - local unescaped = str.value:gsub("\\(.)", (function(c) - return escapes[c] or c - end)) - return unescaped - elseif "Longstring" == _exp_0 then - local result - do - local _accum_0 = { } - local _len_0 = 1 - for line in str.value:gmatch("[ \t]*|([^\n]*)") do - _accum_0[_len_0] = line - _len_0 = _len_0 + 1 - end - result = _accum_0 - end - return table.concat(result, "\n") - else - return self:tree_to_lua(str) - end - end - self:defmacro([[lua block %lua_code]], function(self, vars, kind) - if kind == "Expression" then - error("Expected to be in statement.") - end - local lua_code = vars.lua_code.value - local _exp_0 = lua_code.type - if "List" == _exp_0 then - return table.concat((function() - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = lua_code.value - for _index_0 = 1, #_list_0 do - local i = _list_0[_index_0] - _accum_0[_len_0] = as_lua_code(self, i.value) - _len_0 = _len_0 + 1 - end - return _accum_0 - end)()), true - else - return as_lua_code(self, lua_code), true - end - end) - self:defmacro([[lua expr %lua_code]], function(self, vars, kind) - local lua_code = vars.lua_code.value - local _exp_0 = lua_code.type - if "List" == _exp_0 then - return table.concat((function() - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = lua_code.value - for _index_0 = 1, #_list_0 do - local i = _list_0[_index_0] - _accum_0[_len_0] = as_lua_code(self, i.value) - _len_0 = _len_0 + 1 - end - return _accum_0 - end)()) - else - return as_lua_code(self, lua_code) - end - end) - self:def("rule %spec %body", function(self, vars) - return self:def(vars.spec, vars.body) - end) - self:defmacro([[macro %spec %body]], function(self, vars, kind) - if kind == "Expression" then - error("Macro definitions cannot be used as expressions.") - end - self:defmacro(self:tree_to_value(vars.spec), self:tree_to_value(vars.body)) - return "", true - end) - self:defmacro([[macro block %spec %body]], function(self, vars, kind) - if kind == "Expression" then - error("Macro definitions cannot be used as expressions.") - end - local invocation = self:tree_to_value(vars.spec) - local fn = self:tree_to_value(vars.body) - self:defmacro(invocation, (function(self, vars, kind) - if kind == "Expression" then - error("Macro: " .. tostring(invocation) .. " was defined to be a block, not an expression.") - end - return fn(self, vars, kind), true - end)) - return "", true - end) - return self:def("run file %filename", function(self, vars) - local file = io.open(vars.filename) - return self:run(file:read('*a')) - end) - end - } - _base_0.__index = _base_0 - _class_0 = setmetatable({ - __init = function(self, parent) - self.defs = setmetatable({ }, { - __index = parent and parent.defs - }) - self.callstack = { } - self.debug = false - return self:initialize_core() - end, - __base = _base_0, - __name = "Compiler" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - local self = _class_0 - self.comma_separated_items = function(self, open, items, close) - return utils.accumulate("\n", function() - local buffer = open - local so_far = 0 - for i, item in ipairs(items) do - if i < #items then - item = item .. ", " - end - if so_far + #item >= 80 and #buffer > 0 then - coroutine.yield(buffer) - so_far = so_far - #buffer - buffer = item - else - so_far = so_far + #item - buffer = buffer .. item - end - end - buffer = buffer .. close - return coroutine.yield(buffer) - end) - end - Compiler = _class_0 -end -if arg[1] then - local c = Compiler() - local input = io.open(arg[1]):read("*a") - local _print = print - local _io_write = io.write - if arg[2] == "-" then - local nop - nop = function() end - print, io.write = nop, nop - end - local code = c:run(input) - if arg[2] then - local output - if arg[2] == "-" then - print, io.write = _print, _io_write - output = io.output() - else - output = io.open(arg[2], 'w') - end - 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, {}) - ]]) - end -end -return Compiler diff --git a/nomsub.moon b/nomsub.moon deleted file mode 100755 index 812574c..0000000 --- a/nomsub.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("")) - 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 -- cgit v1.2.3