Try Catch bug fixing
This commit is contained in:
parent
227586c583
commit
fc63c3e46f
@ -34,6 +34,9 @@ private:
|
||||
std::string currentFileName;
|
||||
std::vector<std::string> callStack;
|
||||
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:
|
||||
ErrorReporter() = default;
|
||||
@ -58,6 +61,10 @@ public:
|
||||
void pushCallStack(const std::string& functionName);
|
||||
void popCallStack();
|
||||
|
||||
// Source push/pop for eval
|
||||
void pushSource(const std::string& source, const std::string& fileName);
|
||||
void popSource();
|
||||
|
||||
private:
|
||||
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);
|
||||
|
||||
@ -29,6 +29,7 @@ public:
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
}
|
||||
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
||||
|
||||
// Optimized define with inline
|
||||
inline void define(const std::string& name, const Value& value) {
|
||||
|
||||
@ -9,4 +9,6 @@ struct ExecutionContext {
|
||||
bool shouldContinue = false;
|
||||
bool hasThrow = false;
|
||||
Value thrownValue = NONE_VALUE;
|
||||
int throwLine = 0;
|
||||
int throwColumn = 0;
|
||||
};
|
||||
|
||||
@ -84,6 +84,10 @@ private:
|
||||
// Pending throw propagation from expression evaluation
|
||||
bool hasPendingThrow = false;
|
||||
Value pendingThrow = NONE_VALUE;
|
||||
int pendingThrowLine = 0;
|
||||
int pendingThrowColumn = 0;
|
||||
int lastErrorLine = 0;
|
||||
int lastErrorColumn = 0;
|
||||
int tryDepth = 0;
|
||||
bool inlineErrorReported = false;
|
||||
|
||||
@ -106,6 +110,7 @@ public:
|
||||
bool isInteractiveMode() const;
|
||||
std::shared_ptr<Environment> getEnvironment();
|
||||
void setEnvironment(std::shared_ptr<Environment> env);
|
||||
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
||||
|
||||
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 = "");
|
||||
@ -123,8 +128,8 @@ public:
|
||||
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
|
||||
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; }
|
||||
void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
|
||||
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
|
||||
void enterTry() { tryDepth++; }
|
||||
void exitTry() { if (tryDepth > 0) tryDepth--; }
|
||||
@ -133,6 +138,10 @@ public:
|
||||
bool hasInlineErrorReported() const { return inlineErrorReported; }
|
||||
void clearInlineErrorReported() { inlineErrorReported = false; }
|
||||
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; }
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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) {
|
||||
hadError = true;
|
||||
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
||||
@ -75,6 +98,8 @@ void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
||||
|
||||
if (!context.fileName.empty()) {
|
||||
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) +
|
||||
|
||||
@ -795,8 +795,11 @@ std::shared_ptr<Stmt> Parser::tryStatement() {
|
||||
std::shared_ptr<Stmt> finallyBlock = nullptr;
|
||||
if (match({CATCH})) {
|
||||
consume(OPEN_PAREN, "Expected '(' after 'catch'.");
|
||||
// Allow optional identifier: catch() or catch(e)
|
||||
if (!check(CLOSE_PAREN)) {
|
||||
Token var = consume(IDENTIFIER, "Expected identifier for catch variable.");
|
||||
catchVar = var;
|
||||
}
|
||||
consume(CLOSE_PAREN, "Expected ')' after catch variable.");
|
||||
catchBlock = statement();
|
||||
}
|
||||
|
||||
@ -267,11 +267,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
||||
if (array.isArray()) {
|
||||
// Handle array indexing
|
||||
if (!index.isNumber()) {
|
||||
if (!interpreter->isInTry()) {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -279,11 +277,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
||||
const std::vector<Value>& arr = array.asArray();
|
||||
|
||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||
if (!interpreter->isInTry()) {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -292,11 +288,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
||||
} else if (array.isDict()) {
|
||||
// Handle dictionary indexing
|
||||
if (!index.isString()) {
|
||||
if (!interpreter->isInTry()) {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -311,11 +305,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!interpreter->isInTry()) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -446,11 +438,9 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
||||
return Value(anyFn);
|
||||
}
|
||||
|
||||
if (!interpreter->isInTry()) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,10 @@ void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
||||
auto it = d.find("message");
|
||||
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
|
||||
interpreter->clearInlineErrorReported();
|
||||
@ -44,6 +47,10 @@ void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext*
|
||||
err["message"] = Value(std::string(e.what()));
|
||||
context->hasThrow = true;
|
||||
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) {
|
||||
execute(statement, context);
|
||||
// Bridge any pending throws from expression evaluation into the context
|
||||
Value pending;
|
||||
if (interpreter->consumePendingThrow(pending)) {
|
||||
if (context) { context->hasThrow = true; context->thrownValue = pending; }
|
||||
Value pending; int pl=0, pc=0;
|
||||
if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
|
||||
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),
|
||||
// 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) {
|
||||
Value value = statement->expression->accept(evaluator);
|
||||
Value thrown;
|
||||
if (interpreter->consumePendingThrow(thrown)) {
|
||||
if (context) { context->hasThrow = true; context->thrownValue = thrown; }
|
||||
Value thrown; int tl=0, tc=0;
|
||||
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) {
|
||||
if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; }
|
||||
return;
|
||||
}
|
||||
|
||||
@ -90,8 +97,8 @@ void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, Execution
|
||||
Value value = NONE_VALUE;
|
||||
if (statement->initializer != nullptr) {
|
||||
value = statement->initializer->accept(evaluator);
|
||||
Value thrownInit;
|
||||
if (interpreter->consumePendingThrow(thrownInit)) { if (context) { context->hasThrow = true; context->thrownValue = thrownInit; } return; }
|
||||
Value thrownInit; int tl=0, tc=0;
|
||||
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);
|
||||
}
|
||||
@ -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) {
|
||||
Value condValIf = statement->condition->accept(evaluator);
|
||||
Value thrownIf;
|
||||
if (interpreter->consumePendingThrow(thrownIf)) { if (context) { context->hasThrow = true; context->thrownValue = thrownIf; } return; }
|
||||
Value thrownIf; int tl=0, tc=0;
|
||||
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)) {
|
||||
execute(statement->thenBranch, context);
|
||||
} 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))) {
|
||||
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;
|
||||
@ -169,13 +176,13 @@ void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, E
|
||||
|
||||
while (true) {
|
||||
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.shouldBreak) { break; }
|
||||
if (loopContext.shouldContinue) { loopContext.shouldContinue = false; }
|
||||
Value c = statement->condition->accept(evaluator);
|
||||
Value thrown;
|
||||
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||
Value thrown; int tl=0, tc=0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -193,8 +200,8 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
|
||||
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; }
|
||||
Value thrown; int tl=0, tc=0;
|
||||
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;
|
||||
}
|
||||
execute(statement->body, &loopContext);
|
||||
@ -215,16 +222,16 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
|
||||
loopContext.shouldContinue = false;
|
||||
if (statement->increment != nullptr) {
|
||||
statement->increment->accept(evaluator);
|
||||
Value thrown;
|
||||
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||
Value thrown; int tl=0, tc=0;
|
||||
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (statement->increment != nullptr) {
|
||||
statement->increment->accept(evaluator);
|
||||
Value thrown;
|
||||
if (interpreter->consumePendingThrow(thrown)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; } break; }
|
||||
Value thrown; int tl=0, tc=0;
|
||||
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) {
|
||||
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;
|
||||
if (context) inner.isFunctionBody = context->isFunctionBody;
|
||||
execute(statement->tryBlock, &inner);
|
||||
// Also capture any pending throw signaled by expressions
|
||||
Value pending;
|
||||
if (interpreter->consumePendingThrow(pending)) {
|
||||
Value pending; int pl=0, pc=0;
|
||||
if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
|
||||
inner.hasThrow = true;
|
||||
inner.thrownValue = pending;
|
||||
inner.throwLine = pl;
|
||||
inner.throwColumn = pc;
|
||||
}
|
||||
// If thrown, handle catch
|
||||
if (inner.hasThrow && statement->catchBlock) {
|
||||
@ -267,6 +279,8 @@ void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, Execution
|
||||
execute(statement->catchBlock, &catchCtx);
|
||||
inner.hasThrow = catchCtx.hasThrow;
|
||||
inner.thrownValue = catchCtx.thrownValue;
|
||||
inner.throwLine = catchCtx.throwLine;
|
||||
inner.throwColumn = catchCtx.throwColumn;
|
||||
interpreter->setEnvironment(saved);
|
||||
}
|
||||
// finally always
|
||||
@ -274,16 +288,17 @@ void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, Execution
|
||||
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; }
|
||||
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; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
|
||||
if (fctx.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
|
||||
if (fctx.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); 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; }
|
||||
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; context->throwLine = inner.throwLine; context->throwColumn = inner.throwColumn; } interpreter->setErrorReporter(savedReporter); 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->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
|
||||
interpreter->setErrorReporter(savedReporter);
|
||||
interpreter->exitTry();
|
||||
}
|
||||
|
||||
@ -292,6 +307,8 @@ void Executor::visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, Execu
|
||||
if (context) {
|
||||
context->hasThrow = true;
|
||||
context->thrownValue = v;
|
||||
context->throwLine = statement->keyword.line;
|
||||
context->throwColumn = statement->keyword.column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
// 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) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
@ -472,7 +478,7 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
||||
|
||||
for (const auto& stmt : function->body) {
|
||||
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; }
|
||||
}
|
||||
|
||||
|
||||
@ -415,8 +415,13 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
||||
}
|
||||
|
||||
std::string code = args[0].asString();
|
||||
std::string evalName = "<eval>";
|
||||
|
||||
try {
|
||||
// Push eval source for correct error context
|
||||
if (errorReporter) {
|
||||
errorReporter->pushSource(code, evalName);
|
||||
}
|
||||
// Create a new lexer for the code string
|
||||
Lexer lexer;
|
||||
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);
|
||||
}
|
||||
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));
|
||||
|
||||
@ -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";
|
||||
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("Test suite complete.");
|
||||
10
tests.bob
10
tests.bob
@ -75,14 +75,16 @@ for(var i = 0; i < 100; i++){
|
||||
|
||||
var counter = 0;
|
||||
|
||||
//try{
|
||||
try{
|
||||
while(true){
|
||||
print(arr[counter]);
|
||||
counter++;
|
||||
sleep(0.01);
|
||||
//sleep(0.01);
|
||||
}
|
||||
//}catch(e){
|
||||
}catch(){}
|
||||
try{
|
||||
assert(false);
|
||||
|
||||
//}
|
||||
}catch(){}
|
||||
|
||||
print("done");
|
||||
24
tests/test_try_catch_cross_function.bob
Normal file
24
tests/test_try_catch_cross_function.bob
Normal 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");
|
||||
|
||||
|
||||
66
tests/test_try_catch_edge_cases2.bob
Normal file
66
tests/test_try_catch_edge_cases2.bob
Normal 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");
|
||||
|
||||
|
||||
32
tests/test_try_catch_loop_interactions.bob
Normal file
32
tests/test_try_catch_loop_interactions.bob
Normal 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");
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user