local VERSION = "1.3.0" local insert, concat do local _obj_0 = table insert, concat = _obj_0.insert, _obj_0.concat end local load, setfenv, assert, type, error, tostring, tonumber, setmetatable do local _obj_0 = _G load, setfenv, assert, type, error, tostring, tonumber, setmetatable = _obj_0.load, _obj_0.setfenv, _obj_0.assert, _obj_0.type, _obj_0.error, _obj_0.tostring, _obj_0.tonumber, _obj_0.setmetatable end setfenv = setfenv or function(fn, env) local name local i = 1 while true do name = debug.getupvalue(fn, i) if not name or name == "_ENV" then break end i = i + 1 end if name then debug.upvaluejoin(fn, i, (function() return env end), 1) end return fn end local html_escape_entities = { ['&'] = '&', ['<'] = '<', ['>'] = '>', ['"'] = '"', ["'"] = ''' } local html_escape html_escape = function(str) return (str:gsub([=[["><'&]]=], html_escape_entities)) end local get_line get_line = function(str, line_num) for line in str:gmatch("([^\n]*)\n?") do if line_num == 1 then return line end line_num = line_num - 1 end end local pos_to_line pos_to_line = function(str, pos) local line = 1 for _ in str:sub(1, pos):gmatch("\n") do line = line + 1 end return line end local Compiler do local _class_0 local _base_0 = { render = function(self) return table.concat(self.buffer) end, push = function(self, str, ...) local i = self.i + 1 self.buffer[i] = str self.i = i if ... then return self:push(...) end end, header = function(self) return self:push("local _tostring, _escape, _b, _b_i = ...\n") end, footer = function(self) return self:push("return _b") end, increment = function(self) return self:push("_b_i = _b_i + 1\n") end, mark = function(self, pos) return self:push("--[[", tostring(pos), "]] ") end, assign = function(self, ...) self:push("_b[_b_i] = ", ...) if ... then return self:push("\n") end end } _base_0.__index = _base_0 _class_0 = setmetatable({ __init = function(self) self.buffer = { } self.i = 0 end, __base = _base_0, __name = "Compiler" }, { __index = _base_0, __call = function(cls, ...) local _self_0 = setmetatable({}, _base_0) cls.__init(_self_0, ...) return _self_0 end }) _base_0.__class = _class_0 Compiler = _class_0 end local Parser do local _class_0 local _base_0 = { open_tag = "<%", close_tag = "%>", modifiers = "^[=-]", html_escape = true, next_tag = function(self) local start, stop = self.str:find(self.open_tag, self.pos, true) if not (start) then self:push_raw(self.pos, #self.str) return false end if not (start == self.pos) then self:push_raw(self.pos, start - 1) end self.pos = stop + 1 local modifier if self.str:match(self.modifiers, self.pos) then do local _with_0 = self.str:sub(self.pos, self.pos) self.pos = self.pos + 1 modifier = _with_0 end end local close_start, close_stop = self.str:find(self.close_tag, self.pos, true) if not (close_start) then return nil, self:error_for_pos(start, "failed to find closing tag") end while self:in_string(self.pos, close_start) do close_start, close_stop = self.str:find(self.close_tag, close_stop, true) if not (close_start) then return nil, self:error_for_pos(start, "failed to find string close") end end local trim_newline if "-" == self.str:sub(close_start - 1, close_start - 1) then close_start = close_start - 1 trim_newline = true end self:push_code(modifier or "code", self.pos, close_start - 1) self.pos = close_stop + 1 if trim_newline then do local match = self.str:match("^\n", self.pos) if match then self.pos = self.pos + #match end end end return true end, in_string = function(self, start, stop) local in_string = false local end_delim = nil local escape = false local pos = 0 local skip_until = nil local chunk = self.str:sub(start, stop) for char in chunk:gmatch(".") do local _continue_0 = false repeat pos = pos + 1 if skip_until then if pos <= skip_until then _continue_0 = true break end skip_until = nil end if end_delim then if end_delim == char and not escape then in_string = false end_delim = nil end else if char == "'" or char == '"' then end_delim = char in_string = true end if char == "[" then do local lstring = chunk:match("^%[=*%[", pos) if lstring then local lstring_end = lstring:gsub("%[", "]") local lstring_p1, lstring_p2 = chunk:find(lstring_end, pos, true) if not (lstring_p1) then return true end skip_until = lstring_p2 end end end end escape = char == "\\" _continue_0 = true until true if not _continue_0 then break end end return in_string end, push_raw = function(self, start, stop) return insert(self.chunks, self.str:sub(start, stop)) end, push_code = function(self, kind, start, stop) return insert(self.chunks, { kind, self.str:sub(start, stop), start }) end, compile = function(self, str) local success, err = self:parse(str) if not (success) then return nil, err end local fn fn, err = self:load(self:chunks_to_lua()) if not (fn) then return nil, err end return function(...) local buffer buffer, err = self:run(fn, ...) if buffer then return concat(buffer) else return nil, err end end end, parse = function(self, str) self.str = str assert(type(self.str) == "string", "expecting string for parse") self.pos = 1 self.chunks = { } while true do local found, err = self:next_tag() if err then return nil, err end if not (found) then break end end return true end, parse_error = function(self, err, code) local line_no, err_msg = err:match("%[.-%]:(%d+): (.*)$") line_no = tonumber(line_no) if not (line_no) then return end local line = get_line(code, line_no) local source_pos = tonumber(line:match("^%-%-%[%[(%d+)%]%]")) if not (source_pos) then return end return self:error_for_pos(source_pos, err_msg) end, error_for_pos = function(self, source_pos, err_msg) local source_line_no = pos_to_line(self.str, source_pos) local source_line = get_line(self.str, source_line_no) return tostring(err_msg) .. " [" .. tostring(source_line_no) .. "]: " .. tostring(source_line) end, load = function(self, code, name) if name == nil then name = "etlua" end local code_fn do local code_ref = code code_fn = function() do local ret = code_ref code_ref = nil return ret end end end local fn, err = load(code_fn, name) if not (fn) then do local err_msg = self:parse_error(err, code) if err_msg then return nil, err_msg end end return nil, err end return fn end, run = function(self, fn, env, buffer, i, ...) if env == nil then env = { } end local combined_env = setmetatable({ }, { __index = function(self, name) local val = env[name] if val == nil then val = _G[name] end return val end }) if not (buffer) then buffer = { } i = 0 end setfenv(fn, combined_env) return fn(tostring, html_escape, buffer, i, ...) end, compile_to_lua = function(self, str, ...) local success, err = self:parse(str) if not (success) then return nil, err end return self:chunks_to_lua(...) end, chunks_to_lua = function(self, compiler_cls) if compiler_cls == nil then compiler_cls = Compiler end local r = compiler_cls() r:header() local _list_0 = self.chunks for _index_0 = 1, #_list_0 do local chunk = _list_0[_index_0] local t = type(chunk) if t == "table" then t = chunk[1] end local _exp_0 = t if "string" == _exp_0 then r:increment() r:assign(("%q"):format(chunk)) elseif "code" == _exp_0 then r:mark(chunk[3]) r:push(chunk[2], "\n") elseif "=" == _exp_0 or "-" == _exp_0 then r:increment() r:mark(chunk[3]) r:assign() if t == "=" and self.html_escape then r:push("_escape(_tostring(", chunk[2], "))\n") else r:push("_tostring(", chunk[2], ")\n") end else error("unknown type " .. tostring(t)) end end r:footer() return r:render() end } _base_0.__index = _base_0 _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Parser" }, { __index = _base_0, __call = function(cls, ...) local _self_0 = setmetatable({}, _base_0) cls.__init(_self_0, ...) return _self_0 end }) _base_0.__class = _class_0 Parser = _class_0 end local compile do local _base_0 = Parser() local _fn_0 = _base_0.compile compile = function(...) return _fn_0(_base_0, ...) end end local render render = function(str, ...) local fn, err = compile(str) if fn then return fn(...) else return nil, err end end return { compile = compile, render = render, Parser = Parser, Compiler = Compiler, _version = VERSION }