#include "JetCodegen.h" #include 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 &nodes, int indent, std::ostringstream &target) { for (auto &n : nodes) emitNode(n, indent, target); } void Codegen::emitNode(const NodePtr &n, int indent, std::ostringstream &target) { std::string I = ind(indent); switch (n->kind) { case NodeKind::TEXT: { if (n->text.empty()) break; target << I << "out << " << cstr(n->text) << ";\n"; break; } case NodeKind::VAR: { target << I << "{ coreutils::ZString v(" << cstr(n->varText) << "); out << scope.getVariable(v, true); }\n"; break; } case NodeKind::SET: { std::string k = id("set_"); target << I << "{\n"; target << I << " scope.keywords[\"" << k << "name\"] = " << cstr(n->setName) << ";\n"; target << I << " scope.keywords[\"" << k << "scope\"] = " << cstr(n->setScope) << ";\n"; target << I << " coreutils::MString val;\n"; if (n->setHasExpr) { target << I << " scope.keywords[\"" << k << "expr\"] = " << cstr(n->setExpr) << ";\n"; target << I << " val = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).string;\n"; } else { target << I << " scope.keywords[\"" << k << "value\"] = " << cstr(n->setHasValue ? n->setValue : std::string()) << ";\n"; target << I << " val = scope.resolveKeyword(\"" << k << "value\");\n"; } target << I << " scope.assignVariable(scope.resolveKeyword(\"" << k << "name\"), val, scope.resolveKeyword(\"" << k << "scope\"));\n"; target << I << "}\n"; break; } case NodeKind::IF: { std::string k = id("if_"); target << I << "{\n"; target << I << " bool cond;\n"; if (n->cond.mode == CondMode::EXPR) { target << I << " scope.keywords[\"" << k << "expr\"] = " << cstr(n->cond.expr) << ";\n"; target << I << " cond = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).boolean;\n"; } else { target << I << " scope.keywords[\"" << k << "value1\"] = " << cstr(n->cond.value1) << ";\n"; target << I << " scope.keywords[\"" << k << "value2\"] = " << cstr(n->cond.value2) << ";\n"; target << I << " int rc = scope.resolveKeyword(\"" << k << "value1\").compare(scope.resolveKeyword(\"" << k << "value2\"));\n"; target << I << " cond = (" << staticTypeOp(n->cond.type, "rc") << ");\n"; } target << I << " if (cond) {\n"; emitBody(n->thenBody, indent + 2, target); if (n->hasElse) { target << I << " } else {\n"; emitBody(n->elseBody, indent + 2, target); } target << I << " }\n"; target << I << "}\n"; break; } case NodeKind::FOR: { std::string k = id("for_"); target << I << "{\n"; target << I << " scope.keywords[\"" << k << "start\"] = " << cstr(n->forHasStart ? n->forStart : std::string("0")) << ";\n"; target << I << " scope.keywords[\"" << k << "end\"] = " << cstr(n->forEnd) << ";\n"; target << I << " scope.keywords[\"" << k << "step\"] = " << cstr(n->forStep) << ";\n"; target << I << " double ix = scope.resolveKeyword(\"" << k << "start\").asDouble();\n"; if (n->forHasName) { target << I << " scope.keywords[\"" << k << "name\"] = " << cstr(n->forName) << ";\n"; target << I << " scope.keywords[\"" << k << "scope\"] = " << cstr(n->forScope) << ";\n"; } target << I << " for (; ix <= scope.resolveKeyword(\"" << k << "end\").asDouble(); ix += scope.resolveKeyword(\"" << k << "step\").asDouble()) {\n"; if (n->forHasName) { target << I << " coreutils::MString nameSel = scope.resolveKeyword(\"" << k << "name\");\n"; target << I << " coreutils::MString scopeSel = scope.resolveKeyword(\"" << k << "scope\");\n"; target << I << " if ((scopeSel == \"\") || (scopeSel == \"global\")) global.variables[nameSel] = ix;\n"; target << I << " else if (scopeSel == \"local\") scope.local->variables[nameSel] = ix;\n"; target << I << " else if (scopeSel == \"parent\") scope.parent->local->variables[nameSel] = ix;\n"; target << I << " else throw coreutils::Exception(\"scope value is not valid.\");\n"; } emitBody(n->body, indent + 2, target); target << I << " }\n"; target << I << "}\n"; break; } case NodeKind::WHILE: { std::string k = id("while_"); target << I << "{\n"; target << I << " bool cond;\n"; if (n->cond.mode == CondMode::EXPR) { target << I << " scope.keywords[\"" << k << "expr\"] = " << cstr(n->cond.expr) << ";\n"; target << I << " coreutils::MString exprSaved = scope.keywords[\"" << k << "expr\"];\n"; target << I << " cond = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).boolean;\n"; target << I << " while (cond) {\n"; emitBody(n->body, indent + 2, target); target << I << " scope.keywords[\"" << k << "expr\"].reset();\n"; target << I << " scope.keywords[\"" << k << "expr\"] = exprSaved;\n"; target << I << " cond = jet::Operand(scope.keywords[\"" << k << "expr\"], scope).boolean;\n"; target << I << " }\n"; } else { target << I << " scope.keywords[\"" << k << "value1\"] = " << cstr(n->cond.value1) << ";\n"; target << I << " scope.keywords[\"" << k << "value2\"] = " << cstr(n->cond.value2) << ";\n"; target << I << " int rc = scope.resolveKeyword(\"" << k << "value1\").compare(scope.resolveKeyword(\"" << k << "value2\"));\n"; target << I << " cond = (" << staticTypeOp(n->cond.type, "rc") << ");\n"; target << I << " while (cond) {\n"; emitBody(n->body, indent + 2, target); target << I << " int rc2 = scope.resolveKeyword(\"" << k << "value1\").compare(scope.resolveKeyword(\"" << k << "value2\"));\n"; target << I << " cond = (" << staticTypeOp(n->cond.type, "rc2") << ");\n"; target << I << " }\n"; } target << I << "}\n"; break; } case NodeKind::FALLBACK: { target << I << "{ coreutils::ZString span(" << cstr(n->rawSpan) << "); scope.interpretSpan(span, out); }\n"; break; } case NodeKind::CONTAINER_SLOT: { // Reuses the interpreter's own scope-walking logic // verbatim (see Tag::renderContainer) rather than re-deriving // it - this is exactly what the runtime's own // dispatch does when it's reached via interpretSpan, just // without the text-scanning step to get there. target << I << "scope.renderContainer(out);\n"; break; } case NodeKind::TAGDEF: { // Codegen can still fail for a node that parsed successfully - // e.g. an / whose type= isn't one of eq/ne/lt/le/gt/ge // (staticTypeOp's own check - real legacy content occasionally // has one of these sitting in a branch that's never actually // reached at runtime either, so there's nothing to "correctly" // compile). Parsing this definition's body already succeeded // (see parseTagDef's own try/catch for that stage) or this node // wouldn't exist as TAGDEF at all - so mirror that same // graceful-degradation approach here for the emit stage: try // building the compiled function into a throwaway stream first, // and only splice it into tagDefFuncs (and register the // compiledTags fast path) if that fully succeeds. A failure // here just means this one definition stays interpreted, // unchanged - zero risk to the rest of the file. std::string funcName = "jetc_tagdef_" + id(""); std::ostringstream trial; bool compiledOk = true; try { trial << "static void " << funcName << "(jet::Tag &scope, coreutils::MString &out) {\n"; emitBody(n->tagDefBody, 1, trial); trial << "}\n\n"; } catch (const std::exception &) { compiledOk = false; } // Emitted in place, at this TAGDEF's original position in // program flow - matches exactly what FALLBACK would have // emitted for this same ... span, so // global.tags["x"] still gets populated as before (needed as a // correctness fallback: any invocation this compiler doesn't // itself recognize - e.g. reached through content this pass // never compiled - still resolves it exactly as it always has). target << I << "{ coreutils::ZString span(" << cstr(n->rawSpan) << "); scope.interpretSpan(span, out); }\n"; if (compiledOk) { // The registration into compiledTags right after is the only // new behavior beyond the raw registration above - a pure // additive fast path __dotag checks first (see __dotag.cpp); // a miss there falls through to the now-unchanged interpreted // path, which is exactly what happens when compiledOk is // false here too. tagDefFuncs << trial.str(); target << I << "global.compiledTags[coreutils::MString(" << cstr(n->tagDefName) << ")] = &" << funcName << ";\n"; } break; } } } std::string Codegen::generate(const std::vector &top, const std::string &sourceFileComment, bool sharedMode, const std::string &entrySymbol, const std::vector &apiRoutes, const std::string &apiSymbol) { out.str(""); out.clear(); tagDefFuncs.str(""); tagDefFuncs.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 \n\n"; // The entry function's own text is built into a separate stream so // tagDefFuncs - populated as a SIDE EFFECT of the emitBody() call // below, since TAGDEF nodes append to it as they're encountered - // can be spliced in ahead of it. A compiled tag-definition function // never needs to be forward-declared for anything defined before it // in program order (nothing upstream calls it directly - see // __dotag.cpp's runtime lookup instead), so this ordering is purely // for readability, not a correctness requirement. std::ostringstream body; if (sharedMode) { if (!apiRoutes.empty()) { // A plain C struct, not any CoreUtils/JetCore type - this is the // one piece of the plugin ABI a host loader needs to know about // independent of the rest of this same-ABI C++ boundary. body << "struct JetApiRoute { const char *method; const char *path; };\n"; body << "extern \"C\" const JetApiRoute *" << apiSymbol << "(int *count) {\n"; body << " static const JetApiRoute routes[] = {\n"; for (auto &r : apiRoutes) body << " { " << cstr(r.method) << ", " << cstr(r.path) << " },\n"; body << " };\n"; body << " *count = " << apiRoutes.size() << ";\n"; body << " return routes;\n"; body << "}\n\n"; } body << "extern \"C\" void " << entrySymbol << "(jet::Global &global, coreutils::MString &out) {\n"; body << " coreutils::ZString jetc_empty(\"\");\n"; body << " coreutils::MString jetc_scopeOut;\n"; body << " jet::Tag scope(jetc_empty, jetc_scopeOut, global, NULL, NULL);\n"; emitBody(top, 1, body); body << "}\n"; return out.str() + tagDefFuncs.str() + body.str(); } body << "static void jetc_render(jet::Global &global, coreutils::MString &out) {\n"; body << " coreutils::ZString jetc_empty(\"\");\n"; body << " coreutils::MString jetc_scopeOut;\n"; body << " jet::Tag scope(jetc_empty, jetc_scopeOut, global, NULL, NULL);\n"; emitBody(top, 1, body); body << "}\n\n"; body << "int main(int argc, char **argv, char **envp) {\n"; body << " jet::Global global(envp);\n"; body << " coreutils::MString out;\n"; body << " try {\n"; body << " jetc_render(global, out);\n"; body << " global.outputHeaders();\n"; body << " std::cout << out;\n"; body << " } catch(coreutils::Exception e) {\n"; body << " std::cout << out;\n"; body << " std::cout << \"\\n----- Error: \" << e.text << std::endl;\n"; body << " return 1;\n"; body << " }\n"; body << " return 0;\n"; body << "}\n"; return out.str() + tagDefFuncs.str() + body.str(); } }