Token refactor
This commit is contained in:
parent
48e1c48579
commit
4c921759ef
|
@ -62,6 +62,8 @@ LexerResult ComdelLexer::tokenize() {
|
|||
std::string text = source.substr(tokenBegin.offset,
|
||||
position.offset - tokenBegin.offset);
|
||||
|
||||
tokenType = from_token(text, tokenType.value());
|
||||
|
||||
tokens.push_back(Token(*tokenType, Span(tokenBegin, position), text));
|
||||
}
|
||||
|
||||
|
|
|
@ -215,45 +215,34 @@ std::optional<Library> ComdelParser::parse()
|
|||
|
||||
while ( ! check(TokenType::END_OF_FILE) ) {
|
||||
PResult<poly<AstNode>> err;
|
||||
if (check(TokenType::KEYWORD)) {
|
||||
auto keyword = KeywordType::determineType(current().text);
|
||||
if(check(TokenType::KW_NAME)){
|
||||
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<PropertyNode>(std::optional<TokenType>(TokenType::LBRACE),
|
||||
TokenType::RBRACE,
|
||||
std::nullopt,
|
||||
false,
|
||||
[this]{return parseProperty(std::optional<TokenType>(TokenType::STRING));}
|
||||
));
|
||||
break;
|
||||
default:
|
||||
err = unexpected();
|
||||
}
|
||||
ASSIGN_OR_SET_ERR(library.name, parseString());
|
||||
} else if(check(TokenType::KW_HEADER)) {
|
||||
bump();
|
||||
ASSIGN_OR_SET_ERR(library.header, parseString());
|
||||
} else if(check(TokenType::KW_DIRECTORY)) {
|
||||
bump();
|
||||
ASSIGN_OR_SET_ERR(library.componentDirectory, parseString());
|
||||
} else if(check(TokenType::KW_INFO)) {
|
||||
bump();
|
||||
ASSIGN_OR_SET_ERR(library.libraryInfo, parseString());
|
||||
} else if(check(TokenType::KW_ADDRESS)) {
|
||||
APPEND_OR_SET_ERR(library.addressSpaces, parseAddress());
|
||||
} else if(check(TokenType::KW_COMPONENT)) {
|
||||
APPEND_OR_SET_ERR(library.components, parseComponent());
|
||||
} else if(check(TokenType::KW_BUS)) {
|
||||
APPEND_OR_SET_ERR(library.buses, parseBus());
|
||||
} else if(check(TokenType::KW_CONNECTION)) {
|
||||
APPEND_OR_SET_ERR(library.connections, parseConnection());
|
||||
} else if(check(TokenType::KW_MESSAGES)) {
|
||||
bump();
|
||||
ASSIGN_OR_SET_ERR(library.messages, parseList<PropertyNode>(std::optional<TokenType>(TokenType::LBRACE),
|
||||
TokenType::RBRACE,
|
||||
std::nullopt,
|
||||
false,
|
||||
[this]{return parseProperty(std::optional<TokenType>(TokenType::STRING));}
|
||||
));
|
||||
} else {
|
||||
err = unexpected();
|
||||
}
|
||||
|
@ -324,30 +313,6 @@ PResult<NumberNode> ComdelParser::parseNumber()
|
|||
}
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* 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 + ")"
|
||||
|
@ -356,6 +321,8 @@ void ComdelParser::parseBlock()
|
|||
PResult<CountNode> ComdelParser::parseCount()
|
||||
{
|
||||
auto spanner = getSpanner();
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_COUNT);
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LPAREN);
|
||||
auto first = parseNumber();
|
||||
if(!first.has_value()) {
|
||||
|
@ -383,22 +350,18 @@ PResult<PropertyNode> ComdelParser::parseProperty(std::optional<TokenType> value
|
|||
ASSIGN_OR_RETURN_IF_ERR(key, parseIdentifier());
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::COLON);
|
||||
Value value;
|
||||
|
||||
if(valueType.has_value()) {
|
||||
if(!check(*valueType)) {
|
||||
if(valueType == TokenType::BOOL_TYPE) {
|
||||
if(!(check(TokenType::TRUE) || check(TokenType::FALSE))) {
|
||||
return unexpected();
|
||||
}
|
||||
} else 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();
|
||||
}
|
||||
Value value;
|
||||
ASSIGN_OR_RETURN_IF_ERR(value, parseValue());
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::SEMICOLON);
|
||||
|
||||
|
@ -420,6 +383,8 @@ PResult<AddressSpace> ComdelParser::parseAddress()
|
|||
|
||||
AddressSpace addressSpace{};
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_ADDRESS);
|
||||
|
||||
ASSIGN_OR_RETURN_IF_ERR(addressSpace.name, parseIdentifier());
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LPAREN);
|
||||
ASSIGN_OR_RETURN_IF_ERR(addressSpace.start, parseNumber());
|
||||
|
@ -442,17 +407,16 @@ PResult<Component> ComdelParser::parseComponent()
|
|||
|
||||
Component component{};
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_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'"});
|
||||
}
|
||||
if(check(TokenType::CT_PROCESSOR)) {
|
||||
bump();
|
||||
component.type = Component::PROCESSOR;
|
||||
} else if(check(TokenType::CT_MEMORY)) {
|
||||
bump();
|
||||
component.type = Component::MEMORY;
|
||||
} else {
|
||||
component.type = Component::OTHER;
|
||||
}
|
||||
|
@ -461,37 +425,25 @@ PResult<Component> ComdelParser::parseComponent()
|
|||
|
||||
while(!check(TokenType::RBRACE)) {
|
||||
PResult<poly<AstNode>> err;
|
||||
if (check(TokenType::KEYWORD)) {
|
||||
auto keyword = KeywordType::determineType(current().text);
|
||||
if(check(TokenType::KW_INSTANCE_NAME)) {
|
||||
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();
|
||||
}
|
||||
ASSIGN_OR_RETURN_IF_ERR(component.instanceName, parseString());
|
||||
} else if(check(TokenType::KW_TOOLTIP)) {
|
||||
bump();
|
||||
ASSIGN_OR_RETURN_IF_ERR(component.tooltip, parseString());
|
||||
} else if(check(TokenType::KW_SOURCE)) {
|
||||
bump();
|
||||
ASSIGN_OR_RETURN_IF_ERR(component.source, parseString());
|
||||
} else if(check(TokenType::KW_COUNT)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(component.count, parseCount());
|
||||
} else if(check(TokenType::KW_DISPLAY)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(component.display, parseDisplay());
|
||||
} else if(check(TokenType::KW_PIN)) {
|
||||
APPEND_OR_RETURN_IF_ERR(component.pin, parsePin());
|
||||
} else if(check(TokenType::KW_ATTRIBUTE)) {
|
||||
APPEND_OR_RETURN_IF_ERR(component.attributes, parseAttribute());
|
||||
} else if(check(TokenType::KW_RULE)) {
|
||||
APPEND_OR_RETURN_IF_ERR(component.rules, parseRule());
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
@ -510,6 +462,8 @@ PResult<Component> ComdelParser::parseComponent()
|
|||
****************************************************************************/
|
||||
PResult<Display> ComdelParser::parseDisplay() {
|
||||
auto spanner = getSpanner();
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_DISPLAY);
|
||||
Display display;
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
while(!check(TokenType::RBRACE)) {
|
||||
|
@ -553,6 +507,8 @@ PResult<Bus> ComdelParser::parseBus() {
|
|||
auto spanner = getSpanner();
|
||||
Bus bus;
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_BUS);
|
||||
|
||||
ASSIGN_OR_RETURN_IF_ERR(bus.name, parseIdentifier());
|
||||
|
||||
if(check(TokenType::IDENTIFIER)) {
|
||||
|
@ -571,33 +527,24 @@ PResult<Bus> ComdelParser::parseBus() {
|
|||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
|
||||
while(!check(TokenType::RBRACE)) {
|
||||
if (check(TokenType::KEYWORD)) {
|
||||
auto keyword = KeywordType::determineType(current().text);
|
||||
if(check(TokenType::KW_TOOLTIP)) {
|
||||
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());
|
||||
ASSIGN_OR_RETURN_IF_ERR(bus.tooltip, parseString());
|
||||
} else if(check(TokenType::KW_COUNT)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(bus.count, parseCount());
|
||||
} else if(check(TokenType::KW_DISPLAY)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(bus.display, parseDisplay());
|
||||
} else if(check(TokenType::KW_WIRES)) {
|
||||
bump();
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
while(check(TokenType::IDENTIFIER)) {
|
||||
APPEND_OR_RETURN_IF_ERR(bus.wires, parseWire());
|
||||
|
||||
if(check(TokenType::COMMA)) {
|
||||
RETURN_IF_NOT_TOKEN(TokenType::COMMA);
|
||||
}
|
||||
if(check(TokenType::COMMA)) {
|
||||
RETURN_IF_NOT_TOKEN(TokenType::COMMA);
|
||||
}
|
||||
RETURN_IF_NOT_TOKEN(TokenType::RBRACE);
|
||||
break;
|
||||
default:
|
||||
return unexpected();
|
||||
}
|
||||
RETURN_IF_NOT_TOKEN(TokenType::RBRACE);
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
@ -628,24 +575,21 @@ PResult<Wire> ComdelParser::parseWire() {
|
|||
wire.size.value = 1;
|
||||
}
|
||||
|
||||
if(check(TokenType::IDENTIFIER)) {
|
||||
IdentifierNode identifier;
|
||||
ASSIGN_OR_RETURN_IF_ERR(identifier, parseIdentifier());
|
||||
// default
|
||||
wire.type = Wire::WIRE;
|
||||
|
||||
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 {
|
||||
if(check(TokenType::WIRE_DEFAULT)) {
|
||||
bump();
|
||||
wire.type = Wire::WIRE;
|
||||
} else if(check(TokenType::WIRE_AND)) {
|
||||
bump();
|
||||
wire.type = Wire::WIRED_AND;
|
||||
} else if(check(TokenType::WIRE_OR)) {
|
||||
bump();
|
||||
wire.type = Wire::WIRED_OR;
|
||||
} else if(check(TokenType::R_WIRE)) {
|
||||
bump();
|
||||
wire.type = Wire::R_WIRE;
|
||||
}
|
||||
|
||||
return spanner(wire);
|
||||
|
@ -664,43 +608,34 @@ PResult<Wire> ComdelParser::parseWire() {
|
|||
PResult<Pin> ComdelParser::parsePin() {
|
||||
auto spanner = getSpanner();
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_PIN);
|
||||
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 {
|
||||
|
||||
if(check(TokenType::PIN_IN)) {
|
||||
bump();
|
||||
pin.type = Pin::IN;
|
||||
} else if(check(TokenType::PIN_OUT)) {
|
||||
bump();
|
||||
pin.type = Pin::OUT;
|
||||
} else if(check(TokenType::PIN_IN_OUT)) {
|
||||
bump();
|
||||
pin.type = Pin::IN_OUT;
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
|
||||
while(!check(TokenType::RBRACE)) {
|
||||
if (check(TokenType::KEYWORD)) {
|
||||
auto keyword = KeywordType::determineType(current().text);
|
||||
if (check(TokenType::KW_TOOLTIP)) {
|
||||
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();
|
||||
}
|
||||
ASSIGN_OR_RETURN_IF_ERR(pin.tooltip, parseString());
|
||||
} else if (check(TokenType::KW_DISPLAY)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(pin.display, parseDisplay());
|
||||
} else if (check(TokenType::KW_CONNECTION)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(pin.connection, parsePinConnection());
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
@ -721,11 +656,13 @@ PResult<PinConnection> ComdelParser::parsePinConnection() {
|
|||
auto spanner = getSpanner();
|
||||
PinConnection connection{};
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_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") {
|
||||
} else if(type.value().value == "automatically") {
|
||||
connection.type = PinConnection::AUTOMATICALLY;
|
||||
} else {
|
||||
return PError(SourceError{current().span, "expected identifiers 'check_only' or 'automatically'"});
|
||||
|
@ -751,65 +688,57 @@ PResult<Attribute> ComdelParser::parseAttribute() {
|
|||
auto spanner = getSpanner();
|
||||
Attribute attribute;
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_ATTRIBUTE);
|
||||
|
||||
if(check(TokenType::IDENTIFIER)) {
|
||||
ASSIGN_OR_RETURN_IF_ERR(attribute.name, parseIdentifier());
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
||||
if(check(TokenType::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'"});
|
||||
}
|
||||
|
||||
if(check(TokenType::INT_TYPE)) {
|
||||
attribute.type = Value::INT;
|
||||
} else if(check(TokenType::STRING_TYPE)) {
|
||||
attribute.type = Value::STRING;
|
||||
} else if(check(TokenType::BOOL_TYPE)) {
|
||||
attribute.type = Value::BOOL;
|
||||
} else if(check(TokenType::WIRE_TYPE)) {
|
||||
attribute.type = Value::IDENTIFIER;
|
||||
} 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'"});
|
||||
return unexpected();
|
||||
}
|
||||
bump();
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::DEFAULT);
|
||||
|
||||
if(attribute.type == Value::BOOL) {
|
||||
IdentifierNode identifier;
|
||||
ASSIGN_OR_RETURN_IF_ERR(identifier, parseIdentifier());
|
||||
if(identifier.value == "true") {
|
||||
if(check(TokenType::TRUE)) {
|
||||
attribute.defaultValue = Value::ofBool(true);
|
||||
} else if(identifier.value == "false") {
|
||||
} else if(check(TokenType::FALSE)) {
|
||||
attribute.defaultValue = Value::ofBool(false);
|
||||
} else {
|
||||
return PError(SourceError{current().span, "expected value 'true' or 'false'"});
|
||||
return unexpected();
|
||||
}
|
||||
} else if(attribute.type == Value::INT) {
|
||||
if(check(TokenType::NUMBER)) {
|
||||
auto number = parseNumber();
|
||||
attribute.defaultValue = Value::ofInt(number.value().value);
|
||||
attribute.defaultValue = Value::ofInt(number->value);
|
||||
} else {
|
||||
return PError(SourceError{current().span, "expected number"});
|
||||
return unexpected();
|
||||
}
|
||||
} else if(attribute.type == Value::STRING) {
|
||||
if(check(TokenType::STRING)) {
|
||||
auto string = parseString();
|
||||
attribute.defaultValue = Value::ofString(string.value().value);
|
||||
attribute.defaultValue = Value::ofString(string->value);
|
||||
} else {
|
||||
return PError(SourceError{current().span, "expected string"});
|
||||
return unexpected();
|
||||
}
|
||||
} else if(attribute.type == Value::IDENTIFIER) {
|
||||
if(check(TokenType::IDENTIFIER)) {
|
||||
auto identifier = parseIdentifier();
|
||||
attribute.defaultValue = Value::ofIdentifier(identifier.value().value);
|
||||
attribute.defaultValue = Value::ofIdentifier(identifier->value);
|
||||
} else {
|
||||
return PError(SourceError{current().span, "expected wire"});
|
||||
return unexpected();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -817,10 +746,9 @@ PResult<Attribute> ComdelParser::parseAttribute() {
|
|||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
Popup popup;
|
||||
|
||||
if(!(check(TokenType::KEYWORD) && current().text == "@popup")) {
|
||||
return PError(SourceError{current().span, "expected '@popup'"});
|
||||
if(!check(TokenType::KW_POPUP)) {
|
||||
return unexpected();
|
||||
}
|
||||
bump();
|
||||
|
||||
ASSIGN_OR_RETURN_IF_ERR(popup, parsePopup());
|
||||
attribute.popup = std::optional<Popup>(popup);
|
||||
|
@ -838,19 +766,12 @@ PResult<Attribute> ComdelParser::parseAttribute() {
|
|||
PResult<EnumerationNode> 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();
|
||||
}
|
||||
ASSIGN_OR_RETURN_IF_ERR(value, parseValue());
|
||||
|
||||
EnumerationNode node;
|
||||
node.key = key;
|
||||
|
@ -868,6 +789,8 @@ PResult<Popup> ComdelParser::parsePopup() {
|
|||
auto spanner = getSpanner();
|
||||
Popup popup;
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_POPUP);
|
||||
|
||||
if(check(TokenType::IDENTIFIER)) {
|
||||
auto type = parseIdentifier();
|
||||
if(type.value().value == "automatic") {
|
||||
|
@ -884,35 +807,26 @@ PResult<Popup> ComdelParser::parsePopup() {
|
|||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
|
||||
while(!check(TokenType::RBRACE)) {
|
||||
PResult<poly<AstNode>> err;
|
||||
if (check(TokenType::KEYWORD)) {
|
||||
auto keyword = KeywordType::determineType(current().text);
|
||||
if(check(TokenType::KW_TITLE)) {
|
||||
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<EnumerationNode>(
|
||||
std::optional<TokenType>(TokenType::LBRACE),
|
||||
TokenType::RBRACE,
|
||||
std::optional<TokenType>(TokenType::COMMA),
|
||||
true,
|
||||
[this]{ return parseEnumeration();}
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return unexpected();
|
||||
}
|
||||
ASSIGN_OR_RETURN_IF_ERR(popup.title, parseString());
|
||||
} else if(check(TokenType::KW_TEXT)) {
|
||||
bump();
|
||||
ASSIGN_OR_RETURN_IF_ERR(popup.text, parseString());
|
||||
} else if(check(TokenType::KW_RULE)) {
|
||||
APPEND_OR_RETURN_IF_ERR(popup.rules, parseRule());
|
||||
} else if(check(TokenType::KW_ENUMERATED)) {
|
||||
bump();
|
||||
popup.enumerated = true;
|
||||
ASSIGN_OR_RETURN_IF_ERR(popup.enumeration,
|
||||
parseList<EnumerationNode>(
|
||||
std::optional<TokenType>(TokenType::LBRACE),
|
||||
TokenType::RBRACE,
|
||||
std::optional<TokenType>(TokenType::COMMA),
|
||||
true,
|
||||
[this]{ return parseEnumeration();}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
@ -932,6 +846,8 @@ PResult<Connection> ComdelParser::parseConnection() {
|
|||
auto spanner = getSpanner();
|
||||
Connection connection;
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::KW_CONNECTION);
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LPAREN);
|
||||
ASSIGN_OR_RETURN_IF_ERR(connection.component, parseIdentifier());
|
||||
RETURN_IF_NOT_TOKEN(TokenType::DOT);
|
||||
|
@ -943,24 +859,14 @@ PResult<Connection> ComdelParser::parseConnection() {
|
|||
RETURN_IF_NOT_TOKEN(TokenType::LBRACE);
|
||||
|
||||
while(!check(TokenType::RBRACE)) {
|
||||
if (check(TokenType::KEYWORD)) {
|
||||
auto keyword = KeywordType::determineType(current().text);
|
||||
if (check(TokenType::KW_ATTRIBUTE)) {
|
||||
APPEND_OR_RETURN_IF_ERR(connection.attributes, parseAttribute());
|
||||
} else if(check(TokenType::KW_WIRES)) {
|
||||
bump();
|
||||
switch (keyword) {
|
||||
case KeywordType::ATTRIBUTE:
|
||||
APPEND_OR_RETURN_IF_ERR(connection.attributes, parseAttribute());
|
||||
break;
|
||||
case KeywordType::WIRES:
|
||||
{
|
||||
auto wires = parseList<IdentifierNode>(std::optional<TokenType>(TokenType::LBRACE), TokenType::RBRACE, std::optional<TokenType>(TokenType::COMMA), false,
|
||||
[this] { return parseIdentifier(); });
|
||||
RETURN_IF_ERR(wires);
|
||||
connection.wires = *wires;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return unexpected();
|
||||
}
|
||||
auto wires = parseList<IdentifierNode>(std::optional<TokenType>(TokenType::LBRACE), TokenType::RBRACE, std::optional<TokenType>(TokenType::COMMA), false,
|
||||
[this] { return parseIdentifier(); });
|
||||
RETURN_IF_ERR(wires);
|
||||
connection.wires = *wires;
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
@ -981,17 +887,16 @@ PResult<Rule> ComdelParser::parseRule() {
|
|||
auto spanner = getSpanner();
|
||||
Rule 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::IDENTIFIER)) {
|
||||
IdentifierNode node;
|
||||
ASSIGN_OR_RETURN_IF_ERR(node, parseIdentifier());
|
||||
if(node.value != "else") {
|
||||
return PError(SourceError{node.span, "expected 'else'"});
|
||||
}
|
||||
if(check(TokenType::RBRACE)) {
|
||||
break;
|
||||
}
|
||||
RETURN_IF_NOT_TOKEN(TokenType::ELSE);
|
||||
}
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::RBRACE);
|
||||
|
@ -1009,11 +914,7 @@ PResult<IfStmt> 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::IF);
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LPAREN);
|
||||
|
||||
|
@ -1037,17 +938,14 @@ PResult<IfStmt> ComdelParser::parseIfStatement() {
|
|||
|
||||
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'"});
|
||||
}
|
||||
if(check(TokenType::ERROR)) {
|
||||
ifStatement.action.type = Action::ERROR;
|
||||
} else if(check(TokenType::WARNING)) {
|
||||
ifStatement.action.type = Action::WARNING;
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
bump();
|
||||
|
||||
RETURN_IF_NOT_TOKEN(TokenType::LPAREN);
|
||||
ASSIGN_OR_RETURN_IF_ERR(ifStatement.action.message, parseString());
|
||||
|
@ -1069,6 +967,12 @@ PResult<Value> ComdelParser::parseValue() {
|
|||
value = Value::ofString(parseString()->value);
|
||||
} else if(check(TokenType::NUMBER)) {
|
||||
value = Value::ofInt(parseNumber()->value);
|
||||
} else if(check(TokenType::TRUE)) {
|
||||
value = Value::ofBool(true);
|
||||
} else if(check(TokenType::FALSE)) {
|
||||
value = Value::ofBool(false);
|
||||
} else {
|
||||
return unexpected();
|
||||
}
|
||||
|
||||
return spanner(value);
|
||||
|
|
|
@ -33,35 +33,3 @@ Token::Token(TokenType type, Span span, std::string text)
|
|||
, span(std::move(span))
|
||||
, text(std::move(text))
|
||||
{}
|
||||
|
||||
|
||||
const std::map<std::string, KeywordType::Type> KeywordType::keywords = {
|
||||
{"@name", NAME},
|
||||
{"@info", INFO},
|
||||
{"@header", HEADER},
|
||||
{"@directory", DIRECTORY},
|
||||
{"@library", LIBRARY},
|
||||
{"@address", ADDRESS},
|
||||
{"@component", COMPONENT},
|
||||
{"@messages", MESSAGES},
|
||||
{"@instanceName", INSTANCE_NAME},
|
||||
{"@count", COUNT},
|
||||
{"@display", DISPLAY},
|
||||
{"@pin", PIN},
|
||||
{"@tooltip", TOOLTIP},
|
||||
{"@connection", CONNECTION},
|
||||
{"@attribute", ATTRIBUTE},
|
||||
{"@source", SOURCE},
|
||||
{"@popup", POPUP},
|
||||
{"@rule", RULE},
|
||||
{"@title", TITLE},
|
||||
{"@text", TEXT},
|
||||
{"@bus", BUS},
|
||||
{"@wires", WIRES},
|
||||
{"@enumerated", ENUMERATED},
|
||||
{"@wire", WIRE},
|
||||
{"@instance", INSTANCE},
|
||||
{"@schema", SCHEMA},
|
||||
{"@position", POSITION},
|
||||
{"@size", SIZE}
|
||||
};
|
||||
|
|
|
@ -56,68 +56,73 @@ enum class TokenType {
|
|||
COMMENT,
|
||||
WHITESPACE,
|
||||
|
||||
END_OF_FILE
|
||||
};
|
||||
IF,
|
||||
ELSE,
|
||||
|
||||
DEFAULT,
|
||||
|
||||
// BOOLEAN VALUE
|
||||
TRUE,
|
||||
FALSE,
|
||||
|
||||
// KEYWORDS
|
||||
KW_NAME,
|
||||
KW_INFO,
|
||||
KW_HEADER,
|
||||
KW_DIRECTORY,
|
||||
KW_LIBRARY,
|
||||
KW_ADDRESS,
|
||||
KW_COMPONENT,
|
||||
KW_MESSAGES,
|
||||
KW_INSTANCE_NAME,
|
||||
KW_COUNT,
|
||||
KW_DISPLAY,
|
||||
KW_PIN,
|
||||
KW_TOOLTIP,
|
||||
KW_CONNECTION,
|
||||
KW_ATTRIBUTE,
|
||||
KW_SOURCE,
|
||||
KW_POPUP,
|
||||
KW_RULE,
|
||||
KW_TITLE,
|
||||
KW_TEXT,
|
||||
KW_BUS,
|
||||
KW_WIRES,
|
||||
KW_ENUMERATED,
|
||||
KW_WIRE,
|
||||
KW_INSTANCE,
|
||||
KW_SCHEMA,
|
||||
KW_POSITION,
|
||||
KW_SIZE,
|
||||
KW_UNKNOWN,
|
||||
|
||||
enum class ValueType {
|
||||
// TYPES
|
||||
BOOL,
|
||||
INT,
|
||||
STRING,
|
||||
COLOR,
|
||||
WIRE,
|
||||
ADDRESS_SPACE,
|
||||
};
|
||||
INT_TYPE,
|
||||
BOOL_TYPE,
|
||||
STRING_TYPE,
|
||||
WIRE_TYPE,
|
||||
|
||||
class KeywordType {
|
||||
// WIRE TYPES
|
||||
WIRE_DEFAULT,
|
||||
WIRE_AND,
|
||||
WIRE_OR,
|
||||
R_WIRE,
|
||||
|
||||
public:
|
||||
// PIN_TYPES
|
||||
PIN_IN,
|
||||
PIN_OUT,
|
||||
PIN_IN_OUT,
|
||||
|
||||
enum Type {
|
||||
// KEYWORDS
|
||||
NAME,
|
||||
INFO,
|
||||
HEADER,
|
||||
DIRECTORY,
|
||||
LIBRARY,
|
||||
ADDRESS,
|
||||
COMPONENT,
|
||||
MESSAGES,
|
||||
INSTANCE_NAME,
|
||||
COUNT,
|
||||
DISPLAY,
|
||||
PIN,
|
||||
TOOLTIP,
|
||||
CONNECTION,
|
||||
ATTRIBUTE,
|
||||
SOURCE,
|
||||
POPUP,
|
||||
RULE,
|
||||
TITLE,
|
||||
TEXT,
|
||||
BUS,
|
||||
WIRES,
|
||||
ENUMERATED,
|
||||
WIRE,
|
||||
INSTANCE,
|
||||
SCHEMA,
|
||||
POSITION,
|
||||
SIZE,
|
||||
UNKNOWN
|
||||
};
|
||||
// COMPONENT TYPES
|
||||
CT_MEMORY,
|
||||
CT_PROCESSOR,
|
||||
|
||||
// ACTION TYPES
|
||||
ERROR,
|
||||
WARNING,
|
||||
|
||||
static KeywordType::Type determineType(std::string type) {
|
||||
if(keywords.count(type) == 0) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
return keywords.at(type);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const static std::map<std::string, KeywordType::Type> keywords;
|
||||
|
||||
// OTHER
|
||||
END_OF_FILE
|
||||
};
|
||||
|
||||
struct Token {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
enum TokenClass {
|
||||
BUILT_IN_FUNC = 1,
|
||||
DATA_TYPE = 2,
|
||||
KEYWORD_NAME = 4
|
||||
KEYWORD_NAME = 4,
|
||||
TOKENIZABLE = 8
|
||||
};
|
||||
|
||||
|
||||
|
@ -18,6 +19,8 @@ struct TokenTables {
|
|||
std::unordered_map<TokenType, TokenInfo> allTokensInfo;
|
||||
std::unordered_map<std::string, TokenType> keywords;
|
||||
|
||||
TokenType tokenize(std::string value, TokenType initial);
|
||||
|
||||
void add(TokenType tokenType, const std::string& txt,
|
||||
unsigned short attribs = 0);
|
||||
|
||||
|
@ -41,9 +44,9 @@ TokenTables::TokenTables() {
|
|||
add( TokenType::KEYWORD, "keyword" );
|
||||
|
||||
// Literals (bool is not here, it has two keywords: false and true)
|
||||
add( TokenType::NUMBER, "number" );
|
||||
add( TokenType::STRING, "string" );
|
||||
add( TokenType::COLOR, "color" );
|
||||
add( TokenType::NUMBER, "number");
|
||||
add( TokenType::STRING, "string");
|
||||
add( TokenType::COLOR, "color");
|
||||
|
||||
// Parentheses of all kinds
|
||||
add( TokenType::LPAREN, "(" );
|
||||
|
@ -55,7 +58,7 @@ TokenTables::TokenTables() {
|
|||
add( TokenType::LT, "<" );
|
||||
add( TokenType::GT, ">" );
|
||||
|
||||
// assignments and resizes
|
||||
// assignments
|
||||
add( TokenType::EQUALS, "=" );
|
||||
|
||||
// miscellaneous
|
||||
|
@ -66,6 +69,69 @@ TokenTables::TokenTables() {
|
|||
add( TokenType::WHITESPACE, "whitespace" );
|
||||
add( TokenType::COMMENT, "comment" );
|
||||
|
||||
add( TokenType::TRUE, "true", TOKENIZABLE ),
|
||||
add( TokenType::FALSE, "false", TOKENIZABLE),
|
||||
|
||||
add( TokenType::IF, "if", TOKENIZABLE ),
|
||||
add( TokenType::ELSE, "else", TOKENIZABLE),
|
||||
|
||||
add( TokenType::DEFAULT, "default", TOKENIZABLE),
|
||||
|
||||
// all keywords
|
||||
add( TokenType::KW_NAME, "@name", TOKENIZABLE),
|
||||
add( TokenType::KW_INFO, "@info", TOKENIZABLE),
|
||||
add( TokenType::KW_HEADER, "@header", TOKENIZABLE),
|
||||
add( TokenType::KW_DIRECTORY, "@directory", TOKENIZABLE),
|
||||
add( TokenType::KW_LIBRARY, "@library", TOKENIZABLE),
|
||||
add( TokenType::KW_ADDRESS, "@address", TOKENIZABLE),
|
||||
add( TokenType::KW_COMPONENT, "@component", TOKENIZABLE),
|
||||
add( TokenType::KW_MESSAGES, "@messages", TOKENIZABLE),
|
||||
add( TokenType::KW_INSTANCE_NAME, "@instanceName", TOKENIZABLE),
|
||||
add( TokenType::KW_COUNT, "@count", TOKENIZABLE),
|
||||
add( TokenType::KW_DISPLAY, "@display", TOKENIZABLE),
|
||||
add( TokenType::KW_PIN, "@pin", TOKENIZABLE),
|
||||
add( TokenType::KW_TOOLTIP, "@tooltip", TOKENIZABLE),
|
||||
add( TokenType::KW_CONNECTION, "@connection", TOKENIZABLE),
|
||||
add( TokenType::KW_ATTRIBUTE, "@attribute", TOKENIZABLE),
|
||||
add( TokenType::KW_SOURCE, "@source", TOKENIZABLE),
|
||||
add( TokenType::KW_POPUP, "@popup", TOKENIZABLE),
|
||||
add( TokenType::KW_RULE, "@rule", TOKENIZABLE),
|
||||
add( TokenType::KW_TITLE, "@title", TOKENIZABLE),
|
||||
add( TokenType::KW_TEXT, "@text", TOKENIZABLE),
|
||||
add( TokenType::KW_BUS, "@bus", TOKENIZABLE),
|
||||
add( TokenType::KW_WIRES, "@wires", TOKENIZABLE),
|
||||
add( TokenType::KW_ENUMERATED, "@enumerated", TOKENIZABLE),
|
||||
add( TokenType::KW_WIRE, "@wire", TOKENIZABLE),
|
||||
add( TokenType::KW_INSTANCE, "@instance", TOKENIZABLE),
|
||||
add( TokenType::KW_SCHEMA, "@schema", TOKENIZABLE),
|
||||
add( TokenType::KW_POSITION, "@position", TOKENIZABLE),
|
||||
add( TokenType::KW_SIZE, "@size", TOKENIZABLE),
|
||||
|
||||
// All types
|
||||
add( TokenType::INT_TYPE, "int", TOKENIZABLE),
|
||||
add( TokenType::STRING_TYPE, "string", TOKENIZABLE),
|
||||
add( TokenType::BOOL_TYPE, "bool", TOKENIZABLE),
|
||||
add( TokenType::WIRE_TYPE, "wire", TOKENIZABLE),
|
||||
|
||||
// Wire types
|
||||
add( TokenType::WIRE_DEFAULT, "normal_wire", TOKENIZABLE),
|
||||
add( TokenType::WIRE_AND, "wired_and", TOKENIZABLE),
|
||||
add( TokenType::WIRE_OR, "wired_or", TOKENIZABLE),
|
||||
add( TokenType::R_WIRE, "r_wire", TOKENIZABLE),
|
||||
|
||||
// Component types
|
||||
add( TokenType::CT_MEMORY, "memory", TOKENIZABLE),
|
||||
add( TokenType::CT_PROCESSOR, "processor", TOKENIZABLE),
|
||||
|
||||
// Pin type
|
||||
add( TokenType::PIN_IN, "in", TOKENIZABLE),
|
||||
add( TokenType::PIN_OUT, "out", TOKENIZABLE),
|
||||
add( TokenType::PIN_IN_OUT, "inOut", TOKENIZABLE),
|
||||
|
||||
// Action types
|
||||
add( TokenType::ERROR, "error", TOKENIZABLE),
|
||||
add( TokenType::WARNING, "warning", TOKENIZABLE),
|
||||
|
||||
|
||||
// Built-in functions (they are also keywords)
|
||||
/*
|
||||
|
@ -76,11 +142,23 @@ TokenTables::TokenTables() {
|
|||
}
|
||||
|
||||
|
||||
|
||||
const std::string &tokenTypeToString(TokenType tokenType) {
|
||||
return tokenTables.allTokensInfo[tokenType].text;
|
||||
}
|
||||
|
||||
TokenType TokenTables::tokenize(std::string value, TokenType initial) {
|
||||
for(auto& [key, param]: allTokensInfo) {
|
||||
if(param.attributes & TOKENIZABLE && param.text == value) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return initial;
|
||||
}
|
||||
|
||||
TokenType from_token(std::string value, TokenType initial) {
|
||||
return tokenTables.tokenize(value, initial);
|
||||
}
|
||||
|
||||
bool isBuiltInFunc(TokenType tokenType)
|
||||
{
|
||||
return tokenTables.allTokensInfo[tokenType].attributes & BUILT_IN_FUNC;
|
||||
|
|
|
@ -11,5 +11,6 @@ const std::string& tokenTypeToString(TokenType tokenType);
|
|||
bool isBuiltInFunc(TokenType tokenType);
|
||||
bool isDataType(TokenType tokenType);
|
||||
|
||||
TokenType from_token(std::string value, TokenType initial);
|
||||
|
||||
#endif // TOKENSTYPE_H
|
||||
|
|
Loading…
Reference in New Issue