working on documentation and more API functions

This commit is contained in:
udo
2026-04-29 12:09:37 +00:00
parent cd445f3c9b
commit 9f7625c7fd
94 changed files with 1896 additions and 458 deletions
+2
View File
@@ -38,6 +38,7 @@ RENDER(Request& context)
<? render_card("dtree.uce", "DTree", "Dynamic hierarchical data tree"); ?>
<? render_card("json.uce", "JSON", "Parse and encode JSON data"); ?>
<? render_card("preprocessor-comments.uce", "Preprocessor Comments", "Regression coverage for comment parsing in templates"); ?>
<? render_card("regex.uce", "Regular Expressions", "PCRE2 matching, captures, replacement, and splitting"); ?>
<? render_card("string.uce", "String", "String operations"); ?>
<? render_card("str_replace.uce", "String Replace", "Search and replace in strings"); ?>
<? render_card("utf8.uce", "UTF-8", "Unicode string handling"); ?>
@@ -61,6 +62,7 @@ RENDER(Request& context)
<div class="grid-heading">Advanced</div>
<? render_card("call_file.uce", "unit_call()", "Dynamic file inclusion"); ?>
<? render_card("components.uce", "Components", "Reusable component system"); ?>
<? render_card("once-init.uce", "ONCE / INIT", "Unit lifecycle hooks for worker load and request entry"); ?>
<? render_card("markdown.uce", "Markdown", "Markdown parsing with components"); ?>
<? render_card("script.uce", "Script", "UCE script integration"); ?>
<? render_card("websockets.ws.uce", "WebSockets", "Real-time WebSocket chat"); ?>
+53
View File
@@ -0,0 +1,53 @@
static s64 demo_worker_init_count = 0;
static s64 demo_component_hits = 0;
INIT(Request& context)
{
(void)context;
demo_worker_init_count += 1;
}
ONCE(Request& context)
{
context.call["once_hits"] = context.call["once_hits"].to_s64() + 1;
}
COMPONENT:PROBE(Request& context)
{
demo_component_hits += 1;
<>
<div class="banner">
<strong><?= context.props["label"].to_string() ?></strong>
<div>worker INIT count for this loaded unit: <?= (u64)demo_worker_init_count ?></div>
<div>request ONCE count for this request: <?= context.call["once_hits"].to_u64() ?></div>
<div>component handler calls served by this worker copy: <?= (u64)demo_component_hits ?></div>
</div>
</>
}
RENDER(Request& context)
{
DTree first;
first["label"] = "First component call";
DTree second;
second["label"] = "Second component call in the same request";
DTree third;
third["label"] = "Third component call through unit_call(\"COMPONENT:PROBE\")";
<><html>
<link rel="stylesheet" href='style.css?v=<?= time() ?>'></link>
<h1>
<a href="index.uce">UCE Test</a>:
ONCE() and INIT()
</h1>
<p>
This page calls the same named component twice. `ONCE()` should only run once for the request, while `INIT()` should stay stable for the currently loaded worker copy.
</p>
<?: component(":PROBE", first, context) ?>
<?: component(":PROBE", second, context) ?>
<? unit_call("once-init.uce", "COMPONENT:PROBE", &third); ?>
</html></>
}
+2 -1
View File
@@ -11,6 +11,7 @@ RENDER(Request& context)
</h1>
<p>This page exists to prove the template parser ignores quotes and template markers that appear inside C++ comments.</p>
<p>It also renders a literal raw-string terminator sequence safely: <code>)"</code>.</p>
<?
// Regression: this comment's apostrophe must not swallow the later ?> marker.
@@ -36,4 +37,4 @@ RENDER(Request& context)
<pre><?= var_dump(context.params) ?></pre>
</details>
</>
}
}
+74
View File
@@ -0,0 +1,74 @@
#include "demo_guard.h"
void regex_row(String label, String result, String expect)
{
bool pass = (result == expect);
<><tr>
<td class="test-label"><?= label ?></td>
<td class="test-result"><code><?= result ?></code></td>
<td class="test-status <?= pass ? "pass" : "fail" ?>"><?= pass ? "pass" : "expected: " + expect ?></td>
</tr></>
}
void regex_bool_row(String label, bool result, bool expect)
{
regex_row(label, result ? "true" : "false", expect ? "true" : "false");
}
RENDER(Request& context)
{
String text = "Contact ops@example.test or tag #uce, #docs, and café.";
DTree first_email = regex_search("(?<user>[A-Za-z0-9._%+-]+)@(?<host>[A-Za-z0-9.-]+)", text);
DTree tags = regex_search_all("#(?<tag>[A-Za-z0-9_]+)", text);
StringList pieces = regex_split("\\s*,\\s*", "uce, components, markdown");
<>
<link rel="stylesheet" href='style.css'></link>
<style>
table.tests { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
table.tests td { padding: 6px 12px; border-bottom: 1px solid rgba(255,255,255,0.06); font-family: var(--font-mono); font-size: 0.88rem; }
.test-label { color: var(--text-dim); white-space: nowrap; width: 1%; }
.test-result code { background: var(--bg-code); padding: 2px 8px; border-radius: 4px; }
.test-status.pass { color: #6f6; width: 1%; }
.test-status.fail { color: #f66; }
</style>
<h1>
<a href="index.uce">UCE Test</a>:
Regular Expressions
</h1>
<p>UCE regex functions use PCRE2 and return ordinary UCE strings, lists, and DTree values.</p>
<p>sample = <code><?= text ?></code></p>
<h2>Validation And Search</h2>
<table class="tests"><?
regex_bool_row("regex_match(\"[A-Z][a-z]+\", \"Alice\")", regex_match("[A-Z][a-z]+", "Alice"), true);
regex_bool_row("regex_match(\"[A-Z][a-z]+\", \"Alice!\")", regex_match("[A-Z][a-z]+", "Alice!"), false);
regex_bool_row("regex_search(\"example\", text)[\"matched\"]", first_email["matched"].to_bool(), true);
regex_row("first_email[\"match\"]", first_email["match"].to_string(), "ops@example.test");
regex_row("first_email[\"named\"][\"user\"]", first_email["named"]["user"].to_string(), "ops");
regex_row("first_email[\"named\"][\"host\"]", first_email["named"]["host"].to_string(), "example.test");
regex_bool_row("regex_match(\"\\\\p{L}+\", \"café\")", regex_match("\\p{L}+", "café"), true);
?></table>
<h2>All Matches</h2>
<table class="tests"><?
regex_row("regex_search_all hashtags count", tags["count"].to_string(), "2.000000");
regex_row("first hashtag", tags["matches"]["0"]["named"]["tag"].to_string(), "uce");
regex_row("second hashtag", tags["matches"]["1"]["named"]["tag"].to_string(), "docs");
?></table>
<h2>Replace And Split</h2>
<table class="tests"><?
regex_row("regex_replace(\"#([A-Za-z0-9_]+)\", \"<tag>$1</tag>\", \"#uce\")", regex_replace("#([A-Za-z0-9_]+)", "<tag>$1</tag>", "#uce"), "<tag>uce</tag>");
regex_row("join(regex_split(\"\\\\s*,\\\\s*\", ...), \"|\")", join(pieces, "|"), "uce|components|markdown");
regex_row("case-insensitive flag", regex_search("uce", "Hello UCE", "i")["match"].to_string(), "UCE");
?></table>
<h2>Structured Result</h2>
<pre><?= json_encode(first_email) ?></pre>
<p><a href="../doc/index.uce?p=regex_search">Read the regex API documentation</a></p>
</>
}
+7
View File
@@ -0,0 +1,7 @@
Regular Expressions
regex_match
regex_search
regex_search_all
regex_replace
regex_split
+5
View File
@@ -17,6 +17,11 @@ split
split_space
split_utf8
replace
regex_match
regex_search
regex_search_all
regex_replace
regex_split
to_lower
to_upper
trim
+1 -1
View File
@@ -1,6 +1,6 @@
Task API
kill
task
task_repeat
task_pid
task_kill
+6 -1
View File
@@ -1,8 +1,13 @@
Types
0_Request
array_merge
DTree
0_DTree
get_by_path
set_status
String
StringList
StringMap
to_bool
to_f64
to_u64
+38 -130
View File
@@ -1,104 +1,25 @@
struct DocPage {
String title;
String content;
StringList sig_lines;
StringList param_lines;
StringList see_lines;
};
#include "lib/doc_page.h"
String doc_default_title(String page)
void render_doc_page_link(String page, String label = "", String badge = "")
{
String page_title = page;
if(page_title.length() > 1 && page_title[1] == '_')
nibble(page_title, "_");
return(page_title);
}
page = trim(page);
if(page == "")
return;
if(label == "")
label = doc_index_label(page);
if(badge == "")
badge = doc_page_kind_badge(doc_page_kind(page));
String doc_markdown_inline(String text)
{
text = trim(text);
if(text == "")
return("");
String html = markdown_to_html(text);
if(html.length() >= 7 && html.substr(0, 3) == "<p>" && html.substr(html.length() - 4) == "</p>")
return(html.substr(3, html.length() - 7));
return(html);
}
String doc_legacy_heading(String section)
{
if(section == "desc")
return("");
if(section == "related")
return("## PHP & JS Equivalents");
return("## " + section);
}
DocPage load_doc_page(String page)
{
DocPage result;
StringList lines = split(file_get_contents("pages/" + page + ".txt"), "\n");
String current_section = "";
bool content_mode = false;
StringList content_lines;
for(auto line : lines)
?><a href="index.uce?p=<?= uri_encode(page) ?>"><?= label ?><?
if(badge != "")
{
if(!content_mode && line != "" && line.substr(0, 1) == ":")
{
String section = trim(line.substr(1));
if(section == "title" || section == "sig" || section == "params" || section == "see")
{
current_section = section;
continue;
}
if(section == "content")
{
content_mode = true;
current_section = "content";
continue;
}
current_section = "legacy";
String heading = doc_legacy_heading(section);
if(heading != "")
{
if(content_lines.size() > 0 && content_lines.back() != "")
content_lines.push_back("");
content_lines.push_back(heading);
content_lines.push_back("");
}
continue;
}
if(current_section == "title")
{
if(result.title != "")
result.title += "\n";
result.title += line;
}
else if(current_section == "sig")
{
result.sig_lines.push_back(line);
}
else if(current_section == "params")
{
result.param_lines.push_back(line);
}
else if(current_section == "see")
{
if(trim(line) != "")
result.see_lines.push_back(trim(line));
}
else
{
content_lines.push_back(line);
}
?><span class="badge"><?= badge ?></span><?
}
result.content = join(content_lines, "\n");
result.title = trim(result.title);
return(result);
else
{
?><span class="dim">()</span><?
}
?></a><?
}
void render_doc_params(StringList param_lines)
@@ -130,13 +51,19 @@ void render_see_section(String name)
s32 idx = 0;
for(auto line : lines)
{
line = trim(line);
if(line == "")
{
idx += 1;
continue;
}
if(idx == 0)
{
<><div class="category"><h3><?= line ?></h3><ul></>
}
else if(line != "")
else
{
<><li><a href="index.uce?p=<?= uri_encode(line) ?>"><?= line ?><span class="dim">()</span></a></li></>
?><li><? render_doc_page_link(line); ?></li><?
}
idx += 1;
}
@@ -154,11 +81,19 @@ void render_doc_see_links(StringList see_lines)
{
if(sl[0] == '>')
{
render_see_section(sl.substr(1));
String target = trim(sl.substr(1));
if(doc_has_area(target))
{
render_see_section(target);
}
else if(doc_has_page(target))
{
?><div><? render_doc_page_link(target); ?></div><?
}
}
else
{
?><div><a href="index.uce?p=<?= trim(sl) ?>"><?= trim(sl) ?><span class="dim">()</span></a></div><?
?><div><? render_doc_page_link(trim(sl)); ?></div><?
}
}
?>
@@ -216,36 +151,9 @@ RENDER(Request& context)
for(auto file_name : ls("pages/"))
{
String ft = nibble(file_name, ".");
if(ft.substr(0, 2) == "0_")
{
String fn = ft;
String pre = nibble(fn, "_");
?>
<div class="func-item"><a href="?p=<?= uri_encode(ft) ?>"><?= fn ?><span class="badge">struct</span></a></div>
<?
}
else if(ft.substr(0, 2) == "1_")
{
String fn = ft;
String pre = nibble(fn, "_");
?>
<div class="func-item"><a href="?p=<?= uri_encode(ft) ?>"><?= fn ?><span class="badge">directive</span></a></div>
<?
}
else if(ft.substr(0, 2) == "3_")
{
String fn = ft;
String pre = nibble(fn, "_");
?>
<div class="func-item"><a href="?p=<?= uri_encode(ft) ?>"><?= fn ?><span class="badge">info</span></a></div>
<?
}
else
{
?>
<div class="func-item"><a href="?p=<?= uri_encode(ft) ?>"><?= ft ?><span class="dim">()</span></a></div>
<?
}
String label = doc_index_label(ft);
String badge = doc_page_kind_badge(doc_page_kind(ft));
?><div class="func-item"><? render_doc_page_link(ft, label, badge); ?></div><?
}
?></div>
</main>
+153
View File
@@ -0,0 +1,153 @@
#pragma once
struct DocPage {
String title;
String content;
StringList sig_lines;
StringList param_lines;
StringList see_lines;
};
enum class DocPageKind
{
function,
struct_page,
directive,
info
};
String doc_default_title(String page)
{
String page_title = page;
if(page_title.length() > 1 && page_title[1] == '_')
nibble(page_title, "_");
return(page_title);
}
String doc_markdown_inline(String text)
{
text = trim(text);
if(text == "")
return("");
String html = markdown_to_html(text);
if(html.length() >= 7 && html.substr(0, 3) == "<p>" && html.substr(html.length() - 4) == "</p>")
return(html.substr(3, html.length() - 7));
return(html);
}
String doc_legacy_heading(String section)
{
if(section == "desc")
return("");
if(section == "related")
return("## PHP & JS Equivalents");
return("## " + section);
}
bool doc_has_area(String name)
{
return(file_exists("areas/" + name + ".txt"));
}
bool doc_has_page(String name)
{
return(file_exists("pages/" + name + ".txt"));
}
DocPageKind doc_page_kind(String page)
{
if(page.substr(0, 2) == "0_")
return(DocPageKind::struct_page);
if(page.substr(0, 2) == "1_")
return(DocPageKind::directive);
if(page.substr(0, 2) == "3_")
return(DocPageKind::info);
return(DocPageKind::function);
}
String doc_page_kind_badge(DocPageKind kind)
{
if(kind == DocPageKind::struct_page)
return("struct");
if(kind == DocPageKind::directive)
return("directive");
if(kind == DocPageKind::info)
return("info");
return("");
}
String doc_index_label(String page)
{
String label = page;
auto kind = doc_page_kind(page);
if(kind == DocPageKind::struct_page || kind == DocPageKind::directive || kind == DocPageKind::info)
nibble(label, "_");
return(label);
}
DocPage load_doc_page(String page)
{
DocPage result;
StringList lines = split(file_get_contents("pages/" + page + ".txt"), "\n");
String current_section = "";
bool content_mode = false;
StringList content_lines;
for(auto line : lines)
{
if(!content_mode && line != "" && line.substr(0, 1) == ":")
{
String section = trim(line.substr(1));
if(section == "title" || section == "sig" || section == "params" || section == "see")
{
current_section = section;
continue;
}
if(section == "content")
{
content_mode = true;
current_section = "content";
continue;
}
current_section = "legacy";
String heading = doc_legacy_heading(section);
if(heading != "")
{
if(content_lines.size() > 0 && content_lines.back() != "")
content_lines.push_back("");
content_lines.push_back(heading);
content_lines.push_back("");
}
continue;
}
if(current_section == "title")
{
if(result.title != "")
result.title += "\n";
result.title += line;
}
else if(current_section == "sig")
{
result.sig_lines.push_back(line);
}
else if(current_section == "params")
{
result.param_lines.push_back(line);
}
else if(current_section == "see")
{
if(trim(line) != "")
result.see_lines.push_back(trim(line));
}
else
{
content_lines.push_back(line);
}
}
result.content = join(content_lines, "\n");
result.title = trim(result.title);
return(result);
}
+5
View File
@@ -6,6 +6,11 @@ Request& context;
:see
>types
set_status
component
unit_render
ws_message
session_start
:content
`Request& context` is the request-local state object passed into UCE handlers. It carries incoming request data, response state, runtime metadata, and helper trees such as `context.cfg`, `context.props`, and `context.connection`.
+6
View File
@@ -8,6 +8,8 @@ COMPONENT(Request& context)
>component
>component_render
>1_RENDER
>1_INIT
>1_ONCE
>1_WS
:content
@@ -15,6 +17,10 @@ Defines the default component entrypoint for the current `.uce` file.
`component()` and `component_render()` call `COMPONENT(Request& context)` by default. Named component entrypoints use `COMPONENT:NAME(Request& context)`.
If the same file defines `ONCE(Request& context)`, that hook runs once per request before the first `COMPONENT()` or `COMPONENT:NAME()` call for that unit.
If the file defines `INIT(Request& context)`, that hook runs once when the worker loads the compiled unit into memory.
## Why It Exists
This keeps page rendering and component rendering separate:
+50
View File
@@ -0,0 +1,50 @@
:title
INIT
:sig
INIT(Request& context)
:see
>1_COMPONENT
>1_ONCE
>1_RENDER
>1_WS
>3_C++ Preprocessor
>unit_call
:content
Defines a worker-load hook for the current `.uce` unit.
When a worker loads the unit's compiled shared object into memory, the runtime checks whether the unit exposes `INIT(Request& context)`. If it does, the hook runs once for that load before the unit begins serving later requests from that in-memory copy.
Because UCE usually loads units on demand during a request, `INIT()` still receives a valid `Request& context`. Use it for worker-local initialization, not for request-local state that should reset each request.
## Typical Uses
- warm caches or parse static lookup data into globals
- initialize worker-local helper state for expensive component trees
- perform one-time registration work for that unit's in-memory copy
## Example
```cpp
std::map<String, String> cached_labels;
INIT(Request& context)
{
if(cached_labels.empty())
cached_labels["ready"] = "Ready";
}
COMPONENT(Request& context)
{
<>
<p><?= cached_labels["ready"] ?></p>
</>
}
```
## Related Concepts
- PHP: opcode-cache preload or one-time bootstrap work per worker process
- JavaScript / Node.js: module-load initialization or lazy singleton setup
+49
View File
@@ -0,0 +1,49 @@
:title
ONCE
:sig
ONCE(Request& context)
:see
>1_COMPONENT
>1_INIT
>1_RENDER
>1_WS
>3_C++ Preprocessor
>unit_call
:content
Defines a request-local one-time hook for the current `.uce` unit.
When a request first enters a given file through `RENDER(Request& context)`, `COMPONENT(Request& context)`, or any `COMPONENT:NAME(Request& context)` handler, the runtime checks whether that unit exposes `ONCE(Request& context)`. If it does, the hook runs before the selected render or component handler.
`ONCE()` is tracked per request and per resolved unit file, so repeated component calls to the same file inside one request do not rerun it.
## Typical Uses
- prepare request-local derived state on `context.call`
- load request-scoped config or data needed by multiple named component handlers
- normalize shared props before the unit's first render/component call
## Example
```cpp
ONCE(Request& context)
{
context.call["card_defaults"]["tone"] = "info";
}
COMPONENT(Request& context)
{
<>
<div class="card card-<?= context.call["card_defaults"]["tone"] ?>">
<?: component(":BODY", context.props, context) ?>
</div>
</>
}
```
## Related Concepts
- PHP: per-request bootstrap work before a template or partial first runs
- JavaScript / Node.js: request-scoped lazy initialization before a route or component render
+9 -1
View File
@@ -6,6 +6,10 @@ RENDER(Request& context)
:see
>ob
>1_COMPONENT
>1_INIT
>1_ONCE
>1_WS
:content
Defines the main HTTP render handler for the current `.uce` page.
@@ -16,10 +20,14 @@ When a page is requested over HTTP, the runtime loads the target file and calls
The default page entrypoint is always the plain `RENDER(Request& context)` handler.
Reusable component handlers now live on `COMPONENT(Request& context)` and `COMPONENT:NAME(Request& context)`. The component helpers call those handlers, not `RENDER()`.
Reusable component handlers live on `COMPONENT(Request& context)` and `COMPONENT:NAME(Request& context)`. The component helpers call those handlers, not `RENDER()`.
The request environment is passed explicitly through `context`, including params, cookies, post data, session state, headers, uploaded files, and the current `context.props` tree.
If the file defines `ONCE(Request& context)`, the runtime calls that hook once per request before the first `RENDER()` or `COMPONENT...` entrypoint from that unit runs.
If the file defines `INIT(Request& context)`, the runtime calls that hook once when the worker loads the compiled unit into memory.
For a normal direct page request, `context.props` starts empty.
If the page is invoked from another UCE file via `unit_render(file_name, context)`, the callee receives that same `context`.
+4
View File
@@ -6,6 +6,10 @@ WS(Request& context)
:see
>websocket
>1_COMPONENT
>1_INIT
>1_ONCE
>1_RENDER
:content
Defines the WebSocket message handler for the current `.ws.uce` page.
+24 -5
View File
@@ -8,7 +8,7 @@ UCE source preprocessing
load
unit_render
unit_call
0_context
0_Request
1_COMPONENT
:content
@@ -25,7 +25,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
- Inside a literal block, `<?= expression ?>` emits `print(html_escape(expression));`.
- Inside a literal block, `<?: expression ?>` emits `print(expression);` without HTML escaping.
- `#load "other.uce"` injects another UCE unit at compile time.
- `RENDER(Request& context)`, `COMPONENT(Request& context)`, and `WS(Request& context)` are normal C++ macros from `src/lib/compiler.h`.
- `RENDER(Request& context)`, `COMPONENT(Request& context)`, `ONCE(Request& context)`, `INIT(Request& context)`, and `WS(Request& context)` are normal C++ macros from `src/lib/compiler.h`.
- `COMPONENT:NAME(Request& context)` is rewritten by the custom pass into an exported named component handler.
- `EXPORT` is also a normal C++ macro, but the custom pass additionally records exported declarations for metadata.
@@ -34,7 +34,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
- The generated file starts by including `COMPILER_SYS_PATH/src/lib/uce_lib.h`.
- It then inlines the configured setup template from `SETUP_TEMPLATE` (by default `scripts/setup.h.template`), which defines the internal hook `__uce_set_current_request(Request*)`.
- It inserts `#line 1` before page code so compiler diagnostics point back to the original `.uce` file.
- Each literal region is rewritten into one or more `print(R"( ... )");` calls.
- 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.
@@ -46,6 +46,8 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
- 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` then compiles that generated `.cpp` into `source_file + ".so"` with `clang++ -shared -std=c++20 ...`.
- When a worker loads the compiled unit into memory, the runtime checks for `INIT(Request& context)` and calls it once for that worker-side load.
- On each request, the first time a given unit is entered through `RENDER()` or any `COMPONENT...` handler, the runtime checks for `ONCE(Request& context)` and calls it before the render/component handler.
## Generated Files
@@ -113,15 +115,32 @@ RENDER(Request& context)
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:
```cpp
INIT(Request& context)
{
// load worker-local data, warm caches, or initialize globals for this unit
}
ONCE(Request& context)
{
// prepare request-local state before the first render/component call
context.call["page_title"] = "Demo";
}
```
## Rules
- Literal mode can start on either `<>` or `?>`.
- Literal mode can end on either `</>` or `<?`.
- Literal delimiters are interchangeable; the parser now treats them as one shared code-vs-literal state machine rather than as separate nested block types.
- Literal delimiters are interchangeable; the parser treats them as one shared code-vs-literal state machine rather than as separate nested block types.
- `#load` is recognized only when the current line starts with `#load ` at column 1.
- `EXPORT` harvesting only triggers when the current line starts with `EXPORT` at column 1 and is followed by whitespace.
- Relative `#load` paths are expanded against the including unit's source directory.
- `unit_render()` and `unit_call()` are runtime APIs. `#load` is a compile-time composition feature.
- `INIT()` runs when the shared object is loaded into a worker during a request-triggered load, so it still receives a valid `Request& context`.
- `ONCE()` is tracked per request and per resolved unit file. A file entered multiple times in one request only runs `ONCE()` once.
## Limitations
@@ -129,7 +148,7 @@ The loaded file is resolved relative to the current source file unless the path
- 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.
- Because literal output is emitted as a C++ raw string literal `R"( ... )"`, literal content must not contain the exact terminator sequence `)"` or the generated C++ will break.
- 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 `)"`.
- `#load` depends on the target unit's generated `.cpp` existing and being compilable. If the target cannot be preprocessed or compiled correctly, the including file will fail to compile as well.
## Debugging
+4
View File
@@ -3,6 +3,10 @@ String
:see
>types
>string
split_utf8
substr
html_escape
:content
Primary string type used throughout UCE.
+25
View File
@@ -0,0 +1,25 @@
:sig
StringList
:see
>types
String
split
split_space
split_utf8
join
regex_split
:content
Sequential container of `String` values.
`StringList` is an alias for `std::vector<String>`.
It is returned by split-style helpers such as `split()`, `split_space()`, `split_utf8()`, and `regex_split()`.
Use `join()` when you want to turn a `StringList` back into a single `String`.
Related:
- PHP: indexed arrays of strings
- JavaScript / Node.js: arrays of strings
+4
View File
@@ -3,6 +3,10 @@ StringMap
:see
>types
0_Request
parse_query
encode_query
array_merge
:content
Associative container mapping `String` keys to `String` values.
+3
View File
@@ -9,6 +9,9 @@ return value : merged result
:see
>types
0_DTree
StringMap
json_decode
:content
Merges two maps or trees using PHP-like merge behavior.
+68 -1
View File
@@ -3,6 +3,9 @@ String component(String name, [DTree props], [Request& context])
:see
>ob
>component_render
>1_COMPONENT
>1_RENDER
:content
Renders another `.uce` file as a component and returns the captured output as a `String`.
@@ -21,13 +24,17 @@ The default handler is `COMPONENT(Request& context)`.
When `name` starts with a colon, such as `:BODY`, the target resolves against the current `.uce` file so component files can call their own named handlers without repeating the file name.
When a component unit defines `ONCE(Request& context)`, the runtime calls that hook once per request, per resolved component file, before the first `COMPONENT()` or `COMPONENT:NAME()` handler from that file runs.
## Resolution Order
- exact file name
- exact file name with `.uce`
- the same two forms under `components/`
## Example
## Common Patterns
Default component handler:
```cpp
DTree props;
@@ -36,6 +43,66 @@ props["title"] = "Status";
<><?: component("workspace/panel", props, context) ?></>
```
Named component handler:
```cpp
DTree props;
props["title"] = "System";
props["body"] = "Healthy";
print(component("components/card:BODY", props, context));
```
Self-targeted named handler from inside the same file:
```cpp
COMPONENT(Request& context)
{
<>
<section class="card">
<?: component(":BODY", context.props, context) ?>
</section>
</>
}
COMPONENT:BODY(Request& context)
{
<>
<p><?= context.props["body"] ?></p>
</>
}
```
Preparing props in C++ before rendering:
```cpp
DTree props;
props["items"][0] = "alpha";
props["items"][1] = "beta";
props["items"][2] = "gamma";
String html = component("components/list", props, context);
print(html);
```
Embedding returned component markup inside a literal block:
```cpp
<>
<div class="panel">
<?: component("components/card", props, context) ?>
</div>
</>
```
Because `<?= ... ?>` escapes HTML, use `<?: ... ?>` when inserting the returned markup from `component()`.
## Lifecycle Notes
- `INIT(Request& context)` runs once when the worker loads that unit into memory.
- `ONCE(Request& context)` runs once per request before the first component or render entrypoint from that file.
- `component()` then calls either `COMPONENT(Request& context)` or the selected `COMPONENT:NAME(Request& context)` handler.
## Related Concepts
- PHP: reusable template partials or helper-rendered view fragments returned as strings
+4
View File
@@ -3,6 +3,10 @@ bool component_exists(String name)
:see
>ob
component
component_render
component_resolve
1_COMPONENT
:content
Checks whether a component file can be resolved from the current page context.
+6
View File
@@ -3,6 +3,10 @@ void component_render(String name, [DTree props], [Request& context])
:see
>ob
component
component_exists
component_resolve
1_COMPONENT
:content
Renders another `.uce` file as a component and writes the result directly to the current output buffer.
@@ -13,6 +17,8 @@ Component props are passed through `context.props`, and `name:COMPONENTFUNC` may
When `name` starts with `:`, the runtime resolves that named handler against the current `.uce` file.
If the target file defines `ONCE(Request& context)`, that hook runs once per request before the file's first component or render entrypoint.
Use `component_render()` when you want to write component output directly from C++ code instead of capturing it as a `String`.
## Example
+4
View File
@@ -3,6 +3,10 @@ String component_resolve(String name)
:see
>ob
component
component_exists
component_render
1_COMPONENT
:content
Resolves a component name to the concrete `.uce` file path that will be loaded.
+5
View File
@@ -5,6 +5,11 @@ f64 float_val(String s)
s : string to be converted
return value : a f64 containing the number (0 if no number could be identified).
:see
>string
int_val
String
:content
Extracts a floating point number from a `String`.
+3 -2
View File
@@ -7,9 +7,10 @@ delim : optional path separator
return value : the resolved child node, or an empty `DTree` when the path cannot be followed
:see
DTree
0_context
0_DTree
0_Request
>types
json_decode
:content
Traverses a nested `DTree` without creating missing keys.
+6
View File
@@ -5,6 +5,12 @@ String html_escape(String s)
s : string to be escaped
return value : an HTML-safe escaped version of 's'
:see
>string
json_encode
print
component
:content
Returns a version of the input string where special HTML characters are replaced by entities:
+5
View File
@@ -6,6 +6,11 @@ s : string to be converted
base : number system base (default 10)
return value : a u64 containing the number (0 if no number could be identified).
:see
>string
float_val
String
:content
Extracts an integer value from `s`.
+1 -1
View File
@@ -6,7 +6,7 @@ s : string containing JSON data
return value : a DTree object containing the deserialized JSON data
:see
DTree
0_DTree
json_encode
to_bool
to_f64
+7
View File
@@ -7,6 +7,13 @@ s : string to encode as a JSON string literal
t : DTree object to be serialized
return value : string containing the JSON result
:see
>types
json_decode
0_DTree
String
html_escape
:content
Serializes either a `String` or a `DTree` into JSON notation.
+1 -1
View File
@@ -12,7 +12,7 @@ markdown_to_html
component
component_render
json_encode
DTree
0_DTree
:content
Parses Markdown source into a structured `DTree` document tree.
+1 -1
View File
@@ -7,7 +7,7 @@ code : optional HTTP redirect status, defaults to `302`
:see
set_status
0_context
0_Request
:content
Sets the `Location` response header and updates the current HTTP status code.
+43
View File
@@ -0,0 +1,43 @@
:sig
bool regex_match(String pattern, String subject)
bool regex_match(String pattern, String subject, String flags)
:params
pattern : PCRE2 regular expression pattern
subject : string to test
flags : optional regex flags
return value : `true` when the entire subject matches the pattern
:see
>regex
regex_search
regex_search_all
regex_replace
regex_split
String
:content
Tests whether `subject` matches `pattern` from start to end.
This is a full-string match, not a substring search. Use `regex_search()` when you want to find the first occurrence anywhere in a string.
Examples:
```uce
regex_match("[A-Z][a-z]+", "Alice"); // true
regex_match("[A-Z][a-z]+", "Alice!"); // false
regex_match("\\d{4}-\\d{2}-\\d{2}", "2026-04-29");
```
Supported flags:
- `i` enables case-insensitive matching.
- `m` enables multiline `^` and `$`.
- `s` lets `.` match newlines.
- `x` enables extended / whitespace-insensitive pattern syntax.
- `u` explicitly enables UTF-8 and Unicode character properties.
- `a` disables UTF-8 / Unicode property mode for ASCII-oriented matching.
UCE uses PCRE2 in UTF-8 + Unicode-property mode by default, so patterns such as `\\p{L}+` work naturally on Unicode text.
Invalid patterns or invalid flags raise a request-visible runtime error with the PCRE2 diagnostic message.
+37
View File
@@ -0,0 +1,37 @@
:sig
String regex_replace(String pattern, String replacement, String subject)
String regex_replace(String pattern, String replacement, String subject, String flags)
:params
pattern : PCRE2 regular expression pattern
replacement : replacement string
subject : string where replacements should happen
flags : optional regex flags
return value : a new string with all matches replaced
:see
>regex
regex_match
regex_search
regex_search_all
regex_split
replace
:content
Replaces every match of `pattern` in `subject` and returns the transformed string.
Example:
```uce
String html = regex_replace(
"@([A-Za-z0-9_]+)",
"<a href=\"/users/$1\">@$1</a>",
"Hello @alice and @bob"
);
```
Replacement strings use PCRE2 substitution syntax, including numbered capture references such as `$1` and named references such as `${name}`.
For simple literal search-and-replace, use `replace()`. Use `regex_replace()` when the match condition needs a pattern, captures, character classes, anchors, or flags.
Invalid patterns, invalid flags, or invalid substitution syntax raise a request-visible runtime error.
+59
View File
@@ -0,0 +1,59 @@
:sig
DTree regex_search(String pattern, String subject)
DTree regex_search(String pattern, String subject, String flags)
:params
pattern : PCRE2 regular expression pattern
subject : string to search
flags : optional regex flags
return value : a DTree describing the first match
:see
>regex
regex_match
regex_search_all
regex_replace
regex_split
0_DTree
:content
Searches `subject` for the first occurrence of `pattern` and returns structured match data.
Example:
```uce
DTree match = regex_search(
"(?<user>[A-Za-z0-9._%+-]+)@(?<host>[A-Za-z0-9.-]+)",
"Contact ops@example.test"
);
if(match["matched"].to_bool())
{
print(match["match"].to_string());
print(match["named"]["user"].to_string());
print(match["named"]["host"].to_string());
}
```
Return shape:
- `matched` is a boolean.
- `pattern` stores the original pattern.
- `flags` stores the supplied flags, or `default`.
- `match` is the full matched text when a match exists.
- `start` and `end` are byte offsets into the original string.
- `captures` is a list of capture objects, with capture `0` representing the full match.
- `named` maps named capture groups to their captured text.
- `named_offsets` maps named capture groups to `index`, `start`, and `end` metadata.
Capture entries contain:
```text
capture["index"]
capture["matched"]
capture["start"]
capture["end"]
capture["text"]
```
If no match is found, `matched` is `false` and match-specific fields are omitted.
+39
View File
@@ -0,0 +1,39 @@
:sig
DTree regex_search_all(String pattern, String subject)
DTree regex_search_all(String pattern, String subject, String flags)
:params
pattern : PCRE2 regular expression pattern
subject : string to search
flags : optional regex flags
return value : a DTree containing all non-overlapping matches
:see
>regex
regex_match
regex_search
regex_replace
regex_split
0_DTree
:content
Finds every non-overlapping match of `pattern` in `subject`.
Example:
```uce
DTree tags = regex_search_all("#(?<tag>[A-Za-z0-9_]+)", "Ship #uce and #docs");
tags["matches"].each([](DTree match, String key) {
print(match["named"]["tag"].to_string(), "\n");
});
```
Return shape:
- `matched` is `true` when at least one match exists.
- `count` is the number of matches.
- `matches` is a list of entries with the same shape returned by `regex_search()`.
- `pattern` and `flags` mirror the call inputs.
Zero-length matches are handled safely; the scanner advances after each zero-length match to avoid infinite loops.
+38
View File
@@ -0,0 +1,38 @@
:sig
StringList regex_split(String pattern, String subject)
StringList regex_split(String pattern, String subject, String flags)
:params
pattern : PCRE2 regular expression pattern used as the separator
subject : string to split
flags : optional regex flags
return value : a list of string parts
:see
>regex
regex_match
regex_search
regex_search_all
regex_replace
split
join
StringList
:content
Splits `subject` wherever `pattern` matches.
Example:
```uce
StringList tags = regex_split("\\s*,\\s*", "uce, components, markdown");
print(join(tags, "\n"));
```
This is the pattern-aware companion to `split()`.
Behavior notes:
- Separators are removed from the returned list.
- Empty fields are preserved.
- If the pattern does not match, the result contains the original subject as a single item.
- Zero-length separators are handled safely to avoid infinite loops.
+1 -1
View File
@@ -6,7 +6,7 @@ code : HTTP status code
reason : optional reason phrase override
:see
0_context
0_Request
>types
:content
+3
View File
@@ -8,6 +8,9 @@ return value : 0 if signal was sent, -1 otherwise
:see
>task
task
task_pid
task_repeat
:content
Wraps the standard POSIX `kill()` function.
+18
View File
@@ -0,0 +1,18 @@
:sig
bool DTree::to_bool()
:see
>types
0_DTree
json_decode
to_f64
to_u64
:content
Reads a `DTree` value as a boolean.
String values such as `true`, `yes`, `on`, and `1` read as true. Values such as `false`, `no`, `off`, `0`, and `null` read as false.
Numeric values read as true when non-zero. Empty strings read as false.
Use this when consuming request data, JSON-decoded values, config trees, or component props where the original input may be string-shaped.
+17
View File
@@ -0,0 +1,17 @@
:sig
f64 DTree::to_f64()
:see
>types
0_DTree
json_decode
float_val
to_bool
to_u64
:content
Reads a `DTree` value as a floating-point number.
String values are parsed using the same permissive conversion rules used by the runtime's scalar helpers. Boolean values become `1.0` or `0.0`.
Use this for numeric config values, JSON-decoded fields, component props, and request data that should be treated as a number.
+17
View File
@@ -0,0 +1,17 @@
:sig
u64 DTree::to_u64()
:see
>types
0_DTree
json_decode
int_val
to_bool
to_f64
:content
Reads a `DTree` value as an unsigned integer.
String values are parsed numerically. Boolean values become `1` or `0`. Negative values clamp to `0`.
Use this for counts, identifiers, limits, timestamps, and other non-negative numeric values stored inside a `DTree`.
+37
View File
@@ -9,6 +9,11 @@ return value : DTree* returned from function
:see
>ob
unit_load
unit_render
unit_info
1_RENDER
1_COMPONENT
:content
Calls an exported function inside another UCE file.
@@ -17,6 +22,19 @@ Use `unit_call()` when you need structured data exchange between units rather th
The callee must expose an `EXPORT` function whose name matches `function_name`. Arguments are passed through `call_param`, and the return value is a `DTree*` owned by the callee.
`unit_call()` also understands the request-bound UCE entrypoint names:
- `RENDER`
- `RENDER:NAME`
- `COMPONENT`
- `COMPONENT:NAME`
- `ONCE`
- `INIT`
When `function_name` matches one of those macro-style entrypoints, `unit_call()` does not look for a plain `EXPORT DTree* ...` function. Instead, it translates the name to the generated C++ symbol, uses the current `Request` context, and passes `call_param` into `context.props`, matching the normal component invocation model.
For `RENDER...` and `COMPONENT...`, the unit's `ONCE(Request& context)` hook is still honored automatically before the selected handler runs.
Example:
```cpp
@@ -31,6 +49,25 @@ EXPORT DTree* test_func(DTree* call_param)
unit_call("call_file_funcs.uce", "test_func");
```
Calling a named component handler through `unit_call()`:
```cpp
DTree props;
props["title"] = "Diagnostics";
props["body"] = "Ready";
unit_call("components/card.uce", "COMPONENT:BODY", &props);
```
Calling a page render handler through `unit_call()`:
```cpp
DTree props;
props["section"] = "summary";
unit_call("reports/summary.uce", "RENDER", &props);
```
Related:
- PHP: `include`, `require`, or calling a function from an included module, especially when returning arrays or objects instead of rendering a view
+2
View File
@@ -6,8 +6,10 @@ path : optional UCE unit path. If empty, recompiles the current executing unit.
return value : `true` when the unit was compiled and loaded successfully
:see
>runtime
unit_info
units_list
unit_load
:content
Triggers a manual recompile of a UCE compilation unit.
+3 -1
View File
@@ -6,9 +6,11 @@ path : optional UCE unit path. If empty, uses the current executing unit.
return value : metadata tree for the resolved unit, or an empty tree if the unit cannot be resolved
:see
>runtime
units_list
unit_compile
0_context
0_Request
unit_load
:content
Returns runtime metadata for a UCE compilation unit.
+2
View File
@@ -6,9 +6,11 @@ file_name : UCE file to load
return value : loaded shared unit, or `null` if the unit could not be loaded
:see
>runtime
unit_render
unit_call
load
unit_info
:content
Loads a UCE compilation unit and returns its in-memory `SharedUnit` record.
+4
View File
@@ -8,6 +8,10 @@ context : optional request context to pass into the target page
:see
>ob
unit_call
unit_load
1_RENDER
component
:content
Calls another UCE file and executes its `RENDER(Request& context)` function.
+2
View File
@@ -2,8 +2,10 @@
std::vector<String> units_list()
:see
>runtime
unit_info
unit_compile
unit_load
:content
Returns the normalized paths of all known `.uce` units.
+7
View File
@@ -7,6 +7,13 @@ String var_dump(DTree t, String prefix = "", String postfix = "\n")
t : object to be dumped into a string
return value : string containing a human-friendly representation of 't'
:see
>types
0_DTree
StringMap
json_encode
print
:content
Returns a string representation of `t` intended for debugging.
+1 -102
View File
@@ -1,108 +1,7 @@
struct DocPage {
String title;
String content;
StringList sig_lines;
StringList param_lines;
StringList see_lines;
};
#include "lib/doc_page.h"
StringMap* already_shown_items;
String doc_default_title(String page)
{
String page_title = page;
if(page_title.length() > 1 && page_title[1] == '_')
nibble(page_title, "_");
return(page_title);
}
String doc_markdown_inline(String text)
{
text = trim(text);
if(text == "")
return("");
String html = markdown_to_html(text);
if(html.length() >= 7 && html.substr(0, 3) == "<p>" && html.substr(html.length() - 4) == "</p>")
return(html.substr(3, html.length() - 7));
return(html);
}
String doc_legacy_heading(String section)
{
if(section == "desc")
return("");
if(section == "related")
return("## PHP & JS Equivalents");
return("## " + section);
}
DocPage load_doc_page(String page)
{
DocPage result;
StringList lines = split(file_get_contents("pages/" + page + ".txt"), "\n");
String current_section = "";
bool content_mode = false;
StringList content_lines;
for(auto line : lines)
{
if(!content_mode && line != "" && line.substr(0, 1) == ":")
{
String section = trim(line.substr(1));
if(section == "title" || section == "sig" || section == "params" || section == "see")
{
current_section = section;
continue;
}
if(section == "content")
{
content_mode = true;
current_section = "content";
continue;
}
current_section = "legacy";
String heading = doc_legacy_heading(section);
if(heading != "")
{
if(content_lines.size() > 0 && content_lines.back() != "")
content_lines.push_back("");
content_lines.push_back(heading);
content_lines.push_back("");
}
continue;
}
if(current_section == "title")
{
if(result.title != "")
result.title += "\n";
result.title += line;
}
else if(current_section == "sig")
{
result.sig_lines.push_back(line);
}
else if(current_section == "params")
{
result.param_lines.push_back(line);
}
else if(current_section == "see")
{
if(trim(line) != "")
result.see_lines.push_back(trim(line));
}
else
{
content_lines.push_back(line);
}
}
result.content = join(content_lines, "\n");
result.title = trim(result.title);
return(result);
}
void render_doc_params(StringList param_lines)
{
if(param_lines.size() == 0)
@@ -5,7 +5,7 @@ COMPONENT(Request& context)
starter_boot(context);
String current_theme = context.cfg.get_by_path("theme/key").to_string();
String current_label = first(context.cfg.get_by_path("theme/label").to_string(), current_theme);
String route_path = context.var["starter"]["route"]["l_path"].to_string();
String route_path = context.call["starter"]["route"]["l_path"].to_string();
<>
<div id="theme-switcher" style="position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 9999; font-family: inherit;">
@@ -3,7 +3,7 @@
COMPONENT(Request& context)
{
starter_boot(context);
String title = first(context.var["starter"]["page_title"].to_string(), context.cfg.get_by_path("site/default_page_title").to_string());
String title = first(context.call["starter"]["page_title"].to_string(), context.cfg.get_by_path("site/default_page_title").to_string());
String description = first(context.cfg.get_by_path("theme/meta_description").to_string(), "UCE starter example");
String theme_color = first(context.cfg.get_by_path("theme/theme_color").to_string(), "#0f172a");
String icon = starter_asset_url(context.cfg.get_by_path("theme/path").to_string() + "icon.png", context);
@@ -4,13 +4,13 @@ COMPONENT(Request& context)
{
starter_boot(context);
if(context.props["main_html"].to_string() != "")
context.var["starter"]["fragments"]["main"] = context.props["main_html"];
context.call["starter"]["fragments"]["main"] = context.props["main_html"];
if(context.props["json"].get_type_name() == "array")
context.var["starter"]["json"] = context.props["json"];
context.call["starter"]["json"] = context.props["json"];
if(context.props["page_type"].to_string() != "")
context.var["starter"]["page_type"] = context.props["page_type"];
context.call["starter"]["page_type"] = context.props["page_type"];
if(context.props["embed_mode"].to_string() != "")
context.var["starter"]["embed_mode"] = context.props["embed_mode"];
context.call["starter"]["embed_mode"] = context.props["embed_mode"];
starter_render_page(context);
}
+3 -3
View File
@@ -10,7 +10,7 @@ RENDER(Request& context)
if(resolved["file"].to_string() != "")
{
if(resolved["param"].to_string() != "")
context.var["app"]["route"]["param"] = resolved["param"];
context.call["app"]["route"]["param"] = resolved["param"];
unit_render(resolved["file"].to_string(), context);
}
else
@@ -19,11 +19,11 @@ RENDER(Request& context)
<>
<section class="card">
<h1>404 Not Found</h1>
<p><?= context.var["app"]["error"].to_string() ?></p>
<p><?= context.call["app"]["error"].to_string() ?></p>
</section>
</>
}
String main_html = ob_get_close();
context.var["app"]["fragments"]["main"] = main_html;
context.call["app"]["fragments"]["main"] = main_html;
app_render_page(context);
}
+28 -28
View File
@@ -3,7 +3,7 @@
String app_fs_root(Request& context)
{
String root = context.var["app"]["fs_root"].to_string();
String root = context.call["app"]["fs_root"].to_string();
if(root == "")
root = cwd_get();
return(root);
@@ -11,7 +11,7 @@ String app_fs_root(Request& context)
String app_script_url(Request& context)
{
String url = context.var["app"]["script_url"].to_string();
String url = context.call["app"]["script_url"].to_string();
if(url == "")
url = first(context.params["DOCUMENT_URI"], context.params["SCRIPT_NAME"]);
return(url);
@@ -19,7 +19,7 @@ String app_script_url(Request& context)
String app_base_url(Request& context)
{
String base = context.var["app"]["base_url"].to_string();
String base = context.call["app"]["base_url"].to_string();
if(base == "")
{
base = dirname(app_script_url(context));
@@ -74,7 +74,7 @@ DTree app_make_route(Request& context)
DTree app_resolve_view(Request& context, String base_dir = "views")
{
DTree result;
DTree route = context.var["app"]["route"];
DTree route = context.call["app"]["route"];
String lpath = first(route["l_path"].to_string(), "index");
String base = trim(base_dir);
if(base != "" && base[base.length() - 1] == '/')
@@ -114,12 +114,12 @@ DTree app_resolve_view(Request& context, String base_dir = "views")
void app_register_css(String path, Request& context)
{
context.var["app"]["assets"]["css"][path] = path;
context.call["app"]["assets"]["css"][path] = path;
}
void app_register_js(String path, Request& context)
{
context.var["app"]["assets"]["js"][path] = path;
context.call["app"]["assets"]["js"][path] = path;
}
String app_asset_url(String path, Request& context)
@@ -133,7 +133,7 @@ String app_asset_url(String path, Request& context)
void app_render_registered_css(Request& context)
{
context.var["app"]["assets"]["css"].each([&](DTree item, String key) {
context.call["app"]["assets"]["css"].each([&](DTree item, String key) {
String path = item.to_string();
if(path != "")
{
@@ -144,7 +144,7 @@ void app_render_registered_css(Request& context)
void app_render_registered_js(Request& context)
{
context.var["app"]["assets"]["js"].each([&](DTree item, String key) {
context.call["app"]["assets"]["js"].each([&](DTree item, String key) {
String path = item.to_string();
if(path != "")
{
@@ -185,8 +185,8 @@ void app_redirect(String path, Request& context)
void app_not_found(String message, Request& context)
{
context.set_status(404, "Not Found");
context.var["app"]["error"] = message;
context.var["app"]["page_title"] = "404 Not Found";
context.call["app"]["error"] = message;
context.call["app"]["page_title"] = "404 Not Found";
}
String app_menu_href(String menu_key, DTree menu_item, Request& context)
@@ -208,7 +208,7 @@ String app_page_main_html(Request& context)
{
String main_html = context.props["main_html"].to_string();
if(main_html == "")
main_html = context.var["app"]["fragments"]["main"].to_string();
main_html = context.call["app"]["fragments"]["main"].to_string();
return(main_html);
}
@@ -231,7 +231,7 @@ bool app_bool_value(DTree value, bool fallback = false)
bool app_request_embed_mode(Request& context)
{
return(app_bool_value(context.var["app"]["embed_mode"]));
return(app_bool_value(context.call["app"]["embed_mode"]));
}
bool app_page_embed_mode(Request& context)
@@ -244,7 +244,7 @@ bool app_page_embed_mode(Request& context)
String app_theme_page_component(Request& context)
{
String page_type = first(context.var["app"]["page_type"].to_string(), "html");
String page_type = first(context.call["app"]["page_type"].to_string(), "html");
if(page_type == "blank")
return("themes/common/page.blank.uce");
if(page_type == "json")
@@ -264,9 +264,9 @@ void app_render_page(Request& context)
return;
DTree page_props;
page_props["main_html"] = context.var["app"]["fragments"]["main"];
if(context.var["app"]["json"].get_type_name() == "array")
page_props["json"] = context.var["app"]["json"];
page_props["main_html"] = context.call["app"]["fragments"]["main"];
if(context.call["app"]["json"].get_type_name() == "array")
page_props["json"] = context.call["app"]["json"];
String page_component = app_theme_page_component(context);
if(page_component != "" && file_exists(page_component))
@@ -370,19 +370,19 @@ DTree starter_resolve_view(Request& context, String base_dir = "views")
void app_init(Request& context)
{
if(context.var["app"]["booted"].to_string() == "1")
if(context.call["app"]["booted"].to_string() == "1")
return;
context.var["app"]["booted"] = "1";
context.var["app"]["fs_root"] = cwd_get();
context.var["app"]["script_url"] = first(context.params["DOCUMENT_URI"], context.params["SCRIPT_NAME"]);
context.call["app"]["booted"] = "1";
context.call["app"]["fs_root"] = cwd_get();
context.call["app"]["script_url"] = first(context.params["DOCUMENT_URI"], context.params["SCRIPT_NAME"]);
String base_url = dirname(context.var["app"]["script_url"].to_string());
String base_url = dirname(context.call["app"]["script_url"].to_string());
if(base_url == "")
base_url = "/";
if(base_url[base_url.length() - 1] != '/')
base_url.append(1, '/');
context.var["app"]["base_url"] = base_url;
context.call["app"]["base_url"] = base_url;
DTree config = get_config();
String requested_theme = first(context.get["theme"], context.cookies["app_theme"], config["theme"]["key"].to_string());
@@ -394,12 +394,12 @@ void app_init(Request& context)
});
config["theme"]["key"] = requested_theme;
context.cfg = config;
context.var["cfg"].set_reference(&context.cfg);
context.var["app"]["config"].set_reference(&context.cfg);
context.var["app"]["route"] = app_make_route(context);
context.var["app"]["page_type"] = "html";
context.var["app"]["page_title"] = first(config["menu"][context.var["app"]["route"]["l_path"].to_string()]["title"].to_string(), config["site"]["default_page_title"].to_string(), "Home");
context.var["app"]["embed_mode"].set_bool(context.get["embed"] != "");
context.call["cfg"].set_reference(&context.cfg);
context.call["app"]["config"].set_reference(&context.cfg);
context.call["app"]["route"] = app_make_route(context);
context.call["app"]["page_type"] = "html";
context.call["app"]["page_title"] = first(config["menu"][context.call["app"]["route"]["l_path"].to_string()]["title"].to_string(), config["site"]["default_page_title"].to_string(), "Home");
context.call["app"]["embed_mode"].set_bool(context.get["embed"] != "");
if(context.get["theme"] != "" && context.get["theme"] == requested_theme)
{
+5 -5
View File
@@ -148,7 +148,7 @@ struct AppUser
session_start();
context.session[session_key()] = email;
context.var["app"]["current_user"] = user;
context.call["app"]["current_user"] = user;
result["result"].set_bool(true);
result["profile"] = user;
return(result);
@@ -156,7 +156,7 @@ struct AppUser
bool is_signed_in()
{
if(context.var["app"]["current_user"]["email"].to_string() != "")
if(context.call["app"]["current_user"]["email"].to_string() != "")
return(true);
String user_id = context.session[session_key()];
if(user_id == "")
@@ -167,20 +167,20 @@ struct AppUser
context.session.erase(session_key());
return(false);
}
context.var["app"]["current_user"] = user;
context.call["app"]["current_user"] = user;
return(true);
}
DTree current()
{
if(is_signed_in())
return(context.var["app"]["current_user"]);
return(context.call["app"]["current_user"]);
return(DTree());
}
void logout()
{
context.session.erase(session_key());
context.var["app"]["current_user"].clear();
context.call["app"]["current_user"].clear();
}
};
@@ -7,8 +7,8 @@ COMPONENT(Request& context)
context.header["Cache-Control"] = "no-cache, no-store, must-revalidate";
if(context.props["json"].get_type_name() == "array")
print(json_encode(context.props["json"]));
else if(context.var["starter"]["json"].get_type_name() == "array")
print(json_encode(context.var["starter"]["json"]));
else if(context.call["starter"]["json"].get_type_name() == "array")
print(json_encode(context.call["starter"]["json"]));
else
print(starter_page_main_html(context));
}
@@ -5,7 +5,7 @@ COMPONENT(Request& context)
starter_boot(context);
bool embed_mode = starter_page_embed_mode(context);
String main_html = starter_page_main_html(context);
String current_path = context.var["starter"]["route"]["l_path"].to_string();
String current_path = context.call["starter"]["route"]["l_path"].to_string();
DTree global_props;
global_props["cookie_consent"].set_bool(false);
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Login";
context.call["starter"]["page_title"] = "Login";
StarterUser users(context);
DTree result;
if(context.params["REQUEST_METHOD"] == "POST")
@@ -9,7 +9,7 @@ RENDER(Request& context)
starter_redirect("account/login", context);
return;
}
context.var["starter"]["page_title"] = "Profile";
context.call["starter"]["page_title"] = "Profile";
DTree user = users.current();
String roles = "";
user["roles"].each([&](DTree role, String key) {
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Register";
context.call["starter"]["page_title"] = "Register";
StarterUser users(context);
DTree result;
if(context.params["REQUEST_METHOD"] == "POST")
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "OAuth Callback";
context.call["starter"]["page_title"] = "OAuth Callback";
String code = context.get["code"];
String state = context.get["state"];
String error = context.get["error"];
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Auth";
context.call["starter"]["page_title"] = "Auth";
starter_register_css("views/marketing.css", context);
DTree props;
@@ -3,19 +3,19 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_type"] = "json";
context.call["starter"]["page_type"] = "json";
DTree body = json_decode(context.in);
if(context.params["REQUEST_METHOD"] == "POST" && body["oauth_service"].to_string() != "" && body["oauth_state"].to_string() != "")
{
context.session["oauth_service"] = body["oauth_service"].to_string();
context.session["oauth_state"] = body["oauth_state"].to_string();
context.var["starter"]["json"]["status"] = "success";
context.call["starter"]["json"]["status"] = "success";
}
else
{
context.set_status(400, "Bad Request");
context.var["starter"]["json"]["status"] = "error";
context.var["starter"]["json"]["message"] = "Invalid input";
context.call["starter"]["json"]["status"] = "error";
context.call["starter"]["json"]["message"] = "Invalid input";
}
}
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Dashboard";
context.call["starter"]["page_title"] = "Dashboard";
starter_register_css("views/dashboard.css", context);
DTree props;
+1 -1
View File
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Features";
context.call["starter"]["page_title"] = "Features";
starter_register_css("views/marketing.css", context);
<>
<h1>Features</h1>
+2 -2
View File
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Gauges";
context.call["starter"]["page_title"] = "Gauges";
starter_register_css("themes/common/css/gauges.css", context);
f64 pi = 3.14159265358979323846;
@@ -69,7 +69,7 @@ RENDER(Request& context)
props.clear();
props["id"] = "needle_demo";
props["title"] = "Needle Gauge";
props["subtitle"] = "The original analog gauge now uses the same elevated panels, typography, and theme-token palette as the arc gauges.";
props["subtitle"] = "The original analog gauge uses the same elevated panels, typography, and theme-token palette as the arc gauges.";
props["style"] = "flex:1 1 24rem";
props["listen"].set_bool(true);
props["label"] = "CPU";
+1 -1
View File
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Home";
context.call["starter"]["page_title"] = "Home";
starter_register_css("views/marketing.css", context);
DTree props;
+1 -1
View File
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Components";
context.call["starter"]["page_title"] = "Components";
starter_register_css("views/marketing.css", context);
<>
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_type"] = "blank";
context.call["starter"]["page_type"] = "blank";
<>
<?= std::to_string((u64)time()) ?> - Page 2 Section 1 loaded
<pre>UCE starter AJAX fragment response</pre>
+1 -1
View File
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Ajaxy";
context.call["starter"]["page_title"] = "Ajaxy";
<>
<h1>Ajax Demo</h1>
<div id="page2-section1">
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Theme Preview";
context.call["starter"]["page_title"] = "Theme Preview";
starter_register_css("views/themes.css", context);
String current_theme_key = context.cfg.get_by_path("theme/key").to_string();
String theme_label = first(context.cfg.get_by_path("theme/label").to_string(), current_theme_key);
+1 -1
View File
@@ -3,7 +3,7 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Themes";
context.call["starter"]["page_title"] = "Themes";
starter_register_css("views/themes.css", context);
String current_theme = context.cfg.get_by_path("theme/key").to_string();
@@ -3,8 +3,8 @@
RENDER(Request& context)
{
starter_boot(context);
context.var["starter"]["page_title"] = "Workspace";
String section = first(context.var["starter"]["route"]["param"].to_string(), "overview");
context.call["starter"]["page_title"] = "Workspace";
String section = first(context.call["starter"]["route"]["param"].to_string(), "overview");
if(section != "overview" && section != "projects" && section != "activity")
section = "overview";
+4
View File
@@ -0,0 +1,4 @@
RENDER(Request& context)
{
context.header["Location"] = "/info/";
}
+14 -1
View File
@@ -25,6 +25,19 @@ RENDER(Request& context)
check("trim()", trim(" padded value ") == "padded value", trim(" padded value "));
check("replace()", replace("alpha-beta-beta", "beta", "done") == "alpha-done-done", replace("alpha-beta-beta", "beta", "done"));
check("regex_match()", regex_match("[A-Z][a-z]+", "Alice") && !regex_match("[A-Z][a-z]+", "Alice!"), "full-string validation");
DTree regex_email = regex_search("(?<user>[A-Za-z0-9._%+-]+)@(?<host>[A-Za-z0-9.-]+)", "Contact ops@example.test");
check("regex_search()", regex_email["matched"].to_bool() && regex_email["named"]["user"].to_string() == "ops" && regex_email["named"]["host"].to_string() == "example.test", json_encode(regex_email));
DTree regex_tags = regex_search_all("#(?<tag>[A-Za-z0-9_]+)", "#uce #docs");
check("regex_search_all()", regex_tags["count"].to_s64() == 2 && regex_tags["matches"]["1"]["named"]["tag"].to_string() == "docs", json_encode(regex_tags));
check("regex_replace()", regex_replace("#([A-Za-z0-9_]+)", "<tag>$1</tag>", "#uce") == "<tag>uce</tag>", regex_replace("#([A-Za-z0-9_]+)", "<tag>$1</tag>", "#uce"));
auto regex_parts = regex_split("\\s*,\\s*", "uce, components, markdown");
check("regex_split()", regex_parts.size() == 3 && regex_parts[1] == "components", join(regex_parts, " | "));
check("substr() + strpos()", strpos("component suite", "suite") == 10 && substr("component suite", 10) == "suite", "strpos=10 substr='" + substr("component suite", 10) + "'");
check("str_starts_with()", str_starts_with("websocket-suite", "websocket"), "websocket-suite starts with websocket");
check("str_ends_with()", str_ends_with("component.uce", ".uce"), "component.uce ends with .uce");
@@ -50,4 +63,4 @@ RENDER(Request& context)
site_tests_summary(passed, failed, skipped, "These assertions intentionally stay pure and side-effect free so they remain safe on the public site.");
site_tests_page_end();
}
}
+19
View File
@@ -0,0 +1,19 @@
#include "testlib.h"
RENDER(Request& context)
{
site_tests_page_start("Coverage Index", "Public and local-only UCE regression coverage pages.");
?><div class="tests-grid"><?
site_tests_card("core.uce", "Core APIs", "Pure helper coverage for strings, regex, UTF-8, DTree, and JSON.", "public");
site_tests_card("preprocessor.uce", "Preprocessor", "Literal-output parser regression coverage.", "public");
site_tests_card("http.uce", "HTTP And Session", "Request, response, cookie, and session helpers.", "public");
site_tests_card("components.uce", "Components", "component(), props, and component rendering.", "public");
site_tests_card("markdown.uce", "Markdown", "Markdown parsing, rendering, and component hooks.", "public");
site_tests_card("units.uce", "Units", "unit_call(), lifecycle hooks, and unit metadata.", "public");
site_tests_card("websockets.ws.uce", "WebSockets", "Browser-driven WebSocket helper checks.", "public websocket");
site_tests_card("io.uce", "Filesystem", "Filesystem helpers that are restricted outside trusted networks.", "internal");
site_tests_card("services.uce", "Sockets And Services", "Network/service helpers that are restricted outside trusted networks.", "internal");
site_tests_card("tasks.uce", "Tasks", "Background task helper coverage.", "internal");
?></div><?
site_tests_page_end();
}
+42
View File
@@ -0,0 +1,42 @@
#include "testlib.h"
String preprocessor_nested_literal()
{
ob_start();
<>
<span id="nested-raw-string-terminator">nested )" marker</span>
</>
return(ob_get_close());
}
RENDER(Request& context)
{
u64 passed = 0;
u64 failed = 0;
u64 skipped = 0;
auto check = [&](String name, bool ok, String detail)
{
site_tests_case(name, ok ? "pass" : "fail", detail);
if(ok)
passed++;
else
failed++;
};
String nested = preprocessor_nested_literal();
site_tests_page_start("Preprocessor", "Regression coverage for literal output rewriting and parser edge cases.");
?>
<section class="tests-section">
<p id="top-level-raw-string-terminator">top-level )" marker</p>
<?: nested ?>
</section>
<?
check("raw string terminator in nested literal", contains(nested, "nested )\" marker"), nested);
check("inline code island after dangerous literal", true, "parser returned to C++ after rendering literal content containing )\"");
site_tests_summary(passed, failed, skipped, "Literal content containing the C++ raw-string terminator sequence must compile and render unchanged.");
site_tests_page_end();
}
+1 -1
View File
@@ -123,4 +123,4 @@ WS(Request& context)
}
ws_send_to(ws_connection_id(), json_encode(websocket_suite_event(context, "unknown", nonce)));
}
}