local NomsuCode NomsuCode = require("code_obj").NomsuCode local find, sub, match do local _obj_0 = string find, sub, match = _obj_0.find, _obj_0.sub, _obj_0.match end local R, P, S do local _obj_0 = require('lpeg') R, P, S = _obj_0.R, _obj_0.P, _obj_0.S end local re = require('re') local pretty_error = require("pretty_errors") local MAX_LINE = 80 local GOLDEN_RATIO = ((math.sqrt(5) - 1) / 2) 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_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B") + R("\xA8\xAB")) * R("\128\191")) local operator_patt = operator_char ^ 1 * -1 local identifier_patt = (R("az", "AZ", "09") + P("_") + (-operator_char * utf8_char_patt)) ^ 1 * -1 local is_operator is_operator = function(s) return type(s) == 'string' and not not operator_patt:match(s) end local is_identifier is_identifier = function(s) return type(s) == 'string' and not not identifier_patt:match(s) end local can_be_unary can_be_unary = function(t) return t.type == "Action" and #t == 2 and is_operator(t[1]) and type(t[2]) ~= 'string' and t[2].type ~= "Block" and not (t[2].type == "Number" and t[1] == "-") 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 tree_to_inline_nomsu tree_to_inline_nomsu = function(tree) local _exp_0 = tree.type if "Action" == _exp_0 then local nomsu = NomsuCode:from(tree.source) if can_be_unary(tree) then nomsu:add(tree[1]) local arg_nomsu = tree_to_inline_nomsu(tree[2]) if tree[2].type == "MethodCall" or tree[2].type == "Action" then arg_nomsu:parenthesize() end nomsu:add(arg_nomsu) return nomsu end local num_args, num_words = 0, 0 for i, bit in ipairs(tree) do if type(bit) == "string" then num_words = num_words + 1 local clump_words if type(tree[i - 1]) == 'string' then clump_words = is_operator(bit) ~= is_operator(tree[i - 1]) else clump_words = bit == "'" end if i > 1 and not clump_words then nomsu:add(" ") end nomsu:add(bit) else num_args = num_args + 1 local arg_nomsu = tree_to_inline_nomsu(bit) if tree[i + 1] == "'" and bit.type == "Action" and can_be_unary(bit) then arg_nomsu:parenthesize() end if bit.type == "Block" then if i ~= #tree then if i > 1 then nomsu:add(" ") end arg_nomsu:parenthesize() end else if i > 1 then nomsu:add(" ") end if bit.type == "MethodCall" then arg_nomsu:parenthesize() elseif bit.type == "Action" and not can_be_unary(bit) then arg_nomsu:parenthesize() end end nomsu:add(arg_nomsu) end end if num_args == 1 and num_words == 0 then nomsu:add("()") end return nomsu elseif "MethodCall" == _exp_0 then local target_nomsu = tree_to_inline_nomsu(tree[1]) if tree[1].type == "Block" then target_nomsu:parenthesize() end local nomsu = NomsuCode:from(tree.source, target_nomsu, ", ") if #tree > 2 then nomsu:add("(") end for i = 2, #tree do if i > 2 then nomsu:add("; ") end nomsu:add(tree_to_inline_nomsu(tree[i])) end if #tree > 2 then nomsu:add(")") end return nomsu elseif "EscapedNomsu" == _exp_0 then local inner_nomsu = tree_to_inline_nomsu(tree[1]) if not (tree[1].type == "List" or tree[1].type == "Dict" or tree[1].type == "Var") then inner_nomsu:parenthesize() end return NomsuCode:from(tree.source, "\\", inner_nomsu) elseif "Block" == _exp_0 then local nomsu = NomsuCode:from(tree.source, ":") for i, line in ipairs(tree) do nomsu:add(i == 1 and " " or "; ") nomsu:add(tree_to_inline_nomsu(line)) end if #tree > 1 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:add(inline_escape(bit)) elseif bit.type == "Text" then add_text(nomsu, bit) else local interp_nomsu = tree_to_inline_nomsu(bit) if bit.type ~= "Var" and bit.type ~= "List" and bit.type ~= "Dict" then interp_nomsu:parenthesize() elseif bit.type == "Var" and type(bit[1]) == 'string' and type(tree[i + 1]) == 'string' and not match(tree[i + 1], "^[ \n\t,.:;#(){}[%]]") then interp_nomsu:parenthesize() end nomsu:add("\\", interp_nomsu) end end end local nomsu = NomsuCode:from(tree.source) add_text(nomsu, tree) return NomsuCode:from(tree.source, '"', nomsu, '"') elseif "List" == _exp_0 or "Dict" == _exp_0 then local nomsu = NomsuCode:from(tree.source, (tree.type == "List" and "[" or "{")) for i, item in ipairs(tree) do if i > 1 then nomsu:add(", ") end local item_nomsu = tree_to_inline_nomsu(item, true) if item.type == "MethodCall" or (item.type == "Block" and i < #tree) then item_nomsu:parenthesize() end nomsu:add(item_nomsu) end nomsu:add(tree.type == "List" and "]" or "}") return nomsu elseif "DictEntry" == _exp_0 then local key, value = tree[1], tree[2] local nomsu = NomsuCode:from(tree.source) if key.type ~= "Index" then key = { type = "Index", source = key.source, key } end nomsu:add(tree_to_inline_nomsu(key)) if value then nomsu:add(" = ") local value_nomsu = tree_to_inline_nomsu(value) if value.type == "Block" or value.type == "Action" or value.type == "MethodCall" then value_nomsu:parenthesize() end nomsu:add(value_nomsu) end return nomsu elseif "Index" == _exp_0 then local key = tree[1] local nomsu = NomsuCode:from(key.source, ".") local key_nomsu if key.type == "Text" and #key == 1 and is_identifier(key[1]) then key_nomsu = key[1] else key_nomsu = tree_to_inline_nomsu(key) end local _exp_1 = key.type if "Block" == _exp_1 or "Action" == _exp_1 or "MethodCall" == _exp_1 or "IndexChain" == _exp_1 then key_nomsu:parenthesize() end return NomsuCode:from(key.source, ".", key_nomsu) elseif "IndexChain" == _exp_0 then local nomsu = NomsuCode:from(tree.source) local target = tree[1] local target_nomsu = tree_to_inline_nomsu(target) local _exp_1 = target.type if "Action" == _exp_1 or "MethodCall" == _exp_1 or "EscapedNomsu" == _exp_1 then target_nomsu:parenthesize() elseif "Number" == _exp_1 then if target_nomsu:text():match("%.") then target_nomsu:parenthesize() end end nomsu:add(target_nomsu) for i = 2, #tree do if tree[i].type ~= "Index" then tree[i] = { type = "Index", source = tree[i].source, tree[i] } end nomsu:add(tree_to_inline_nomsu(tree[i])) end return nomsu elseif "Number" == _exp_0 then local n = tostring(tree[1]) local s if n:match("^-*0x") then s = n:upper():gsub("0X", "0x") elseif tree.hex and tonumber((n:gsub("_", ""))) < 0 then s = ("-0x%X"):format(-tree[1]) elseif tree.hex then s = ("0x%X"):format(tree[1]) else s = n end return NomsuCode:from(tree.source, s) elseif "Var" == _exp_0 then local varname = tree[1] if type(varname) == "string" then return NomsuCode:from(tree.source, "$", varname) else return NomsuCode:from(tree.source, "$(", tree_to_inline_nomsu(varname), ")") end elseif "FileChunks" == _exp_0 then return error("Can't inline a FileChunks") elseif "Comment" == _exp_0 then return NomsuCode:from(tree.source) elseif "Error" == _exp_0 then local err_msg = pretty_error({ title = "Parse error", error = tree.error, hint = tree.hint, source = tree:get_source_file(), start = tree.source.start, stop = tree.source.stop, filename = tree.source.filename }) return error(err_msg) else return error("Unknown type: " .. tostring(tree.type)) end end local tree_to_nomsu tree_to_nomsu = function(tree) local nomsu = NomsuCode:from(tree.source) local recurse recurse = function(t, argnum) if argnum == nil then argnum = nil end local space = MAX_LINE - nomsu:trailing_line_len() local try_inline = true for subtree in coroutine.wrap(function() return (t:with(coroutine.yield) and nil) end) do local _exp_0 = subtree.type if "Comment" == _exp_0 then try_inline = false elseif "Block" == _exp_0 then if #subtree > 1 then try_inline = false end elseif "Text" == _exp_0 then local indented = tree_to_nomsu(subtree) local indented_lines do local _accum_0 = { } local _len_0 = 1 local _list_0 = indented:text():lines() for _index_0 = 1, #_list_0 do local line = _list_0[_index_0] if line:match("^ +([^ ].*)") then _accum_0[_len_0] = line _len_0 = _len_0 + 1 end end indented_lines = _accum_0 end for i = #indented_lines, 1, -1 do if indented_lines[i]:match("^ *\\;$") then table.remove(indented_lines, i) end end if #indented_lines > 1 or (#indented_lines == 1 and #indented_lines[1] > MAX_LINE + 8) then try_inline = false end end end local inline_nomsu if try_inline then inline_nomsu = tree_to_inline_nomsu(t) if t.type == "MethodCall" then inline_nomsu:parenthesize() elseif t.type == "Action" and not can_be_unary(t) then inline_nomsu:parenthesize() end if #inline_nomsu:text() <= space or #inline_nomsu:text() <= 8 then if t.type ~= "Text" then return inline_nomsu end end end local indented = tree_to_nomsu(t) if t.type == "Action" or t.type == "MethodCall" then if indented:is_multiline() then if argnum == nil or argnum == 1 then return NomsuCode:from(t.source, "(\n ", indented, "\n)") else return NomsuCode:from(t.source, "\n ", indented) end elseif argnum and argnum > 1 then return NomsuCode:from(t.source, "\n ", indented) else indented:parenthesize() end end local indented_lines do local _accum_0 = { } local _len_0 = 1 local _list_0 = indented:text():lines() for _index_0 = 1, #_list_0 do local line = _list_0[_index_0] if line:match("^ +([^ ].*)") then _accum_0[_len_0] = line _len_0 = _len_0 + 1 end end indented_lines = _accum_0 end if t.type == "Text" then for i = #indented_lines, 1, -1 do if indented_lines[i]:match("^ *\\;$") then table.remove(indented_lines, i) end end end if inline_nomsu and (#inline_nomsu:text() < MAX_LINE or #inline_nomsu:text() <= space) and #indented_lines <= 1 then return inline_nomsu end return indented end local _exp_0 = tree.type if "FileChunks" == _exp_0 then if tree.shebang then nomsu:add(tree.shebang, "\n") end for chunk_no, chunk in ipairs(tree) do if chunk_no > 1 then nomsu:add("\n\n" .. tostring(("~"):rep(80)) .. "\n\n") end if chunk.type == "Block" then nomsu:add(NomsuCode:from(chunk.source, table.unpack(tree_to_nomsu(chunk).bits, 2))) else nomsu:add(tree_to_nomsu(chunk)) end end return nomsu elseif "Action" == _exp_0 then if can_be_unary(tree) and not can_be_unary(tree[2]) then nomsu:add(tree[1]) nomsu:add(recurse(tree[2])) return nomsu end local next_space = "" local word_buffer = { } local num_args, num_words = 0, 0 for i, bit in ipairs(tree) do local _continue_0 = false repeat if type(bit) == "string" then num_words = num_words + 1 if #word_buffer > 0 and is_operator(bit) == is_operator(word_buffer[#word_buffer]) then table.insert(word_buffer, " ") end table.insert(word_buffer, bit) _continue_0 = true break end if #word_buffer > 0 then local words = table.concat(word_buffer) if next_space == " " then if nomsu:trailing_line_len() + #words > MAX_LINE and nomsu:trailing_line_len() > 8 then next_space = "\n.." elseif word_buffer[1] == "'" then next_space = "" end end nomsu:add(next_space, words) word_buffer = { } next_space = " " end num_args = num_args + 1 local bit_nomsu = recurse(bit, i) if tree[i + 1] == "'" and bit.type == "Action" and not bit_nomsu:is_multiline() and can_be_unary(bit) then bit_nomsu:parenthesize() end if bit.type == "Block" then if not bit_nomsu:is_multiline() and (#bit_nomsu:text() > nomsu:trailing_line_len() * GOLDEN_RATIO and #bit_nomsu:text() > 8) or #bit_nomsu:text() + nomsu:trailing_line_len() > MAX_LINE then bit_nomsu = tree_to_nomsu(bit) end elseif (not bit_nomsu:is_multiline() and nomsu:trailing_line_len() + #bit_nomsu:text() > MAX_LINE and nomsu:trailing_line_len() > 8) then if next_space == " " and #bit_nomsu:text() < MAX_LINE then if i == #tree then bit_nomsu = tree_to_inline_nomsu(bit) next_space = "\n " elseif bit.type == "List" or bit.type == "Dict" then bit_nomsu = tree_to_nomsu(bit) else next_space = "\n.." end elseif bit.type == 'Action' or bit.type == "MethodCall" then bit_nomsu = NomsuCode:from(bit.source, "\n ", tree_to_nomsu(bit)) else bit_nomsu = tree_to_nomsu(bit) end end if not (next_space == " " and bit_nomsu:text():match("^[:\n]")) then nomsu:add(next_space) end nomsu:add(bit_nomsu) next_space = (bit.type == "Block" or bit_nomsu:text():matches("\n [^\n]*$")) and "\n.." or " " _continue_0 = true until true if not _continue_0 then break end end if #word_buffer > 0 then local words = table.concat(word_buffer) if next_space == " " then if nomsu:trailing_line_len() + #words > MAX_LINE + 8 and nomsu:trailing_line_len() > 8 then next_space = "\n.." elseif word_buffer[1] == "'" then next_space = "" end end nomsu:add(next_space, words) next_space = " " end if num_args == 1 and num_words == 0 then if next_space ~= " " then nomsu:add(next_space) end nomsu:add("()") end return nomsu elseif "MethodCall" == _exp_0 then local target_nomsu = recurse(tree[1]) if tree[1].type == "Block" and not target_nomsu:is_multiline() then target_nomsu:parenthesize() end nomsu:add(target_nomsu, ", ") local inner_nomsu = NomsuCode() for i = 2, #tree do if i > 2 then inner_nomsu:add("\n") end inner_nomsu:add(tree_to_nomsu(tree[i])) end if #tree == 2 and nomsu:trailing_line_len() + #inner_nomsu:text():match("^[^\n]*") < MAX_LINE then nomsu:add(inner_nomsu) else nomsu:add("\n ", inner_nomsu) end return nomsu elseif "EscapedNomsu" == _exp_0 then nomsu = recurse(tree[1]) if tree[1].type == 'Block' and not nomsu:is_multiline() then nomsu:parenthesize() end return NomsuCode:from(tree.source, "\\", nomsu) elseif "Block" == _exp_0 then local prev_line, needs_space = nil, { } for i, line in ipairs(tree) do local line_nomsu = tree_to_nomsu(line) if i > 1 then nomsu:add("\n") if tree[i - 1].type ~= "Comment" then needs_space[i] = (line_nomsu:is_multiline() and prev_line:is_multiline()) if (tree[i].type == "Comment" or needs_space[i] or needs_space[i - 1] or prev_line:text():match("\n [^\n]*$")) then nomsu:add("\n") end end end nomsu:add(line_nomsu) prev_line = line_nomsu end return NomsuCode:from(tree.source, ":\n ", nomsu) elseif "Text" == _exp_0 then local max_line = MAX_LINE + 8 local add_text add_text = function(tree) for i, bit in ipairs(tree) do if type(bit) == 'string' then bit = escape(bit) for j, line in ipairs(bit:lines()) do if j > 1 then if nomsu:text():match(" $") then nomsu:add("\\;") end nomsu:add("\n") elseif #line > 10 and nomsu:trailing_line_len() > max_line then nomsu:add("\\\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 + 16 then split = space + 16 end if #line - split < 16 then split = #line end local bite bite, line = sub(line, 1, split), sub(line, split + 1, -1) nomsu:add(bite) if #line > 0 then nomsu:add("\\\n..") end end end elseif bit.type == "Text" then add_text(bit) else nomsu:add("\\") local interp_nomsu = recurse(bit) if interp_nomsu:is_multiline() then local curr_indent = nomsu:text():match("\n( *)[^\n]*$") or nomsu:text():match("^( *)") interp_nomsu = NomsuCode((interp_nomsu:text():gsub("\n", "\n" .. curr_indent))) else local space = max_line - nomsu:trailing_line_len() if bit.type == "Var" then local next_str = tree[i + 1] while type(next_str) == 'table' and next_str.type == 'Text' do next_str = next_str[1] end if type(next_str) == 'string' and not match(next_str, "^[ \n\t,.:;#(){}[%]]") then interp_nomsu:parenthesize() end elseif #interp_nomsu:text() > space then local interp_nomsu2 if bit.type == "Action" or bit.type == "MethodCall" then interp_nomsu2 = NomsuCode:from(bit.source, "(\n ", tree_to_nomsu(bit), "\n)") else interp_nomsu2 = tree_to_nomsu(bit) end if #interp_nomsu2:text():lines() > 3 or #interp_nomsu2:text() >= MAX_LINE * GOLDEN_RATIO then local curr_indent = nomsu:text():match("\n( *)[^\n]*$") or nomsu:text():match("^( *)") interp_nomsu2 = NomsuCode((interp_nomsu2:text():gsub("\n", "\n" .. curr_indent))) interp_nomsu = interp_nomsu2 else nomsu:add("\n..\\") if bit.type == "EscapedNomsu" or bit.type == "Block" or bit.type == "IndexChain" then interp_nomsu:parenthesize() end end elseif bit.type == "EscapedNomsu" or bit.type == "Block" or bit.type == "IndexChain" then interp_nomsu:parenthesize() elseif bit.type == "Action" and can_be_unary(bit) then interp_nomsu:parenthesize() end end nomsu:add(interp_nomsu) if interp_nomsu:is_multiline() and bit.type == "Block" then nomsu:add("\n..") end end end end add_text(tree) if nomsu:text():match(" $") then nomsu:add("\\;") end return NomsuCode:from(tree.source, '("\n ', nomsu, '\n")') elseif "List" == _exp_0 or "Dict" == _exp_0 then if #tree == 0 then nomsu:add(tree.type == "List" and "[]" or "{}") return nomsu end if #tree == 1 and tree[1].type == "Block" then local block_lua = recurse(tree[1]) if block_lua:is_multiline() then block_lua:add("\n") end if tree.type == "List" then return NomsuCode:from(tree.source, "[", block_lua, "]") else return NomsuCode:from(tree.source, "{", block_lua, "}") end end local sep = '' local prev_item, needs_space = nil, { } for i, item in ipairs(tree) do local item_nomsu if item.type == 'MethodCall' then item_nomsu = recurse(item) elseif item.type == 'Comment' then item_nomsu = tree_to_nomsu(item) if i > 1 then sep = '\n' end elseif item.type == 'Block' and #item == 1 then item_nomsu = tree_to_nomsu(item[1]) item_nomsu:prepend(": ") if i > 1 then sep = '\n' end else item_nomsu = tree_to_inline_nomsu(item) if nomsu:trailing_line_len() + #item_nomsu:text() > MAX_LINE then if i > 1 then sep = '\n' end item_nomsu = item.type == "Action" and tree_to_nomsu(item) or recurse(item) end end nomsu:add(sep) if sep == '\n' then if i > 1 and tree[i - 1].type ~= "Comment" then needs_space[i] = (item_nomsu:is_multiline() and prev_item:is_multiline()) if (tree[i].type == "Comment" or needs_space[i] or needs_space[i - 1] or prev_item:text():match("\n [^\n]*$")) then nomsu:add("\n") end end end nomsu:add(item_nomsu) prev_item = item_nomsu if item_nomsu:is_multiline() or item.type == 'Comment' or item.type == "Block" or nomsu:trailing_line_len() + #tostring(item_nomsu) >= MAX_LINE then sep = '\n' else sep = ', ' end end if tree.type == "List" then return NomsuCode:from(tree.source, "[\n ", nomsu, "\n]") else return NomsuCode:from(tree.source, "{\n ", nomsu, "\n}") end elseif "DictEntry" == _exp_0 then local key, value = tree[1], tree[2] nomsu = NomsuCode:from(tree.source) if key.type ~= "Index" then key = { type = "Index", source = key.source, key } end nomsu:add(tree_to_nomsu(key)) if value then local value_nomsu = recurse(value) nomsu:add(" = ", value_nomsu) end return nomsu elseif "Comment" == _exp_0 then nomsu:add("###", (tree[1]:gsub("\n", "\n "))) return nomsu elseif "IndexChain" == _exp_0 or "Index" == _exp_0 or "Number" == _exp_0 or "Var" == _exp_0 or "Comment" == _exp_0 or "Error" == _exp_0 then return tree_to_inline_nomsu(tree) else return error("Unknown type: " .. tostring(tree.type)) end end return { tree_to_nomsu = tree_to_nomsu, tree_to_inline_nomsu = tree_to_inline_nomsu }