#include "JetParser.h" #include "JetCodegen.h" #include #include #include #include #include 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 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] " << 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 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 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; }