From 0f0fb2256a46a8808794d7d4746d112278da3730 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 10 Jan 2019 16:33:37 -0800 Subject: Major overhaul of how modules and environments work, along with some steamlining and tweaks to the makefile. Version bump: 6.14.13.8 --- nomsu_compiler.moon | 545 +++++++++++++++++++++------------------------------- 1 file changed, 220 insertions(+), 325 deletions(-) (limited to 'nomsu_compiler.moon') diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index 7ab2494..f3530e9 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -1,28 +1,13 @@ +-- -- This file contains the source code of the Nomsu compiler. --- Nomsu is a programming language that cross-compiles to Lua. It was designed to be good --- at natural-language-like code that is highly self-modifying and flexible. --- The only dependency is LPEG, which can be installed using "luarocks install lpeg" --- File usage: --- Either, in a lua/moonscript file: --- Nomsu = require "nomsu" --- nomsu = Nomsu() --- nomsu:run(your_nomsu_code) --- Or from the command line: --- lua nomsu.lua your_file.nom -lpeg = require 'lpeg' -{:R,:P,:S} = lpeg -re = require 're' +-- {:List, :Dict, :Text} = require 'containers' -{:insert, :remove, :concat} = table unpack or= table.unpack {:match, :sub, :gsub, :format, :byte, :find} = string {:LuaCode, :Source} = require "code_obj" SyntaxTree = require "syntax_tree" -{:Importer, :import_to_1_from, :_1_forked} = require 'importer' Files = require "files" -table.map = (t, fn)-> setmetatable([fn(v) for _,v in ipairs(t)], getmetatable(t)) - -- TODO: de-duplicate this pretty_error = require("pretty_errors") compile_error = (source, err_msg, hint=nil)-> @@ -45,137 +30,74 @@ compile_error = (source, err_msg, hint=nil)-> -- This is a bit of a hack, but this code handles arbitrarily complex -- math expressions like 2*x + 3^2 without having to define a single -- action for every possibility. +re = require 're' math_expression = re.compile [[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]] MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value -compile = setmetatable({ - action: Importer{ - [""]: (compile, fn, ...)-> - lua = LuaCode! - fn_lua = compile(fn) - lua\add fn_lua - unless fn_lua\text!\match("^%(.*%)$") or fn_lua\text!\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") - lua\parenthesize! - lua\add "(" - for i=1,select('#',...) - lua\add(", ") if i > 1 - lua\add compile((select(i, ...))) - lua\add ")" - return lua - - ["Lua"]: (compile, code)-> - if not code - return LuaCode("LuaCode()") - if code.type != "Text" - return LuaCode("LuaCode:from(", tostring(code.source)\as_lua!, ", ", compile(code), ")") - - operate_on_text = (text)-> - lua = LuaCode\from(text.source, "LuaCode:from(", tostring(text.source)\as_lua!) - for bit in *text - local bit_lua - if type(bit) == "string" - bit_lua = bit\as_lua! - elseif bit.type == "Text" - bit_lua = operate_on_text(bit) - elseif bit.type == "Block" - bit_lua = LuaCode\from bit.source, "(function()", - "\n local _lua = LuaCode:from(", tostring(bit.source)\as_lua!, ")", - "\n local function add(...) _lua:add(...) end", - "\n local function join_with(glue)", - "\n local old_bits = _lua.bits", - "\n _lua = LuaCode:from(_lua.source)", - "\n _lua:concat_add(old_bits, glue)", - "\n end", - "\n ", compile(bit), - "\n return _lua", - "\nend)()" - else - bit_lua = compile(bit) - - bit_leading_len = #(bit_lua\match("^[^\n]*")) - lua\add(lua\trailing_line_len! + bit_leading_len > MAX_LINE and ",\n " or ", ") - lua\add(bit_lua) - lua\add ")" - return lua - - return operate_on_text code - - ["lua >"]: (compile, code)-> - if code.type != "Text" - return code - operate_on_text = (text)-> - lua = LuaCode\from(text.source) - for bit in *text - if type(bit) == "string" - lua\add bit - elseif bit.type == "Text" - lua\add(operate_on_text(bit)) +compile = (tree)=> + switch tree.type + when "Action" + stub = tree.stub + compile_action = @COMPILE_RULES[stub] + if not compile_action and math_expression\match(stub) + lua = LuaCode\from(tree.source) + for i,tok in ipairs tree + if type(tok) == 'string' + lua\add tok else - lua\add compile(bit) + tok_lua = @compile(tok) + -- TODO: this is overly eager, should be less aggressive + tok_lua\parenthesize! if tok.type == "Action" + lua\add tok_lua + lua\add " " if i < #tree return lua - return operate_on_text code - - ["= lua"]: (compile, code)-> compile.action["lua >"](compile, code) - - ["use"]: (compile, path)-> LuaCode("run_file_1_in(#{compile(path)}, _ENV, OPTIMIZATION)") - ["use 1 with prefix"]: (compile, path, prefix)-> - LuaCode("run_file_1_in(#{compile(path)}, _ENV, OPTIMIZATION, ", compile(prefix), ")") - - ["test"]: (compile, body)-> - unless body.type == 'Block' - compile_error(body, "This should be a Block") - test_nomsu = body\get_source_code!\match(":[ ]*(.*)") - if indent = test_nomsu\match("\n([ ]*)") - test_nomsu = test_nomsu\gsub("\n"..indent, "\n") - test_text = compile(SyntaxTree{type:"Text", source:body.source, test_nomsu}) - return LuaCode "TESTS[#{tostring(body.source)\as_lua!}] = ", test_text - - ["is jit"]: (compile, code)-> LuaCode("jit") - ["Lua version"]: (compile, code)-> LuaCode("_VERSION") - ["nomsu environment"]: (compile)-> LuaCode("_ENV") - } -}, { - __import: import_to_1_from - __call: (compile, tree)-> - switch tree.type - when "Action" - stub = tree.stub - compile_action = compile.action[stub] - if not compile_action and math_expression\match(stub) - lua = LuaCode\from(tree.source) - for i,tok in ipairs tree - if type(tok) == 'string' - lua\add tok - else - tok_lua = compile(tok) - -- TODO: this is overly eager, should be less aggressive - tok_lua\parenthesize! if tok.type == "Action" - lua\add tok_lua - lua\add " " if i < #tree - return lua - - if compile_action - args = [arg for arg in *tree when type(arg) != "string"] - -- Force Lua to avoid tail call optimization for debugging purposes - -- TODO: use tail call? - ret = compile_action(compile, unpack(args)) - if ret == nil - info = debug.getinfo(compile_action, "S") - filename = Source\from_string(info.source).filename - compile_error tree, - "The compile-time action here (#{stub}) failed to return any value.", - "Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's returning something." - unless SyntaxTree\is_instance(ret) - ret.source or= tree.source - return ret - if ret != tree - return compile(ret) + if compile_action + args = [arg for arg in *tree when type(arg) != "string"] + -- Force Lua to avoid tail call optimization for debugging purposes + -- TODO: use tail call? + ret = compile_action(@, unpack(args)) + if ret == nil + info = debug.getinfo(compile_action, "S") + filename = Source\from_string(info.source).filename + compile_error tree, + "The compile-time action here (#{stub}) failed to return any value.", + "Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's returning something." + unless SyntaxTree\is_instance(ret) + ret.source or= tree.source + return ret + if ret != tree + return @compile(ret) + + lua = LuaCode\from(tree.source) + lua\add((stub)\as_lua_id!,"(") + for argnum, arg in ipairs tree\get_args! + arg_lua = @compile(arg) + if arg.type == "Block" + arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()") + if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE + lua\add(argnum > 1 and ",\n " or "\n ") + elseif argnum > 1 + lua\add ", " + lua\add arg_lua + lua\add ")" + return lua - lua = LuaCode\from(tree.source) - lua\add((stub)\as_lua_id!,"(") - for argnum, arg in ipairs tree\get_args! - arg_lua = compile(arg) + when "MethodCall" + lua = LuaCode\from tree.source + target_lua = @compile tree[1] + target_text = target_lua\text! + -- TODO: this parenthesizing is maybe overly conservative + if not (target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or + tree[1].type == "IndexChain") + target_lua\parenthesize! + + for i=2,#tree + lua\add "\n" if i > 2 + lua\add target_lua, ":" + lua\add((tree[i].stub)\as_lua_id!,"(") + for argnum, arg in ipairs tree[i]\get_args! + arg_lua = @compile(arg) if arg.type == "Block" arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()") if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE @@ -184,207 +106,180 @@ compile = setmetatable({ lua\add ", " lua\add arg_lua lua\add ")" - return lua - - when "MethodCall" - lua = LuaCode\from tree.source - target_lua = compile tree[1] - target_text = target_lua\text! - -- TODO: this parenthesizing is maybe overly conservative - if not (target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or - tree[1].type == "IndexChain") - target_lua\parenthesize! - - for i=2,#tree - lua\add "\n" if i > 2 - lua\add target_lua, ":" - lua\add((tree[i].stub)\as_lua_id!,"(") - for argnum, arg in ipairs tree[i]\get_args! - arg_lua = compile(arg) - if arg.type == "Block" - arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()") - if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE - lua\add(argnum > 1 and ",\n " or "\n ") - elseif argnum > 1 - lua\add ", " - lua\add arg_lua - lua\add ")" - return lua - - when "EscapedNomsu" - lua = LuaCode\from tree.source, "SyntaxTree{" - needs_comma, i = false, 1 - as_lua = (x)-> - if type(x) == 'number' - tostring(x) - elseif SyntaxTree\is_instance(x) - compile(x) - elseif Source\is_instance(x) - tostring(x)\as_lua! - else x\as_lua! - - for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) - entry_lua = LuaCode! - if k == i - i += 1 - elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*") - entry_lua\add(k, "= ") - else - entry_lua\add("[", as_lua(k), "]= ") - entry_lua\add as_lua(v) - if needs_comma then lua\add "," - if lua\trailing_line_len! + #(entry_lua\text!\match("^[\n]*")) > MAX_LINE - lua\add "\n " - elseif needs_comma - lua\add " " - lua\add entry_lua - needs_comma = true - lua\add "}" - return lua - - when "Block" - lua = LuaCode\from(tree.source) - for i, line in ipairs tree - if i > 1 then lua\add "\n" - lua\add compile(line) - return lua - - when "Text" - lua = LuaCode\from(tree.source) - added = 0 - string_buffer = "" - add_bit = (bit)-> - if added > 0 - if lua\trailing_line_len! + #bit > MAX_LINE - lua\add "\n " - lua\add ".." - lua\add bit - added += 1 - - for i, bit in ipairs tree - if type(bit) == "string" - string_buffer ..= bit - continue - if string_buffer != "" - for i=1,#string_buffer,MAX_LINE - add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua! - string_buffer = "" + return lua - bit_lua = compile(bit) - if bit.type == "Block" and #bit == 1 - bit = bit[1] - if bit.type == "Block" - bit_lua = LuaCode\from bit.source, "List(function(add)", - "\n ", bit_lua, - "\nend):joined()" - elseif bit.type != "Text" - bit_lua = LuaCode\from(bit.source, "tostring(",bit_lua,")") - add_bit bit_lua + when "EscapedNomsu" + lua = LuaCode\from tree.source, "SyntaxTree{" + needs_comma, i = false, 1 + as_lua = (x)-> + if type(x) == 'number' + tostring(x) + elseif SyntaxTree\is_instance(x) + @compile(x) + elseif Source\is_instance(x) + tostring(x)\as_lua! + else x\as_lua! + + for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) + entry_lua = LuaCode! + if k == i + i += 1 + elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*") + entry_lua\add(k, "= ") + else + entry_lua\add("[", as_lua(k), "]= ") + entry_lua\add as_lua(v) + if needs_comma then lua\add "," + if lua\trailing_line_len! + #(entry_lua\text!\match("^[\n]*")) > MAX_LINE + lua\add "\n " + elseif needs_comma + lua\add " " + lua\add entry_lua + needs_comma = true + lua\add "}" + return lua + + when "Block" + lua = LuaCode\from(tree.source) + for i, line in ipairs tree + if i > 1 then lua\add "\n" + lua\add @compile(line) + return lua + when "Text" + lua = LuaCode\from(tree.source) + added = 0 + string_buffer = "" + add_bit = (bit)-> + if added > 0 + if lua\trailing_line_len! + #bit > MAX_LINE + lua\add "\n " + lua\add ".." + lua\add bit + added += 1 + + for i, bit in ipairs tree + if type(bit) == "string" + string_buffer ..= bit + continue if string_buffer != "" for i=1,#string_buffer,MAX_LINE add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua! string_buffer = "" - if added == 0 - add_bit '""' - if added > 1 - lua\parenthesize! - return lua + bit_lua = @compile(bit) + if bit.type == "Block" and #bit == 1 + bit = bit[1] + if bit.type == "Block" + bit_lua = LuaCode\from bit.source, "List(function(add)", + "\n ", bit_lua, + "\nend):joined()" + elseif bit.type != "Text" + bit_lua = LuaCode\from(bit.source, "tostring(",bit_lua,")") + add_bit bit_lua + + if string_buffer != "" + for i=1,#string_buffer,MAX_LINE + add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua! + string_buffer = "" - when "List", "Dict" - if #tree == 0 - return LuaCode\from tree.source, tree.type, "{}" + if added == 0 + add_bit '""' + if added > 1 + lua\parenthesize! + return lua - lua = LuaCode\from tree.source - chunks = 0 - i = 1 - while tree[i] - if tree[i].type == 'Block' - lua\add " + " if chunks > 0 - lua\add tree.type, "(function(", (tree.type == 'List' and "add" or ("add, "..("add 1 =")\as_lua_id!)), ")" - lua\add "\n ", compile(tree[i]), "\nend)" - chunks += 1 + when "List", "Dict" + if #tree == 0 + return LuaCode\from tree.source, tree.type, "{}" + + lua = LuaCode\from tree.source + chunks = 0 + i = 1 + while tree[i] + if tree[i].type == 'Block' + lua\add " + " if chunks > 0 + lua\add tree.type, "(function(", (tree.type == 'List' and "add" or ("add, "..("add 1 =")\as_lua_id!)), ")" + lua\add "\n ", @compile(tree[i]), "\nend)" + chunks += 1 + i += 1 + else + lua\add " + " if chunks > 0 + sep = '' + items_lua = LuaCode\from tree[i].source + while tree[i] + if tree[i].type == "Block" + break + item_lua = @compile tree[i] + if item_lua\text!\match("^%.[a-zA-Z_]") + item_lua = item_lua\text!\sub(2) + if tree.type == 'Dict' and tree[i].type == 'Index' + item_lua = LuaCode\from tree[i].source, item_lua, "=true" + items_lua\add sep, item_lua + if tree[i].type == "Comment" + items_lua\add "\n" + sep = '' + elseif items_lua\trailing_line_len! > MAX_LINE + sep = ',\n ' + else + sep = ', ' i += 1 + if items_lua\is_multiline! + lua\add LuaCode\from items_lua.source, tree.type, "{\n ", items_lua, "\n}" else - lua\add " + " if chunks > 0 - sep = '' - items_lua = LuaCode\from tree[i].source - while tree[i] - if tree[i].type == "Block" - break - item_lua = compile tree[i] - if item_lua\text!\match("^%.[a-zA-Z_]") - item_lua = item_lua\text!\sub(2) - if tree.type == 'Dict' and tree[i].type == 'Index' - item_lua = LuaCode\from tree[i].source, item_lua, "=true" - items_lua\add sep, item_lua - if tree[i].type == "Comment" - items_lua\add "\n" - sep = '' - elseif items_lua\trailing_line_len! > MAX_LINE - sep = ',\n ' - else - sep = ', ' - i += 1 - if items_lua\is_multiline! - lua\add LuaCode\from items_lua.source, tree.type, "{\n ", items_lua, "\n}" - else - lua\add LuaCode\from items_lua.source, tree.type, "{", items_lua, "}" - chunks += 1 - - return lua - - when "Index" - key_lua = compile(tree[1]) - key_str = match(key_lua\text!, '^"([a-zA-Z_][a-zA-Z0-9_]*)"$') - return if key_str and key_str\is_lua_id! - LuaCode\from tree.source, ".", key_str - elseif sub(key_lua\text!,1,1) == "[" - -- NOTE: this *must* use a space after the [ to avoid freaking out - -- Lua's parser if the inner expression is a long string. Lua - -- parses x[[[y]]] as x("[y]"), not as x["y"] - LuaCode\from tree.source, "[ ",key_lua,"]" - else - LuaCode\from tree.source, "[",key_lua,"]" + lua\add LuaCode\from items_lua.source, tree.type, "{", items_lua, "}" + chunks += 1 + + return lua - when "DictEntry" - key = tree[1] + when "Index" + key_lua = @compile(tree[1]) + key_str = match(key_lua\text!, '^"([a-zA-Z_][a-zA-Z0-9_]*)"$') + return if key_str and key_str\is_lua_id! + LuaCode\from tree.source, ".", key_str + elseif sub(key_lua\text!,1,1) == "[" + -- NOTE: this *must* use a space after the [ to avoid freaking out + -- Lua's parser if the inner expression is a long string. Lua + -- parses x[[[y]]] as x("[y]"), not as x["y"] + LuaCode\from tree.source, "[ ",key_lua,"]" + else + LuaCode\from tree.source, "[",key_lua,"]" + + when "DictEntry" + key = tree[1] + if key.type != "Index" + key = SyntaxTree{type:"Index", source:key.source, key} + return LuaCode\from tree.source, @compile(key),"=",(tree[2] and @compile(tree[2]) or "true") + + when "IndexChain" + lua = @compile(tree[1]) + if lua\text!\match("['\"}]$") or lua\text!\match("]=*]$") + lua\parenthesize! + for i=2,#tree + key = tree[i] + -- TODO: remove this shim if key.type != "Index" key = SyntaxTree{type:"Index", source:key.source, key} - return LuaCode\from tree.source, compile(key),"=",(tree[2] and compile(tree[2]) or "true") - - when "IndexChain" - lua = compile(tree[1]) - if lua\text!\match("['\"}]$") or lua\text!\match("]=*]$") - lua\parenthesize! - for i=2,#tree - key = tree[i] - -- TODO: remove this shim - if key.type != "Index" - key = SyntaxTree{type:"Index", source:key.source, key} - lua\add compile(key) - return lua - - when "Number" - return LuaCode\from(tree.source, tostring(tree[1])) + lua\add @compile(key) + return lua - when "Var" - return LuaCode\from(tree.source, tree\as_var!\as_lua_id!) + when "Number" + return LuaCode\from(tree.source, tostring(tree[1])) - when "FileChunks" - error("Can't convert FileChunks to a single block of lua, since each chunk's ".. - "compilation depends on the earlier chunks") - - when "Comment" - return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- '))) - - when "Error" - error("Can't compile errors") + when "Var" + return LuaCode\from(tree.source, tree\as_var!\as_lua_id!) - else - error("Unknown type: #{tree.type}") + when "FileChunks" + error("Can't convert FileChunks to a single block of lua, since each chunk's ".. + "compilation depends on the earlier chunks") + + when "Comment" + return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- '))) + + when "Error" + error("Can't compile errors") -}) + else + error("Unknown type: #{tree.type}") return {:compile, :compile_error} -- cgit v1.2.3