From 652c29bdef1f0991cc13bef59d6dc78b657ae9a4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 8 Nov 2018 15:23:22 -0800 Subject: Major overhaul, splitting nomsu_compiler into nomsu_environment, nomsu_compiler, and nomsu_decompiler. Also added comprehensions. --- nomsu_compiler.moon | 939 +++++++++------------------------------------------- 1 file changed, 159 insertions(+), 780 deletions(-) (limited to 'nomsu_compiler.moon') diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index 6fe6501..8c21eef 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -12,427 +12,153 @@ lpeg = require 'lpeg' {:R,:P,:S} = lpeg re = require 're' -Files = require 'files' {:List, :Dict, :Text} = require 'containers' -export colors, colored -colors = require 'consolecolors' -colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..tostring(msg or '')..colors.reset)}) {:insert, :remove, :concat} = table unpack or= table.unpack {:match, :sub, :gsub, :format, :byte, :find} = string -{:NomsuCode, :LuaCode, :Source} = require "code_obj" +{:LuaCode, :Source} = require "code_obj" SyntaxTree = require "syntax_tree" -make_parser = require("parser") -pretty_error = require("pretty_errors") --- Mapping from source string (e.g. "@core/metaprogramming.nom[1:100]") to a mapping --- from lua line number to nomsu line number -export SOURCE_MAP -SOURCE_MAP = {} +{:Importer, :import_to_1_from, :_1_forked} = require 'importer' table.map = (t, fn)-> setmetatable([fn(v) for _,v in ipairs(t)], getmetatable(t)) -table.fork = (t, values)-> setmetatable(values or {}, {__index:t}) -table.copy = (t)-> setmetatable({k,v for k,v in pairs(t)}, getmetatable(t)) - --- Parsing helper functions -utf8_char_patt = ( - R("\194\223")*R("\128\191") + - R("\224\239")*R("\128\191")*R("\128\191") + - R("\240\244")*R("\128\191")*R("\128\191")*R("\128\191")) -operator_patt = S("'`~!@$^&*+=|<>?/-")^1 * -1 -identifier_patt = (R("az","AZ","09") + P("_") + utf8_char_patt)^1 * -1 - -is_operator = (s)-> - return not not operator_patt\match(s) - -is_identifier = (s)-> - return not not identifier_patt\match(s) - -inline_escaper = re.compile("{~ (%utf8_char / ('\"' -> '\\\"') / ('\n' -> '\\n') / ('\t' -> '\\t') / ('\b' -> '\\b') / ('\a' -> '\\a') / ('\v' -> '\\v') / ('\f' -> '\\f') / ('\r' -> '\\r') / ('\\' -> '\\\\') / ([^ -~] -> escape) / .)* ~}", {utf8_char: utf8_char_patt, escape:(=> ("\\%03d")\format(@byte!))}) -inline_escape = (s)-> - return inline_escaper\match(s) - -escaper = re.compile("{~ (%utf8_char / ('\\' -> '\\\\') / [\n\r\t -~] / (. -> escape))* ~}", - {utf8_char: utf8_char_patt, escape:(=> ("\\%03d")\format(@byte!))}) -escape = (s)-> - return escaper\match(s) -- TODO: -- Re-implement nomsu-to-lua comment translation? -make_tree = (tree, userdata)-> - tree.source = Source(userdata.filename, tree.start, tree.stop) - tree.start, tree.stop = nil, nil - tree.comments = [t for t in *tree when SyntaxTree\is_instance(t) and t.type == "Comment"] - if #tree.comments == 0 then tree.comments = nil - for i=#tree,1,-1 - if SyntaxTree\is_instance(tree[i]) and tree[i].type == "Comment" - table.remove(tree, i) - tree = SyntaxTree(tree) - return tree - -Parsers = {} -max_parser_version = 4 -for version=1,max_parser_version - peg_file = io.open("nomsu.#{version}.peg") - if not peg_file and package.nomsupath - for path in package.nomsupath\gmatch("[^;]+") - peg_file = io.open(path.."/nomsu.#{version}.peg") - break if peg_file - assert(peg_file, "could not find nomsu .peg file") - peg_contents = peg_file\read('*a') - peg_file\close! - Parsers[version] = make_parser(peg_contents, make_tree) - -MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value -NomsuCompiler = setmetatable {}, {__tostring: => "Nomsu"} -_anon_chunk = 0 -with NomsuCompiler - .can_optimize = -> false - - -- Discretionary/convenience stuff - .environment = { - NOMSU_COMPILER_VERSION: 11, NOMSU_SYNTAX_VERSION: max_parser_version - -- Lua stuff: - :next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall, - :error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module, - :print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :rawlen, - :table, :assert, :dofile, :loadstring, lua_type_of:type, :select, :math, :io, :load, - :pairs, :ipairs, - -- Nomsu types: - List:List, Dict:Dict, - -- Utilities and misc. - lpeg:lpeg, re:re, Files:Files, - :SyntaxTree, TESTS: Dict({}), globals: Dict({}), - :LuaCode, :NomsuCode, :Source - nomsu:NomsuCompiler - __imported: Dict{} - __parent: nil +-- TODO: de-duplicate this +pretty_error = require("pretty_errors") +compile_error = (tree, err_msg, hint=nil)-> + err_str = pretty_error{ + title: "Compile error" + error:err_msg, hint:hint, source:tree\get_source_code! + start:tree.source.start, stop:tree.source.stop, filename:tree.source.filename } - setmetatable(.environment, { - __index: (key)=> - if imported = rawget(@, "__imported") - ret = imported[key] - return ret unless ret == nil - if parent = rawget(@, "__parent") - return parent[key] - }) - - if _VERSION == "Lua 5.4" - .environment.ipairs = (x)-> - if mt = getmetatable(x) - if mt.__ipairs then return mt.__ipairs(x) - return ipairs(x) - if jit or _VERSION == "Lua 5.2" - .environment.bit = require("bitops") - - .fork = => - f = setmetatable({}, {__index:@}) - f.environment = setmetatable({ - __parent: @environment - __imported: Dict{} - nomsu: f - COMPILE_ACTIONS: setmetatable({ - __parent: @environment.COMPILE_ACTIONS - __imported: Dict{} - }, getmetatable(@environment)) - }, getmetatable(@environment)) - return f - - .parse = (nomsu_code, source=nil, version=nil)=> - source or= nomsu_code.source - nomsu_code = tostring(nomsu_code) - unless source - source = Source("anonymous chunk ##{_anon_chunk}", 1, #nomsu_code) - _anon_chunk += 1 - version or= nomsu_code\match("^#![^\n]*nomsu[ ]+-V[ ]*([0-9.]+)") - syntax_version = version and tonumber(version\match("^[0-9]+")) or max_parser_version - parse = Parsers[syntax_version] or Parsers[max_parser_version] - tree = parse(nomsu_code, source.filename) - find_errors = (t)-> - if t.type == "Error" - coroutine.yield t - else - for k,v in pairs(t) - continue unless SyntaxTree\is_instance(v) - find_errors(v) - - errs = [err for err in coroutine.wrap(-> find_errors(tree))] - num_errs = #errs - if num_errs > 0 - err_strings = [pretty_error{ - title:"Parse error" - error:t.error, hint:t.hint, source:t\get_source_code! - start:t.source.start, stop:t.source.stop, filename:t.source.filename - } for i, t in ipairs(errs) when i <= 3] - if num_errs > 3 - table.insert(err_strings, "\027[31;1m +#{num_errs-#errs} additional errors...\027[0m\n") - error(table.concat(err_strings, '\n\n'), 0) - return tree - - .compile_error = (tree, err_msg, hint=nil)=> - err_str = pretty_error{ - title: "Compile error" - error:err_msg, hint:hint, source:tree\get_source_code! - start:tree.source.start, stop:tree.source.stop, filename:tree.source.filename - } - error(err_str, 0) - - add_lua_bits = (val_or_stmt, code, compile_actions)=> - cls = val_or_stmt == "value" and LuaCode.Value or LuaCode - operate_on_text = (text)-> - lua = cls(text.source) - for bit in *text - if type(bit) == "string" - lua\append bit - elseif bit.type == "Text" - lua\append(operate_on_text(bit)) - else - bit_lua = @compile(bit, compile_actions) - unless bit_lua.is_value - @compile_error bit, - "Can't use this as a string interpolation value, since it's not an expression." - lua\append bit_lua - return lua - return operate_on_text code - - add_lua_string_bits = (val_or_stmt, code)=> - cls_str = val_or_stmt == "value" and "LuaCode.Value(" or "LuaCode(" - if code.type != "Text" - return LuaCode.Value(code.source, cls_str, tostring(code.source)\as_lua!, ", ", @compile(code), ")") - add_bit_lua = (lua, bit_lua)-> - bit_leading_len = #(bit_lua\match("^[^\n]*")) - lua\append(lua\trailing_line_len! + bit_leading_len > MAX_LINE and ",\n " or ", ") - lua\append(bit_lua) - operate_on_text = (text)-> - lua = LuaCode.Value(text.source, cls_str, tostring(text.source)\as_lua!) - for bit in *text - if type(bit) == "string" - add_bit_lua(lua, bit\as_lua!) - elseif bit.type == "Text" - add_bit_lua(lua, operate_on_text(bit)) - else - bit_lua = @compile(bit) - unless bit_lua.is_value - @compile_error bit, - "Can't use this as a string interpolation value, since it's not an expression." - add_bit_lua(lua, bit_lua) - lua\append ")" - return lua - return operate_on_text code + error(err_str, 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]+) " ")* [*/^+-] !. ]] +compile_math_expression = (compile, tree, ...)-> + lua = LuaCode(tree.source) + for i,tok in ipairs tree + if type(tok) == 'string' + lua\append tok + else + tok_lua = compile(tok) + tok_lua\parenthesize! if tok.type == "Action" + lua\append tok_lua + lua\append " " if i < #tree + return lua - -- 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]+) " ")* [*/^+-] !. ]] - compile_math_expression = (tree, ...)=> - lua = LuaCode.Value(tree.source) - for i,tok in ipairs tree - if type(tok) == 'string' - lua\append tok - else - tok_lua = @compile(tok, compile_actions) - unless tok_lua.is_value - @compile_error tok, - "Can't use this as a value in a math expression, since it's not a value." - tok_lua\parenthesize! if tok.type == "Action" - lua\append tok_lua - lua\append " " if i < #tree - return lua - .environment.COMPILE_ACTIONS = setmetatable({ - __imported: Dict{} - [""]: (tree, fn, ...)=> - lua = LuaCode.Value(tree.source) - lua\append @compile(fn, compile_actions) - lua\parenthesize! unless lua\text!\is_lua_id! +MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value +compile = setmetatable({ + action: Importer{ + [""]: (compile, tree, fn, ...)-> + lua = LuaCode(tree.source) + fn_lua = compile(fn) + lua\append fn_lua + unless fn_lua\text!\match("^%(.*%)$") or fn_lua\text!\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") + lua\parenthesize! lua\append "(" for i=1,select('#',...) lua\append(", ") if i > 1 - lua\append @compile(select(i, ...), compile_actions) + lua\append compile(select(i, ...)) lua\append ")" return lua - ["Lua"]: (tree, code)=> - return add_lua_string_bits(@, 'statements', code) - - ["Lua value"]: (tree, code)=> - return add_lua_string_bits(@, 'value', code) - - ["lua >"]: (tree, code)=> - if code.type != "Text" - return LuaCode tree.source, "nomsu:run_lua(", @compile(code), ", nomsu);" - return add_lua_bits(@, "statements", code) - - ["= lua"]: (tree, code)=> + ["Lua"]: (compile, tree, code)-> if code.type != "Text" - return LuaCode.Value tree.source, "nomsu:run_lua(", @compile(code), ":as_statements('return '), nomsu)" - return add_lua_bits(@, "value", code) - - ["use"]: (tree, path)=> - if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' - unless @import_file(path[1]) - @compile_error tree, "Could not find anything to import for #{path}" - return LuaCode(tree.source, "nomsu:import_file(#{@compile(path)})") - - ["tests"]: (tree)=> LuaCode.Value(tree.source, "TESTS") - ["test"]: (tree, body)=> - test_str = table.concat [tostring(@tree_to_nomsu(line)) for line in *body], "\n" - LuaCode tree.source, "TESTS[#{tostring(tree.source)\as_lua!}] = ", test_str\as_lua! - - ["is jit"]: (tree, code)=> - return LuaCode.Value(tree.source, jit and "true" or "false") - - ["Lua version"]: (tree, code)=> - return LuaCode.Value(tree.source, _VERSION\as_lua!) - - __parent: setmetatable({}, { - __index: (key)=> - if type(key) == 'string' and math_expression\match(key) - return compile_math_expression - }) - }, getmetatable(.environment)) - - .import = (mod)=> - for k,v in pairs(mod) - continue if k == "__imported" or k == "__parent" - @environment.__imported[k] = v - for k,v in pairs(mod.COMPILE_ACTIONS) - continue if k == "__imported" or k == "__parent" - @environment.COMPILE_ACTIONS.__imported[k] or= v - - .import_file = (path)=> - found = false - for _,f in Files.walk(path) - if match(f, "%.lua$") or match(f, "%.nom$") or match(f, "^/dev/fd/[012]$") - found = true - @import(@run_file(f)) - return found + return LuaCode(code.source, "LuaCode(", tostring(code.source)\as_lua!, ", ", compile(code), ")") + add_bit_lua = (lua, bit_lua)-> + bit_leading_len = #(bit_lua\match("^[^\n]*")) + lua\append(lua\trailing_line_len! + bit_leading_len > MAX_LINE and ",\n " or ", ") + lua\append(bit_lua) + operate_on_text = (text)-> + lua = LuaCode(text.source, "LuaCode(", tostring(text.source)\as_lua!) + for bit in *text + if type(bit) == "string" + add_bit_lua(lua, bit\as_lua!) + elseif bit.type == "Text" + add_bit_lua(lua, operate_on_text(bit)) + else + add_bit_lua(lua, compile(bit)) + lua\append ")" + return lua + return operate_on_text code - .run = (to_run, compile_actions)=> - 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 SyntaxTree\is_instance(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" - tree = {tree} - -- Each chunk's compilation is affected by the code in the previous chunks - -- (typically), so each chunk needs to compile and run before the next one - -- compiles. - ret = nil - all_lua = {} - for chunk in *tree - lua = @compile(chunk, compile_actions)\as_statements("return ") - lua\declare_locals! - lua\prepend "-- File: #{source.filename\gsub("\n.*", "...")}\n" - insert all_lua, tostring(lua) - ret = @run_lua(lua) - return ret + -- TODO: remove shim + ["Lua value"]: (compile, tree, code)-> compile.action["Lua"](compile, tree, code) - _running_files = {} -- For detecting circular imports - _loaded_files = {} - .run_file = (filename, compile_actions)=> - -- Filename should be an absolute path, i.e. package.nomsupath will not be searched for it - if _loaded_files[filename] - return _loaded_files[filename] - -- Check for circular import - -- TODO: optimize? - for i,running in ipairs _running_files - if running == filename - loop = [_running_files[j] for j=i,#_running_files] - insert loop, filename - error("Circular import, this loops forever: #{concat loop, " -> "}...") + ["lua >"]: (compile, tree, code)-> + if code.type != "Text" + return tree + operate_on_text = (text)-> + lua = LuaCode(text.source) + for bit in *text + if type(bit) == "string" + lua\append bit + elseif bit.type == "Text" + lua\append(operate_on_text(bit)) + else + lua\append compile(bit) + return lua + return operate_on_text code - insert _running_files, filename - mod = @fork! - ret = mod.environment - mod.from_file = filename - if match(filename, "%.lua$") - file = assert(Files.read(filename), "Could not find file: #{filename}") - ret = mod\run_lua(LuaCode(Source(filename, 1, #file), file)) or ret - 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 = mod\run_lua(LuaCode(Source(lua_filename, 1, #file), file)) or ret - true - unless ran_lua - file = Files.read(filename) - if not file - error("Tried to run file that does not exist: #{filename}") - ret = mod\run(NomsuCode(Source(filename,1,#file), file), compile_actions) or ret - else - error("Invalid filetype for #{filename}", 0) - _loaded_files[filename] = ret - remove _running_files - return ret + ["= lua"]: (compile, tree, code)-> compile.action["lua >"](compile, tree, code) - .run_lua = (lua)=> - lua_string = tostring(lua) - run_lua_fn, err = load(lua_string, tostring(source or lua.source), "t", @environment) - if not run_lua_fn - line_numbered_lua = concat( - [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 = lua.source or Source(lua_string, 1, #lua_string) - source_key = tostring(source) - unless SOURCE_MAP[source_key] - map = {} - file = Files.read(source.filename) - if not file - error "Failed to find file: #{source.filename}" - nomsu_str = file\sub(source.start, source.stop) - assert type(nomsu_str) == 'string' - lua_line = 1 - nomsu_line = Files.get_line_number(file, source.start) - map_sources = (s)-> - if type(s) == 'string' - for nl in s\gmatch("\n") - map[lua_line] or= nomsu_line - lua_line += 1 - else - if s.source and s.source.filename == source.filename - nomsu_line = Files.get_line_number(file, s.source.start) - for b in *s.bits do map_sources(b) - map_sources(lua) - map[lua_line] or= nomsu_line - map[0] = 0 - -- Mapping from lua line number to nomsu line numbers - SOURCE_MAP[source_key] = map + ["use"]: (compile, tree, path)-> + --if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' + -- unless import_to_1_from(compile, path[1]) + -- compile_error tree, "Could not find anything to import for #{path}" + return LuaCode(tree.source, "run_file_1_in(#{compile(path)}, _ENV)") - return run_lua_fn! + ["tests"]: (compile, tree)-> LuaCode(tree.source, "TESTS") + ["test"]: (compile, tree, body)-> + -- TODO: maybe go back to storing nomsu code instead of syntax tree + LuaCode tree.source, "TESTS[#{tostring(tree.source)\as_lua!}] = ", body\as_lua! - .compile = (tree, compile_actions, force_value=false)=> - compile_actions or= @environment.COMPILE_ACTIONS + ["is jit"]: (compile, tree, code)-> LuaCode(tree.source, "jit") + ["Lua version"]: (compile, tree, code)-> LuaCode(tree.source, "_VERSION") + ["nomsu environment"]: (compile, tree)-> LuaCode(tree.source, "_ENV") + } +}, { + __import: (other)=> + import_to_1_from(@action, other.action) + return + __call: (compile, tree, force_value=false)-> if tree.version - if get_version = @[("Nomsu version")\as_lua_id!] - if upgrade = @[("1 upgraded from 2 to")\as_lua_id!] + if get_version = compile.action[("Nomsu version")\as_lua_id!] + if upgrade = compile.action[("1 upgraded from 2 to")\as_lua_id!] tree = upgrade(tree, tree.version, get_version!) switch tree.type when "Action" stub = tree.stub - compile_action = compile_actions[stub] + compile_action = compile.action[stub] + if not compile_action and math_expression\match(stub) + compile_action = compile_math_expression if compile_action and not tree.target 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(@, tree, unpack(args)) + ret = compile_action(compile, tree, unpack(args)) if ret == nil info = debug.getinfo(compile_action, "S") filename = Source\from_string(info.source).filename - @compile_error tree, + 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) return ret if ret != tree - return @compile(ret, compile_actions) + return compile(ret) - lua = LuaCode.Value(tree.source) + lua = LuaCode(tree.source) if tree.target -- Method call - target_lua = @compile tree.target, compile_actions - if tostring(target_lua)\match("^%(.*%)$") or tostring(target_lua)\match("^[_a-zA-Z][_a-zA-Z0-9]*$") + target_lua = compile tree.target + target_text = target_lua\text! + if target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") lua\append target_lua, ":" else lua\append "(", target_lua, "):" @@ -440,33 +166,20 @@ with NomsuCompiler args = {} for i, tok in ipairs tree if type(tok) == "string" then continue - arg_lua = @compile(tok, compile_actions, true) - unless arg_lua.is_value - if tok.type == "Block" - @compile_error tok, - "Can't compile action (#{stub}) with a Block as an argument.", - "Maybe there should be a compile-time action with that name that isn't being found?" - - elseif tok.type == "Action" - @compile_error tok, - "Can't use this as an argument to (#{stub}), since it's not an expression, it produces: #{tostring(arg_lua)}", - "Check the implementation of (#{tok.stub}) to see if it is actually meant to produce an expression." - else - @compile_error tok, - "Can't use this as an argument to (#{stub}), since it's not an expression, it produces: #{tostring(arg_lua)}" + arg_lua = compile(tok, true) insert args, arg_lua lua\concat_append args, ", " lua\append ")" return lua when "EscapedNomsu" - lua = LuaCode.Value tree.source, "SyntaxTree{" + lua = LuaCode tree.source, "SyntaxTree{" needs_comma, i = false, 1 as_lua = (x)-> if type(x) == 'number' tostring(x) elseif SyntaxTree\is_instance(x) - @compile(x, compile_actions) + compile(x) else x\as_lua! for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) @@ -488,29 +201,18 @@ with NomsuCompiler when "Block" if not force_value lua = LuaCode(tree.source) - lua\concat_append([@compile(line, compile_actions)\as_statements! for line in *tree], "\n") + lua\concat_append([compile(line) for line in *tree], "\n") return lua else - lua = LuaCode.Value(tree.source) - values = [@compile(line) for line in *tree] - all_values = true - for v in *values do all_values and= v.is_value - if all_values - return values[1] if #values == 1 - lua\append "(" - lua\concat_append(values, " and nil or ") - lua\append ")" - else - lua\append("((function()") - for i, v in ipairs(values) - if v.is_value - v = v\as_statements(i == #values and 'return ' or '') - lua\append "\n ", v - lua\append("\nend)())") + lua = LuaCode(tree.source) + lua\append("((function()") + for i, line in ipairs(tree) + lua\append "\n ", compile(line) + lua\append("\nend)())") return lua when "Text" - lua = LuaCode.Value(tree.source) + lua = LuaCode(tree.source) string_buffer = "" for i, bit in ipairs tree if type(bit) == "string" @@ -520,15 +222,10 @@ with NomsuCompiler if #lua.bits > 0 then lua\append ".." lua\append string_buffer\as_lua! string_buffer = "" - bit_lua = @compile(bit, compile_actions) - unless bit_lua.is_value - src = ' '..gsub(tostring(@compile(bit, compile_actions)), '\n','\n ') - line = "#{bit.source.filename}:#{Files.get_line_number(Files.read(bit.source.filename), bit.source.start)}" - @compile_error bit, - "Can't this as a string interpolation value, since it's not an expression." + bit_lua = compile(bit) if #lua.bits > 0 then lua\append ".." if bit.type != "Text" - bit_lua = LuaCode.Value(bit.source, "tostring(",bit_lua,")") + bit_lua = LuaCode(bit.source, "tostring(",bit_lua,")") lua\append bit_lua if string_buffer ~= "" or #lua.bits == 0 @@ -539,32 +236,50 @@ with NomsuCompiler lua\parenthesize! return lua - when "List" - lua = LuaCode.Value tree.source, "List{" - lua\concat_append([@compile(e, compile_actions) for e in *tree], ", ", ",\n ") - lua\append "}" - return lua - - when "Dict" - lua = LuaCode.Value tree.source, "Dict{" - lua\concat_append([@compile(e, compile_actions) for e in *tree], ", ", ",\n ") + when "List", "Dict" + lua = LuaCode tree.source, "#{tree.type}{" + i = 1 + sep = '' + while i <= #tree + item = tree[i] + if item.type == "Block" + break + lua\append sep + if item.type == "Comment" + lua\append compile(item), "\n" + sep = '' + else + item_lua = compile(item) + lua\append item_lua + sep = ', ' + i += 1 lua\append "}" + if i <= #tree + lua = LuaCode tree.source, "(function()\n local it = ", lua + while i <= #tree + lua\append "\n " + if tree[i].type == 'Block' or tree[i].type == 'Comment' + lua\append compile(tree[i]) + elseif tree[i].type == "DictEntry" + lua\append "it[ ", compile(tree[i][1]), "] = ", (tree[i][2] and compile(tree[i][2]) or "true") + else + lua\append "it:add(", compile(tree[i]), ")" + i += 1 + lua\append "\n return it\nend)()" return lua + --lua = LuaCode tree.source, "#{tree.type}{" + --lua\concat_append([compile(e) for e in *tree when e.type != 'Comment'], ", ", ",\n ") + --lua\append "}" + --return lua when "DictEntry" key, value = tree[1], tree[2] - key_lua = @compile(key, compile_actions) - unless key_lua.is_value - @compile_error tree[1], - "Can't use this as a dict key, since it's not an expression." - value_lua = value and @compile(value, compile_actions) or LuaCode.Value(key.source, "true") - unless value_lua.is_value - @compile_error tree[2], - "Can't use this as a dict value, since it's not an expression." - key_str = match(tostring(key_lua), [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=]) + key_lua = compile(key) + value_lua = value and compile(value) or LuaCode(key.source, "true") + key_str = match(key_lua\text!, [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=]) return if key_str and key_str\is_lua_id! LuaCode tree.source, key_str,"=",value_lua - elseif sub(tostring(key_lua),1,1) == "[" + 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"] @@ -573,21 +288,15 @@ with NomsuCompiler LuaCode tree.source, "[",key_lua,"]=",value_lua when "IndexChain" - lua = @compile(tree[1], compile_actions) - unless lua.is_value - @compile_error tree[1], - "Can't index into this, since it's not an expression." - first_char = sub(tostring(lua),1,1) + lua = compile(tree[1]) + first_char = sub(lua\text!,1,1) if first_char == "{" or first_char == '"' or first_char == "[" lua\parenthesize! for i=2,#tree key = tree[i] - key_lua = @compile(key, compile_actions) - unless key_lua.is_value - @compile_error key, - "Can't use this as an index, since it's not an expression." - key_lua_str = tostring(key_lua) + key_lua = compile(key) + key_lua_str = key_lua\text! lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") if lua_id and lua_id\is_lua_id! lua\append ".#{lua_id}" @@ -601,10 +310,10 @@ with NomsuCompiler return lua when "Number" - return LuaCode.Value(tree.source, tostring(tree[1])) + return LuaCode(tree.source, tostring(tree[1])) when "Var" - return LuaCode.Value(tree.source, (tree[1])\as_lua_id!) + return LuaCode(tree.source, (tree[1])\as_lua_id!) when "FileChunks" error("Can't convert FileChunks to a single block of lua, since each chunk's ".. @@ -620,336 +329,6 @@ with NomsuCompiler else error("Unknown type: #{tree.type}") - .tree_to_inline_nomsu = (tree, parenthesize_blocks=false, check=nil, len=0)=> - recurse = (tree, nomsu=nil, parenthesize_blocks=false)-> - @tree_to_inline_nomsu(tree, parenthesize_blocks, check, len + (nomsu and #tostring(nomsu) or 0)) - switch tree.type - when "FileChunks" - error("Can't inline a FileChunks") - - when "Comment" - -- TODO: implement? - return NomsuCode(tree.source, "") - - when "Error" - error("Can't compile errors") - - when "Action" - nomsu = NomsuCode(tree.source) - if tree.target - inline_target = @tree_to_inline_nomsu(tree.target) - if tree.target.type == "Action" - inline_target\parenthesize! - nomsu\append inline_target, "::" - for i,bit in ipairs tree - if type(bit) == "string" - clump_words = (type(tree[i-1]) == 'string' and is_operator(bit) != is_operator(tree[i-1])) - nomsu\append " " if i > 1 and not clump_words - nomsu\append bit - else - arg_nomsu = recurse(bit, nomsu, parenthesize_blocks or (i == 1 or i < #tree)) - nomsu\append " " unless arg_nomsu\match("^:") or i == 1 - arg_nomsu\parenthesize! if bit.type == "Action" - nomsu\append arg_nomsu - check(len, nomsu, tree) if check - return nomsu - - when "EscapedNomsu" - inner_nomsu = recurse(tree[1]) - unless tree[1].type == "List" or tree[1].type == "Dict" or tree[1].type == "Var" - inner_nomsu\parenthesize! - nomsu = NomsuCode(tree.source, "\\", inner_nomsu) - check(len, nomsu, tree) if check - return nomsu - - when "Block" - nomsu = NomsuCode(tree.source, ":") - check(len, nomsu, tree) if check - for i,line in ipairs tree - nomsu\append(i == 1 and " " or "; ") - nomsu\append recurse(line, nomsu, i == 1 or i < #tree) - check(len, nomsu, tree) if check - nomsu\parenthesize! if #tree > 1 or parenthesize_blocks - return nomsu - - when "Text" - add_text = (nomsu, tree)-> - for i, bit in ipairs tree - if type(bit) == 'string' - escaped = inline_escape(bit) - nomsu\append inline_escape(bit) - elseif bit.type == "Text" - add_text(nomsu, bit) - else - interp_nomsu = recurse(bit, nomsu) - if bit.type != "Var" and bit.type != "List" and bit.type != "Dict" - interp_nomsu\parenthesize! - elseif bit.type == "Var" and type(tree[i+1]) == 'string' and not match(tree[i+1], "^[ \n\t,.:;#(){}[%]]") - interp_nomsu\parenthesize! - nomsu\append "\\", interp_nomsu - check(len, nomsu, tree) if check - nomsu = NomsuCode(tree.source) - add_text(nomsu, tree) - return NomsuCode(tree.source, '"', nomsu, '"') - - when "List", "Dict" - nomsu = NomsuCode(tree.source, (tree.type == "List" and "[" or "{")) - for i, item in ipairs tree - nomsu\append ", " if i > 1 - nomsu\append recurse(item, nomsu) - check(len, nomsu, tree) if check - nomsu\append(tree.type == "List" and "]" or "}") - return nomsu - - when "DictEntry" - key, value = tree[1], tree[2] - nomsu = if key.type == "Text" and #key == 1 and is_identifier(key[1]) - NomsuCode(key.source, key[1]) - else recurse(key) - nomsu\parenthesize! if key.type == "Action" or key.type == "Block" - assert(value.type != "Block", "Didn't expect to find a Block as a value in a dict") - nomsu\append ":" - if value - value_nomsu = recurse(value, nomsu) - value_nomsu\parenthesize! if value.type == "Block" - nomsu\append value_nomsu - check(len, nomsu, tree) if check - return nomsu - - when "IndexChain" - nomsu = NomsuCode(tree.source) - for i, bit in ipairs tree - nomsu\append "." if i > 1 - local bit_nomsu - bit_nomsu = if i > 1 and bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string' and is_identifier(bit[1]) - bit[1] - else recurse(bit, nomsu) - assert bit.type != "Block" - if bit.type == "Action" or bit.type == "IndexChain" or (bit.type == "Number" and i < #tree) - bit_nomsu\parenthesize! - nomsu\append bit_nomsu - check(len, nomsu, tree) if check - return nomsu - - when "Number" - return NomsuCode(tree.source, tostring(tree[1])) - - when "Var" - return NomsuCode(tree.source, "%", tree[1]) - - else - error("Unknown type: #{tree.type}") - - .tree_to_nomsu = (tree, pop_comments=nil)=> - unless pop_comments - comment_set = {} - find_comments = (t)-> - if t.comments and t.source.filename == tree.source.filename - comment_set[c] = true for c in *t.comments - find_comments(x) for x in *t when SyntaxTree\is_instance x - find_comments(tree) - -- Sort in reversed order so they can be easily popped - comments = [c for c in pairs comment_set] - table.sort(comments, (a,b)->(a.source.start > b.source.start)) - - pop_comments = (pos, prefix='', suffix='')-> - nomsu = NomsuCode(tree.source) - for i=#comments,1,-1 - break if comments[i].source.start > pos - comment, comments[i] = comments[i][1], nil - nomsu\append("#"..(gsub(comment, "\n", "\n ")).."\n") - if comment\match("^\n.") then nomsu\append("\n") -- for aesthetics - return '' if #nomsu.bits == 0 - nomsu\prepend(prefix) unless prefix == '' - nomsu\append(suffix) unless suffix == '' - return nomsu - - -- For concision: - recurse = (t, pos)-> - if pos == nil then pos = 0 - if type(pos) != 'number' then pos = #tostring(pos)\match("[ ]*([^\n]*)$") - space = MAX_LINE - pos - local inline - for prefix, nomsu, tree in coroutine.wrap(-> inline = @tree_to_inline_nomsu(t, false, coroutine.yield)) - len = #tostring(nomsu) - break if prefix+len > MAX_LINE - break if tree.type == "Block" and (#tree > 1 or len > 20) - if tree.type == "Text" - -- Disallow inline text if it's got newlines between text, e.g. "hello\nworld" - check_for_nl = (tree)-> - found_nl = false - for i,b in ipairs tree - return true if type(b) != 'string' and b.type == "Text" and check_for_nl(b) - b = b\match('^[\n]*(.*)') if i == 1 and type(b) == 'string' - found_nl or= type(b) == 'string' and b\match('\n') - return true if found_nl and (type(b) != 'string' or b\match('[^\n]')) - break if check_for_nl(tree) - return inline if inline and #tostring(inline) <= space - indented = @tree_to_nomsu(t, pop_comments, space) - if t.type == "Action" and not (tree.type == "Block" or tree.type == "FileChunks") - indented = NomsuCode(t.source, "(..)\n ", pop_comments(t.source.start), indented) - return indented - - switch tree.type - when "FileChunks" - nomsu = NomsuCode(tree.source, pop_comments(tree.source.start)) - should_clump = (prev_line, line)-> - if prev_line.type == "Action" and line.type == "Action" - if prev_line.stub == "use" then return line.stub == "use" - if prev_line.stub == "test" then return true - if line.stub == "test" then return false - return not recurse(prev_line)\is_multiline! - for chunk_no, chunk in ipairs tree - nomsu\append "\n\n#{("~")\rep(80)}\n\n" if chunk_no > 1 - nomsu\append pop_comments(chunk.source.start) - if chunk.type == "Block" - for line_no, line in ipairs chunk - if line_no > 1 - if should_clump(chunk[line_no-1], line) - nomsu\append "\n", pop_comments(line.source.start, '\n') - else - nomsu\append "\n\n", pop_comments(line.source.start) - nomsu\append @tree_to_nomsu(line, pop_comments) - nomsu\append pop_comments(chunk.source.stop, '\n') - else - nomsu\append recurse(chunk) - nomsu\append pop_comments(tree.source.stop, '\n') - nomsu\append('\n') unless nomsu\match("\n$") - return nomsu - - when "Action" - pos, next_space = tree.source.start, '' - nomsu = NomsuCode(tree.source, pop_comments(pos)) - if tree.target - if tree.target.type == "Block" - nomsu\append(recurse(tree.target, #nomsu\match('[^\n]*$'))) - pos = tree.target.source.stop - next_space = inline and "::" or "\n..::" - else - target_nomsu = recurse(tree.target, #nomsu\match('[^\n]*$')) - if tree.target.type == "Action" and not target_nomsu\is_multiline! - target_nomsu\parenthesize! - nomsu\append target_nomsu - pos = tree.target.source.stop - next_space = target_nomsu\is_multiline! and "\n..::" or "::" - - for i,bit in ipairs tree - if next_space == "\n.." - nomsu\append "\n", pop_comments(pos), '..' - next_space = "" - elseif next_space == " " and nomsu\trailing_line_len! > MAX_LINE - nomsu\append " \\\n", pop_comments(pos), '..' - next_space = "" - - if type(bit) == "string" - unless type(tree[i-1]) == 'string' and is_operator(tree[i-1]) != is_operator(bit) - nomsu\append(next_space) - nomsu\append bit - next_space = ' ' - elseif bit.type == "Block" - nomsu\append(recurse(bit, #nomsu\match('[^\n]*$'))) - pos = bit.source.stop - next_space = inline and " " or "\n.." - else - nomsu\append next_space - bit_nomsu = recurse(bit, #nomsu\match('[^\n]*$')) - if bit.type == "Action" and not bit_nomsu\is_multiline! - bit_nomsu\parenthesize! - nomsu\append bit_nomsu - pos = bit.source.stop - next_space = bit_nomsu\is_multiline! and "\n.." or " " - - nomsu\append pop_comments(tree.source.stop, '\n') - return nomsu - - when "EscapedNomsu" - val_nomsu = recurse(tree[1], 1) - if tree[1].type == "Action" and not val_nomsu\is_multiline! - val_nomsu\parenthesize! - return NomsuCode tree.source, "\\", val_nomsu - - when "Block" - nomsu = NomsuCode(tree.source, pop_comments(tree.source.start)) - for i, line in ipairs tree - nomsu\append pop_comments(line.source.start, i > 1 and '\n' or '') - line_nomsu = recurse(line) - nomsu\append line_nomsu - if i < #tree - nomsu\append(line_nomsu\match('\n[^\n]*\n') and "\n\n" or "\n") - nomsu\append pop_comments(tree.source.stop, '\n') - return NomsuCode(tree.source, ":\n ", nomsu) - - when "Text" - -- Multi-line text has more generous wrap margins - max_line = math.floor(1.5*MAX_LINE) - add_text = (nomsu, tree)-> - for i, bit in ipairs tree - if type(bit) == 'string' - bit = escape(bit) - bit_lines = Files.get_lines(bit) - for j, line in ipairs bit_lines - if j > 1 - nomsu\append "\n" - elseif #line > 10 and nomsu\trailing_line_len! > max_line - nomsu\append "\\\n.." - - while #line > 0 - space = max_line - nomsu\trailing_line_len! - split = find(line, "[%p%s]", space) - if not split or split > space + 10 - split = space + 10 - if #line - split < 10 - split = #line - bite, line = sub(line, 1, split), sub(line, split+1, -1) - nomsu\append bite - nomsu\append "\\\n.." if #line > 0 - elseif bit.type == "Text" - add_text(nomsu, bit) - else - nomsu\append "\\" - interp_nomsu = recurse(bit, #nomsu\match('[^\n]*$')) - unless interp_nomsu\is_multiline! - if bit.type == "Var" - if type(tree[i+1]) == 'string' and not match(tree[i+1], "^[ \n\t,.:;#(){}[%]]") - interp_nomsu\parenthesize! - elseif bit.type != "List" and bit.type != "Dict" - interp_nomsu\parenthesize! - nomsu\append interp_nomsu - if interp_nomsu\is_multiline! - nomsu\append "\n.." - nomsu = NomsuCode(tree.source) - add_text(nomsu, tree) - return NomsuCode(tree.source, '"\\\n ..', nomsu, '"') - - when "List", "Dict" - assert #tree > 0 - nomsu = NomsuCode(tree.source, pop_comments(tree[1].source.start)) - for i, item in ipairs tree - nomsu\append(pop_comments(item.source.start)) if nomsu\trailing_line_len! == 0 - inline_nomsu = @tree_to_inline_nomsu(item) - item_nomsu = #tostring(inline_nomsu) <= MAX_LINE and inline_nomsu or recurse(item, #nomsu\match('[^\n]*$')) - nomsu\append item_nomsu - if i < #tree - nomsu\append((item_nomsu\is_multiline! or nomsu\trailing_line_len! + #tostring(item_nomsu) >= MAX_LINE) and '\n' or ', ') - nomsu\append pop_comments(tree.source.stop, '\n') - return if tree.type == "List" then - NomsuCode(tree.source, "[..]\n ", nomsu) - else - NomsuCode(tree.source, "{..}\n ", nomsu) - - when "DictEntry" - key, value = tree[1], tree[2] - nomsu = if key.type == "Text" and #key == 1 and is_identifier(key[1]) - NomsuCode(key.source, key[1]) - else @tree_to_inline_nomsu(key) - nomsu\parenthesize! if key.type == "Action" or key.type == "Block" - nomsu\append ": ", recurse(value, #tostring(nomsu)) - return nomsu - - when "IndexChain", "Number", "Var", "Comment", "Error" - return @tree_to_inline_nomsu tree - - else - error("Unknown type: #{tree.type}") +}) -return NomsuCompiler +return compile -- cgit v1.2.3