Signature
UCE source preprocessing
UCE runs a small custom source-to-source preprocessor before Clang sees a .uce or .ws.uce file.
The template rewriting implementation lives in src/lib/compiler-parser.cpp, with orchestration in src/lib/compiler.cpp. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands literal output, inline code islands, #load, and EXPORT harvesting, then writes a generated .cpp file and compiles that file into a WebAssembly side module.
Syntax
<> ... </>enters literal-output mode.?> ... <?also enters literal-output mode.The open and close pairs are interchangeable, so
<> ... <?,?> ... </>, and the traditional matched forms all work.Inside literal output,
<? ... ?>emits raw C++.Inside a literal block,
<?= expression ?>emitsprint(html_escape(expression));.Inside a literal block,
<?: expression ?>emitsprint(expression);without HTML escaping.#load "other.uce"injects another UCE unit at compile time.RENDER(Request& context),COMPONENT(Request& context),CLI(Request& context),ONCE(Request& context),INIT(Request& context), andWS(Request& context)are normal C++ macros fromsrc/lib/compiler.h.ONCE,RENDER, andCOMPONENTmay be followed by a preprocessor attribute line such as@fragment headbefore the opening{. The handler's output is then captured and appended tocontext.call["fragments"]["head"]instead of being emitted at the call site.ONCEdefaults to@fragment oncewhen no fragment is specified.COMPONENT:NAME(Request& context)is rewritten by the custom pass into an exported named component handler.EXPORTis also a normal C++ macro, but the custom pass additionally records exported declarations for metadata.
Pipeline
The generated file starts by including the logical runtime header
uce_lib.h; the wasm unit compile script provides the include path.It then inlines the configured setup template from
SETUP_TEMPLATE(by defaultscripts/setup.h.template), which defines the internal hook__uce_set_current_request(Request*).It inserts
#line 1before page code so compiler diagnostics point back to the original.ucefile.Each literal region is rewritten into one or more
print(R"...( ... )...");calls using a safe raw-string delimiter selected for that literal content.<>and?>both switch from code mode into literal output.</>and<?both switch from literal output back into code mode.<? ... ?>temporarily breaks out of literal printing, emits the enclosed C++ unchanged, then resumes literal output.<?= ... ?>becomesprint(html_escape(...));.<?: ... ?>becomesprint(...);and is intended for trusted markup or already-escaped content.#load "file.uce"is replaced with a generated C++#includethat points at the loaded unit's preprocessed.cppfile underBIN_DIRECTORY.Lines beginning with
EXPORTare scanned so their declarations can be written to a sibling.exports.txtfile.@fragment slot-namelines immediately followingONCE,RENDER, orCOMPONENTare removed and replaced with an output-capture guard at the start of the handler body.Lines beginning with
RENDER:NAME(...)are rewritten into exported__uce_render_NAME(...)functions.Lines beginning with
COMPONENT:NAME(...)are rewritten into exported__uce_component_NAME(...)functions for the component helpers.The final generated source is written to
BIN_DIRECTORY + src_path + "/" + source_file + ".cpp".scripts/compile_wasm_unitthen compiles that generated.cppintosource_file + ".wasm"as a PIC WebAssembly side module.When a worker instantiates the compiled unit, the runtime checks for
INIT(Request& context)and calls it once for that worker-side instance.On each request, the first time a given unit is entered through
RENDER(),CLI(), or anyCOMPONENT...handler, the runtime checks forONCE(Request& context)and calls it before the selected handler.
Generated Files
For a source file like /some/path/page.uce, the preprocessor produces:
generated C++:
BIN_DIRECTORY/some/path/page.uce.cppwasm side module:
BIN_DIRECTORY/some/path/page.uce.wasmexport list:
BIN_DIRECTORY/some/path/page.uce.exports.txt
Examples
Literal output with escaped data:
The same thing can also be written with PHP-style literal delimiters:
Roughly becomes:
Literal output with trusted unescaped markup:
Roughly becomes:
Compile-time composition:
The loaded file is resolved relative to the current source file unless the path is already absolute.
One-time worker initialization plus request-local setup:
One-time page assets captured for a template-controlled slot:
The page template can then render context.call["fragments"]["head"] inside <head>.
Rules
Literal mode can start on either
<>or?>.Literal mode can end on either
</>or<?.Literal delimiters are interchangeable; the parser treats them as one shared code-vs-literal state machine rather than as separate nested block types.
#loadis recognized only when the current line starts with#loadat column 1.EXPORTharvesting only triggers when the current line starts withEXPORTat column 1 and is followed by whitespace.Relative
#loadpaths are expanded against the including unit's source directory.unit_render()andunit_call()are runtime APIs.#loadis a compile-time composition feature.INIT()runs when the wasm unit is instantiated by a worker during a request-triggered load, so it still receives a validRequest& context.ONCE()is tracked per request and per resolved unit file. A file entered multiple times in one request only runsONCE()once.
Limitations
This pass is character-wise, not a full parser.
Outside literal blocks it tracks C++ quotes and comments while deciding whether
<>or?>should open literal mode.It does not understand comments, raw string literals, templates, or general C++ token structure.
Inside literal blocks it tracks quotes and comments while scanning
<? ... ?>,<?= ... ?>, and<?: ... ?>islands so quoted?>text does not close those islands early.Literal output is emitted through C++ string literals generated by the preprocessor. The preprocessor chooses a raw-string delimiter that does not occur in the literal content, so literal text may safely contain the ordinary raw-string terminator sequence
)".#loaddepends on the target unit's generated.cppexisting and being compilable. If the target cannot be preprocessed or compiled correctly, the including file will fail to compile as well.
Debugging
Inspect the generated file under
BIN_DIRECTORYfirst. That file shows the exact C++ produced by the UCE preprocessor.Compiler errors usually point back to the
.ucesource because the preprocessor inserts#line 1, but the generated.cppis still the best place to inspect expansion problems.Compile failures are reported with the source path, generated C++ path, compile-output artifact path, an excerpt when UCE can identify a line, and the raw compiler output from the configured compile script.
Runtime request failures include the request/script path, generated C++ path, a hint about inspecting template delimiters and recent component/unit calls, and a native trace when available.
If a
#loadinclude looks wrong, check the current file's directory, the configuredBIN_DIRECTORY, and whether the loaded page already produced its own generated.cpp.
Example
// The preprocessor lets you mix C++ logic with output. Code generates markup:
StringList items = split("apples,pears,plums", ",");
String html = "";
items.each([&](String item) { html += "<li>" + item + "</li>"; });
print(html, "\n");
<li>apples</li><li>pears</li><li>plums</li>