#include "JetParser.h" #include #include namespace jetc { bool Parser::starts(const std::string &lit) const { return s.compare(pos, lit.size(), lit) == 0; } bool Parser::isNameChar(char c) { return std::isalnum((unsigned char)c) || c == '-' || c == '_'; } void Parser::skipWs() { while (!eof() && std::isspace((unsigned char)s[pos])) pos++; } std::string Parser::readName() { size_t start = pos; while (!eof() && isNameChar(s[pos])) pos++; return s.substr(start, pos - start); } std::string Parser::peekTagName(size_t at) const { size_t p = at + 1; size_t start = p; while (p < s.size() && isNameChar(s[p])) p++; return s.substr(start, p - start); } bool Parser::matchTagOpen(const std::string &name, size_t at) const { if (s.compare(at, 1, "<") != 0) return false; size_t p = at + 1; size_t nstart = p; while (p < s.size() && isNameChar(s[p])) p++; if (s.compare(nstart, p - nstart, name) != 0) return false; if (p >= s.size()) return false; char c = s[p]; return c == ' ' || c == '>' || c == '/'; } bool Parser::matchTagClose(const std::string &name, size_t at) const { std::string closer = ""; return s.compare(at, closer.size(), closer) == 0; } bool Parser::skipOpenTag(size_t &p) const { p++; // '<' while (p < s.size() && isNameChar(s[p])) p++; while (p < s.size()) { if (s[p] == '"') { p++; while (p < s.size() && s[p] != '"') { if (s[p] == '\\' && p + 1 < s.size()) p++; p++; } if (p < s.size()) p++; continue; } if (s.compare(p, 2, "/>") == 0) { p += 2; return true; } if (s[p] == '>') { p += 1; return false; } p++; } throw std::runtime_error("jetc: unterminated tag near end of input"); } std::map Parser::parseAttrs(bool &hasContainer) { std::map attrs; while (true) { skipWs(); if (eof()) throw std::runtime_error("jetc: unterminated tag near end of input"); if (starts("/>")) { pos += 2; hasContainer = false; break; } if (starts(">")) { pos += 1; hasContainer = true; break; } std::string aname = readName(); if (aname.empty()) throw std::runtime_error("jetc: malformed attribute near offset " + std::to_string(pos)); if (starts("=\"")) { pos += 2; std::string val; while (!eof() && s[pos] != '"') { if (s[pos] == '\\' && pos + 1 < s.size()) { val += s[pos]; val += s[pos + 1]; pos += 2; continue; } val += s[pos++]; } if (!eof()) pos++; // closing quote attrs[aname] = val; } } return attrs; } std::pair Parser::scanBody(size_t bodyStart, const std::string &tagName, bool lookForElse) { size_t p = bodyStart; int depth = 1; size_t elsePos = std::string::npos; while (p < s.size()) { if (matchTagClose(tagName, p)) { depth--; if (depth == 0) return {p, elsePos}; p += 3 + tagName.size(); continue; } if (lookForElse && depth == 1 && elsePos == std::string::npos && s.compare(p, 6, "") == 0) { elsePos = p; p += 6; continue; } if (matchTagOpen(tagName, p)) { bool selfClosing = skipOpenTag(p); if (!selfClosing) depth++; continue; } if (s[p] == '<') { if (s.compare(p, 2, "') q++; p = q; } else { skipOpenTag(p); } continue; } p++; } throw std::runtime_error("jetc: unterminated <" + tagName + "> - no matching close tag found"); } std::vector Parser::parseSub(size_t start, size_t end) { Parser sub(s.substr(start, end - start)); return sub.parseNodes(); } NodePtr Parser::parseVar() { size_t start = pos; pos += 2; // "$[" or "#[" int depth = 1; while (!eof() && depth > 0) { if (starts("$[") || starts("#[")) { depth++; pos += 2; continue; } if (s[pos] == ']') { depth--; pos++; continue; } pos++; } auto n = std::make_shared(); n->kind = NodeKind::VAR; n->varText = s.substr(start, pos - start); return n; } NodePtr Parser::parseSet() { size_t origStart = pos; pos += 4; // " (value comes from rendering nested content) // isn't modeled by this prototype - fall back to full interpretation // of the whole tag instead of failing to compile. auto span = scanBody(pos, "set", false); pos = span.first + 3 + 3; // "" auto n = std::make_shared(); n->kind = NodeKind::FALLBACK; n->rawSpan = s.substr(origStart, pos - origStart); return n; } auto n = std::make_shared(); n->kind = NodeKind::SET; n->setName = attrs.count("name") ? attrs["name"] : ""; if (attrs.count("value")) { n->setHasValue = true; n->setValue = attrs["value"]; } if (attrs.count("expr")) { n->setHasExpr = true; n->setExpr = attrs["expr"]; } n->setScope = attrs.count("scope") ? attrs["scope"] : ""; return n; } NodePtr Parser::parseIf() { pos += 3; // "(); n->kind = NodeKind::IF; if (attrs.count("expr")) { n->cond.mode = CondMode::EXPR; n->cond.expr = attrs["expr"]; } else { n->cond.mode = CondMode::VALUES; n->cond.value1 = attrs.count("value1") ? attrs["value1"] : ""; n->cond.value2 = attrs.count("value2") ? attrs["value2"] : ""; n->cond.type = attrs.count("type") ? attrs["type"] : ""; } if (hasContainer) { auto span = scanBody(pos, "if", true); size_t closePos = span.first, elsePos = span.second; size_t thenEnd = (elsePos != std::string::npos) ? elsePos : closePos; n->thenBody = parseSub(pos, thenEnd); if (elsePos != std::string::npos) { n->hasElse = true; n->elseBody = parseSub(elsePos + 6, closePos); } pos = closePos + 3 + 2; // "" } return n; } NodePtr Parser::parseFor() { pos += 4; // "(); n->kind = NodeKind::FOR; if (attrs.count("start")) { n->forHasStart = true; n->forStart = attrs["start"]; } n->forEnd = attrs.count("end") ? attrs["end"] : "0"; n->forStep = attrs.count("step") ? attrs["step"] : "1"; if (attrs.count("name")) { n->forHasName = true; n->forName = attrs["name"]; } n->forScope = attrs.count("scope") ? attrs["scope"] : ""; if (hasContainer) { auto span = scanBody(pos, "for", false); n->body = parseSub(pos, span.first); pos = span.first + 3 + 3; // "" } return n; } NodePtr Parser::parseWhile() { pos += 6; // "(); n->kind = NodeKind::WHILE; if (attrs.count("expr")) { n->cond.mode = CondMode::EXPR; n->cond.expr = attrs["expr"]; } else { n->cond.mode = CondMode::VALUES; n->cond.value1 = attrs.count("value1") ? attrs["value1"] : ""; n->cond.value2 = attrs.count("value2") ? attrs["value2"] : ""; n->cond.type = attrs.count("type") ? attrs["type"] : ""; } if (hasContainer) { auto span = scanBody(pos, "while", false); n->body = parseSub(pos, span.first); pos = span.first + 3 + 5; // "" } return n; } NodePtr Parser::parseFallback() { size_t origStart = pos; std::string tagName = peekTagName(pos); size_t p = pos; bool selfClosing = skipOpenTag(p); if (!selfClosing) { auto span = scanBody(p, tagName, false); p = span.first + 3 + tagName.size(); } pos = p; auto n = std::make_shared(); n->kind = NodeKind::FALLBACK; n->rawSpan = s.substr(origStart, pos - origStart); return n; } std::vector Parser::parseNodes() { std::vector nodes; std::string textBuf; auto flush = [&]() { if (!textBuf.empty()) { auto n = std::make_shared(); n->kind = NodeKind::TEXT; n->text = textBuf; nodes.push_back(n); textBuf.clear(); } }; while (!eof()) { if (starts("|>")) { flush(); pos += 2; std::string buf; while (!eof() && !starts("<|")) buf += s[pos++]; if (!eof()) pos += 2; auto n = std::make_shared(); n->kind = NodeKind::TEXT; n->text = buf; nodes.push_back(n); continue; } if (s[pos] == '<') { if (matchTagOpen("set", pos)) { flush(); nodes.push_back(parseSet()); continue; } if (matchTagOpen("if", pos)) { flush(); nodes.push_back(parseIf()); continue; } if (matchTagOpen("for", pos)) { flush(); nodes.push_back(parseFor()); continue; } if (matchTagOpen("while", pos)) { flush(); nodes.push_back(parseWhile()); continue; } flush(); nodes.push_back(parseFallback()); continue; } if (starts("$[") || starts("#[")) { flush(); nodes.push_back(parseVar()); continue; } textBuf += s[pos++]; } flush(); return nodes; } }