diff --git a/nomic.lua b/nomic.lua new file mode 100644 index 0000000..a2cd6c3 --- /dev/null +++ b/nomic.lua @@ -0,0 +1,847 @@ +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 + local _ = [==[ lua_thunk, err = loadstring(code) + if not lua_thunk + error("Failed to compile generated code:\n#{code}\n\n#{err}") + action = lua_thunk! + if @debug + print("Running...") + return action(self, {}) + ]==] + 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/utils.lua b/utils.lua new file mode 100644 index 0000000..bb5005d --- /dev/null +++ b/utils.lua @@ -0,0 +1,304 @@ +local utils +utils = { + is_list = function(t) + local i = 1 + for _ in pairs(t) do + if t[i] == nil then + return false + end + i = i + 1 + end + return true + end, + repr = function(x, add_quotes) + if add_quotes == nil then + add_quotes = false + end + local _exp_0 = type(x) + if 'table' == _exp_0 then + if utils.is_list(x) then + return "{" .. tostring(table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #x do + local i = x[_index_0] + _accum_0[_len_0] = utils.repr(i, true) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ")) .. "}" + else + return "{" .. tostring(table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(x) do + _accum_0[_len_0] = "[" .. tostring(utils.repr(k, true)) .. "]= " .. tostring(utils.repr(v, true)) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ")) .. "}" + end + elseif 'string' == _exp_0 then + if not add_quotes then + return x + elseif not x:find([["]]) and not x:find("\n") then + return "\"" .. x .. "\"" + elseif not x:find([[']]) and not x:find("\n") then + return "\'" .. x .. "\'" + else + for i = 0, math.huge do + local eq = ("="):rep(i) + if not x:find("%[" .. tostring(eq) .. "%[") and not x:find("%]" .. tostring(eq) .. "%]") then + if x:sub(1, 1) == "\n" then + return "[" .. tostring(eq) .. "[\n" .. x .. "]" .. tostring(eq) .. "]" + else + return "[" .. tostring(eq) .. "[" .. x .. "]" .. tostring(eq) .. "]" + end + end + end + end + else + return tostring(x) + end + end, + split = function(str, sep) + if sep == nil then + sep = "%s" + end + local _accum_0 = { } + local _len_0 = 1 + for chunk in str:gmatch("[^" .. tostring(sep) .. "]+") do + _accum_0[_len_0] = chunk + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + accumulate = function(glue, co) + if co == nil then + glue, co = "", glue + end + local bits = { } + for bit in coroutine.wrap(co) do + table.insert(bits, bit) + end + return table.concat(bits, glue) + end, + range = function(start, stop, step) + if stop == nil then + start, stop, step = 1, start, 1 + elseif step == nil then + step = 1 + end + return setmetatable({ + start = start, + stop = stop, + step = step + }, { + __ipairs = function(self) + local iter + iter = function(self, i) + if i <= (self.stop - self.start) / self.step then + return i + 1, self.start + i * self.step + end + end + return iter, self, 0 + end + }) + end, + keys = function(t) + local _accum_0 = { } + local _len_0 = 1 + for k in pairs(t) do + _accum_0[_len_0] = k + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + values = function(t) + local _accum_0 = { } + local _len_0 = 1 + for _, v in pairs(t) do + _accum_0[_len_0] = v + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + set = function(list) + local _tbl_0 = { } + for _index_0 = 1, #list do + local i = list[_index_0] + _tbl_0[i] = true + end + return _tbl_0 + end, + sum = function(t) + do + local tot = 0 + for _, x in pairs(t) do + tot = tot + x + end + return tot + end + end, + product = function(t) + do + local prod = 1 + for _, x in pairs(t) do + prod = prod * x + end + return prod + end + end, + all = function(t) + for _, x in pairs(t) do + if not x then + return false + end + end + return true + end, + any = function(t) + for _, x in pairs(t) do + if x then + return true + end + end + return false + end, + min = function(list, keyFn) + if keyFn == nil then + keyFn = (function(x) + return x + end) + end + assert(utils.is_list(list), "min() expects to be operating on a list") + do + local best = list[1] + if type(keyFn) == 'table' then + local keyTable = keyFn + keyFn = function(k) + return keyTable[k] + end + end + for i = 2, #list do + if keyFn(list[i]) < keyFn(best) then + best = list[i] + end + end + return best + end + end, + max = function(list, keyFn) + if keyFn == nil then + keyFn = (function(x) + return x + end) + end + assert(utils.is_list(list), "min() expects to be operating on a list") + do + local best = list[1] + if type(keyFn) == 'table' then + local keyTable = keyFn + keyFn = function(k) + return keyTable[k] + end + end + for i = 2, #list do + if keyFn(list[i]) > keyFn(best) then + best = list[i] + end + end + return best + end + end, + sort = function(list, keyFn, reverse) + if keyFn == nil then + keyFn = (function(x) + return x + end) + end + if reverse == nil then + reverse = false + end + assert(utils.is_list(list), "min() expects to be operating on a list") + if type(keyFn) == 'table' then + local keyTable = keyFn + keyFn = function(k) + return keyTable[k] + end + end + local comparison + if reverse then + comparison = (function(x, y) + return (keyFn(x) > keyFn(y)) + end) + else + comparison = (function(x, y) + return (keyFn(x) < keyFn(y)) + end) + end + return table.sort(list, comparison) + end, + equivalent = function(x, y) + if x == y then + return true + end + if type(x) ~= type(y) then + return false + end + if type(x) ~= 'table' then + return false + end + for k, v in pairs(x) do + if y[k] ~= v then + return false + end + end + for k, v in pairs(y) do + if x[k] ~= v then + return false + end + end + return true + end, + key_for = function(t, value) + for k, v in pairs(t) do + if v == value then + return k + end + end + return nil + end, + clamp = function(x, min, max) + if x < min then + return min + elseif x > max then + return max + else + return x + end + end, + mix = function(min, max, amount) + return (1 - amount) * min + amount * max + end, + sign = function(x) + if x == 0 then + return 0 + elseif x < 0 then + return -1 + else + return 1 + end + end, + round = function(x, increment) + if increment == nil then + increment = 1 + end + if x >= 0 then + return math.floor(x / increment + .5) * increment + else + return math.ceil(x / increment - .5) * increment + end + end +} +return utils