diff --git a/parser_gui/Data/include/Token.h b/parser_gui/Data/include/Token.h index 796ea37..e6350be 100644 --- a/parser_gui/Data/include/Token.h +++ b/parser_gui/Data/include/Token.h @@ -113,6 +113,20 @@ class Token { QString toHTMLString(bool includePosition = false) const; + void setType(QString type){ + // loop over the token type strings + for (size_t i = 0; i < tokenTypeStrings.size(); i++) { + // check if the type string matches + if (type == tokenTypeStrings[i]) { + // set the type + this->type = static_cast(i); + return; + } + } + } + void setValue(QString value){ + this->value = value; + } // copy constructor Token(const Token &other) = default; diff --git a/parser_gui/Parser/src/Parser.cpp b/parser_gui/Parser/src/Parser.cpp index dc8fc50..efd9c46 100644 --- a/parser_gui/Parser/src/Parser.cpp +++ b/parser_gui/Parser/src/Parser.cpp @@ -33,13 +33,17 @@ void Parser::match(Data::Token::TokenType expectedType) { if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In match(): Unexpected end of file while expecting " + Token::getTokenTypeString(expectedType).toString().toStdString()); } Data::Token currentToken = tokenIterator.next(); if(currentToken.getType() != expectedType) { - throw std::invalid_argument("Unexpected token: " + currentToken.getValue().toStdString()); + throw std::invalid_argument("In match(): Expected " + + Token::getTokenTypeString(expectedType).toString().toStdString() + + " but got " + + Token::getTokenTypeString(currentToken.getType()).toString().toStdString() + + " (value: " + currentToken.getValue().toStdString() + ")"); } } @@ -57,6 +61,12 @@ void Parser::assignLevels(Node *node, int baseLevel) Node *Parser::parseStmtSequence() { + // parseStmtSequence rule + if(!tokenIterator.hasNext()) + { + throw std::out_of_range("In parseStmtSequence(): Unexpected end of file"); + } + Node* stmt = parseStatement(); stmt->setLevel(currentLevel); @@ -77,41 +87,38 @@ Node *Parser::parseStmtSequence() Node *Parser::parseStatement() { - Node* stmt = nullptr; + // parseStatement rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseStatement(): Unexpected end of file"); } switch(tokenIterator.peekNext().getType()) { - case Token::TokenType::IF: - stmt = parseIfStatement(); - break; - case Token::TokenType::REPEAT: - stmt = parseRepeatStatement(); - break; - case Token::TokenType::IDENTIFIER: - stmt = parseAssignStatement(); - break; - case Token::TokenType::READ: - stmt = parseReadStatement(); - break; - case Token::TokenType::WRITE: - stmt = parseWriteStatement(); - break; - default: - throw std::invalid_argument("Unexpected token: " + tokenIterator.peekNext().getValue().toStdString()); + case Token::TokenType::IF: + return parseIfStatement(); + case Token::TokenType::REPEAT: + return parseRepeatStatement(); + case Token::TokenType::IDENTIFIER: + return parseAssignStatement(); + case Token::TokenType::READ: + return parseReadStatement(); + case Token::TokenType::WRITE: + return parseWriteStatement(); + default: + throw std::invalid_argument("In parseStatement(): Unexpected token: " + + tokenIterator.peekNext().getValue().toStdString() + + " of type " + + Token::getTokenTypeString(tokenIterator.peekNext().getType()).toString().toStdString()); } - - return stmt; } Node *Parser::parseIfStatement() { + // parseIfStatement rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseIfStatement(): Unexpected end of file"); } match(Token::TokenType::IF); @@ -120,30 +127,25 @@ Node *Parser::parseIfStatement() currentLevel++; - // Parse the expression Node* expNode = parseExp(); expNode->setLevel(currentLevel); ifNode->addChild(expNode); match(Token::TokenType::THEN); - // Parse the statement sequence Node* stmtSeqNode = parseStmtSequence(); stmtSeqNode->setLevel(currentLevel); ifNode->addChild(stmtSeqNode); - // Check for an optional ELSE clause if(tokenIterator.hasNext() && tokenIterator.peekNext().getType() == Token::TokenType::ELSE) { match(Token::TokenType::ELSE); - // Parse the else statement sequence Node* elseStmtSeqNode = parseStmtSequence(); elseStmtSeqNode->setLevel(currentLevel); ifNode->addChild(elseStmtSeqNode); } - // End the IF statement match(Token::TokenType::END); currentLevel--; @@ -153,21 +155,19 @@ Node *Parser::parseIfStatement() Node *Parser::parseRepeatStatement() { - // Parse the REPEAT statement + // parseRepeatStatement rule match(Token::TokenType::REPEAT); Node* repeatNode = new Node(Node::NodeType::Repeat); repeatNode->setLevel(currentLevel); currentLevel++; - // Parse the statement sequence Node* stmtSeqNode = parseStmtSequence(); stmtSeqNode->setLevel(currentLevel); repeatNode->addChild(stmtSeqNode); match(Token::TokenType::UNTIL); - // Parse the expression Node* expNode = parseExp(); expNode->setLevel(currentLevel); repeatNode->addChild(expNode); @@ -179,24 +179,21 @@ Node *Parser::parseRepeatStatement() Node *Parser::parseAssignStatement() { - // get the id value + // parseAssignStatement rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseAssignStatement(): Unexpected end of file"); } QString idValue = tokenIterator.peekNext().getValue(); - // match the id match(Token::TokenType::IDENTIFIER); - // parse the assignment operator match(Token::TokenType::ASSIGN); Node* assignNode = new Node(Node::NodeType::Assign); assignNode->setLevel(currentLevel); assignNode->setValue(idValue); - // parse the expression Node* expNode = parseExp(); expNode->setLevel(currentLevel + 1); assignNode->addChild(expNode); @@ -206,19 +203,17 @@ Node *Parser::parseAssignStatement() Node *Parser::parseReadStatement() { - // parse the READ statement + // parseReadStatement rule match(Token::TokenType::READ); Node* readNode = new Node(Node::NodeType::Read); readNode->setLevel(currentLevel); - // get the id value if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseReadStatement(): Unexpected end of file expecting IDENTIFIER"); } QString idValue = tokenIterator.peekNext().getValue(); - // match the id match(Token::TokenType::IDENTIFIER); readNode->setValue(idValue); @@ -229,12 +224,11 @@ Node *Parser::parseReadStatement() Node *Parser::parseWriteStatement() { - // parse the WRITE statement + // parseWriteStatement rule match(Token::TokenType::WRITE); Node* writeNode = new Node(Node::NodeType::Write); writeNode->setLevel(currentLevel); - // parse the expression Node* expNode = parseExp(); expNode->setLevel(currentLevel + 1); writeNode->addChild(expNode); @@ -244,9 +238,12 @@ Node *Parser::parseWriteStatement() Node *Parser::parseExp() { + // parseExp rule Node* simpleExpNode = parseSimpleExp(); - if(tokenIterator.hasNext() && (tokenIterator.peekNext().getType() == Token::TokenType::LESSTHAN || tokenIterator.peekNext().getType() == Token::TokenType::EQUAL)) + if(tokenIterator.hasNext() && + (tokenIterator.peekNext().getType() == Token::TokenType::LESSTHAN || + tokenIterator.peekNext().getType() == Token::TokenType::EQUAL)) { Node* opNode = parseComparisonOp(); opNode->setLevel(currentLevel); @@ -266,50 +263,36 @@ Node *Parser::parseExp() Node *Parser::parseSimpleExp() { - // Parse the first term + // parseSimpleExp rule Node* firstTerm = parseTerm(); - // Vectors to store terms and operators std::vector terms; std::vector ops; terms.push_back(firstTerm); - // Collect all following operators and terms while (tokenIterator.hasNext() && (tokenIterator.peekNext().getType() == Token::TokenType::PLUS || tokenIterator.peekNext().getType() == Token::TokenType::MINUS)) { - Node* opNode = parseAddop(); // parse the operator - Node* nextTerm = parseTerm(); // parse the next term + Node* opNode = parseAddop(); + Node* nextTerm = parseTerm(); ops.push_back(opNode); terms.push_back(nextTerm); } - // Now we have all terms and operators. - // Example: For "a + a + a + a": - // terms = [a, a, a, a] - // ops = [+, +, +] - - // Decide how to build the tree. For example, if you want the last term deepest, - // you might build the tree right-associatively: - // a + a + a + a = a + (a + (a + a)) - - // Start from the left - Node* root = terms[0]; // The first term is the initial root + // Left-associative build: + Node* root = terms[0]; for (int i = 0; i < (int)ops.size(); i++) { Node* op = ops[i]; - Node* rightTerm = terms[i + 1]; // The next term after the operator + Node* rightTerm = terms[i + 1]; - // For left-associativity: op(root, rightTerm) op->addChild(root); op->addChild(rightTerm); - - root = op; // Now op becomes the current root + root = op; } - // Now 'root' is left-associative assignLevels(root, currentLevel); return root; } @@ -317,21 +300,26 @@ Node *Parser::parseSimpleExp() Node *Parser::parseComparisonOp() { + // parseComparisonOp rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseComparisonOp(): Unexpected end of file"); } - if(tokenIterator.peekNext().getType() != Token::TokenType::LESSTHAN && tokenIterator.peekNext().getType() != Token::TokenType::EQUAL) + Token::TokenType t = tokenIterator.peekNext().getType(); + if(t != Token::TokenType::LESSTHAN && t != Token::TokenType::EQUAL) { - throw std::invalid_argument("Unexpected token: " + tokenIterator.peekNext().getValue().toStdString()); + throw std::invalid_argument("In parseComparisonOp(): Unexpected token: " + + tokenIterator.peekNext().getValue().toStdString() + + " of type " + + Token::getTokenTypeString(t).toString().toStdString()); } Node* opNode = new Node(Node::NodeType::Op); opNode->setLevel(currentLevel); opNode->setValue(tokenIterator.peekNext().getValue()); - if(tokenIterator.peekNext().getType() == Token::TokenType::LESSTHAN) + if(t == Token::TokenType::LESSTHAN) { match(Token::TokenType::LESSTHAN); } @@ -345,21 +333,26 @@ Node *Parser::parseComparisonOp() Node *Parser::parseAddop() { + // parseAddop rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseAddop(): Unexpected end of file"); } - if(tokenIterator.peekNext().getType() != Token::TokenType::PLUS && tokenIterator.peekNext().getType() != Token::TokenType::MINUS) + Token::TokenType t = tokenIterator.peekNext().getType(); + if(t != Token::TokenType::PLUS && t != Token::TokenType::MINUS) { - throw std::invalid_argument("Unexpected token: " + tokenIterator.peekNext().getValue().toStdString()); + throw std::invalid_argument("In parseAddop(): Unexpected token: " + + tokenIterator.peekNext().getValue().toStdString() + + " of type " + + Token::getTokenTypeString(t).toString().toStdString()); } Node* opNode = new Node(Node::NodeType::Op); opNode->setLevel(currentLevel); opNode->setValue(tokenIterator.peekNext().getValue()); - if(tokenIterator.peekNext().getType() == Token::TokenType::PLUS) + if(t == Token::TokenType::PLUS) { match(Token::TokenType::PLUS); } @@ -373,16 +366,14 @@ Node *Parser::parseAddop() Node *Parser::parseTerm() { - // Parse the first factor + // parseTerm rule Node* firstFactor = parseFactor(); - // Temporary storage for factors and operators std::vector factors; std::vector ops; factors.push_back(firstFactor); - // Collect all subsequent operators and factors while (tokenIterator.hasNext() && (tokenIterator.peekNext().getType() == Token::TokenType::MULT || tokenIterator.peekNext().getType() == Token::TokenType::DIV)) @@ -394,17 +385,15 @@ Node *Parser::parseTerm() factors.push_back(nextFactor); } - // Now build the tree from these operators and factors. - Node* root = factors[0]; // Start with the first factor + // Left-associative build: + Node* root = factors[0]; for (int i = 0; i < (int)ops.size(); i++) { Node* op = ops[i]; Node* rightFactor = factors[i + 1]; - // Left-associative: ( (firstFactor op secondFactor) op thirdFactor ) op fourthFactor op->addChild(root); op->addChild(rightFactor); - - root = op; // Update root + root = op; } assignLevels(root, currentLevel); @@ -413,21 +402,26 @@ Node *Parser::parseTerm() Node *Parser::parseMulop() { + // parseMulop rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseMulop(): Unexpected end of file"); } - if(tokenIterator.peekNext().getType() != Token::TokenType::MULT && tokenIterator.peekNext().getType() != Token::TokenType::DIV) + Token::TokenType t = tokenIterator.peekNext().getType(); + if(t != Token::TokenType::MULT && t != Token::TokenType::DIV) { - throw std::invalid_argument("Unexpected token: " + tokenIterator.peekNext().getValue().toStdString()); + throw std::invalid_argument("In parseMulop(): Unexpected token: " + + tokenIterator.peekNext().getValue().toStdString() + + " of type " + + Token::getTokenTypeString(t).toString().toStdString()); } Node* opNode = new Node(Node::NodeType::Op); opNode->setLevel(currentLevel); opNode->setValue(tokenIterator.peekNext().getValue()); - if(tokenIterator.peekNext().getType() == Token::TokenType::MULT) + if(t == Token::TokenType::MULT) { match(Token::TokenType::MULT); } @@ -441,38 +435,45 @@ Node *Parser::parseMulop() Node *Parser::parseFactor() { + // parseFactor rule if(!tokenIterator.hasNext()) { - throw std::out_of_range("Unexpected end of file"); + throw std::out_of_range("In parseFactor(): Unexpected end of file"); } Node* factorNode = nullptr; + Token::TokenType t = tokenIterator.peekNext().getType(); - switch(tokenIterator.peekNext().getType()) + switch(t) { - case Token::TokenType::OPENBRACKET: - match(Token::TokenType::OPENBRACKET); - factorNode = parseExp(); - factorNode->setLevel(currentLevel); - match(Token::TokenType::CLOSEDBRACKET); - break; - case Token::TokenType::NUMBER: - factorNode = new Node(Node::NodeType::Const); - factorNode->setLevel(currentLevel); - factorNode->setValue(tokenIterator.peekNext().getValue()); - match(Token::TokenType::NUMBER); - break; - case Token::TokenType::IDENTIFIER: - factorNode = new Node(Node::NodeType::Id); - factorNode->setLevel(currentLevel); - factorNode->setValue(tokenIterator.peekNext().getValue()); - match(Token::TokenType::IDENTIFIER); - break; - default: - throw std::invalid_argument("Unexpected token: " + tokenIterator.peekNext().getValue().toStdString()); + case Token::TokenType::OPENBRACKET: + match(Token::TokenType::OPENBRACKET); + factorNode = parseExp(); + factorNode->setLevel(currentLevel); + if(!tokenIterator.hasNext()) + { + throw std::out_of_range("In parseFactor(): Unexpected end of file, expecting CLOSEDBRACKET"); + } + match(Token::TokenType::CLOSEDBRACKET); + break; + case Token::TokenType::NUMBER: + factorNode = new Node(Node::NodeType::Const); + factorNode->setLevel(currentLevel); + factorNode->setValue(tokenIterator.peekNext().getValue()); + match(Token::TokenType::NUMBER); + break; + case Token::TokenType::IDENTIFIER: + factorNode = new Node(Node::NodeType::Id); + factorNode->setLevel(currentLevel); + factorNode->setValue(tokenIterator.peekNext().getValue()); + match(Token::TokenType::IDENTIFIER); + break; + default: + throw std::invalid_argument("In parseFactor(): Unexpected token: " + + tokenIterator.peekNext().getValue().toStdString() + + " of type " + + Token::getTokenTypeString(t).toString().toStdString()); } return factorNode; } - - diff --git a/parser_gui/Widgets/include/TabContent.h b/parser_gui/Widgets/include/TabContent.h index fbecc45..e8e76a4 100644 --- a/parser_gui/Widgets/include/TabContent.h +++ b/parser_gui/Widgets/include/TabContent.h @@ -38,6 +38,7 @@ class TabContent : public QWidget{ bool getIsTokenOnly() const; bool getIsTokenShowed() const; + QList> parseErrors; public slots: void showTokens(); @@ -75,6 +76,7 @@ public slots: bool processUnknownTokens(); void processReservedTokens(); + }; } // namespace Tiny::Widgets diff --git a/parser_gui/Widgets/include/TextEditor.h b/parser_gui/Widgets/include/TextEditor.h index 8613c4f..8aff239 100644 --- a/parser_gui/Widgets/include/TextEditor.h +++ b/parser_gui/Widgets/include/TextEditor.h @@ -19,6 +19,7 @@ namespace Tiny::Widgets { void markUnknowTokens(int line, int column, int charCount); void markReservedToken(int line, int column, int charCount); void resetFormat(); + void markParseError(int line, int column, int charCount, QString message); signals: public slots: diff --git a/parser_gui/Widgets/src/TabContent.cpp b/parser_gui/Widgets/src/TabContent.cpp index 2af1d56..fe524fb 100644 --- a/parser_gui/Widgets/src/TabContent.cpp +++ b/parser_gui/Widgets/src/TabContent.cpp @@ -22,6 +22,8 @@ TabContent::TabContent(bool tokenTextOnly, bool newFile, QWidget *parent) : initStyle(); initConfig(); + this->parseErrors = QVector>(); + // if not a new file, load the file if (!newFile) { // load the file @@ -54,7 +56,10 @@ TabContent::TabContent(bool tokenTextOnly, bool newFile, QWidget *parent) : connect(parser, &Parser::error, this, [this](Tiny::Data::Token token, QString message) { // show the error // TODO - //qDebug() << message; + qDebug() << message; + // append the error + this->parseErrors.append({token, message}); + }); } @@ -132,23 +137,59 @@ void TabContent::saveAsFile() void TabContent::textChanged() { - // skip the next text change - if (skipNextTextChange) { - skipNextTextChange = false; - return; - } - - // token only mode + // Token-only mode if (isTokenOnly) { - // get the new tokens - tokenText = textEditor->toPlainText(); + // Get the new tokens from the text editor + QString tokenText = textEditor->toPlainText(); qDebug() << "Tokens: " << tokenText; - // parse and update the tree visualiser - // TODO - } + // Tokens are expected in the format: type, value + QStringList lines = tokenText.split("\n", Qt::SkipEmptyParts); // Skip empty lines + tokensList.clear(); // Clear the existing tokens list + + // Loop over each line and process tokens + for (const QString& line : lines) { + QStringList tokenParts = line.split(",", Qt::SkipEmptyParts); + if (tokenParts.size() != 2) { + qDebug() << "Invalid token format: " << line; + continue; // Skip invalid lines + } + + QString type = tokenParts[0].trimmed(); + QString value = tokenParts[1].trimmed(); + Tiny::Data::Token token(Tiny::Data::Token::TokenType::ASSIGN, value, 0, 0); // Line and column default to 0 + token.setType(type); + tokensList.append(token); + } + // Parse the tokens + if (tokensList.isEmpty()) { + this->treeVisualiser->setRoot(nullptr); // Clear the tree if no tokens + return; + } + + this->parser->setTokens(tokensList); + Node* root = nullptr; + + try { + this->parseErrors.clear(); // Clear any previous parse errors + root = this->parser->parse(); + } catch (const std::exception& e) { + qDebug() << "Parser exception: " << e.what(); + } catch (...) { + qDebug() << "An unknown exception occurred during parsing."; + } + + // Handle parse errors + for (const auto& error : this->parseErrors) { + qDebug() << "Parse error:" << error.first.getValue() << error.second; + this->textEditor->markParseError(error.first.getLine(), error.first.getColumn(), error.first.getValue().length(), error.second); + } + + // Update the tree visualizer + this->treeVisualiser->setRoot(root ? root : nullptr); + } // text mode else { // get the new text @@ -175,12 +216,12 @@ void TabContent::textChanged() processReservedTokens(); if(!hasUnknown){ - static int counter; // parse this->parser->setTokens(tokensList); Node* root = nullptr; try { - qDebug() << "Parsing..." + QString::number(counter++); + // reset errors list + this->parseErrors.clear(); root = this->parser->parse(); } catch (const std::exception& e) { // qDebug() << e.what(); @@ -188,6 +229,13 @@ void TabContent::textChanged() qDebug() << "An unknown exception occurred"; } + // parse errors + for (const auto& error : this->parseErrors) { + qDebug() << error.first.getValue() << " " << error.second; + this->textEditor->markParseError(error.first.getLine(), error.first.getColumn(), error.first.getValue().length(), error.second); + } + + // update the tree visualiser if (root != nullptr) { this->treeVisualiser->setRoot(root); diff --git a/parser_gui/Widgets/src/TextEditor.cpp b/parser_gui/Widgets/src/TextEditor.cpp index eead9ba..8ceac52 100644 --- a/parser_gui/Widgets/src/TextEditor.cpp +++ b/parser_gui/Widgets/src/TextEditor.cpp @@ -102,6 +102,44 @@ void TextEditor::resetFormat() this->blockSignals(false); } +void TextEditor::markParseError(int line, int column, int charCount, QString message) +{ + this->blockSignals(true); + + // Create a QTextCursor to navigate the document + QTextCursor cursor = this->textCursor(); + + // Get the block (line) based on the 1-based line number + QTextBlock block = this->document()->findBlockByLineNumber(line - 1); + if (!block.isValid()) { + qWarning() << "Invalid line number:" << line; + return; + } + + // Calculate the position in the block (column is 1-based) + int position = block.position() + (column - 1); + + // Set the cursor to the starting position + cursor.setPosition(position); + + // Select the characters to mark by moving the cursor to the end of the selection range (left) + cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, charCount); + + // Create the format for marking + QTextCharFormat format; + format.setForeground(QColor("orange") ); // Text color: orange + format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); // Zigzag underline + format.setToolTip(message); + + // Apply the format to the selected text only + cursor.setCharFormat(format); + + // Clear the selection to avoid further changes + cursor.clearSelection(); + + this->blockSignals(false); // Re-enable signals after formatting +} + void TextEditor::initStyle() { // set the style diff --git a/parser_gui/Widgets/src/TreeVisualiser.cpp b/parser_gui/Widgets/src/TreeVisualiser.cpp index 7dcfcb4..d1cdf49 100644 --- a/parser_gui/Widgets/src/TreeVisualiser.cpp +++ b/parser_gui/Widgets/src/TreeVisualiser.cpp @@ -3,6 +3,7 @@ #include using Tiny::Widgets::TreeVisualiser; +using Tiny::Data::Node; void TreeVisualiser::paintEvent(QPaintEvent *event) { Q_UNUSED(event); @@ -25,7 +26,7 @@ TreeVisualiser::TreeVisualiser(QWidget *parent) : QWidget(parent) { } -void TreeVisualiser::drawTree(QPainter *painter, Node *node) +void TreeVisualiser::drawTree(QPainter* painter, Node *node) { if (!node) return; @@ -56,10 +57,13 @@ void TreeVisualiser::drawTree(QPainter *painter, Node *node) painter->setPen(QPen(Qt::black, 2)); for (Node* child : node->getChildren()) { QPoint childPos = positions[child]; - painter->drawLine(pos.x(), pos.y() + 20, childPos.x(), childPos.y() - 20); + if (child->getLevel() == node->getLevel()) { + painter->drawLine(pos.x() + 40, pos.y(), childPos.x(), childPos.y()); + } else { + painter->drawLine(pos.x(), pos.y() + 20, childPos.x(), childPos.y() - 20); + } drawTree(painter, child); // Recursively draw children } - } void TreeVisualiser::drawTree(QPainter *painter, Node *node, int x, int y, int availableWidth, int level) {