diff --git a/src/headers/parsing/ErrorReporter.h b/src/headers/parsing/ErrorReporter.h index ffbac04..fa06771 100644 --- a/src/headers/parsing/ErrorReporter.h +++ b/src/headers/parsing/ErrorReporter.h @@ -34,6 +34,9 @@ private: std::string currentFileName; std::vector callStack; bool hadError = false; + // Support nested sources (e.g., eval of external files) + std::vector> sourceStack; + std::vector 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& callStack); diff --git a/src/headers/runtime/Environment.h b/src/headers/runtime/Environment.h index 567e50e..2aacb33 100644 --- a/src/headers/runtime/Environment.h +++ b/src/headers/runtime/Environment.h @@ -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) { diff --git a/src/headers/runtime/ExecutionContext.h b/src/headers/runtime/ExecutionContext.h index 7c8e911..d55a677 100644 --- a/src/headers/runtime/ExecutionContext.h +++ b/src/headers/runtime/ExecutionContext.h @@ -9,4 +9,6 @@ struct ExecutionContext { bool shouldContinue = false; bool hasThrow = false; Value thrownValue = NONE_VALUE; + int throwLine = 0; + int throwColumn = 0; }; diff --git a/src/headers/runtime/Interpreter.h b/src/headers/runtime/Interpreter.h index 97e95ab..a52e60b 100644 --- a/src/headers/runtime/Interpreter.h +++ b/src/headers/runtime/Interpreter.h @@ -82,8 +82,12 @@ private: std::unique_ptr evaluator; std::unique_ptr executor; // Pending throw propagation from expression evaluation - bool hasPendingThrow = false; - Value pendingThrow = NONE_VALUE; + 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 getEnvironment(); void setEnvironment(std::shared_ptr env); + ErrorReporter* getErrorReporter() const { return errorReporter; } void addFunction(std::shared_ptr 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 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; } diff --git a/src/sources/parsing/ErrorReporter.cpp b/src/sources/parsing/ErrorReporter.cpp index 5aa1fdc..579cdc7 100644 --- a/src/sources/parsing/ErrorReporter.cpp +++ b/src/sources/parsing/ErrorReporter.cpp @@ -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) + diff --git a/src/sources/parsing/Parser.cpp b/src/sources/parsing/Parser.cpp index fe4a08a..2c04ba4 100644 --- a/src/sources/parsing/Parser.cpp +++ b/src/sources/parsing/Parser.cpp @@ -795,8 +795,11 @@ std::shared_ptr Parser::tryStatement() { std::shared_ptr finallyBlock = nullptr; if (match({CATCH})) { consume(OPEN_PAREN, "Expected '(' after 'catch'."); - Token var = consume(IDENTIFIER, "Expected identifier for catch variable."); - catchVar = var; + // 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(); } diff --git a/src/sources/runtime/Evaluator.cpp b/src/sources/runtime/Evaluator.cpp index a9077dc..820c585 100644 --- a/src/sources/runtime/Evaluator.cpp +++ b/src/sources/runtime/Evaluator.cpp @@ -267,11 +267,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr& 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(); - } + 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& expr const std::vector& arr = array.asArray(); if (idx < 0 || idx >= static_cast(arr.size())) { - if (!interpreter->isInTry()) { - interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "Array index out of bounds", ""); - interpreter->markInlineErrorReported(); - } + 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& 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(); - } + 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& expr } } else { - if (!interpreter->isInTry()) { - interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "Can only index arrays and dictionaries", ""); - interpreter->markInlineErrorReported(); - } + 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& 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(); - } + 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"); } } diff --git a/src/sources/runtime/Executor.cpp b/src/sources/runtime/Executor.cpp index b0ebb96..e113726 100644 --- a/src/sources/runtime/Executor.cpp +++ b/src/sources/runtime/Executor.cpp @@ -27,7 +27,10 @@ void Executor::interpret(const std::vector>& 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& 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>& 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& statement, Execu void Executor::visitExpressionStmt(const std::shared_ptr& 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& 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& statement, Exe void Executor::visitIfStmt(const std::shared_ptr& 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& 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& 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& 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& 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& statement, void Executor::visitTryStmt(const std::shared_ptr& 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& 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& 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& statement, Execu if (context) { context->hasThrow = true; context->thrownValue = v; + context->throwLine = statement->keyword.line; + context->throwColumn = statement->keyword.column; } } diff --git a/src/sources/runtime/Interpreter.cpp b/src/sources/runtime/Interpreter.cpp index d894dd3..397caf1 100644 --- a/src/sources/runtime/Interpreter.cpp +++ b/src/sources/runtime/Interpreter.cpp @@ -94,6 +94,12 @@ void Interpreter::setEnvironment(std::shared_ptr 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& 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& 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; } } diff --git a/src/sources/stdlib/BobStdLib.cpp b/src/sources/stdlib/BobStdLib.cpp index 24616c7..5d5bdbc 100644 --- a/src/sources/stdlib/BobStdLib.cpp +++ b/src/sources/stdlib/BobStdLib.cpp @@ -415,8 +415,13 @@ void BobStdLib::addToEnvironment(std::shared_ptr env, Interpreter& } std::string code = args[0].asString(); + std::string evalName = ""; 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 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)); diff --git a/test_bob_language.bob b/test_bob_language.bob index 1ffb21d..631372e 100644 --- a/test_bob_language.bob +++ b/test_bob_language.bob @@ -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."); \ No newline at end of file diff --git a/tests.bob b/tests.bob index 4cf6374..b6aee0c 100644 --- a/tests.bob +++ b/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"); \ No newline at end of file diff --git a/tests/test_try_catch_cross_function.bob b/tests/test_try_catch_cross_function.bob new file mode 100644 index 0000000..39d0f40 --- /dev/null +++ b/tests/test_try_catch_cross_function.bob @@ -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"); + + diff --git a/tests/test_try_catch_edge_cases2.bob b/tests/test_try_catch_edge_cases2.bob new file mode 100644 index 0000000..7529e80 --- /dev/null +++ b/tests/test_try_catch_edge_cases2.bob @@ -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"); + + diff --git a/tests/test_try_catch_loop_interactions.bob b/tests/test_try_catch_loop_interactions.bob new file mode 100644 index 0000000..686c762 --- /dev/null +++ b/tests/test_try_catch_loop_interactions.bob @@ -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"); + +