#include "comdelparser.h" #include "assert.h" #include "poly.h" #include "tokenstype.h" #include #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 ends with // 'separator' and otherwise the list should ends 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 std::move(vec); } 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(); Library library{}; while ( ! check(TokenType::END_OF_FILE) ) { PResult> err; if (check(TokenType::KEYWORD)) { auto keyword = KeywordType::determineType(current().text); bump(); switch (keyword) { case KeywordType::NAME: ASSIGN_OR_SET_ERR(library.name, parseString()); break; case KeywordType::HEADER: ASSIGN_OR_SET_ERR(library.header, parseString()); break; case KeywordType::DIRECTORY: ASSIGN_OR_SET_ERR(library.componentDirectory, parseString()); break; case KeywordType::INFO: ASSIGN_OR_SET_ERR(library.libraryInfo, parseString()); break; case KeywordType::ADDRESS: APPEND_OR_SET_ERR(library.addressSpaces, parseAddress()); break; case KeywordType::COMPONENT: APPEND_OR_SET_ERR(library.components, parseComponent()); break; case KeywordType::BUS: APPEND_OR_SET_ERR(library.buses, parseBus()); break; case KeywordType::CONNECTION: APPEND_OR_SET_ERR(library.connections, parseConnection()); break; case KeywordType::MESSAGES: ASSIGN_OR_SET_ERR(library.messages, parseList(std::optional(TokenType::LBRACE), TokenType::RBRACE, std::nullopt, false, [this]{return parseProperty(std::optional(TokenType::STRING));} )); break; default: err = unexpected(); } } else { err = unexpected(); } if(!err.has_value()) { errors.push_back(err.error()); break; } } 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(); } /**************************************************************************** * * Used for skipping block while testing * ****************************************************************************/ void ComdelParser::parseBlock() { int depth = 0; if(consume(TokenType::LBRACE)) { depth++; } while(depth != 0) { if(consume(TokenType::LBRACE)) { depth++; } else if(consume(TokenType::RBRACE)) { depth--; } else { bump(); } } } /**************************************************************************** * * CountNode := "@size (" + NumberNode + "," + NumberNode + ")" * ****************************************************************************/ PResult ComdelParser::parseCount() { 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(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); Value value; if(valueType.has_value()) { if(!check(*valueType)) { return unexpected(); } } if(check(TokenType::NUMBER)) { value = Value::ofInt(parseNumber().value().value); } else if(check(TokenType::STRING)) { value = Value::ofString(parseString().value().value); } else if(check(TokenType::IDENTIFIER)) { value = Value::ofIdentifier(parseIdentifier().value().value); } else { return unexpected(); } RETURN_IF_NOT_TOKEN(TokenType::SEMICOLON); PropertyNode node; node.key = key; node.value = value; return spanner(node); } /**************************************************************************** * * AddressSpace := "@address" + IDENTIFIER "(" + NUMER + "," + NUMBER + ")" * ****************************************************************************/ PResult ComdelParser::parseAddress() { auto spanner = getSpanner(); AddressSpace addressSpace{}; 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); } /**************************************************************************** * * Component := "@component" + IDENTIFIER + ("processor" | "memory") { COMPONENT_BLOCK } * ****************************************************************************/ PResult ComdelParser::parseComponent() { auto spanner = getSpanner(); Component component{}; ASSIGN_OR_RETURN_IF_ERR(component.name, parseIdentifier()); if(check(TokenType::IDENTIFIER)) { auto tokenType = parseIdentifier(); if(tokenType.value().value == "processor") { component.type = Component::PROCESSOR; } else if(tokenType.value().value == "memory") { component.type = Component::MEMORY; } else { return PError(SourceError{current().span, "expected 'processor' or 'memory'"}); } } else { component.type = Component::OTHER; } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if (check(TokenType::KEYWORD)) { auto keyword = KeywordType::determineType(current().text); bump(); switch (keyword) { case KeywordType::INSTANCE_NAME: ASSIGN_OR_RETURN_IF_ERR(component.instanceName, parseString()); break; case KeywordType::TOOLTIP: ASSIGN_OR_RETURN_IF_ERR(component.tooltip, parseString()); break; case KeywordType::SOURCE: ASSIGN_OR_RETURN_IF_ERR(component.source, parseString()); break; case KeywordType::COUNT: ASSIGN_OR_RETURN_IF_ERR(component.count, parseCount()); break; case KeywordType::DISPLAY: ASSIGN_OR_RETURN_IF_ERR(component.display, parseDisplay()); break; case KeywordType::PIN: APPEND_OR_RETURN_IF_ERR(component.pin, parsePin()); break; case KeywordType::ATTRIBUTE: APPEND_OR_RETURN_IF_ERR(component.attributes, parseAttribute()); break; case KeywordType::RULE: APPEND_OR_RETURN_IF_ERR(component.rules, parseRule()); break; default: return unexpected(); } } else { return unexpected(); } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(component); } /**************************************************************************** * * Display := "@display {" + (DISPLAY_ITEM)* + "}" * ****************************************************************************/ PResult ComdelParser::parseDisplay() { auto spanner = getSpanner(); Display 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); } /**************************************************************************** * * DisplayItem := "TYPE {(KEY + ":" + VALUE + ";")*} * ****************************************************************************/ PResult ComdelParser::parseDisplayItem() { auto spanner = getSpanner(); DisplayItem 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); } /**************************************************************************** * * Bus := "@bus " + NAME + TYPE + "{" + POPUP + "}" * ****************************************************************************/ PResult ComdelParser::parseBus() { auto spanner = getSpanner(); Bus bus; ASSIGN_OR_RETURN_IF_ERR(bus.name, parseIdentifier()); if(check(TokenType::IDENTIFIER)) { auto tokenType = parseIdentifier(); if(tokenType.value().value == "automatic") { bus.type = Bus::AUTOMATIC; } else if(tokenType.value().value == "regular") { bus.type = Bus::REGULAR; } else { return PError(SourceError{current().span, "expected 'automatic' or 'regular'"}); } } else { return PError(SourceError{current().span, "expected 'automatic' or 'regular'"}); } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { if (check(TokenType::KEYWORD)) { auto keyword = KeywordType::determineType(current().text); bump(); switch (keyword) { case KeywordType::TOOLTIP: ASSIGN_OR_RETURN_IF_ERR(bus.tooltip, parseString()); break; case KeywordType::COUNT: ASSIGN_OR_RETURN_IF_ERR(bus.count, parseCount()); break; case KeywordType::DISPLAY: ASSIGN_OR_RETURN_IF_ERR(bus.display, parseDisplay()); break; case KeywordType::WIRES: 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); break; default: return unexpected(); } } else { return unexpected(); } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(bus); } /**************************************************************************** * * Wire := NAME(){0,1} TYPE)* * ****************************************************************************/ PResult ComdelParser::parseWire() { auto spanner = getSpanner(); Wire 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; } if(check(TokenType::IDENTIFIER)) { IdentifierNode identifier; ASSIGN_OR_RETURN_IF_ERR(identifier, parseIdentifier()); if(identifier.value == "wire") { wire.type = Wire::WIRE; } else if(identifier.value == "wired_and") { wire.type = Wire::WIRED_AND; } else if(identifier.value == "wired_or") { wire.type = Wire::WIRED_OR; }else if(identifier.value == "r_wire") { wire.type = Wire::R_WIRE; } else { return PError(SourceError{current().span, "expected 'wire', 'wired_and', 'wired_or' or 'r_wire'"}); } } else { wire.type = Wire::WIRE; } return spanner(wire); } /**************************************************************************** * * Pin := "@pin" NAME TYPE "{" "@tooltip" MESSAGE "@connection" TYPE "(" MESSAGE ")" Display } * ****************************************************************************/ PResult ComdelParser::parsePin() { auto spanner = getSpanner(); Pin pin{}; ASSIGN_OR_RETURN_IF_ERR(pin.name, parseIdentifier()); if(check(TokenType::IDENTIFIER)) { auto type = parseIdentifier(); if(type.value().value == "InOut") { pin.type = Pin::IN_OUT; } else if(type.value().value == "In") { pin.type = Pin::IN; } else if(type.value().value == "Out") { pin.type = Pin::OUT; } else { return PError(SourceError{type.value().span, "expected 'InOut', 'In', or 'Out'"}); } } else { pin.type = Pin::IN_OUT; } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { if (check(TokenType::KEYWORD)) { auto keyword = KeywordType::determineType(current().text); bump(); switch (keyword) { case KeywordType::TOOLTIP: ASSIGN_OR_RETURN_IF_ERR(pin.tooltip, parseString()); break; case KeywordType::DISPLAY: ASSIGN_OR_RETURN_IF_ERR(pin.display, parseDisplay()); break; case KeywordType::CONNECTION: ASSIGN_OR_RETURN_IF_ERR(pin.connection, parsePinConnection()); break; default: return unexpected(); } } else { return unexpected(); } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(pin); } /**************************************************************************** * * PinConnection := "@connection " + ("check_only" | "automatically") + "(" + MESSAGE + ")" * ****************************************************************************/ PResult ComdelParser::parsePinConnection() { auto spanner = getSpanner(); PinConnection connection{}; if(check(TokenType::IDENTIFIER)) { auto type = parseIdentifier(); if(type.value().value == "check_only") { connection.type = PinConnection::CHECK_ONLY; } else if(type.value().value == "In") { connection.type = PinConnection::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_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(connection.message, parseString()); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); return spanner(connection); } /**************************************************************************** * * Attribute := "@attribute " + NAME + TYPE ("default" + VALUE){0,1} ("{" POPUP "}"){0,1} * ****************************************************************************/ PResult ComdelParser::parseAttribute() { auto spanner = getSpanner(); Attribute attribute; if(check(TokenType::IDENTIFIER)) { ASSIGN_OR_RETURN_IF_ERR(attribute.name, parseIdentifier()); } else { return unexpected(); } if(check(TokenType::IDENTIFIER)) { auto type = parseIdentifier(); if(type.value().value == "int") { attribute.type = Value::INT; } else if(type.value().value == "string") { attribute.type = Value::STRING; } else if(type.value().value == "bool") { attribute.type = Value::BOOL; } else if(type.value().value == "wire") { attribute.type = Value::IDENTIFIER; } else { return PError(SourceError{current().span, "expected type 'int', 'bool', 'string', 'wire'"}); } } else { return PError(SourceError{current().span, "expected type 'int', 'bool', 'string', 'wire'"}); } if(!(check(TokenType::IDENTIFIER) && current().text == "default")) { return PError(SourceError{current().span, "expected 'default'"}); } bump(); if(attribute.type == Value::BOOL) { IdentifierNode identifier; ASSIGN_OR_RETURN_IF_ERR(identifier, parseIdentifier()); if(identifier.value == "true") { attribute.defaultValue = Value::ofBool(true); } else if(identifier.value == "false") { attribute.defaultValue = Value::ofBool(false); } else { return PError(SourceError{current().span, "expected value 'true' or 'false'"}); } } else if(attribute.type == Value::INT) { if(check(TokenType::NUMBER)) { auto number = parseNumber(); attribute.defaultValue = Value::ofInt(number.value().value); } else { return PError(SourceError{current().span, "expected number"}); } } else if(attribute.type == Value::STRING) { if(check(TokenType::STRING)) { auto string = parseString(); attribute.defaultValue = Value::ofString(string.value().value); } else { return PError(SourceError{current().span, "expected string"}); } } else if(attribute.type == Value::IDENTIFIER) { if(check(TokenType::IDENTIFIER)) { auto identifier = parseIdentifier(); attribute.defaultValue = Value::ofIdentifier(identifier.value().value); } else { return PError(SourceError{current().span, "expected wire"}); } } if(check(TokenType::LBRACE)) { RETURN_IF_NOT_TOKEN(TokenType::LBRACE); Popup popup; if(!(check(TokenType::KEYWORD) && current().text == "@popup")) { return PError(SourceError{current().span, "expected '@popup'"}); } bump(); 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); Value value; if(check(TokenType::NUMBER)) { value = Value::ofInt(parseNumber().value().value); } else if(check(TokenType::STRING)) { value = Value::ofString(parseString().value().value); } else if(check(TokenType::IDENTIFIER)) { value = Value::ofIdentifier(parseIdentifier().value().value); } else { return unexpected(); } EnumerationNode node; node.key = key; node.value = value; return spanner(node); } /**************************************************************************** * * Popup := "@popup " + ("automatic" | "on_demand") { POPUP BODY } * ****************************************************************************/ PResult ComdelParser::parsePopup() { auto spanner = getSpanner(); Popup popup; if(check(TokenType::IDENTIFIER)) { auto type = parseIdentifier(); if(type.value().value == "automatic") { popup.type = Popup::AUTOMATIC; } else if(type.value().value == "on_demand") { popup.type = Popup::ON_DEMAND; } else { return PError(SourceError{current().span, "expected type 'automatic', 'on_demand'"}); } } else { popup.type = Popup::ON_DEMAND; } RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { PResult> err; if (check(TokenType::KEYWORD)) { auto keyword = KeywordType::determineType(current().text); bump(); switch (keyword) { case KeywordType::TITLE: ASSIGN_OR_RETURN_IF_ERR(popup.title, parseString()); break; case KeywordType::TEXT: ASSIGN_OR_RETURN_IF_ERR(popup.text, parseString()); break; case KeywordType::RULE: APPEND_OR_RETURN_IF_ERR(popup.rules, parseRule()); break; case KeywordType::ENUMERATED: popup.enumerated = true; ASSIGN_OR_RETURN_IF_ERR(popup.enumeration, parseList( std::optional(TokenType::LBRACE), TokenType::RBRACE, std::optional(TokenType::COMMA), true, [this]{ return parseEnumeration();} ) ); break; default: return unexpected(); } } else { return unexpected(); } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(popup); } /**************************************************************************** * * Connection := "@connection (" + COMPONENT + "." + PIN + "," + BUS) {" + CONNECTION + "}" * ****************************************************************************/ PResult ComdelParser::parseConnection() { auto spanner = getSpanner(); Connection connection; RETURN_IF_NOT_TOKEN(TokenType::LPAREN); ASSIGN_OR_RETURN_IF_ERR(connection.component, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::DOT); ASSIGN_OR_RETURN_IF_ERR(connection.pin, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::COMMA); ASSIGN_OR_RETURN_IF_ERR(connection.bus, parseIdentifier()); RETURN_IF_NOT_TOKEN(TokenType::RPAREN); RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { if (check(TokenType::KEYWORD)) { auto keyword = KeywordType::determineType(current().text); bump(); switch (keyword) { case KeywordType::ATTRIBUTE: APPEND_OR_RETURN_IF_ERR(connection.attributes, parseAttribute()); break; case KeywordType::WIRES: { auto wires = parseList(std::optional(TokenType::LBRACE), TokenType::RBRACE, std::optional(TokenType::COMMA), false, [this] { return parseIdentifier(); }); RETURN_IF_ERR(wires); connection.wires = *wires; } break; default: return unexpected(); } } else { return unexpected(); } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(connection); } /**************************************************************************** * * Rule := "@rule {" + if-else statements + "}" * ****************************************************************************/ PResult ComdelParser::parseRule() { auto spanner = getSpanner(); Rule rule; RETURN_IF_NOT_TOKEN(TokenType::LBRACE); while(!check(TokenType::RBRACE)) { APPEND_OR_RETURN_IF_ERR(rule.statements, parseIfStatement()); if(check(TokenType::IDENTIFIER)) { IdentifierNode node; ASSIGN_OR_RETURN_IF_ERR(node, parseIdentifier()); if(node.value != "else") { return PError(SourceError{node.span, "expected 'else'"}); } } } RETURN_IF_NOT_TOKEN(TokenType::RBRACE); return spanner(rule); } /**************************************************************************** * * IfStatement := "if(!function(params...)) { error(MESSAGE) | warning(MESSAGE) } * ****************************************************************************/ PResult ComdelParser::parseIfStatement() { auto spanner = getSpanner(); IfStmt ifStatement; IdentifierNode node; ASSIGN_OR_RETURN_IF_ERR(node, parseIdentifier()); if(node.value != "if") { return unexpected(); } 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::IDENTIFIER)) { IdentifierNode action; ASSIGN_OR_RETURN_IF_ERR(action, parseIdentifier()); if(action.value == "error") { ifStatement.action.type = Action::ERROR; } else if(action.value == "warning") { ifStatement.action.type = Action::WARNING; } else { return PError(SourceError{action.span, "expected 'error' of 'warning'"}); } } 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(); Value value; if(check(TokenType::IDENTIFIER)) { value = Value::ofIdentifier(parseIdentifier()->value); } else if(check(TokenType::STRING)) { value = Value::ofString(parseString()->value); } else if(check(TokenType::NUMBER)) { value = Value::ofInt(parseNumber()->value); } return spanner(value); }