#include "comdelparser.h" #include "poly.h" #include "tokenstype.h" #include #include #include #include ComdelParser::ComdelParser(std::vector tokens) : tokens(std::move(tokens)) , position(0) {} // Tries to consume a token, returns an error otherwise. // Unlike the regular consume() call this should be used // when a missing token means a syntax error. #define RETURN_IF_NOT_TOKEN(tokenType) \ do { \ if (!consume(tokenType)) { \ return unexpected(); \ } \ } while (0) // Checks if the current token matches the argument. // Otherwise, adds the token to the list of expected tokens (used in // error message). bool ComdelParser::check(TokenType tokenType) { if(current().type == tokenType) return true; expectedTokens.insert(tokenType); return false; } Token &ComdelParser::current() { return tokens[position]; } // attempts to consume a token and returns whether it's successful bool ComdelParser::consume(TokenType tokenType) { bool exists = check(tokenType); if (exists) { bump(); } return exists; } #define ATLAS_ASSERT_FAIL(x) \ do{}while(0) void ComdelParser::bump() { if (tokens[position].type == TokenType::END_OF_FILE) { ATLAS_ASSERT_FAIL("Internal parser error, called bump after EOF"); } else { tokens[++position]; } expectedTokens.clear(); } // Generates an "unexpected token" error that also // includes a list of valid alternatives based on the contents // of the expectedTokens vector // // e.g. "expected one of: ',', ')', found 'c'" // when parsing "func(a, b c);" PError ComdelParser::unexpected() { std::stringstream ss; ss << "Found: `" << current().text << "`. "; ss << "Expected"; if (expectedTokens.size() > 1) { ss << " one of: "; } else { ss << ": "; } uint token_counter = 0; for (auto& type : expectedTokens) { if (type == TokenType::IDENTIFIER || type == TokenType::NUMBER || type == TokenType::STRING || type == TokenType::COLOR) { ss << tokenTypeToString(type); } else { ss << "`" << tokenTypeToString(type) << "`"; } ++token_counter; if (token_counter < expectedTokens.size()) ss << ", "; else ss <<"."; } return PError({current().span, ss.str()}); } // Parses a list of 'nodes' separated by 'separator'. Parsed nodes // are returned in a vector. A list can be empty which will give // an empty vector. // // The list is enclosed in 'openDelim' and 'closeDelim'. // // 'openDelim' and 'separator' are optional (could be nullopt). // // 'closeDelim' is mandatory, and it will be consumed. // // Function 'parse_f' should only parse a single node of type T. // // Parameter allowTrailing==true means that the list should end with // 'separator' and otherwise the list should end with 'node'. This // parameter is false in every call-site! template PResult> ComdelParser::parseList(std::optional openDelim, TokenType closeDelim, std::optional separator, bool allowTrailing, const std::function()>& parse_f) { std::vector vec; if(openDelim) { RETURN_IF_NOT_TOKEN(*openDelim); } bool first = true; while (true) { if (consume(closeDelim)) { break; } if (!first && separator) { RETURN_IF_NOT_TOKEN(*separator); if (allowTrailing && consume(closeDelim)) { break; } } first = false; auto item = parse_f(); RETURN_IF_ERR(item); vec.push_back(*item); } return vec; } void ComdelParser::skipUntilNextKeyword() { int depth = 0; bool enteredBlock = false; while(true) { if(is_keyword(current().type) && (!enteredBlock || (enteredBlock && depth == 0))) { break; } if(check(TokenType::LBRACE)) { enteredBlock = true; depth++; } else if(check(TokenType::RBRACE)) { if(depth == 0) { break; } depth--; } else if(check(TokenType::END_OF_FILE)) { break; } bump(); } } const std::vector &ComdelParser::getErrors() { return errors; } Span &ComdelParser::getPreviousSpan() { static Span span; if(position == 0) { return span; } else { return tokens[position].span; } } // See comment for Spanner Spanner ComdelParser::getSpanner() { return Spanner(current().span, getPreviousSpan()); } // utility for reading strings #define ASSIGN_OR_SET_ERR(var, presult) \ do { \ auto&& assign_or_return_if_err_temp_ = (presult); \ if (!assign_or_return_if_err_temp_) { \ err = PError(assign_or_return_if_err_temp_.error()); \ } else { \ var = *assign_or_return_if_err_temp_; \ } \ } while (0) // utility for reading strings #define APPEND_OR_SET_ERR(var, presult) \ do { \ auto&& assign_or_return_if_err_temp_ = (presult); \ if (!assign_or_return_if_err_temp_) { \ err = PError(assign_or_return_if_err_temp_.error()); \ } else { \ var.push_back(*assign_or_return_if_err_temp_); \ } \ } while (0) // utility for reading strings #define APPEND_OR_RETURN_IF_ERR(var, presult) \ do { \ auto&& assign_or_return_if_err_temp_ = (presult); \ if (!assign_or_return_if_err_temp_) { \ return PError(assign_or_return_if_err_temp_.error()); \ } else { \ var.push_back(*assign_or_return_if_err_temp_); \ } \ } while (0) /**************************************************************************** * * F U N C T I O N S O F R E C U R S I V E D E S C E N T P A R S E R * ****************************************************************************/ std::optional ComdelParser::parse() { auto spanner = getSpanner(); LibraryNode library{}; while ( ! check(TokenType::END_OF_FILE) ) { PResult> err; if(check(TokenType::KW_NAME)){ bump(); ASSIGN_OR_SET_ERR(library.name, parseString()); } else if(check(TokenType::KW_HEADER)) { bump(); ASSIGN_OR_SET_ERR(library.header, parseString()); } else if(check(TokenType::KW_DIRECTORY)) { bump(); ASSIGN_OR_SET_ERR(library.componentDirectory, parseString()); } else if(check(TokenType::KW_INFO)) { bump(); ASSIGN_OR_SET_ERR(library.libraryInfo, parseString()); } else if(check(TokenType::KW_ADDRESS)) { APPEND_OR_SET_ERR(library.addressSpaces, parseAddress()); } else if(check(TokenType::KW_COMPONENT)) { APPEND_OR_SET_ERR(library.components, parseComponent()); } else if(check(TokenType::KW_BUS)) { APPEND_OR_SET_ERR(library.buses, parseBus()); } else if(check(TokenType::KW_CONNECTION)) { APPEND_OR_SET_ERR(library.connections, parseConnection()); } else if(check(TokenType::KW_MESSAGES)) { bump(); ASSIGN_OR_SET_ERR(library.messages, parseList(std::optional(TokenType::LBRACE), TokenType::RBRACE, std::nullopt, false, [this]{return parseProperty(std::optional(TokenType::STRING));} )); if(!err.has_value()) { skipUntilNextKeyword(); bump(); } } else { err = unexpected(); bump(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); } } if (errors.size()) return std::nullopt; return spanner(library); } /**************************************************************************** * * StringNode := "\"" + CONTENT + "\"" * ****************************************************************************/ PResult ComdelParser::parseString() { auto spanner = getSpanner(); if (check(TokenType::STRING)) { StringNode node; node.value = current().text; node.span = current().span; bump(); return spanner(node); } return unexpected(); } /**************************************************************************** * * IdentifierNode := IDENTIFIER * ****************************************************************************/ PResult ComdelParser::parseIdentifier() { auto spanner = getSpanner(); if (check(TokenType::IDENTIFIER)) { IdentifierNode node; node.value = current().text; node.span = current().span; bump(); return spanner(node); } return unexpected(); } /**************************************************************************** * * NumberNode := ('0x' | '0b'){0,1}[0-9]* * ****************************************************************************/ PResult ComdelParser::parseNumber() { auto spanner = getSpanner(); if (check(TokenType::NUMBER)) { NumberNode node{current().text}; node.span = current().span; bump(); return spanner(node); } return unexpected(); } /**************************************************************************** * * CountNode := "@size (" + NumberNode + "," + NumberNode + ")" * ****************************************************************************/ PResult ComdelParser::parseCount() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_COUNT); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); auto first = parseNumber(); if(!first.has_value()) { return PError(first.error()); } RETURN_IF_NOT_TOKEN(TokenType::COMMA); auto second = parseNumber(); if(!second.has_value()) { return PError(second.error()); } RETURN_IF_NOT_TOKEN(TokenType::RPAREN); return spanner(CountNode{first.value(), second.value()}); } /**************************************************************************** * * PropertyNode := key: value; * ****************************************************************************/ PResult ComdelParser::parseProperty(std::optional valueType = std::nullopt) { auto spanner = getSpanner(); IdentifierNode key; ASSIGN_OR_RETURN_IF_ERR(key, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::COLON); if(valueType.has_value()) { if(valueType == TokenType::BOOL_TYPE) { if(!(check(TokenType::TRUE) || check(TokenType::FALSE))) { return unexpected(); } } else if(!check(*valueType)) { return unexpected(); } } ValueNode value; ASSIGN_OR_RETURN_IF_ERR(value, parseValue()); RETURN_IF_NOT_TOKEN(TokenType::SEMICOLON); PropertyNode node; node.key = key; node.value = value; return spanner(node); } /**************************************************************************** * * AddressSpaceNode := "@address" + IDENTIFIER "(" + NUMBER + "," + NUMBER + ")" * ****************************************************************************/ PResult ComdelParser::parseAddress() { auto spanner = getSpanner(); AddressSpaceNode addressSpace{}; RETURN_IF_NOT_TOKEN(TokenType::KW_ADDRESS); ASSIGN_OR_RETURN_IF_ERR(addressSpace.name, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(addressSpace.start, parseNumber()); RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(addressSpace.end, parseNumber()); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); return spanner(addressSpace); } /**************************************************************************** * * ComponentNode := "@component" + IDENTIFIER + ("processor" | "memory") { COMPONENT_BLOCK } * ****************************************************************************/ PResult ComdelParser::parseComponent() { auto spanner = getSpanner(); ComponentNode component{}; RETURN_IF_NOT_TOKEN(TokenType::KW_COMPONENT); ASSIGN_OR_RETURN_IF_ERR(component.name, parseIdentifier()); auto type = parseComponentType(); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if(check(TokenType::KW_INSTANCE_NAME)) { bump(); ASSIGN_OR_SET_ERR(component.instanceName, parseString()); } else if(check(TokenType::KW_TOOLTIP)) { bump(); ASSIGN_OR_SET_ERR(component.tooltip, parseString()); } else if(check(TokenType::KW_SOURCE)) { bump(); ASSIGN_OR_SET_ERR(component.source, parseString()); } else if(check(TokenType::KW_COUNT)) { ASSIGN_OR_SET_ERR(component.count, parseCount()); } else if(check(TokenType::KW_DISPLAY)) { ASSIGN_OR_SET_ERR(component.display, parseDisplay()); } else if(check(TokenType::KW_PIN)) { APPEND_OR_SET_ERR(component.pins, parsePin()); } else if(check(TokenType::KW_ATTRIBUTE)) { APPEND_OR_SET_ERR(component.attributes, parseAttribute()); } else if(check(TokenType::KW_RULE)) { APPEND_OR_SET_ERR(component.rules, parseRule()); } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(component); } PResult> ComdelParser::parseComponentType() { EnumNode type; auto spanner = getSpanner(); if(check(TokenType::CT_PROCESSOR)) { bump(); type = EnumNode(ComponentNode::PROCESSOR); } else if(check(TokenType::CT_MEMORY)) { bump(); type = EnumNode(ComponentNode::MEMORY); } else { type = EnumNode(ComponentNode::OTHER); } return spanner(type); } /**************************************************************************** * * DisplayNode := "@display {" + (DISPLAY_ITEM)* + "}" * ****************************************************************************/ PResult ComdelParser::parseDisplay() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_DISPLAY); DisplayNode display; RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult item; item = parseDisplayItem(); RETURN_IF_ERR(item); display.items.push_back(*item); } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(display); } /**************************************************************************** * * DisplayItemNode := "TYPE {(KEY + ":" + VALUE + ";")*} * ****************************************************************************/ PResult ComdelParser::parseDisplayItem() { auto spanner = getSpanner(); DisplayItemNode displayItem; ASSIGN_OR_RETURN_IF_ERR(displayItem.type, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult item{parseProperty()}; RETURN_IF_ERR(item); displayItem.values.push_back(*item); } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(displayItem); } /**************************************************************************** * * BusNode := "@bus " + NAME + TYPE + "{" + POPUP + "}" * ****************************************************************************/ PResult ComdelParser::parseBus() { auto spanner = getSpanner(); BusNode bus; RETURN_IF_NOT_TOKEN(TokenType::KW_BUS); ASSIGN_OR_RETURN_IF_ERR(bus.name, parseIdentifier()); auto type = parseBusType(); RETURN_IF_ERR(type); bus.type = *type; RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if(check(TokenType::KW_TOOLTIP)) { bump(); ASSIGN_OR_RETURN_IF_ERR(bus.tooltip, parseString()); } else if(check(TokenType::KW_COUNT)) { ASSIGN_OR_RETURN_IF_ERR(bus.count, parseCount()); } else if(check(TokenType::KW_DISPLAY)) { ASSIGN_OR_RETURN_IF_ERR(bus.display, parseDisplay()); } else if(check(TokenType::KW_WIRES)) { bump(); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(check(TokenType::IDENTIFIER)) { APPEND_OR_RETURN_IF_ERR(bus.wires, parseWire()); if(check(TokenType::COMMA)) { RETURN_IF_NOT_TOKEN(TokenType::COMMA); } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(bus); } PResult> ComdelParser::parseBusType() { PResult> type; if(check(TokenType::IDENTIFIER)) { auto tokenType = parseIdentifier(); if(tokenType.value().value == "automatic") { type = EnumNode(BusNode::AUTOMATIC); } else if(tokenType.value().value == "regular") { type = EnumNode(BusNode::REGULAR); } else { type = PError(SourceError{current().span, "expected 'automatic' or 'regular'"}); } } else { type = PError(SourceError{current().span, "expected 'automatic' or 'regular'"}); } return type; } /**************************************************************************** * * WireNode := NAME(){0,1} TYPE [hidden | terminated_with (#number | null)])* * ****************************************************************************/ PResult ComdelParser::parseWire() { auto spanner = getSpanner(); WireNode wire; ASSIGN_OR_RETURN_IF_ERR(wire.name, parseIdentifier()); if(check(TokenType::LT)) { RETURN_IF_NOT_TOKEN(TokenType::LT); ASSIGN_OR_RETURN_IF_ERR(wire.size, parseNumber()); RETURN_IF_NOT_TOKEN(TokenType::GT); } else { wire.size.value = 1; } // default wire.type = EnumNode(WireNode::WIRE); if(check(TokenType::WIRE_DEFAULT)) { bump(); wire.type = EnumNode(WireNode::WIRE); } else if(check(TokenType::WIRE_AND)) { bump(); wire.type = EnumNode(WireNode::WIRED_AND); } else if(check(TokenType::WIRE_OR)) { bump(); wire.type = EnumNode(WireNode::WIRED_OR); } else if(check(TokenType::R_WIRE)) { bump(); wire.type = EnumNode(WireNode::R_WIRE); } while(true) { if(check(TokenType::HIDDEN)) { bump(); wire.hidden = true; } else if(check(TokenType::TERMINATE_WITH)) { bump(); wire.hasTerminateWith = true; if(check(TokenType::NIL)) { bump(); wire.terminateWith = ValueNode::ofNull(); } else if(check(TokenType::NUMBER)) { auto number = parseNumber(); wire.terminateWith = ValueNode::ofInt(number->value); } else { auto token = current(); return PError(SourceError{token.span, "unexpected token"}); } } else { break; } } return spanner(wire); } /**************************************************************************** * * PinNode := "@pin" NAME TYPE "{" "@tooltip" MESSAGE "@connection" TYPE "(" MESSAGE ")" DisplayNode } * ****************************************************************************/ PResult ComdelParser::parsePin() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_PIN); PinNode pin{}; ASSIGN_OR_RETURN_IF_ERR(pin.name, parseIdentifier()); if(check(TokenType::PIN_IN)) { bump(); pin.type = EnumNode(PinNode::IN); } else if(check(TokenType::PIN_OUT)) { bump(); pin.type = EnumNode(PinNode::OUT); } else if(check(TokenType::PIN_IN_OUT)) { bump(); pin.type = EnumNode(PinNode::IN_OUT); } else { return unexpected(); } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if (check(TokenType::KW_TOOLTIP)) { bump(); ASSIGN_OR_SET_ERR(pin.tooltip, parseString()); } else if (check(TokenType::KW_DISPLAY)) { ASSIGN_OR_SET_ERR(pin.display, parseDisplay()); } else if (check(TokenType::KW_CONNECTION)) { ASSIGN_OR_SET_ERR(pin.connection, parsePinConnection()); } else if (check(TokenType::KW_WIRES)){ bump(); auto wires = parseList(std::optional(TokenType::LBRACE), TokenType::RBRACE, std::optional(TokenType::COMMA), false, [this] { return parseConnectionWire(); }); RETURN_IF_ERR(wires); pin.wires = *wires; } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(pin); } /**************************************************************************** * * PinConnectionNode := "@connection " + ("check_only" | "automatically") + "(" + MESSAGE + ")" * ****************************************************************************/ PResult ComdelParser::parsePinConnection() { auto spanner = getSpanner(); PinConnectionNode connection{}; RETURN_IF_NOT_TOKEN(TokenType::KW_CONNECTION); ASSIGN_OR_RETURN_IF_ERR(connection.type, parseConnectionType()); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(connection.message, parseString()); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); return spanner(connection); } PResult> ComdelParser::parseConnectionType() { auto spanner = getSpanner(); EnumNode type; if(check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); if(identifier.value().value == "check_only") { type = EnumNode(PinConnectionNode::CHECK_ONLY); } else if(identifier.value().value == "automatically") { type = EnumNode(PinConnectionNode::AUTOMATICALLY); } else { return PError(SourceError{current().span, "expected identifiers 'check_only' or 'automatically'"}); } } else { return PError(SourceError{current().span, "expected identifiers 'check_only' or 'automatically'"}); } return spanner(type); } /**************************************************************************** * * AttributeNode := "@attribute " + NAME + TYPE ("default" + VALUE){0,1} ("{" POPUP "}"){0,1} * ****************************************************************************/ PResult ComdelParser::parseAttribute() { auto spanner = getSpanner(); AttributeNode attribute; RETURN_IF_NOT_TOKEN(TokenType::KW_ATTRIBUTE); if(check(TokenType::IDENTIFIER)) { ASSIGN_OR_RETURN_IF_ERR(attribute.name, parseIdentifier()); } else { return unexpected(); } if(check(TokenType::INT_TYPE)) { attribute.type = ValueNode::INT; } else if(check(TokenType::STRING_TYPE)) { attribute.type = ValueNode::STRING; } else if(check(TokenType::BOOL_TYPE)) { attribute.type = ValueNode::BOOL; } else if(check(TokenType::WIRE_TYPE)) { attribute.type = ValueNode::WIRE; } else { return unexpected(); } bump(); RETURN_IF_NOT_TOKEN(TokenType::DEFAULT); if(attribute.type == ValueNode::BOOL) { if(check(TokenType::TRUE)) { bump(); attribute.defaultValue = ValueNode::ofBool(true); } else if(check(TokenType::FALSE)) { bump(); attribute.defaultValue = ValueNode::ofBool(false); } else { return unexpected(); } } else if(attribute.type == ValueNode::INT) { if(check(TokenType::NUMBER)) { auto number = parseNumber(); attribute.defaultValue = ValueNode::ofInt(number->value); } else { return unexpected(); } } else if(attribute.type == ValueNode::STRING) { if(check(TokenType::STRING)) { auto string = parseString(); attribute.defaultValue = ValueNode::ofString(string->asString()); } else { return unexpected(); } } else if(attribute.type == ValueNode::IDENTIFIER) { if(check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); attribute.defaultValue = ValueNode::ofIdentifier(identifier->value); } else { return unexpected(); } } else if(attribute.type == ValueNode::WIRE) { if(check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); attribute.defaultValue = ValueNode::ofWire(identifier->value); } else if(check(TokenType::NIL)) { bump(); attribute.defaultValue = ValueNode::ofNull(); } else { return unexpected(); } } if(check(TokenType::LBRACE)) { RETURN_IF_NOT_TOKEN(TokenType::LBRACE); PopupNode popup; if(!check(TokenType::KW_POPUP)) { return unexpected(); } ASSIGN_OR_RETURN_IF_ERR(popup, parsePopup()); attribute.popup = std::optional(popup); RETURN_IF_NOT_TOKEN(TokenType::RBRACE); } return spanner(attribute); } /**************************************************************************** * * Enumeration * ****************************************************************************/ PResult ComdelParser::parseEnumeration() { auto spanner = getSpanner(); StringNode key; ASSIGN_OR_RETURN_IF_ERR(key, parseString()); RETURN_IF_NOT_TOKEN(TokenType::EQUALS); ValueNode value; ASSIGN_OR_RETURN_IF_ERR(value, parseValue()); EnumerationNode node; node.key = key; node.value = value; return spanner(node); } /**************************************************************************** * * PopupNode := "@popup " + ("automatic" | "on_demand") { POPUP BODY } * ****************************************************************************/ PResult ComdelParser::parsePopup() { auto spanner = getSpanner(); PopupNode popup; RETURN_IF_NOT_TOKEN(TokenType::KW_POPUP); if(check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); if(identifier.value().value == "automatic") { popup.type = EnumNode(PopupNode::AUTOMATIC); } else if(identifier.value().value == "on_demand") { popup.type = EnumNode(PopupNode::ON_DEMAND); } else { return PError(SourceError{current().span, "expected type 'automatic', 'on_demand'"}); } } else { popup.type = EnumNode(PopupNode::ON_DEMAND); } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if(check(TokenType::KW_TITLE)) { bump(); ASSIGN_OR_SET_ERR(popup.title, parseString()); } else if(check(TokenType::KW_TEXT)) { bump(); ASSIGN_OR_SET_ERR(popup.text, parseString()); } else if(check(TokenType::KW_RULE)) { APPEND_OR_SET_ERR(popup.rules, parseRule()); } else if(check(TokenType::KW_ENUMERATED)) { bump(); popup.enumerated = true; ASSIGN_OR_SET_ERR(popup.enumeration, parseList( std::optional(TokenType::LBRACE), TokenType::RBRACE, std::optional(TokenType::COMMA), true, [this]{ return parseEnumeration();} ) ); } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(popup); } /**************************************************************************** * * ConnectionNode := "@connection (" + COMPONENT + "." + PIN + "," + BUS) {" + CONNECTION + "}" * ****************************************************************************/ PResult ComdelParser::parseConnection() { auto spanner = getSpanner(); ConnectionNode connection; RETURN_IF_NOT_TOKEN(TokenType::KW_CONNECTION); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(connection.first.component, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(connection.first.pin, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(connection.bus, parseIdentifier()); if(check(TokenType::COMMA)) { auto conn = ConnectionComponentNode{}; RETURN_IF_NOT_TOKEN(TokenType::COMMA); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(conn.component, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(conn.pin, parseIdentifier()); connection.second = conn; } RETURN_IF_NOT_TOKEN(TokenType::RPAREN); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); int wireCount = 0; while(!check(TokenType::RBRACE)) { PResult> err; if (check(TokenType::KW_ATTRIBUTE)) { APPEND_OR_RETURN_IF_ERR(connection.attributes, parseAttribute()); } else if(check(TokenType::KW_WIRES)) { bump(); auto wires = parseList(TokenType::LBRACE, TokenType::RBRACE, TokenType::COMMA, false, [this] { return parseConnectionWire(); }); RETURN_IF_ERR(wires); if(wireCount == 0) { connection.firstWires = *wires; } else if(wireCount == 1 && connection.second.has_value()) { connection.secondWires = *wires; } else { return unexpected(); } wireCount++; } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(connection); } /**************************************************************************** * * RuleNode := "@rule {" + if-else statements + "}" * ****************************************************************************/ PResult ComdelParser::parseRule() { auto spanner = getSpanner(); RuleNode rule; RETURN_IF_NOT_TOKEN(TokenType::KW_RULE); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { APPEND_OR_RETURN_IF_ERR(rule.statements, parseIfStatement()); if(check(TokenType::RBRACE)) { break; } RETURN_IF_NOT_TOKEN(TokenType::ELSE); } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(rule); } /**************************************************************************** * * IfStatement := "if(!function(params...)) { error(MESSAGE) | warning(MESSAGE) } * ****************************************************************************/ PResult ComdelParser::parseIfStatement() { auto spanner = getSpanner(); IfStatementNode ifStatement; RETURN_IF_NOT_TOKEN(TokenType::IF); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); if(check(TokenType::NOT)) { ifStatement.condition.negated = true; bump(); } else { ifStatement.condition.negated = false; } ASSIGN_OR_RETURN_IF_ERR(ifStatement.condition.functionName, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(ifStatement.condition.params, parseList(std::optional(TokenType::LPAREN), TokenType::RPAREN, TokenType::COMMA, false, [this]{return parseValue();} )); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); if(check(TokenType::ERROR)) { ifStatement.action.type = EnumNode(ActionNode::ERROR); } else if(check(TokenType::WARNING)) { ifStatement.action.type = EnumNode(ActionNode::WARNING); } else { return unexpected(); } bump(); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(ifStatement.action.message, parseString()); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(ifStatement); } PResult ComdelParser::parseValue() { auto spanner = getSpanner(); ValueNode value; if(check(TokenType::IDENTIFIER)) { value = ValueNode::ofIdentifier(parseIdentifier()->value); } else if(check(TokenType::STRING)) { value = ValueNode::ofString(parseString()->asString()); } else if(check(TokenType::NUMBER)) { value = ValueNode::ofInt(parseNumber()->value); } else if(check(TokenType::TRUE)) { bump(); value = ValueNode::ofBool(true); } else if(check(TokenType::FALSE)) { bump(); value = ValueNode::ofBool(false); } else if(check(TokenType::NIL)) { bump(); value = ValueNode::ofNull(); } else { return unexpected(); } return spanner(value); } std::optional ComdelParser::parseSchema() { auto spanner = getSpanner(); SchemaNode schema{}; if(check(TokenType::KW_SOURCE)) { bump(); if(check(TokenType::STRING)) { auto source = parseString(); schema.source = *source; } else { errors.emplace_back(current().span, "expected `@source`"); return std::nullopt; } } else { errors.emplace_back(current().span, "expected `@source`"); return std::nullopt; } if(!check(TokenType::KW_SCHEMA)) { errors.emplace_back(current().span, "expected `@schema`"); return std::nullopt; } bump(); if(!check(TokenType::LBRACE)) { errors.emplace_back(current().span, "expected `{`"); return std::nullopt; } bump(); while ( ! check(TokenType::RBRACE) ) { PResult> err; if(check(TokenType::KW_INSTANCE)){ APPEND_OR_SET_ERR(schema.instances, parseInstance()); } else if(check(TokenType::KW_CONNECTION)) { APPEND_OR_SET_ERR(schema.connections, parseConnectionInstance()); } else { err = unexpected(); bump(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { errors.emplace_back(current().span, "expected `}` reached EOF"); return std::nullopt; } } } if(!check(TokenType::RBRACE)) { errors.emplace_back(current().span, "expected `}`"); return std::nullopt; } else { bump(); } if(!check(TokenType::END_OF_FILE)) { errors.emplace_back(current().span, "expected `EOF`"); } if (errors.size()) return std::nullopt; return spanner(schema); } /**************************************************************************** * * CountNode := "@position (" + NumberNode + "," + NumberNode + ")" * ****************************************************************************/ PResult ComdelParser::parsePosition() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_POSITION); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); auto first = parseNumber(); if(!first.has_value()) { return PError(first.error()); } RETURN_IF_NOT_TOKEN(TokenType::COMMA); auto second = parseNumber(); if(!second.has_value()) { return PError(second.error()); } RETURN_IF_NOT_TOKEN(TokenType::RPAREN); return spanner(CountNode{first.value(), second.value()}); } PResult ComdelParser::parseInstance() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_INSTANCE); InstanceNode instance; ASSIGN_OR_RETURN_IF_ERR(instance.name, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(instance.component, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if(check(TokenType::KW_POSITION)) { ASSIGN_OR_SET_ERR(instance.position, parsePosition()); } else if(check(TokenType::KW_ATTRIBUTE)) { APPEND_OR_SET_ERR(instance.attributes, parseInstanceAttribute()); } else if(check(TokenType::KW_SIZE)) { bump(); auto number = parseNumber(); RETURN_IF_ERR(number); instance.size = *number; } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(instance); } PResult ComdelParser::parseInstanceAttribute() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_ATTRIBUTE); InstanceAttributeNode attribute; ASSIGN_OR_RETURN_IF_ERR(attribute.name, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(attribute.value, parseValue()); return spanner(attribute); } PResult ComdelParser::parseConnectionInstance() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_CONNECTION); ConnectionInstanceNode connection; RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(connection.first.instance, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(connection.first.pin, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(connection.bus, parseIdentifier()); if(check(TokenType::COMMA)) { bump(); ConnectionComponentInstance second; ASSIGN_OR_RETURN_IF_ERR(second.instance, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(second.pin, parseIdentifier()); connection.second = second; } RETURN_IF_NOT_TOKEN(TokenType::RPAREN); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if(check(TokenType::KW_ATTRIBUTE)) { APPEND_OR_SET_ERR(connection.attributes, parseInstanceAttribute()); } else { err = unexpected(); bump(); } if(!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); if(check(TokenType::END_OF_FILE)) { return PError({Span(spanner.lo), "Reached EOF"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(connection); } PResult ComdelParser::parseConnectionWire() { if(check(TokenType::NUMBER)) { return ValueNode::ofInt(parseNumber()->value); } else if(check(TokenType::NIL)) { bump(); return ValueNode::ofNull(); } else if(check(TokenType::IDENTIFIER)) { return ValueNode::ofIdentifier(parseIdentifier()->value); } else { return unexpected(); } }