Stage 1 of the APIServer plan: a compiled --shared .so currently exports
only a bare jetc_render_<name>(Global&, MString&) with no way for a host
to discover what HTTP method+path it wants to serve. Adds a leading
"#api METHOD /path" directive (one or more, parsed the same way jetc
already strips a leading "#!" shebang line - not modeled as a new
AST/tag-attribute concept, since jetc's parser has no existing notion of
the enclosing <jet> tag's attributes to hang this off of).
In --shared mode, JetCodegen now also emits a second exported function
alongside the render entry point:
struct JetApiRoute { const char *method; const char *path; };
extern "C" const JetApiRoute *jetc_api_<name>(int *count) { ... }
<name> is derived the same way for both symbols (sanitizeIdent(
baseNameNoExt(inputPath))), so a future host loader can find both by
convention from just the .so's filename - no name needs to be embedded
in the metadata itself, and the routes a module declares can never drift
out of sync with the render function that implements them (the failure
mode a sidecar manifest file would have).
Malformed directives (missing method/path, path without a leading '/')
are a hard compile error; #api directives present without --shared print
a non-fatal warning (the metadata has nowhere to go without a host to
load a .so, but the render-only executable/CGI path still works fine).
Verified live, not just compiled: rebuilt jetc, compiled demo3.jet (a new
worked example with two #api lines sharing one handler) via
build-shared.sh into a real demo3.so, and added api_test.cpp (mirrors
host_test.cpp's dlopen()/dlsym() pattern) confirming jetc_api_demo3
returns the expected two routes at runtime. Re-ran host_test against the
same .so to confirm the render path is unaffected. Confirmed demo.jet/
demo2.jet (no #api lines) emit no JetApiRoute code at all - no regression
for templates that don't declare routes.
Also fixed a gitignore gap surfaced while committing this: an earlier
"jetc/*.cpp" ignore rule (meant for generated *.compiled.cpp output) was
a blanket pattern that silently hid this commit's own api_test.cpp from
git status. Narrowed to "jetc/*.compiled.cpp" (the actual generated-file
naming, per build-compiled.sh/build-shared.sh) and added explicit ignores
for the host_test/api_test/demo3 binaries plus two known stale generated
leftovers (demo.cpp/demo2.cpp) that predate this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
78 lines
3.8 KiB
C++
78 lines
3.8 KiB
C++
#ifndef __JetCodegen_h__
|
|
#define __JetCodegen_h__
|
|
|
|
#include "JetAST.h"
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
namespace jetc {
|
|
|
|
// One `#api METHOD /path` directive from the top of the source template
|
|
// (see jetc.cpp) - a compile-time-known route this template wants to
|
|
// serve once loaded as a .so. Purely descriptive data handed to Codegen;
|
|
// jetc itself never interprets the method/path beyond storing them.
|
|
struct ApiRoute {
|
|
std::string method;
|
|
std::string path;
|
|
};
|
|
|
|
// 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.
|
|
// apiRoutes/apiSymbol are ignored in this mode (there's no host to
|
|
// dlsym them).
|
|
//
|
|
// 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.
|
|
//
|
|
// If apiRoutes is non-empty, sharedMode also emits a second exported
|
|
// function `extern "C" const JetApiRoute *<apiSymbol>(int *count)`
|
|
// returning a static array describing every `#api` directive the
|
|
// source declared - a host's directory watcher/loader dlsym()'s this
|
|
// (by the same filename-derived naming convention as entrySymbol) to
|
|
// learn what to register, without any separate manifest file that
|
|
// could drift out of sync with the render function it describes.
|
|
std::string generate(const std::vector<NodePtr> &top, const std::string &sourceFileComment,
|
|
bool sharedMode = false, const std::string &entrySymbol = "jetc_render",
|
|
const std::vector<ApiRoute> &apiRoutes = {}, const std::string &apiSymbol = "jetc_api");
|
|
|
|
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
|