#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; } int BXParser::peek() { return buf_.peek(); } int BXParser::peek2() { return buf_.peek2(); } BXParser::Operator BXParser::readOperator() { int ch2 = peek2(); if (peek() == ':' && ch2 == '=') { skip(); // ':' skip(); // '=' return Operator::COLON_EQUALS; } else if (peek() == '+' && ch2 == '=') { skip(); // '+' skip(); // '=' return Operator::PLUS_EQUALS; } else if (peek() == '=' && ch2 == '+') { skip(); // '=' skip(); // '+' return Operator::EQUALS_PLUS; } return Operator::NONE; } 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); } static bool isWhitespace(int ch) { if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') return true; return false; } void BXParser::skipWhitespace() { if (flags_ & FLAG_ONE_LINE) { int ch; while (isWhitespace(ch = peek()) && ch != '\r' && ch != '\n') get(); } else { while (isWhitespace(peek())) get(); } } char BXParser::parseEscape() { skip(); // '\' int ch; switch (ch = get()) { case EOF: error("Unexpected EOF"); case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; default: return (char)ch; } } static void appendVariableToString( const BXVariables &vars, std::string &name, std::string &value) { if (name.size() == 0) return; auto it = vars.find(name); if (it == vars.end()) return; auto &vec = it->second; bool first = true; for (auto &part: vec) { if (!first) { value += ' '; } first = false; value += part; } } static void appendVariableToArray( const BXVariables &vars, const std::string &name, std::vector &values) { if (name.size() == 0) return; auto it = vars.find(name); if (it == vars.end()) return; auto &vec = it->second; for (auto &part: vec) { values.push_back(part); } } void BXParser::parseExpansion(const BXVariables &vars, std::vector &values) { skip(); // '$' std::string str; switch (peek()) { case '{': skip(); parseString(vars, str, '}'); skip('}'); appendVariableToArray(vars, str, values); break; default: if (!parseIdentifier(str)) { error("No identifier after $."); } appendVariableToArray(vars, str, values); break; } } void BXParser::parseQuotedExpansion(const BXVariables &vars, std::string &content) { skip(); // '$' std::string str; switch (peek()) { case '{': skip(); parseString(vars, str, '}'); skip('}'); appendVariableToString(vars, str, content); break; default: if (!parseIdentifier(str)) { error("No identifier after $."); } appendVariableToString(vars, str, content); break; } } void BXParser::parseQuotedString(const BXVariables &vars, std::string &content) { skip(); // '"' int ch; while ((ch = peek()) != EOF) { switch (ch) { case EOF: error("Unexpected EOF"); case '\\': content.push_back(parseEscape()); break; case '$': parseQuotedExpansion(vars, content); break; case '"': skip(); return; default: content.push_back(get()); break; } } } bool BXParser::parseString(const BXVariables &vars, std::string &content, int sep) { bool success = false; int ch; while (1) { ch = peek(); if ((sep > 0 && ch == sep) || isWhitespace(ch)) { return success; } switch (ch) { case EOF: return success; case '\\': content.push_back(parseEscape()); success = true; break; case '$': parseQuotedExpansion(vars, content); success = true; break; case '"': parseQuotedString(vars, content); success = true; break; default: if (ch == ':' && peek2() == '=') return success; content.push_back(get()); success = true; break; } } } bool BXParser::parseIdentifier(std::string &content) { int ch = peek(); if (!( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_'))) { return false; } content += get(); while (1) { ch = peek(); if (!( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_'))) { return true; } content += get(); } } void BXParser::parse(BXVariables &vars) { std::string key, value; std::vector values; skipWhitespace(); if (!parseString(vars, key)) { return; } skipWhitespace(); Operator prevOper = readOperator(); if (prevOper == Operator::NONE) { error("Expected operator."); } auto doAssignment = [&] { switch (prevOper) { case Operator::COLON_EQUALS: vars[key] = std::move(values); values.clear(); break; case Operator::PLUS_EQUALS: { auto &vec = vars[key]; vec.reserve(vec.size() + values.size()); for (size_t i = 0; i < values.size(); ++i) { vec.push_back(std::move(values[i])); } } values.clear(); break; case Operator::EQUALS_PLUS: { auto &vec = vars[key]; vec.reserve(vec.size() + values.size()); for (size_t i = 0; i < vec.size(); ++i) { values.push_back(std::move(vec[i])); } vec = std::move(values); } values.clear(); break; case Operator::NONE: break; } }; while (true) { skipWhitespace(); // Parse next value if (peek() == '$') { parseExpansion(vars, values); value.clear(); continue; // We can't have an assignment after an expansion } else if (!parseString(vars, value)) { break; } skipWhitespace(); // If there's an operator next, the value we just read was a actually a key. // Otherwise, it was just another value. Operator op = readOperator(); if (op == Operator::NONE) { values.push_back(std::move(value)); value.clear(); } else { if (value.size() == 0) { error("Expected string before assignment operator"); } doAssignment(); prevOper = op; key = std::move(value); value.clear(); } } doAssignment(); } void BXParser::parseList(const BXVariables &vars, std::vector &values) { while (true) { skipWhitespace(); std::string value; if (!parseString(vars, value)) { break; } values.push_back(std::move(value)); } } 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'); } }