diff options
Diffstat (limited to 'nomsu_compiler.lua')
| -rw-r--r-- | nomsu_compiler.lua | 1148 |
1 files changed, 1148 insertions, 0 deletions
diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua new file mode 100644 index 0000000..d093ff5 --- /dev/null +++ b/nomsu_compiler.lua @@ -0,0 +1,1148 @@ +local lpeg = require('lpeg') +local re = require('re') +lpeg.setmaxstack(10000) +local utils = require('utils') +local repr, stringify, equivalent +repr, stringify, equivalent = utils.repr, utils.stringify, utils.equivalent +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, rep, gsub, format, byte, find +do + local _obj_0 = string + match, sub, rep, gsub, format, byte, match, find = _obj_0.match, _obj_0.sub, _obj_0.rep, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.match, _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 AST = require("nomsu_tree") +local parse = require("parser") +SOURCE_MAP = { } +string.as_lua_id = function(str) + local argnum = 0 + str = gsub(str, "x([0-9A-F][0-9A-F])", "x\0%1") + str = gsub(str, "%W", function(c) + if c == ' ' then + return '_' + elseif c == '%' then + argnum = argnum + 1 + return tostring(argnum) + else + return format("x%02X", byte(c)) + end + end) + return '_' .. str +end +table.map = function(self, fn) + local _accum_0 = { } + local _len_0 = 1 + for _, v in ipairs(self) do + _accum_0[_len_0] = fn(v) + _len_0 = _len_0 + 1 + end + return _accum_0 +end +table.fork = function(t, values) + return setmetatable(values or { }, { + __index = t + }) +end +FILE_CACHE = setmetatable({ }, { + __index = function(self, filename) + local file = io.open(filename) + if not (file) then + return nil + end + local contents = file:read("*a") + file:close() + self[filename] = contents + return contents + end +}) +local iterate_single +iterate_single = function(item, prev) + if item == prev then + return nil + else + return item + end +end +all_files = function(path) + if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") then + return iterate_single, path + end + path = gsub(path, "\\", "\\\\") + path = gsub(path, "`", "") + path = gsub(path, '"', '\\"') + path = gsub(path, "$", "") + return coroutine.wrap(function() + local f = io.popen('find -L "' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"') + for line in f:lines() do + coroutine.yield(line) + end + local success = f:close() + if not (success) then + return error("Invalid file path: " .. tostring(path)) + end + end) +end +local line_counter = re.compile([[ lines <- {| line (%nl line)* |} + line <- {} (!%nl .)* +]], { + nl = lpeg.P("\r") ^ -1 * lpeg.P("\n") +}) +local get_lines = re.compile([[ lines <- {| line (%nl line)* |} + line <- {[^%nl]*} +]], { + nl = lpeg.P("\r") ^ -1 * lpeg.P("\n") +}) +LINE_STARTS = setmetatable({ }, { + __mode = "k", + __index = function(self, k) + if type(k) ~= 'string' then + k = tostring(k) + do + local v = rawget(self, k) + if v then + return v + end + end + end + local line_starts = line_counter:match(k) + self[k] = line_starts + return line_starts + end +}) +pos_to_line = function(str, pos) + local line_starts = LINE_STARTS[str] + local lo, hi = 1, #line_starts + while lo <= hi do + local mid = math.floor((lo + hi) / 2) + if line_starts[mid] > pos then + hi = mid - 1 + else + lo = mid + 1 + end + end + return hi +end +do + local STRING_METATABLE = getmetatable("") + STRING_METATABLE.__add = function(self, other) + return self .. stringify(other) + end + STRING_METATABLE.__index = function(self, i) + local ret = string[i] + if ret ~= nil then + return ret + end + if type(i) == 'number' then + return sub(self, i, i) + elseif type(i) == 'table' then + return sub(self, i[1], i[2]) + end + end +end +local _list_mt = { + __eq = equivalent, + __tostring = function(self) + return "[" .. concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self do + local b = self[_index_0] + _accum_0[_len_0] = repr(b) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") .. "]" + end +} +local list +list = function(t) + return setmetatable(t, _list_mt) +end +local _dict_mt = { + __eq = equivalent, + __tostring = function(self) + return "{" .. concat((function() + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(self) do + _accum_0[_len_0] = tostring(repr(k)) .. ": " .. tostring(repr(v)) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") .. "}" + end +} +local dict +dict = function(t) + return setmetatable(t, _dict_mt) +end +local MAX_LINE = 80 +local NomsuCompiler = setmetatable({ }, { + __index = function(self, k) + do + local _self = rawget(self, "self") + if _self then + return _self[k] + else + return nil + end + end + end +}) +do + NomsuCompiler._ENV = NomsuCompiler + NomsuCompiler.nomsu = NomsuCompiler + NomsuCompiler.parse = function(self, ...) + return parse(...) + end + local to_add = { + repr = repr, + stringify = stringify, + utils = utils, + lpeg = lpeg, + re = re, + 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, + type = type, + select = select, + debug = debug, + math = math, + io = io, + load = load, + pairs = pairs, + ipairs = ipairs, + list = list, + dict = dict + } + for k, v in pairs(to_add) do + NomsuCompiler[k] = v + end + for k, v in pairs(AST) do + NomsuCompiler[k] = v + end + NomsuCompiler.LuaCode = LuaCode + NomsuCompiler.NomsuCode = NomsuCode + NomsuCompiler.Source = Source + NomsuCompiler.ALIASES = setmetatable({ }, { + __mode = "k" + }) + NomsuCompiler.LOADED = { } + NomsuCompiler.AST = AST + NomsuCompiler.compile_error = function(self, tok, err_format_string, ...) + local file = FILE_CACHE[tok.source.filename] + local line_no = pos_to_line(file, tok.source.start) + local line_start = LINE_STARTS[file][line_no] + local src = colored.dim(file:sub(line_start, tok.source.start - 1)) + src = src .. colored.underscore(colored.bright(colored.red(file:sub(tok.source.start, tok.source.stop - 1)))) + local end_of_line = (LINE_STARTS[file][pos_to_line(file, tok.source.stop) + 1] or 0) - 1 + src = src .. colored.dim(file:sub(tok.source.stop, end_of_line - 1)) + src = ' ' .. src:gsub('\n', '\n ') + local err_msg = err_format_string:format(src, ...) + return error(tostring(tok.source.filename) .. ":" .. tostring(line_no) .. ": " .. err_msg, 0) + end + local math_expression = re.compile([[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]]) + local add_lua_bits + add_lua_bits = function(self, lua, code) + for _index_0 = 1, #code do + local bit = code[_index_0] + if type(bit) == "string" then + lua:append(bit) + else + local bit_lua = self:compile(bit) + if not (bit_lua.is_value) then + self:compile_error(bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression.") + end + lua:append(bit_lua) + end + end + return lua + end + local add_lua_string_bits + add_lua_string_bits = function(self, lua, code) + local line_len = 0 + if code.type ~= "Text" then + lua:append(", ", self:compile(code)) + return + end + for _index_0 = 1, #code do + local bit = code[_index_0] + local bit_lua + if type(bit) == "string" then + bit_lua = repr(bit) + else + bit_lua = self:compile(bit) + if not (bit_lua.is_value) then + self:compile_error(bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression.") + end + bit_lua = bit_lua + end + line_len = line_len + #tostring(bit_lua) + if line_len > MAX_LINE then + lua:append(",\n ") + line_len = 4 + else + lua:append(", ") + end + lua:append(bit_lua) + end + end + NomsuCompiler.COMPILE_ACTIONS = setmetatable({ + ["# compile math expr #"] = 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) + if not (tok_lua.is_value) then + self:compile_error(tok, "Non-expression value inside math expression:\n%s") + 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, + ["Lua %"] = function(self, tree, _code) + local lua = LuaCode.Value(_code.source, "LuaCode(", repr(tostring(_code.source))) + add_lua_string_bits(self, lua, _code) + lua:append(")") + return lua + end, + ["Lua value %"] = function(self, tree, _code) + local lua = LuaCode.Value(_code.source, "LuaCode.Value(", repr(tostring(_code.source))) + add_lua_string_bits(self, lua, _code) + lua:append(")") + return lua + end, + ["lua > %"] = function(self, tree, _code) + if _code.type ~= "Text" then + return LuaCode(tree.source, "nomsu:run_lua(", self:compile(_code), ");") + end + return add_lua_bits(self, LuaCode(tree.source), _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 '))") + end + return add_lua_bits(self, LuaCode.Value(tree.source), _code) + end, + ["use %"] = function(self, tree, _path) + if not (_path.type == 'Text' and #_path == 1 and type(_path[1]) == 'string') then + return LuaCode(tree.source, "nomsu:run_file(" .. tostring(self:compile(_path)) .. ");") + end + local path = _path[1] + self:run_file(path) + return LuaCode(tree.source, "nomsu:run_file(" .. tostring(repr(path)) .. ");") + end + }, { + __index = function(self, stub) + if math_expression:match(stub) then + return self["# compile math expr #"] + end + end + }) + NomsuCompiler.run = function(self, to_run, source) + if source == nil then + source = nil + end + local tree + if AST.is_syntax_tree(to_run) then + tree = tree + else + tree = self:parse(to_run, source or to_run.source) + end + if tree == nil then + return nil + end + if tree.type == "FileChunks" then + local ret = nil + local all_lua = { } + for _index_0 = 1, #tree do + local chunk = tree[_index_0] + local lua = self:compile(chunk):as_statements() + lua:declare_locals() + lua:prepend("-- File: " .. tostring(chunk.source or "") .. "\n") + insert(all_lua, tostring(lua)) + ret = self:run_lua(lua) + end + if self.on_compile then + self.on_compile(concat(all_lua, "\n"), (source or to_run.source).filename) + end + return ret + else + local lua = self:compile(tree, compile_actions):as_statements() + lua:declare_locals() + lua:prepend("-- File: " .. tostring(source or to_run.source or "") .. "\n") + if self.on_compile then + self.on_compile(lua, (source or to_run.source).filename) + end + return self:run_lua(lua) + end + end + local _running_files = { } + NomsuCompiler.run_file = function(self, filename) + if self.LOADED[filename] then + return self.LOADED[filename] + end + local ret = nil + for filename in all_files(filename) do + local _continue_0 = false + repeat + do + ret = self.LOADED[filename] + if ret then + _continue_0 = true + break + end + 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) + if match(filename, "%.lua$") then + local file = assert(FILE_CACHE[filename], "Could not find file: " .. tostring(filename)) + ret = self:run_lua(file, Source(filename, 1, #file)) + elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") then + local ran_lua + if not self.skip_precompiled then + local lua_filename = gsub(filename, "%.nom$", ".lua") + do + local file = FILE_CACHE[lua_filename] + if file then + ret = self:run_lua(file, Source(lua_filename, 1, #file)) + ran_lua = true + end + end + end + if not (ran_lua) then + local file = file or FILE_CACHE[filename] + if not file then + error("File does not exist: " .. tostring(filename), 0) + end + ret = self:run(file, Source(filename, 1, #file)) + end + else + error("Invalid filetype for " .. tostring(filename), 0) + end + self.LOADED[filename] = ret or true + remove(_running_files) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + self.LOADED[filename] = ret or true + return ret + end + NomsuCompiler.run_lua = function(self, lua, source) + if source == nil then + source = nil + end + 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 + local line_numbered_lua = concat((function() + local _accum_0 = { } + local _len_0 = 1 + for i, line in ipairs(get_lines:match(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_key = tostring(source or lua.source) + if not (SOURCE_MAP[source_key]) then + local map = { } + local offset = 1 + source = source or lua.source + local nomsu_str = tostring(FILE_CACHE[source.filename]:sub(source.start, source.stop)) + local lua_line = 1 + local nomsu_line = pos_to_line(nomsu_str, source.start) + local fn + fn = 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 + local old_line = nomsu_line + if s.source then + nomsu_line = pos_to_line(nomsu_str, s.source.start) + end + local _list_0 = s.bits + for _index_0 = 1, #_list_0 do + local b = _list_0[_index_0] + fn(b) + end + end + end + fn(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) + local _exp_0 = tree.type + if "Action" == _exp_0 then + local stub = tree.stub + do + local compile_action = self.COMPILE_ACTIONS[stub] + if compile_action 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 not ret then + self:compile_error(tree, "Compile-time action:\n%s\nfailed to produce any Lua") + end + return ret + end + end + local lua = LuaCode.Value(tree.source, "A", string.as_lua_id(stub), "(") + 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) + if not (arg_lua.is_value) then + self:compile_error(tok, "Cannot use:\n%s\nas an argument to %s, since it's not an expression, it produces: %s", stub, repr(arg_lua)) + 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 make_tree + make_tree = function(t) + if not (AST.is_syntax_tree(t)) then + return repr(t) + end + local bits + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #t do + local bit = t[_index_0] + _accum_0[_len_0] = make_tree(bit) + _len_0 = _len_0 + 1 + end + bits = _accum_0 + end + return t.type .. "(" .. repr(tostring(t.source)) .. ", " .. table.concat(bits, ", ") .. ")" + end + return LuaCode.Value(tree.source, make_tree(tree[1])) + elseif "Block" == _exp_0 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):as_statements() + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), "\n") + return lua + 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(repr(string_buffer)) + string_buffer = "" + end + local bit_lua = self:compile(bit) + if not (bit_lua.is_value) then + local src = ' ' .. gsub(tostring(self:tree_to_nomsu(bit)), '\n', '\n ') + local line = tostring(bit.source.filename) .. ":" .. tostring(pos_to_line(FILE_CACHE[bit.source.filename], bit.source.start)) + self:compile_error(bit, "Cannot use:\n%s\nas 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(repr(string_buffer)) + end + if #lua.bits > 1 then + lua:parenthesize() + end + return lua + elseif "List" == _exp_0 then + local lua = LuaCode.Value(tree.source, "list{") + local items = { } + for i, item in ipairs(tree) do + local item_lua = self:compile(item) + if not (item_lua.is_value) then + self:compile_error(item, "Cannot use:\n%s\nas a list item, since it's not an expression.") + end + items[i] = item_lua + end + lua:concat_append(items, ", ", ",\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) + _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) + if not (key_lua.is_value) then + self:compile_error(tree[1], "Cannot use:\n%s\nas a dict key, since it's not an expression.") + end + local value_lua = value and self:compile(value) or LuaCode.Value(key.source, "true") + if not (value_lua.is_value) then + self:compile_error(tree[2], "Cannot use:\n%s\nas 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 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]) + if not (lua.is_value) then + self:compile_error(tree[1], "Cannot index:\n%s\nsince 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) + if not (key_lua.is_value) then + self:compile_error(key, "Cannot use:\n%s\nas an index, since it's not an expression.") + end + local key_lua_str = tostring(key_lua) + do + local lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if 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 + 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, string.as_lua_id(tree[1])) + elseif "FileChunks" == _exp_0 then + return error("Cannot convert FileChunks to a single block of lua, since each chunk's " .. "compilation depends on the earlier chunks") + else + return error("Unknown type: " .. tostring(tree.type)) + end + end + NomsuCompiler.tree_to_nomsu = function(self, tree, inline, can_use_colon) + if inline == nil then + inline = false + end + if can_use_colon == nil then + can_use_colon = false + end + local _exp_0 = tree.type + if "FileChunks" == _exp_0 then + if inline then + return nil + end + local nomsu = NomsuCode(tree.source) + nomsu:concat_append((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #tree do + local c = tree[_index_0] + _accum_0[_len_0] = self:tree_to_nomsu(c) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), "\n" .. tostring(("~"):rep(80)) .. "\n") + return nomsu + elseif "Action" == _exp_0 then + if inline then + local nomsu = NomsuCode(tree.source) + for i, bit in ipairs(tree) do + if type(bit) == "string" then + if i > 1 then + nomsu:append(" ") + end + nomsu:append(bit) + else + local arg_nomsu = self:tree_to_nomsu(bit, true) + if not (arg_nomsu) then + return nil + end + if not (i == 1) then + nomsu:append(" ") + end + if bit.type == "Action" or bit.type == "Block" then + arg_nomsu:parenthesize() + end + nomsu:append(arg_nomsu) + end + end + return nomsu + else + local nomsu = NomsuCode(tree.source) + local next_space = "" + local line_len, last_colon = 0, nil + for i, bit in ipairs(tree) do + if type(bit) == "string" then + line_len = line_len + #next_space + #bit + nomsu:append(next_space, bit) + next_space = " " + else + local arg_nomsu + if last_colon == i - 1 and bit.type == "Action" then + arg_nomsu = nil + elseif bit.type == "Block" then + arg_nomsu = nil + else + arg_nomsu = self:tree_to_nomsu(bit, true) + end + if arg_nomsu and line_len + #tostring(arg_nomsu) < MAX_LINE then + if bit.type == "Action" then + if can_use_colon and i > 1 then + nomsu:append(match(next_space, "[^ ]*"), ": ", arg_nomsu) + next_space = "\n.." + line_len = 2 + last_colon = i + else + nomsu:append(next_space, "(", arg_nomsu, ")") + line_len = line_len + #next_space + 2 + #tostring(arg_nomsu) + next_space = " " + end + else + nomsu:append(next_space, arg_nomsu) + line_len = line_len + #next_space + #tostring(arg_nomsu) + next_space = " " + end + else + arg_nomsu = self:tree_to_nomsu(bit, nil, true) + if not (nomsu) then + return nil + end + if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then + if i == 1 then + arg_nomsu = NomsuCode(bit.source, "(..)\n ", arg_nomsu) + else + arg_nomsu = NomsuCode(bit.source, "\n ", arg_nomsu) + end + end + if last_colon == i - 1 and (bit.type == "Action" or bit.type == "Block") then + next_space = "" + end + nomsu:append(next_space, arg_nomsu) + next_space = "\n.." + line_len = 2 + end + if next_space == " " and #(match(tostring(nomsu), "[^\n]*$")) > MAX_LINE then + next_space = "\n.." + end + end + end + return nomsu + end + elseif "EscapedNomsu" == _exp_0 then + local nomsu = self:tree_to_nomsu(tree[1], true) + if nomsu == nil and not inline then + nomsu = self:tree_to_nomsu(tree[1]) + return nomsu and NomsuCode(tree.source, "\\:\n ", nomsu) + end + return nomsu and NomsuCode(tree.source, "\\(", nomsu, ")") + elseif "Block" == _exp_0 then + if inline then + local nomsu = NomsuCode(tree.source) + for i, line in ipairs(tree) do + if i > 1 then + nomsu:append("; ") + end + local line_nomsu = self:tree_to_nomsu(line, true) + if not (line_nomsu) then + return nil + end + nomsu:append(line_nomsu) + end + return nomsu + end + local nomsu = NomsuCode(tree.source) + for i, line in ipairs(tree) do + line = assert(self:tree_to_nomsu(line, nil, true), "Could not convert line to nomsu") + nomsu:append(line) + if i < #tree then + nomsu:append("\n") + if match(tostring(line), "\n") then + nomsu:append("\n") + end + end + end + return nomsu + elseif "Text" == _exp_0 then + if inline then + local nomsu = NomsuCode(tree.source, '"') + for _index_0 = 1, #tree do + local bit = tree[_index_0] + if type(bit) == 'string' then + nomsu:append((gsub(gsub(gsub(bit, "\\", "\\\\"), "\n", "\\n"), '"', '\\"'))) + else + local interp_nomsu = self:tree_to_nomsu(bit, true) + if interp_nomsu then + if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then + interp_nomsu:parenthesize() + end + nomsu:append("\\", interp_nomsu) + else + return nil + end + end + end + nomsu:append('"') + return nomsu + else + local inline_version = self:tree_to_nomsu(tree, true) + if inline_version and #inline_version <= MAX_LINE then + return inline_version + end + local nomsu = NomsuCode(tree.source, '".."\n ') + for i, bit in ipairs(tree) do + if type(bit) == 'string' then + local bit_lines = get_lines:match(bit) + for j, line in ipairs(bit_lines) do + if j > 1 then + nomsu:append("\n ") + end + if #line > 1.25 * MAX_LINE then + local remainder = line + while #remainder > 0 do + local split = find(remainder, " ", MAX_LINE, true) + if split then + local chunk + chunk, remainder = sub(remainder, 1, split), sub(remainder, split + 1, -1) + nomsu:append(chunk) + elseif #remainder > 1.75 * MAX_LINE then + split = math.floor(1.5 * MAX_LINE) + local chunk + chunk, remainder = sub(remainder, 1, split), sub(remainder, split + 1, -1) + nomsu:append(chunk) + else + nomsu:append(remainder) + break + end + if #remainder > 0 then + nomsu:append("\\\n ..") + end + end + else + nomsu:append(line) + end + end + else + local interp_nomsu = self:tree_to_nomsu(bit, true) + if interp_nomsu then + if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then + interp_nomsu:parenthesize() + end + nomsu:append("\\", interp_nomsu) + else + interp_nomsu = assert(self:tree_to_nomsu(bit)) + if not (interp_nomsu) then + return nil + end + nomsu:append("\\\n ", interp_nomsu) + if i < #tree then + nomsu:append("\n ..") + end + end + end + end + return nomsu + end + elseif "List" == _exp_0 then + if inline then + local nomsu = NomsuCode(tree.source, "[") + for i, item in ipairs(tree) do + local item_nomsu = self:tree_to_nomsu(item, true) + if not (item_nomsu) then + return nil + end + if i > 1 then + nomsu:append(", ") + end + nomsu:append(item_nomsu) + end + nomsu:append("]") + return nomsu + else + local inline_version = self:tree_to_nomsu(tree, true) + if inline_version and #inline_version <= MAX_LINE then + return inline_version + end + local nomsu = NomsuCode(tree.source, "[..]") + local line = NomsuCode(tree.source, "\n ") + for _index_0 = 1, #tree do + local item = tree[_index_0] + local item_nomsu = self:tree_to_nomsu(item, true) + if item_nomsu and #line + #", " + #item_nomsu <= MAX_LINE then + if #line.bits > 1 then + line:append(", ") + end + line:append(item_nomsu) + else + if not (item_nomsu) then + item_nomsu = self:tree_to_nomsu(item) + if not (item_nomsu) then + return nil + end + end + if #line.bits > 1 then + nomsu:append(line) + line = NomsuCode(line.source, "\n ") + end + line:append(item_nomsu) + end + end + if #line.bits > 1 then + nomsu:append(line) + end + return nomsu + end + elseif "Dict" == _exp_0 then + if inline then + local nomsu = NomsuCode(tree.source, "{") + for i, entry in ipairs(tree) do + local entry_nomsu = self:tree_to_nomsu(entry, true) + if not (entry_nomsu) then + return nil + end + if i > 1 then + nomsu:append(", ") + end + nomsu:append(entry_nomsu) + end + nomsu:append("}") + return nomsu + else + local inline_version = self:tree_to_nomsu(tree, true) + if inline_version then + return inline_version + end + local nomsu = NomsuCode(tree.source, "{..}") + local line = NomsuCode(tree.source, "\n ") + for _index_0 = 1, #tree do + local entry = tree[_index_0] + local entry_nomsu = self:tree_to_nomsu(entry) + if not (entry_nomsu) then + return nil + end + if #line + #tostring(entry_nomsu) <= MAX_LINE then + if #line.bits > 1 then + line:append(", ") + end + line:append(entry_nomsu) + else + if #line.bits > 1 then + nomsu:append(line) + line = NomsuCode(line.source, "\n ") + end + line:append(entry_nomsu) + end + end + if #line.bits > 1 then + nomsu:append(line) + end + return nomsu + end + elseif "DictEntry" == _exp_0 then + local key, value = tree[1], tree[2] + local key_nomsu = self:tree_to_nomsu(key, true) + if not (key_nomsu) then + return nil + end + if key.type == "Action" or key.type == "Block" then + key_nomsu:parenthesize() + end + local value_nomsu + if value then + value_nomsu = self:tree_to_nomsu(value, true) + else + value_nomsu = NomsuCode(tree.source, "") + end + if inline and not value_nomsu then + return nil + end + if not value_nomsu then + if inline then + return nil + end + value_nomsu = self:tree_to_nomsu(value) + if not (value_nomsu) then + return nil + end + end + return NomsuCode(tree.source, key_nomsu, ":", value_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 bit.type == "Text" and #bit == 1 and type(bit[1]) == 'string' then + if bit[1]:match("[_a-zA-Z][_a-zA-Z0-9]*") then + bit_nomsu = bit[1] + end + end + if not (bit_nomsu) then + bit_nomsu = self:tree_to_nomsu(bit, true) + end + if not (bit_nomsu) then + return nil + end + local _exp_1 = bit.type + if "Action" == _exp_1 or "Block" == _exp_1 or "IndexChain" == _exp_1 then + bit_nomsu:parenthesize() + elseif "Number" == _exp_1 then + if i < #tree then + bit_nomsu:parenthesize() + end + end + nomsu:append(bit_nomsu) + 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 +end +return NomsuCompiler |
