#include "BXParser.h" #include #include #include #include int BXParser::get() { int c = buf_.get(); ch_ += 1; if (c == '\n') { ch_ = 1; line_ += 1; } return c; } void BXParser::skip(char expected) { int ch = get(); if (ch == EOF) { error(std::string("Expected '") + expected + "', got EOF"); } else if (ch != expected) { error(std::string("Expected '") + expected + "', got '" + (char)ch + "'"); } } [[noreturn]] void BXParser::error(std::string msg) { throw BXParseError(std::to_string(line_) + ":" + std::to_string(ch_) + ": " + msg); } [[noreturn]] void BXParser::error(std::string msg, TokenKind kind) { switch (kind) { case TokenKind::E_O_F: msg += " EOF"; break; case TokenKind::INDENTATION: msg += " indentation"; break; case TokenKind::NEWLINE: msg += " newline"; break; case TokenKind::COMMA: msg += " comma ','"; break; case TokenKind::COLON_EQUALS: msg += " colon equals ':='"; break; case TokenKind::PLUS_EQUALS: msg += " plus equals '+='"; break; case TokenKind::EQUALS_PLUS: msg += " equals plus '=+'"; break; case TokenKind::BAR_EQUALS: msg += " bar equals '|='"; break; case TokenKind::EXPANSION: msg += " expansion"; break; case TokenKind::STRING: msg += " string"; break; case TokenKind::NONE: msg += " none"; break; } error(msg); } std::string BXParser::readIdent(const BXVariables &vars) { std::string str; int ch; while ((ch = peek()) != EOF) { if ( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_')) { str.push_back(ch); get(); } else { break; } } return str; } void BXParser::skipWhitespace() { int ch; while ((ch = peek()) == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { get(); } } char BXParser::readEscape() { int ch = get(); if (ch == EOF) { error("Unexpected EOF"); } else if (ch == 'n') { return '\n'; } else if (ch == 'r') { return '\r'; } else if (ch == 't') { return '\t'; } else { return (char)ch; } } std::string BXParser::readStringExpansion(const BXVariables &vars) { bool braced = peek() == '{'; std::string key; if (braced) { get(); skipWhitespace(); key = readString(vars); } else { key = readIdent(vars); } auto it = vars.find(key); if (it == vars.end()) { error("Key '" + key + "' doesn't exist"); } if (braced) { skipWhitespace(); if (peek() != '}') { error("Expected a '}' after a '${' expansion"); } get(); } // TODO: Use BXValue.asString() return it->second[0]; } std::string BXParser::readQuotedString(const BXVariables &vars) { std::string str; int ch; while ((ch = peek()) != EOF) { if (ch == '\\') { get(); str.push_back(readEscape()); } else if (ch == '$') { get(); str += readStringExpansion(vars); } else if (ch == '"') { get(); break; } else { str.push_back(ch); get(); } } return str; } std::string BXParser::readString(const BXVariables &vars) { std::string str; int ch; while ((ch = peek()) != EOF) { if (ch == '\\') { get(); str.push_back(readEscape()); } else if (ch == '$') { get(); str += readStringExpansion(vars); } else if (ch == '"') { get(); str += readQuotedString(vars); } else if ( ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '}' || ch == ',') { break; } else { str.push_back(ch); get(); } } return str; } BXParser::Token BXParser::getToken(const BXVariables &vars) { Token tok; tok.line = line(); tok.ch = ch(); int ch = peek(); if (ch == EOF) { tok.kind = TokenKind::E_O_F; return tok; } if (ch == '\t' || ch == ' ') { tok.kind = TokenKind::INDENTATION; do { get(); ch = peek(); } while (ch == '\t' || ch == ' '); return tok; } else if (ch == '\n' || ch == '\r') { tok.kind = TokenKind::NEWLINE; do { get(); ch = peek(); } while (ch == '\n' || ch == '\r'); return tok; } int ch2 = peek(2); if (ch == ',') { get(); tok.kind = TokenKind::COMMA; } else if (ch == ':' && ch2 == '=') { get(); get(); tok.kind = TokenKind::COLON_EQUALS; } else if (ch == '+' && ch2 == '=') { get(); get(); tok.kind = TokenKind::PLUS_EQUALS; } else if (ch == '=' && ch2 == '+') { get(); get(); tok.kind = TokenKind::EQUALS_PLUS; } else if (ch == '|' && ch2 == '=') { get(); get(); tok.kind = TokenKind::BAR_EQUALS; } else if (ch == '$' && ch2 == '{') { get(); get(); skipWhitespace(); tok.kind = TokenKind::EXPANSION; tok.str = readString(vars); skipWhitespace(); if (peek() != '}') { error("Expected a '}' after a '${' expansion."); } get(); } else if (ch == '$') { get(); tok.kind = TokenKind::EXPANSION; tok.str = readString(vars); } else { tok.kind = TokenKind::STRING; tok.str = readString(vars); } while ((ch = peek()) == '\t' || ch == ' ') { get(); } return tok; } BXParser::Token BXParser::readToken(const BXVariables &vars) { Token t = tok_; tok_ = getToken(vars); return t; } void BXWriter::escape(const std::string &str) { buf_.put('"'); for (char ch: str) { if (ch == '$' || ch == '"' || ch == '\\') { buf_.put('\\'); } buf_.put(ch); } buf_.put('"'); } void BXWriter::write(const BXVariables &vars) { for (const auto &pair: vars) { size_t chars = 0; buf_.put(pair.first); buf_.put(" :="); for (auto &val: pair.second) { if (chars >= 80) { buf_.put('\n'); buf_.put('\t'); chars = 0; } else { buf_.put(' '); } escape(val); chars += val.size(); } buf_.put('\n'); } } void BXParser::parse(BXVariables &vars, bool oneLine) { readToken(vars); while (true) { if (peekToken().kind == TokenKind::E_O_F) { break; } else if (peekToken().kind != TokenKind::STRING) { error("Expected string, got", peekToken().kind); } Token t = readToken(vars); std::string key = t.str; std::vector &var = vars[key]; void (*addVal)(std::vector &var, std::string val); switch (peekToken().kind) { case TokenKind::COLON_EQUALS: var.clear(); // Fallthrough case TokenKind::PLUS_EQUALS: addVal = [](auto &var, auto val) { var.push_back(std::move(val)); }; break; case TokenKind::EQUALS_PLUS: addVal = [](auto &var, auto val) { var.insert(var.begin(), std::move(val)); }; break; case TokenKind::BAR_EQUALS: addVal = [](auto &var, auto val) { for (auto &v: var) { if (v == val) { return; } } var.push_back(val); }; break; default: error("Expected operator, got", peekToken().kind); } readToken(vars); parseList(vars, var, addVal, oneLine); } } void BXParser::parseList( BXVariables &vars, std::vector &var, void (*addVal)(std::vector &var, std::string val), bool oneLine) { while (true) { Token tok = peekToken(); switch (tok.kind) { case TokenKind::NEWLINE: if (oneLine) { return; } readToken(vars); if (peekToken().kind != TokenKind::INDENTATION) { return; } readToken(vars); // Read indentation break; case TokenKind::STRING: addVal(var, std::move(tok.str)); readToken(vars); break; case TokenKind::COMMA: readToken(vars); return; case TokenKind::E_O_F: return; case TokenKind::EXPANSION: for (auto &v: vars[tok.str]) { addVal(var, v); } readToken(vars); break; default: error("Unexpected token", tok.kind); } } } void BXParser::parseList(BXVariables &vars, std::vector &var) { auto addVal = [](auto &var, auto val) { var.push_back(std::move(val)); }; readToken(vars); parseList(vars, var, addVal, false); }