local lpeg = require('lpeg') local R, P, S R, P, S = lpeg.R, lpeg.P, lpeg.S local re = require('re') local utils = require('utils') local Files = require('files') local stringify, equivalent stringify, equivalent = utils.stringify, utils.equivalent local List, Dict, Text do local _obj_0 = require('containers') List, Dict, Text = _obj_0.List, _obj_0.Dict, _obj_0.Text end colors = require('consolecolors') colored = setmetatable({ }, { __index = function(_, color) return (function(msg) return colors[color] .. tostring(msg or '') .. colors.reset end) end }) local insert, remove, concat do local _obj_0 = table insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat end local unpack = unpack or table.unpack local match, sub, gsub, format, byte, find do local _obj_0 = string match, sub, gsub, format, byte, find = _obj_0.match, _obj_0.sub, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.find end local NomsuCode, LuaCode, Source do local _obj_0 = require("code_obj") NomsuCode, LuaCode, Source = _obj_0.NomsuCode, _obj_0.LuaCode, _obj_0.Source end local SyntaxTree = require("syntax_tree") local make_parser = require("parser") local pretty_error = require("pretty_errors") SOURCE_MAP = { } table.map = function(t, fn) return setmetatable((function() local _accum_0 = { } local _len_0 = 1 for _, v in ipairs(t) do _accum_0[_len_0] = fn(v) _len_0 = _len_0 + 1 end return _accum_0 end)(), getmetatable(t)) end table.fork = function(t, values) return setmetatable(values or { }, { __index = t }) end table.copy = function(t) return setmetatable((function() local _tbl_0 = { } for k, v in pairs(t) do _tbl_0[k] = v end return _tbl_0 end)(), getmetatable(t)) end local 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")) local operator_patt = S("'`~!@$^&*+=|<>?/-") ^ 1 * -1 local identifier_patt = (R("az", "AZ", "09") + P("_") + utf8_char_patt) ^ 1 * -1 local is_operator is_operator = function(s) return not not operator_patt:match(s) end local is_identifier is_identifier = function(s) return not not identifier_patt:match(s) end local 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 = (function(self) return ("\\%03d"):format(self:byte()) end) }) local inline_escape inline_escape = function(s) return inline_escaper:match(s) end local escaper = re.compile("{~ (%utf8_char / ('\\' -> '\\\\') / [\n\r\t -~] / (. -> escape))* ~}", { utf8_char = utf8_char_patt, escape = (function(self) return ("\\%03d"):format(self:byte()) end) }) local escape escape = function(s) return escaper:match(s) end local make_tree make_tree = function(tree, userdata) tree.source = Source(userdata.filename, tree.start, tree.stop) tree.start, tree.stop = nil, nil do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local t = tree[_index_0] if SyntaxTree:is_instance(t) and t.type == "Comment" then _accum_0[_len_0] = t _len_0 = _len_0 + 1 end end tree.comments = _accum_0 end if #tree.comments == 0 then tree.comments = nil end for i = #tree, 1, -1 do if SyntaxTree:is_instance(tree[i]) and tree[i].type == "Comment" then table.remove(tree, i) end end tree = SyntaxTree(tree) return tree end local Parsers = { } local max_parser_version = 4 for version = 1, max_parser_version do local peg_file = io.open("nomsu." .. tostring(version) .. ".peg") if not peg_file and package.nomsupath then for path in package.nomsupath:gmatch("[^;]+") do peg_file = io.open(path .. "/nomsu." .. tostring(version) .. ".peg") if peg_file then break end end end assert(peg_file, "could not find nomsu .peg file") local peg_contents = peg_file:read('*a') peg_file:close() Parsers[version] = make_parser(peg_contents, make_tree) end local MAX_LINE = 80 local NomsuCompiler = setmetatable({ }, { __tostring = function(self) return "Nomsu" end }) local _anon_chunk = 0 do NomsuCompiler.can_optimize = function() return false end NomsuCompiler.environment = { NOMSU_COMPILER_VERSION = 11, NOMSU_SYNTAX_VERSION = max_parser_version, next = next, unpack = unpack, setmetatable = setmetatable, coroutine = coroutine, rawequal = rawequal, getmetatable = getmetatable, pcall = pcall, error = error, package = package, os = os, require = require, tonumber = tonumber, tostring = tostring, string = string, xpcall = xpcall, module = module, print = print, loadfile = loadfile, rawset = rawset, _VERSION = _VERSION, collectgarbage = collectgarbage, rawget = rawget, rawlen = rawlen, table = table, assert = assert, dofile = dofile, loadstring = loadstring, lua_type_of = type, select = select, math = math, io = io, load = load, pairs = pairs, ipairs = ipairs, _List = List, _Dict = Dict, stringify = stringify, utils = utils, lpeg = lpeg, re = re, Files = Files, SyntaxTree = SyntaxTree, TESTS = Dict({ }), globals = Dict({ }), LuaCode = LuaCode, NomsuCode = NomsuCode, Source = Source, nomsu = NomsuCompiler, __imported = Dict({ }), __parent = nil } setmetatable(NomsuCompiler.environment, { __index = function(self, key) do local imported = rawget(self, "__imported") if imported then local ret = imported[key] if not (ret == nil) then return ret end end end do local parent = rawget(self, "__parent") if parent then return parent[key] end end end }) if _VERSION == "Lua 5.4" then NomsuCompiler.environment.ipairs = function(x) do local mt = getmetatable(x) if mt then if mt.__ipairs then return mt.__ipairs(x) end end end return ipairs(x) end end if jit or _VERSION == "Lua 5.2" then NomsuCompiler.environment.bit = require("bitops") end NomsuCompiler.fork = function(self) local f = setmetatable({ }, { __index = self }) f.environment = setmetatable({ __parent = self.environment, __imported = Dict({ }), nomsu = f, COMPILE_ACTIONS = setmetatable({ __parent = self.environment.COMPILE_ACTIONS, __imported = Dict({ }) }, getmetatable(self.environment)) }, getmetatable(self.environment)) return f end NomsuCompiler.parse = function(self, nomsu_code, source, version) if source == nil then source = nil end if version == nil then version = nil end source = source or nomsu_code.source nomsu_code = tostring(nomsu_code) if not (source) then source = Source("anonymous chunk #" .. tostring(_anon_chunk), 1, #nomsu_code) _anon_chunk = _anon_chunk + 1 end version = version or nomsu_code:match("^#![^\n]*nomsu[ ]+-V[ ]*([0-9.]+)") local syntax_version = version and tonumber(version:match("^[0-9]+")) or max_parser_version local parse = Parsers[syntax_version] or Parsers[max_parser_version] local tree = parse(nomsu_code, source.filename) local find_errors find_errors = function(t) if t.type == "Error" then return coroutine.yield(t) else for k, v in pairs(t) do local _continue_0 = false repeat if not (SyntaxTree:is_instance(v)) then _continue_0 = true break end find_errors(v) _continue_0 = true until true if not _continue_0 then break end end end end local errs do local _accum_0 = { } local _len_0 = 1 for err in coroutine.wrap(function() return find_errors(tree) end) do _accum_0[_len_0] = err _len_0 = _len_0 + 1 end errs = _accum_0 end local num_errs = #errs if num_errs > 0 then local err_strings do local _accum_0 = { } local _len_0 = 1 for i, t in ipairs(errs) do if i <= 3 then _accum_0[_len_0] = 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 }) _len_0 = _len_0 + 1 end end err_strings = _accum_0 end if num_errs > 3 then table.insert(err_strings, "\027[31;1m +" .. tostring(num_errs - #errs) .. " additional errors...\027[0m\n") end error(table.concat(err_strings, '\n\n'), 0) end return tree end NomsuCompiler.compile_error = function(self, tree, err_msg, hint) if hint == nil then hint = nil end local 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 }) return error(err_str, 0) end local add_lua_bits add_lua_bits = function(self, val_or_stmt, code, compile_actions) local cls = val_or_stmt == "value" and LuaCode.Value or LuaCode local operate_on_text operate_on_text = function(text) local lua = cls(text.source) for _index_0 = 1, #text do local bit = text[_index_0] if type(bit) == "string" then lua:append(bit) elseif bit.type == "Text" then lua:append(operate_on_text(bit)) else local bit_lua = self:compile(bit, compile_actions) if not (bit_lua.is_value) then self:compile_error(bit, "Can't use this as a string interpolation value, since it's not an expression.") end lua:append(bit_lua) end end return lua end return operate_on_text(code) end local add_lua_string_bits add_lua_string_bits = function(self, val_or_stmt, code) local cls_str = val_or_stmt == "value" and "LuaCode.Value(" or "LuaCode(" if code.type ~= "Text" then return LuaCode.Value(code.source, cls_str, tostring(code.source):as_lua(), ", ", self:compile(code), ")") end local add_bit_lua add_bit_lua = function(lua, bit_lua) local bit_leading_len = #(bit_lua:match("^[^\n]*")) lua:append(lua:trailing_line_len() + bit_leading_len > MAX_LINE and ",\n " or ", ") return lua:append(bit_lua) end local operate_on_text operate_on_text = function(text) local lua = LuaCode.Value(text.source, cls_str, tostring(text.source):as_lua()) for _index_0 = 1, #text do local bit = text[_index_0] if type(bit) == "string" then add_bit_lua(lua, bit:as_lua()) elseif bit.type == "Text" then add_bit_lua(lua, operate_on_text(bit)) else local bit_lua = self:compile(bit) if not (bit_lua.is_value) then self:compile_error(bit, "Can't use this as a string interpolation value, since it's not an expression.") end add_bit_lua(lua, bit_lua) end end lua:append(")") return lua end return operate_on_text(code) end local math_expression = re.compile([[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]]) local compile_math_expression compile_math_expression = function(self, tree, ...) local lua = LuaCode.Value(tree.source) for i, tok in ipairs(tree) do if type(tok) == 'string' then lua:append(tok) else local tok_lua = self:compile(tok, compile_actions) if not (tok_lua.is_value) then self:compile_error(tok, "Can't use this as a value in a math expression, since it's not a value.") end if tok.type == "Action" then tok_lua:parenthesize() end lua:append(tok_lua) end if i < #tree then lua:append(" ") end end return lua end NomsuCompiler.environment.COMPILE_ACTIONS = setmetatable({ __imported = Dict({ }), ["Lua"] = function(self, tree, code) return add_lua_string_bits(self, 'statements', code) end, ["Lua value"] = function(self, tree, code) return add_lua_string_bits(self, 'value', code) end, ["lua >"] = function(self, tree, code) if code.type ~= "Text" then return LuaCode(tree.source, "nomsu:run_lua(", self:compile(code), ", nomsu);") end return add_lua_bits(self, "statements", code) end, ["= lua"] = function(self, tree, code) if code.type ~= "Text" then 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"] = function(self, tree, path) if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' then if not (self:import_file(path[1])) then self:compile_error(tree, "Could not find anything to import for " .. tostring(path)) end end return LuaCode(tree.source, "nomsu:import_file(" .. tostring(self:compile(path)) .. ")") end, ["tests"] = function(self, tree) return LuaCode.Value(tree.source, "TESTS") end, ["test"] = function(self, tree, body) local test_str = table.concat((function() local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #body do local line = body[_index_0] _accum_0[_len_0] = tostring(self:tree_to_nomsu(line)) _len_0 = _len_0 + 1 end return _accum_0 end)(), "\n") return LuaCode(tree.source, "TESTS[" .. tostring(tostring(tree.source):as_lua()) .. "] = ", test_str:as_lua()) end, ["is jit"] = function(self, tree, code) return LuaCode.Value(tree.source, jit and "true" or "false") end, ["Lua version"] = function(self, tree, code) return LuaCode.Value(tree.source, _VERSION:as_lua()) end, __parent = setmetatable({ }, { __index = function(self, key) if type(key) == 'string' and math_expression:match(key) then return compile_math_expression end end }) }, getmetatable(NomsuCompiler.environment)) NomsuCompiler.import = function(self, mod) for k, v in pairs(mod) do local _continue_0 = false repeat if k == "__imported" or k == "__parent" then _continue_0 = true break end self.environment.__imported[k] = v _continue_0 = true until true if not _continue_0 then break end end for k, v in pairs(mod.COMPILE_ACTIONS) do local _continue_0 = false repeat if k == "__imported" or k == "__parent" then _continue_0 = true break end self.environment.COMPILE_ACTIONS.__imported[k] = self.environment.COMPILE_ACTIONS.__imported[k] or v _continue_0 = true until true if not _continue_0 then break end end end NomsuCompiler.import_file = function(self, path) local found = false for _, f in Files.walk(path) do if match(f, "%.lua$") or match(f, "%.nom$") or match(f, "^/dev/fd/[012]$") then found = true self:import(self:run_file(f)) end end return found end NomsuCompiler.run = function(self, to_run, compile_actions) local source = to_run.source or Source(to_run, 1, #to_run) if type(source) == 'string' then source = Source:from_string(source) end if not Files.read(source.filename) then Files.spoof(source.filename, to_run) end local tree if SyntaxTree:is_instance(to_run) then tree = to_run else tree = self:parse(to_run, source) end if tree == nil then return nil end if tree.type ~= "FileChunks" then tree = { tree } end local ret = nil local all_lua = { } for _index_0 = 1, #tree do local chunk = tree[_index_0] local lua = self:compile(chunk, compile_actions):as_statements("return ") lua:declare_locals() lua:prepend("-- File: " .. tostring(source.filename:gsub("\n.*", "...")) .. "\n") insert(all_lua, tostring(lua)) ret = self:run_lua(lua) end return ret end local _running_files = { } local _loaded_files = { } NomsuCompiler.run_file = function(self, filename, compile_actions) if _loaded_files[filename] then return _loaded_files[filename] end for i, running in ipairs(_running_files) do if running == filename then local loop do local _accum_0 = { } local _len_0 = 1 for j = i, #_running_files do _accum_0[_len_0] = _running_files[j] _len_0 = _len_0 + 1 end loop = _accum_0 end insert(loop, filename) error("Circular import, this loops forever: " .. tostring(concat(loop, " -> ")) .. "...") end end insert(_running_files, filename) local mod = self:fork() local ret = mod.environment mod.from_file = filename if match(filename, "%.lua$") then local file = assert(Files.read(filename), "Could not find file: " .. tostring(filename)) ret = mod:run_lua(LuaCode(Source(filename, 1, #file), file)) or ret elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") then local ran_lua if self.can_optimize(filename) then local lua_filename = gsub(filename, "%.nom$", ".lua") do local file = Files.read(lua_filename) if file then ret = mod:run_lua(LuaCode(Source(lua_filename, 1, #file), file)) or ret ran_lua = true end end end if not (ran_lua) then local file = Files.read(filename) if not file then error("Tried to run file that does not exist: " .. tostring(filename)) end ret = mod:run(NomsuCode(Source(filename, 1, #file), file), compile_actions) or ret end else error("Invalid filetype for " .. tostring(filename), 0) end _loaded_files[filename] = ret remove(_running_files) return ret end NomsuCompiler.run_lua = function(self, lua) local lua_string = tostring(lua) local run_lua_fn, err = load(lua_string, tostring(source or lua.source), "t", self.environment) if not run_lua_fn then local line_numbered_lua = concat((function() local _accum_0 = { } local _len_0 = 1 for i, line in ipairs(Files.get_lines(lua_string)) do _accum_0[_len_0] = format("%3d|%s", i, line) _len_0 = _len_0 + 1 end return _accum_0 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 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 = { } local file = Files.read(source.filename) if not file then error("Failed to find file: " .. tostring(source.filename)) end local nomsu_str = file:sub(source.start, source.stop) assert(type(nomsu_str) == 'string') local lua_line = 1 local nomsu_line = Files.get_line_number(file, source.start) local map_sources map_sources = function(s) if type(s) == 'string' then for nl in s:gmatch("\n") do map[lua_line] = map[lua_line] or nomsu_line lua_line = lua_line + 1 end else if s.source and s.source.filename == source.filename then nomsu_line = Files.get_line_number(file, s.source.start) end local _list_0 = s.bits for _index_0 = 1, #_list_0 do local b = _list_0[_index_0] map_sources(b) end end end map_sources(lua) map[lua_line] = map[lua_line] or nomsu_line map[0] = 0 SOURCE_MAP[source_key] = map end return run_lua_fn() end NomsuCompiler.compile = function(self, tree, compile_actions, force_value) if force_value == nil then force_value = false end compile_actions = compile_actions or self.environment.COMPILE_ACTIONS if tree.version then do local get_version = self[("Nomsu version"):as_lua_id()] if get_version then do local upgrade = self[("1 upgraded from 2 to"):as_lua_id()] if upgrade then tree = upgrade(tree, tree.version, get_version()) end end end end end local _exp_0 = tree.type if "Action" == _exp_0 then local stub = tree.stub local compile_action = compile_actions[stub] if compile_action and not tree.target then local args do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local arg = tree[_index_0] if type(arg) ~= "string" then _accum_0[_len_0] = arg _len_0 = _len_0 + 1 end end args = _accum_0 end local ret = compile_action(self, tree, unpack(args)) if ret == nil then local info = debug.getinfo(compile_action, "S") local filename = Source:from_string(info.source).filename self:compile_error(tree, "The compile-time action here (" .. tostring(stub) .. ") failed to return any value.", "Look at the implementation of (" .. tostring(stub) .. ") in " .. tostring(filename) .. ":" .. tostring(info.linedefined) .. " and make sure it's returning something.") end if SyntaxTree:is_instance(ret) then if ret == tree then local info = debug.getinfo(compile_action, "S") local filename = Source:from_string(info.source).filename self:compile_error(tree, "The compile-time action here (" .. tostring(stub) .. ") is producing an endless loop.", "Look at the implementation of (" .. tostring(stub) .. ") in " .. tostring(filename) .. ":" .. tostring(info.linedefined) .. " and make sure it's not just returning the original tree.") end return self:compile(ret, compile_actions) end return ret end local lua = LuaCode.Value(tree.source) if tree.target then local target_lua = self:compile(tree.target, compile_actions) if tostring(target_lua):match("^%(.*%)$") or tostring(target_lua):match("^[_a-zA-Z][_a-zA-Z0-9]*$") then lua:append(target_lua, ":") else lua:append("(", target_lua, "):") end end lua:append((stub):as_lua_id(), "(") local args = { } for i, tok in ipairs(tree) do local _continue_0 = false repeat if type(tok) == "string" then _continue_0 = true break end local arg_lua = self:compile(tok, compile_actions, true) if not (arg_lua.is_value) then if tok.type == "Block" then self:compile_error(tok, "Can't compile action (" .. tostring(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" then self:compile_error(tok, "Can't use this as an argument to (" .. tostring(stub) .. "), since it's not an expression, it produces: " .. tostring(tostring(arg_lua)), "Check the implementation of (" .. tostring(tok.stub) .. ") to see if it is actually meant to produce an expression.") else self:compile_error(tok, "Can't use this as an argument to (" .. tostring(stub) .. "), since it's not an expression, it produces: " .. tostring(tostring(arg_lua))) end end insert(args, arg_lua) _continue_0 = true until true if not _continue_0 then break end end lua:concat_append(args, ", ") lua:append(")") return lua elseif "EscapedNomsu" == _exp_0 then local lua = LuaCode.Value(tree.source, "SyntaxTree{") local needs_comma, i = false, 1 local as_lua as_lua = function(x) if type(x) == 'number' then return tostring(x) elseif SyntaxTree:is_instance(x) then return self:compile(x, compile_actions) else return x:as_lua() end end for k, v in pairs((SyntaxTree:is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) do if needs_comma then lua:append(", ") else needs_comma = true end if k == i then i = i + 1 elseif type(k) == 'string' and match(k, "[_a-zA-Z][_a-zA-Z0-9]*") then lua:append(k, "= ") else lua:append("[", as_lua(k), "]= ") end if k == "source" then lua:append(tostring(v):as_lua()) else lua:append(as_lua(v)) end end lua:append("}") return lua elseif "Block" == _exp_0 then if not force_value then local lua = LuaCode(tree.source) lua:concat_append((function() local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local line = tree[_index_0] _accum_0[_len_0] = self:compile(line, compile_actions):as_statements() _len_0 = _len_0 + 1 end return _accum_0 end)(), "\n") return lua else local lua = LuaCode.Value(tree.source) local values do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local line = tree[_index_0] _accum_0[_len_0] = self:compile(line) _len_0 = _len_0 + 1 end values = _accum_0 end local all_values = true for _index_0 = 1, #values do local v = values[_index_0] all_values = all_values and v.is_value end if all_values then if #values == 1 then return values[1] end lua:append("(") lua:concat_append(values, " and nil or ") lua:append(")") else lua:append("((function()") for i, v in ipairs(values) do if v.is_value then v = v:as_statements(i == #values and 'return ' or '') end lua:append("\n ", v) end lua:append("\nend)())") end return lua end elseif "Text" == _exp_0 then local lua = LuaCode.Value(tree.source) local string_buffer = "" for i, bit in ipairs(tree) do local _continue_0 = false repeat if type(bit) == "string" then string_buffer = string_buffer .. bit _continue_0 = true break end if string_buffer ~= "" then if #lua.bits > 0 then lua:append("..") end lua:append(string_buffer:as_lua()) string_buffer = "" end local bit_lua = self:compile(bit, compile_actions) if not (bit_lua.is_value) then local src = ' ' .. gsub(tostring(self:compile(bit, compile_actions)), '\n', '\n ') local line = tostring(bit.source.filename) .. ":" .. tostring(Files.get_line_number(Files.read(bit.source.filename), bit.source.start)) self:compile_error(bit, "Can't this as a string interpolation value, since it's not an expression.") end if #lua.bits > 0 then lua:append("..") end if bit.type ~= "Text" then bit_lua = LuaCode.Value(bit.source, "stringify(", bit_lua, ")") end lua:append(bit_lua) _continue_0 = true until true if not _continue_0 then break end end if string_buffer ~= "" or #lua.bits == 0 then if #lua.bits > 0 then lua:append("..") end lua:append(string_buffer:as_lua()) end if #lua.bits > 1 then lua:parenthesize() end return lua elseif "List" == _exp_0 then local lua = LuaCode.Value(tree.source, "_List{") lua:concat_append((function() local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local e = tree[_index_0] _accum_0[_len_0] = self:compile(e, compile_actions) _len_0 = _len_0 + 1 end return _accum_0 end)(), ", ", ",\n ") lua:append("}") return lua elseif "Dict" == _exp_0 then local lua = LuaCode.Value(tree.source, "_Dict{") lua:concat_append((function() local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local e = tree[_index_0] _accum_0[_len_0] = self:compile(e, compile_actions) _len_0 = _len_0 + 1 end return _accum_0 end)(), ", ", ",\n ") lua:append("}") return lua elseif "DictEntry" == _exp_0 then local key, value = tree[1], tree[2] local key_lua = self:compile(key, compile_actions) if not (key_lua.is_value) then self:compile_error(tree[1], "Can't use this as a dict key, since it's not an expression.") end local value_lua = value and self:compile(value, compile_actions) or LuaCode.Value(key.source, "true") if not (value_lua.is_value) then self:compile_error(tree[2], "Can't use this as a dict value, since it's not an expression.") end local key_str = match(tostring(key_lua), [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=]) if key_str and key_str:is_lua_id() then return LuaCode(tree.source, key_str, "=", value_lua) elseif sub(tostring(key_lua), 1, 1) == "[" then return LuaCode(tree.source, "[ ", key_lua, "]=", value_lua) else return LuaCode(tree.source, "[", key_lua, "]=", value_lua) end elseif "IndexChain" == _exp_0 then local lua = self:compile(tree[1], compile_actions) if not (lua.is_value) then self:compile_error(tree[1], "Can't index into this, since it's not an expression.") end local first_char = sub(tostring(lua), 1, 1) if first_char == "{" or first_char == '"' or first_char == "[" then lua:parenthesize() end for i = 2, #tree do local key = tree[i] local key_lua = self:compile(key, compile_actions) if not (key_lua.is_value) then self:compile_error(key, "Can't use this as an index, since it's not an expression.") end local key_lua_str = tostring(key_lua) local lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") if lua_id and lua_id:is_lua_id() then lua:append("." .. tostring(lua_id)) elseif sub(key_lua_str, 1, 1) == '[' then lua:append("[ ", key_lua, " ]") else lua:append("[", key_lua, "]") end end return lua elseif "Number" == _exp_0 then return LuaCode.Value(tree.source, tostring(tree[1])) elseif "Var" == _exp_0 then return LuaCode.Value(tree.source, (tree[1]):as_lua_id()) elseif "FileChunks" == _exp_0 then return error("Can't convert FileChunks to a single block of lua, since each chunk's " .. "compilation depends on the earlier chunks") elseif "Comment" == _exp_0 then return LuaCode(tree.source, "") elseif "Error" == _exp_0 then return error("Can't compile errors") else return error("Unknown type: " .. tostring(tree.type)) end end NomsuCompiler.tree_to_inline_nomsu = function(self, tree, parenthesize_blocks, check, len) if parenthesize_blocks == nil then parenthesize_blocks = false end if check == nil then check = nil end if len == nil then len = 0 end local recurse recurse = function(tree, nomsu, parenthesize_blocks) if nomsu == nil then nomsu = nil end if parenthesize_blocks == nil then parenthesize_blocks = false end return self:tree_to_inline_nomsu(tree, parenthesize_blocks, check, len + (nomsu and #tostring(nomsu) or 0)) end local _exp_0 = tree.type if "FileChunks" == _exp_0 then return error("Can't inline a FileChunks") elseif "Comment" == _exp_0 then return NomsuCode(tree.source, "") elseif "Error" == _exp_0 then return error("Can't compile errors") elseif "Action" == _exp_0 then local nomsu = NomsuCode(tree.source) if tree.target then local inline_target = self:tree_to_inline_nomsu(tree.target) if tree.target.type == "Action" then inline_target:parenthesize() end nomsu:append(inline_target, "::") end for i, bit in ipairs(tree) do if type(bit) == "string" then local clump_words = (type(tree[i - 1]) == 'string' and is_operator(bit) ~= is_operator(tree[i - 1])) if i > 1 and not clump_words then nomsu:append(" ") end nomsu:append(bit) else local arg_nomsu = recurse(bit, nomsu, parenthesize_blocks or (i == 1 or i < #tree)) if not (arg_nomsu:match("^:") or i == 1) then nomsu:append(" ") end if bit.type == "Action" then arg_nomsu:parenthesize() end nomsu:append(arg_nomsu) end if check then check(len, nomsu, tree) end end return nomsu elseif "EscapedNomsu" == _exp_0 then local inner_nomsu = recurse(tree[1]) if not (tree[1].type == "List" or tree[1].type == "Dict" or tree[1].type == "Var") then inner_nomsu:parenthesize() end local nomsu = NomsuCode(tree.source, "\\", inner_nomsu) if check then check(len, nomsu, tree) end return nomsu elseif "Block" == _exp_0 then local nomsu = NomsuCode(tree.source, ":") if check then check(len, nomsu, tree) end for i, line in ipairs(tree) do nomsu:append(i == 1 and " " or "; ") nomsu:append(recurse(line, nomsu, i == 1 or i < #tree)) if check then check(len, nomsu, tree) end end if #tree > 1 or parenthesize_blocks then nomsu:parenthesize() end return nomsu elseif "Text" == _exp_0 then local add_text add_text = function(nomsu, tree) for i, bit in ipairs(tree) do if type(bit) == 'string' then local escaped = inline_escape(bit) nomsu:append(inline_escape(bit)) elseif bit.type == "Text" then add_text(nomsu, bit) else local interp_nomsu = recurse(bit, nomsu) if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" then interp_nomsu:parenthesize() elseif bit.type == "Var" and type(tree[i + 1]) == 'string' and not match(tree[i + 1], "^[ \n\t,.:;#(){}[%]]") then interp_nomsu:parenthesize() end nomsu:append("\\", interp_nomsu) end if check then check(len, nomsu, tree) end end end local nomsu = NomsuCode(tree.source) add_text(nomsu, tree) return NomsuCode(tree.source, '"', nomsu, '"') elseif "List" == _exp_0 or "Dict" == _exp_0 then local nomsu = NomsuCode(tree.source, (tree.type == "List" and "[" or "{")) for i, item in ipairs(tree) do if i > 1 then nomsu:append(", ") end nomsu:append(recurse(item, nomsu)) if check then check(len, nomsu, tree) end end nomsu:append(tree.type == "List" and "]" or "}") return nomsu elseif "DictEntry" == _exp_0 then local key, value = tree[1], tree[2] local nomsu if key.type == "Text" and #key == 1 and is_identifier(key[1]) then nomsu = NomsuCode(key.source, key[1]) else nomsu = recurse(key) end if key.type == "Action" or key.type == "Block" then nomsu:parenthesize() end assert(value.type ~= "Block", "Didn't expect to find a Block as a value in a dict") nomsu:append(":") if value then local value_nomsu = recurse(value, nomsu) if value.type == "Block" then value_nomsu:parenthesize() end nomsu:append(value_nomsu) end if check then check(len, nomsu, tree) end return nomsu elseif "IndexChain" == _exp_0 then local nomsu = NomsuCode(tree.source) for i, bit in ipairs(tree) do if i > 1 then nomsu:append(".") end local bit_nomsu if i > 1 and bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string' and is_identifier(bit[1]) then bit_nomsu = bit[1] else bit_nomsu = recurse(bit, nomsu) end assert(bit.type ~= "Block") if bit.type == "Action" or bit.type == "IndexChain" or (bit.type == "Number" and i < #tree) then bit_nomsu:parenthesize() end nomsu:append(bit_nomsu) if check then check(len, nomsu, tree) end end return nomsu elseif "Number" == _exp_0 then return NomsuCode(tree.source, tostring(tree[1])) elseif "Var" == _exp_0 then return NomsuCode(tree.source, "%", tree[1]) else return error("Unknown type: " .. tostring(tree.type)) end end NomsuCompiler.tree_to_nomsu = function(self, tree, pop_comments) if pop_comments == nil then pop_comments = nil end if not (pop_comments) then local comment_set = { } local find_comments find_comments = function(t) if t.comments and t.source.filename == tree.source.filename then local _list_0 = t.comments for _index_0 = 1, #_list_0 do local c = _list_0[_index_0] comment_set[c] = true end end local _list_0 = t for _index_0 = 1, #_list_0 do local x = _list_0[_index_0] if SyntaxTree:is_instance(x) then find_comments(x) end end end find_comments(tree) local comments do local _accum_0 = { } local _len_0 = 1 for c in pairs(comment_set) do _accum_0[_len_0] = c _len_0 = _len_0 + 1 end comments = _accum_0 end table.sort(comments, function(a, b) return (a.source.start > b.source.start) end) pop_comments = function(pos, prefix, suffix) if prefix == nil then prefix = '' end if suffix == nil then suffix = '' end local nomsu = NomsuCode(tree.source) for i = #comments, 1, -1 do if comments[i].source.start > pos then break end local comment comment, comments[i] = comments[i][1], nil nomsu:append("#" .. (gsub(comment, "\n", "\n ")) .. "\n") if comment:match("^\n.") then nomsu:append("\n") end end if #nomsu.bits == 0 then return '' end if not (prefix == '') then nomsu:prepend(prefix) end if not (suffix == '') then nomsu:append(suffix) end return nomsu end end local recurse recurse = function(t, pos) if pos == nil then pos = 0 end if type(pos) ~= 'number' then pos = #tostring(pos):match("[ ]*([^\n]*)$") end local space = MAX_LINE - pos local inline for prefix, nomsu, tree in coroutine.wrap(function() inline = self:tree_to_inline_nomsu(t, false, coroutine.yield) end) do local len = #tostring(nomsu) if prefix + len > MAX_LINE then break end if tree.type == "Block" and (#tree > 1 or len > 20) then break end if tree.type == "Text" then local check_for_nl check_for_nl = function(tree) local found_nl = false for i, b in ipairs(tree) do if type(b) ~= 'string' and b.type == "Text" and check_for_nl(b) then return true end if i == 1 and type(b) == 'string' then b = b:match('^[\n]*(.*)') end found_nl = found_nl or (type(b) == 'string' and b:match('\n')) if found_nl and (type(b) ~= 'string' or b:match('[^\n]')) then return true end end end if check_for_nl(tree) then break end end end if inline and #tostring(inline) <= space then return inline end local indented = self:tree_to_nomsu(t, pop_comments, space) if t.type == "Action" and not (tree.type == "Block" or tree.type == "FileChunks") then indented = NomsuCode(t.source, "(..)\n ", pop_comments(t.source.start), indented) end return indented end local _exp_0 = tree.type if "FileChunks" == _exp_0 then local nomsu = NomsuCode(tree.source, pop_comments(tree.source.start)) local should_clump should_clump = function(prev_line, line) if prev_line.type == "Action" and line.type == "Action" then if prev_line.stub == "use" then return line.stub == "use" end if prev_line.stub == "test" then return true end if line.stub == "test" then return false end end return not recurse(prev_line):is_multiline() end for chunk_no, chunk in ipairs(tree) do if chunk_no > 1 then nomsu:append("\n\n" .. tostring(("~"):rep(80)) .. "\n\n") end nomsu:append(pop_comments(chunk.source.start)) if chunk.type == "Block" then for line_no, line in ipairs(chunk) do if line_no > 1 then if should_clump(chunk[line_no - 1], line) then nomsu:append("\n", pop_comments(line.source.start, '\n')) else nomsu:append("\n\n", pop_comments(line.source.start)) end end nomsu:append(self:tree_to_nomsu(line, pop_comments)) end nomsu:append(pop_comments(chunk.source.stop, '\n')) else nomsu:append(recurse(chunk)) end end nomsu:append(pop_comments(tree.source.stop, '\n')) if not (nomsu:match("\n$")) then nomsu:append('\n') end return nomsu elseif "Action" == _exp_0 then local pos, next_space = tree.source.start, '' local nomsu = NomsuCode(tree.source, pop_comments(pos)) if tree.target then if tree.target.type == "Block" then nomsu:append(recurse(tree.target, #nomsu:match('[^\n]*$'))) pos = tree.target.source.stop next_space = inline and "::" or "\n..::" else local target_nomsu = recurse(tree.target, #nomsu:match('[^\n]*$')) if tree.target.type == "Action" and not target_nomsu:is_multiline() then target_nomsu:parenthesize() end nomsu:append(target_nomsu) pos = tree.target.source.stop next_space = target_nomsu:is_multiline() and "\n..::" or "::" end end for i, bit in ipairs(tree) do if next_space == "\n.." then nomsu:append("\n", pop_comments(pos), '..') next_space = "" elseif next_space == " " and nomsu:trailing_line_len() > MAX_LINE then nomsu:append(" \\\n", pop_comments(pos), '..') next_space = "" end if type(bit) == "string" then if not (type(tree[i - 1]) == 'string' and is_operator(tree[i - 1]) ~= is_operator(bit)) then nomsu:append(next_space) end nomsu:append(bit) next_space = ' ' elseif bit.type == "Block" then nomsu:append(recurse(bit, #nomsu:match('[^\n]*$'))) pos = bit.source.stop next_space = inline and " " or "\n.." else nomsu:append(next_space) local bit_nomsu = recurse(bit, #nomsu:match('[^\n]*$')) if bit.type == "Action" and not bit_nomsu:is_multiline() then bit_nomsu:parenthesize() end nomsu:append(bit_nomsu) pos = bit.source.stop next_space = bit_nomsu:is_multiline() and "\n.." or " " end end nomsu:append(pop_comments(tree.source.stop, '\n')) return nomsu elseif "EscapedNomsu" == _exp_0 then local val_nomsu = recurse(tree[1], 1) if tree[1].type == "Action" and not val_nomsu:is_multiline() then val_nomsu:parenthesize() end return NomsuCode(tree.source, "\\", val_nomsu) elseif "Block" == _exp_0 then local nomsu = NomsuCode(tree.source, pop_comments(tree.source.start)) for i, line in ipairs(tree) do nomsu:append(pop_comments(line.source.start, i > 1 and '\n' or '')) local line_nomsu = recurse(line) nomsu:append(line_nomsu) if i < #tree then nomsu:append(line_nomsu:match('\n[^\n]*\n') and "\n\n" or "\n") end end nomsu:append(pop_comments(tree.source.stop, '\n')) return NomsuCode(tree.source, ":\n ", nomsu) elseif "Text" == _exp_0 then local max_line = math.floor(1.5 * MAX_LINE) local add_text add_text = function(nomsu, tree) for i, bit in ipairs(tree) do if type(bit) == 'string' then bit = escape(bit) local bit_lines = Files.get_lines(bit) for j, line in ipairs(bit_lines) do if j > 1 then nomsu:append("\n") elseif #line > 10 and nomsu:trailing_line_len() > max_line then nomsu:append("\\\n..") end while #line > 0 do local space = max_line - nomsu:trailing_line_len() local split = find(line, "[%p%s]", space) if not split or split > space + 10 then split = space + 10 end if #line - split < 10 then split = #line end local bite bite, line = sub(line, 1, split), sub(line, split + 1, -1) nomsu:append(bite) if #line > 0 then nomsu:append("\\\n..") end end end elseif bit.type == "Text" then add_text(nomsu, bit) else nomsu:append("\\") local interp_nomsu = recurse(bit, #nomsu:match('[^\n]*$')) if not (interp_nomsu:is_multiline()) then if bit.type == "Var" then if type(tree[i + 1]) == 'string' and not match(tree[i + 1], "^[ \n\t,.:;#(){}[%]]") then interp_nomsu:parenthesize() end elseif bit.type ~= "List" and bit.type ~= "Dict" then interp_nomsu:parenthesize() end end nomsu:append(interp_nomsu) if interp_nomsu:is_multiline() then nomsu:append("\n..") end end end end local nomsu = NomsuCode(tree.source) add_text(nomsu, tree) return NomsuCode(tree.source, '"\\\n ..', nomsu, '"') elseif "List" == _exp_0 or "Dict" == _exp_0 then assert(#tree > 0) local nomsu = NomsuCode(tree.source, pop_comments(tree[1].source.start)) for i, item in ipairs(tree) do if nomsu:trailing_line_len() == 0 then nomsu:append(pop_comments(item.source.start)) end local inline_nomsu = self:tree_to_inline_nomsu(item) local item_nomsu = #tostring(inline_nomsu) <= MAX_LINE and inline_nomsu or recurse(item, #nomsu:match('[^\n]*$')) nomsu:append(item_nomsu) if i < #tree then nomsu:append((item_nomsu:is_multiline() or nomsu:trailing_line_len() + #tostring(item_nomsu) >= MAX_LINE) and '\n' or ', ') end end nomsu:append(pop_comments(tree.source.stop, '\n')) if tree.type == "List" then return NomsuCode(tree.source, "[..]\n ", nomsu) else return NomsuCode(tree.source, "{..}\n ", nomsu) end elseif "DictEntry" == _exp_0 then local key, value = tree[1], tree[2] local nomsu if key.type == "Text" and #key == 1 and is_identifier(key[1]) then nomsu = NomsuCode(key.source, key[1]) else nomsu = self:tree_to_inline_nomsu(key) end if key.type == "Action" or key.type == "Block" then nomsu:parenthesize() end nomsu:append(": ", recurse(value, #tostring(nomsu))) return nomsu elseif "IndexChain" == _exp_0 or "Number" == _exp_0 or "Var" == _exp_0 or "Comment" == _exp_0 or "Error" == _exp_0 then return self:tree_to_inline_nomsu(tree) else return error("Unknown type: " .. tostring(tree.type)) end end end return NomsuCompiler