W3
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
// Production WASM W1 core entrypoint.
|
||||
//
|
||||
// This file deliberately includes the real UCE runtime amalgamation with
|
||||
// __UCE_WASM_CORE__ enabled. Native-only pieces are carved out in the runtime
|
||||
// sources, while the workspace-owned DValue ABI and output plumbing are built
|
||||
// into core.wasm.
|
||||
|
||||
#define __UCE_WASM_CORE__ 1
|
||||
#include "../lib/uce_lib.cpp"
|
||||
#include "../lib/mysql-connector.h"
|
||||
#include "../lib/sqlite-connector.h"
|
||||
|
||||
// ---- W3 connector membrane stubs -------------------------------------------
|
||||
// Generated units reference the connector class surface through uce_lib.h, so
|
||||
// the core must define it. Until the real hostcall connectors land (W5 parity
|
||||
// scope: the starter uses no database), every operation fails cleanly with an
|
||||
// explanatory error instead of trapping.
|
||||
|
||||
static const char* WASM_DB_UNAVAILABLE =
|
||||
"database connectors are not yet available in the wasm workspace";
|
||||
|
||||
bool MySQL::connect(String host, String username, String password)
|
||||
{
|
||||
(void)host; (void)username; (void)password;
|
||||
connection = 0;
|
||||
statement_info = WASM_DB_UNAVAILABLE;
|
||||
return(false);
|
||||
}
|
||||
|
||||
void MySQL::disconnect() { connection = 0; }
|
||||
String MySQL::error() { return(WASM_DB_UNAVAILABLE); }
|
||||
String MySQL::escape(String raw, char quote_char) { (void)quote_char; return(raw); }
|
||||
String MySQL::parse_query_parameters(String query, StringMap m) { (void)m; return(query); }
|
||||
DValue MySQL::query(String q) { (void)q; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
|
||||
DValue MySQL::query(String q, StringMap params) { (void)q; (void)params; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
|
||||
DValue MySQL::get_pending_result() { return(DValue()); }
|
||||
|
||||
String mysql_escape(String raw, char quote_char) { (void)quote_char; return(raw); }
|
||||
|
||||
bool SQLite::connect(String path) { this->path = path; connection = 0; set_error(1, WASM_DB_UNAVAILABLE); return(false); }
|
||||
void SQLite::disconnect() { connection = 0; }
|
||||
String SQLite::error() { return(error_code ? String(WASM_DB_UNAVAILABLE) : String("")); }
|
||||
DValue SQLite::query(String q) { (void)q; set_error(1, WASM_DB_UNAVAILABLE); return(DValue()); }
|
||||
DValue SQLite::query(String q, const StringMap& params) { (void)q; (void)params; set_error(1, WASM_DB_UNAVAILABLE); return(DValue()); }
|
||||
void SQLite::set_error(s32 code, String info) { error_code = code; statement_info = info; }
|
||||
bool SQLite::apply_default_pragmas() { return(false); }
|
||||
bool SQLite::bind_params(void* statement, const StringMap& params) { (void)statement; (void)params; return(false); }
|
||||
DValue SQLite::collect_rows(void* statement) { (void)statement; return(DValue()); }
|
||||
|
||||
SQLite* sqlite_connect(String path)
|
||||
{
|
||||
SQLite* db = new SQLite();
|
||||
db->request_cleanup_delete = true;
|
||||
db->connect(path);
|
||||
return(db);
|
||||
}
|
||||
|
||||
void sqlite_disconnect(SQLite* db) { if(db) { db->disconnect(); if(db->request_cleanup_delete) delete db; } }
|
||||
String sqlite_error(SQLite* db) { return(db ? db->error() : String(WASM_DB_UNAVAILABLE)); }
|
||||
DValue sqlite_query(SQLite* db, String q) { return(db ? db->query(q) : DValue()); }
|
||||
DValue sqlite_query(SQLite* db, String q, const StringMap& params) { return(db ? db->query(q, params) : DValue()); }
|
||||
u64 sqlite_insert_id(SQLite* db) { return(db ? db->insert_id : 0); }
|
||||
u32 sqlite_affected_rows(SQLite* db) { return(db ? db->affected_rows : 0); }
|
||||
void cleanup_sqlite_connections() { }
|
||||
|
||||
static ServerState wasm_server;
|
||||
static Request wasm_request;
|
||||
static String wasm_output;
|
||||
static String wasm_response_meta;
|
||||
|
||||
// ---- vague-linkage link anchors --------------------------------------------
|
||||
// Units import libc++ template instantiations they use; --export-all only
|
||||
// exports what the core itself instantiated. Some libc++ internals lack the
|
||||
// hide-from-ABI attribute (the Phase 0 lambda finding), so units emit them as
|
||||
// imports rather than binding locally. This function exists purely to make
|
||||
// the core instantiate — and therefore export — the ones the site tree needs.
|
||||
// Extend it when the loader reports "unresolved import env.<libc++ symbol>".
|
||||
extern "C" void uce_wasm_link_anchors()
|
||||
{
|
||||
StringMap string_map;
|
||||
string_map["k"] = "v";
|
||||
string_map.erase(String("k")); // __tree::__erase_unique<String>
|
||||
std::map<String, DValue> dvalue_map;
|
||||
dvalue_map["k"] = DValue();
|
||||
dvalue_map.erase(String("k"));
|
||||
std::vector<String> string_list = { "a", "b" };
|
||||
string_list.erase(string_list.begin());
|
||||
std::set<String> string_set;
|
||||
string_set.insert("k");
|
||||
string_set.erase(String("k"));
|
||||
|
||||
// libc functions units may call that the core itself never references —
|
||||
// taking their address forces them into the link (and --export-all)
|
||||
static void* volatile libc_anchors[] = {
|
||||
(void*)&atof, (void*)&atoi, (void*)&atol, (void*)&atoll,
|
||||
(void*)&strtol, (void*)&strtoul, (void*)&strtoll, (void*)&strtoull,
|
||||
(void*)&strtod, (void*)&strtof,
|
||||
(void*)&qsort, (void*)&bsearch,
|
||||
(void*)&snprintf, (void*)&sscanf,
|
||||
(void*)&memmove, (void*)&strncmp, (void*)&strncpy,
|
||||
// memchr/strchr/strrchr/strstr are C++-overloaded; cast to the C shape
|
||||
(void*)(const void* (*)(const void*, int, size_t))&memchr,
|
||||
(void*)(const char* (*)(const char*, int))&strchr,
|
||||
(void*)(const char* (*)(const char*, int))&strrchr,
|
||||
(void*)(const char* (*)(const char*, const char*))&strstr,
|
||||
};
|
||||
(void)libc_anchors;
|
||||
}
|
||||
|
||||
// W3 membrane: the host resolves component/render targets to funcref-table
|
||||
// slots (loading units lazily) and writes the resolved unit path back so
|
||||
// nested relative component resolution keeps working.
|
||||
extern "C" int32_t uce_host_component_resolve(
|
||||
const char* target, size_t target_len, int32_t kind,
|
||||
const char* current_unit, size_t current_unit_len,
|
||||
char* resolved_buf, size_t resolved_cap);
|
||||
|
||||
// target → table slot, reset per request (workspaces die with the request,
|
||||
// but a single workspace can render the same component many times)
|
||||
static std::map<String, s32> wasm_component_slots;
|
||||
|
||||
// These mirror small page-runtime pieces of compiler.cpp, which is carved
|
||||
// out of the wasm core wholesale (it is the native toolchain: parser, clang
|
||||
// driver, dlopen). Kept byte-identical where possible.
|
||||
String component_normalize_path(String name)
|
||||
{
|
||||
name = trim(name);
|
||||
if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce")
|
||||
return(name);
|
||||
return(name + ".uce");
|
||||
}
|
||||
|
||||
void component_parse_target(String target, String& file_name, String& render_name)
|
||||
{
|
||||
target = trim(target);
|
||||
render_name = "";
|
||||
auto render_split_pos = target.find(":");
|
||||
if(render_split_pos != String::npos)
|
||||
{
|
||||
render_name = trim(target.substr(render_split_pos + 1));
|
||||
target = trim(target.substr(0, render_split_pos));
|
||||
}
|
||||
file_name = target;
|
||||
}
|
||||
|
||||
String component_error_banner(String message)
|
||||
{
|
||||
return("<div class=\"banner\">" + html_escape(message) + "</div>");
|
||||
}
|
||||
|
||||
struct RequestPropsScope
|
||||
{
|
||||
Request* context = 0;
|
||||
DValue previous_props;
|
||||
|
||||
RequestPropsScope(Request* context, const DValue& props)
|
||||
{
|
||||
this->context = context;
|
||||
if(this->context)
|
||||
{
|
||||
previous_props = this->context->props;
|
||||
this->context->props = props;
|
||||
}
|
||||
}
|
||||
|
||||
~RequestPropsScope()
|
||||
{
|
||||
if(context)
|
||||
context->props = previous_props;
|
||||
}
|
||||
};
|
||||
|
||||
// kind values shared with the host loader (see src/wasm/worker.h)
|
||||
enum WasmResolveKind { WASM_RESOLVE_COMPONENT = 0, WASM_RESOLVE_RENDER = 1, WASM_RESOLVE_EXISTS = 2 };
|
||||
|
||||
static s32 wasm_resolve_target(String target, s32 kind, String* resolved_out = 0)
|
||||
{
|
||||
String cache_key = std::to_string(kind) + ":" + target;
|
||||
auto cached = wasm_component_slots.find(cache_key);
|
||||
if(cached != wasm_component_slots.end() && kind != WASM_RESOLVE_EXISTS)
|
||||
return(cached->second);
|
||||
char resolved[512];
|
||||
String current = context ? context->resources.current_unit_file : "";
|
||||
s32 slot = uce_host_component_resolve(
|
||||
target.data(), target.size(), kind,
|
||||
current.data(), current.size(),
|
||||
resolved, sizeof(resolved));
|
||||
if(resolved_out && slot)
|
||||
*resolved_out = String(resolved, strnlen(resolved, sizeof(resolved)));
|
||||
if(kind != WASM_RESOLVE_EXISTS)
|
||||
wasm_component_slots[cache_key] = slot;
|
||||
return(slot);
|
||||
}
|
||||
|
||||
String component_resolve(String name)
|
||||
{
|
||||
String resolved;
|
||||
if(wasm_resolve_target(trim(name), WASM_RESOLVE_EXISTS, &resolved))
|
||||
return(resolved);
|
||||
return("");
|
||||
}
|
||||
|
||||
bool component_exists(String name)
|
||||
{
|
||||
return(component_resolve(name) != "");
|
||||
}
|
||||
|
||||
void component_render(String name, DValue props, Request& request)
|
||||
{
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(trim(name), WASM_RESOLVE_COMPONENT, &resolved);
|
||||
if(!slot)
|
||||
{
|
||||
print(component_error_banner("component not found: " + trim(name)));
|
||||
return;
|
||||
}
|
||||
RequestPropsScope props_scope(&request, props);
|
||||
String previous_unit = request.resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
request.resources.current_unit_file = resolved;
|
||||
// a wasm function pointer is its index in the shared funcref table; the
|
||||
// host returned the handler's slot, so this is a plain call_indirect
|
||||
request_ref_handler handler = (request_ref_handler)(uintptr_t)slot;
|
||||
handler(request);
|
||||
request.resources.current_unit_file = previous_unit;
|
||||
}
|
||||
|
||||
void component_render(String name) { DValue props; component_render(name, props, *context); }
|
||||
void component_render(String name, Request& request) { DValue props; component_render(name, props, request); }
|
||||
void component_render(String name, DValue props) { component_render(name, props, *context); }
|
||||
|
||||
String component(String name, DValue props, Request& request)
|
||||
{
|
||||
ob_start();
|
||||
component_render(name, props, request);
|
||||
return(ob_get_close());
|
||||
}
|
||||
|
||||
String component(String name) { DValue props; return(component(name, props, *context)); }
|
||||
String component(String name, Request& request) { DValue props; return(component(name, props, request)); }
|
||||
String component(String name, DValue props) { return(component(name, props, *context)); }
|
||||
|
||||
void unit_render(String file_name, Request& request)
|
||||
{
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(trim(file_name), WASM_RESOLVE_RENDER, &resolved);
|
||||
if(!slot)
|
||||
{
|
||||
print(component_error_banner("unit not found: " + trim(file_name)));
|
||||
return;
|
||||
}
|
||||
String previous_unit = request.resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
request.resources.current_unit_file = resolved;
|
||||
request_ref_handler handler = (request_ref_handler)(uintptr_t)slot;
|
||||
handler(request);
|
||||
request.resources.current_unit_file = previous_unit;
|
||||
}
|
||||
|
||||
void unit_render(String file_name) { unit_render(file_name, *context); }
|
||||
|
||||
extern "C" {
|
||||
|
||||
void* uce_alloc(size_t len)
|
||||
{
|
||||
return(malloc(len));
|
||||
}
|
||||
|
||||
void uce_free(void* ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
u32 uce_wasm_core_abi_version()
|
||||
{
|
||||
return(6);
|
||||
}
|
||||
|
||||
int uce_wasm_core_init()
|
||||
{
|
||||
wasm_server.config = default_config();
|
||||
wasm_request.server = &wasm_server;
|
||||
// the primary output stream must live ON ob_stack (native semantics):
|
||||
// ob_get_close()/ob_close() pop and rebalance against the stack, so a
|
||||
// stream outside it would be orphaned by the first component() capture
|
||||
if(wasm_request.ob_stack.empty())
|
||||
wasm_request.ob_start();
|
||||
context = &wasm_request;
|
||||
return(0);
|
||||
}
|
||||
|
||||
void uce_wasm_core_reset_request()
|
||||
{
|
||||
if(context == 0)
|
||||
uce_wasm_core_init();
|
||||
wasm_request.call = DValue();
|
||||
wasm_request.props = DValue();
|
||||
wasm_request.params.clear();
|
||||
wasm_request.get.clear();
|
||||
wasm_request.post.clear();
|
||||
wasm_request.header.clear();
|
||||
wasm_request.set_cookies.clear();
|
||||
wasm_request.response_code = "HTTP/1.1 200 OK";
|
||||
wasm_request.flags = Request::Flags();
|
||||
wasm_request.stats = Request::Stats();
|
||||
for(auto* stream : wasm_request.ob_stack)
|
||||
delete stream;
|
||||
wasm_request.ob_stack.clear();
|
||||
wasm_request.ob_start();
|
||||
wasm_output = "";
|
||||
wasm_response_meta = "";
|
||||
wasm_request.cookies.clear();
|
||||
wasm_request.session.clear();
|
||||
wasm_request.out = "";
|
||||
wasm_request.resources.current_unit_file = "";
|
||||
wasm_component_slots.clear();
|
||||
}
|
||||
|
||||
// Host pushes the UCEB1-encoded request context into a guest buffer
|
||||
// (uce_alloc) and applies it here; mirrors the native param population.
|
||||
int uce_wasm_apply_context(const char* buf, size_t len)
|
||||
{
|
||||
if(context == 0)
|
||||
uce_wasm_core_init();
|
||||
DValue decoded;
|
||||
String error;
|
||||
if(!ucb_decode(String(buf, len), decoded, &error))
|
||||
{
|
||||
uce_host_log(3, error.data(), error.size());
|
||||
return(1);
|
||||
}
|
||||
wasm_request.call = decoded;
|
||||
auto apply_map = [](DValue* source, StringMap& dest) {
|
||||
dest.clear();
|
||||
if(source)
|
||||
source->each([&](const DValue& item, String key) {
|
||||
dest[key] = item.to_string();
|
||||
});
|
||||
};
|
||||
apply_map(decoded.key("params"), wasm_request.params);
|
||||
apply_map(decoded.key("get"), wasm_request.get);
|
||||
apply_map(decoded.key("post"), wasm_request.post);
|
||||
apply_map(decoded.key("cookies"), wasm_request.cookies);
|
||||
apply_map(decoded.key("session"), wasm_request.session);
|
||||
DValue* entry = decoded.key("entry_unit");
|
||||
if(entry)
|
||||
wasm_request.resources.current_unit_file = entry->to_string();
|
||||
return(0);
|
||||
}
|
||||
|
||||
Request* uce_wasm_request()
|
||||
{
|
||||
if(context == 0)
|
||||
uce_wasm_core_init();
|
||||
return(&wasm_request);
|
||||
}
|
||||
|
||||
// After render: response metadata (status line, headers, cookies, session)
|
||||
// goes back to the host as UCEB1.
|
||||
void uce_wasm_finish_response_meta()
|
||||
{
|
||||
DValue meta;
|
||||
meta["status"] = wasm_request.response_code;
|
||||
for(auto& header : wasm_request.header)
|
||||
meta["headers"][header.first] = header.second;
|
||||
for(auto& cookie : wasm_request.set_cookies)
|
||||
{
|
||||
DValue cookie_value;
|
||||
cookie_value = cookie;
|
||||
meta["cookies"].push(cookie_value);
|
||||
}
|
||||
for(auto& entry : wasm_request.session)
|
||||
meta["session"][entry.first] = entry.second;
|
||||
wasm_response_meta = ucb_encode(meta);
|
||||
}
|
||||
|
||||
const char* uce_wasm_response_meta_data()
|
||||
{
|
||||
return(wasm_response_meta.data());
|
||||
}
|
||||
|
||||
size_t uce_wasm_response_meta_size()
|
||||
{
|
||||
return(wasm_response_meta.size());
|
||||
}
|
||||
|
||||
void uce_print_bytes(const char* data, size_t len)
|
||||
{
|
||||
if(context == 0)
|
||||
uce_wasm_core_init();
|
||||
if(context->ob && data && len)
|
||||
context->ob->write(data, len);
|
||||
}
|
||||
|
||||
void uce_wasm_finish_output()
|
||||
{
|
||||
// ob_stack[0] is the request's primary stream; nested captures above it
|
||||
// belong to unbalanced ob_start() calls and are intentionally ignored
|
||||
wasm_output = wasm_request.ob_stack.empty() ? String("") : wasm_request.ob_stack[0]->str();
|
||||
}
|
||||
|
||||
const char* uce_wasm_output_data()
|
||||
{
|
||||
return(wasm_output.data());
|
||||
}
|
||||
|
||||
size_t uce_wasm_output_size()
|
||||
{
|
||||
return(wasm_output.size());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user