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:
30
include/Ast/ForSt.hpp
Normal file
30
include/Ast/ForSt.hpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Ast/astBase.hpp>
|
||||||
|
|
||||||
|
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<ForSt>;
|
||||||
|
};
|
||||||
@@ -47,6 +47,7 @@ namespace Fig::Ast
|
|||||||
|
|
||||||
VarAssignSt,
|
VarAssignSt,
|
||||||
WhileSt,
|
WhileSt,
|
||||||
|
ForSt,
|
||||||
ReturnSt,
|
ReturnSt,
|
||||||
BreakSt,
|
BreakSt,
|
||||||
ContinueSt,
|
ContinueSt,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <Ast/ContainerInitExprs.hpp>
|
#include <Ast/ContainerInitExprs.hpp>
|
||||||
#include <Ast/ControlSt.hpp>
|
#include <Ast/ControlSt.hpp>
|
||||||
#include <Ast/ExpressionStmt.hpp>
|
#include <Ast/ExpressionStmt.hpp>
|
||||||
|
#include <Ast/ForSt.hpp>
|
||||||
#include <Ast/FunctionCall.hpp>
|
#include <Ast/FunctionCall.hpp>
|
||||||
#include <Ast/functionParameters.hpp>
|
#include <Ast/functionParameters.hpp>
|
||||||
#include <Ast/FunctionDefSt.hpp>
|
#include <Ast/FunctionDefSt.hpp>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <memory>
|
||||||
|
|
||||||
#include <context_forward.hpp>
|
#include <context_forward.hpp>
|
||||||
#include <fig_string.hpp>
|
#include <fig_string.hpp>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace Fig
|
namespace Fig
|
||||||
{
|
{
|
||||||
struct Context
|
class Context : public std::enable_shared_from_this<Context>
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
FString scopeName;
|
FString scopeName;
|
||||||
@@ -145,6 +145,33 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
throw RuntimeError(FStringView(std::format("Variable '{}' not defined", name.toBasicString())));
|
throw RuntimeError(FStringView(std::format("Variable '{}' not defined", name.toBasicString())));
|
||||||
}
|
}
|
||||||
|
bool isInFunctionContext()
|
||||||
|
{
|
||||||
|
ContextPtr ctx = shared_from_this();
|
||||||
|
while (ctx)
|
||||||
|
{
|
||||||
|
if (ctx->getScopeName().find(u8"<Function ") == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ctx = ctx->parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool isInLoopContext()
|
||||||
|
{
|
||||||
|
ContextPtr ctx = shared_from_this();
|
||||||
|
while (ctx)
|
||||||
|
{
|
||||||
|
if (ctx->getScopeName().find(u8"<While ") == 0 or
|
||||||
|
ctx->getScopeName().find(u8"<For ") == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ctx = ctx->parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
void printStackTrace(std::ostream &os = std::cerr, int indent = 0) const
|
void printStackTrace(std::ostream &os = std::cerr, int indent = 0) const
|
||||||
{
|
{
|
||||||
const Context *ctx = this;
|
const Context *ctx = this;
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
namespace Fig
|
namespace Fig
|
||||||
{
|
{
|
||||||
struct Context;
|
class Context;
|
||||||
using ContextPtr = std::shared_ptr<Context>;
|
using ContextPtr = std::shared_ptr<Context>;
|
||||||
};
|
};
|
||||||
@@ -111,6 +111,7 @@ namespace Fig
|
|||||||
Value evalBinary(const Ast::BinaryExpr &);
|
Value evalBinary(const Ast::BinaryExpr &);
|
||||||
Value evalUnary(const Ast::UnaryExpr &);
|
Value evalUnary(const Ast::UnaryExpr &);
|
||||||
|
|
||||||
|
StatementResult evalBlockStatement(const Ast::BlockStatement &, ContextPtr = nullptr);
|
||||||
StatementResult evalStatement(const Ast::Statement &);
|
StatementResult evalStatement(const Ast::Statement &);
|
||||||
|
|
||||||
Value evalFunctionCall(const Function &, const Ast::FunctionArguments &, FString fnName = u8"<anonymous>");
|
Value evalFunctionCall(const Function &, const Ast::FunctionArguments &, FString fnName = u8"<anonymous>");
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ namespace Fig
|
|||||||
{
|
{
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token nextToken();
|
Token nextToken();
|
||||||
|
|
||||||
Token scanNumber();
|
Token scanNumber();
|
||||||
|
|||||||
@@ -29,6 +29,28 @@ namespace Fig
|
|||||||
|
|
||||||
std::stack<Ast::Expression> exprStack;
|
std::stack<Ast::Expression> 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)
|
void pushNode(const Ast::AstBase &_node)
|
||||||
{
|
{
|
||||||
Ast::AstBase node = _node;
|
Ast::AstBase node = _node;
|
||||||
@@ -109,7 +131,7 @@ namespace Fig
|
|||||||
return currentAAI;
|
return currentAAI;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Token nextToken()
|
inline const Token &nextToken()
|
||||||
{
|
{
|
||||||
// 没有Rollback时, 存在 currentTokenIndex = tokenPruduced - 1
|
// 没有Rollback时, 存在 currentTokenIndex = tokenPruduced - 1
|
||||||
next();
|
next();
|
||||||
@@ -143,7 +165,7 @@ namespace Fig
|
|||||||
setCurrentAAI(Ast::AstAddressInfo{.line = tok.line, .column = tok.column});
|
setCurrentAAI(Ast::AstAddressInfo{.line = tok.line, .column = tok.column});
|
||||||
previousTokens.push_back(tok);
|
previousTokens.push_back(tok);
|
||||||
}
|
}
|
||||||
inline Token currentToken()
|
inline const Token ¤tToken()
|
||||||
{
|
{
|
||||||
if (tokenPruduced == 0) return nextToken();
|
if (tokenPruduced == 0) return nextToken();
|
||||||
return previousTokens.at(currentTokenIndex);
|
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)
|
bool isNext(TokenType type)
|
||||||
{
|
{
|
||||||
return peekToken().getType() == type;
|
return peekToken().getType() == type;
|
||||||
@@ -244,12 +301,15 @@ namespace Fig
|
|||||||
Value __parseValue();
|
Value __parseValue();
|
||||||
Ast::ValueExpr __parseValueExpr();
|
Ast::ValueExpr __parseValueExpr();
|
||||||
Ast::FunctionParameters __parseFunctionParameters(); // entry: current is Token::LeftParen
|
Ast::FunctionParameters __parseFunctionParameters(); // entry: current is Token::LeftParen
|
||||||
Ast::Statement __parseStatement(); // entry: (idk)
|
|
||||||
Ast::BlockStatement __parseBlockStatement(); // entry: current is Token::LeftBrace
|
Ast::BlockStatement __parseBlockStatement(); // entry: current is Token::LeftBrace
|
||||||
Ast::VarAssign __parseVarAssign(FString); // entry: current is Token::Assign, para1 is var name
|
Ast::VarAssign __parseVarAssign(FString); // entry: current is Token::Assign, para1 is var name
|
||||||
Ast::If __parseIf(); // entry: current is Token::If
|
Ast::If __parseIf(); // entry: current is Token::If
|
||||||
Ast::While __parseWhile(); // entry: current is Token::While
|
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::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::VarExpr __parseVarExpr(FString);
|
||||||
Ast::FunctionDef __parseFunctionDef(bool); // entry: current is Token::Identifier (isPublic: Bool)
|
Ast::FunctionDef __parseFunctionDef(bool); // entry: current is Token::Identifier (isPublic: Bool)
|
||||||
@@ -267,6 +327,7 @@ namespace Fig
|
|||||||
|
|
||||||
Ast::FunctionLiteralExpr __parseFunctionLiteralExpr(); // entry: current is Token::LParen after Token::Function
|
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);
|
Ast::Expression parseExpression(Precedence, TokenType = TokenType::Semicolon, TokenType = TokenType::Semicolon);
|
||||||
std::vector<Ast::AstBase> parseAll();
|
std::vector<Ast::AstBase> parseAll();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ namespace Fig
|
|||||||
Implement, // implement
|
Implement, // implement
|
||||||
Public, // public
|
Public, // public
|
||||||
Return, // return
|
Return, // return
|
||||||
|
Break, // break
|
||||||
|
Continue, // continue
|
||||||
|
|
||||||
// TypeNull, // Null
|
// TypeNull, // Null
|
||||||
// TypeInt, // Int
|
// TypeInt, // Int
|
||||||
@@ -130,7 +132,7 @@ namespace Fig
|
|||||||
column = _column;
|
column = _column;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FString getValue()
|
const FString& getValue() const
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -151,7 +153,7 @@ namespace Fig
|
|||||||
return type == TokenType::LiteralNull || type == TokenType::LiteralBool || type == TokenType::LiteralNumber || type == TokenType::LiteralString;
|
return type == TokenType::LiteralNull || type == TokenType::LiteralBool || type == TokenType::LiteralNumber || type == TokenType::LiteralString;
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenType getType()
|
TokenType getType() const
|
||||||
{
|
{
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,10 +261,34 @@ namespace Fig
|
|||||||
return Value::getNullInstance();
|
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)
|
StatementResult Evaluator::evalStatement(const Ast::Statement &stmt)
|
||||||
{
|
{
|
||||||
using Fig::Ast::AstType;
|
using Fig::Ast::AstType;
|
||||||
|
currentAddressInfo = stmt->getAAI();
|
||||||
switch (stmt->getType())
|
switch (stmt->getType())
|
||||||
{
|
{
|
||||||
case AstType::VarDefSt: {
|
case AstType::VarDefSt: {
|
||||||
@@ -312,21 +336,7 @@ namespace Fig
|
|||||||
};
|
};
|
||||||
case AstType::BlockStatement: {
|
case AstType::BlockStatement: {
|
||||||
auto blockSt = std::dynamic_pointer_cast<Ast::BlockStatementAst>(stmt);
|
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);
|
return evalBlockStatement(blockSt); // auto create new context in block statement
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
case AstType::FunctionDefSt: {
|
case AstType::FunctionDefSt: {
|
||||||
auto fnDef = std::dynamic_pointer_cast<Ast::FunctionDefSt>(stmt);
|
auto fnDef = std::dynamic_pointer_cast<Ast::FunctionDefSt>(stmt);
|
||||||
@@ -412,7 +422,7 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
if (condVal.as<Bool>().getValue())
|
if (condVal.as<Bool>().getValue())
|
||||||
{
|
{
|
||||||
return evalStatement(ifSt->body);
|
return evalBlockStatement(ifSt->body);
|
||||||
}
|
}
|
||||||
// else
|
// else
|
||||||
for (const auto &elif : ifSt->elifs)
|
for (const auto &elif : ifSt->elifs)
|
||||||
@@ -425,12 +435,12 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
if (elifCondVal.as<Bool>().getValue())
|
if (elifCondVal.as<Bool>().getValue())
|
||||||
{
|
{
|
||||||
return evalStatement(elif->body);
|
return evalBlockStatement(elif->body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ifSt->els)
|
if (ifSt->els)
|
||||||
{
|
{
|
||||||
return evalStatement(ifSt->els->body);
|
return evalBlockStatement(ifSt->els->body);
|
||||||
}
|
}
|
||||||
return StatementResult::normal();
|
return StatementResult::normal();
|
||||||
};
|
};
|
||||||
@@ -442,13 +452,17 @@ namespace Fig
|
|||||||
if (condVal.getTypeInfo() != ValueType::Bool)
|
if (condVal.getTypeInfo() != ValueType::Bool)
|
||||||
{
|
{
|
||||||
static constexpr char ConditionTypeErrorName[] = "ConditionTypeError";
|
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())
|
if (!condVal.as<Bool>().getValue())
|
||||||
{
|
{
|
||||||
break;
|
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())
|
if (sr.shouldReturn())
|
||||||
{
|
{
|
||||||
return sr;
|
return sr;
|
||||||
@@ -464,22 +478,63 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
return StatementResult::normal();
|
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: {
|
case AstType::ReturnSt: {
|
||||||
if (!currentContext->parent)
|
if (!currentContext->parent)
|
||||||
{
|
{
|
||||||
static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError";
|
static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError";
|
||||||
throw EvaluatorError<ReturnOutsideFunctionErrorName>(FStringView(u8"'return' statement outside function"), currentAddressInfo);
|
throw EvaluatorError<ReturnOutsideFunctionErrorName>(FStringView(u8"'return' statement outside function"), currentAddressInfo);
|
||||||
}
|
}
|
||||||
std::shared_ptr<Context> fc = currentContext;
|
if (!currentContext->isInFunctionContext())
|
||||||
while (fc->parent)
|
|
||||||
{
|
|
||||||
if (fc->getScopeName().find(u8"<Function ") == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fc = fc->parent;
|
|
||||||
}
|
|
||||||
if (fc->getScopeName().find(u8"<Function ") != 0)
|
|
||||||
{
|
{
|
||||||
static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError";
|
static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError";
|
||||||
throw EvaluatorError<ReturnOutsideFunctionErrorName>(FStringView(u8"'return' statement outside function"), currentAddressInfo);
|
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);
|
auto returnSt = std::dynamic_pointer_cast<Ast::ReturnSt>(stmt);
|
||||||
return StatementResult::returnFlow(eval(returnSt->retValue));
|
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:
|
default:
|
||||||
throw RuntimeError(FStringView(std::string("Unknown statement type:") + magic_enum::enum_name(stmt->getType()).data()));
|
throw RuntimeError(FStringView(std::string("Unknown statement type:") + magic_enum::enum_name(stmt->getType()).data()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ namespace Fig
|
|||||||
{FString(u8"implement"), TokenType::Implement},
|
{FString(u8"implement"), TokenType::Implement},
|
||||||
{FString(u8"public"), TokenType::Public},
|
{FString(u8"public"), TokenType::Public},
|
||||||
{FString(u8"return"), TokenType::Return},
|
{FString(u8"return"), TokenType::Return},
|
||||||
|
{FString(u8"break"), TokenType::Break},
|
||||||
|
{FString(u8"continue"), TokenType::Continue},
|
||||||
|
|
||||||
|
|
||||||
// {FString(u8"Null"), TokenType::TypeNull},
|
// {FString(u8"Null"), TokenType::TypeNull},
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
if (isThis(TokenType::Semicolon))
|
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);
|
return makeAst<Ast::VarDefAst>(isPublic, isConst, name, tiName, nullptr);
|
||||||
}
|
}
|
||||||
if (!isThis(TokenType::Assign) and !isThis(TokenType::Walrus)) expect(TokenType::Assign, u8"assign or walrus");
|
if (!isThis(TokenType::Assign) and !isThis(TokenType::Walrus)) expect(TokenType::Assign, u8"assign or walrus");
|
||||||
@@ -72,8 +72,7 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
Ast::Expression exp = parseExpression(0);
|
Ast::Expression exp = parseExpression(0);
|
||||||
expect(TokenType::Semicolon);
|
expectSemicolon();
|
||||||
next();
|
|
||||||
return makeAst<Ast::VarDefAst>(isPublic, isConst, name, tiName, exp);
|
return makeAst<Ast::VarDefAst>(isPublic, isConst, name, tiName, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,8 +275,7 @@ namespace Fig
|
|||||||
if (isEOF()) throwAddressableError<SyntaxError>(FStringView(u8"expect an expression"));
|
if (isEOF()) throwAddressableError<SyntaxError>(FStringView(u8"expect an expression"));
|
||||||
initExpr = parseExpression(0);
|
initExpr = parseExpression(0);
|
||||||
}
|
}
|
||||||
expect(TokenType::Semicolon);
|
expectSemicolon();
|
||||||
next(); // consume `;`
|
|
||||||
return Ast::StructDefField(am, fieldName, tiName, initExpr);
|
return Ast::StructDefField(am, fieldName, tiName, initExpr);
|
||||||
};
|
};
|
||||||
std::vector<Ast::Statement> stmts;
|
std::vector<Ast::Statement> stmts;
|
||||||
@@ -359,8 +357,6 @@ namespace Fig
|
|||||||
if (isThis(TokenType::EndOfFile)) { return makeAst<Ast::EofStmt>(); }
|
if (isThis(TokenType::EndOfFile)) { return makeAst<Ast::EofStmt>(); }
|
||||||
if (isThis(TokenType::Public))
|
if (isThis(TokenType::Public))
|
||||||
{
|
{
|
||||||
// stmt = __parseVarDef();
|
|
||||||
// expect(TokenType::Semicolon);
|
|
||||||
next(); // consume `public`
|
next(); // consume `public`
|
||||||
if (isThis(TokenType::Variable) || isThis(TokenType::Const))
|
if (isThis(TokenType::Variable) || isThis(TokenType::Const))
|
||||||
{
|
{
|
||||||
@@ -417,16 +413,27 @@ namespace Fig
|
|||||||
{
|
{
|
||||||
stmt = __parseWhile();
|
stmt = __parseWhile();
|
||||||
}
|
}
|
||||||
|
else if (isThis(TokenType::For))
|
||||||
|
{
|
||||||
|
stmt = __parseFor();
|
||||||
|
}
|
||||||
else if (isThis(TokenType::Return))
|
else if (isThis(TokenType::Return))
|
||||||
{
|
{
|
||||||
stmt = __parseReturn();
|
stmt = __parseReturn();
|
||||||
}
|
}
|
||||||
|
else if (isThis(TokenType::Break))
|
||||||
|
{
|
||||||
|
stmt = __parseBreak();
|
||||||
|
}
|
||||||
|
else if (isThis(TokenType::Continue))
|
||||||
|
{
|
||||||
|
stmt = __parseContinue();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// expression statement
|
// expression statement
|
||||||
Ast::Expression exp = parseExpression(0);
|
Ast::Expression exp = parseExpression(0);
|
||||||
expect(TokenType::Semicolon);
|
expectSemicolon();
|
||||||
next();
|
|
||||||
stmt = makeAst<Ast::ExpressionStmtAst>(exp);
|
stmt = makeAst<Ast::ExpressionStmtAst>(exp);
|
||||||
}
|
}
|
||||||
return stmt;
|
return stmt;
|
||||||
@@ -452,8 +459,7 @@ namespace Fig
|
|||||||
// entry: current is `=`
|
// entry: current is `=`
|
||||||
next(); // consume `=`
|
next(); // consume `=`
|
||||||
Ast::Expression exp = parseExpression(0);
|
Ast::Expression exp = parseExpression(0);
|
||||||
expect(TokenType::Semicolon);
|
expectSemicolon();
|
||||||
next(); // consume `;`
|
|
||||||
return makeAst<Ast::VarAssignSt>(varName, exp);
|
return makeAst<Ast::VarAssignSt>(varName, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +468,7 @@ namespace Fig
|
|||||||
// entry: current is `if`
|
// entry: current is `if`
|
||||||
next(); // consume `if`
|
next(); // consume `if`
|
||||||
Ast::Expression condition;
|
Ast::Expression condition;
|
||||||
if (isThis( TokenType::LeftParen))
|
if (isThis(TokenType::LeftParen))
|
||||||
{
|
{
|
||||||
next(); // consume `(`
|
next(); // consume `(`
|
||||||
condition = parseExpression(0, TokenType::RightParen);
|
condition = parseExpression(0, TokenType::RightParen);
|
||||||
@@ -509,15 +515,73 @@ namespace Fig
|
|||||||
Ast::BlockStatement body = __parseBlockStatement();
|
Ast::BlockStatement body = __parseBlockStatement();
|
||||||
return makeAst<Ast::WhileSt>(condition, body);
|
return makeAst<Ast::WhileSt>(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<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()
|
Ast::Return Parser::__parseReturn()
|
||||||
{
|
{
|
||||||
// entry: current is `return`
|
// entry: current is `return`
|
||||||
next(); // consume `return`
|
next(); // consume `return`
|
||||||
Ast::Expression retValue = parseExpression(0);
|
Ast::Expression retValue = parseExpression(0);
|
||||||
expect(TokenType::Semicolon);
|
expectSemicolon();
|
||||||
next(); // consume `;`
|
|
||||||
return makeAst<Ast::ReturnSt>(retValue);
|
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)
|
Ast::VarExpr Parser::__parseVarExpr(FString name)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user