From 55abb4f6f81fc370e349385b38dffb05fa9d5dcb Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Sat, 19 Apr 2025 20:36:38 +0000
Subject: [PATCH] add vscodium / vscode and vim syntax highlight

---
 src/Parser/Parser.cpp                                                   |   10 
 src/Interpreter/ExpressionBuilder.hpp                                   |    4 
 test_scripts/test_error.vs                                              |    1 
 assets/vim/syntax/voidscript.vim                                        |   77 ++++++
 test_error.vs                                                           |    1 
 assets/vim/indent/voidscript.vim                                        |   54 ++++
 assets/vscode/voidscript-syntax/LICENSE                                 |   21 +
 assets/vscode/voidscript-syntax/README.md                               |   57 ++++
 test_scripts/test_throw_error.vs                                        |    2 
 assets/vscode/voidscript-syntax/create-vscode-package.sh                |   28 ++
 assets/vscode/voidscript-syntax/src/grammar/voidscript.tmLanguage.json  |   43 +++
 assets/vscode/voidscript-syntax/.gitignore                              |   11 
 test_scripts/test_error2.vs                                             |    1 
 cli/main.cpp                                                            |   28 +
 src/VoidScript.hpp                                                      |   11 
 src/Parser/ParsedExpression.hpp                                         |    4 
 assets/vim/ftdetect/voidscript.vim                                      |    1 
 assets/vscode/voidscript-syntax/package.json                            |   44 +++
 src/Modules/BuiltIn/PrintModule.hpp                                     |    9 
 test_scripts/throw_error_test.vs                                        |    1 
 assets/vscode/voidscript-syntax/tsconfig.json                           |   12 +
 assets/vscode/voidscript-syntax/src/grammar/language-configuration.json |   82 ++++++
 src/Interpreter/CallExpressionNode.hpp                                  |  143 ++++++-----
 src/Modules/BaseModule.hpp                                              |   16 +
 24 files changed, 584 insertions(+), 77 deletions(-)

diff --git a/assets/vim/ftdetect/voidscript.vim b/assets/vim/ftdetect/voidscript.vim
new file mode 100644
index 0000000..eb73eb6
--- /dev/null
+++ b/assets/vim/ftdetect/voidscript.vim
@@ -0,0 +1 @@
+au BufRead,BufNewFile *.vs,*.vscript set filetype=voidscript
diff --git a/assets/vim/indent/voidscript.vim b/assets/vim/indent/voidscript.vim
new file mode 100644
index 0000000..6d29c94
--- /dev/null
+++ b/assets/vim/indent/voidscript.vim
@@ -0,0 +1,54 @@
+" Advanced VoidScript indent
+if exists("b:did_indent")
+  finish
+endif
+let b:did_indent = 1
+
+setlocal indentexpr=GetVoidScriptIndent()
+setlocal indentkeys=o,O,0},0),=else,=elif
+
+function! GetVoidScriptIndent()
+  let lnum = prevnonblank(v:lnum - 1)
+  if lnum == 0
+    return 0
+  endif
+
+  let prevline = getline(lnum)
+  let currline = getline(v:lnum)
+
+  " Strip comments
+  let prev = substitute(prevline, '#.*$', '', '')
+  let prev = substitute(prev, '//.*$', '', '')
+  let curr = substitute(currline, '#.*$', '', '')
+  let curr = substitute(curr, '//.*$', '', '')
+
+  let indent = indent(lnum)
+
+  " De-indent for lines that start with } or )
+  if curr =~ '^\s*[\]\)}]'
+    return indent - &shiftwidth
+  endif
+
+  " De-indent for else or elif
+  if curr =~ '^\s*\(else\|elif\)\>'
+    return indent - &shiftwidth
+  endif
+
+  " Object inline literals: ignore increase if likely inline
+  if prev =~ '^\s*object\s\+\$\?\w\+\s*=\s*{'
+    return indent
+  endif
+
+  " Increase indent after these block starters
+  if prev =~ '\v<((if|else|elif|for|while|function|class|object)\b.*)?{[ \t]*$'
+    return indent + &shiftwidth
+  endif
+
+  " Increase indent for lines ending with just a {
+  if prev =~ '{\s*$'
+    return indent + &shiftwidth
+  endif
+
+  " Keep same indent
+  return indent
+endfunction
diff --git a/assets/vim/syntax/voidscript.vim b/assets/vim/syntax/voidscript.vim
new file mode 100644
index 0000000..3c5490c
--- /dev/null
+++ b/assets/vim/syntax/voidscript.vim
@@ -0,0 +1,77 @@
+" Vim syntax file for VoidScript
+" Save as ~/.vim/syntax/voidscript.vim
+
+if exists("b:current_syntax")
+  finish
+endif
+
+
+" --- Keywords ---
+syntax keyword voidscriptKeyword const function return for if else new sizeof this
+
+" --- Access Modifiers ---
+syntax keyword voidscriptAccess public private protected
+
+" --- Types ---
+syntax keyword voidscriptType int string float double boolean object class void
+
+" --- Control values ---
+syntax keyword voidscriptBoolean true false null
+
+" --- Variable Names ---
+syntax match voidscriptVariable /\$\k\+/
+
+" --- Class definitions ---
+syntax match voidscriptClass /\<class\s\+\k\+\>/
+
+" --- Object property access (this->something or object->key) ---
+syntax match voidscriptObjectAccess /\<this\>\|->/
+
+" --- Function calls (e.g. func(...)) ---
+syntax match voidscriptFunction /\<\k\+\s*(/ containedin=ALL
+
+" --- Numbers ---
+syntax match voidscriptNumber /\<\d\+\(\.\d\+\)\?\>/
+
+" --- Strings with variable highlighting ---
+syntax region voidscriptString start=/"/ skip=/\\"/ end=/"/ contains=voidscriptVariable
+
+" --- Object literal keys (key: value) ---
+syntax match voidscriptObjectKey /\<\k\+\>\s*:/
+
+" Comments
+syntax match voidscriptComment "#.*"
+syntax match voidscriptComment "\/\/.*" contains=voidscriptTodo
+highlight link voidscriptComment Comment
+
+" Optional: highlight TODO, FIXME, NOTE in comments
+syntax match voidscriptTodo "\(TODO\|FIXME\|NOTE\):" contained
+highlight link voidscriptTodo Todo
+
+
+" --- Operators ---
+syntax match voidscriptOperator /==\|!=\|<=\|>=\|[-+*/%<>=!&|]/
+
+" --- Braces & Delimiters ---
+syntax match voidscriptBraces /[{}[\]()]/
+syntax match voidscriptArrow /->/
+
+" --- Highlight groups ---
+highlight link voidscriptKeyword Keyword
+highlight link voidscriptAccess StorageClass
+highlight link voidscriptType Type
+highlight link voidscriptBoolean Boolean
+highlight link voidscriptVariable Identifier
+highlight link voidscriptFunction Function
+highlight link voidscriptNumber Number
+highlight link voidscriptString String
+highlight link voidscriptComment Comment
+highlight link voidscriptTodo Todo
+highlight link voidscriptOperator Operator
+highlight link voidscriptBraces Delimiter
+highlight link voidscriptArrow Operator
+highlight link voidscriptClass Structure
+highlight link voidscriptObjectAccess Operator
+highlight link voidscriptObjectKey Identifier
+
+let b:current_syntax = "voidscript"
diff --git a/assets/vscode/voidscript-syntax/.gitignore b/assets/vscode/voidscript-syntax/.gitignore
new file mode 100644
index 0000000..de93163
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/.gitignore
@@ -0,0 +1,11 @@
+# Node.js
+node_modules/
+.vscode/
+*.vsix
+.idea/
+*.sublime-workspace
+*.sublime-project
+.DS_Store
+Thumbs.db
+package-lock.json
+.vscode/settings.json
diff --git a/assets/vscode/voidscript-syntax/LICENSE b/assets/vscode/voidscript-syntax/LICENSE
new file mode 100644
index 0000000..dac7157
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 fszontagh
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/assets/vscode/voidscript-syntax/README.md b/assets/vscode/voidscript-syntax/README.md
new file mode 100644
index 0000000..cfbb8e1
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/README.md
@@ -0,0 +1,57 @@
+# VoidScript Syntax Highlighting
+
+This Visual Studio Code extension provides syntax highlighting for the [VoidScript](https://github.com/fszontagh/voidscript) scripting language.
+
+## Features
+
+- Syntax highlighting for VoidScript source files with extensions `.vs` and `.voidscript`.
+
+## Requirements
+
+- Visual Studio Code version 1.58.0 or higher or compatible VSCodium
+
+## Installation
+
+### From Visual Studio Marketplace
+
+1. Open the Extensions view (`Ctrl+Shift+X` / `Cmd+Shift+X` on macOS).
+2. Search for **VoidScript Syntax**.
+3. Click **Install**.
+
+### From VSIX Package
+
+1. Download the `voidscript-syntax-<version>.vsix` file from the [Releases](https://github.com/fszontagh/voidscript/releases) page.
+2. In VS Code, open the Command Palette (`Ctrl+Shift+P`), then select **Extensions: Install from VSIX...**.
+3. Choose the downloaded `.vsix` file.
+
+## Development & Building
+
+To build and package the extension from source:
+
+```bash
+git clone https://github.com/fszontagh/voidscript.git
+cd voidscript/assets/vscode/voidscript-syntax
+npm install
+npm run build
+```
+
+After building, a `.vsix` package (e.g., `voidscript-syntax-1.0.0.vsix`) will be generated in the current directory.
+
+To install the extension from file: `code --install-extension voidscript-syntax-1.0.0.vsix`
+
+## Usage
+
+Open a VoidScript file (extension `.vs` or `.voidscript`) in VS Code. The syntax highlighting will be applied automatically.
+
+## Contributing
+
+Contributions to the grammar and syntax definitions are welcome!
+
+1. Fork the repository and create a new branch.
+2. Make changes under `src/grammar/`.
+3. Commit and push your changes.
+4. Open a Pull Request.
+
+## License
+
+MIT License. See the [LICENSE](LICENSE) file for details.
diff --git a/assets/vscode/voidscript-syntax/create-vscode-package.sh b/assets/vscode/voidscript-syntax/create-vscode-package.sh
new file mode 100755
index 0000000..7347332
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/create-vscode-package.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# Check if 'vsce' (Visual Studio Code Extension Manager) is installed
+if ! command -v vsce &> /dev/null
+then
+    echo "'vsce' not found! Please install it to proceed."
+    echo "Install with: npm install -g vsce"
+    exit 1
+fi
+
+# Check if the package.json file exists
+if [ ! -f "package.json" ]; then
+    echo "package.json not found. Are you running this script in the extension's root directory?"
+    exit 1
+fi
+
+# Start packaging the extension
+echo "Packaging extension..."
+
+vsce package
+
+# If packaging succeeded, inform the user
+if [ $? -eq 0 ]; then
+    echo "Extension package created successfully!"
+else
+    echo "Error: Packaging failed."
+    exit 1
+fi
diff --git a/assets/vscode/voidscript-syntax/package.json b/assets/vscode/voidscript-syntax/package.json
new file mode 100644
index 0000000..c72acc9
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/package.json
@@ -0,0 +1,44 @@
+{
+    "name": "voidscript-syntax",
+    "displayName": "VoidScript Syntax",
+    "description": "Syntax highlighting for VoidScript language.",
+    "license": "MIT",
+    "version": "1.0.0",
+    "publisher": "fszontagh",
+    "repository": {
+      "type": "git",
+      "url": "https://github.com/fszontagh/voidscript.git"
+    },
+    "engines": {
+      "vscode": "^1.58.0"
+    },
+    "contributes": {
+      "languages": [
+        {
+          "id": "voidscript",
+          "extensions": [".voidscript", ".vs"],
+          "aliases": ["VoidScript"],
+          "configuration": "./src/grammar/language-configuration.json",
+          "mimetypes": ["application/x-voidscript", "application/vpodscript"]
+        }
+      ],
+      "grammars": [
+        {
+          "language": "voidscript",
+          "scopeName": "source.voidscript",
+          "path": "./src/grammar/voidscript.tmLanguage.json"
+        }
+      ]
+    },
+    "scripts": {
+      "vscode:prepublish": "npm install",
+      "compile": "echo \"No compile needed\"",
+      "create:package": "vsce package",
+      "build": "npm run vscode:prepublish; npm run create:package"
+    },
+    "devDependencies": {
+      "typescript": "^4.0.0",
+      "vscode": "^1.1.37",
+      "eslint": "^7.23.0"
+    }
+  }
diff --git a/assets/vscode/voidscript-syntax/src/grammar/language-configuration.json b/assets/vscode/voidscript-syntax/src/grammar/language-configuration.json
new file mode 100644
index 0000000..a14af62
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/src/grammar/language-configuration.json
@@ -0,0 +1,82 @@
+{
+  "comments": {
+    "lineComment": "#",
+    "blockComment": [
+      "/*",
+      "*/"
+    ]
+  },
+  "brackets": [
+    [
+      "{",
+      "}"
+    ],
+    [
+      "[",
+      "]"
+    ],
+    [
+      "(",
+      ")"
+    ]
+  ],
+  "surroundingPairs": [
+    [
+      "{",
+      "}"
+    ],
+    [
+      "[",
+      "]"
+    ],
+    [
+      "(",
+      ")"
+    ],
+    [
+      "'",
+      "'"
+    ],
+    [
+      "\"",
+      "\""
+    ]
+  ],
+  "autoClosingPairs": [
+    {
+      "open": "(",
+      "close": ")",
+      "notIn": [
+        "string"
+      ]
+    },
+    {
+      "open": "\"",
+      "close": "\"",
+      "notIn": [
+        "string"
+      ]
+    },
+    {
+      "open": "'",
+      "close": "'",
+      "notIn": [
+        "string"
+      ]
+    },
+    {
+      "open": "{",
+      "close": "}",
+      "notIn": [
+        "string"
+      ]
+    },
+    {
+      "open": "[",
+      "close": "]",
+      "notIn": [
+        "string"
+      ]
+    },
+  ]
+}
\ No newline at end of file
diff --git a/assets/vscode/voidscript-syntax/src/grammar/voidscript.tmLanguage.json b/assets/vscode/voidscript-syntax/src/grammar/voidscript.tmLanguage.json
new file mode 100644
index 0000000..090b838
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/src/grammar/voidscript.tmLanguage.json
@@ -0,0 +1,43 @@
+{
+  "fileTypes": ["voidscript", "vs"],
+    "scopeName": "source.voidscript",
+    "patterns": [
+      {
+        "name": "comment.line.number-sign.voidscript",
+        "match": "#.*"
+      },
+      {
+        "name": "comment.line.double-slash.voidscript",
+        "match": "//.*"
+      },
+      {
+        "name": "keyword.control.voidscript",
+        "match": "\\b(function|class|const|object|string|int|boolean|double|float|if|else|for|while|switch|case|break|continue|new|return|throw|typeof)\\b"
+      },
+      {
+        "name": "variable.other.local.voidscript",
+        "match": "\\$[a-zA-Z_][a-zA-Z0-9_]*"
+      },
+      {
+        "name": "variable.language.this.voidscript",
+        "match": "\\bthis\\b"
+      },
+      {
+        "name": "string.quoted.double.voidscript",
+        "match": "\"([^\"]|\\\\\")*\""
+      },
+      {
+        "name": "constant.numeric.voidscript",
+        "match": "\\b\\d+(?:\\.\\d+)?\\b"
+      },
+      {
+        "name": "constant.language.boolean.voidscript",
+        "match": "\\b(true|false)\\b"
+      },
+      {
+        "name": "keyword.operator.voidscript",
+        "match": "(->|\\+=|-=|\\*=|\\/=|%=|&&|\\|\\||==|!=|>=|<=|>|<|\\+|-|\\*|\\/|%|=|!)"
+      }
+    ],
+    "repository": {}
+  }
diff --git a/assets/vscode/voidscript-syntax/tsconfig.json b/assets/vscode/voidscript-syntax/tsconfig.json
new file mode 100644
index 0000000..2900549
--- /dev/null
+++ b/assets/vscode/voidscript-syntax/tsconfig.json
@@ -0,0 +1,12 @@
+{
+    "compilerOptions": {
+      "target": "ES6",
+      "module": "commonjs",
+      "outDir": "./out",
+      "rootDir": "./src",
+      "strict": true
+    },
+    "include": [
+      "src/**/*"
+    ]
+  }
diff --git a/cli/main.cpp b/cli/main.cpp
index f0daa59..f7bbede 100644
--- a/cli/main.cpp
+++ b/cli/main.cpp
@@ -1,6 +1,7 @@
 #include <filesystem>
 #include <iostream>
 #include <unordered_map>
+#include <unistd.h>  // for isatty, STDIN_FILENO
 
 #include "options.h"
 #include "VoidScript.hpp"
@@ -17,6 +18,8 @@
     for (const auto & [key, value] : params) {
         usage.append(" [" + key + "]");
     }
+    // [file] is optional; if omitted, script is read from stdin
+    usage.append(" [file]");
     // Parse arguments: allow --help, --version, --debug[=component], and a single file
     bool debugLexer       = false;
     bool debugParser      = false;
@@ -61,6 +64,9 @@
                 std::cerr << usage << "\n";
                 return 1;
             }
+        } else if (a == "-") {
+            // Read script from stdin
+            file = a;
         } else if (a.starts_with("-")) {
             std::cerr << "Error: Unknown option '" << a << "'\n";
             std::cerr << usage << "\n";
@@ -74,17 +80,27 @@
         }
     }
     if (file.empty()) {
-        std::cerr << "Error: No input file specified\n";
+        // No input file specified: read script from stdin
+        file = "-";
+    }
+    // If reading from stdin but stdin is a tty (no piped input), show usage
+    if (file == "-" && isatty(STDIN_FILENO)) {
         std::cerr << usage << "\n";
         return 1;
     }
 
-    if (!std::filesystem::exists(file)) {
-        std::cerr << "Error: File " << file << " does not exist.\n";
-        return 1;
+    // Determine if reading from a file or stdin
+    std::string filename;
+    if (file == "-") {
+        // Read script from standard input
+        filename = file;
+    } else {
+        if (!std::filesystem::exists(file)) {
+            std::cerr << "Error: File " << file << " does not exist.\n";
+            return 1;
+        }
+        filename = std::filesystem::canonical(file).string();
     }
-
-    const std::string filename = std::filesystem::canonical(file).string();
 
     // Initialize and run with debug options
     VoidScript voidscript(filename, debugLexer, debugParser, debugInterp, debugSymbolTable);
diff --git a/src/Interpreter/CallExpressionNode.hpp b/src/Interpreter/CallExpressionNode.hpp
index 1fdcf9c..d36bd0a 100644
--- a/src/Interpreter/CallExpressionNode.hpp
+++ b/src/Interpreter/CallExpressionNode.hpp
@@ -21,79 +21,92 @@
  /**
   * @brief Expression node representing a function call returning a value.
   */
- class CallExpressionNode : public ExpressionNode {
-     std::string functionName_;
-     std::vector<std::unique_ptr<ExpressionNode>> args_;
+class CallExpressionNode : public ExpressionNode {
+    std::string functionName_;
+    std::vector<std::unique_ptr<ExpressionNode>> args_;
+    // Source location for error reporting
+    std::string filename_;
+    int line_;
+    size_t column_;
 
    public:
      CallExpressionNode(std::string functionName,
-                        std::vector<std::unique_ptr<ExpressionNode>> args) :
-         functionName_(std::move(functionName)), args_(std::move(args)) {}
+                        std::vector<std::unique_ptr<ExpressionNode>> args,
+                        const std::string & filename,
+                        int line, size_t column) :
+         functionName_(std::move(functionName)), args_(std::move(args)),
+         filename_(filename), line_(line), column_(column) {}
 
      Symbols::Value evaluate(Interpreter &interpreter) const override {
          using namespace Symbols;
-         // Evaluate argument expressions
-         std::vector<Value> argValues;
-         argValues.reserve(args_.size());
-         for (const auto &expr : args_) {
-             argValues.push_back(expr->evaluate(interpreter));
-         }
+         try {
+             // Evaluate argument expressions
+             std::vector<Value> argValues;
+             argValues.reserve(args_.size());
+             for (const auto &expr : args_) {
+                 argValues.push_back(expr->evaluate(interpreter));
+             }
 
-         // Built-in function
-         auto &mgr = Modules::ModuleManager::instance();
-         if (mgr.hasFunction(functionName_)) {
-             return mgr.callFunction(functionName_, argValues);
-         }
+             // Built-in function
+             auto &mgr = Modules::ModuleManager::instance();
+             if (mgr.hasFunction(functionName_)) {
+                 return mgr.callFunction(functionName_, argValues);
+             }
 
-        // User-defined function: lookup through scope hierarchy
-        SymbolContainer *sc = SymbolContainer::instance();
-        std::string lookupNs = sc->currentScopeName();
-        std::shared_ptr<FunctionSymbol> funcSym;
-        // Search for function symbol in current and parent scopes
-        while (true) {
-            std::string fnSymNs = lookupNs + ".functions";
-            auto sym = sc->get(fnSymNs, functionName_);
-            if (sym && sym->getKind() == Kind::Function) {
-                funcSym = std::static_pointer_cast<FunctionSymbol>(sym);
-                break;
-            }
-            auto pos = lookupNs.find_last_of('.');
-            if (pos == std::string::npos) {
-                break;
-            }
-            lookupNs = lookupNs.substr(0, pos);
-        }
-        if (!funcSym) {
-            throw std::runtime_error("Function not found: " + functionName_);
-        }
-        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()));
-        }
-        // Enter function scope and bind parameters
-        const std::string fnOpNs = funcSym->context() + "." + functionName_;
-        sc->enter(fnOpNs);
-        for (size_t i = 0; i < params.size(); ++i) {
-            const auto &p = params[i];
-            const Value &v = argValues[i];
-            auto varSym = SymbolFactory::createVariable(p.name, v, fnOpNs);
-            sc->add(varSym);
-        }
-        // Execute function body operations and capture return
-        Symbols::Value returnValue;
-        auto ops = Operations::Container::instance()->getAll(fnOpNs);
-        for (const auto &op : ops) {
-            try {
-                interpreter.runOperation(*op);
-            } catch (const ReturnException &ret) {
-                returnValue = ret.value();
-                break;
-            }
-        }
-        sc->enterPreviousScope();
-        return returnValue;
+             // User-defined function: lookup through scope hierarchy
+             SymbolContainer *sc = SymbolContainer::instance();
+             std::string lookupNs = sc->currentScopeName();
+             std::shared_ptr<FunctionSymbol> funcSym;
+             // Search for function symbol in current and parent scopes
+             while (true) {
+                 std::string fnSymNs = lookupNs + ".functions";
+                 auto sym = sc->get(fnSymNs, functionName_);
+                 if (sym && sym->getKind() == Kind::Function) {
+                     funcSym = std::static_pointer_cast<FunctionSymbol>(sym);
+                     break;
+                 }
+                 auto pos = lookupNs.find_last_of('.');
+                 if (pos == std::string::npos) {
+                     break;
+                 }
+                 lookupNs = lookupNs.substr(0, pos);
+             }
+             if (!funcSym) {
+                 throw std::runtime_error("Function not found: " + functionName_);
+             }
+             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()));
+             }
+             // Enter function scope and bind parameters
+             const std::string fnOpNs = funcSym->context() + "." + functionName_;
+             sc->enter(fnOpNs);
+             for (size_t i = 0; i < params.size(); ++i) {
+                 const auto &p = params[i];
+                 const Value &v = argValues[i];
+                 auto varSym = SymbolFactory::createVariable(p.name, v, fnOpNs);
+                 sc->add(varSym);
+             }
+             // Execute function body operations and capture return
+             Symbols::Value returnValue;
+             auto ops = Operations::Container::instance()->getAll(fnOpNs);
+             for (const auto &op : ops) {
+                 try {
+                     interpreter.runOperation(*op);
+                 } catch (const ReturnException &ret) {
+                     returnValue = ret.value();
+                     break;
+                 }
+             }
+             sc->enterPreviousScope();
+             return returnValue;
+         } catch (const std::exception &e) {
+             throw ::Interpreter::Exception(e.what(), filename_, line_, column_);
+         }
+         // Unreachable: all paths either return or throw
+         return Symbols::Value();
      }
 
      std::string toString() const override {
diff --git a/src/Interpreter/ExpressionBuilder.hpp b/src/Interpreter/ExpressionBuilder.hpp
index bb5fefa..6ff5507 100644
--- a/src/Interpreter/ExpressionBuilder.hpp
+++ b/src/Interpreter/ExpressionBuilder.hpp
@@ -69,7 +69,9 @@
                 for (const auto &arg : expr->args) {
                     callArgs.push_back(buildExpressionFromParsed(arg));
                 }
-                return std::make_unique<Interpreter::CallExpressionNode>(expr->name, std::move(callArgs));
+                // Create call node with source location
+                return std::make_unique<Interpreter::CallExpressionNode>(expr->name,
+                    std::move(callArgs), expr->filename, expr->line, expr->column);
             }
         case Kind::Object:
             {
diff --git a/src/Modules/BaseModule.hpp b/src/Modules/BaseModule.hpp
index a629851..a22cfd9 100644
--- a/src/Modules/BaseModule.hpp
+++ b/src/Modules/BaseModule.hpp
@@ -3,6 +3,8 @@
 #define MODULES_BASEMODULE_HPP
 
 
+// Base exception type for module errors
+#include "../BaseException.hpp"
 namespace Modules {
 
 /**
@@ -20,5 +22,19 @@
     virtual void registerModule() = 0;
 };
 
+/**
+ * @brief Exception type for errors thrown within module functions.
+ * Inherit from BaseException to allow rich error messages.
+ */
+class Exception : public ::BaseException {
+  public:
+    /**
+     * Construct a module exception with a message.
+     * @param msg Error message
+     */
+    explicit Exception(const std::string & msg)
+        : BaseException(msg) {}
+};
+
 } // namespace Modules
 #endif // MODULES_BASEMODULE_HPP
\ No newline at end of file
diff --git a/src/Modules/BuiltIn/PrintModule.hpp b/src/Modules/BuiltIn/PrintModule.hpp
index e27564a..a7c0b4e 100644
--- a/src/Modules/BuiltIn/PrintModule.hpp
+++ b/src/Modules/BuiltIn/PrintModule.hpp
@@ -37,6 +37,15 @@
             std::cerr << "\n";
             return Symbols::Value();
         });
+        // Built-in error thrower: throws a module exception with provided message
+        mgr.registerFunction("throw_error", [](const std::vector<Symbols::Value> & args) {
+            if (args.size() != 1 || args[0].getType() != Symbols::Variables::Type::STRING) {
+                throw Exception("throw_error requires exactly one string argument");
+            }
+            std::string msg = args[0].get<std::string>();
+            throw Exception(msg);
+            return Symbols::Value();  // never reached
+        });
     }
 };
 
diff --git a/src/Parser/ParsedExpression.hpp b/src/Parser/ParsedExpression.hpp
index 4acff85..f95f196 100644
--- a/src/Parser/ParsedExpression.hpp
+++ b/src/Parser/ParsedExpression.hpp
@@ -30,6 +30,10 @@
     // For function call arguments
     std::vector<ParsedExpressionPtr> args;
     std::vector<std::pair<std::string, ParsedExpressionPtr>> objectMembers;
+    // Source location for error reporting
+    std::string filename;
+    int line = 0;
+    size_t column = 0;
 
     // Constructor for literal
     static ParsedExpressionPtr makeLiteral(const Symbols::Value & val) {
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index d475d46..f940642 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -927,8 +927,14 @@
                 }
             }
             expect(Lexer::Tokens::Type::PUNCTUATION, ")");
-            // Create call expression node
-            output_queue.push_back(ParsedExpression::makeCall(func_name, std::move(call_args)));
+            // Create call expression node with source location
+            {
+                auto pe = ParsedExpression::makeCall(func_name, std::move(call_args));
+                pe->filename = this->current_filename_;
+                pe->line = token.line_number;
+                pe->column = token.column_number;
+                output_queue.push_back(std::move(pe));
+            }
             expect_unary = false;
         } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC ||
                    token.type == Lexer::Tokens::Type::OPERATOR_RELATIONAL ||
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index ab472bb..96cc277 100644
--- a/src/VoidScript.hpp
+++ b/src/VoidScript.hpp
@@ -3,6 +3,8 @@
 #include <filesystem>
 #include <fstream>
 #include <string>
+#include <iostream>
+#include <iterator>
 
 #include "Interpreter/Interpreter.hpp"
 #include "Lexer/Lexer.hpp"
@@ -32,15 +34,18 @@
     std::shared_ptr<Parser::Parser> parser = nullptr;
 
     static std::string readFile(const std::string & file) {
+        // Read from stdin if '-' is specified
+        if (file == "-") {
+            return std::string(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
+        }
         if (!std::filesystem::exists(file)) {
-            throw std::runtime_error("File " + file + " does not exits");
+            throw std::runtime_error("File " + file + " does not exist");
         }
         std::ifstream input(file, std::ios::in);
         if (!input.is_open()) {
             throw std::runtime_error("Could not open file " + file);
-            return "";
         }
-        std::string content = std::string((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
+        std::string content((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
         input.close();
         return content;
     }
diff --git a/test_error.vs b/test_error.vs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test_error.vs
@@ -0,0 +1 @@
+
diff --git a/test_scripts/test_error.vs b/test_scripts/test_error.vs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test_scripts/test_error.vs
@@ -0,0 +1 @@
+
diff --git a/test_scripts/test_error2.vs b/test_scripts/test_error2.vs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test_scripts/test_error2.vs
@@ -0,0 +1 @@
+
diff --git a/test_scripts/test_throw_error.vs b/test_scripts/test_throw_error.vs
new file mode 100644
index 0000000..78012fb
--- /dev/null
+++ b/test_scripts/test_throw_error.vs
@@ -0,0 +1,2 @@
+// Test script for throw_error built-in function
+throw_error("Oops here");
\ No newline at end of file
diff --git a/test_scripts/throw_error_test.vs b/test_scripts/throw_error_test.vs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test_scripts/throw_error_test.vs
@@ -0,0 +1 @@
+

--
Gitblit v1.9.3