Post-deletion cleanup (units run only on wasm): - types.h/compiler.cpp: drop the native-era SharedUnit fields so_name, bin_file_name, and the opt_so_optional cache-mode plumbing (no native optional .so path remains). The per-unit compile lock is re-keyed from so_name+.lock to wasm_name+.lock (still per-unit). - unit_info() and to_string(SharedUnit*) no longer expose .so artifact fields. - backend.h: drop the stale "+ fallback-token gate" comment. - Docs/comments corrected to wasm-only reality: README, tests/README, site/doc C++ preprocessor + error_pages + unit_info pages, site/info intro, site/demo/unit-browser artifact card; the Phase-5 native-vs-wasm benchmark harness (tests/wasm_benchmark.py) reframed for the wasm-only backend. Audit confirmed no live references remain to so_handle, load_shared_unit, compiler_load_shared_unit, compiler_invoke*/_cli/_websocket/_serve_http, COMPILE_SCRIPT/COMPILE_WASM_UNITS, or the native export-symbol constants; request_ref_handler/dv_call_handler are kept (live wasm funcref casts). Swept via the pi agent (delegated to a gpt-5.3-codex-spark sub-model); independently re-verified on the host: run_cli_tests --include-wasm-kill => 87 passed, 0 failed, 0 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
608 lines
14 KiB
C++
608 lines
14 KiB
C++
#include "compiler-parser.h"
|
|
|
|
#include "compiler.h"
|
|
|
|
namespace {
|
|
|
|
const char* UCE_NAMED_RENDER_SYMBOL = "__uce_render";
|
|
const char* UCE_NAMED_COMPONENT_SYMBOL = "__uce_component";
|
|
const char* UCE_NAMED_SERVE_HTTP_SYMBOL = "__uce_serve_http";
|
|
|
|
struct CompilerCodeState
|
|
{
|
|
char quote_char = '\0';
|
|
bool inside_quote = false;
|
|
bool inside_line_comment = false;
|
|
bool inside_block_comment = false;
|
|
};
|
|
|
|
bool compiler_code_state_is_neutral(const CompilerCodeState& state)
|
|
{
|
|
return(!state.inside_quote && !state.inside_line_comment && !state.inside_block_comment);
|
|
}
|
|
|
|
String compiler_cpp_raw_string_delimiter(const String& content)
|
|
{
|
|
StringList candidates = {
|
|
"",
|
|
"UCE",
|
|
"UCE_LITERAL",
|
|
"uce_literal_0",
|
|
"uce_literal_1"
|
|
};
|
|
|
|
u64 hash = 1469598103934665603ULL;
|
|
for(unsigned char c : content)
|
|
{
|
|
hash ^= c;
|
|
hash *= 1099511628211ULL;
|
|
}
|
|
|
|
for(u32 i = 0; i < 64; i += 1)
|
|
{
|
|
String suffix = to_hex<u64>(hash ^ ((u64)i * 0x9E3779B97F4A7C15ULL), 12);
|
|
candidates.push_back("UCE" + suffix);
|
|
}
|
|
|
|
for(auto& delimiter : candidates)
|
|
{
|
|
String terminator = ")" + delimiter + "\"";
|
|
if(content.find(terminator) == String::npos)
|
|
return(delimiter);
|
|
}
|
|
|
|
return("");
|
|
}
|
|
|
|
String compiler_cpp_string_literal(const String& content)
|
|
{
|
|
String delimiter = compiler_cpp_raw_string_delimiter(content);
|
|
if(delimiter != "" || content.find(")\"") == String::npos)
|
|
return("R\"" + delimiter + "(" + content + ")" + delimiter + "\"");
|
|
|
|
// This fallback is only reachable for deliberately adversarial content that
|
|
// contains every generated raw-string delimiter candidate.
|
|
return(json_escape(content));
|
|
}
|
|
|
|
void compiler_code_state_consume(CompilerCodeState& state, String& buffer, const String& content, u32& i)
|
|
{
|
|
char c = content[i];
|
|
char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0';
|
|
|
|
buffer.append(1, c);
|
|
|
|
if(state.inside_quote)
|
|
{
|
|
if(state.quote_char == c && (i == 0 || content[i-1] != '\\'))
|
|
state.inside_quote = false;
|
|
return;
|
|
}
|
|
|
|
if(state.inside_line_comment)
|
|
{
|
|
if(c == '\n')
|
|
state.inside_line_comment = false;
|
|
return;
|
|
}
|
|
|
|
if(state.inside_block_comment)
|
|
{
|
|
if(c == '*' && c1 == '/')
|
|
{
|
|
buffer.append(1, c1);
|
|
state.inside_block_comment = false;
|
|
i += 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(c == '/' && c1 == '/')
|
|
{
|
|
buffer.append(1, c1);
|
|
state.inside_line_comment = true;
|
|
i += 1;
|
|
return;
|
|
}
|
|
|
|
if(c == '/' && c1 == '*')
|
|
{
|
|
buffer.append(1, c1);
|
|
state.inside_block_comment = true;
|
|
i += 1;
|
|
return;
|
|
}
|
|
|
|
if(c == '"' || c == '\'')
|
|
{
|
|
state.inside_quote = true;
|
|
state.quote_char = c;
|
|
}
|
|
}
|
|
|
|
String compiler_capture_markup_literal(const String& content, u32& i)
|
|
{
|
|
String buffer = "";
|
|
u32 depth = 0;
|
|
bool inside_code = false;
|
|
CompilerCodeState code_state;
|
|
u32 j = i + 2;
|
|
|
|
while(j < content.length())
|
|
{
|
|
char c = content[j];
|
|
char c1 = (j + 1 < content.length()) ? content[j + 1] : '\0';
|
|
char c2 = (j + 2 < content.length()) ? content[j + 2] : '\0';
|
|
|
|
if(inside_code)
|
|
{
|
|
if(compiler_code_state_is_neutral(code_state) && c == '?' && c1 == '>')
|
|
{
|
|
buffer.append(1, c);
|
|
buffer.append(1, c1);
|
|
inside_code = false;
|
|
j += 2;
|
|
continue;
|
|
}
|
|
|
|
compiler_code_state_consume(code_state, buffer, content, j);
|
|
j += 1;
|
|
continue;
|
|
}
|
|
|
|
if(c == '<' && c1 == '?')
|
|
{
|
|
inside_code = true;
|
|
code_state = CompilerCodeState();
|
|
buffer.append(1, c);
|
|
buffer.append(1, c1);
|
|
j += 2;
|
|
continue;
|
|
}
|
|
|
|
if(c == '<' && c1 == '/' && c2 == '>')
|
|
{
|
|
if(depth > 0)
|
|
{
|
|
depth -= 1;
|
|
buffer.append("</>");
|
|
j += 3;
|
|
continue;
|
|
}
|
|
|
|
i = j + 2;
|
|
return(buffer);
|
|
}
|
|
|
|
if(c == '<' && c1 == '>')
|
|
{
|
|
depth += 1;
|
|
buffer.append("<>");
|
|
j += 2;
|
|
continue;
|
|
}
|
|
|
|
buffer.append(1, c);
|
|
j += 1;
|
|
}
|
|
|
|
i = content.length();
|
|
return(buffer);
|
|
}
|
|
|
|
String compiler_capture_code_until_php_close(const String& content, u32& i)
|
|
{
|
|
String buffer = "";
|
|
CompilerCodeState code_state;
|
|
|
|
while(i < content.length())
|
|
{
|
|
char c = content[i];
|
|
char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0';
|
|
|
|
if(compiler_code_state_is_neutral(code_state) && c == '?' && c1 == '>')
|
|
{
|
|
i += 1;
|
|
return(buffer);
|
|
}
|
|
|
|
compiler_code_state_consume(code_state, buffer, content, i);
|
|
i += 1;
|
|
}
|
|
|
|
return(buffer);
|
|
}
|
|
|
|
void compiler_append_text_literal_output(String& parsed_content, String& literal_buffer)
|
|
{
|
|
if(literal_buffer == "")
|
|
return;
|
|
parsed_content.append("print(" + compiler_cpp_string_literal(literal_buffer) + ");");
|
|
literal_buffer.clear();
|
|
}
|
|
|
|
String compiler_process_text_literal(Request* context, SharedUnit* su, String content)
|
|
{
|
|
String parsed_content;
|
|
String code_buffer = "";
|
|
String literal_buffer = "";
|
|
CompilerCodeState code_state;
|
|
bool inside_code = false;
|
|
bool is_field = false;
|
|
bool escape_field = false;
|
|
|
|
for(u32 i = 0; i < content.length(); i++)
|
|
{
|
|
char c = content[i];
|
|
char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0';
|
|
char c2 = (i + 2 < content.length()) ? content[i + 2] : '\0';
|
|
|
|
if(!inside_code)
|
|
{
|
|
if(c == '<' && c1 == '?')
|
|
{
|
|
inside_code = true;
|
|
code_buffer = "";
|
|
code_state = CompilerCodeState();
|
|
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
|
if(c2 == '=')
|
|
{
|
|
is_field = true;
|
|
escape_field = true;
|
|
i += 2;
|
|
}
|
|
else if(c2 == ':')
|
|
{
|
|
is_field = true;
|
|
escape_field = false;
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
is_field = false;
|
|
escape_field = false;
|
|
i += 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
literal_buffer.append(1, c);
|
|
continue;
|
|
}
|
|
|
|
if(compiler_code_state_is_neutral(code_state) && c == '?' && c1 == '>')
|
|
{
|
|
inside_code = false;
|
|
i += 1;
|
|
if(is_field)
|
|
{
|
|
if(escape_field)
|
|
{
|
|
parsed_content.append(
|
|
"print(html_escape( " +
|
|
code_buffer +
|
|
" )); "
|
|
);
|
|
}
|
|
else
|
|
{
|
|
parsed_content.append(
|
|
"print( " +
|
|
code_buffer +
|
|
" ); "
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parsed_content.append(code_buffer);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(compiler_code_state_is_neutral(code_state) && c == '<' && c1 == '>')
|
|
{
|
|
String nested_markup = compiler_capture_markup_literal(content, i);
|
|
code_buffer.append(compiler_process_text_literal(context, su, nested_markup));
|
|
continue;
|
|
}
|
|
|
|
compiler_code_state_consume(code_state, code_buffer, content, i);
|
|
}
|
|
|
|
if(literal_buffer != "")
|
|
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
|
|
|
return(parsed_content);
|
|
}
|
|
|
|
bool compiler_line_starts_entrypoint(String trimmed, String macro_name)
|
|
{
|
|
if(trimmed.rfind(macro_name + "(", 0) == 0)
|
|
return(true);
|
|
return(trimmed.rfind(macro_name + ":", 0) == 0 && trimmed.find("(") != String::npos);
|
|
}
|
|
|
|
bool compiler_line_starts_fragmentable_entrypoint(String trimmed, String& kind)
|
|
{
|
|
if(compiler_line_starts_entrypoint(trimmed, "ONCE"))
|
|
{
|
|
kind = "ONCE";
|
|
return(true);
|
|
}
|
|
if(compiler_line_starts_entrypoint(trimmed, "RENDER"))
|
|
{
|
|
kind = "RENDER";
|
|
return(true);
|
|
}
|
|
if(compiler_line_starts_entrypoint(trimmed, "COMPONENT"))
|
|
{
|
|
kind = "COMPONENT";
|
|
return(true);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
String compiler_fragment_capture_prelude(String slot)
|
|
{
|
|
return("\nUceFragmentCapture __uce_fragment_capture(context, " + compiler_cpp_string_literal(slot) + ");\n");
|
|
}
|
|
|
|
String compiler_rewrite_fragment_attributes(String content)
|
|
{
|
|
StringList lines = split(content, "\n");
|
|
String result;
|
|
for(u32 i = 0; i < lines.size(); i++)
|
|
{
|
|
String line = lines[i];
|
|
String kind;
|
|
if(!compiler_line_starts_fragmentable_entrypoint(trim(line), kind))
|
|
{
|
|
result += line;
|
|
if(i + 1 < lines.size())
|
|
result += "\n";
|
|
continue;
|
|
}
|
|
|
|
String slot = (kind == "ONCE" ? "once" : "");
|
|
bool has_fragment_attr = false;
|
|
u32 j = i + 1;
|
|
while(j < lines.size())
|
|
{
|
|
String attr_line = lines[j];
|
|
String attr_trimmed = trim(attr_line);
|
|
if(attr_trimmed.rfind("@fragment", 0) == 0 && (attr_trimmed.length() == 9 || isspace((unsigned char)attr_trimmed[9])))
|
|
{
|
|
slot = trim(attr_trimmed.substr(9));
|
|
has_fragment_attr = true;
|
|
j++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
bool should_capture = (slot != "") && (kind == "ONCE" || has_fragment_attr);
|
|
if(!should_capture)
|
|
{
|
|
result += line;
|
|
if(j < lines.size())
|
|
result += "\n";
|
|
i = j - 1;
|
|
continue;
|
|
}
|
|
|
|
auto declaration_brace_pos = line.find("{");
|
|
if(declaration_brace_pos != String::npos)
|
|
{
|
|
result += line.substr(0, declaration_brace_pos + 1) + compiler_fragment_capture_prelude(slot) + line.substr(declaration_brace_pos + 1);
|
|
if(j < lines.size())
|
|
result += "\n";
|
|
i = j - 1;
|
|
continue;
|
|
}
|
|
|
|
// The body must open on the next non-blank line after the attribute
|
|
// lines; anything else (e.g. literal text that merely starts with
|
|
// "ONCE(") is not an entry point and passes through untouched.
|
|
u32 body_index = j;
|
|
while(body_index < lines.size() && trim(lines[body_index]) == "")
|
|
body_index++;
|
|
bool body_opens = body_index < lines.size() && trim(lines[body_index]).rfind("{", 0) == 0;
|
|
if(!body_opens)
|
|
{
|
|
result += line;
|
|
if(i + 1 < lines.size())
|
|
result += "\n";
|
|
continue;
|
|
}
|
|
|
|
result += line;
|
|
result += "\n";
|
|
for(u32 k = j; k < body_index; k++)
|
|
result += lines[k] + "\n";
|
|
String body_line = lines[body_index];
|
|
auto brace_pos = body_line.find("{");
|
|
result += body_line.substr(0, brace_pos + 1) + compiler_fragment_capture_prelude(slot) + body_line.substr(brace_pos + 1);
|
|
if(body_index + 1 < lines.size())
|
|
result += "\n";
|
|
i = body_index;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
bool compiler_rewrite_named_entrypoint_line(String& line, String macro_prefix, String symbol_prefix)
|
|
{
|
|
u32 indent_length = 0;
|
|
while(indent_length < line.length() && isspace(line[indent_length]))
|
|
indent_length += 1;
|
|
|
|
String indent = line.substr(0, indent_length);
|
|
String trimmed = trim(line);
|
|
if(trimmed.rfind(macro_prefix, 0) != 0)
|
|
return(false);
|
|
|
|
String signature = trimmed.substr(macro_prefix.length());
|
|
auto open_paren_pos = signature.find("(");
|
|
if(open_paren_pos == String::npos)
|
|
return(false);
|
|
|
|
String render_name = trim(signature.substr(0, open_paren_pos));
|
|
String render_signature = signature.substr(open_paren_pos);
|
|
if(render_name == "")
|
|
return(false);
|
|
|
|
line = indent + "EXPORT void " + symbol_prefix + "_" + safe_name(render_name) + render_signature;
|
|
return(true);
|
|
}
|
|
|
|
String compiler_rewrite_named_render_syntax(String content)
|
|
{
|
|
String result = "";
|
|
String current_line = "";
|
|
|
|
auto flush_line = [&]() {
|
|
if(current_line.length() == 0)
|
|
return;
|
|
|
|
String line = current_line;
|
|
String line_break = "";
|
|
if(line.length() > 0 && line.back() == '\n')
|
|
{
|
|
line_break = "\n";
|
|
line.pop_back();
|
|
}
|
|
|
|
compiler_rewrite_named_entrypoint_line(line, "RENDER:", UCE_NAMED_RENDER_SYMBOL) ||
|
|
compiler_rewrite_named_entrypoint_line(line, "COMPONENT:", UCE_NAMED_COMPONENT_SYMBOL) ||
|
|
compiler_rewrite_named_entrypoint_line(line, "SERVE_HTTP:", UCE_NAMED_SERVE_HTTP_SYMBOL);
|
|
|
|
result += line + line_break;
|
|
current_line = "";
|
|
};
|
|
|
|
for(auto c : content)
|
|
{
|
|
current_line.append(1, c);
|
|
if(c == '\n')
|
|
flush_line();
|
|
}
|
|
flush_line();
|
|
|
|
return(result);
|
|
}
|
|
|
|
String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String content)
|
|
{
|
|
String parsed_content =
|
|
"#include \"uce_lib.h\" \n"+
|
|
file_get_contents(
|
|
context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]
|
|
)+
|
|
"#line 1\n";
|
|
CompilerCodeState code_state;
|
|
String current_line = "";
|
|
String literal_buffer = "";
|
|
bool inside_literal = false;
|
|
|
|
for(u32 i = 0; i < content.length(); i++)
|
|
{
|
|
char c = content[i];
|
|
char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0';
|
|
char c2 = (i + 2 < content.length()) ? content[i + 2] : '\0';
|
|
|
|
if(inside_literal)
|
|
{
|
|
if(c == '<' && c1 == '?' && (c2 == '=' || c2 == ':'))
|
|
{
|
|
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
|
bool escape_field = (c2 == '=');
|
|
i += 3;
|
|
String field_code = compiler_capture_code_until_php_close(content, i);
|
|
if(escape_field)
|
|
parsed_content.append("print(html_escape( " + field_code + " )); ");
|
|
else
|
|
parsed_content.append("print( " + field_code + " ); ");
|
|
continue;
|
|
}
|
|
|
|
if(c == '<' && c1 == '?')
|
|
{
|
|
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
|
inside_literal = false;
|
|
code_state = CompilerCodeState();
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if(c == '<' && c1 == '/' && c2 == '>')
|
|
{
|
|
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
|
inside_literal = false;
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
literal_buffer.append(1, c);
|
|
continue;
|
|
}
|
|
|
|
if(compiler_code_state_is_neutral(code_state) && ((c == '<' && c1 == '>') || (c == '?' && c1 == '>')))
|
|
{
|
|
inside_literal = true;
|
|
literal_buffer = "";
|
|
current_line = "";
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
current_line.append(1, c);
|
|
|
|
compiler_code_state_consume(code_state, parsed_content, content, i);
|
|
|
|
if(c == 10 && current_line.substr(0, 6) == "#load ")
|
|
{
|
|
parsed_content.resize(parsed_content.length() - current_line.length());
|
|
nibble(current_line, "\"");
|
|
String unit_name = nibble(current_line, "\"");
|
|
String resolved_unit = unit_name;
|
|
if(resolved_unit != "" && resolved_unit[0] != '/')
|
|
resolved_unit = expand_path(resolved_unit, su->src_path);
|
|
SharedUnit* sub_su = (resolved_unit == "" ? 0 : get_shared_unit(context, resolved_unit));
|
|
if(sub_su)
|
|
parsed_content.append("#include \"" + sub_su->bin_path + "/" + sub_su->pre_file_name + "\"\n");
|
|
}
|
|
else
|
|
{
|
|
String trimmed_line = trim(current_line);
|
|
if(c == 10 && trimmed_line.length() > 7 && trimmed_line.substr(0, 6) == "EXPORT" && isspace(trimmed_line[6]))
|
|
{
|
|
current_line = "";
|
|
auto end_declaration_pos = content.find("{", i);
|
|
if(end_declaration_pos != std::string::npos)
|
|
{
|
|
parsed_content.append(1, '\n');
|
|
String declaration = trim(content.substr(i, end_declaration_pos - i));
|
|
su->api_declarations.push_back(declaration+";\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(c == 10)
|
|
current_line = "";
|
|
}
|
|
|
|
if(inside_literal)
|
|
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
|
|
|
return(parsed_content);
|
|
}
|
|
|
|
}
|
|
|
|
String compiler_preprocess_source(Request* context, SharedUnit* su, String content)
|
|
{
|
|
content = compiler_rewrite_fragment_attributes(content);
|
|
content = compiler_rewrite_named_render_syntax(content);
|
|
return(compiler_preprocess_shared_unit_char_wise(context, su, content));
|
|
}
|