From 2282085e13f11581d2d5c0ac1491d54da4a3e19d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Sep 2018 20:20:40 -0700 Subject: [PATCH] Initial working version. --- core/metaprogramming.nom | 19 +++----- nomsu.lua | 2 +- nomsu.moon | 2 +- nomsu_compiler.lua | 101 +++++++++++++++++++++------------------ nomsu_compiler.moon | 80 +++++++++++++++++++------------ syntax_tree.lua | 4 +- syntax_tree.moon | 4 +- tools/test.nom | 1 + 8 files changed, 117 insertions(+), 96 deletions(-) diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom index f669493..93d5486 100644 --- a/core/metaprogramming.nom +++ b/core/metaprogramming.nom @@ -205,14 +205,8 @@ compile [parse %actions as %body] to (..) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -action [%tree as lua expr]: - lua> "\ - ..\%tree_lua = nomsu:compile(\%tree) - if not \%tree_lua.is_value then - nomsu:compile_error(\%tree.source, "Could not convert %s to a Lua expression", - nomsu:tree_to_nomsu(\%tree)) - end - return \%tree_lua" +# TODO: add check for .is_value +compile [%tree as lua expr] to (Lua value "nomsu:compile(\(=lua "nomsu:compile(\%tree)"))") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -343,18 +337,17 @@ compile [parse %text from %filename] to (..) test: assume ((run "return (2 + 99)") == 101) external %passed = (no) - run \: - \(external \%passed = \(yes)) + run "external %passed = (yes)" assume %passed compile [run %nomsu_code] to (..) Lua value "\ - ..nomsu:run(\(%nomsu_code as lua expr), \(..) + ..nomsu:run(NomsuCode(\(..) =lua "repr(tostring(\(%nomsu_code.source)))" - ..)" + .., \(%nomsu_code as lua expr)))" test: assume ((\(\(5) + \(5)) as value) == 10) or barf "%tree as value failed." -action [run tree %tree, %tree as value] (lua> "return nomsu:run(\%tree)") +compile [run tree %tree, %tree as value] to (Lua value "nomsu:run(\(%tree as lua expr))") compile [compile %block, compiled %block, %block compiled] to (..) Lua value "nomsu:compile(\(%block as lua))" diff --git a/nomsu.lua b/nomsu.lua index 328ef32..fda8dd4 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -187,7 +187,7 @@ run = function() if not (args.no_core) then for _, filename in Files.walk('core') do if filename:match("%.nom$") then - nomsu:run_file(filename) + nomsu:import(nomsu:run_file(filename)) end end end diff --git a/nomsu.moon b/nomsu.moon index fcae65d..04a9515 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -126,7 +126,7 @@ run = -> unless args.no_core for _,filename in Files.walk('core') if filename\match "%.nom$" - nomsu\run_file filename + nomsu\import(nomsu\run_file(filename)) get_file_and_source = (filename)-> local file, source diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index bdacdfb..7d733e6 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -141,21 +141,9 @@ for version = 1, 999 do end end local MAX_LINE = 80 -local NomsuCompiler = setmetatable({ - name = "Nomsu" -}, { - __index = function(self, k) - do - local _self = rawget(self, "self") - if _self then - return _self[k] - else - return nil - end - end - end, +local NomsuCompiler = setmetatable({ }, { __tostring = function(self) - return self.name + return "Nomsu" end }) local _anon_chunk = 0 @@ -163,6 +151,7 @@ do NomsuCompiler.NOMSU_COMPILER_VERSION = 8 NomsuCompiler.NOMSU_SYNTAX_VERSION = max_parser_version NomsuCompiler.nomsu = NomsuCompiler + NomsuCompiler.global = Dict({ }) NomsuCompiler.parse = function(self, nomsu_code, source, version) if source == nil then source = nil @@ -316,6 +305,26 @@ do NomsuCompiler.LOADED = { } NomsuCompiler.TESTS = { } NomsuCompiler.AST = AST + NomsuCompiler.fork = function(self) + local f = setmetatable({ }, { + __index = self + }) + f.COMPILE_ACTIONS = setmetatable({ }, { + __index = self.COMPILE_ACTIONS + }) + f.nomsu = f + return f + end + local math_expression = re.compile([[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]]) + NomsuCompiler.get_compile_action = function(self, stub) + local ret = self.COMPILE_ACTIONS[stub] + if not (ret == nil) then + return ret + end + if math_expression:match(stub) then + return self.COMPILE_ACTIONS["# compile math expr #"] + end + end NomsuCompiler.compile_error = function(self, source, err_format_string, ...) err_format_string = err_format_string:gsub("%%[^s]", "%%%1") local file = Files.read(source.filename) @@ -330,7 +339,6 @@ do local err_msg = err_format_string:format(src, ...) return error(tostring(source.filename) .. ":" .. tostring(line_no) .. ": " .. err_msg, 0) end - local math_expression = re.compile([[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]]) local add_lua_bits add_lua_bits = function(self, val_or_stmt, code) local cls = val_or_stmt == "value" and LuaCode.Value or LuaCode @@ -389,7 +397,7 @@ do end return operate_on_text(code) end - NomsuCompiler.COMPILE_ACTIONS = setmetatable({ + NomsuCompiler.COMPILE_ACTIONS = { ["# compile math expr #"] = function(self, tree, ...) local lua = LuaCode.Value(tree.source) for i, tok in ipairs(tree) do @@ -419,23 +427,23 @@ do end, ["lua > 1"] = function(self, tree, code) if code.type ~= "Text" then - return LuaCode(tree.source, "nomsu:run_lua(", self:compile(code), ");") + return LuaCode(tree.source, "nomsu:run_lua(", self:compile(code), ", nomsu);") end return add_lua_bits(self, "statements", code) end, ["= lua 1"] = function(self, tree, code) if code.type ~= "Text" then - return LuaCode.Value(tree.source, "nomsu:run_lua(", self:compile(code), ":as_statements('return '))") + return LuaCode.Value(tree.source, "nomsu:run_lua(", self:compile(code), ":as_statements('return '), nomsu)") end return add_lua_bits(self, "value", code) end, ["use 1"] = function(self, tree, path) if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' then for _, f in Files.walk(path[1]) do - self:run_file(f) + self:import(self:run_file(f)) end end - return LuaCode(tree.source, "for i,f in Files.walk(", self:compile(path), ") do nomsu:run_file(f) end") + return LuaCode(tree.source, "for i,f in Files.walk(", self:compile(path), ") do nomsu:import(nomsu:run_file(f)) end") end, ["tests"] = function(self, tree) return LuaCode.Value(tree.source, "TESTS") @@ -459,21 +467,19 @@ do ["Lua version"] = function(self, tree, code) return LuaCode.Value(tree.source, repr(_VERSION)) end - }, { - __index = function(self, stub) - if math_expression:match(stub) then - return self["# compile math expr #"] + } + NomsuCompiler.import = function(self, mod) + for k, v in pairs(mod) do + if not (k == "COMPILE_ACTIONS" or k == "nomsu" or k == "_ENV") then + self[k] = v end end - }) - NomsuCompiler.run = function(self, to_run, source, version) - if source == nil then - source = nil + for k, v in pairs(mod.COMPILE_ACTIONS) do + self.COMPILE_ACTIONS[k] = v end - if version == nil then - version = nil - end - source = source or (to_run.source or Source(to_run, 1, #to_run)) + end + NomsuCompiler.run = function(self, to_run) + local source = to_run.source or Source(to_run, 1, #to_run) if type(source) == 'string' then source = Source:from_string(source) end @@ -484,7 +490,7 @@ do if AST.is_syntax_tree(to_run) then tree = to_run else - tree = self:parse(to_run, source, version) + tree = self:parse(to_run, source) end if tree == nil then return nil @@ -528,10 +534,11 @@ do end end insert(_running_files, filename) - local ret = nil + local mod = self:fork() + mod.from_file = filename if match(filename, "%.lua$") then local file = assert(Files.read(filename), "Could not find file: " .. tostring(filename)) - ret = self:run_lua(file, Source(filename, 1, #file)) + local ret = mod:run_lua(LuaCode(Source(filename, 1, #file), file)) elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") then local ran_lua if self.can_optimize(filename) then @@ -539,7 +546,10 @@ do do local file = Files.read(lua_filename) if file then - ret = self:run_lua(file, Source(lua_filename, 1, #file)) + local ret = mod:run_lua(LuaCode(Source(lua_filename, 1, #file), file)) + if type(ret) == 'table' then + mod = ret + end ran_lua = true end end @@ -549,20 +559,19 @@ do if not file then error("Tried to run file that does not exist: " .. tostring(filename)) end - ret = self:run(file, Source(filename, 1, #file)) + local ret = mod:run(NomsuCode(Source(filename, 1, #file), file)) + if type(ret) == 'table' then + mod = ret + end end else error("Invalid filetype for " .. tostring(filename), 0) end - self.LOADED[filename] = ret or true + self.LOADED[filename] = mod remove(_running_files) - self.LOADED[filename] = ret or true - return ret + return mod end - NomsuCompiler.run_lua = function(self, lua, source) - if source == nil then - source = nil - end + NomsuCompiler.run_lua = function(self, lua) local lua_string = tostring(lua) local run_lua_fn, err = load(lua_string, nil and tostring(source or lua.source), "t", self) if not run_lua_fn then @@ -577,7 +586,7 @@ do end)(), "\n") error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(line_numbered_lua)))) .. "\n\n" .. tostring(err), 0) end - source = source or lua.source + local source = lua.source or Source(lua_string, 1, #lua_string) local source_key = tostring(source) if not (SOURCE_MAP[source_key]) then local map = { } @@ -632,7 +641,7 @@ do if "Action" == _exp_0 then local stub = tree.stub do - local compile_action = self.COMPILE_ACTIONS[stub] + local compile_action = self:get_compile_action(stub) if compile_action then local args do diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index a86a9e7..944f7b8 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -84,14 +84,13 @@ for version=1,999 else break MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value -NomsuCompiler = setmetatable {name:"Nomsu"}, - __index: (k)=> if _self = rawget(@, "self") then _self[k] else nil - __tostring: => @name +NomsuCompiler = setmetatable {}, __tostring: => "Nomsu" _anon_chunk = 0 with NomsuCompiler .NOMSU_COMPILER_VERSION = 8 .NOMSU_SYNTAX_VERSION = max_parser_version .nomsu = NomsuCompiler + .global = Dict{} .parse = (nomsu_code, source=nil, version=nil)=> source or= nomsu_code.source nomsu_code = tostring(nomsu_code) @@ -152,6 +151,24 @@ with NomsuCompiler .TESTS = {} .AST = AST + .fork = => + f = setmetatable({}, {__index:@}) + f.COMPILE_ACTIONS = setmetatable({}, {__index:@COMPILE_ACTIONS}) + f.nomsu = f + return f + + -- 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. + math_expression = re.compile [[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]] + + .get_compile_action = (stub)=> + ret = @COMPILE_ACTIONS[stub] + return ret unless ret == nil + if math_expression\match(stub) + return @COMPILE_ACTIONS["# compile math expr #"] + + -- TODO: use pretty_error instead of this .compile_error = (source, err_format_string, ...)=> err_format_string = err_format_string\gsub("%%[^s]", "%%%1") file = Files.read(source.filename) @@ -166,11 +183,6 @@ with NomsuCompiler err_msg = err_format_string\format(src, ...) error("#{source.filename}:#{line_no}: "..err_msg, 0) - -- 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. - math_expression = re.compile [[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]] - add_lua_bits = (val_or_stmt, code)=> cls = val_or_stmt == "value" and LuaCode.Value or LuaCode operate_on_text = (text)-> @@ -214,7 +226,7 @@ with NomsuCompiler return lua return operate_on_text code - .COMPILE_ACTIONS = setmetatable { + .COMPILE_ACTIONS = { ["# compile math expr #"]: (tree, ...)=> lua = LuaCode.Value(tree.source) for i,tok in ipairs tree @@ -239,19 +251,20 @@ with NomsuCompiler ["lua > 1"]: (tree, code)=> if code.type != "Text" - return LuaCode tree.source, "nomsu:run_lua(", @compile(code), ");" + return LuaCode tree.source, "nomsu:run_lua(", @compile(code), ", nomsu);" return add_lua_bits(@, "statements", code) ["= lua 1"]: (tree, code)=> if code.type != "Text" - return LuaCode.Value tree.source, "nomsu:run_lua(", @compile(code), ":as_statements('return '))" + return LuaCode.Value tree.source, "nomsu:run_lua(", @compile(code), ":as_statements('return '), nomsu)" return add_lua_bits(@, "value", code) ["use 1"]: (tree, path)=> if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' for _,f in Files.walk(path[1]) - @run_file(f) - return LuaCode(tree.source, "for i,f in Files.walk(", @compile(path), ") do nomsu:run_file(f) end") + @import(@run_file(f)) + + return LuaCode(tree.source, "for i,f in Files.walk(", @compile(path), ") do nomsu:import(nomsu:run_file(f)) end") ["tests"]: (tree)=> LuaCode.Value(tree.source, "TESTS") ["test 1"]: (tree, body)=> @@ -263,17 +276,19 @@ with NomsuCompiler ["Lua version"]: (tree, code)=> return LuaCode.Value(tree.source, repr(_VERSION)) - }, { - __index: (stub)=> - if math_expression\match(stub) - return @["# compile math expr #"] } - .run = (to_run, source=nil, version=nil)=> - source or= to_run.source or Source(to_run, 1, #to_run) + .import = (mod)=> + for k,v in pairs(mod) + @[k] = v unless k == "COMPILE_ACTIONS" or k == "nomsu" or k == "_ENV" + for k,v in pairs(mod.COMPILE_ACTIONS) + @COMPILE_ACTIONS[k] = v + + .run = (to_run)=> + source = to_run.source or Source(to_run, 1, #to_run) if type(source) == 'string' then source = Source\from_string(source) if not Files.read(source.filename) then Files.spoof(source.filename, to_run) - tree = if AST.is_syntax_tree(to_run) then to_run else @parse(to_run, source, version) + tree = if AST.is_syntax_tree(to_run) then to_run else @parse(to_run, source) if tree == nil -- Happens if pattern matches, but there are no captures, e.g. an empty string return nil if tree.type != "FileChunks" @@ -305,30 +320,33 @@ with NomsuCompiler error("Circular import, this loops forever: #{concat loop, " -> "}...") insert _running_files, filename - ret = nil + mod = @fork! + mod.from_file = filename if match(filename, "%.lua$") file = assert(Files.read(filename), "Could not find file: #{filename}") - ret = @run_lua file, Source(filename, 1, #file) + ret = mod\run_lua(LuaCode(Source(filename, 1, #file), file)) elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") ran_lua = if @.can_optimize(filename) -- Look for precompiled version lua_filename = gsub(filename, "%.nom$", ".lua") if file = Files.read(lua_filename) - ret = @run_lua file, Source(lua_filename, 1, #file) + ret = mod\run_lua(LuaCode(Source(lua_filename, 1, #file), file)) + if type(ret) == 'table' + mod = ret true unless ran_lua file = Files.read(filename) if not file error("Tried to run file that does not exist: #{filename}") - ret = @run file, Source(filename,1,#file) + ret = mod\run(NomsuCode(Source(filename,1,#file), file)) + if type(ret) == 'table' + mod = ret else error("Invalid filetype for #{filename}", 0) - @LOADED[filename] = ret or true + @LOADED[filename] = mod remove _running_files + return mod - @LOADED[filename] = ret or true - return ret - - .run_lua = (lua, source=nil)=> + .run_lua = (lua)=> lua_string = tostring(lua) run_lua_fn, err = load(lua_string, nil and tostring(source or lua.source), "t", self) if not run_lua_fn @@ -336,7 +354,7 @@ with NomsuCompiler [format("%3d|%s",i,line) for i, line in ipairs Files.get_lines(lua_string)], "\n") error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack line_numbered_lua}\n\n#{err}", 0) - source or= lua.source + source = lua.source or Source(lua_string, 1, #lua_string) source_key = tostring(source) unless SOURCE_MAP[source_key] map = {} @@ -372,7 +390,7 @@ with NomsuCompiler switch tree.type when "Action" stub = tree.stub - if compile_action = @COMPILE_ACTIONS[stub] + if compile_action = @get_compile_action(stub) args = [arg for arg in *tree when type(arg) != "string"] -- Force Lua to avoid tail call optimization for debugging purposes -- TODO: use tail call? diff --git a/syntax_tree.lua b/syntax_tree.lua index 38e71ef..72b510e 100644 --- a/syntax_tree.lua +++ b/syntax_tree.lua @@ -42,10 +42,10 @@ for _index_0 = 1, #types do return getmetatable(x) == self end cls.__tostring = function(self) - return tostring(self.type) .. tostring(repr(self)) + return tostring(self.type) .. tostring(repr(self, (function() end))) end cls.__repr = function(self) - return tostring(self.type) .. tostring(repr(self)) + return tostring(self.type) .. tostring(repr(self, (function() end))) end cls.source_code_for_tree = setmetatable({ }, { __index = function(self, t) diff --git a/syntax_tree.moon b/syntax_tree.moon index 633b5ca..305a1ee 100644 --- a/syntax_tree.moon +++ b/syntax_tree.moon @@ -19,8 +19,8 @@ for name in *types .__name = name .type = name .is_instance = (x)=> getmetatable(x) == @ - .__tostring = => "#{@type}#{repr @}" - .__repr = => "#{@type}#{repr @}" + .__tostring = => "#{@type}#{repr @, (->)}" + .__repr = => "#{@type}#{repr @, (->)}" .source_code_for_tree = setmetatable({}, {__index:(t)=> s = t.source Files = require 'files' diff --git a/tools/test.nom b/tools/test.nom index 379f968..6d557ea 100755 --- a/tools/test.nom +++ b/tools/test.nom @@ -17,6 +17,7 @@ for %path in (command line args): if (%filename::matches "%.nom$"): use %filename for %path in (command line args): use %path + %tests = ((=lua "Source:from_string(\%s)") = %t for %s = %t in (tests)) for %path in (command line args): for file %filename in %path: