Files
JetCore/jetc/jetc.cpp
T
Brad ArantandClaude Sonnet 5 9bbb95cc57 jetc: rename --shared build output convention from .so to .jtc
Purely a naming-convention change per user request, not a format
change - the output is still a real ELF shared object (-shared -fPIC),
still dlopen()-able exactly as before. .jtc ("jet compiled") marks it as
an APIServer-loadable content module rather than a generic library.

build-shared.sh: usage text and STEM derivation
(basename "$OUTPUT" .so -> .jtc) both updated - the STEM change is the
one functional piece here, everything else in this commit is comments/
usage strings. Doc comments in jetc.cpp/JetCodegen.h/host_test.cpp/
api_test.cpp updated to match.

Verified live: rebuilt jetc and all five demo*.jtc modules, reran
api_test/host_test against demo3.jtc (still resolves jetc_api_demo3/
jetc_render_demo3 and renders correctly) - the extension is purely
cosmetic to dlopen(), as expected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 11:41:44 -07:00

119 lines
4.5 KiB
C++

#include "JetParser.h"
#include "JetCodegen.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <cctype>
#include <vector>
namespace {
std::string baseNameNoExt(const std::string &path) {
size_t slash = path.find_last_of("/\\");
std::string base = (slash == std::string::npos) ? path : path.substr(slash + 1);
size_t dot = base.find_last_of('.');
if (dot != std::string::npos) base = base.substr(0, dot);
return base;
}
std::string sanitizeIdent(const std::string &s) {
std::string r;
for (char c : s) r += (std::isalnum((unsigned char)c) || c == '_') ? c : '_';
return r;
}
}
int main(int argc, char **argv) {
bool shared = false;
std::vector<std::string> pos;
for (int i = 1; i < argc; ++i) {
std::string a = argv[i];
if (a == "--shared") shared = true;
else pos.push_back(a);
}
if (pos.size() < 2) {
std::cerr << "usage: jetc [--shared] <input.jet> <output.cpp>" << std::endl;
std::cerr << " --shared emit a dlsym()-able entry point (see build-shared.sh)" << std::endl;
std::cerr << " instead of a standalone main()." << std::endl;
return 1;
}
const std::string &inputPath = pos[0];
const std::string &outputPath = pos[1];
std::ifstream in(inputPath);
if (!in) {
std::cerr << "jetc: cannot open " << inputPath << std::endl;
return 1;
}
std::ostringstream ss;
ss << in.rdbuf();
std::string src = ss.str();
// Skip a leading shebang line, mirroring jet-2.0.cpp's own data.goeol()
// call (run before it constructs the top-level __jet tag) so a
// "#!../jet-2.0" first line doesn't get compiled in as literal output.
if (src.rfind("#!", 0) == 0) {
size_t nl = src.find('\n');
src = (nl == std::string::npos) ? std::string() : src.substr(nl + 1);
}
// Consume zero or more leading "#api METHOD /path" directive lines
// (right after the optional shebang), the same way the shebang itself
// is stripped - a plain directive line, not a new tag/attribute concept,
// since jetc's parser has no existing notion of the enclosing <jet> tag's
// attributes to hang this off of. Each becomes one route this template
// wants to serve once loaded as a .jtc (see JetCodegen's apiRoutes).
std::vector<jetc::ApiRoute> apiRoutes;
while (src.rfind("#api ", 0) == 0) {
size_t nl = src.find('\n');
std::string line = (nl == std::string::npos) ? src : src.substr(0, nl);
src = (nl == std::string::npos) ? std::string() : src.substr(nl + 1);
std::istringstream ls(line);
std::string tag, method, path;
ls >> tag >> method >> path;
if (method.empty() || path.empty() || path[0] != '/') {
std::cerr << "jetc: malformed #api directive (expected \"#api METHOD /path\"): "
<< line << std::endl;
return 1;
}
for (auto &c : method) c = (char)std::toupper((unsigned char)c);
apiRoutes.push_back({method, path});
}
try {
jetc::Parser parser(src);
auto nodes = parser.parseNodes();
jetc::Codegen codegen;
// Both symbols are derived from the input filename (not the output
// path) so they stay stable and predictable for whatever builds and
// dlsym()'s the resulting .jtc - unique per template, so multiple
// compiled templates' .jtc's can coexist in one process even under
// dlopen(..., RTLD_GLOBAL) instead of requiring isolated handles.
std::string baseIdent = sanitizeIdent(baseNameNoExt(inputPath));
std::string symbol = "jetc_render_" + baseIdent;
std::string apiSymbol = "jetc_api_" + baseIdent;
if (!apiRoutes.empty() && !shared)
std::cerr << "jetc: warning: #api directive(s) present but --shared not specified - "
"route metadata will not be emitted" << std::endl;
std::string cpp = codegen.generate(nodes, std::string("compiled from ") + inputPath, shared,
symbol, apiRoutes, apiSymbol);
std::ofstream outFile(outputPath);
outFile << cpp;
outFile.close();
if (shared) {
std::cout << "jetc: wrote " << outputPath << " (entry symbol: " << symbol << ")";
if (!apiRoutes.empty())
std::cout << " (api symbol: " << apiSymbol << ", " << apiRoutes.size() << " route(s))";
std::cout << std::endl;
} else {
std::cout << "jetc: wrote " << outputPath << std::endl;
}
} catch (std::exception &e) {
std::cerr << "jetc: " << e.what() << std::endl;
return 1;
}
return 0;
}