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 ¶ms = 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 ¶ms = 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