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>
57 lines
2.7 KiB
C++
57 lines
2.7 KiB
C++
#ifndef __JetCodegen_h__
|
|
#define __JetCodegen_h__
|
|
|
|
#include "JetAST.h"
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
namespace jetc {
|
|
|
|
// Emits a standalone .cpp translation unit that renders a compiled JET
|
|
// template. The static tag/text structure becomes real C++ control flow;
|
|
// anything genuinely dynamic (variable values, expr/value comparisons,
|
|
// loop bounds) still calls into the existing JetCore/CoreUtils runtime
|
|
// (Tag::getVariable/resolveKeyword/assignVariable, jet::Operand) exactly
|
|
// as the interpreter does - compiling doesn't try to eliminate that, only
|
|
// the repeated re-parsing of the template's own structure.
|
|
class Codegen {
|
|
public:
|
|
// sharedMode == false (default): emits a static jetc_render() plus a
|
|
// main() that runs it once, catches coreutils::Exception, and writes
|
|
// the result to stdout - the existing CGI-style standalone program.
|
|
//
|
|
// sharedMode == true: emits no main() at all. Instead the render
|
|
// logic is exported as `extern "C" void <entrySymbol>(jet::Global
|
|
// &global, coreutils::MString &out)`, meant to be built into a .so
|
|
// (see build-shared.sh) and dlsym()'d by a long-running host server -
|
|
// one call renders one request into the caller-owned `out`, using
|
|
// whatever Global the host passes in (its lifecycle, cgi/session
|
|
// setup etc. is entirely the host's business, not this module's).
|
|
// extern "C" only affects name mangling here, not the parameter
|
|
// types - both sides are compiled from the same CoreUtils/JetCore
|
|
// headers with the same toolchain, so passing C++ class types (and
|
|
// letting coreutils::Exception propagate to the caller) across the
|
|
// dlopen() boundary is safe; this is a same-ABI plugin boundary, not
|
|
// a foreign C ABI one. entrySymbol should be unique per compiled
|
|
// template (see jetc.cpp) so multiple .so's can coexist even if a
|
|
// host ever dlopen()s them RTLD_GLOBAL instead of keeping separate
|
|
// handles.
|
|
std::string generate(const std::vector<NodePtr> &top, const std::string &sourceFileComment,
|
|
bool sharedMode = false, const std::string &entrySymbol = "jetc_render");
|
|
|
|
private:
|
|
std::ostringstream out;
|
|
size_t nextId = 0;
|
|
std::string id(const std::string &prefix) { return prefix + std::to_string(nextId++) + "_"; }
|
|
|
|
void emitBody(const std::vector<NodePtr> &nodes, int indent);
|
|
void emitNode(const NodePtr &n, int indent);
|
|
static std::string ind(int indent);
|
|
static std::string cstr(const std::string &raw); // C++ string-literal escape
|
|
static const char *staticTypeOp(const std::string &type, const char *rcVar);
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|