From 9a186053a690f2216b43355549c8aa7e2959583c Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 20:53:59 +0000
Subject: [PATCH] better error reporting
---
src/Parser/Parser.cpp | 3 +
src/Interpreter/CallStatementNode.hpp | 10 ++-
src/Interpreter/ForStatementNode.hpp | 9 +-
src/Parser/Parser.hpp | 9 ++-
src/Interpreter/ConditionalStatementNode.hpp | 13 ++--
test_scripts/object.vs | 10 +-
src/Interpreter/AssignmentStatementNode.hpp | 49 ++++++++++------
src/Interpreter/DeclareFunctionStatementNode.hpp | 5 +
src/Interpreter/Interpreter.hpp | 17 +++++
src/Interpreter/DeclareVariableStatementNode.hpp | 13 ++--
10 files changed, 90 insertions(+), 48 deletions(-)
diff --git a/src/Interpreter/AssignmentStatementNode.hpp b/src/Interpreter/AssignmentStatementNode.hpp
index 5f4a969..c5eeabc 100644
--- a/src/Interpreter/AssignmentStatementNode.hpp
+++ b/src/Interpreter/AssignmentStatementNode.hpp
@@ -2,6 +2,8 @@
#define INTERPRETER_ASSIGNMENT_STATEMENT_NODE_HPP
#include "StatementNode.hpp"
+// Include for unified runtime Exception
+#include "Interpreter/Interpreter.hpp"
#include "ExpressionNode.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Symbols/Value.hpp"
@@ -36,9 +38,9 @@
const std::string base_ns = symContainer->currentScopeName();
const std::string var_ns = base_ns + ".variables";
if (!symContainer->exists(targetName_, var_ns)) {
- throw std::runtime_error("Variable '" + targetName_ + "' does not exist in namespace: " + var_ns +
- " File: " + filename_ + ", Line: " + std::to_string(line_) +
- ", Column: " + std::to_string(column_));
+ throw Exception(
+ "Variable '" + targetName_ + "' does not exist in namespace: " + var_ns,
+ filename_, line_, column_);
}
auto symbol = symContainer->get(var_ns, targetName_);
// Copy current value for potential nested updates
@@ -48,20 +50,22 @@
// Simple variable assignment
if (propertyPath_.empty()) {
// Type check
- if (newValue.getType() != varValue.getType()) {
- using namespace Variables;
- throw std::runtime_error("Type mismatch assigning to variable '" + targetName_ +
- "': expected '" + TypeToString(varValue.getType()) +
- "' but got '" + TypeToString(newValue.getType()) +
- "' File: " + filename_ + ", Line: " + std::to_string(line_) +
- ", Column: " + std::to_string(column_));
- }
+ if (newValue.getType() != varValue.getType()) {
+ using namespace Variables;
+ throw Exception(
+ "Type mismatch assigning to variable '" + targetName_ +
+ "': expected '" + TypeToString(varValue.getType()) +
+ "' but got '" + TypeToString(newValue.getType()) + "'",
+ filename_, line_, column_);
+ }
symbol->setValue(newValue);
return;
}
// Nested object property assignment
if (varValue.getType() != Variables::Type::OBJECT) {
- throw std::runtime_error("Attempting to assign property on non-object variable '" + targetName_ + "'");
+ throw Exception(
+ "Attempting to assign property on non-object variable '" + targetName_ + "'",
+ filename_, line_, column_);
}
// Traverse into nested maps
using ObjectMap = Value::ObjectMap;
@@ -71,11 +75,15 @@
const auto & key = propertyPath_[i];
auto it = currMap->find(key);
if (it == currMap->end()) {
- throw std::runtime_error("Property '" + key + "' not found on object '" + targetName_ + "'");
+ throw Exception(
+ "Property '" + key + "' not found on object '" + targetName_ + "'",
+ filename_, line_, column_);
}
Value & child = it->second;
if (child.getType() != Variables::Type::OBJECT) {
- throw std::runtime_error("Property '" + key + "' is not an object, cannot assign nested property");
+ throw Exception(
+ "Property '" + key + "' is not an object, cannot assign nested property",
+ filename_, line_, column_);
}
currMap = &std::get<ObjectMap>(child.get());
}
@@ -83,15 +91,18 @@
const std::string & lastKey = propertyPath_.back();
auto it = currMap->find(lastKey);
if (it == currMap->end()) {
- throw std::runtime_error("Property '" + lastKey + "' not found on object '" + targetName_ + "'");
+ throw Exception(
+ "Property '" + lastKey + "' not found on object '" + targetName_ + "'",
+ filename_, line_, column_);
}
// Type check against existing property
if (newValue.getType() != it->second.getType()) {
using namespace Variables;
- throw std::runtime_error("Type mismatch for property '" + lastKey + "': expected '" +
- TypeToString(it->second.getType()) + "' but got '" +
- TypeToString(newValue.getType()) + "' File: " + filename_ +
- ", Line: " + std::to_string(line_) + ", Column: " + std::to_string(column_));
+ throw Exception(
+ "Type mismatch for property '" + lastKey + "': expected '" +
+ TypeToString(it->second.getType()) + "' but got '" +
+ TypeToString(newValue.getType()) + "'",
+ filename_, line_, column_);
}
// Assign and write back to symbol
(*currMap)[lastKey] = newValue;
diff --git a/src/Interpreter/CallStatementNode.hpp b/src/Interpreter/CallStatementNode.hpp
index 224ac16..5724318 100644
--- a/src/Interpreter/CallStatementNode.hpp
+++ b/src/Interpreter/CallStatementNode.hpp
@@ -7,6 +7,8 @@
#include "ExpressionNode.hpp"
#include "Interpreter/Interpreter.hpp"
+// Include for unified runtime Exception (inherits BaseException)
+#include "BaseException.hpp"
#include "Interpreter/OperationContainer.hpp"
#include "StatementNode.hpp"
#include "Symbols/FunctionSymbol.hpp"
@@ -54,15 +56,17 @@
const std::string fnSymNs = currentNs + ".functions";
auto sym = sc->get(fnSymNs, functionName_);
if (!sym || sym->getKind() != Kind::Function) {
- throw std::runtime_error("Function not found: " + functionName_);
+ throw Exception("Function not found: " + functionName_, filename_, line_, column_);
}
auto funcSym = std::static_pointer_cast<FunctionSymbol>(sym);
// Check parameter count
const auto & params = funcSym->parameters();
if (params.size() != argValues.size()) {
- throw std::runtime_error("Function '" + functionName_ + "' expects " + std::to_string(params.size()) +
- " args, got " + std::to_string(argValues.size()));
+ throw Exception(
+ "Function '" + functionName_ + "' expects " + std::to_string(params.size()) +
+ " args, got " + std::to_string(argValues.size()),
+ filename_, line_, column_);
}
// Enter function scope to bind parameters and execute body
diff --git a/src/Interpreter/ConditionalStatementNode.hpp b/src/Interpreter/ConditionalStatementNode.hpp
index 254f4a9..efb0730 100644
--- a/src/Interpreter/ConditionalStatementNode.hpp
+++ b/src/Interpreter/ConditionalStatementNode.hpp
@@ -4,8 +4,10 @@
#include <vector>
#include <memory>
#include <string>
- #include "Interpreter/StatementNode.hpp"
- #include "Interpreter/ExpressionNode.hpp"
+#include "Interpreter/StatementNode.hpp"
+// Include for unified runtime Exception
+#include "Interpreter/Interpreter.hpp"
+#include "Interpreter/ExpressionNode.hpp"
namespace Interpreter {
@@ -36,10 +38,9 @@
bool cond = false;
if (val.getType() == Symbols::Variables::Type::BOOLEAN) {
cond = val.get<bool>();
- } else {
- throw std::runtime_error("Condition did not evaluate to boolean at " + filename_ +
- ":" + std::to_string(line_) + "," + std::to_string(column_));
- }
+ } else {
+ throw Exception("Condition did not evaluate to boolean", filename_, line_, column_);
+ }
// Execute appropriate branch
const auto & branch = cond ? thenBranch_ : elseBranch_;
for (const auto & stmt : branch) {
diff --git a/src/Interpreter/DeclareFunctionStatementNode.hpp b/src/Interpreter/DeclareFunctionStatementNode.hpp
index bb3eea7..f2ddb04 100644
--- a/src/Interpreter/DeclareFunctionStatementNode.hpp
+++ b/src/Interpreter/DeclareFunctionStatementNode.hpp
@@ -8,6 +8,8 @@
#include "ExpressionNode.hpp"
#include "Interpreter.hpp"
#include "Interpreter/StatementNode.hpp"
+// Include for unified runtime Exception
+#include "Interpreter/Interpreter.hpp"
#include "Symbols/ParameterContainer.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Symbols/SymbolFactory.hpp"
@@ -37,8 +39,7 @@
void interpret(Interpreter & /*interpreter*/) const override {
//Symbols::Value value = expression_->evaluate(interpreter);
if (Symbols::SymbolContainer::instance()->exists(functionName_)) {
- throw std::runtime_error("Function already declared: " + functionName_ + " file: " + filename_ +
- ", line: " + std::to_string(line_) + ", column: " + std::to_string(column_));
+ throw Exception("Function already declared: " + functionName_, filename_, line_, column_);
}
const auto func = Symbols::SymbolFactory::createFunction(functionName_, ns, params_, "", returnType_);
Symbols::SymbolContainer::instance()->add(func);
diff --git a/src/Interpreter/DeclareVariableStatementNode.hpp b/src/Interpreter/DeclareVariableStatementNode.hpp
index 7dbfa3f..1ee7874 100644
--- a/src/Interpreter/DeclareVariableStatementNode.hpp
+++ b/src/Interpreter/DeclareVariableStatementNode.hpp
@@ -8,6 +8,8 @@
#include "ExpressionNode.hpp"
#include "Interpreter.hpp"
#include "Interpreter/StatementNode.hpp"
+// Include for unified runtime Exception
+#include "Interpreter/Interpreter.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Symbols/SymbolFactory.hpp"
@@ -35,18 +37,17 @@
Symbols::Value value = expression_->evaluate(interpreter);
// Check for duplicate declaration
if (Symbols::SymbolContainer::instance()->exists(variableName_)) {
- throw std::runtime_error("Variable already declared: " + variableName_ + " File: " + filename_ +
- ", Line: " + std::to_string(line_) + ", Column: " + std::to_string(column_));
+ throw Exception("Variable already declared: " + variableName_, filename_, line_, column_);
}
// Enforce type correctness: the evaluated value must match the declared type
if (value.getType() != variableType_) {
using namespace Symbols::Variables;
std::string expected = TypeToString(variableType_);
std::string actual = TypeToString(value.getType());
- throw std::runtime_error("Type mismatch for variable '" + variableName_ +
- "': expected '" + expected + "' but got '" + actual +
- "' File: " + filename_ + ", Line: " + std::to_string(line_) +
- ", Column: " + std::to_string(column_));
+ throw Exception(
+ "Type mismatch for variable '" + variableName_ +
+ "': expected '" + expected + "' but got '" + actual + "'",
+ filename_, line_, column_);
}
// Create and add the variable symbol
const auto variable = Symbols::SymbolFactory::createVariable(variableName_, value, ns, variableType_);
diff --git a/src/Interpreter/ForStatementNode.hpp b/src/Interpreter/ForStatementNode.hpp
index 6d33723..0a1092e 100644
--- a/src/Interpreter/ForStatementNode.hpp
+++ b/src/Interpreter/ForStatementNode.hpp
@@ -5,8 +5,10 @@
#include <memory>
#include <string>
#include <stdexcept>
- #include "Interpreter/StatementNode.hpp"
- #include "Interpreter/ExpressionNode.hpp"
+#include "Interpreter/StatementNode.hpp"
+// Include for unified runtime Exception
+#include "Interpreter/Interpreter.hpp"
+#include "Interpreter/ExpressionNode.hpp"
#include "Symbols/Value.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Symbols/SymbolFactory.hpp"
@@ -45,8 +47,7 @@
// Evaluate iterable expression
auto iterableVal = iterableExpr_->evaluate(interpreter);
if (iterableVal.getType() != Variables::Type::OBJECT) {
- throw std::runtime_error("For-in loop applied to non-object at " + filename_ + ":" + std::to_string(line_) +
- "," + std::to_string(column_));
+ throw Exception("For-in loop applied to non-object", filename_, line_, column_);
}
// Access underlying object map
const auto & objMap = std::get<Value::ObjectMap>(iterableVal.get());
diff --git a/src/Interpreter/Interpreter.hpp b/src/Interpreter/Interpreter.hpp
index e67443b..e573552 100644
--- a/src/Interpreter/Interpreter.hpp
+++ b/src/Interpreter/Interpreter.hpp
@@ -7,6 +7,23 @@
#include "Interpreter/Operation.hpp"
#include "Interpreter/OperationContainer.hpp"
#include "Symbols/SymbolContainer.hpp"
+#include "BaseException.hpp"
+
+// Exception type for runtime errors, includes file, line, and column context
+namespace Interpreter {
+class Exception : public BaseException {
+public:
+ Exception(const std::string &msg, const std::string &filename, int line, size_t column) {
+ rawMessage_ = msg;
+ context_ = std::string(" in file \"") + filename + "\" at line: " + std::to_string(line)
+ + ", column: " + std::to_string(column);
+ formattedMessage_ = formatMessage();
+ }
+ std::string formatMessage() const override {
+ return std::string("[Runtime ERROR] >>") + context_ + " << : " + rawMessage_;
+ }
+};
+} // namespace Interpreter
namespace Interpreter {
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index c802a9d..c3241c2 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -1,4 +1,6 @@
#include "Parser/Parser.hpp"
+// Static filename for unified error reporting in Parser::Exception
+std::string Parser::Parser::Exception::current_filename_;
#include <stack>
@@ -781,6 +783,7 @@
void Parser::parseScript(const std::vector<Lexer::Tokens::Token> & tokens, std::string_view input_string,
const std::string & filename) {
+ ::Parser::Parser::Exception::current_filename_ = filename;
tokens_ = tokens;
input_str_view_ = input_string;
current_token_index_ = 0;
diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp
index ed08bde..0acedf7 100644
--- a/src/Parser/Parser.hpp
+++ b/src/Parser/Parser.hpp
@@ -22,11 +22,13 @@
class Exception : public BaseException {
public:
using BaseException::BaseException;
+ // Filename for error reporting
+ static std::string current_filename_;
Exception(const std::string & msg, const std::string & expected, const Lexer::Tokens::Token & token) {
rawMessage_ = msg + ": " + token.dump();
- context_ =
- " at line: " + std::to_string(token.line_number) + ", column: " + std::to_string(token.column_number);
+ context_ = " in file \"" + current_filename_ + "\" at line: " + std::to_string(token.line_number)
+ + ", column: " + std::to_string(token.column_number);
if (expected.empty() == false) {
rawMessage_ += " (expected: " + expected + ")";
}
@@ -38,7 +40,8 @@
if (expected.empty() == false) {
rawMessage_ += " (expected: " + expected + ")";
}
- context_ = " at line: " + std::to_string(line) + ", column: " + std::to_string(col);
+ context_ = " in file \"" + current_filename_ + "\" at line: " + std::to_string(line)
+ + ", column: " + std::to_string(col);
formattedMessage_ = formatMessage();
}
diff --git a/test_scripts/object.vs b/test_scripts/object.vs
index 753bc57..ce29033 100644
--- a/test_scripts/object.vs
+++ b/test_scripts/object.vs
@@ -1,6 +1,6 @@
object $person = {
- string name: "Szoni",
+ string name: "Batman",
int age: 37
};
@@ -8,7 +8,7 @@
object $person2 = {
- string name: "Not Szoni",
+ string name: "Not Batman",
int age: 37,
object children: {
string name: "Child1",
@@ -24,9 +24,9 @@
printnl("Person name: ", $person_name);
-printnl("Child1 old age: ",$person2->children->age);
-$person2->children->age = $person2->children->age + 2;
-printnl("Child1 new age: ",$person2->children->age);
+printnl("Child1 old age: ", $person2->children->age);
+$person2->children->age = 22;
+printnl("Child1 new age: ", $person2->children->age);
int $age = 10;
if ($person2->children->age > 18) {
--
Gitblit v1.9.3