-- render.lua -- Template processing with code execution function render(template_str, env) local function is_control_structure(code) -- Check if code is a control structure that doesn't produce output local trimmed = code:match("^%s*(.-)%s*$") return trimmed == "else" or trimmed == "end" or trimmed:match("^if%s") or trimmed:match("^elseif%s") or trimmed:match("^for%s") or trimmed:match("^while%s") or trimmed:match("^repeat%s*$") or trimmed:match("^until%s") or trimmed:match("^do%s*$") or trimmed:match("^local%s") or trimmed:match("^function%s") or trimmed:match(".*=%s*function%s*%(") or trimmed:match(".*then%s*$") or trimmed:match(".*do%s*$") end local pos, chunks = 1, {} while pos <= #template_str do local unescaped_start = template_str:find("{{{", pos, true) local escaped_start = template_str:find("{{", pos, true) local start, tag_type, open_len if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then start, tag_type, open_len = unescaped_start, "-", 3 elseif escaped_start then start, tag_type, open_len = escaped_start, "=", 2 else table.insert(chunks, template_str:sub(pos)) break end if start > pos then table.insert(chunks, template_str:sub(pos, start-1)) end pos = start + open_len local close_tag = tag_type == "-" and "}}}" or "}}" local close_start, close_stop = template_str:find(close_tag, pos, true) if not close_start then error("Failed to find closing tag at position " .. pos) end local code = template_str:sub(pos, close_start-1):match("^%s*(.-)%s*$") local is_control = is_control_structure(code) table.insert(chunks, {tag_type, code, pos, is_control}) pos = close_stop + 1 end local buffer = {"local _tostring, _escape, _b, _b_i = ...\n"} for _, chunk in ipairs(chunks) do local t = type(chunk) if t == "string" then table.insert(buffer, "_b_i = _b_i + 1\n") table.insert(buffer, "_b[_b_i] = " .. string.format("%q", chunk) .. "\n") else local tag_type, code, pos, is_control = chunk[1], chunk[2], chunk[3], chunk[4] if is_control then -- Control structure - just insert as raw Lua code table.insert(buffer, "--[[" .. pos .. "]] " .. code .. "\n") elseif tag_type == "=" then -- Simple variable check if code:match("^[%w_]+$") then table.insert(buffer, "_b_i = _b_i + 1\n") table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _escape(_tostring(" .. code .. "))\n") else -- Expression output with escaping table.insert(buffer, "_b_i = _b_i + 1\n") table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _escape(_tostring(" .. code .. "))\n") end elseif tag_type == "-" then -- Unescaped output table.insert(buffer, "_b_i = _b_i + 1\n") table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _tostring(" .. code .. ")\n") end end end table.insert(buffer, "return _b") local generated_code = table.concat(buffer) -- DEBUG: Uncomment to see generated code -- print("Generated Lua code:") -- print(generated_code) -- print("---") local fn, err = loadstring(generated_code) if not fn then print("Generated code that failed to compile:") print(generated_code) error(err) end env = env or {} local runtime_env = setmetatable({}, {__index = function(_, k) return env[k] or _G[k] end}) setfenv(fn, runtime_env) local output_buffer = {} fn(tostring, html_special_chars, output_buffer, 0) return table.concat(output_buffer) end -- Named placeholder processing function parse(template_str, env) local pos, output = 1, {} env = env or {} while pos <= #template_str do local unescaped_start, unescaped_end, unescaped_name = template_str:find("{{{%s*([%w_]+)%s*}}}", pos) local escaped_start, escaped_end, escaped_name = template_str:find("{{%s*([%w_]+)%s*}}", pos) local next_pos, placeholder_end, name, escaped if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then next_pos, placeholder_end, name, escaped = unescaped_start, unescaped_end, unescaped_name, false elseif escaped_start then next_pos, placeholder_end, name, escaped = escaped_start, escaped_end, escaped_name, true else local text = template_str:sub(pos) if text and #text > 0 then table.insert(output, text) end break end local text = template_str:sub(pos, next_pos - 1) if text and #text > 0 then table.insert(output, text) end local value = env[name] local str = tostring(value or "") if escaped then str = html_special_chars(str) end table.insert(output, str) pos = placeholder_end + 1 end return table.concat(output) end -- Indexed placeholder processing function iparse(template_str, values) local pos, output, value_index = 1, {}, 1 values = values or {} while pos <= #template_str do local unescaped_start, unescaped_end = template_str:find("{{{}}}", pos, true) local escaped_start, escaped_end = template_str:find("{{}}", pos, true) local next_pos, placeholder_end, escaped if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then next_pos, placeholder_end, escaped = unescaped_start, unescaped_end, false elseif escaped_start then next_pos, placeholder_end, escaped = escaped_start, escaped_end, true else local text = template_str:sub(pos) if text and #text > 0 then table.insert(output, text) end break end local text = template_str:sub(pos, next_pos - 1) if text and #text > 0 then table.insert(output, text) end local value = values[value_index] local str = tostring(value or "") if escaped then str = html_special_chars(str) end table.insert(output, str) pos = placeholder_end + 1 value_index = value_index + 1 end return table.concat(output) end