From 014803b7050736d7fd0e5e5d65415ea080fc67fa Mon Sep 17 00:00:00 2001 From: PuqiAR Date: Sun, 21 Dec 2025 22:55:46 +0800 Subject: [PATCH] feat: implement for-loops with proper scope management - Add support for C-style for loops: for (init; condition; increment) { body } - Implement three-level scoping: loop context + per-iteration contexts - Add semicolon disabler for increment statements using RAII guards - Support break/continue/return control flow with scope validation - Fix semicolon handling in parser for flexible for-loop syntax --- include/Ast/ForSt.hpp | 30 ++++++++ include/Ast/astBase.hpp | 1 + include/ast.hpp | 1 + include/context.hpp | 31 +++++++- include/context_forward.hpp | 2 +- include/evaluator.hpp | 1 + include/lexer.hpp | 1 - include/parser.hpp | 69 ++++++++++++++++- include/token.hpp | 6 +- src/evaluator.cpp | 143 ++++++++++++++++++++++++++++-------- src/lexer.cpp | 2 + src/parser.cpp | 94 ++++++++++++++++++++---- test.fig | 5 +- 13 files changed, 327 insertions(+), 59 deletions(-) create mode 100644 include/Ast/ForSt.hpp diff --git a/include/Ast/ForSt.hpp b/include/Ast/ForSt.hpp new file mode 100644 index 0000000..77bc0ba --- /dev/null +++ b/include/Ast/ForSt.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class ForSt final : public StatementAst + { + public: + Statement initSt; + Expression condition; + Statement incrementSt; + BlockStatement body; + + ForSt() + { + type = AstType::ForSt; + } + ForSt(Statement _initSt, Expression _condition, Statement _incrementSt, BlockStatement _body) : + initSt(std::move(_initSt)), + condition(std::move(_condition)), + incrementSt(std::move(_incrementSt)), + body(std::move(_body)) + { + type = AstType::ForSt; + } + }; + + using For = std::shared_ptr; +}; \ No newline at end of file diff --git a/include/Ast/astBase.hpp b/include/Ast/astBase.hpp index 42e6448..d7ba424 100644 --- a/include/Ast/astBase.hpp +++ b/include/Ast/astBase.hpp @@ -47,6 +47,7 @@ namespace Fig::Ast VarAssignSt, WhileSt, + ForSt, ReturnSt, BreakSt, ContinueSt, diff --git a/include/ast.hpp b/include/ast.hpp index 0ae6cc8..93d29a2 100644 --- a/include/ast.hpp +++ b/include/ast.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/include/context.hpp b/include/context.hpp index e785601..fab559d 100644 --- a/include/context.hpp +++ b/include/context.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -10,7 +10,7 @@ namespace Fig { - struct Context + class Context : public std::enable_shared_from_this { private: FString scopeName; @@ -145,6 +145,33 @@ namespace Fig } throw RuntimeError(FStringView(std::format("Variable '{}' not defined", name.toBasicString()))); } + bool isInFunctionContext() + { + ContextPtr ctx = shared_from_this(); + while (ctx) + { + if (ctx->getScopeName().find(u8"parent; + } + return false; + } + bool isInLoopContext() + { + ContextPtr ctx = shared_from_this(); + while (ctx) + { + if (ctx->getScopeName().find(u8"getScopeName().find(u8"parent; + } + return false; + } void printStackTrace(std::ostream &os = std::cerr, int indent = 0) const { const Context *ctx = this; diff --git a/include/context_forward.hpp b/include/context_forward.hpp index 143026b..a67420f 100644 --- a/include/context_forward.hpp +++ b/include/context_forward.hpp @@ -4,6 +4,6 @@ namespace Fig { - struct Context; + class Context; using ContextPtr = std::shared_ptr; }; \ No newline at end of file diff --git a/include/evaluator.hpp b/include/evaluator.hpp index af27a4d..c07ff5a 100644 --- a/include/evaluator.hpp +++ b/include/evaluator.hpp @@ -111,6 +111,7 @@ namespace Fig Value evalBinary(const Ast::BinaryExpr &); Value evalUnary(const Ast::UnaryExpr &); + StatementResult evalBlockStatement(const Ast::BlockStatement &, ContextPtr = nullptr); StatementResult evalStatement(const Ast::Statement &); Value evalFunctionCall(const Function &, const Ast::FunctionArguments &, FString fnName = u8""); diff --git a/include/lexer.hpp b/include/lexer.hpp index a1f7d1e..6d656fc 100644 --- a/include/lexer.hpp +++ b/include/lexer.hpp @@ -81,7 +81,6 @@ namespace Fig { return warnings; } - Token nextToken(); Token scanNumber(); diff --git a/include/parser.hpp b/include/parser.hpp index 666f40f..700b0ff 100644 --- a/include/parser.hpp +++ b/include/parser.hpp @@ -29,6 +29,28 @@ namespace Fig std::stack exprStack; + bool needSemicolon = true; + + class SemicolonDisabler + { + Parser &p; + bool original; + + public: + SemicolonDisabler(Parser &parser) : + p(parser), original(p.needSemicolon) + { + p.needSemicolon = false; + } + ~SemicolonDisabler() + { + p.needSemicolon = original; + } + // disable copy and assign + SemicolonDisabler(const SemicolonDisabler &) = delete; + SemicolonDisabler &operator=(const SemicolonDisabler &) = delete; + }; + void pushNode(const Ast::AstBase &_node) { Ast::AstBase node = _node; @@ -109,7 +131,7 @@ namespace Fig return currentAAI; } - inline Token nextToken() + inline const Token &nextToken() { // 没有Rollback时, 存在 currentTokenIndex = tokenPruduced - 1 next(); @@ -143,7 +165,7 @@ namespace Fig setCurrentAAI(Ast::AstAddressInfo{.line = tok.line, .column = tok.column}); previousTokens.push_back(tok); } - inline Token currentToken() + inline const Token ¤tToken() { if (tokenPruduced == 0) return nextToken(); return previousTokens.at(currentTokenIndex); @@ -229,6 +251,41 @@ namespace Fig } } + [[nodiscard]] SemicolonDisabler disableSemicolon() + { + return SemicolonDisabler(*this); + } + + void expectSemicolon() + { + // if need semicolon and stream has `;`, consume it. if not need semicolon, do nothing + + if (!needSemicolon) + { + // disabled semicolon check + if (currentToken().getType() == TokenType::Semicolon) + { + next(); // consume `;` + } + return; + } + + // normal semicolon check + expectConsume(TokenType::Semicolon); + } + + void expectConsume(TokenType type, FString expected) + { + expect(type, expected); + next(); + } + + void expectConsume(TokenType type) + { + expect(type); + next(); + } + bool isNext(TokenType type) { return peekToken().getType() == type; @@ -244,12 +301,15 @@ namespace Fig Value __parseValue(); Ast::ValueExpr __parseValueExpr(); Ast::FunctionParameters __parseFunctionParameters(); // entry: current is Token::LeftParen - Ast::Statement __parseStatement(); // entry: (idk) Ast::BlockStatement __parseBlockStatement(); // entry: current is Token::LeftBrace Ast::VarAssign __parseVarAssign(FString); // entry: current is Token::Assign, para1 is var name Ast::If __parseIf(); // entry: current is Token::If Ast::While __parseWhile(); // entry: current is Token::While + Ast::Statement __parseIncrementStatement(); // only allowed in __parseFor function + Ast::For __parseFor(); // entry: current is Token::For Ast::Return __parseReturn(); // entry: current is Token::Return + Ast::Break __parseBreak(); // entry: current is Token::Break + Ast::Continue __parseContinue(); // entry: current is Token::Continue Ast::VarExpr __parseVarExpr(FString); Ast::FunctionDef __parseFunctionDef(bool); // entry: current is Token::Identifier (isPublic: Bool) @@ -266,7 +326,8 @@ namespace Fig Ast::Expression __parseTupleOrParenExpr(); // entry: current is `(` Ast::FunctionLiteralExpr __parseFunctionLiteralExpr(); // entry: current is Token::LParen after Token::Function - + + Ast::Statement __parseStatement(); // entry: (idk) Ast::Expression parseExpression(Precedence, TokenType = TokenType::Semicolon, TokenType = TokenType::Semicolon); std::vector parseAll(); }; diff --git a/include/token.hpp b/include/token.hpp index 3cef360..b84ae24 100644 --- a/include/token.hpp +++ b/include/token.hpp @@ -35,6 +35,8 @@ namespace Fig Implement, // implement Public, // public Return, // return + Break, // break + Continue, // continue // TypeNull, // Null // TypeInt, // Int @@ -130,7 +132,7 @@ namespace Fig column = _column; return *this; } - FString getValue() + const FString& getValue() const { return value; } @@ -151,7 +153,7 @@ namespace Fig return type == TokenType::LiteralNull || type == TokenType::LiteralBool || type == TokenType::LiteralNumber || type == TokenType::LiteralString; } - TokenType getType() + TokenType getType() const { return type; } diff --git a/src/evaluator.cpp b/src/evaluator.cpp index cd6388a..bc18913 100644 --- a/src/evaluator.cpp +++ b/src/evaluator.cpp @@ -261,10 +261,34 @@ namespace Fig return Value::getNullInstance(); } } - + StatementResult Evaluator::evalBlockStatement(const Ast::BlockStatement &blockSt, ContextPtr context) + { + auto previousContext = currentContext; + if (context) + { + currentContext = context; + } + else + { + currentContext = std::make_shared(FString(std::format("", blockSt->getAAI().line, blockSt->getAAI().column)), currentContext); + } + StatementResult lstResult = StatementResult::normal(); + for (const auto &s : blockSt->stmts) + { + StatementResult sr = evalStatement(s); + if (!sr.isNormal()) + { + lstResult = sr; + break; + } + } + currentContext = previousContext; + return lstResult; + } StatementResult Evaluator::evalStatement(const Ast::Statement &stmt) { using Fig::Ast::AstType; + currentAddressInfo = stmt->getAAI(); switch (stmt->getType()) { case AstType::VarDefSt: { @@ -312,21 +336,7 @@ namespace Fig }; case AstType::BlockStatement: { auto blockSt = std::dynamic_pointer_cast(stmt); - auto newContext = std::make_shared(FString(std::format("", blockSt->getAAI().line, blockSt->getAAI().column)), currentContext); - auto previousContext = currentContext; - currentContext = newContext; - StatementResult lstResult = StatementResult::normal(); - for (const auto &s : blockSt->stmts) - { - StatementResult sr = evalStatement(s); - if (!sr.isNormal()) - { - lstResult = sr; - break; - } - } - currentContext = previousContext; - return lstResult; + return evalBlockStatement(blockSt); // auto create new context in block statement }; case AstType::FunctionDefSt: { auto fnDef = std::dynamic_pointer_cast(stmt); @@ -412,7 +422,7 @@ namespace Fig } if (condVal.as().getValue()) { - return evalStatement(ifSt->body); + return evalBlockStatement(ifSt->body); } // else for (const auto &elif : ifSt->elifs) @@ -425,12 +435,12 @@ namespace Fig } if (elifCondVal.as().getValue()) { - return evalStatement(elif->body); + return evalBlockStatement(elif->body); } } if (ifSt->els) { - return evalStatement(ifSt->els->body); + return evalBlockStatement(ifSt->els->body); } return StatementResult::normal(); }; @@ -442,13 +452,17 @@ namespace Fig if (condVal.getTypeInfo() != ValueType::Bool) { static constexpr char ConditionTypeErrorName[] = "ConditionTypeError"; - throw EvaluatorError(FStringView(u8"While condition must be boolean"), currentAddressInfo); + throw EvaluatorError(FStringView(u8"While condition must be boolean"), whileSt->condition->getAAI()); } if (!condVal.as().getValue()) { break; } - StatementResult sr = evalStatement(whileSt->body); + ContextPtr loopContext = std::make_shared( + FString(std::format("", + whileSt->getAAI().line, whileSt->getAAI().column)), + currentContext); // every loop has its own context + StatementResult sr = evalBlockStatement(whileSt->body, loopContext); if (sr.shouldReturn()) { return sr; @@ -464,22 +478,63 @@ namespace Fig } return StatementResult::normal(); }; + case AstType::ForSt: { + auto forSt = std::dynamic_pointer_cast(stmt); + ContextPtr loopContext = std::make_shared( + FString(std::format("", + forSt->getAAI().line, forSt->getAAI().column)), + currentContext); // for loop has its own context + ContextPtr previousContext = currentContext; + currentContext = loopContext; + + evalStatement(forSt->initSt); // ignore init statement result + size_t iteration = 0; + + while (true) // use while loop to simulate for loop, cause we need to check condition type every iteration + { + Value condVal = eval(forSt->condition); + if (condVal.getTypeInfo() != ValueType::Bool) + { + static constexpr char ConditionTypeErrorName[] = "ConditionTypeError"; + throw EvaluatorError(FStringView(u8"For condition must be boolean"), forSt->condition->getAAI()); + } + if (!condVal.as().getValue()) + { + break; + } + iteration++; + ContextPtr iterationContext = std::make_shared( + FString(std::format("", + forSt->getAAI().line, forSt->getAAI().column, iteration)), + loopContext); // every loop has its own context + StatementResult sr = evalBlockStatement(forSt->body, iterationContext); + if (sr.shouldReturn()) + { + currentContext = previousContext; // restore context before return + return sr; + } + if (sr.shouldBreak()) + { + break; + } + if (sr.shouldContinue()) + { + // continue to next iteration + continue; + } + currentContext = loopContext; // let increment statement be in loop context + evalStatement(forSt->incrementSt); // ignore increment statement result + } + currentContext = previousContext; // restore context + return StatementResult::normal(); + } case AstType::ReturnSt: { if (!currentContext->parent) { static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError"; throw EvaluatorError(FStringView(u8"'return' statement outside function"), currentAddressInfo); } - std::shared_ptr fc = currentContext; - while (fc->parent) - { - if (fc->getScopeName().find(u8"parent; - } - if (fc->getScopeName().find(u8"isInFunctionContext()) { static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError"; throw EvaluatorError(FStringView(u8"'return' statement outside function"), currentAddressInfo); @@ -487,6 +542,32 @@ namespace Fig auto returnSt = std::dynamic_pointer_cast(stmt); return StatementResult::returnFlow(eval(returnSt->retValue)); }; + case AstType::BreakSt: { + if (!currentContext->parent) + { + static constexpr char BreakOutsideLoopErrorName[] = "BreakOutsideLoopError"; + throw EvaluatorError(FStringView(u8"'break' statement outside loop"), currentAddressInfo); + } + if (!currentContext->isInLoopContext()) + { + static constexpr char BreakOutsideLoopErrorName[] = "BreakOutsideLoopError"; + throw EvaluatorError(FStringView(u8"'break' statement outside loop"), currentAddressInfo); + } + return StatementResult::breakFlow(); + }; + case AstType::ContinueSt: { + if (!currentContext->parent) + { + static constexpr char ContinueOutsideLoopErrorName[] = "ContinueOutsideLoopError"; + throw EvaluatorError(FStringView(u8"'continue' statement outside loop"), currentAddressInfo); + } + if (!currentContext->isInLoopContext()) + { + static constexpr char ContinueOutsideLoopErrorName[] = "ContinueOutsideLoopError"; + throw EvaluatorError(FStringView(u8"'continue' statement outside loop"), currentAddressInfo); + } + return StatementResult::continueFlow(); + }; default: throw RuntimeError(FStringView(std::string("Unknown statement type:") + magic_enum::enum_name(stmt->getType()).data())); } diff --git a/src/lexer.cpp b/src/lexer.cpp index 0999ab2..5c7dd5f 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -76,6 +76,8 @@ namespace Fig {FString(u8"implement"), TokenType::Implement}, {FString(u8"public"), TokenType::Public}, {FString(u8"return"), TokenType::Return}, + {FString(u8"break"), TokenType::Break}, + {FString(u8"continue"), TokenType::Continue}, // {FString(u8"Null"), TokenType::TypeNull}, diff --git a/src/parser.cpp b/src/parser.cpp index 9321794..9a0da8b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -61,7 +61,7 @@ namespace Fig } if (isThis(TokenType::Semicolon)) { - next(); + next(); // consume `;`, no using expectConsume here cause we don't need to check again return makeAst(isPublic, isConst, name, tiName, nullptr); } if (!isThis(TokenType::Assign) and !isThis(TokenType::Walrus)) expect(TokenType::Assign, u8"assign or walrus"); @@ -72,8 +72,7 @@ namespace Fig } next(); Ast::Expression exp = parseExpression(0); - expect(TokenType::Semicolon); - next(); + expectSemicolon(); return makeAst(isPublic, isConst, name, tiName, exp); } @@ -276,8 +275,7 @@ namespace Fig if (isEOF()) throwAddressableError(FStringView(u8"expect an expression")); initExpr = parseExpression(0); } - expect(TokenType::Semicolon); - next(); // consume `;` + expectSemicolon(); return Ast::StructDefField(am, fieldName, tiName, initExpr); }; std::vector stmts; @@ -359,8 +357,6 @@ namespace Fig if (isThis(TokenType::EndOfFile)) { return makeAst(); } if (isThis(TokenType::Public)) { - // stmt = __parseVarDef(); - // expect(TokenType::Semicolon); next(); // consume `public` if (isThis(TokenType::Variable) || isThis(TokenType::Const)) { @@ -417,16 +413,27 @@ namespace Fig { stmt = __parseWhile(); } + else if (isThis(TokenType::For)) + { + stmt = __parseFor(); + } else if (isThis(TokenType::Return)) { stmt = __parseReturn(); } + else if (isThis(TokenType::Break)) + { + stmt = __parseBreak(); + } + else if (isThis(TokenType::Continue)) + { + stmt = __parseContinue(); + } else { // expression statement Ast::Expression exp = parseExpression(0); - expect(TokenType::Semicolon); - next(); + expectSemicolon(); stmt = makeAst(exp); } return stmt; @@ -452,8 +459,7 @@ namespace Fig // entry: current is `=` next(); // consume `=` Ast::Expression exp = parseExpression(0); - expect(TokenType::Semicolon); - next(); // consume `;` + expectSemicolon(); return makeAst(varName, exp); } @@ -462,14 +468,14 @@ namespace Fig // entry: current is `if` next(); // consume `if` Ast::Expression condition; - if (isThis( TokenType::LeftParen)) + if (isThis(TokenType::LeftParen)) { next(); // consume `(` condition = parseExpression(0, TokenType::RightParen); expect(TokenType::RightParen); next(); // consume `)` } - else + else { condition = parseExpression(0); } @@ -509,15 +515,73 @@ namespace Fig Ast::BlockStatement body = __parseBlockStatement(); return makeAst(condition, body); } + Ast::Statement Parser::__parseIncrementStatement() + { + // allowed: + // 1. assignment:i = 1, i += 1 + // 2. expression stmt:i++, foo() + // ❌ not allowed:if/while/for/block stmt + + if (isThis(TokenType::LeftBrace)) + { + throwAddressableError(u8"BlockStatement cannot be used as for loop increment"); + } + + if (isThis(TokenType::If) || isThis(TokenType::While) || isThis(TokenType::For) || isThis(TokenType::Return) || isThis(TokenType::Break) || isThis(TokenType::Continue)) + { + throwAddressableError(u8"Control flow statements cannot be used as for loop increment"); + } + + return __parseStatement(); + } + Ast::For Parser::__parseFor() + { + // entry: current is `for` + // TODO: support enumeration + next(); // consume `for` + bool paren = isThis(TokenType::LeftParen); + if (paren) + next(); // consume `(` + // support 3-part for loop + // for init; condition; increment {} + Ast::Statement initStmt = __parseStatement(); // auto check `` + Ast::Expression condition = parseExpression(0); + expectSemicolon(); // auto consume `;` + + Ast::Statement incrementStmt = nullptr; + if (!isThis(paren ? TokenType::RightParen : TokenType::LeftBrace)) // need parse increment? + { + auto guard = disableSemicolon(); + incrementStmt = __parseIncrementStatement(); + } // after parse increment, semicolon check state restored + if (paren) + expectConsume(TokenType::RightParen); // consume `)` if has `(` + expect(TokenType::LeftBrace); // { + Ast::BlockStatement body = __parseBlockStatement(); // auto consume `}` + return makeAst(initStmt, condition, incrementStmt, body); + } Ast::Return Parser::__parseReturn() { // entry: current is `return` next(); // consume `return` Ast::Expression retValue = parseExpression(0); - expect(TokenType::Semicolon); - next(); // consume `;` + expectSemicolon(); return makeAst(retValue); } + Ast::Continue Parser::__parseContinue() + { + // entry: current is `continue` + next(); // consume `continue` + expectSemicolon(); + return makeAst(); + } + Ast::Break Parser::__parseBreak() + { + // entry: current is `break` + next(); // consume `break` + expectSemicolon(); + return makeAst(); + } Ast::VarExpr Parser::__parseVarExpr(FString name) { diff --git a/test.fig b/test.fig index 509a2ef..b6b4e4b 100644 --- a/test.fig +++ b/test.fig @@ -1,5 +1,4 @@ -while (true) +for (var i=0;i < 10;i = i + 1) { - __fstdout_println("1"); - break; + __fstdout_println(i); } \ No newline at end of file