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
This commit is contained in:
2025-12-21 22:55:46 +08:00
parent acc2d33dbc
commit 014803b705
13 changed files with 327 additions and 59 deletions

View File

@@ -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<Ast::VarDefAst>(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<Ast::VarDefAst>(isPublic, isConst, name, tiName, exp);
}
@@ -276,8 +275,7 @@ namespace Fig
if (isEOF()) throwAddressableError<SyntaxError>(FStringView(u8"expect an expression"));
initExpr = parseExpression(0);
}
expect(TokenType::Semicolon);
next(); // consume `;`
expectSemicolon();
return Ast::StructDefField(am, fieldName, tiName, initExpr);
};
std::vector<Ast::Statement> stmts;
@@ -359,8 +357,6 @@ namespace Fig
if (isThis(TokenType::EndOfFile)) { return makeAst<Ast::EofStmt>(); }
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<Ast::ExpressionStmtAst>(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<Ast::VarAssignSt>(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<Ast::WhileSt>(condition, body);
}
Ast::Statement Parser::__parseIncrementStatement()
{
// allowed
// 1. assignmenti = 1, i += 1
// 2. expression stmti++, foo()
// ❌ not allowedif/while/for/block stmt
if (isThis(TokenType::LeftBrace))
{
throwAddressableError<SyntaxError>(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<SyntaxError>(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<Ast::ForSt>(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<Ast::ReturnSt>(retValue);
}
Ast::Continue Parser::__parseContinue()
{
// entry: current is `continue`
next(); // consume `continue`
expectSemicolon();
return makeAst<Ast::ContinueSt>();
}
Ast::Break Parser::__parseBreak()
{
// entry: current is `break`
next(); // consume `break`
expectSemicolon();
return makeAst<Ast::BreakSt>();
}
Ast::VarExpr Parser::__parseVarExpr(FString name)
{