From a25d916b19f9c14fbe47ededa243a59559f83bcb Mon Sep 17 00:00:00 2001 From: Clement Doucy Date: Sun, 29 Oct 2023 18:11:56 +0100 Subject: [PATCH] Added break and continue --- inc/ast/nodes/BreakNode.hpp | 17 +++++++ inc/ast/nodes/ContinueNode.hpp | 17 +++++++ inc/ast/visitors/EvalVisitor.hpp | 7 ++- inc/ast/visitors/IVisitor.hpp | 6 +++ inc/parser/Parser.hpp | 2 + inc/runtime/Break.hpp | 12 +++++ inc/runtime/Continue.hpp | 12 +++++ inc/runtime/Jump.hpp | 18 +++++++ inc/runtime/Return.hpp | 4 +- main.cpp | 2 - src/ast/nodes/BreakNode.cpp | 11 ++++ src/ast/nodes/ContinueNode.cpp | 11 ++++ src/ast/visitors/EvalVisitor.cpp | 54 +++++++++++++++----- src/evaluator/Evaluator.cpp | 12 ++++- src/lexer/lexProgrammingWord.cpp | 4 +- src/parser/Parser.cpp | 34 ++++++++++++- src/runtime/Break.cpp | 5 ++ src/runtime/Continue.cpp | 5 ++ src/runtime/Jump.cpp | 12 +++++ src/runtime/Return.cpp | 3 +- src/src.cmake | 5 ++ tst/evaluator/statements_test.cpp | 85 +++++++++++++++++++++++++++++++ 22 files changed, 314 insertions(+), 24 deletions(-) create mode 100644 inc/ast/nodes/BreakNode.hpp create mode 100644 inc/ast/nodes/ContinueNode.hpp create mode 100644 inc/runtime/Break.hpp create mode 100644 inc/runtime/Continue.hpp create mode 100644 inc/runtime/Jump.hpp create mode 100644 src/ast/nodes/BreakNode.cpp create mode 100644 src/ast/nodes/ContinueNode.cpp create mode 100644 src/runtime/Break.cpp create mode 100644 src/runtime/Continue.cpp create mode 100644 src/runtime/Jump.cpp diff --git a/inc/ast/nodes/BreakNode.hpp b/inc/ast/nodes/BreakNode.hpp new file mode 100644 index 0000000..510e1ca --- /dev/null +++ b/inc/ast/nodes/BreakNode.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "StatementNode.hpp" + +namespace ast +{ + class BreakNode final : public StatementNode + { + public: + using ptr = std::shared_ptr; + static ptr create(); + BreakNode() = default; + ~BreakNode() final = default; + + void accept(IVisitor &visitor) final; + }; +} \ No newline at end of file diff --git a/inc/ast/nodes/ContinueNode.hpp b/inc/ast/nodes/ContinueNode.hpp new file mode 100644 index 0000000..7fb2fa7 --- /dev/null +++ b/inc/ast/nodes/ContinueNode.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "StatementNode.hpp" + +namespace ast +{ + class ContinueNode final : public StatementNode + { + public: + using ptr = std::shared_ptr; + static ptr create(); + ContinueNode() = default; + ~ContinueNode() final = default; + + void accept(IVisitor &visitor) final; + }; +} \ No newline at end of file diff --git a/inc/ast/visitors/EvalVisitor.hpp b/inc/ast/visitors/EvalVisitor.hpp index 0713552..73a2287 100644 --- a/inc/ast/visitors/EvalVisitor.hpp +++ b/inc/ast/visitors/EvalVisitor.hpp @@ -25,6 +25,9 @@ #include "ReturnNode.hpp" #include "CallNode.hpp" +#include "BreakNode.hpp" +#include "ContinueNode.hpp" + #include "ProgramNode.hpp" #include "State.hpp" @@ -59,6 +62,9 @@ namespace ast void visit(CallNode &node) final; void visit(ReturnNode &node) final; + void visit(BreakNode &node) final; + void visit(ContinueNode &node) final; + void visit(ProgramNode &node) final; [[nodiscard]] const runtime::Object &value() const noexcept; @@ -73,7 +79,6 @@ namespace ast runtime::Object _expressionResult; runtime::State::ptr _globalState; runtime::State::ptr _localState; - bool _isExecutingCall; const runtime::Object &evaluate(const ast::ExpressionNode::ptr &expr); diff --git a/inc/ast/visitors/IVisitor.hpp b/inc/ast/visitors/IVisitor.hpp index 0fb7b1b..aed7d5b 100644 --- a/inc/ast/visitors/IVisitor.hpp +++ b/inc/ast/visitors/IVisitor.hpp @@ -24,6 +24,9 @@ namespace ast class CallNode; class ReturnNode; + class BreakNode; + class ContinueNode; + class ProgramNode; class IVisitor @@ -52,6 +55,9 @@ namespace ast virtual void visit(CallNode &node) = 0; virtual void visit(ReturnNode &node) = 0; + virtual void visit(BreakNode &node) = 0; + virtual void visit(ContinueNode &node) = 0; + virtual void visit(ProgramNode &node) = 0; }; } \ No newline at end of file diff --git a/inc/parser/Parser.hpp b/inc/parser/Parser.hpp index f014c08..2dc0440 100644 --- a/inc/parser/Parser.hpp +++ b/inc/parser/Parser.hpp @@ -28,6 +28,8 @@ class Parser ast::StatementNode::ptr parseAssignmentStatement(); ast::StatementNode::ptr parseIncrementStatement(); ast::StatementNode::ptr parseReturnStatement(); + ast::StatementNode::ptr parseBreakStatement(); + ast::StatementNode::ptr parseContinueStatement(); ast::DeclarationNode::ptr parseDeclaration(); ast::AssignmentNode::ptr parseAssignment(); ast::IncrementNode::ptr parseIncrement(); diff --git a/inc/runtime/Break.hpp b/inc/runtime/Break.hpp new file mode 100644 index 0000000..c99f57e --- /dev/null +++ b/inc/runtime/Break.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Jump.hpp" + +namespace runtime +{ + class Break : public Jump + { + public: + Break(); + }; +}; \ No newline at end of file diff --git a/inc/runtime/Continue.hpp b/inc/runtime/Continue.hpp new file mode 100644 index 0000000..c56fcc6 --- /dev/null +++ b/inc/runtime/Continue.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Jump.hpp" + +namespace runtime +{ + class Continue : public Jump + { + public: + Continue(); + }; +}; \ No newline at end of file diff --git a/inc/runtime/Jump.hpp b/inc/runtime/Jump.hpp new file mode 100644 index 0000000..00ed196 --- /dev/null +++ b/inc/runtime/Jump.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace runtime +{ + class Jump : public std::exception + { + public: + Jump(const std::string &jumpStatement, const std::string &appropriatePlace) noexcept; + + [[nodiscard]] const char *what() const noexcept override; + + private: + std::string _what; + }; +}; \ No newline at end of file diff --git a/inc/runtime/Return.hpp b/inc/runtime/Return.hpp index 8c3589f..6cbfbac 100644 --- a/inc/runtime/Return.hpp +++ b/inc/runtime/Return.hpp @@ -1,13 +1,13 @@ #pragma once -#include #include +#include "Jump.hpp" #include "Object.hpp" namespace runtime { - class Return : public std::exception + class Return : public Jump { public: explicit Return(std::optional object) noexcept; diff --git a/main.cpp b/main.cpp index e1f3dd6..a436d4a 100644 --- a/main.cpp +++ b/main.cpp @@ -19,8 +19,6 @@ void runFileMode(const std::string &filePath) int main(int ac, char **av) { - for (int i = 0; i < 10; i++) continue; - try { if (ac < 2) runInteractiveMode(); diff --git a/src/ast/nodes/BreakNode.cpp b/src/ast/nodes/BreakNode.cpp new file mode 100644 index 0000000..57ac112 --- /dev/null +++ b/src/ast/nodes/BreakNode.cpp @@ -0,0 +1,11 @@ +#include "BreakNode.hpp" + +ast::BreakNode::ptr ast::BreakNode::create() +{ + return std::make_shared(); +} + +void ast::BreakNode::accept(ast::IVisitor &visitor) +{ + visitor.visit(*this); +} diff --git a/src/ast/nodes/ContinueNode.cpp b/src/ast/nodes/ContinueNode.cpp new file mode 100644 index 0000000..75b19d0 --- /dev/null +++ b/src/ast/nodes/ContinueNode.cpp @@ -0,0 +1,11 @@ +#include "ContinueNode.hpp" + +ast::ContinueNode::ptr ast::ContinueNode::create() +{ + return std::make_shared(); +} + +void ast::ContinueNode::accept(ast::IVisitor &visitor) +{ + visitor.visit(*this); +} diff --git a/src/ast/visitors/EvalVisitor.cpp b/src/ast/visitors/EvalVisitor.cpp index 2b20323..2bd834d 100644 --- a/src/ast/visitors/EvalVisitor.cpp +++ b/src/ast/visitors/EvalVisitor.cpp @@ -2,6 +2,8 @@ #include "EvalVisitor.hpp" #include "Return.hpp" +#include "Break.hpp" +#include "Continue.hpp" #define OPERATOR_FUNCTION(type, op) {type, [](const auto &l, const auto &r){return l op r;}} @@ -154,8 +156,13 @@ void ast::EvalVisitor::visit(ast::BlockNode &node) { this->_localState = runtime::State::create(this->_localState); - for (const auto &stmt : node.getStatements()) { - stmt->accept(*this); + try { + for (const auto &stmt: node.getStatements()) { + stmt->accept(*this); + } + } catch (const LogicalError &err) { + this->_localState = this->_localState->restoreParent(); + throw err; } this->_localState = this->_localState->restoreParent(); @@ -205,8 +212,13 @@ void ast::EvalVisitor::visit(ast::WhileNode &node) auto obj = this->evaluate(node.getExpression()); while (obj) { - if (stmt) - stmt->accept(*this); + try { + if (stmt) + stmt->accept(*this); + } catch (const runtime::Break &_) { + break; + } catch (const runtime::Continue &_) { + } obj = this->evaluate(node.getExpression()); } @@ -237,8 +249,14 @@ void ast::EvalVisitor::visit(ast::ForNode &node) } while (this->evaluate(expr)) { - if (stmt) - stmt->accept(*this); + + try { + if (stmt) + stmt->accept(*this); + } catch (const runtime::Break &_) { + break; + } catch (const runtime::Continue &_) { + } if (step) step->accept(*this); @@ -302,13 +320,10 @@ void ast::EvalVisitor::visit(ast::CallNode &node) auto originalState = std::move(this->_localState); this->_localState = state; bool hasReturned = false; - bool previousExecCall = this->_isExecutingCall; - this->_isExecutingCall = true; try { function.getBlock()->accept(*this); } catch (const runtime::Return &ret) { - this->_isExecutingCall = previousExecCall; this->_localState = std::move(originalState); originalState = nullptr; @@ -324,10 +339,12 @@ void ast::EvalVisitor::visit(ast::CallNode &node) } else if (function.getReturnType() != Token::VOID_TYPE) throw invalidReturnType(Token::Type::VOID_TYPE, function.getReturnType()); + } catch (const LogicalError &err) { + this->_localState = std::move(originalState); + originalState = nullptr; + throw err; } - this->_isExecutingCall = previousExecCall; - if (!hasReturned && function.getReturnType() != Token::VOID_TYPE) throw missingReturn(function.getReturnType()); @@ -337,9 +354,6 @@ void ast::EvalVisitor::visit(ast::CallNode &node) void ast::EvalVisitor::visit(ast::ReturnNode &node) { - if (!this->_isExecutingCall) - throw LogicalError("return statement used outside of a function"); - std::optional returnedObject; if (node.getExpression()) @@ -372,3 +386,15 @@ LogicalError ast::EvalVisitor::missingReturn(Token::Type actualType) Token::typeToString(actualType) )); } + +void ast::EvalVisitor::visit(ast::BreakNode &_) +{ + (void)_; + throw runtime::Break(); +} + +void ast::EvalVisitor::visit(ast::ContinueNode &_) +{ + (void)_; + throw runtime::Continue(); +} diff --git a/src/evaluator/Evaluator.cpp b/src/evaluator/Evaluator.cpp index 8a7b9b3..18e52f0 100644 --- a/src/evaluator/Evaluator.cpp +++ b/src/evaluator/Evaluator.cpp @@ -1,4 +1,6 @@ #include "Evaluator.hpp" +#include "Break.hpp" +#include "Continue.hpp" void Evaluator::feed(const std::string &expression) { @@ -6,8 +8,14 @@ void Evaluator::feed(const std::string &expression) auto root = this->_parser.getAstRoot(); - if (root) - root->accept(this->_evalVisitor); + if (root) { + + try { + root->accept(this->_evalVisitor); + } catch (const runtime::Jump &jump) { + throw LogicalError(jump.what()); + } + } this->_parser.clear(); } diff --git a/src/lexer/lexProgrammingWord.cpp b/src/lexer/lexProgrammingWord.cpp index 863112e..a3b9529 100644 --- a/src/lexer/lexProgrammingWord.cpp +++ b/src/lexer/lexProgrammingWord.cpp @@ -10,7 +10,9 @@ static const std::unordered_map keywordsTable{ {"else", Token::ELSE}, {"for", Token::FOR}, {"fnc", Token::FNC}, - {"return", Token::RETURN} + {"return", Token::RETURN}, + {"break", Token::BREAK}, + {"continue", Token::CONTINUE} }; bool Lexer::lexProgrammingWord(std::string::const_iterator &begin) diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp index c07de0f..9c576c6 100644 --- a/src/parser/Parser.cpp +++ b/src/parser/Parser.cpp @@ -41,7 +41,9 @@ ast::StatementNode::ptr Parser::parseStatement() &Parser::parseWhile, &Parser::parseConditions, &Parser::parseFor, - &Parser::parseReturnStatement + &Parser::parseReturnStatement, + &Parser::parseBreakStatement, + &Parser::parseContinueStatement }; for (const auto &parse: statementsParser) { @@ -138,6 +140,36 @@ ast::StatementNode::ptr Parser::parseReturnStatement() return stmt; } +ast::StatementNode::ptr Parser::parseBreakStatement() +{ + auto token = this->_tokenItr.get(); + if (!token || !token->isType(Token::BREAK)) + return nullptr; + this->_tokenItr.advance(); + + token = this->_tokenItr.get(); + if (!token || !token->isType(Token::SEMICOLON)) + throw syntaxError("expecting \";\"", *this->_tokenItr.prev()); + this->_tokenItr.advance(); + + return ast::BreakNode::create(); +} + +ast::StatementNode::ptr Parser::parseContinueStatement() +{ + auto token = this->_tokenItr.get(); + if (!token || !token->isType(Token::CONTINUE)) + return nullptr; + this->_tokenItr.advance(); + + token = this->_tokenItr.get(); + if (!token || !token->isType(Token::SEMICOLON)) + throw syntaxError("expecting \";\"", *this->_tokenItr.prev()); + this->_tokenItr.advance(); + + return ast::ContinueNode::create(); +} + ast::IncrementNode::ptr Parser::parseIncrement() { auto token = this->_tokenItr.get(); diff --git a/src/runtime/Break.cpp b/src/runtime/Break.cpp new file mode 100644 index 0000000..8714993 --- /dev/null +++ b/src/runtime/Break.cpp @@ -0,0 +1,5 @@ +#include "Break.hpp" + +runtime::Break::Break() +: Jump("break", "loop") +{} diff --git a/src/runtime/Continue.cpp b/src/runtime/Continue.cpp new file mode 100644 index 0000000..cd405f6 --- /dev/null +++ b/src/runtime/Continue.cpp @@ -0,0 +1,5 @@ +#include "Continue.hpp" + +runtime::Continue::Continue() +: Jump("continue", "loop") +{} diff --git a/src/runtime/Jump.cpp b/src/runtime/Jump.cpp new file mode 100644 index 0000000..de87f77 --- /dev/null +++ b/src/runtime/Jump.cpp @@ -0,0 +1,12 @@ +#include + +#include "Jump.hpp" + +runtime::Jump::Jump(const std::string &jumpStatement, const std::string &appropriatePlace) noexcept +: _what(fmt::format("use of {} outside of {}", jumpStatement, appropriatePlace)) +{} + +const char *runtime::Jump::what() const noexcept +{ + return this->_what.c_str(); +} diff --git a/src/runtime/Return.cpp b/src/runtime/Return.cpp index e82229b..eb6f663 100644 --- a/src/runtime/Return.cpp +++ b/src/runtime/Return.cpp @@ -3,7 +3,8 @@ runtime::Return::Return(std::optional object) noexcept -: _object(std::move(object)) +: Jump("return", "function"), + _object(std::move(object)) {} bool runtime::Return::hasValue() const noexcept diff --git a/src/src.cmake b/src/src.cmake index 26a9f13..d9d213d 100644 --- a/src/src.cmake +++ b/src/src.cmake @@ -39,12 +39,17 @@ set(SOURCES ${PROJECT_ROOT}/src/ast/nodes/FunctionNode.cpp ${PROJECT_ROOT}/src/ast/nodes/CallNode.cpp ${PROJECT_ROOT}/src/ast/nodes/ReturnNode.cpp + ${PROJECT_ROOT}/src/ast/nodes/BreakNode.cpp + ${PROJECT_ROOT}/src/ast/nodes/ContinueNode.cpp ${PROJECT_ROOT}/src/ast/visitors/EvalVisitor.cpp ${PROJECT_ROOT}/src/runtime/Object.cpp ${PROJECT_ROOT}/src/runtime/object_operators.cpp ${PROJECT_ROOT}/src/runtime/State.cpp + ${PROJECT_ROOT}/src/runtime/Jump.cpp + ${PROJECT_ROOT}/src/runtime/Break.cpp + ${PROJECT_ROOT}/src/runtime/Continue.cpp ${PROJECT_ROOT}/src/runtime/Return.cpp ${PROJECT_ROOT}/src/repl/repl.cpp diff --git a/tst/evaluator/statements_test.cpp b/tst/evaluator/statements_test.cpp index 9031813..5ab9a8c 100644 --- a/tst/evaluator/statements_test.cpp +++ b/tst/evaluator/statements_test.cpp @@ -573,3 +573,88 @@ TEST(StatementTest, ReturnOutsideOfFunction) testStatements(testCases); } + +TEST(StatementTest, BreakAndContinue) +{ + std::vector testCases{ + StatementTest{ + .description = "1. break outside of a loop", + .program = "break;", + .shouldThrow = true, + }, + StatementTest{ + .description = "2. continue outside of a loop", + .program = "continue;", + .shouldThrow = true, + }, + StatementTest{ + .description = "3. break in for loop", + .program = "{ \n" + " for (int i = 0; i < 10; i++) { \n" + " print(i); \n" + " break; \n" + " } \n" + "} \n", + .expectedOutput = "0\n" + }, + StatementTest{ + .description = "4. continue in for loop", + .program = "{" + " for (int i = 0; i < 4; i++) {" + " if (i % 2 == 0)" + " continue;" + " print(i);" + " }" + "}", + .expectedOutput = "1\n3\n" + }, + StatementTest{ + .description = "5. break in while loop", + .program = "{" + " int i = 0;" + " while (1) {" + " if (i == 1)" + " break;" + " print(i);" + " i++;" + " }" + "}", + .expectedOutput = "0\n" + }, + StatementTest{ + .description = "6. continue and break mixed in while loop", + .program = "{" + " int i = 0;" + " while (1) {" + " if (i != 10) {" + " i++;" + " continue;" + " }" + " print(i);" + " break;" + " }" + "}", + .expectedOutput = "10\n" + }, + StatementTest{ + .description = "7. For loop continue no block", + .program = "{" + " int i = 0;" + " for (; i < 10; i++) continue;" + " print(i);" + "}", + .expectedOutput = "10\n" + }, + StatementTest{ + .description = "8. For loop break no block", + .program = "{" + " int i = 0;" + " for (; i < 10; i++) break;" + " print(i);" + "}", + .expectedOutput = "0\n" + } + }; + + testStatements(testCases); +}