Moonshark/runner/lua/render.lua

191 lines
5.7 KiB
Lua

-- 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