From 9c5a3397d9f254030cb5d8f0f266e4b469917371 Mon Sep 17 00:00:00 2001 From: zekexiao Date: Fri, 16 Jan 2026 00:01:23 +0800 Subject: [PATCH] Add break continue --- example.chun | 8 +++++ src/ast.h | 8 +++++ src/interpreter.cpp | 33 +++++++++++++++++++-- src/interpreter.h | 5 ++++ src/lexer.cpp | 2 ++ src/lexer.h | 2 +- src/parser.cpp | 12 ++++++++ src/parser.h | 2 ++ tests/test_basic.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 137 insertions(+), 4 deletions(-) diff --git a/example.chun b/example.chun index c79cef8..0af1ef0 100644 --- a/example.chun +++ b/example.chun @@ -34,6 +34,14 @@ print("List:", numbers); print("First element (index 0):", numbers[0]); print("Third element (index 2):", numbers[2]); +for(number i = 0; i < len(numbers); i = i + 1) { + print("List element", numbers[i]); +} + +while(true) { + print("test break"); + break; +} // Test maps print("\n=== Map Demo ==="); map config = {"host": "localhost", "port": "8080"}; diff --git a/src/ast.h b/src/ast.h index 81d9d49..80b67fe 100644 --- a/src/ast.h +++ b/src/ast.h @@ -183,6 +183,14 @@ struct ReturnStmt : public Stmt { explicit ReturnStmt(ExprPtr value = nullptr) : value(std::move(value)) {} }; +struct BreakStmt : public Stmt { + BreakStmt() = default; +}; + +struct ContinueStmt : public Stmt { + ContinueStmt() = default; +}; + struct FunctionDecl : public Stmt { std::string name; std::vector> parameters; // (type, name) diff --git a/src/interpreter.cpp b/src/interpreter.cpp index 0caad4c..3a117b2 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -106,6 +106,10 @@ void Interpreter::execute_statement(const Stmt& stmt) { exec_for(*for_stmt); } else if (auto* return_stmt = dynamic_cast(&stmt)) { exec_return(*return_stmt); + } else if (auto* break_stmt = dynamic_cast(&stmt)) { + exec_break(*break_stmt); + } else if (auto* continue_stmt = dynamic_cast(&stmt)) { + exec_continue(*continue_stmt); } else if (auto* func_decl = dynamic_cast(&stmt)) { exec_function_decl(*func_decl); } else if (auto* class_decl = dynamic_cast(&stmt)) { @@ -397,8 +401,17 @@ void Interpreter::exec_if(const IfStmt& stmt) { } void Interpreter::exec_while(const WhileStmt& stmt) { - while (is_truthy(evaluate(*stmt.condition))) { - execute_statement(*stmt.body); + try { + while (is_truthy(evaluate(*stmt.condition))) { + try { + execute_statement(*stmt.body); + } catch (const ContinueException&) { + // Continue: just proceed to the next iteration (check condition again) + continue; + } + } + } catch (const BreakException&) { + // Break: exit the loop } } @@ -412,12 +425,18 @@ void Interpreter::exec_for(const ForStmt& stmt) { } while (!stmt.condition || is_truthy(evaluate(*stmt.condition))) { - execute_statement(*stmt.body); + try { + execute_statement(*stmt.body); + } catch (const ContinueException&) { + // Continue: proceed to increment + } if (stmt.increment) { evaluate(*stmt.increment); } } + } catch (const BreakException&) { + // Break: exit the loop } catch (...) { environment = previous; throw; @@ -431,6 +450,14 @@ void Interpreter::exec_return(const ReturnStmt& stmt) { throw ReturnException(value); } +void Interpreter::exec_break(const BreakStmt& stmt) { + throw BreakException(); +} + +void Interpreter::exec_continue(const ContinueStmt& stmt) { + throw ContinueException(); +} + void Interpreter::exec_function_decl(const FunctionDecl& stmt) { auto func_decl = std::make_shared(stmt); auto func = std::make_shared(stmt.name, func_decl); diff --git a/src/interpreter.h b/src/interpreter.h index 35beaaf..c0fa6ce 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -20,6 +20,9 @@ public: explicit ReturnException(ValuePtr value) : value(std::move(value)) {} }; +class BreakException : public std::exception {}; +class ContinueException : public std::exception {}; + class Environment { public: std::shared_ptr parent; @@ -138,6 +141,8 @@ private: void exec_while(const WhileStmt& stmt); void exec_for(const ForStmt& stmt); void exec_return(const ReturnStmt& stmt); + void exec_break(const BreakStmt& stmt); + void exec_continue(const ContinueStmt& stmt); void exec_function_decl(const FunctionDecl& stmt); void exec_class_decl(const ClassDecl& stmt); diff --git a/src/lexer.cpp b/src/lexer.cpp index 6b62f68..07eedce 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -224,6 +224,8 @@ TokenType Lexer::get_keyword_type(const std::string& text) const { {"and", TokenType::AND}, {"or", TokenType::OR}, {"this", TokenType::THIS}, + {"continue", TokenType::CONTINUE}, + {"break", TokenType::BREAK}, }; auto it = keywords.find(text); diff --git a/src/lexer.h b/src/lexer.h index 9fa8811..f4709e3 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -12,7 +12,7 @@ enum class TokenType { // Keywords CLASS, FUNC, NUMBER, STRING, BOOL, LIST, MAP, IF, ELSE, WHILE, FOR, RETURN, VAR, - TRUE, FALSE, NIL, THIS, + TRUE, FALSE, NIL, THIS, CONTINUE, BREAK, // Operators PLUS, MINUS, STAR, SLASH, PERCENT, diff --git a/src/parser.cpp b/src/parser.cpp index 35b4f9c..b7c74b8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -179,6 +179,8 @@ StmtPtr Parser::statement() { if (match({TokenType::WHILE})) return while_statement(); if (match({TokenType::FOR})) return for_statement(); if (match({TokenType::RETURN})) return return_statement(); + if (match({TokenType::BREAK})) return break_statement(); + if (match({TokenType::CONTINUE})) return continue_statement(); if (match({TokenType::LEFT_BRACE})) return block_statement(); return expression_statement(); @@ -247,6 +249,16 @@ StmtPtr Parser::return_statement() { return std::make_unique(std::move(value)); } +StmtPtr Parser::break_statement() { + consume(TokenType::SEMICOLON, "Expected ';' after 'break'."); + return std::make_unique(); +} + +StmtPtr Parser::continue_statement() { + consume(TokenType::SEMICOLON, "Expected ';' after 'continue'."); + return std::make_unique(); +} + StmtPtr Parser::block_statement() { std::vector statements; diff --git a/src/parser.h b/src/parser.h index d5b8dae..dffc1d2 100644 --- a/src/parser.h +++ b/src/parser.h @@ -44,6 +44,8 @@ private: StmtPtr while_statement(); StmtPtr for_statement(); StmtPtr return_statement(); + StmtPtr break_statement(); + StmtPtr continue_statement(); StmtPtr block_statement(); StmtPtr expression_statement(); diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp index d9e5918..ff7fa18 100644 --- a/tests/test_basic.cpp +++ b/tests/test_basic.cpp @@ -148,3 +148,72 @@ TEST_CASE("interpreter performance: simple loop", "[perf][script]") { REQUIRE(r_num->value == 499500.0); }; } + +TEST_CASE("loop break", "[script][loop]") { + State state; + const char* script = R"( + number sum = 0; + for (number i = 0; i < 10; i = i + 1) { + if (i == 5) { + break; + } + sum = sum + i; + } + )"; + + REQUIRE(state.do_string(script)); + auto sum_val = state.get_global("sum"); + REQUIRE(sum_val); + auto sum_num = std::dynamic_pointer_cast(sum_val); + REQUIRE(sum_num->value == 10.0); // 0+1+2+3+4 = 10 +} + +TEST_CASE("loop continue", "[script][loop]") { + State state; + const char* script = R"( + number sum = 0; + for (number i = 0; i < 5; i = i + 1) { + if (i == 2) { + continue; + } + sum = sum + i; + } + )"; + + REQUIRE(state.do_string(script)); + auto sum_val = state.get_global("sum"); + REQUIRE(sum_val); + auto sum_num = std::dynamic_pointer_cast(sum_val); + REQUIRE(sum_num->value == 8.0); // 0+1+3+4 = 8 +} + +TEST_CASE("while break and continue", "[script][loop]") { + State state; + const char* script = R"( + number i = 0; + number sum = 0; + while (i < 10) { + i = i + 1; + if (i == 3) { + continue; + } + if (i == 6) { + break; + } + sum = sum + i; + } + )"; + + REQUIRE(state.do_string(script)); + auto sum_val = state.get_global("sum"); + REQUIRE(sum_val); + auto sum_num = std::dynamic_pointer_cast(sum_val); + REQUIRE(sum_num->value == 12.0); + // 1st iter: i=1, sum=1 + // 2nd iter: i=2, sum=1+2=3 + // 3rd iter: i=3, continue + // 4th iter: i=4, sum=3+4=7 + // 5th iter: i=5, sum=7+5=12 + // 6th iter: i=6, break + // Result should be 12.0 +}