diff --git a/site/doc/index.uce b/site/doc/index.uce index 2072212..2331516 100644 --- a/site/doc/index.uce +++ b/site/doc/index.uce @@ -1,7 +1,132 @@ +struct DocPage { + String title; + String content; + StringList sig_lines; + StringList param_lines; + StringList see_lines; +}; + +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) == "
" && html.substr(html.length() - 4) == "
") + 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) + return; + + <>print(s); ?>- } - else if(layout_class == "params") - { - ?>
print(join(doc_page.sig_lines, "\n")); ?>+
= context.call["body"] ?>
> +} +``` -:related -**PHP:** View partials, reusable include files, small template helpers, and server-side component-like rendering patterns -**JavaScript / Node.js:** Reusable component functions, React or Vue components, and server-rendered partials +## Related Concepts + +- PHP: view partials, reusable include files, small template helpers, and server-side component-like rendering patterns +- JavaScript / Node.js: reusable component functions, React or Vue components, and server-rendered partials diff --git a/site/doc/pages/1_RENDER.txt b/site/doc/pages/1_RENDER.txt index 94c1b8a..5d12b69 100644 --- a/site/doc/pages/1_RENDER.txt +++ b/site/doc/pages/1_RENDER.txt @@ -1,11 +1,19 @@ +:title +RENDER + :sig RENDER(Request& context) -:desc +:see +>ob + +:content Defines the main HTTP render handler for the current `.uce` page. When a page is requested over HTTP, the runtime loads the target file and calls its `RENDER(Request& context)` function. +## Entry Point Behavior + 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()`. @@ -16,11 +24,15 @@ For a normal direct page request, `context.call` starts empty. If the page is invoked from another UCE file via `unit_render(file_name, context)`, the callee receives that same `context`. -Pages intended to serve WebSocket traffic may expose both `RENDER(Request& context)` and `WS(Request& context)`. Files may also define `COMPONENT()` handlers when they intentionally need both page and component behavior in one unit. In that case `RENDER(Request& context)` serves the direct HTTP response, `WS(Request& context)` handles subsequent WebSocket messages, and `COMPONENT()` remains available only through the component helpers. +Pages intended to serve WebSocket traffic may expose both `RENDER(Request& context)` and `WS(Request& context)`. Files may also define `COMPONENT()` handlers when they intentionally need both page and component behavior in one unit. -:see ->ob +In that case: -:related -**PHP:** Front controller entrypoints, template files, `include`, `require`, and route handlers that write the HTTP response -**JavaScript / Node.js:** Express or Fastify route handlers, page controller functions, and SSR entrypoints that build a response +- `RENDER(Request& context)` serves the direct HTTP response +- `WS(Request& context)` handles later WebSocket messages +- `COMPONENT()` remains available only through the component helpers + +## Related Concepts + +- PHP: front controller entrypoints, template files, `include`, `require`, and route handlers that write the HTTP response +- JavaScript / Node.js: Express or Fastify route handlers, page controller functions, and SSR entrypoints that build a response diff --git a/site/doc/pages/1_WS.txt b/site/doc/pages/1_WS.txt index 409f2a3..e9667b0 100644 --- a/site/doc/pages/1_WS.txt +++ b/site/doc/pages/1_WS.txt @@ -1,15 +1,25 @@ +:title +WS + :sig WS(Request& context) -:desc +:see +>websocket + +:content Defines the WebSocket message handler for the current `.ws.uce` page. The same page may expose both `RENDER(Request& context)` and `WS(Request& context)`. `RENDER(Request& context)` serves the initial HTTP response, while `WS(Request& context)` is called whenever a complete WebSocket message arrives for that page. UCE reassembles fragmented messages before calling `WS(Request& context)`. Text and binary frames are both delivered. Use `context.call`, `context.connection`, `ws_opcode()`, and `ws_is_binary()` to inspect the current message. +## Connection State + `context.connection` is a broker-owned `DTree` for the current socket. It starts empty for a new client and persists across later `WS(Request& context)` calls on that same connection. +## Message Data + The current message data is available in `context.call`: - `context.call["message"]`: current message payload @@ -18,9 +28,7 @@ The current message data is available in `context.call`: - `context.call["opcode"]`: WebSocket opcode of the current message - `context.call["document_uri"]`: request URI of the current endpoint -:related -**PHP:** Ratchet `onMessage`, Workerman WebSocket handlers, or lower-level callbacks around accepted socket connections. -**JavaScript / Node.js:** Browser `WebSocket` `message` handlers and Node `ws` server `connection` and `message` callbacks. +## Related Concepts -:see ->websocket +- PHP: Ratchet `onMessage`, Workerman WebSocket handlers, or lower-level callbacks around accepted socket connections +- JavaScript / Node.js: browser `WebSocket` `message` handlers and Node `ws` server `connection` and `message` callbacks diff --git a/site/doc/pages/3_C++ Preprocessor.txt b/site/doc/pages/3_C++ Preprocessor.txt index 4158b57..ac54af3 100644 --- a/site/doc/pages/3_C++ Preprocessor.txt +++ b/site/doc/pages/3_C++ Preprocessor.txt @@ -1,12 +1,23 @@ +:title +C++ Preprocessor + :sig UCE source preprocessing -:desc +:see +load +unit_render +unit_call +0_context +1_COMPONENT + +:content UCE runs a small custom source-to-source preprocessor before Clang sees a `.uce` or `.ws.uce` file. The implementation lives in `src/lib/compiler.cpp`. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands UCE literal blocks, inline code islands, `#load`, and `EXPORT` harvesting, then writes a generated `.cpp` file and compiles that file into a shared object. -:Syntax +## Syntax + - `<> ... >` enters literal-output mode. - Inside a literal block, ` ... ?>` emits raw C++. - Inside a literal block, `= expression ?>` emits `print(html_escape(expression));`. @@ -16,13 +27,14 @@ The implementation lives in `src/lib/compiler.cpp`. It does not try to parse all - `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. -:Pipeline +## Pipeline + - 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 block is rewritten into one or more `print(R"( ... )");` calls. - ` ... ?>` temporarily breaks out of literal printing, emits the enclosed C++ unchanged, then resumes literal output. -- `= ... ?>` becomes `print(html_escape(...));`. The runtime currently provides `html_escape()` overloads for `String`, `u64`, and `f64`. +- `= ... ?>` becomes `print(html_escape(...));`. - `` becomes `print(...);` and is intended for trusted markup or already-escaped content. - `#load "file.uce"` is replaced with a generated C++ `#include` that points at the loaded unit's preprocessed `.cpp` file under `BIN_DIRECTORY`. - Lines beginning with `EXPORT` are scanned so their declarations can be written to a sibling `.exports.txt` file. @@ -31,53 +43,74 @@ The implementation lives in `src/lib/compiler.cpp`. It does not try to parse all - 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 ...`. -:GeneratedFiles -- Source file: `/some/path/page.uce` -- Generated C++: `BIN_DIRECTORY/some/path/page.uce.cpp` -- Shared object: `BIN_DIRECTORY/some/path/page.uce.so` -- Export list: `BIN_DIRECTORY/some/path/page.uce.exports.txt` +## Generated Files -:Example -Example 1: literal output with escaped data -`RENDER(Request& context)` -`{` -` <><h1><?= context.params["DOCUMENT_URI"] ?></h1></>` -`}` +For a source file like `/some/path/page.uce`, the preprocessor produces: + +- generated C++: `BIN_DIRECTORY/some/path/page.uce.cpp` +- shared object: `BIN_DIRECTORY/some/path/page.uce.so` +- export list: `BIN_DIRECTORY/some/path/page.uce.exports.txt` + +## Examples + +Literal output with escaped data: + +```cpp +RENDER(Request& context) +{ + <>" && html.substr(html.length() - 4) == "
") + 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) + return; + + <>print(join(doc_page.sig_lines, "\n")); ?>> + } + <>
print(s); ?>> - } - else if(layout_class == "params") - { - <>