#include "comdel_parser.h" #include "poly.h" #include "tokens_type.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) { ++position; } else { ATLAS_ASSERT_FAIL("Internal parser error, called bump after EOF"); } 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 {{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 parseLibrary 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 {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 * ****************************************************************************/ /**************************************************************************** * * ComponentNode := * "@name" + StringNode * "@header" + StringNode * "@componentHeader" + StringNode * "@directory" + StringNode * "@info" + StringNode * [AddressSpaceNode]{0..N} * [ComponentNode]{0..N} * [BusNode]{0..N} * [ConnectionNode]{0..N} * @messages "{" + PropertyNode{0..N} + "}" * ****************************************************************************/ std::optional ComdelParser::parseLibrary() { auto spanner = getSpanner(); LibraryNode library{}; while (!check(TokenType::END_OF_FILE)) { PResult> err; if (consume(TokenType::KW_NAME)) { ASSIGN_OR_SET_ERR(library.name, parseString()); } else if (consume(TokenType::KW_HEADER)) { ASSIGN_OR_SET_ERR(library.header, parseString()); } else if (consume(TokenType::KW_COMPONENT_HEADER)) { ASSIGN_OR_SET_ERR(library.componentHeader, parseString()); } else if (consume(TokenType::KW_DIRECTORY)) { ASSIGN_OR_SET_ERR(library.componentDirectory, parseString()); } else if (consume(TokenType::KW_INFO)) { 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 (consume(TokenType::KW_MESSAGES)) { ASSIGN_OR_SET_ERR(library.messages, parseList(TokenType::LBRACE, TokenType::RBRACE, std::nullopt, false, [this] { return parseProperty(TokenType::STRING); } )); if (!err.has_value()) { skipUntilNextKeyword(); bump(); } } else { err = unexpected(); bump(); } if (!err.has_value()) { errors.push_back(err.error()); skipUntilNextKeyword(); } } if (!errors.empty()) return std::nullopt; return spanner(library); } /**************************************************************************** * * StringNode := ('"' + TEXT + '"' | "'" + TEXT + "'") * ****************************************************************************/ PResult ComdelParser::parseString() { auto spanner = getSpanner(); if (check(TokenType::STRING)) { StringNode node; node.value = current().text; bump(); return spanner(node); } return unexpected(); } /**************************************************************************** * * ColorNode := '#' + [0-9A-Fa-f]{6,8} * ****************************************************************************/ PResult ComdelParser::parseColor() { auto spanner = getSpanner(); if (check(TokenType::COLOR)) { ColorNode node{current().text}; node.span = current().span; bump(); return spanner(node); } return unexpected(); } /**************************************************************************** * * IdentifierNode := [_a-zA-Z][_a-zA-Z0-9]* * ****************************************************************************/ PResult ComdelParser::parseIdentifier() { auto spanner = getSpanner(); if (check(TokenType::IDENTIFIER)) { IdentifierNode node; node.value = current().text; 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(); } /**************************************************************************** * * NumberPairNode := '(' + NumberNode + ',' + NumberNode + ')' * ****************************************************************************/ PResult ComdelParser::parseNumberPair() { auto spanner = getSpanner(); 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(NumberPairNode{*first, *second}); } /**************************************************************************** * * ValueNode := (IdentifierNode | NumberNode | ColorNode | "NULL" | "true" | "false") * ****************************************************************************/ 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 (consume(TokenType::TRUE)) { value = ValueNode::ofBool(true); } else if (consume(TokenType::FALSE)) { value = ValueNode::ofBool(false); } else if (consume(TokenType::NIL)) { value = ValueNode::ofNull(); } else if(check(TokenType::COLOR)) { value = ValueNode::ofColor(parseColor()->color); } else { return unexpected(); } return spanner(value); } /**************************************************************************** * * PropertyNode := IdentifierNode + ":" + ValueNode + ";" * ****************************************************************************/ 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 is defined value must be of given TokenType 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); return spanner(PropertyNode(key, value)); } /**************************************************************************** * * StringPropertyNode := StringNode + ":" + ValueNode + ";" * ****************************************************************************/ PResult ComdelParser::parseStringProperty() { 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()); return spanner(StringPropertyNode{key, value}); } /**************************************************************************** * * AddressSpaceNode := "@address" + IdentifierNode + NumberPairNode * ****************************************************************************/ PResult ComdelParser::parseAddress() { auto spanner = getSpanner(); AddressSpaceNode addressSpace{}; RETURN_IF_NOT_TOKEN(TokenType::KW_ADDRESS); ASSIGN_OR_RETURN_IF_ERR(addressSpace.name, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(addressSpace.range, parseNumberPair()); return spanner(addressSpace); } /**************************************************************************** * * ComponentNode := "@component" + IdentifierNode + ComponentType + "{" * "@instanceName" + IdentifierNode * "@tooltip" + StringNode * "@source" + StringNode * "@tooltip" + StringNode * "@count" + NumberPairNode * DisplayNode * [PinNode]{0..N} * [AttributeNode]{0..N} * [RuleNode]{0..N} * "}" * ****************************************************************************/ PResult ComdelParser::parseComponent() { auto spanner = getSpanner(); ComponentNode component{}; RETURN_IF_NOT_TOKEN(TokenType::KW_COMPONENT); ASSIGN_OR_RETURN_IF_ERR(component.name, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(component.type, parseComponentType()); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while (!check(TokenType::RBRACE)) { PResult> err; if (consume(TokenType::KW_INSTANCE_NAME)) { ASSIGN_OR_SET_ERR(component.instanceName, parseIdentifier()); } else if (consume(TokenType::KW_TOOLTIP)) { ASSIGN_OR_SET_ERR(component.tooltip, parseString()); } else if (consume(TokenType::KW_SOURCE)) { ASSIGN_OR_SET_ERR(component.source, parseString()); } else if (consume(TokenType::KW_COUNT)) { ASSIGN_OR_SET_ERR(component.count, parseNumberPair()); } 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(); 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(component); } /**************************************************************************** * * ComponentType := ["processor" | "memory"]{0,1} * ****************************************************************************/ PResult> ComdelParser::parseComponentType() { EnumNode type; auto spanner = getSpanner(); if (consume(TokenType::CT_PROCESSOR)) { type = EnumNode(ComponentNode::PROCESSOR); } else if (consume(TokenType::CT_MEMORY)) { type = EnumNode(ComponentNode::MEMORY); } else { type = EnumNode(ComponentNode::OTHER); } return spanner(type); } /**************************************************************************** * * DisplayNode := "@display {" + DisplayItemNode{0..N} + "}" * ****************************************************************************/ 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 := IdentifierNode + "{" + PropertyNode{0..N} + "}" * ****************************************************************************/ 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 " + IdentifierNode + BusType + "{" * "@tooltip" + StringNode * "@instanceName" + StringNode * DisplayNode * "@wires {" + [WireNode + ","]{0..N} + WireNode "}" * "}" * ****************************************************************************/ PResult ComdelParser::parseBus() { auto spanner = getSpanner(); BusNode bus; RETURN_IF_NOT_TOKEN(TokenType::KW_BUS); ASSIGN_OR_RETURN_IF_ERR(bus.name, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(bus.type, parseBusType()); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while (!check(TokenType::RBRACE)) { PResult> err; if (consume(TokenType::KW_TOOLTIP)) { ASSIGN_OR_SET_ERR(bus.tooltip, parseString()); } else if (consume(TokenType::KW_INSTANCE_NAME)) { ASSIGN_OR_SET_ERR(bus.instanceName, parseIdentifier()); } else if (consume(TokenType::KW_COUNT)) { ASSIGN_OR_SET_ERR(bus.count, parseNumberPair()); } else if (check(TokenType::KW_DISPLAY)) { ASSIGN_OR_SET_ERR(bus.display, parseDisplay()); } else if (consume(TokenType::KW_WIRES)) { if(consume(TokenType::LBRACE)) { while (check(TokenType::IDENTIFIER)) { APPEND_OR_RETURN_IF_ERR(bus.wires, parseWire()); if (check(TokenType::COMMA)) { RETURN_IF_NOT_TOKEN(TokenType::COMMA); } } } else { err = unexpected(); } if(!consume(TokenType::RBRACE)) { err = unexpected(); } } 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(bus); } /**************************************************************************** * * BusType := ["automatic" | "regular" | "singleAutomatic"]{0,1} * ****************************************************************************/ 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 if (tokenType.value().value == "singleAutomatic") { type = EnumNode(BusNode::SINGLE_AUTOMATIC); } else { type = PError(SourceError{current().span, "expected 'automatic', 'singleAutomatic' or 'regular'"}); } } else { type = PError(SourceError{current().span, "expected 'automatic', 'singleAutomatic' or 'regular'"}); } return type; } /**************************************************************************** * * WireNode := Identifier + ["<" + NumberNode + ">"]{0,1} + WireType{0,1} + "hidden"{0,1} + ["terminated_with" + (NumberNode | NULL)]{0,1} * ****************************************************************************/ 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 (consume(TokenType::WIRE_DEFAULT)) { wire.type = EnumNode(WireNode::WIRE); } else if (consume(TokenType::WIRE_AND)) { wire.type = EnumNode(WireNode::WIRED_AND); } else if (consume(TokenType::WIRE_OR)) { wire.type = EnumNode(WireNode::WIRED_OR); } else if (consume(TokenType::R_WIRE)) { wire.type = EnumNode(WireNode::R_WIRE); } while (true) { if (consume(TokenType::HIDDEN)) { wire.hidden = true; } else if (consume(TokenType::TERMINATE_WITH)) { wire.hasTerminateWith = true; if (consume(TokenType::NIL)) { 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" + IdentifierType + PinType + "{" * "@tooltip" + StringNode * "@connection" + StringNode * DisplayNode * "@wires {" + [ConnectionWireNode + ","]{0..N} + ConnectionWireNode + "}" * "}" * ****************************************************************************/ PResult ComdelParser::parsePin() { auto spanner = getSpanner(); RETURN_IF_NOT_TOKEN(TokenType::KW_PIN); PinNode pin{}; ASSIGN_OR_RETURN_IF_ERR(pin.name, parseIdentifier()); if (consume(TokenType::PIN_IN)) { pin.type = EnumNode(PinNode::IN); } else if (consume(TokenType::PIN_OUT)) { pin.type = EnumNode(PinNode::OUT); } else if (consume(TokenType::PIN_IN_OUT)) { pin.type = EnumNode(PinNode::IN_OUT); } else { return unexpected(); } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while (!check(TokenType::RBRACE)) { PResult> err; if (consume(TokenType::KW_TOOLTIP)) { ASSIGN_OR_SET_ERR(pin.tooltip, parseString()); } else if (check(TokenType::KW_DISPLAY)) { ASSIGN_OR_SET_ERR(pin.display, parseDisplay()); } else if (consume(TokenType::KW_CONNECTION)) { ASSIGN_OR_SET_ERR(pin.connection, parseString()); } else if (consume(TokenType::KW_WIRES)) { auto wires = parseList(TokenType::LBRACE, TokenType::RBRACE, TokenType::COMMA, false, [this] { return parseConnectionWire(); }); RETURN_IF_ERR(wires); pin.wires = *wires; } 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(pin); } /**************************************************************************** * * AttributeNode := "@attribute " + IdentifierNode + ValueType + ("default" + ValueNode){0,1} + ("{" PopupNode "}"){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 (consume(TokenType::INT_TYPE)) { attribute.type = ValueNode::INT; } else if (consume(TokenType::STRING_TYPE)) { attribute.type = ValueNode::STRING; } else if (consume(TokenType::BOOL_TYPE)) { attribute.type = ValueNode::BOOL; } else if (consume(TokenType::WIRE_TYPE)) { attribute.type = ValueNode::WIRE; } else { return unexpected(); } RETURN_IF_NOT_TOKEN(TokenType::DEFAULT); if (attribute.type == ValueNode::BOOL) { if (consume(TokenType::TRUE)) { attribute.defaultValue = ValueNode::ofBool(true); } else if (consume(TokenType::FALSE)) { 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::MEMORY) { if (check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); attribute.defaultValue = ValueNode::ofMemory(identifier->value); } else if (consume(TokenType::NIL)) { attribute.defaultValue = ValueNode::ofMemory(std::nullopt); } else { return unexpected(); } } else if (attribute.type == ValueNode::WIRE) { if (check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); attribute.defaultValue = ValueNode::ofWire(identifier->value); } else if (consume(TokenType::NIL)) { attribute.defaultValue = ValueNode::ofWire(std::nullopt); } else { return unexpected(); } } if (check(TokenType::LBRACE)) { RETURN_IF_NOT_TOKEN(TokenType::LBRACE); if (!check(TokenType::KW_POPUP)) { return unexpected(); } PopupNode popup; ASSIGN_OR_RETURN_IF_ERR(popup, parsePopup()); attribute.popup = std::optional(popup); RETURN_IF_NOT_TOKEN(TokenType::RBRACE); } return spanner(attribute); } /**************************************************************************** * * PopupNode := "@popup " + ("automatic" | "on_demand") + "{" * "@title" + StringNode * "@text" + StringNode * [RuleNode]{0..N} * "@enumerated {" + [StringPropertyNode + ","]{0..N} + StringPropertyNode + "}" * "}" * ****************************************************************************/ 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 (consume(TokenType::KW_TITLE)) { ASSIGN_OR_SET_ERR(popup.title, parseString()); } else if (consume(TokenType::KW_TEXT)) { ASSIGN_OR_SET_ERR(popup.text, parseString()); } else if (check(TokenType::KW_RULE)) { APPEND_OR_SET_ERR(popup.rules, parseRule()); } else if (consume(TokenType::KW_ENUMERATED)) { popup.enumerated = true; ASSIGN_OR_SET_ERR(popup.enumeration, parseList( TokenType::LBRACE, TokenType::RBRACE, TokenType::COMMA, true, [this] { return parseStringProperty(); } ) ); } 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(popup); } /**************************************************************************** * * ConnectionComponentNode := IdentifierNode + "." + IdentifierNode * ****************************************************************************/ PResult ComdelParser::parseConnectionComponent() { auto spanner = getSpanner(); ConnectionComponentNode node; ASSIGN_OR_RETURN_IF_ERR(node.component, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(node.pin, parseIdentifier()); return spanner(node); } /**************************************************************************** * * ConnectionNode := "@connection (" + ConnectionComponentNode + "," + IdentifierNode + ["," + ConnectionComponentNode]{0,1} + ") {" * [AttributeNode]{0..N} * ["@wires {" + [ConnectionWireNode + ","]{0..N} + ConnectionWireNode + "}"]{1,2} * "}" * ****************************************************************************/ 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, parseConnectionComponent()); RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(connection.bus, parseIdentifier()); if (check(TokenType::COMMA)) { RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(connection.second, parseConnectionComponent()); } RETURN_IF_NOT_TOKEN(TokenType::RPAREN); int wireCount = 0; RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while (!check(TokenType::RBRACE)) { PResult> err; if (check(TokenType::KW_ATTRIBUTE)) { APPEND_OR_RETURN_IF_ERR(connection.attributes, parseAttribute()); } else if (consume(TokenType::KW_WIRES)) { 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(); 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); } /**************************************************************************** * * RuleNode := "@rule {" * IfStatementNode * ["else" + IfStatementNode]{0..N} * "}" * ****************************************************************************/ 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(" + "!"{0,1} + IdentifierNode + "(" + [ValueNode + ","]{0..N} + ValueNode + ")) {" * ["error" | "warning"] + "(" + StringNode + ")" * "}" * ****************************************************************************/ PResult ComdelParser::parseIfStatement() { auto spanner = getSpanner(); IfStatementNode ifStatement; RETURN_IF_NOT_TOKEN(TokenType::IF); RETURN_IF_NOT_TOKEN(TokenType::LPAREN); if (consume(TokenType::NOT)) { ifStatement.condition.negated = true; } else { ifStatement.condition.negated = false; } ASSIGN_OR_RETURN_IF_ERR(ifStatement.condition.functionName, parseIdentifier()); ASSIGN_OR_RETURN_IF_ERR(ifStatement.condition.params, parseList(TokenType::LPAREN, TokenType::RPAREN, TokenType::COMMA, false, [this] { return parseValue(); } )); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); if (consume(TokenType::ERROR)) { ifStatement.action.type = EnumNode(ActionNode::ERROR); } else if (consume(TokenType::WARNING)) { ifStatement.action.type = EnumNode(ActionNode::WARNING); } else { return unexpected(); } 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); } /**************************************************************************** * * SchemaNode := * "@source" + StringNode * "@schema {" * [InstanceNode]{0..N} * [ConnectionInstanceNode]{0..N} * "}" * ****************************************************************************/ std::optional ComdelParser::parseSchema() { auto spanner = getSpanner(); SchemaNode schema{}; if (consume(TokenType::KW_SOURCE)) { 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 (!consume(TokenType::KW_SCHEMA)) { errors.emplace_back(current().span, "expected `@schema`"); return std::nullopt; } if (!consume(TokenType::LBRACE)) { errors.emplace_back(current().span, "expected `{`"); return std::nullopt; } 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 (!consume(TokenType::RBRACE)) { errors.emplace_back(current().span, "expected `}`"); return std::nullopt; } if (!check(TokenType::END_OF_FILE)) { errors.emplace_back(current().span, "expected `EOF`"); } if (!errors.empty()) return std::nullopt; return spanner(schema); } /**************************************************************************** * * InstanceNode := "@instance" + IdentifierNode + IdentifierNode "{" * "@position" + NumberPairNode * [InstanceAttributeNode]{0..N} * "@size" + NumberNode * "}" * ****************************************************************************/ 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 (consume(TokenType::KW_POSITION)) { ASSIGN_OR_SET_ERR(instance.position, parseNumberPair()); } else if (check(TokenType::KW_ATTRIBUTE)) { APPEND_OR_SET_ERR(instance.attributes, parseInstanceAttribute()); } else if (consume(TokenType::KW_SIZE)) { auto number = parseNumber(); RETURN_IF_ERR(number); instance.size = *number; } 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(instance); } /**************************************************************************** * * InstanceAttributeNode := "@attribute" + IdentifierNode + ValueNode * ****************************************************************************/ 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); } /**************************************************************************** * * ConnectionComponentInstanceNode := IdentifierNode + "." + IdentifierNode * ****************************************************************************/ PResult ComdelParser::parseConnectionComponentInstance() { auto spanner = getSpanner(); ConnectionComponentInstanceNode node; ASSIGN_OR_RETURN_IF_ERR(node.instance, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(node.pin, parseIdentifier()); return spanner(node); } /**************************************************************************** * * ConnectionInstanceNode := "@connection (" + ConnectionComponentInstanceNode + "," + IdentifierNode + ["," + ConnectionComponentInstanceNode ] + ") {" * [InstanceAttributeNode]{0..N} * "}" * ****************************************************************************/ 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, parseConnectionComponentInstance()); RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(connection.bus, parseIdentifier()); if (check(TokenType::COMMA)) { bump(); ConnectionComponentInstanceNode second; ASSIGN_OR_RETURN_IF_ERR(second, parseConnectionComponentInstance()); 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); } /**************************************************************************** * * ConnectionWireNode := [IdentifierNode | StringNode | "NULL" | NumberNode] * ****************************************************************************/ PResult ComdelParser::parseConnectionWire() { auto spanner = getSpanner(); if (check(TokenType::NUMBER)) { return spanner(ValueNode::ofInt(parseNumber()->value)); } else if (check(TokenType::NIL)) { bump(); return spanner(ValueNode::ofNull()); } else if (check(TokenType::STRING)) { return spanner(ValueNode::ofString(parseString()->asString())); } else if (check(TokenType::IDENTIFIER)) { return spanner(ValueNode::ofIdentifier(parseIdentifier()->value)); } else { return unexpected(); } }