Try Catch
This commit is contained in:
parent
3138f6fb92
commit
227586c583
@ -26,6 +26,7 @@ enum TokenType{
|
|||||||
|
|
||||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||||
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
||||||
|
TRY, CATCH, FINALLY, THROW,
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||||
@ -55,6 +56,7 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
|||||||
|
|
||||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||||
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
||||||
|
"TRY", "CATCH", "FINALLY", "THROW",
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||||
@ -85,6 +87,10 @@ const std::map<std::string, TokenType> KEYWORDS {
|
|||||||
{"return", RETURN},
|
{"return", RETURN},
|
||||||
{"break", BREAK},
|
{"break", BREAK},
|
||||||
{"continue", CONTINUE},
|
{"continue", CONTINUE},
|
||||||
|
{"try", TRY},
|
||||||
|
{"catch", CATCH},
|
||||||
|
{"finally", FINALLY},
|
||||||
|
{"throw", THROW},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Token
|
struct Token
|
||||||
|
|||||||
@ -70,6 +70,8 @@ private:
|
|||||||
std::shared_ptr<Stmt> declaration();
|
std::shared_ptr<Stmt> declaration();
|
||||||
std::shared_ptr<Stmt> classDeclaration();
|
std::shared_ptr<Stmt> classDeclaration();
|
||||||
std::shared_ptr<Stmt> extensionDeclaration();
|
std::shared_ptr<Stmt> extensionDeclaration();
|
||||||
|
std::shared_ptr<Stmt> tryStatement();
|
||||||
|
std::shared_ptr<Stmt> throwStatement();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> varDeclaration();
|
std::shared_ptr<Stmt> varDeclaration();
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,8 @@ struct StmtVisitor
|
|||||||
virtual void visitAssignStmt(const std::shared_ptr<AssignStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
virtual void visitAssignStmt(const std::shared_ptr<AssignStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitClassStmt(const std::shared_ptr<ClassStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
virtual void visitClassStmt(const std::shared_ptr<ClassStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitTryStmt(const std::shared_ptr<struct TryStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitThrowStmt(const std::shared_ptr<struct ThrowStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||||
@ -247,3 +249,26 @@ struct AssignStmt : Stmt
|
|||||||
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
|
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TryStmt : Stmt {
|
||||||
|
std::shared_ptr<Stmt> tryBlock;
|
||||||
|
Token catchVar; // IDENTIFIER or empty token if no catch
|
||||||
|
std::shared_ptr<Stmt> catchBlock; // may be null
|
||||||
|
std::shared_ptr<Stmt> finallyBlock; // may be null
|
||||||
|
|
||||||
|
TryStmt(std::shared_ptr<Stmt> t, Token cvar, std::shared_ptr<Stmt> cblk, std::shared_ptr<Stmt> fblk)
|
||||||
|
: tryBlock(t), catchVar(cvar), catchBlock(cblk), finallyBlock(fblk) {}
|
||||||
|
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
||||||
|
visitor->visitTryStmt(std::static_pointer_cast<TryStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThrowStmt : Stmt {
|
||||||
|
const Token keyword;
|
||||||
|
std::shared_ptr<Expr> value;
|
||||||
|
ThrowStmt(Token kw, std::shared_ptr<Expr> v) : keyword(kw), value(v) {}
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
||||||
|
visitor->visitThrowStmt(std::static_pointer_cast<ThrowStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -7,4 +7,6 @@ struct ExecutionContext {
|
|||||||
Value returnValue = NONE_VALUE;
|
Value returnValue = NONE_VALUE;
|
||||||
bool shouldBreak = false;
|
bool shouldBreak = false;
|
||||||
bool shouldContinue = false;
|
bool shouldContinue = false;
|
||||||
|
bool hasThrow = false;
|
||||||
|
Value thrownValue = NONE_VALUE;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -40,6 +40,8 @@ public:
|
|||||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitClassStmt(const std::shared_ptr<ClassStmt>& statement, ExecutionContext* context = nullptr) override;
|
void visitClassStmt(const std::shared_ptr<ClassStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
|
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
||||||
|
|||||||
@ -81,6 +81,11 @@ private:
|
|||||||
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
||||||
std::unique_ptr<Evaluator> evaluator;
|
std::unique_ptr<Evaluator> evaluator;
|
||||||
std::unique_ptr<Executor> executor;
|
std::unique_ptr<Executor> executor;
|
||||||
|
// Pending throw propagation from expression evaluation
|
||||||
|
bool hasPendingThrow = false;
|
||||||
|
Value pendingThrow = NONE_VALUE;
|
||||||
|
int tryDepth = 0;
|
||||||
|
bool inlineErrorReported = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Interpreter(bool isInteractive);
|
explicit Interpreter(bool isInteractive);
|
||||||
@ -117,6 +122,17 @@ public:
|
|||||||
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
|
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
|
||||||
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
|
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
|
||||||
void addStdLibFunctions();
|
void addStdLibFunctions();
|
||||||
|
// Throw propagation helpers
|
||||||
|
void setPendingThrow(const Value& v) { hasPendingThrow = true; pendingThrow = v; }
|
||||||
|
bool consumePendingThrow(Value& out) { if (!hasPendingThrow) return false; out = pendingThrow; hasPendingThrow = false; pendingThrow = NONE_VALUE; return true; }
|
||||||
|
// Try tracking
|
||||||
|
void enterTry() { tryDepth++; }
|
||||||
|
void exitTry() { if (tryDepth > 0) tryDepth--; }
|
||||||
|
bool isInTry() const { return tryDepth > 0; }
|
||||||
|
void markInlineErrorReported() { inlineErrorReported = true; }
|
||||||
|
bool hasInlineErrorReported() const { return inlineErrorReported; }
|
||||||
|
void clearInlineErrorReported() { inlineErrorReported = false; }
|
||||||
|
bool hasReportedError() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -75,8 +75,9 @@ void Bob::run(std::string source)
|
|||||||
}
|
}
|
||||||
catch(std::exception &e)
|
catch(std::exception &e)
|
||||||
{
|
{
|
||||||
// Only suppress errors that have already been reported by the error reporter
|
// Only suppress errors that have already been reported inline/top-level
|
||||||
if (errorReporter.hasReportedError()) {
|
if (errorReporter.hasReportedError() || (interpreter && (interpreter->hasReportedError() || interpreter->hasInlineErrorReported()))) {
|
||||||
|
if (interpreter) interpreter->clearInlineErrorReported();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -269,7 +269,8 @@ sptr(Expr) Parser::postfix()
|
|||||||
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
||||||
Token oper = previous();
|
Token oper = previous();
|
||||||
if (!std::dynamic_pointer_cast<VarExpr>(expr) &&
|
if (!std::dynamic_pointer_cast<VarExpr>(expr) &&
|
||||||
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr)) {
|
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr) &&
|
||||||
|
!std::dynamic_pointer_cast<PropertyExpr>(expr)) {
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(oper.line, oper.column, "Parse Error",
|
errorReporter->reportError(oper.line, oper.column, "Parse Error",
|
||||||
"Postfix increment/decrement can only be applied to variables or array elements", "");
|
"Postfix increment/decrement can only be applied to variables or array elements", "");
|
||||||
@ -585,6 +586,8 @@ std::shared_ptr<Expr> Parser::functionExpression() {
|
|||||||
sptr(Stmt) Parser::statement()
|
sptr(Stmt) Parser::statement()
|
||||||
{
|
{
|
||||||
if(match({RETURN})) return returnStatement();
|
if(match({RETURN})) return returnStatement();
|
||||||
|
if(match({TRY})) return tryStatement();
|
||||||
|
if(match({THROW})) return throwStatement();
|
||||||
if(match({IF})) return ifStatement();
|
if(match({IF})) return ifStatement();
|
||||||
if(match({DO})) return doWhileStatement();
|
if(match({DO})) return doWhileStatement();
|
||||||
if(match({WHILE})) return whileStatement();
|
if(match({WHILE})) return whileStatement();
|
||||||
@ -784,6 +787,32 @@ std::vector<sptr(Stmt)> Parser::block()
|
|||||||
return statements;
|
return statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Stmt> Parser::tryStatement() {
|
||||||
|
// try { ... } (catch (e) { ... })? (finally { ... })?
|
||||||
|
auto tryBlock = statement();
|
||||||
|
Token catchVar{IDENTIFIER, "", previous().line, previous().column};
|
||||||
|
std::shared_ptr<Stmt> catchBlock = nullptr;
|
||||||
|
std::shared_ptr<Stmt> finallyBlock = nullptr;
|
||||||
|
if (match({CATCH})) {
|
||||||
|
consume(OPEN_PAREN, "Expected '(' after 'catch'.");
|
||||||
|
Token var = consume(IDENTIFIER, "Expected identifier for catch variable.");
|
||||||
|
catchVar = var;
|
||||||
|
consume(CLOSE_PAREN, "Expected ')' after catch variable.");
|
||||||
|
catchBlock = statement();
|
||||||
|
}
|
||||||
|
if (match({FINALLY})) {
|
||||||
|
finallyBlock = statement();
|
||||||
|
}
|
||||||
|
return msptr(TryStmt)(tryBlock, catchVar, catchBlock, finallyBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Stmt> Parser::throwStatement() {
|
||||||
|
Token kw = previous();
|
||||||
|
auto val = expression();
|
||||||
|
consume(SEMICOLON, "Expected ';' after throw expression.");
|
||||||
|
return msptr(ThrowStmt)(kw, val);
|
||||||
|
}
|
||||||
|
|
||||||
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
||||||
std::vector<sptr(Expr)> arguments;
|
std::vector<sptr(Expr)> arguments;
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,9 @@ void Environment::assign(const Token& name, const Value& value) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Report only if not within a try; otherwise let try/catch handle
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
|
// We cannot check tryDepth here directly; rely on Executor to suppress double-reporting
|
||||||
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
||||||
"Undefined variable '" + name.lexeme + "'", "");
|
"Undefined variable '" + name.lexeme + "'", "");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,8 +37,6 @@ Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
|||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case MINUS:
|
case MINUS:
|
||||||
if (!right.isNumber()) {
|
if (!right.isNumber()) {
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
}
|
}
|
||||||
return Value(-right.asNumber());
|
return Value(-right.asNumber());
|
||||||
@ -48,8 +46,6 @@ Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
|||||||
|
|
||||||
case BIN_NOT:
|
case BIN_NOT:
|
||||||
if (!right.isNumber()) {
|
if (!right.isNumber()) {
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
}
|
}
|
||||||
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
||||||
@ -106,8 +102,6 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
|
|||||||
default: break; // Unreachable
|
default: break; // Unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +124,7 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
|
|||||||
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
||||||
}
|
}
|
||||||
} catch (const std::runtime_error& e) {
|
} catch (const std::runtime_error& e) {
|
||||||
// The Value operators provide good error messages, just add context
|
throw; // Propagate to statement driver (try/catch) without reporting here
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
e.what(), expression->oper.lexeme);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +157,7 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
|
|||||||
throw std::runtime_error("Invalid increment/decrement operator.");
|
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the variable or array element
|
// Update the variable, array element, or object property
|
||||||
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||||
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
||||||
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
||||||
@ -197,6 +188,14 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
|
|||||||
|
|
||||||
// Update the array element
|
// Update the array element
|
||||||
arr[idx] = Value(newValue);
|
arr[idx] = Value(newValue);
|
||||||
|
} else if (auto propExpr = std::dynamic_pointer_cast<PropertyExpr>(expression->operand)) {
|
||||||
|
// obj.prop++ / obj.prop--
|
||||||
|
Value object = interpreter->evaluate(propExpr->object);
|
||||||
|
if (!object.isDict()) {
|
||||||
|
throw std::runtime_error("Can only increment/decrement properties on objects");
|
||||||
|
}
|
||||||
|
std::unordered_map<std::string, Value>& dict = object.asDict();
|
||||||
|
dict[propExpr->name.lexeme] = Value(newValue);
|
||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
||||||
@ -268,8 +267,11 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
if (array.isArray()) {
|
if (array.isArray()) {
|
||||||
// Handle array indexing
|
// Handle array indexing
|
||||||
if (!index.isNumber()) {
|
if (!index.isNumber()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Array index must be a number", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index must be a number", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Array index must be a number");
|
throw std::runtime_error("Array index must be a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,8 +279,11 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
const std::vector<Value>& arr = array.asArray();
|
const std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Array index out of bounds", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index out of bounds", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Array index out of bounds");
|
throw std::runtime_error("Array index out of bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,8 +292,11 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
} else if (array.isDict()) {
|
} else if (array.isDict()) {
|
||||||
// Handle dictionary indexing
|
// Handle dictionary indexing
|
||||||
if (!index.isString()) {
|
if (!index.isString()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Dictionary key must be a string", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Dictionary key must be a string", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,8 +311,11 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Can only index arrays and dictionaries", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Can only index arrays and dictionaries", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Can only index arrays and dictionaries");
|
throw std::runtime_error("Can only index arrays and dictionaries");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,8 +446,11 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
|||||||
return Value(anyFn);
|
return Value(anyFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Cannot access property '" + propertyName + "' on this type", "");
|
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
||||||
|
"Cannot access property '" + propertyName + "' on this type", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
|
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -449,8 +463,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
if (array.isArray()) {
|
if (array.isArray()) {
|
||||||
// Handle array assignment
|
// Handle array assignment
|
||||||
if (!index.isNumber()) {
|
if (!index.isNumber()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Array index must be a number", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index must be a number", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Array index must be a number");
|
throw std::runtime_error("Array index must be a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,8 +475,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
std::vector<Value>& arr = array.asArray();
|
std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Array index out of bounds", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index out of bounds", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Array index out of bounds");
|
throw std::runtime_error("Array index out of bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,8 +489,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
} else if (array.isDict()) {
|
} else if (array.isDict()) {
|
||||||
// Handle dictionary assignment
|
// Handle dictionary assignment
|
||||||
if (!index.isString()) {
|
if (!index.isString()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Dictionary key must be a string", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Dictionary key must be a string", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,8 +504,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
return value;
|
return value;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Can only assign to array or dictionary elements", "");
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Can only assign to array or dictionary elements", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Can only assign to array or dictionary elements");
|
throw std::runtime_error("Can only assign to array or dictionary elements");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -510,8 +536,11 @@ Value Evaluator::visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExp
|
|||||||
dict[propertyName] = value;
|
dict[propertyName] = value;
|
||||||
return value; // Return the assigned value
|
return value; // Return the assigned value
|
||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Cannot assign property '" + propertyName + "' on non-object", "");
|
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
||||||
|
"Cannot assign property '" + propertyName + "' on non-object", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
throw std::runtime_error("Cannot assign property '" + propertyName + "' on non-object");
|
throw std::runtime_error("Cannot assign property '" + propertyName + "' on non-object");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,13 +12,40 @@ Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
|||||||
Executor::~Executor() {}
|
Executor::~Executor() {}
|
||||||
|
|
||||||
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
||||||
|
ExecutionContext top;
|
||||||
for (const auto& statement : statements) {
|
for (const auto& statement : statements) {
|
||||||
execute(statement, nullptr);
|
execute(statement, &top);
|
||||||
|
if (top.hasThrow) break;
|
||||||
|
}
|
||||||
|
if (top.hasThrow) {
|
||||||
|
// If already reported inline, don't double-report here
|
||||||
|
if (!interpreter->hasInlineErrorReported()) {
|
||||||
|
std::string msg = "Uncaught exception";
|
||||||
|
if (top.thrownValue.isString()) msg = top.thrownValue.asString();
|
||||||
|
if (top.thrownValue.isDict()) {
|
||||||
|
auto& d = top.thrownValue.asDict();
|
||||||
|
auto it = d.find("message");
|
||||||
|
if (it != d.end() && it->second.isString()) msg = it->second.asString();
|
||||||
|
}
|
||||||
|
interpreter->reportError(0, 0, "Runtime Error", msg, "");
|
||||||
|
}
|
||||||
|
// Clear inline marker after handling
|
||||||
|
interpreter->clearInlineErrorReported();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
||||||
statement->accept(this, context);
|
try {
|
||||||
|
statement->accept(this, context);
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
if (context) {
|
||||||
|
std::unordered_map<std::string, Value> err;
|
||||||
|
err["type"] = Value(std::string("RuntimeError"));
|
||||||
|
err["message"] = Value(std::string(e.what()));
|
||||||
|
context->hasThrow = true;
|
||||||
|
context->thrownValue = Value(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
||||||
@ -27,7 +54,14 @@ void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements
|
|||||||
|
|
||||||
for (const auto& statement : statements) {
|
for (const auto& statement : statements) {
|
||||||
execute(statement, context);
|
execute(statement, context);
|
||||||
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
|
// Bridge any pending throws from expression evaluation into the context
|
||||||
|
Value pending;
|
||||||
|
if (interpreter->consumePendingThrow(pending)) {
|
||||||
|
if (context) { context->hasThrow = true; context->thrownValue = pending; }
|
||||||
|
}
|
||||||
|
// If an inline reporter already handled this error and we are at top level (no try),
|
||||||
|
// avoid reporting it again here.
|
||||||
|
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue || context->hasThrow)) {
|
||||||
interpreter->setEnvironment(previous);
|
interpreter->setEnvironment(previous);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -42,6 +76,11 @@ void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, Execu
|
|||||||
|
|
||||||
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||||
Value value = statement->expression->accept(evaluator);
|
Value value = statement->expression->accept(evaluator);
|
||||||
|
Value thrown;
|
||||||
|
if (interpreter->consumePendingThrow(thrown)) {
|
||||||
|
if (context) { context->hasThrow = true; context->thrownValue = thrown; }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (interpreter->isInteractiveMode())
|
if (interpreter->isInteractiveMode())
|
||||||
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
||||||
@ -51,6 +90,8 @@ void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, Execution
|
|||||||
Value value = NONE_VALUE;
|
Value value = NONE_VALUE;
|
||||||
if (statement->initializer != nullptr) {
|
if (statement->initializer != nullptr) {
|
||||||
value = statement->initializer->accept(evaluator);
|
value = statement->initializer->accept(evaluator);
|
||||||
|
Value thrownInit;
|
||||||
|
if (interpreter->consumePendingThrow(thrownInit)) { if (context) { context->hasThrow = true; context->thrownValue = thrownInit; } return; }
|
||||||
}
|
}
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
||||||
}
|
}
|
||||||
@ -82,7 +123,10 @@ void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, Exe
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
||||||
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
Value condValIf = statement->condition->accept(evaluator);
|
||||||
|
Value thrownIf;
|
||||||
|
if (interpreter->consumePendingThrow(thrownIf)) { if (context) { context->hasThrow = true; context->thrownValue = thrownIf; } return; }
|
||||||
|
if (interpreter->isTruthy(condValIf)) {
|
||||||
execute(statement->thenBranch, context);
|
execute(statement->thenBranch, context);
|
||||||
} else if (statement->elseBranch != nullptr) {
|
} else if (statement->elseBranch != nullptr) {
|
||||||
execute(statement->elseBranch, context);
|
execute(statement->elseBranch, context);
|
||||||
@ -97,7 +141,7 @@ void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, Execu
|
|||||||
|
|
||||||
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
execute(statement->body, &loopContext);
|
execute(statement->body, &loopContext);
|
||||||
|
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; } break; }
|
||||||
if (loopContext.hasReturn) {
|
if (loopContext.hasReturn) {
|
||||||
if (context) {
|
if (context) {
|
||||||
context->hasReturn = true;
|
context->hasReturn = true;
|
||||||
@ -123,26 +167,17 @@ void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, E
|
|||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
while (true) {
|
||||||
execute(statement->body, &loopContext);
|
execute(statement->body, &loopContext);
|
||||||
|
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; } break; }
|
||||||
if (loopContext.hasReturn) {
|
if (loopContext.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = loopContext.returnValue; } break; }
|
||||||
if (context) {
|
if (loopContext.shouldBreak) { break; }
|
||||||
context->hasReturn = true;
|
if (loopContext.shouldContinue) { loopContext.shouldContinue = false; }
|
||||||
context->returnValue = loopContext.returnValue;
|
Value c = statement->condition->accept(evaluator);
|
||||||
}
|
Value thrown;
|
||||||
break;
|
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||||
}
|
if (!interpreter->isTruthy(c)) break;
|
||||||
|
}
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
||||||
@ -155,9 +190,15 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
|
|||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
while (true) {
|
||||||
|
if (statement->condition != nullptr) {
|
||||||
|
Value c = statement->condition->accept(evaluator);
|
||||||
|
Value thrown;
|
||||||
|
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||||
|
if (!interpreter->isTruthy(c)) break;
|
||||||
|
}
|
||||||
execute(statement->body, &loopContext);
|
execute(statement->body, &loopContext);
|
||||||
|
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; } break; }
|
||||||
if (loopContext.hasReturn) {
|
if (loopContext.hasReturn) {
|
||||||
if (context) {
|
if (context) {
|
||||||
context->hasReturn = true;
|
context->hasReturn = true;
|
||||||
@ -174,12 +215,16 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
|
|||||||
loopContext.shouldContinue = false;
|
loopContext.shouldContinue = false;
|
||||||
if (statement->increment != nullptr) {
|
if (statement->increment != nullptr) {
|
||||||
statement->increment->accept(evaluator);
|
statement->increment->accept(evaluator);
|
||||||
|
Value thrown;
|
||||||
|
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statement->increment != nullptr) {
|
if (statement->increment != nullptr) {
|
||||||
statement->increment->accept(evaluator);
|
statement->increment->accept(evaluator);
|
||||||
|
Value thrown;
|
||||||
|
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,6 +241,60 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context) {
|
||||||
|
interpreter->enterTry();
|
||||||
|
ExecutionContext inner;
|
||||||
|
if (context) inner.isFunctionBody = context->isFunctionBody;
|
||||||
|
execute(statement->tryBlock, &inner);
|
||||||
|
// Also capture any pending throw signaled by expressions
|
||||||
|
Value pending;
|
||||||
|
if (interpreter->consumePendingThrow(pending)) {
|
||||||
|
inner.hasThrow = true;
|
||||||
|
inner.thrownValue = pending;
|
||||||
|
}
|
||||||
|
// If thrown, handle catch
|
||||||
|
if (inner.hasThrow && statement->catchBlock) {
|
||||||
|
auto saved = interpreter->getEnvironment();
|
||||||
|
auto env = std::make_shared<Environment>(saved);
|
||||||
|
env->setErrorReporter(nullptr);
|
||||||
|
// Bind catch var if provided
|
||||||
|
if (!statement->catchVar.lexeme.empty()) {
|
||||||
|
env->define(statement->catchVar.lexeme, inner.thrownValue);
|
||||||
|
}
|
||||||
|
interpreter->setEnvironment(env);
|
||||||
|
ExecutionContext catchCtx;
|
||||||
|
catchCtx.isFunctionBody = inner.isFunctionBody;
|
||||||
|
execute(statement->catchBlock, &catchCtx);
|
||||||
|
inner.hasThrow = catchCtx.hasThrow;
|
||||||
|
inner.thrownValue = catchCtx.thrownValue;
|
||||||
|
interpreter->setEnvironment(saved);
|
||||||
|
}
|
||||||
|
// finally always
|
||||||
|
if (statement->finallyBlock) {
|
||||||
|
ExecutionContext fctx;
|
||||||
|
fctx.isFunctionBody = inner.isFunctionBody;
|
||||||
|
execute(statement->finallyBlock, &fctx);
|
||||||
|
if (fctx.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = fctx.returnValue; } return; }
|
||||||
|
if (fctx.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = fctx.thrownValue; } return; }
|
||||||
|
if (fctx.shouldBreak) { if (context) { context->shouldBreak = true; } return; }
|
||||||
|
if (fctx.shouldContinue) { if (context) { context->shouldContinue = true; } return; }
|
||||||
|
}
|
||||||
|
// propagate remaining control flow
|
||||||
|
if (inner.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = inner.returnValue; } interpreter->exitTry(); return; }
|
||||||
|
if (inner.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = inner.thrownValue; } interpreter->exitTry(); return; }
|
||||||
|
if (inner.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->exitTry(); return; }
|
||||||
|
if (inner.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->exitTry(); return; }
|
||||||
|
interpreter->exitTry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value v = statement->value ? statement->value->accept(evaluator) : NONE_VALUE;
|
||||||
|
if (context) {
|
||||||
|
context->hasThrow = true;
|
||||||
|
context->thrownValue = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
||||||
Value value = statement->value->accept(evaluator);
|
Value value = statement->value->accept(evaluator);
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,10 @@ Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
|||||||
return runTrampoline(result);
|
return runTrampoline(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Interpreter::hasReportedError() const {
|
||||||
|
return inlineErrorReported;
|
||||||
|
}
|
||||||
|
|
||||||
Value Interpreter::runTrampoline(Value initialResult) {
|
Value Interpreter::runTrampoline(Value initialResult) {
|
||||||
Value current = initialResult;
|
Value current = initialResult;
|
||||||
while (current.isThunk()) {
|
while (current.isThunk()) {
|
||||||
@ -429,6 +433,7 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
|
|
||||||
for (const auto& stmt : function->body) {
|
for (const auto& stmt : function->body) {
|
||||||
stmt->accept(executor.get(), &context);
|
stmt->accept(executor.get(), &context);
|
||||||
|
if (context.hasThrow) { setPendingThrow(context.thrownValue); return NONE_VALUE; }
|
||||||
if (context.hasReturn) {
|
if (context.hasReturn) {
|
||||||
return context.returnValue;
|
return context.returnValue;
|
||||||
}
|
}
|
||||||
@ -467,9 +472,8 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
|
|
||||||
for (const auto& stmt : function->body) {
|
for (const auto& stmt : function->body) {
|
||||||
stmt->accept(executor.get(), &context);
|
stmt->accept(executor.get(), &context);
|
||||||
if (context.hasReturn) {
|
if (context.hasThrow) { setPendingThrow(context.thrownValue); return NONE_VALUE; }
|
||||||
return context.returnValue;
|
if (context.hasReturn) { return context.returnValue; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.returnValue;
|
return context.returnValue;
|
||||||
|
|||||||
@ -3303,5 +3303,8 @@ eval(readFile(path13));
|
|||||||
var path14 = fileExists("tests/test_builtin_methods_style.bob") ? "tests/test_builtin_methods_style.bob" : "../tests/test_builtin_methods_style.bob";
|
var path14 = fileExists("tests/test_builtin_methods_style.bob") ? "tests/test_builtin_methods_style.bob" : "../tests/test_builtin_methods_style.bob";
|
||||||
eval(readFile(path14));
|
eval(readFile(path14));
|
||||||
|
|
||||||
|
var path15 = fileExists("tests/test_try_catch.bob") ? "tests/test_try_catch.bob" : "../tests/test_try_catch.bob";
|
||||||
|
eval(readFile(path15));
|
||||||
|
|
||||||
print("\nAll tests passed.");
|
print("\nAll tests passed.");
|
||||||
print("Test suite complete.");
|
print("Test suite complete.");
|
||||||
24
tests.bob
24
tests.bob
@ -68,13 +68,21 @@ class Test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var t = Test();
|
var arr = [];
|
||||||
t.test();
|
for(var i = 0; i < 100; i++){
|
||||||
|
arr.push(i);
|
||||||
extension number{
|
|
||||||
func negate(){
|
|
||||||
return -this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print(69.negate());
|
var counter = 0;
|
||||||
|
|
||||||
|
//try{
|
||||||
|
while(true){
|
||||||
|
print(arr[counter]);
|
||||||
|
counter++;
|
||||||
|
sleep(0.01);
|
||||||
|
}
|
||||||
|
//}catch(e){
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
print("done");
|
||||||
14
tests/test_inc_property.bob
Normal file
14
tests/test_inc_property.bob
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
print("\n--- Test: property increment/decrement ---");
|
||||||
|
|
||||||
|
var o = {"v": 10};
|
||||||
|
o.v++;
|
||||||
|
assert(o.v == 11, "postfix inc on prop");
|
||||||
|
+o.v;
|
||||||
|
assert(o.v == 12, "prefix inc on prop");
|
||||||
|
o.v--;
|
||||||
|
assert(o.v == 11, "postfix dec on prop");
|
||||||
|
--o.v;
|
||||||
|
assert(o.v == 10, "prefix dec on prop");
|
||||||
|
|
||||||
|
print("property inc/dec: PASS");
|
||||||
|
|
||||||
29
tests/test_try_catch.bob
Normal file
29
tests/test_try_catch.bob
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
print("\n--- Test: try/catch/finally (basic) ---");
|
||||||
|
|
||||||
|
// Basic: catch handles, finally always runs
|
||||||
|
var steps = [];
|
||||||
|
try { steps.push("try"); throw {"message":"boom"}; }
|
||||||
|
catch (e) { steps.push("catch:" + e.message); }
|
||||||
|
finally { steps.push("finally"); }
|
||||||
|
assert(steps[0] == "try" && steps[1] == "catch:boom" && steps[2] == "finally", "basic order");
|
||||||
|
|
||||||
|
// Nested: no inner catch, outer catches, finally still runs
|
||||||
|
var seen = [];
|
||||||
|
try {
|
||||||
|
try { throw {"message":"x"}; } finally { seen.push("innerFinally"); }
|
||||||
|
}
|
||||||
|
catch (e) { seen.push("outer:" + e.message); }
|
||||||
|
assert(seen[0] == "innerFinally" && seen[1] == "outer:x", "nested propagation");
|
||||||
|
|
||||||
|
// Finally overrides return
|
||||||
|
func r() { try { return 1; } finally { return 2; } }
|
||||||
|
assert(r() == 2, "finally overrides return");
|
||||||
|
|
||||||
|
// Throw non-dict
|
||||||
|
var info = "";
|
||||||
|
try { throw "oops"; } catch (e) { info = type(e) + ":" + e; }
|
||||||
|
assert(info == "string:oops", "non-dict throw");
|
||||||
|
|
||||||
|
print("try/catch/finally basic: PASS");
|
||||||
|
|
||||||
|
|
||||||
95
tests/test_try_catch_extensive.bob
Normal file
95
tests/test_try_catch_extensive.bob
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
print("\n--- Test: try/catch/finally (extensive) ---");
|
||||||
|
|
||||||
|
// 1) Basic catch and finally order
|
||||||
|
var steps = [];
|
||||||
|
try { steps.push("A"); throw {"message":"err"}; } catch (e) { steps.push("B:" + e.message); } finally { steps.push("C"); }
|
||||||
|
assert(steps.len() == 3, "basic order length");
|
||||||
|
assert(steps[0] == "A", "order A");
|
||||||
|
assert(steps[1] == "B:err", "order B");
|
||||||
|
assert(steps[2] == "C", "order C");
|
||||||
|
|
||||||
|
// 2) No catch; finally runs; thrown propagates
|
||||||
|
var caughtOuter = false; var fin = [];
|
||||||
|
try {
|
||||||
|
try { fin.push("try"); throw {"message":"up"}; } finally { fin.push("finally"); }
|
||||||
|
} catch (e) { caughtOuter = (e.message == "up"); }
|
||||||
|
assert(caughtOuter == true, "outer caught propagated throw");
|
||||||
|
assert(fin.len() == 2 && fin[1] == "finally", "finally executed without catch");
|
||||||
|
|
||||||
|
// 3) Rethrow from catch
|
||||||
|
caughtOuter = false;
|
||||||
|
try {
|
||||||
|
try { throw {"message":"boom"}; }
|
||||||
|
catch (e) { throw e; }
|
||||||
|
} catch (e) { caughtOuter = (e.message == "boom"); }
|
||||||
|
assert(caughtOuter == true, "rethrow works");
|
||||||
|
|
||||||
|
// 4) finally overriding return
|
||||||
|
func f() {
|
||||||
|
try { return 1; } finally { return 2; }
|
||||||
|
}
|
||||||
|
assert(f() == 2, "finally overrides return");
|
||||||
|
|
||||||
|
// 5) finally overriding throw
|
||||||
|
func g() {
|
||||||
|
try { throw {"message":"x"}; } finally { return 3; }
|
||||||
|
}
|
||||||
|
assert(g() == 3, "finally overrides throw with return");
|
||||||
|
|
||||||
|
// 6) Loop with continue/break inside finally
|
||||||
|
var i = 0; var seq = [];
|
||||||
|
while (i < 3) {
|
||||||
|
try { i = i + 1; seq.push(i); }
|
||||||
|
finally {
|
||||||
|
if (i < 3) continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(i == 3 && seq.len() == 3, "finally continue in loop");
|
||||||
|
|
||||||
|
i = 0; seq = [];
|
||||||
|
while (true) {
|
||||||
|
try { i = i + 1; seq.push(i); if (i == 2) throw {"message":"stop"}; }
|
||||||
|
catch (e) { seq.push("caught:" + e.message); }
|
||||||
|
finally {
|
||||||
|
if (i >= 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(seq.len() == 3 && seq[2] == "caught:stop", "finally break in loop");
|
||||||
|
|
||||||
|
// 7) Throw from method and catch in caller
|
||||||
|
class T { func f(x) { if (x < 0) throw {"message":"neg"}; return x; } }
|
||||||
|
var t = T();
|
||||||
|
var ok = 0;
|
||||||
|
try { ok = t.f(5); } catch (e) { ok = -1; }
|
||||||
|
var got = "";
|
||||||
|
try { t.f(-1); } catch (e) { got = e.message; }
|
||||||
|
assert(ok == 5 && got == "neg", "throw from method");
|
||||||
|
|
||||||
|
// 8) Throw non-dict value
|
||||||
|
var t = "";
|
||||||
|
try { throw "oops"; } catch (e) { t = type(e) + ":" + e; }
|
||||||
|
assert(t == "string:oops", "throw non-dict");
|
||||||
|
|
||||||
|
// 9) Nested try with inner catch consuming error
|
||||||
|
var mark = [];
|
||||||
|
try {
|
||||||
|
try { throw {"message":"inner"}; }
|
||||||
|
catch (e) { mark.push(e.message); }
|
||||||
|
} catch (e) { mark.push("outer:" + e.message); }
|
||||||
|
assert(mark.len() == 1 && mark[0] == "inner", "inner catch consumes");
|
||||||
|
|
||||||
|
// 10) Method/extension throws and is caught
|
||||||
|
class P { func bad() { throw {"message":"m"}; } }
|
||||||
|
var p = P();
|
||||||
|
var mmsg = "";
|
||||||
|
try { p.bad(); } catch (e) { mmsg = e.message; }
|
||||||
|
assert(mmsg == "m", "method throw caught");
|
||||||
|
|
||||||
|
extension array { func thrower() { throw {"message":"arr"}; } }
|
||||||
|
var a = [1]; var amsg = "";
|
||||||
|
try { a.thrower(); } catch (e) { amsg = e.message; }
|
||||||
|
assert(amsg == "arr", "extension throw caught");
|
||||||
|
|
||||||
|
print("try/catch/finally extensive: PASS");
|
||||||
|
|
||||||
|
|
||||||
34
tests/test_try_catch_runtime.bob
Normal file
34
tests/test_try_catch_runtime.bob
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
print("\n--- Test: try/catch runtime errors ---");
|
||||||
|
|
||||||
|
// Division by zero
|
||||||
|
var caught = "";
|
||||||
|
try { var x = 10 / 0; }
|
||||||
|
catch (e) { caught = e.type + ":" + e.message; }
|
||||||
|
assert(type(caught) == "string", "caught is string");
|
||||||
|
|
||||||
|
// Undefined variable
|
||||||
|
var c2 = "";
|
||||||
|
try { var y = missingVar + 1; }
|
||||||
|
catch (e) { c2 = e.type; }
|
||||||
|
assert(c2 == "RuntimeError", "undefined variable caught");
|
||||||
|
|
||||||
|
// Array out-of-bounds
|
||||||
|
var c3 = "";
|
||||||
|
try { var a = [1,2]; var z = a[5]; }
|
||||||
|
catch (e) { c3 = e.type; }
|
||||||
|
assert(c3 == "RuntimeError", "array oob caught");
|
||||||
|
|
||||||
|
// Dict key type error
|
||||||
|
var c4 = "";
|
||||||
|
try { var d = {"x":1}; var z = d[1]; }
|
||||||
|
catch (e) { c4 = e.type; }
|
||||||
|
assert(c4 == "RuntimeError", "dict key type caught");
|
||||||
|
|
||||||
|
// Non-array/dict index
|
||||||
|
var c5 = "";
|
||||||
|
try { var z = 42[0]; }
|
||||||
|
catch (e) { c5 = e.type; }
|
||||||
|
assert(c5 == "RuntimeError", "non-array index caught");
|
||||||
|
|
||||||
|
print("try/catch runtime errors: PASS");
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user