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

@@ -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<Context>(FString(std::format("<Block {}:{}>", 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<Ast::BlockStatementAst>(stmt);
auto newContext = std::make_shared<Context>(FString(std::format("<Block {}:{}>", 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<Ast::FunctionDefSt>(stmt);
@@ -412,7 +422,7 @@ namespace Fig
}
if (condVal.as<Bool>().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<Bool>().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<ConditionTypeErrorName>(FStringView(u8"While condition must be boolean"), currentAddressInfo);
throw EvaluatorError<ConditionTypeErrorName>(FStringView(u8"While condition must be boolean"), whileSt->condition->getAAI());
}
if (!condVal.as<Bool>().getValue())
{
break;
}
StatementResult sr = evalStatement(whileSt->body);
ContextPtr loopContext = std::make_shared<Context>(
FString(std::format("<While {}:{}>",
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<Ast::ForSt>(stmt);
ContextPtr loopContext = std::make_shared<Context>(
FString(std::format("<For {}:{}>",
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<ConditionTypeErrorName>(FStringView(u8"For condition must be boolean"), forSt->condition->getAAI());
}
if (!condVal.as<Bool>().getValue())
{
break;
}
iteration++;
ContextPtr iterationContext = std::make_shared<Context>(
FString(std::format("<For {}:{}, Iteration {}>",
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<ReturnOutsideFunctionErrorName>(FStringView(u8"'return' statement outside function"), currentAddressInfo);
}
std::shared_ptr<Context> fc = currentContext;
while (fc->parent)
{
if (fc->getScopeName().find(u8"<Function ") == 0)
{
break;
}
fc = fc->parent;
}
if (fc->getScopeName().find(u8"<Function ") != 0)
if (!currentContext->isInFunctionContext())
{
static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError";
throw EvaluatorError<ReturnOutsideFunctionErrorName>(FStringView(u8"'return' statement outside function"), currentAddressInfo);
@@ -487,6 +542,32 @@ namespace Fig
auto returnSt = std::dynamic_pointer_cast<Ast::ReturnSt>(stmt);
return StatementResult::returnFlow(eval(returnSt->retValue));
};
case AstType::BreakSt: {
if (!currentContext->parent)
{
static constexpr char BreakOutsideLoopErrorName[] = "BreakOutsideLoopError";
throw EvaluatorError<BreakOutsideLoopErrorName>(FStringView(u8"'break' statement outside loop"), currentAddressInfo);
}
if (!currentContext->isInLoopContext())
{
static constexpr char BreakOutsideLoopErrorName[] = "BreakOutsideLoopError";
throw EvaluatorError<BreakOutsideLoopErrorName>(FStringView(u8"'break' statement outside loop"), currentAddressInfo);
}
return StatementResult::breakFlow();
};
case AstType::ContinueSt: {
if (!currentContext->parent)
{
static constexpr char ContinueOutsideLoopErrorName[] = "ContinueOutsideLoopError";
throw EvaluatorError<ContinueOutsideLoopErrorName>(FStringView(u8"'continue' statement outside loop"), currentAddressInfo);
}
if (!currentContext->isInLoopContext())
{
static constexpr char ContinueOutsideLoopErrorName[] = "ContinueOutsideLoopError";
throw EvaluatorError<ContinueOutsideLoopErrorName>(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()));
}

View File

@@ -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},

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)
{