For the server you're about to build: `jetc --shared template.jet out.cpp`
now emits no main() at all - instead the render logic is exported as
`extern "C" void jetc_render_<template>(jet::Global &global,
coreutils::MString &out)`, one call per request, meant to be dlsym()'d
by a long-running host process instead of spawning a jet-2.0 process (or
even a separate compiled executable) per request. extern "C" here only
fixes the symbol's name mangling for a stable dlsym() target - the
parameter types are still real C++ classes, safe to pass across the
dlopen() boundary since the host and the .so share the same
CoreUtils/JetCore headers and toolchain (a same-ABI plugin boundary, not
a foreign C one). coreutils::Exception propagates out of the call
uncaught, so the host decides how to handle a render failure rather than
the module unilaterally deciding (the executable mode's try/catch prints
to stdout and exits, which is CGI-appropriate but wrong for a persistent
server). The exported symbol name is derived from the input filename
(jetc.cpp's baseNameNoExt+sanitizeIdent) so multiple compiled templates'
.so's stay collision-free even under dlopen(..., RTLD_GLOBAL).
jetc/build-shared.sh builds the .so: JetCore's 22 tag classes are still
force-linked directly (Tag::interpretSpan's fallback path can dispatch
to any of them at runtime, regardless of what a given template
statically references), but CoreUtils objects are compiled -fPIC and
archived into a throwaway libCoreUtils_pic.a instead of being linked
directly - libCoreUtils.a itself isn't position-independent, so linking
it into a .so fails outright ("relocation ... can not be used when
making a shared object"), and force-linking every CoreUtils .cpp
directly (rather than archiving, so the linker only pulls in what's
actually referenced) surfaced an unrelated, pre-existing incomplete
class - IMFMessage's vtable is never emitted anywhere, presumably a
partially-migrated class (see project memory on IMFMultipart/IMFText
still being fd-based) - as an unresolved dlopen()-time symbol, even
though nothing here uses it. A normal archive-based link (like the
existing libCoreUtils.a already does for jet-2.0) never pulls IMFMessage
in at all, so building libCoreUtils_pic.a the same way sidesteps it
without touching the unrelated bug itself.
jetc/host_test.cpp is a minimal stand-in for the real server: dlopen()s
a compiled .so, dlsym()s its entry point, and calls it twice with fresh
jet::Global objects (simulating two separate requests against one loaded
module) to prove the .so is genuinely reusable, not single-use. Verified
both demo templates (demo.jet, demo2.jet - the latter covering nested
if/for, a stateful while loop, and the interpretSpan fallback path) build
as .so's and produce the same byte-identical output as the executable
and interpreter paths, across two calls each. Also re-verified the
existing (non-shared) executable build path still matches jet-2.0's
output after the Codegen::generate() signature change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
223 lines
10 KiB
C++
223 lines
10 KiB
C++
#include "JetCodegen.h"
|
|
#include <stdexcept>
|
|
|
|
namespace jetc {
|
|
|
|
std::string Codegen::ind(int indent) {
|
|
return std::string(indent * 3, ' ');
|
|
}
|
|
|
|
std::string Codegen::cstr(const std::string &raw) {
|
|
std::string r;
|
|
r.reserve(raw.size() + 8);
|
|
r += '"';
|
|
for (unsigned char c : raw) {
|
|
switch (c) {
|
|
case '\\': r += "\\\\"; break;
|
|
case '"': r += "\\\""; break;
|
|
case '\n': r += "\\n"; break;
|
|
case '\r': r += "\\r"; break;
|
|
case '\t': r += "\\t"; break;
|
|
default:
|
|
if (c < 0x20) {
|
|
char buf[8];
|
|
snprintf(buf, sizeof(buf), "\\x%02x\" \"", c);
|
|
r += buf;
|
|
} else {
|
|
r += (char)c;
|
|
}
|
|
}
|
|
}
|
|
r += '"';
|
|
return r;
|
|
}
|
|
|
|
const char *Codegen::staticTypeOp(const std::string &type, const char *rc) {
|
|
// type is known at compile time (a literal attribute value, not a
|
|
// runtime expression) so the comparison operator is picked once here
|
|
// instead of being re-checked against six string constants on every
|
|
// request the way Tag::__if/__while do it at runtime.
|
|
static thread_local std::string expr;
|
|
if (type == "eq") expr = std::string(rc) + " == 0";
|
|
else if (type == "ne") expr = std::string(rc) + " != 0";
|
|
else if (type == "lt") expr = std::string(rc) + " == -1";
|
|
else if (type == "le") expr = std::string(rc) + " != 1";
|
|
else if (type == "gt") expr = std::string(rc) + " == 1";
|
|
else if (type == "ge") expr = std::string(rc) + " != -1";
|
|
else throw std::runtime_error("jetc: type=\"" + type + "\" is not one of eq/ne/lt/le/gt/ge "
|
|
"(and jetc only supports a literal type= value - use expr= for a dynamic comparison).");
|
|
return expr.c_str();
|
|
}
|
|
|
|
void Codegen::emitBody(const std::vector<NodePtr> &nodes, int indent) {
|
|
for (auto &n : nodes) emitNode(n, indent);
|
|
}
|
|
|
|
void Codegen::emitNode(const NodePtr &n, int indent) {
|
|
std::string I = ind(indent);
|
|
switch (n->kind) {
|
|
|
|
case NodeKind::TEXT: {
|
|
if (n->text.empty()) break;
|
|
out << I << "out << " << cstr(n->text) << ";\n";
|
|
break;
|
|
}
|
|
|
|
case NodeKind::VAR: {
|
|
out << I << "{ coreutils::ZString v(" << cstr(n->varText) << "); out << scope.getVariable(v, true); }\n";
|
|
break;
|
|
}
|
|
|
|
case NodeKind::SET: {
|
|
std::string k = id("set_");
|
|
out << I << "{\n";
|
|
out << I << " scope.keywords[\"" << k << "name\"] = " << cstr(n->setName) << ";\n";
|
|
out << I << " scope.keywords[\"" << k << "scope\"] = " << cstr(n->setScope) << ";\n";
|
|
out << I << " coreutils::MString val;\n";
|
|
if (n->setHasExpr) {
|
|
out << I << " scope.keywords[\"" << k << "expr\"] = " << cstr(n->setExpr) << ";\n";
|
|
out << I << " val = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).string;\n";
|
|
} else {
|
|
out << I << " scope.keywords[\"" << k << "value\"] = " << cstr(n->setHasValue ? n->setValue : std::string()) << ";\n";
|
|
out << I << " val = scope.resolveKeyword(\"" << k << "value\");\n";
|
|
}
|
|
out << I << " scope.assignVariable(scope.resolveKeyword(\"" << k << "name\"), val, scope.resolveKeyword(\"" << k << "scope\"));\n";
|
|
out << I << "}\n";
|
|
break;
|
|
}
|
|
|
|
case NodeKind::IF: {
|
|
std::string k = id("if_");
|
|
out << I << "{\n";
|
|
out << I << " bool cond;\n";
|
|
if (n->cond.mode == CondMode::EXPR) {
|
|
out << I << " scope.keywords[\"" << k << "expr\"] = " << cstr(n->cond.expr) << ";\n";
|
|
out << I << " cond = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).boolean;\n";
|
|
} else {
|
|
out << I << " scope.keywords[\"" << k << "value1\"] = " << cstr(n->cond.value1) << ";\n";
|
|
out << I << " scope.keywords[\"" << k << "value2\"] = " << cstr(n->cond.value2) << ";\n";
|
|
out << I << " int rc = scope.resolveKeyword(\"" << k << "value1\").compare(scope.resolveKeyword(\"" << k << "value2\"));\n";
|
|
out << I << " cond = (" << staticTypeOp(n->cond.type, "rc") << ");\n";
|
|
}
|
|
out << I << " if (cond) {\n";
|
|
emitBody(n->thenBody, indent + 2);
|
|
if (n->hasElse) {
|
|
out << I << " } else {\n";
|
|
emitBody(n->elseBody, indent + 2);
|
|
}
|
|
out << I << " }\n";
|
|
out << I << "}\n";
|
|
break;
|
|
}
|
|
|
|
case NodeKind::FOR: {
|
|
std::string k = id("for_");
|
|
out << I << "{\n";
|
|
out << I << " scope.keywords[\"" << k << "start\"] = " << cstr(n->forHasStart ? n->forStart : std::string("0")) << ";\n";
|
|
out << I << " scope.keywords[\"" << k << "end\"] = " << cstr(n->forEnd) << ";\n";
|
|
out << I << " scope.keywords[\"" << k << "step\"] = " << cstr(n->forStep) << ";\n";
|
|
out << I << " double ix = scope.resolveKeyword(\"" << k << "start\").asDouble();\n";
|
|
if (n->forHasName) {
|
|
out << I << " scope.keywords[\"" << k << "name\"] = " << cstr(n->forName) << ";\n";
|
|
out << I << " scope.keywords[\"" << k << "scope\"] = " << cstr(n->forScope) << ";\n";
|
|
}
|
|
out << I << " for (; ix <= scope.resolveKeyword(\"" << k << "end\").asDouble(); ix += scope.resolveKeyword(\"" << k << "step\").asDouble()) {\n";
|
|
if (n->forHasName) {
|
|
out << I << " coreutils::MString nameSel = scope.resolveKeyword(\"" << k << "name\");\n";
|
|
out << I << " coreutils::MString scopeSel = scope.resolveKeyword(\"" << k << "scope\");\n";
|
|
out << I << " if ((scopeSel == \"\") || (scopeSel == \"global\")) global.variables[nameSel] = ix;\n";
|
|
out << I << " else if (scopeSel == \"local\") scope.local->variables[nameSel] = ix;\n";
|
|
out << I << " else if (scopeSel == \"parent\") scope.parent->local->variables[nameSel] = ix;\n";
|
|
out << I << " else throw coreutils::Exception(\"scope value is not valid.\");\n";
|
|
}
|
|
emitBody(n->body, indent + 2);
|
|
out << I << " }\n";
|
|
out << I << "}\n";
|
|
break;
|
|
}
|
|
|
|
case NodeKind::WHILE: {
|
|
std::string k = id("while_");
|
|
out << I << "{\n";
|
|
out << I << " bool cond;\n";
|
|
if (n->cond.mode == CondMode::EXPR) {
|
|
out << I << " scope.keywords[\"" << k << "expr\"] = " << cstr(n->cond.expr) << ";\n";
|
|
out << I << " coreutils::MString exprSaved = scope.keywords[\"" << k << "expr\"];\n";
|
|
out << I << " cond = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).boolean;\n";
|
|
out << I << " while (cond) {\n";
|
|
emitBody(n->body, indent + 2);
|
|
out << I << " scope.keywords[\"" << k << "expr\"].reset();\n";
|
|
out << I << " scope.keywords[\"" << k << "expr\"] = exprSaved;\n";
|
|
out << I << " cond = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).boolean;\n";
|
|
out << I << " }\n";
|
|
} else {
|
|
out << I << " scope.keywords[\"" << k << "value1\"] = " << cstr(n->cond.value1) << ";\n";
|
|
out << I << " scope.keywords[\"" << k << "value2\"] = " << cstr(n->cond.value2) << ";\n";
|
|
out << I << " int rc = scope.resolveKeyword(\"" << k << "value1\").compare(scope.resolveKeyword(\"" << k << "value2\"));\n";
|
|
out << I << " cond = (" << staticTypeOp(n->cond.type, "rc") << ");\n";
|
|
out << I << " while (cond) {\n";
|
|
emitBody(n->body, indent + 2);
|
|
out << I << " int rc2 = scope.resolveKeyword(\"" << k << "value1\").compare(scope.resolveKeyword(\"" << k << "value2\"));\n";
|
|
out << I << " cond = (" << staticTypeOp(n->cond.type, "rc2") << ");\n";
|
|
out << I << " }\n";
|
|
}
|
|
out << I << "}\n";
|
|
break;
|
|
}
|
|
|
|
case NodeKind::FALLBACK: {
|
|
out << I << "{ coreutils::ZString span(" << cstr(n->rawSpan) << "); scope.interpretSpan(span, out); }\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string Codegen::generate(const std::vector<NodePtr> &top, const std::string &sourceFileComment,
|
|
bool sharedMode, const std::string &entrySymbol) {
|
|
out.str("");
|
|
out.clear();
|
|
out << "// " << sourceFileComment << "\n";
|
|
out << "// Generated by jetc - DO NOT EDIT BY HAND.\n";
|
|
out << "#include \"Global.h\"\n";
|
|
out << "#include \"Tag.h\"\n";
|
|
out << "#include \"Operand.h\"\n";
|
|
out << "#include \"ZString.h\"\n";
|
|
out << "#include \"MString.h\"\n";
|
|
out << "#include \"Exception.h\"\n";
|
|
out << "#include <iostream>\n\n";
|
|
|
|
if (sharedMode) {
|
|
out << "extern \"C\" void " << entrySymbol << "(jet::Global &global, coreutils::MString &out) {\n";
|
|
out << " coreutils::ZString jetc_empty(\"\");\n";
|
|
out << " coreutils::MString jetc_scopeOut;\n";
|
|
out << " jet::Tag scope(jetc_empty, jetc_scopeOut, global, NULL, NULL);\n";
|
|
emitBody(top, 1);
|
|
out << "}\n";
|
|
return out.str();
|
|
}
|
|
|
|
out << "static void jetc_render(jet::Global &global, coreutils::MString &out) {\n";
|
|
out << " coreutils::ZString jetc_empty(\"\");\n";
|
|
out << " coreutils::MString jetc_scopeOut;\n";
|
|
out << " jet::Tag scope(jetc_empty, jetc_scopeOut, global, NULL, NULL);\n";
|
|
emitBody(top, 1);
|
|
out << "}\n\n";
|
|
out << "int main(int argc, char **argv, char **envp) {\n";
|
|
out << " jet::Global global(envp);\n";
|
|
out << " coreutils::MString out;\n";
|
|
out << " try {\n";
|
|
out << " jetc_render(global, out);\n";
|
|
out << " global.outputHeaders();\n";
|
|
out << " std::cout << out;\n";
|
|
out << " } catch(coreutils::Exception e) {\n";
|
|
out << " std::cout << out;\n";
|
|
out << " std::cout << \"\\n----- Error: \" << e.text << std::endl;\n";
|
|
out << " return 1;\n";
|
|
out << " }\n";
|
|
out << " return 0;\n";
|
|
out << "}\n";
|
|
return out.str();
|
|
}
|
|
|
|
}
|