schema_editor/comdel/parser/comdel_parser.cpp

1369 lines
45 KiB
C++

#include "comdel_parser.h"
#include "poly.h"
#include "tokens_type.h"
#include <utility>
#include <algorithm>
#include <fstream>
#include <sstream>
ComdelParser::ComdelParser(std::vector<Token> 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 << ": ";
}
unsigned int 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<typename T>
PResult<std::vector<T>>
ComdelParser::parseList(std::optional<TokenType> openDelim,
TokenType closeDelim,
std::optional<TokenType> separator,
bool allowTrailing,
const std::function<PResult<T>()> &parse_f) {
std::vector<T> 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<SourceError> &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<LibraryNode> ComdelParser::parseLibrary() {
auto spanner = getSpanner();
LibraryNode library{};
while (!check(TokenType::END_OF_FILE)) {
PResult<poly<AstNode>> 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<PropertyNode>(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<StringNode> 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<ColorNode> 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<IdentifierNode> 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<NumberNode> 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<NumberPairNode> 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<ValueNode> 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<PropertyNode> ComdelParser::parseProperty(std::optional<TokenType> 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<StringPropertyNode> 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<AddressSpaceNode> 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
* ["@displayName" + StringNode]{0,1}
* "@tooltip" + StringNode
* "@source" + StringNode
* "@tooltip" + StringNode
* "@count" + NumberPairNode
* DisplayNode
* [PinNode]{0..N}
* [AttributeNode]{0..N}
* [RuleNode]{0..N}
* "}"
*
****************************************************************************/
PResult<ComponentNode> 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<poly<AstNode>> err;
if (consume(TokenType::KW_INSTANCE_NAME)) {
ASSIGN_OR_SET_ERR(component.instanceName, parseIdentifier());
} else if (consume(TokenType::KW_DISPLAY_NAME)) {
ASSIGN_OR_SET_ERR(component.displayName, parseString());
} 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<EnumNode<ComponentNode::ComponentType>> ComdelParser::parseComponentType() {
EnumNode<ComponentNode::ComponentType> 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<DisplayNode> 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<DisplayItemNode> 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<DisplayItemNode> 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<PropertyNode> 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
* ["@displayName" + StringNode]{0,1}
* DisplayNode
* "@wires {" + [WireNode + ","]{0..N} + WireNode "}"
* "}"
*
****************************************************************************/
PResult<BusNode> 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<poly<AstNode>> err;
if (consume(TokenType::KW_TOOLTIP)) {
ASSIGN_OR_SET_ERR(bus.tooltip, parseString());
} else if (consume(TokenType::KW_DISPLAY_NAME)) {
ASSIGN_OR_SET_ERR(bus.displayName, 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<EnumNode<BusNode::BusType>> ComdelParser::parseBusType() {
PResult<EnumNode<BusNode::BusType>> 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<WireNode> 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<PinNode> 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<poly<AstNode>> 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<ValueNode>(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]
* ["@displayName" + StringNode]{0,1}
* "}"){0,1}
*
****************************************************************************/
PResult<AttributeNode> 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 (consume(TokenType::LBRACE)) {
while(!consume(TokenType::RBRACE)) {
PResult<poly<AstNode>> err;
if (check(TokenType::KW_POPUP)) {
PopupNode popup;
ASSIGN_OR_SET_ERR(popup, parsePopup());
attribute.popup = std::optional<PopupNode>(popup);
} else if (consume(TokenType::KW_DISPLAY_NAME)) {
ASSIGN_OR_SET_ERR(attribute.displayName, parseString());
} 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 spanner(attribute);
}
/****************************************************************************
*
* PopupNode := "@popup " + ("automatic" | "on_demand") + "{"
* "@title" + StringNode
* "@text" + StringNode
* [RuleNode]{0..N}
* "@enumerated {" + [StringPropertyNode + ","]{0..N} + StringPropertyNode + "}"
* "}"
*
****************************************************************************/
PResult<PopupNode> 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<poly<AstNode>> 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<StringPropertyNode>(
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<ConnectionComponentNode> 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<ConnectionNode> 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<poly<AstNode>> err;
if (check(TokenType::KW_ATTRIBUTE)) {
APPEND_OR_RETURN_IF_ERR(connection.attributes, parseAttribute());
} else if (consume(TokenType::KW_WIRES)) {
auto wires = parseList<ValueNode>(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<RuleNode> 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<IfStatementNode> 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<ValueNode>(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<SchemaNode> 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<poly<AstNode>> 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<InstanceNode> 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<poly<AstNode>> 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<InstanceAttributeNode> 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<ConnectionComponentInstanceNode> 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<ConnectionInstanceNode> 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<poly<AstNode>> 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<ValueNode> 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();
}
}