Try Catch bug fixing

This commit is contained in:
Bobby Lucero 2025-08-11 20:12:24 -04:00
parent 227586c583
commit fc63c3e46f
15 changed files with 276 additions and 69 deletions

View File

@ -34,6 +34,9 @@ private:
std::string currentFileName; std::string currentFileName;
std::vector<std::string> callStack; std::vector<std::string> callStack;
bool hadError = false; bool hadError = false;
// Support nested sources (e.g., eval of external files)
std::vector<std::vector<std::string>> sourceStack;
std::vector<std::string> fileNameStack;
public: public:
ErrorReporter() = default; ErrorReporter() = default;
@ -58,6 +61,10 @@ public:
void pushCallStack(const std::string& functionName); void pushCallStack(const std::string& functionName);
void popCallStack(); void popCallStack();
// Source push/pop for eval
void pushSource(const std::string& source, const std::string& fileName);
void popSource();
private: private:
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true); void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
void displayCallStack(const std::vector<std::string>& callStack); void displayCallStack(const std::vector<std::string>& callStack);

View File

@ -29,6 +29,7 @@ public:
void setErrorReporter(ErrorReporter* reporter) { void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter; errorReporter = reporter;
} }
ErrorReporter* getErrorReporter() const { return errorReporter; }
// Optimized define with inline // Optimized define with inline
inline void define(const std::string& name, const Value& value) { inline void define(const std::string& name, const Value& value) {

View File

@ -9,4 +9,6 @@ struct ExecutionContext {
bool shouldContinue = false; bool shouldContinue = false;
bool hasThrow = false; bool hasThrow = false;
Value thrownValue = NONE_VALUE; Value thrownValue = NONE_VALUE;
int throwLine = 0;
int throwColumn = 0;
}; };

View File

@ -82,8 +82,12 @@ private:
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 // Pending throw propagation from expression evaluation
bool hasPendingThrow = false; bool hasPendingThrow = false;
Value pendingThrow = NONE_VALUE; Value pendingThrow = NONE_VALUE;
int pendingThrowLine = 0;
int pendingThrowColumn = 0;
int lastErrorLine = 0;
int lastErrorColumn = 0;
int tryDepth = 0; int tryDepth = 0;
bool inlineErrorReported = false; bool inlineErrorReported = false;
@ -106,6 +110,7 @@ public:
bool isInteractiveMode() const; bool isInteractiveMode() const;
std::shared_ptr<Environment> getEnvironment(); std::shared_ptr<Environment> getEnvironment();
void setEnvironment(std::shared_ptr<Environment> env); void setEnvironment(std::shared_ptr<Environment> env);
ErrorReporter* getErrorReporter() const { return errorReporter; }
void addFunction(std::shared_ptr<Function> function); void addFunction(std::shared_ptr<Function> function);
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = ""); void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
@ -123,8 +128,8 @@ public:
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 // Throw propagation helpers
void setPendingThrow(const Value& v) { hasPendingThrow = true; pendingThrow = v; } void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
bool consumePendingThrow(Value& out) { if (!hasPendingThrow) return false; out = pendingThrow; hasPendingThrow = false; pendingThrow = NONE_VALUE; return true; } bool consumePendingThrow(Value& out, int* lineOut = nullptr, int* colOut = nullptr) { if (!hasPendingThrow) return false; out = pendingThrow; if (lineOut) *lineOut = pendingThrowLine; if (colOut) *colOut = pendingThrowColumn; hasPendingThrow = false; pendingThrow = NONE_VALUE; pendingThrowLine = 0; pendingThrowColumn = 0; return true; }
// Try tracking // Try tracking
void enterTry() { tryDepth++; } void enterTry() { tryDepth++; }
void exitTry() { if (tryDepth > 0) tryDepth--; } void exitTry() { if (tryDepth > 0) tryDepth--; }
@ -133,6 +138,10 @@ public:
bool hasInlineErrorReported() const { return inlineErrorReported; } bool hasInlineErrorReported() const { return inlineErrorReported; }
void clearInlineErrorReported() { inlineErrorReported = false; } void clearInlineErrorReported() { inlineErrorReported = false; }
bool hasReportedError() const; bool hasReportedError() const;
// Last error site tracking
void setLastErrorSite(int line, int column) { lastErrorLine = line; lastErrorColumn = column; }
int getLastErrorLine() const { return lastErrorLine; }
int getLastErrorColumn() const { return lastErrorColumn; }

View File

@ -56,6 +56,29 @@ void ErrorReporter::loadSource(const std::string& source, const std::string& fil
} }
} }
void ErrorReporter::pushSource(const std::string& source, const std::string& fileName) {
// Save current
sourceStack.push_back(sourceLines);
fileNameStack.push_back(currentFileName);
// Load new
loadSource(source, fileName);
}
void ErrorReporter::popSource() {
if (!sourceStack.empty()) {
sourceLines = sourceStack.back();
sourceStack.pop_back();
} else {
sourceLines.clear();
}
if (!fileNameStack.empty()) {
currentFileName = fileNameStack.back();
fileNameStack.pop_back();
} else {
currentFileName.clear();
}
}
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) { void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
hadError = true; hadError = true;
displaySourceContext(line, column, errorType, message, operator_, showArrow); displaySourceContext(line, column, errorType, message, operator_, showArrow);
@ -75,6 +98,8 @@ void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
if (!context.fileName.empty()) { if (!context.fileName.empty()) {
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n"; std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
} else if (!currentFileName.empty()) {
std::cout << colorize("File: ", Colors::BOLD) << colorize(currentFileName, Colors::CYAN) << "\n";
} }
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) + std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +

View File

@ -795,8 +795,11 @@ std::shared_ptr<Stmt> Parser::tryStatement() {
std::shared_ptr<Stmt> finallyBlock = nullptr; std::shared_ptr<Stmt> finallyBlock = nullptr;
if (match({CATCH})) { if (match({CATCH})) {
consume(OPEN_PAREN, "Expected '(' after 'catch'."); consume(OPEN_PAREN, "Expected '(' after 'catch'.");
Token var = consume(IDENTIFIER, "Expected identifier for catch variable."); // Allow optional identifier: catch() or catch(e)
catchVar = var; if (!check(CLOSE_PAREN)) {
Token var = consume(IDENTIFIER, "Expected identifier for catch variable.");
catchVar = var;
}
consume(CLOSE_PAREN, "Expected ')' after catch variable."); consume(CLOSE_PAREN, "Expected ')' after catch variable.");
catchBlock = statement(); catchBlock = statement();
} }

View File

@ -267,11 +267,9 @@ 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()) {
if (!interpreter->isInTry()) { interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", "Array index must be a number", "");
"Array index must be a number", ""); interpreter->markInlineErrorReported();
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Array index must be a number"); throw std::runtime_error("Array index must be a number");
} }
@ -279,11 +277,9 @@ 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())) {
if (!interpreter->isInTry()) { interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", "Array index out of bounds", "");
"Array index out of bounds", ""); interpreter->markInlineErrorReported();
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Array index out of bounds"); throw std::runtime_error("Array index out of bounds");
} }
@ -292,11 +288,9 @@ 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()) {
if (!interpreter->isInTry()) { interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", "Dictionary key must be a string", "");
"Dictionary key must be a string", ""); interpreter->markInlineErrorReported();
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Dictionary key must be a string"); throw std::runtime_error("Dictionary key must be a string");
} }
@ -311,11 +305,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
} }
} else { } else {
if (!interpreter->isInTry()) { interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", "Can only index arrays and dictionaries", "");
"Can only index arrays and dictionaries", ""); interpreter->markInlineErrorReported();
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Can only index arrays and dictionaries"); throw std::runtime_error("Can only index arrays and dictionaries");
} }
} }
@ -446,11 +438,9 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
return Value(anyFn); return Value(anyFn);
} }
if (!interpreter->isInTry()) { interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error", "Cannot access property '" + propertyName + "' on this type", "");
"Cannot access property '" + propertyName + "' on this type", ""); interpreter->markInlineErrorReported();
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type"); throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
} }
} }

View File

@ -27,7 +27,10 @@ void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
auto it = d.find("message"); auto it = d.find("message");
if (it != d.end() && it->second.isString()) msg = it->second.asString(); if (it != d.end() && it->second.isString()) msg = it->second.asString();
} }
interpreter->reportError(0, 0, "Runtime Error", msg, ""); int line = top.throwLine;
int col = top.throwColumn;
if (line == 0 && col == 0) { line = interpreter->getLastErrorLine(); col = interpreter->getLastErrorColumn(); }
interpreter->reportError(line, col, "Runtime Error", msg, "");
} }
// Clear inline marker after handling // Clear inline marker after handling
interpreter->clearInlineErrorReported(); interpreter->clearInlineErrorReported();
@ -44,6 +47,10 @@ void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext*
err["message"] = Value(std::string(e.what())); err["message"] = Value(std::string(e.what()));
context->hasThrow = true; context->hasThrow = true;
context->thrownValue = Value(err); context->thrownValue = Value(err);
if (context->throwLine == 0 && context->throwColumn == 0) {
context->throwLine = interpreter->getLastErrorLine();
context->throwColumn = interpreter->getLastErrorColumn();
}
} }
} }
} }
@ -55,9 +62,9 @@ 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);
// Bridge any pending throws from expression evaluation into the context // Bridge any pending throws from expression evaluation into the context
Value pending; Value pending; int pl=0, pc=0;
if (interpreter->consumePendingThrow(pending)) { if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
if (context) { context->hasThrow = true; context->thrownValue = pending; } if (context) { context->hasThrow = true; context->thrownValue = pending; context->throwLine = pl; context->throwColumn = pc; }
} }
// If an inline reporter already handled this error and we are at top level (no try), // If an inline reporter already handled this error and we are at top level (no try),
// avoid reporting it again here. // avoid reporting it again here.
@ -76,9 +83,9 @@ 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; Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown)) { if (interpreter->consumePendingThrow(thrown, &tl, &tc)) {
if (context) { context->hasThrow = true; context->thrownValue = thrown; } if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; }
return; return;
} }
@ -90,8 +97,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; Value thrownInit; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrownInit)) { if (context) { context->hasThrow = true; context->thrownValue = thrownInit; } return; } if (interpreter->consumePendingThrow(thrownInit, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrownInit; context->throwLine = tl; context->throwColumn = tc; } return; }
} }
interpreter->getEnvironment()->define(statement->name.lexeme, value); interpreter->getEnvironment()->define(statement->name.lexeme, value);
} }
@ -124,8 +131,8 @@ 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) {
Value condValIf = statement->condition->accept(evaluator); Value condValIf = statement->condition->accept(evaluator);
Value thrownIf; Value thrownIf; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrownIf)) { if (context) { context->hasThrow = true; context->thrownValue = thrownIf; } return; } if (interpreter->consumePendingThrow(thrownIf, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrownIf; context->throwLine = tl; context->throwColumn = tc; } return; }
if (interpreter->isTruthy(condValIf)) { if (interpreter->isTruthy(condValIf)) {
execute(statement->thenBranch, context); execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) { } else if (statement->elseBranch != nullptr) {
@ -141,7 +148,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.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; context->throwLine = loopContext.throwLine; context->throwColumn = loopContext.throwColumn; } break; }
if (loopContext.hasReturn) { if (loopContext.hasReturn) {
if (context) { if (context) {
context->hasReturn = true; context->hasReturn = true;
@ -169,13 +176,13 @@ void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, E
while (true) { 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.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; context->throwLine = loopContext.throwLine; context->throwColumn = loopContext.throwColumn; } break; }
if (loopContext.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = loopContext.returnValue; } break; } if (loopContext.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = loopContext.returnValue; } break; }
if (loopContext.shouldBreak) { break; } if (loopContext.shouldBreak) { break; }
if (loopContext.shouldContinue) { loopContext.shouldContinue = false; } if (loopContext.shouldContinue) { loopContext.shouldContinue = false; }
Value c = statement->condition->accept(evaluator); Value c = statement->condition->accept(evaluator);
Value thrown; Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; } if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
if (!interpreter->isTruthy(c)) break; if (!interpreter->isTruthy(c)) break;
} }
} }
@ -193,8 +200,8 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
while (true) { while (true) {
if (statement->condition != nullptr) { if (statement->condition != nullptr) {
Value c = statement->condition->accept(evaluator); Value c = statement->condition->accept(evaluator);
Value thrown; Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; } if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
if (!interpreter->isTruthy(c)) break; if (!interpreter->isTruthy(c)) break;
} }
execute(statement->body, &loopContext); execute(statement->body, &loopContext);
@ -215,16 +222,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; Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; } if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
} }
continue; continue;
} }
if (statement->increment != nullptr) { if (statement->increment != nullptr) {
statement->increment->accept(evaluator); statement->increment->accept(evaluator);
Value thrown; Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; } if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
} }
} }
} }
@ -243,14 +250,19 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context) { void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context) {
interpreter->enterTry(); interpreter->enterTry();
// Temporarily detach the reporter so any direct uses (e.g., in Environment) won't print inline
auto savedReporter = interpreter->getErrorReporter();
interpreter->setErrorReporter(nullptr);
ExecutionContext inner; ExecutionContext inner;
if (context) inner.isFunctionBody = context->isFunctionBody; if (context) inner.isFunctionBody = context->isFunctionBody;
execute(statement->tryBlock, &inner); execute(statement->tryBlock, &inner);
// Also capture any pending throw signaled by expressions // Also capture any pending throw signaled by expressions
Value pending; Value pending; int pl=0, pc=0;
if (interpreter->consumePendingThrow(pending)) { if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
inner.hasThrow = true; inner.hasThrow = true;
inner.thrownValue = pending; inner.thrownValue = pending;
inner.throwLine = pl;
inner.throwColumn = pc;
} }
// If thrown, handle catch // If thrown, handle catch
if (inner.hasThrow && statement->catchBlock) { if (inner.hasThrow && statement->catchBlock) {
@ -267,6 +279,8 @@ void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, Execution
execute(statement->catchBlock, &catchCtx); execute(statement->catchBlock, &catchCtx);
inner.hasThrow = catchCtx.hasThrow; inner.hasThrow = catchCtx.hasThrow;
inner.thrownValue = catchCtx.thrownValue; inner.thrownValue = catchCtx.thrownValue;
inner.throwLine = catchCtx.throwLine;
inner.throwColumn = catchCtx.throwColumn;
interpreter->setEnvironment(saved); interpreter->setEnvironment(saved);
} }
// finally always // finally always
@ -274,16 +288,17 @@ void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, Execution
ExecutionContext fctx; ExecutionContext fctx;
fctx.isFunctionBody = inner.isFunctionBody; fctx.isFunctionBody = inner.isFunctionBody;
execute(statement->finallyBlock, &fctx); execute(statement->finallyBlock, &fctx);
if (fctx.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = fctx.returnValue; } return; } if (fctx.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = fctx.returnValue; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (fctx.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = fctx.thrownValue; } return; } if (fctx.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = fctx.thrownValue; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (fctx.shouldBreak) { if (context) { context->shouldBreak = true; } return; } if (fctx.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (fctx.shouldContinue) { if (context) { context->shouldContinue = true; } return; } if (fctx.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
} }
// propagate remaining control flow // propagate remaining control flow
if (inner.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = inner.returnValue; } interpreter->exitTry(); return; } if (inner.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = inner.returnValue; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (inner.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = inner.thrownValue; } interpreter->exitTry(); return; } if (inner.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = inner.thrownValue; context->throwLine = inner.throwLine; context->throwColumn = inner.throwColumn; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (inner.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->exitTry(); return; } if (inner.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (inner.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->exitTry(); return; } if (inner.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
interpreter->setErrorReporter(savedReporter);
interpreter->exitTry(); interpreter->exitTry();
} }
@ -292,6 +307,8 @@ void Executor::visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, Execu
if (context) { if (context) {
context->hasThrow = true; context->hasThrow = true;
context->thrownValue = v; context->thrownValue = v;
context->throwLine = statement->keyword.line;
context->throwColumn = statement->keyword.column;
} }
} }

View File

@ -94,6 +94,12 @@ void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
} }
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) { void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
// Always track last error site
setLastErrorSite(line, column);
// Suppress inline printing while inside try; error will propagate to catch/finally
if (isInTry()) {
return;
}
if (errorReporter) { if (errorReporter) {
errorReporter->reportError(line, column, errorType, message, lexeme); errorReporter->reportError(line, column, errorType, message, lexeme);
} }
@ -433,7 +439,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.hasThrow) { setPendingThrow(context.thrownValue, context.throwLine, context.throwColumn); return NONE_VALUE; }
if (context.hasReturn) { if (context.hasReturn) {
return context.returnValue; return context.returnValue;
} }
@ -472,7 +478,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.hasThrow) { setPendingThrow(context.thrownValue, context.throwLine, context.throwColumn); return NONE_VALUE; }
if (context.hasReturn) { return context.returnValue; } if (context.hasReturn) { return context.returnValue; }
} }

View File

@ -415,8 +415,13 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
} }
std::string code = args[0].asString(); std::string code = args[0].asString();
std::string evalName = "<eval>";
try { try {
// Push eval source for correct error context
if (errorReporter) {
errorReporter->pushSource(code, evalName);
}
// Create a new lexer for the code string // Create a new lexer for the code string
Lexer lexer; Lexer lexer;
lexer.setErrorReporter(errorReporter); lexer.setErrorReporter(errorReporter);
@ -441,6 +446,14 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
"Failed to evaluate code: " + std::string(e.what()), code); "Failed to evaluate code: " + std::string(e.what()), code);
} }
throw std::runtime_error("eval failed: " + std::string(e.what())); throw std::runtime_error("eval failed: " + std::string(e.what()));
} catch (...) {
if (errorReporter) {
errorReporter->popSource();
}
throw;
}
if (errorReporter) {
errorReporter->popSource();
} }
}); });
env->define("eval", Value(evalFunc)); env->define("eval", Value(evalFunc));

View File

@ -3305,6 +3305,16 @@ eval(readFile(path14));
var path15 = fileExists("tests/test_try_catch.bob") ? "tests/test_try_catch.bob" : "../tests/test_try_catch.bob"; var path15 = fileExists("tests/test_try_catch.bob") ? "tests/test_try_catch.bob" : "../tests/test_try_catch.bob";
eval(readFile(path15)); eval(readFile(path15));
var path15a = fileExists("tests/test_try_catch_runtime.bob") ? "tests/test_try_catch_runtime.bob" : "../tests/test_try_catch_runtime.bob";
eval(readFile(path15a));
var path15b = fileExists("tests/test_try_catch_extensive.bob") ? "tests/test_try_catch_extensive.bob" : "../tests/test_try_catch_extensive.bob";
eval(readFile(path15b));
var path15c = fileExists("tests/test_try_catch_edge_cases2.bob") ? "tests/test_try_catch_edge_cases2.bob" : "../tests/test_try_catch_edge_cases2.bob";
eval(readFile(path15c));
var path15d = fileExists("tests/test_try_catch_cross_function.bob") ? "tests/test_try_catch_cross_function.bob" : "../tests/test_try_catch_cross_function.bob";
eval(readFile(path15d));
var path15e = fileExists("tests/test_try_catch_loop_interactions.bob") ? "tests/test_try_catch_loop_interactions.bob" : "../tests/test_try_catch_loop_interactions.bob";
eval(readFile(path15e));
print("\nAll tests passed."); print("\nAll tests passed.");
print("Test suite complete."); print("Test suite complete.");

View File

@ -75,14 +75,16 @@ for(var i = 0; i < 100; i++){
var counter = 0; var counter = 0;
//try{ try{
while(true){ while(true){
print(arr[counter]); print(arr[counter]);
counter++; counter++;
sleep(0.01); //sleep(0.01);
} }
//}catch(e){ }catch(){}
try{
assert(false);
//} }catch(){}
print("done"); print("done");

View File

@ -0,0 +1,24 @@
print("\n--- Test: try/catch across function boundaries ---");
// Throw in callee, catch in caller
func callee(x) { if (x == 0) throw {"message":"zero"}; return x + 1; }
var r1 = 0; var m1 = "";
try { r1 = callee(1); } catch (e) { m1 = e.message; }
try { callee(0); } catch (e) { m1 = e.message; }
assert(r1 == 2 && m1 == "zero", "throw in callee, catch in caller");
// Throw in nested callee
func inner() { throw {"message":"deep"}; }
func outer() { inner(); }
var m2 = "";
try { outer(); } catch (e) { m2 = e.message; }
assert(m2 == "deep", "nested callee throw");
// Finally in caller overrides callee throw
func thrower() { throw {"message":"boom"}; }
func wrapper() { try { thrower(); } finally { return 9; } }
assert(wrapper() == 9, "caller finally overrides");
print("try/catch across functions: PASS");

View File

@ -0,0 +1,66 @@
print("\n--- Test: try/catch/finally (edge cases 2) ---");
// 1) finally throw overrides try return
func h1() {
try { return 1; }
finally { throw "f1"; }
}
var msg1 = "";
try { h1(); } catch (e) { msg1 = e; }
assert(msg1 == "f1", "finally throw overrides return");
// 2) finally return overrides catch rethrow
func h2() {
try { throw {"message":"boom"}; }
catch (e) { throw e; }
finally { return 7; }
}
assert(h2() == 7, "finally return overrides catch rethrow");
// 3) finally throw overrides catch handling
var outer = "";
try {
try { throw {"message":"inner"}; }
catch (e) { /* handled */ }
finally { throw "outer"; }
} catch (e) { outer = e; }
assert(outer == "outer", "finally throw overrides catch");
// 4) rethrow with new error from catch
var out4 = "";
try {
try { throw {"message":"x"}; }
catch (e) { throw {"message":"y"}; }
} catch (e) { out4 = e.message; }
assert(out4 == "y", "rethrow new error");
// 5) catch variable scoping (not visible outside catch block)
var scopeMark = "";
try { var z = e; } catch (err) { scopeMark = "scoped"; }
assert(scopeMark == "scoped", "catch var e is scoped to catch block");
// 6) throw non-dict: number
var tnum = 0; var ttype = "";
try { throw 42; } catch (e) { tnum = e; ttype = type(e); }
assert(tnum == 42 && ttype == "number", "throw number");
// 7) throw from finally with no try error propagates
var msg7 = "";
try {
try { var x = 1; }
finally { throw "F"; }
} catch (e) { msg7 = e; }
assert(msg7 == "F", "finally throw with no prior error propagates");
// 8) try inside finally is handled locally
var log8 = [];
try {
// body
} finally {
try { throw "inner"; } catch (e) { log8.push("handled:" + e); }
}
assert(log8.len() == 1 && log8[0] == "handled:inner", "try/catch inside finally");
print("try/catch/finally edge cases 2: PASS");

View File

@ -0,0 +1,32 @@
print("\n--- Test: try/catch with loops ---");
// Break and continue in finally
var i = 0; var seq = [];
while (true) {
try {
i = i + 1;
seq.push(i);
if (i == 1) {}
}
finally {
if (i < 2) continue;
break;
}
}
assert(i == 2 && seq.len() == 2, "continue/break in finally");
// Throw in loop body caught outside
var hits = [];
for (var j = 0; j < 3; j = j + 1) {
try {
if (j == 1) throw {"message":"bang"};
hits.push(j);
} catch (e) {
hits.push("caught:" + e.message);
}
}
assert(hits.len() == 4 && hits[1] == "caught:bang", "loop catch per-iteration");
print("try/catch with loops: PASS");