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