Compare commits

..

No commits in common. "ec4b5afa9ca87273383e74eb4ded63a1e638778f" and "f70c6abd7743e4b83cb76b9f0a75a5d7baaeae3b" have entirely different histories.

117 changed files with 2029 additions and 4609 deletions

1
.gitignore vendored
View File

@ -3,4 +3,3 @@ build/
.DS_Store
build-ninja
build-release

View File

@ -45,18 +45,16 @@ elseif(MSVC)
endif()
# Collect source files
file(GLOB_RECURSE BOB_RUNTIME_SOURCES CONFIGURE_DEPENDS "src/sources/runtime/*.cpp")
file(GLOB_RECURSE BOB_PARSING_SOURCES CONFIGURE_DEPENDS "src/sources/parsing/*.cpp")
file(GLOB_RECURSE BOB_STDLIB_SOURCES CONFIGURE_DEPENDS "src/sources/stdlib/*.cpp")
file(GLOB_RECURSE BOB_BUILTIN_SOURCES CONFIGURE_DEPENDS "src/sources/builtinModules/*.cpp")
file(GLOB_RECURSE BOB_CLI_SOURCES CONFIGURE_DEPENDS "src/sources/cli/*.cpp")
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.cpp")
file(GLOB_RECURSE BOB_PARSING_SOURCES "src/sources/parsing/*.cpp")
file(GLOB_RECURSE BOB_STDLIB_SOURCES "src/sources/stdlib/*.cpp")
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.cpp")
# All source files
set(BOB_ALL_SOURCES
${BOB_RUNTIME_SOURCES}
${BOB_PARSING_SOURCES}
${BOB_STDLIB_SOURCES}
${BOB_BUILTIN_SOURCES}
${BOB_CLI_SOURCES}
)
@ -68,7 +66,6 @@ target_include_directories(bob PRIVATE
src/headers/runtime
src/headers/parsing
src/headers/stdlib
src/headers/builtinModules
src/headers/cli
src/headers/common
)

View File

@ -233,26 +233,19 @@ toBoolean(1); // true
type(42); // "number"
```
### Arrays, Strings, and Dictionaries: Method style (preferred)
### Arrays & Strings
```go
[1, 2, 3].len(); // 3
"hello".len(); // 5
var a = [1, 2]; a.push(3); // a is now [1, 2, 3]
var v = a.pop(); // v == 3
var d = {"a": 1, "b": 2};
d.len(); // 2
d.keys(); // ["a", "b"]
d.values(); // [1, 2]
d.has("a"); // true
len([1, 2, 3]); // 3
len("hello"); // 5
push(array, value); // Add to end
pop(array); // Remove from end
```
Note: Global forms like `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)` have been removed. Use method style.
### Numbers
### Dictionaries
```go
toInt(3.9); // 3 (global)
(3.9).toInt(); // 3 (method on number)
keys(dict); // Array of keys
values(dict); // Array of values
has(dict, "key"); // Check if key exists
```
### Utility
@ -260,7 +253,7 @@ toInt(3.9); // 3 (global)
assert(condition, "message"); // Testing
time(); // Current time in microseconds
sleep(1.5); // Sleep for 1.5 seconds
rand.random(); // Random number 0-1
random(); // Random number 0-1
eval("print('Hello');"); // Execute string as code
exit(0); // Exit program
```
@ -273,93 +266,8 @@ var lines = readLines("config.txt");
var exists = fileExists("test.txt");
```
## Standard Library Reference
The following built-ins are available by default. Unless specified, functions throw on invalid argument counts/types.
- print(x): prints x with newline
- printRaw(x): prints x without newline
- input(prompt?): reads a line from stdin (optional prompt)
- toString(x): returns string representation
- toNumber(s): parses string to number or returns none
- toInt(n): truncates number to integer
- toBoolean(x): converts to boolean using truthiness rules
- type(x): returns the type name as string
- len(x) / x.len(): length of array/string/dict
- push(arr, ...values) / arr.push(...values): appends values to array in place, returns arr
- pop(arr) / arr.pop(): removes and returns last element
- keys(dict) / dict.keys(): returns array of keys
- values(dict) / dict.values(): returns array of values
- has(dict, key) / dict.has(key): returns true if key exists
- readFile(path): returns entire file contents as string
- writeFile(path, content): writes content to file
- readLines(path): returns array of lines
- fileExists(path): boolean
- time(): microseconds since Unix epoch
- sleep(seconds): pauses execution
- rand.random(): float in [0,1)
- eval(code): executes code string in current environment
- exit(code?): terminates the program
Notes:
- Arrays support properties: length, first, last, empty
- Dicts support properties: length, empty, keys, values
- Method-style builtins on arrays/strings/dicts are preferred; global forms remain for compatibility.
## Advanced Features
### Classes (Phase 1)
```go
// Declare a class with fields and methods
class Person {
var name;
var age;
// Methods can use implicit `this`
func setName(n) { this.name = n; }
func greet() { print("Hi, I'm " + this.name); }
}
// Construct via the class name
var p = Person();
p.setName("Bob");
p.greet();
// Fields are stored on the instance (a dictionary under the hood)
p.age = 30;
```
Notes:
- Instances are plain dictionaries; methods are shared functions placed on the instance.
- On a property call like `obj.method(...)`, the interpreter injects `this = obj` into the call frame (no argument injection).
- Taking a method reference and calling it later does not autobind `this`; call via `obj.method(...)` when needed.
### Extensions (Builtins and Classes)
Extend existing types (including builtins) with new methods:
```go
extension array {
func sum() {
var i = 0; var s = 0;
while (i < len(this)) { s = s + this[i]; i = i + 1; }
return s;
}
}
extension dict { func size() { return len(this); } }
extension string { func shout() { return toString(this) + "!"; } }
extension any { func tag() { return "<" + type(this) + ">"; } }
assert([1,2,3].sum() == 6);
assert({"a":1,"b":2}.size() == 2);
assert("hi".shout() == "hi!");
assert(42.tag() == "<number>");
```
Notes:
- Lookup order for `obj.method(...)`: instance dictionary → class extensions (for user classes) → builtin extensions (string/array/dict) → `any`.
- `this` is injected for property calls.
### String Interpolation
```go
var name = "Alice";
@ -441,7 +349,7 @@ var people = [
{"name": "Bob", "age": 25}
];
for (var i = 0; i < people.len(); i = i + 1) {
for (var i = 0; i < len(people); i = i + 1) {
var person = people[i];
print(person["name"] + " is " + person["age"] + " years old");
}
@ -452,17 +360,17 @@ for (var i = 0; i < people.len(); i = i + 1) {
var lines = readLines("data.txt");
var processed = [];
for (var i = 0; i < lines.len(); i = i + 1) {
for (var i = 0; i < len(lines); i = i + 1) {
var line = lines[i];
if (line.len() > 0) {
processed.push("Processed: " + line);
if (len(line) > 0) {
push(processed, "Processed: " + line);
}
}
var output = "";
for (var i = 0; i < processed.len(); i = i + 1) {
for (var i = 0; i < len(processed); i = i + 1) {
output = output + processed[i];
if (i < processed.len() - 1) {
if (i < len(processed) - 1) {
output = output + "\n";
}
}

View File

@ -1,113 +0,0 @@
Embedding Bob: Public API Guide
================================
This document explains how to embed the Bob interpreter in your C++ application, register custom modules, and control sandbox policies.
Quick Start
-----------
```cpp
#include "cli/bob.h"
#include "ModuleRegistry.h"
int main() {
Bob bob;
// Optional: configure policies or modules before first use
bob.setBuiltinModulePolicy(true); // allow builtin modules (default)
bob.setBuiltinModuleDenyList({/* e.g., "sys" */});
// Register a custom builtin module called "demo"
bob.registerModule("demo", [](ModuleRegistry::ModuleBuilder& m) {
m.fn("hello", [](std::vector<Value> args, int, int) -> Value {
std::string who = (args.size() >= 1 && args[0].isString()) ? args[0].asString() : "world";
return Value(std::string("hello ") + who);
});
m.val("meaning", Value(42.0));
});
// Evaluate code from a string
bob.evalString("import demo; print(demo.hello(\"Bob\"));", "<host>");
// Evaluate a file (imports inside resolve relative to the file's directory)
bob.evalFile("script.bob");
}
```
API Overview
------------
Bob exposes a single high-level object with a minimal, consistent API. It self-manages an internal interpreter and applies configuration on first use.
- Program execution
- `bool evalString(const std::string& code, const std::string& filename = "<eval>")`
- `bool evalFile(const std::string& path)`
- `void runFile(const std::string& path)` (CLI convenience delegates to `evalFile`)
- `void runPrompt()` (interactive CLI delegates each line to `evalString`)
- Module registration and sandboxing
- `void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init)`
- `void setBuiltinModulePolicy(bool allow)`
- `void setBuiltinModuleAllowList(const std::vector<std::string>& allowed)`
- `void setBuiltinModuleDenyList(const std::vector<std::string>& denied)`
- Global environment helpers
- `bool defineGlobal(const std::string& name, const Value& value)`
- `bool tryGetGlobal(const std::string& name, Value& out) const`
All configuration calls are safe to use before any evaluation they are queued and applied automatically when the interpreter is first created.
Registering Custom Builtin Modules
----------------------------------
Use the builder convenience to create a module:
```cpp
bob.registerModule("raylib", [](ModuleRegistry::ModuleBuilder& m) {
m.fn("init", [](std::vector<Value> args, int line, int col) -> Value {
// call into your library here; validate args, return Value
return NONE_VALUE;
});
m.val("VERSION", Value(std::string("5.0")));
});
```
At runtime:
```bob
import raylib;
raylib.init();
print(raylib.VERSION);
```
Notes
-----
- Modules are immutable, first-class objects. `type(module)` is "module" and `toString(module)` prints `<module 'name'>`.
- Reassigning a module binding or setting module properties throws an error.
Builtin Modules and Sandboxing
------------------------------
- Builtin modules (e.g., `sys`) are registered during interpreter construction.
- File imports are always resolved relative to the importing file's directory.
- Policies:
- `setBuiltinModulePolicy(bool allow)` enable/disable all builtin modules.
- `setBuiltinModuleAllowList(vector<string>)` allow only listed modules (deny others).
- `setBuiltinModuleDenyList(vector<string>)` explicitly deny listed modules.
- Denied/disabled modules are cloaked: `import name` reports "Module not found".
Error Reporting
---------------
- `evalString`/`evalFile` set file context for error reporting so line/column references point to the real source.
- Both return `true` on success and `false` if execution failed (errors are reported via the internal error reporter).
CLI vs Embedding
----------------
- CLI builds include `main.cpp` (entry point), which uses `Bob::runFile` or `Bob::runPrompt`.
- Embedded hosts do not use `main.cpp`; instead they instantiate `Bob` and call `evalString`/`evalFile` directly.

View File

@ -56,7 +56,7 @@ Bob is a mature, working programming language with a modern architecture and com
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
- **Testing**: `assert()` with custom error messages
- **Timing**: `time()` (microsecond precision), `sleep()`
- **Utility**: `rand.random()` (properly seeded), `eval.eval()`, `sys.exit()`
- **Utility**: `random()` (properly seeded), `eval()`, `exit()`
- **Data Structure**: `len()`, `push()`, `pop()`, `keys()`, `values()`, `has()`
- **File I/O**: `readFile()`, `writeFile()`, `readLines()`, `fileExists()`

View File

@ -17,14 +17,11 @@ This extension provides syntax highlighting and language support for the Bob pro
- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return`
- Variable declaration: `var`
- Function declaration: `func`
- Classes and OOP: `class`, `extends`, `extension`, `this`, `super`
- Logical operators: `and`, `or`, `not`
### Built-in Functions
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `toInt()`, `time()`, `sleep()`, `printRaw()`
- Arrays/Dictionaries (preferred method style): `arr.len()`, `arr.push(...)`, `arr.pop()`, `dict.len()`, `dict.keys()`, `dict.values()`, `dict.has()`
- Global forms still available: `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)`
- Misc: `rand.random()`, `eval.eval()`
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `time()`
- `sleep()`, `printRaw()`, `len()`, `push()`, `pop()`, `random()`, `eval()`
### Data Types
- Numbers (integers, floats, binary `0b1010`, hex `0xFF`)
@ -95,11 +92,9 @@ Files with the `.bob` extension will automatically be recognized as Bob language
var message = "Hello, Bob!";
print(message);
// Array operations (method style)
// Array operations
var numbers = [1, 2, 3, 4, 5];
print("Array length: " + numbers.len());
numbers.push(6);
print("Popped: " + numbers.pop());
print("Array length: " + len(numbers));
print("First element: " + numbers[0]);
// Function with ternary operator

View File

@ -20,12 +20,12 @@ print("Pi: " + toString(pi));
var numbers = [1, 2, 3, 4, 5];
var fruits = ["apple", "banana", "cherry"];
print("Array length: " + numbers.len());
print("Array length: " + len(numbers));
print("First element: " + numbers[0]);
numbers[2] = 99; // Array assignment
numbers.push(6); // Add element
var lastElement = numbers.pop(); // Remove and get last element
push(numbers, 6); // Add element
var lastElement = pop(numbers); // Remove and get last element
// Function definition
func factorial(n) {
@ -91,7 +91,7 @@ value *= 2;
value -= 3;
// New built-in functions
import rand; var randomValue = rand.random();
var randomValue = random();
sleep(100); // Sleep for 100ms
printRaw("No newline here");
eval("print('Dynamic code execution!');");

View File

@ -23,7 +23,7 @@
["'", "'"]
],
"indentationRules": {
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for|class|extension)\\b.*$",
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for)\\b.*$",
"decreaseIndentPattern": "^\\s*[})]"
},
"folding": {

View File

@ -2,7 +2,7 @@
"name": "bob-language",
"displayName": "Bob Language",
"description": "Syntax highlighting and language support for the Bob programming language - featuring arrays, dictionaries, auto-truncating float indices, increment/decrement operators, cross-type comparisons, and comprehensive built-in functions",
"version": "0.5.0",
"version": "0.4.0",
"engines": {
"vscode": "^1.60.0"
},

View File

@ -53,32 +53,6 @@
],
"description": "Declare a variable"
},
"Class Declaration": {
"prefix": "class",
"body": [
"class ${1:ClassName} ${2:extends ${3:Parent}} {",
" var ${4:field} = ${5:none};",
" func init(${6:args}) {",
" this.${4:field} = ${7:value};",
" }",
" func ${8:method}(${9:params}) {",
" $0",
" }",
"}"
],
"description": "Create a class with optional extends, fields, init, and method"
},
"Extension Block": {
"prefix": "extension",
"body": [
"extension ${1:Target} {",
" func ${2:method}(${3:params}) {",
" $0",
" }",
"}"
],
"description": "Create an extension for a class or builtin (string, array, dict, number, any)"
}
"Print Statement": {
"prefix": "print",
"body": [
@ -300,7 +274,7 @@
"Random Number": {
"prefix": "random",
"body": [
"import rand; var randomValue = rand.random();"
"var randomValue = random();"
],
"description": "Generate random number"
},

View File

@ -153,7 +153,7 @@
"patterns": [
{
"name": "keyword.control.bob",
"match": "\\b(if|else|while|do|for|break|continue|return|var|func|class|extends|extension|this|super)\\b"
"match": "\\b(if|else|while|do|for|break|continue|return|var|func)\\b"
},
{
"name": "keyword.operator.bob",

View File

@ -1,13 +0,0 @@
class A {
var inner = 10;
func test(){
print(this.inner);
}
}
func hello(){
print("hello");
}

View File

@ -9,14 +9,14 @@ print("Test 1: Heavy string operations");
var stringData = [];
for (var i = 0; i < 100000; i++) {
var str = toString(i) + "_" + toString(i * 2) + "_" + toString(i * 3);
stringData.push({
push(stringData, {
"original": str,
"upper": str, // Bob doesn't have toUpper, but test string storage
"length": str.len(),
"length": len(str),
"type": type(str)
});
}
print("Created " + stringData.len() + " string operation results");
print("Created " + len(stringData) + " string operation results");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear string data...");
stringData = none;
@ -33,12 +33,12 @@ for (var i = 0; i < 200000; i++) {
var intVal = toInt(num);
var boolVal = toBoolean(i % 2);
conversions.push([
push(conversions, [
num, str, backToNum, intVal, boolVal,
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
]);
}
print("Created " + conversions.len() + " type conversion results");
print("Created " + len(conversions) + " type conversion results");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear conversions...");
conversions = [];
@ -53,15 +53,15 @@ for (var i = 0; i < 50000; i++) {
var dict = {"a": i, "b": i+1, "c": i+2};
// Use builtin functions heavily
var arrLen = arr.len();
arr.push(i+3);
var popped = arr.pop();
var arrLen = len(arr);
push(arr, i+3);
var popped = pop(arr);
var dictKeys = dict.keys();
var dictValues = dict.values();
var hasA = dict.has("a");
var dictKeys = keys(dict);
var dictValues = values(dict);
var hasA = has(dict, "a");
collections.push({
push(collections, {
"array": arr,
"dict": dict,
"arrLen": arrLen,
@ -71,7 +71,7 @@ for (var i = 0; i < 50000; i++) {
"hasA": hasA
});
}
print("Created " + collections.len() + " collection operation results");
print("Created " + len(collections) + " collection operation results");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear collections...");
collections = "cleared";
@ -88,14 +88,14 @@ for (var i = 0; i < 10000; i++) {
var funcExpr = "func() { return " + toString(i) + "; };";
var evalFunc = eval(funcExpr);
evalResults.push({
push(evalResults, {
"expr": expression,
"result": result,
"func": evalFunc,
"funcResult": evalFunc()
});
}
print("Created " + evalResults.len() + " eval results");
print("Created " + len(evalResults) + " eval results");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear eval results...");
evalResults = none;
@ -121,15 +121,15 @@ for (var i = 0; i < 1000; i++) {
var readContent = readFile(filename);
var lines = readLines(filename);
fileData.push({
push(fileData, {
"filename": filename,
"exists": exists,
"content": readContent,
"lines": lines,
"lineCount": lines.len()
"lineCount": len(lines)
});
}
print("Created " + fileData.len() + " file operation results");
print("Created " + len(fileData) + " file operation results");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear file data...");
fileData = [];
@ -151,19 +151,18 @@ input("File data cleared. Check memory usage...");
print("Test 6: Random number stress");
var randomData = [];
for (var i = 0; i < 200000; i++) {
import rand as RLeak;
var rand1 = RLeak.random();
var rand2 = RLeak.random();
var rand1 = random();
var rand2 = random();
var sum = rand1 + rand2;
randomData.push({
push(randomData, {
"rand1": rand1,
"rand2": rand2,
"sum": sum,
"product": rand1 * rand2
});
}
print("Created " + randomData.len() + " random number results");
print("Created " + len(randomData) + " random number results");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear random data...");
randomData = none;

View File

@ -8,13 +8,13 @@ print("Initial memory: " + memoryUsage() + " MB");
print("Test 1: Large nested arrays");
var nestedArrays = [];
for (var i = 0; i < 50000; i++) {
nestedArrays.push([
push(nestedArrays, [
[i, i+1, i+2],
[i*2, i*3, i*4],
[[i, [i+1, [i+2]]], i*5]
]);
}
print("Created " + nestedArrays.len() + " nested array structures");
print("Created " + len(nestedArrays) + " nested array structures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear nested arrays...");
nestedArrays = none;
@ -25,7 +25,7 @@ input("Cleared. Check memory usage...");
print("Test 2: Large nested dictionaries");
var nestedDicts = [];
for (var i = 0; i < 50000; i++) {
nestedDicts.push({
push(nestedDicts, {
"id": i,
"data": {
"value": i * 2,
@ -42,7 +42,7 @@ for (var i = 0; i < 50000; i++) {
}
});
}
print("Created " + nestedDicts.len() + " nested dictionary structures");
print("Created " + len(nestedDicts) + " nested dictionary structures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear nested dicts...");
nestedDicts = [];
@ -53,7 +53,7 @@ input("Cleared. Check memory usage...");
print("Test 3: Mixed array/dict structures");
var mixedStructures = [];
for (var i = 0; i < 30000; i++) {
mixedStructures.push([
push(mixedStructures, [
{"arrays": [[i, i+1], [i+2, i+3]]},
[{"dicts": {"a": i, "b": i+1}}, {"more": [i, i+1]}],
{
@ -64,7 +64,7 @@ for (var i = 0; i < 30000; i++) {
}
]);
}
print("Created " + mixedStructures.len() + " mixed structures");
print("Created " + len(mixedStructures) + " mixed structures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear mixed structures...");
mixedStructures = "cleared";
@ -78,15 +78,11 @@ for (var i = 0; i < 1000000; i++) {
var item = {"id": i, "value": i * 2};
// Create a structure that references itself
item["self"] = [item, {"parent": item}];
selfRef.push(item);
push(selfRef, item);
}
print("Created " + selfRef.len() + " self-referencing structures");
print("Created " + len(selfRef) + " self-referencing structures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear self-ref structures...");
// Break cycles explicitly so reference counting can reclaim memory deterministically
for (var i = 0; i < selfRef.len(); i++) {
selfRef[i]["self"] = none;
}
selfRef = 123;
print("Memory after clear: " + memoryUsage() + " MB");
input("Cleared. Check memory usage...");
@ -99,12 +95,12 @@ for (var i = 0; i < 100000; i++) {
for (var j = 0; j < 100; j++) {
longString = longString + "data" + i + "_" + j + " ";
}
stringCollections.push({
push(stringCollections, {
"content": longString,
"words": [longString, longString + "_copy", longString + "_backup"]
});
}
print("Created " + stringCollections.len() + " string collections");
print("Created " + len(stringCollections) + " string collections");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear string collections...");
stringCollections = none;

View File

@ -8,7 +8,7 @@ print("Initial memory: " + memoryUsage() + " MB");
print("Test 1: Recursive function closures");
var recursiveFuncs = [];
for (var i = 0; i < 100000; i++) {
recursiveFuncs.push(func() {
push(recursiveFuncs, func() {
var capturedI = i;
return func() {
if (capturedI > 0) {
@ -18,7 +18,7 @@ for (var i = 0; i < 100000; i++) {
};
});
}
print("Created " + recursiveFuncs.len() + " recursive closure functions");
print("Created " + len(recursiveFuncs) + " recursive closure functions");
print("Memory after creation: " + memoryUsage() + " MB");
input("Press Enter to clear recursive functions...");
recursiveFuncs = none;
@ -29,14 +29,14 @@ input("Cleared. Check memory usage...");
print("Test 2: Function factories");
var factories = [];
for (var i = 0; i < 100000; i++) {
factories.push(func() {
push(factories, func() {
var multiplier = i;
return func(x) {
return x * multiplier;
};
});
}
print("Created " + factories.len() + " function factories");
print("Created " + len(factories) + " function factories");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear factories...");
factories = [];
@ -47,7 +47,7 @@ input("Cleared. Check memory usage...");
print("Test 3: Deep function nesting");
var deepNested = [];
for (var i = 0; i < 50000; i++) {
deepNested.push(func() {
push(deepNested, func() {
return func() {
return func() {
return func() {
@ -59,7 +59,7 @@ for (var i = 0; i < 50000; i++) {
};
});
}
print("Created " + deepNested.len() + " deeply nested functions");
print("Created " + len(deepNested) + " deeply nested functions");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear deep nested...");
deepNested = "test";
@ -76,10 +76,10 @@ for (var i = 0; i < 1000000; i++) {
var funcB = func() {
return "B" + i;
};
// Store both in same array element to create potential circular refs
circularFuncs.push([funcA, funcB, func() { return funcA; }]);
// Store both in same array element to create potential circular refs
push(circularFuncs, [funcA, funcB, func() { return funcA; }]);
}
print("Created " + circularFuncs.len() + " circular function structures");
print("Created " + len(circularFuncs) + " circular function structures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear circular functions...");
circularFuncs = 42;

View File

@ -10,16 +10,16 @@ var nestedData = [];
for (var i = 0; i < 1000; i++) {
var row = [];
for (var j = 0; j < 1000; j++) {
row.push({
push(row, {
"i": i,
"j": j,
"func": func() { return i * j; },
"data": [i, j, i+j]
});
}
nestedData.push(row);
push(nestedData, row);
}
print("Created " + nestedData.len() + "x" + nestedData[0].len() + " nested structure");
print("Created " + len(nestedData) + "x" + len(nestedData[0]) + " nested structure");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear nested data...");
nestedData = none;
@ -31,14 +31,14 @@ print("Test 2: While loop accumulation");
var accumulator = [];
var counter = 0;
while (counter < 500000) {
accumulator.push({
push(accumulator, {
"count": counter,
"func": func() { return counter * 2; },
"meta": ["item" + counter, counter % 100]
});
counter = counter + 1;
}
print("Accumulated " + accumulator.len() + " items in while loop");
print("Accumulated " + len(accumulator) + " items in while loop");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear accumulator...");
accumulator = [];
@ -72,7 +72,7 @@ print("Test 4: Do-while function creation");
var doWhileFuncs = [];
var dwCounter = 0;
do {
doWhileFuncs.push(func() {
push(doWhileFuncs, func() {
var captured = dwCounter;
return func() {
return captured * captured;
@ -80,7 +80,7 @@ do {
});
dwCounter = dwCounter + 1;
} while (dwCounter < 100000);
print("Created " + doWhileFuncs.len() + " functions in do-while");
print("Created " + len(doWhileFuncs) + " functions in do-while");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear do-while functions...");
doWhileFuncs = "cleared";
@ -97,7 +97,7 @@ for (var i = 0; i < 500000; i++) {
if (i > 400000 && i % 100 == 0) {
// Create larger objects near the end
complexData.push({
push(complexData, {
"large": [
func() { return i; },
[i, i+1, i+2, i+3],
@ -105,14 +105,14 @@ for (var i = 0; i < 500000; i++) {
]
});
} else {
complexData.push(func() { return i; });
push(complexData, func() { return i; });
}
if (i > 450000 && complexData.len() > 350000) {
if (i > 450000 && len(complexData) > 350000) {
break; // Early exit
}
}
print("Complex loop created " + complexData.len() + " items");
print("Complex loop created " + len(complexData) + " items");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear complex data...");
complexData = none;
@ -126,7 +126,7 @@ for (var cycle = 0; cycle < 100; cycle++) {
// Create lots of data
for (var i = 0; i < 10000; i++) {
churnData.push([
push(churnData, [
func() { return i + cycle; },
{"cycle": cycle, "item": i}
]);

View File

@ -11,16 +11,16 @@ for (var i = 0; i < 50000; i++) {
var capturedArray = [i, i+1, i+2, "data" + i];
var capturedDict = {"id": i, "values": [i*2, i*3]};
funcWithCollections.push(func() {
push(funcWithCollections, func() {
// Capture both collections
var localArray = capturedArray;
var localDict = capturedDict;
return func() {
return localArray.len() + localDict.len();
return len(localArray) + len(localDict);
};
});
}
print("Created " + funcWithCollections.len() + " functions with collection captures");
print("Created " + len(funcWithCollections) + " functions with collection captures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear function collections...");
funcWithCollections = [];
@ -31,7 +31,7 @@ input("Cleared. Check memory usage...");
print("Test 2: Collections with mixed content");
var mixedContent = [];
for (var i = 0; i < 30000; i++) {
mixedContent.push([
push(mixedContent, [
func() { return i; }, // Function
[i, i+1, func() { return i*2; }], // Array with function
{"value": i, "func": func() { return i*3; }}, // Dict with function
@ -40,7 +40,7 @@ for (var i = 0; i < 30000; i++) {
i % 2 == 0 // Boolean
]);
}
print("Created " + mixedContent.len() + " mixed content collections");
print("Created " + len(mixedContent) + " mixed content collections");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear mixed content...");
mixedContent = none;
@ -58,9 +58,9 @@ for (var i = 0; i < 100000; i++) {
obj["array"] = [1, 2, 3];
obj["array"][0] = func() { return i * 2; };
propObjects.push(obj);
push(propObjects, obj);
}
print("Created " + propObjects.len() + " objects with dynamic properties");
print("Created " + len(propObjects) + " objects with dynamic properties");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear property objects...");
propObjects = "cleared";
@ -71,20 +71,20 @@ input("Cleared. Check memory usage...");
print("Test 4: Type reassignment chains");
var typeChains = [];
for (var i = 0; i < 200000; i++) {
typeChains.push(i);
push(typeChains, i);
}
print("Memory after number array: " + memoryUsage() + " MB");
input("Created number array. Press Enter to convert to functions...");
// Reassign all elements to functions
for (var i = 0; i < typeChains.len(); i++) {
for (var i = 0; i < len(typeChains); i++) {
typeChains[i] = func() { return i; };
}
print("Memory after function conversion: " + memoryUsage() + " MB");
input("Converted to functions. Press Enter to convert to dicts...");
// Reassign all elements to dicts
for (var i = 0; i < typeChains.len(); i++) {
for (var i = 0; i < len(typeChains); i++) {
typeChains[i] = {"id": i, "func": func() { return i; }};
}
print("Memory after dict conversion: " + memoryUsage() + " MB");
@ -98,12 +98,12 @@ print("Test 5: Rapid allocation/deallocation");
for (var round = 0; round < 10; round++) {
var temp = [];
for (var i = 0; i < 100000; i++) {
temp.push([
push(temp, [
func() { return i; },
{"data": [i, i+1, func() { return i*2; }]}
]);
}
print("Round " + round + ": Created " + temp.len() + " items, Memory: " + memoryUsage() + " MB");
print("Round " + round + ": Created " + len(temp) + " items, Memory: " + memoryUsage() + " MB");
temp = none; // Clear immediately
if (round % 2 == 1) print("After clear round " + round + ": " + memoryUsage() + " MB");
}

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'base64' module
void registerBase64Module(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'eval' module providing eval(code) and evalFile(path)
void registerEvalModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'io' module (file I/O and input)
void registerIoModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'json' module
void registerJsonModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'math' module
void registerMathModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'os' module
void registerOsModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'path' module (path utilities)
void registerPathModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'rand' module
void registerRandModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Registers all builtin modules with the interpreter
void registerAllBuiltinModules(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'sys' module
void registerSysModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'time' module (time functions)
void registerTimeModule(Interpreter& interpreter);

View File

@ -5,7 +5,6 @@
#include <string>
#include "Lexer.h"
#include "Interpreter.h"
#include "ModuleRegistry.h"
#include "helperFunctions/ShortHands.h"
#include "ErrorReporter.h"
@ -21,105 +20,10 @@ public:
~Bob() = default;
public:
// Embedding helpers (bridge to internal interpreter)
void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init) {
if (interpreter) interpreter->registerModule(name, init);
else pendingConfigurators.push_back([name, init](Interpreter& I){ I.registerModule(name, init); });
}
void setBuiltinModulePolicy(bool allow) {
if (interpreter) interpreter->setBuiltinModulePolicy(allow);
else pendingConfigurators.push_back([allow](Interpreter& I){ I.setBuiltinModulePolicy(allow); });
}
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) {
if (interpreter) interpreter->setBuiltinModuleAllowList(allowed);
else pendingConfigurators.push_back([allowed](Interpreter& I){ I.setBuiltinModuleAllowList(allowed); });
}
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) {
if (interpreter) interpreter->setBuiltinModuleDenyList(denied);
else pendingConfigurators.push_back([denied](Interpreter& I){ I.setBuiltinModuleDenyList(denied); });
}
bool defineGlobal(const std::string& name, const Value& v) {
if (interpreter) return interpreter->defineGlobalVar(name, v);
pendingConfigurators.push_back([name, v](Interpreter& I){ I.defineGlobalVar(name, v); });
return true;
}
bool tryGetGlobal(const std::string& name, Value& out) const { return interpreter ? interpreter->tryGetGlobalVar(name, out) : false; }
void runFile(const std::string& path);
void runPrompt();
bool evalFile(const std::string& path);
bool evalString(const std::string& code, const std::string& filename = "<eval>");
// Safety policy helpers (public API)
// Set all safety-related policies at once
void setSafetyPolicy(
bool allowBuiltins,
const std::vector<std::string>& allowList,
const std::vector<std::string>& denyList,
bool allowFileImports,
bool preferFileOverBuiltin,
const std::vector<std::string>& searchPaths
) {
if (interpreter) {
interpreter->setBuiltinModulePolicy(allowBuiltins);
interpreter->setBuiltinModuleAllowList(allowList);
interpreter->setBuiltinModuleDenyList(denyList);
interpreter->setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
} else {
pendingConfigurators.push_back([=](Interpreter& I){
I.setBuiltinModulePolicy(allowBuiltins);
I.setBuiltinModuleAllowList(allowList);
I.setBuiltinModuleDenyList(denyList);
I.setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
});
}
}
// Simple presets: "open", "safe", "locked"
void setSafetyPreset(const std::string& preset) {
if (preset == "open") {
setSafetyPolicy(
true, /* allowBuiltins */
{}, /* allowList -> empty means allow all */
{},
true, /* allowFileImports */
true, /* preferFileOverBuiltin */
{} /* searchPaths */
);
} else if (preset == "safe") {
// Allow only pure/harmless modules by default
setSafetyPolicy(
true,
std::vector<std::string>{
"sys", "time", "rand", "math", "path", "base64"
},
std::vector<std::string>{ /* denyList empty when allowList is used */ },
false, /* disallow file-based imports */
true,
{}
);
} else if (preset == "locked") {
// No builtins visible; no file imports
setSafetyPolicy(
false,
{},
{},
false,
true,
{}
);
} else {
// Default to safe
setSafetyPreset("safe");
}
}
private:
void ensureInterpreter(bool interactive);
void applyPendingConfigs() {
if (!interpreter) return;
for (auto& f : pendingConfigurators) { f(*interpreter); }
pendingConfigurators.clear();
}
std::vector<std::function<void(Interpreter&)>> pendingConfigurators;
void run(std::string source);
};

View File

@ -3,7 +3,6 @@
#include <iostream>
#include <vector>
#include <bitset>
#include <cctype>
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
std::vector<std::string> tokens;
@ -57,9 +56,9 @@ inline bool isHexDigit(char c) {
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
}
inline unsigned long long binaryStringToLong(const std::string& binaryString) {
inline u_long binaryStringToLong(const std::string& binaryString) {
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
unsigned long long result = 0;
u_long result = 0;
for (char ch : binaryDigits) {
result <<= 1;
result += (ch - '0');

View File

@ -34,9 +34,6 @@ private:
std::string currentFileName;
std::vector<std::string> callStack;
bool hadError = false;
// Support nested sources (e.g., eval of external files)
std::vector<std::vector<std::string>> sourceStack;
std::vector<std::string> fileNameStack;
public:
ErrorReporter() = default;
@ -61,11 +58,6 @@ public:
void pushCallStack(const std::string& functionName);
void popCallStack();
// Source push/pop for eval
void pushSource(const std::string& source, const std::string& fileName);
void popSource();
const std::string& getCurrentFileName() const { return currentFileName; }
private:
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
void displayCallStack(const std::vector<std::string>& callStack);

View File

@ -22,12 +22,10 @@ enum TokenType{
// Increment/decrement operators
PLUS_PLUS, MINUS_MINUS,
IDENTIFIER, STRING, NUMBER, KW_BOOL,
IDENTIFIER, STRING, NUMBER, BOOL,
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
IMPORT, FROM, AS,
TRY, CATCH, FINALLY, THROW,
WHILE, DO, VAR, CLASS, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
// Compound assignment operators
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
@ -53,12 +51,10 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
"PLUS_PLUS", "MINUS_MINUS",
"IDENTIFIER", "STRING", "NUMBER", "KW_BOOL",
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
"IMPORT", "FROM", "AS",
"TRY", "CATCH", "FINALLY", "THROW",
"WHILE", "DO", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
// Compound assignment operators
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
@ -81,21 +77,12 @@ const std::map<std::string, TokenType> KEYWORDS {
{"do", DO},
{"var", VAR},
{"class", CLASS},
{"extends", EXTENDS},
{"extension", EXTENSION},
{"super", SUPER},
{"this", THIS},
{"none", NONE},
{"return", RETURN},
{"break", BREAK},
{"continue", CONTINUE},
{"import", IMPORT},
{"from", FROM},
{"as", AS},
{"try", TRY},
{"catch", CATCH},
{"finally", FINALLY},
{"throw", THROW},
};
struct Token

View File

@ -68,12 +68,6 @@ private:
std::shared_ptr<Stmt> continueStatement();
std::shared_ptr<Stmt> declaration();
std::shared_ptr<Stmt> classDeclaration();
std::shared_ptr<Stmt> extensionDeclaration();
std::shared_ptr<Stmt> tryStatement();
std::shared_ptr<Stmt> throwStatement();
std::shared_ptr<Stmt> importStatement();
std::shared_ptr<Stmt> fromImportStatement();
std::shared_ptr<Stmt> varDeclaration();

View File

@ -17,8 +17,6 @@ struct ForStmt;
struct BreakStmt;
struct ContinueStmt;
struct AssignStmt;
struct ClassStmt;
struct ExtensionStmt;
#include "ExecutionContext.h"
@ -36,12 +34,6 @@ struct StmtVisitor
virtual void visitBreakStmt(const std::shared_ptr<BreakStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitContinueStmt(const std::shared_ptr<ContinueStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitAssignStmt(const std::shared_ptr<AssignStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitClassStmt(const std::shared_ptr<ClassStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitTryStmt(const std::shared_ptr<struct TryStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitThrowStmt(const std::shared_ptr<struct ThrowStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitImportStmt(const std::shared_ptr<struct ImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitFromImportStmt(const std::shared_ptr<struct FromImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
};
struct Stmt : public std::enable_shared_from_this<Stmt>
@ -51,39 +43,6 @@ struct Stmt : public std::enable_shared_from_this<Stmt>
virtual ~Stmt(){};
};
struct ClassField {
Token name;
std::shared_ptr<Expr> initializer; // may be null
ClassField(Token name, std::shared_ptr<Expr> init) : name(name), initializer(init) {}
};
struct ClassStmt : Stmt {
const Token name;
bool hasParent;
Token parentName; // valid only if hasParent
std::vector<ClassField> fields;
std::vector<std::shared_ptr<FunctionStmt>> methods;
ClassStmt(Token name, bool hasParent, Token parentName, std::vector<ClassField> fields, std::vector<std::shared_ptr<FunctionStmt>> methods)
: name(name), hasParent(hasParent), parentName(parentName), fields(std::move(fields)), methods(std::move(methods)) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitClassStmt(std::static_pointer_cast<ClassStmt>(shared_from_this()), context);
}
};
struct ExtensionStmt : Stmt {
const Token target;
std::vector<std::shared_ptr<FunctionStmt>> methods;
ExtensionStmt(Token target, std::vector<std::shared_ptr<FunctionStmt>> methods)
: target(target), methods(std::move(methods)) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitExtensionStmt(std::static_pointer_cast<ExtensionStmt>(shared_from_this()), context);
}
};
struct BlockStmt : Stmt
{
std::vector<std::shared_ptr<Stmt>> statements;
@ -251,55 +210,3 @@ struct AssignStmt : Stmt
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
}
};
struct TryStmt : Stmt {
std::shared_ptr<Stmt> tryBlock;
Token catchVar; // IDENTIFIER or empty token if no catch
std::shared_ptr<Stmt> catchBlock; // may be null
std::shared_ptr<Stmt> finallyBlock; // may be null
TryStmt(std::shared_ptr<Stmt> t, Token cvar, std::shared_ptr<Stmt> cblk, std::shared_ptr<Stmt> fblk)
: tryBlock(t), catchVar(cvar), catchBlock(cblk), finallyBlock(fblk) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitTryStmt(std::static_pointer_cast<TryStmt>(shared_from_this()), context);
}
};
struct ThrowStmt : Stmt {
const Token keyword;
std::shared_ptr<Expr> value;
ThrowStmt(Token kw, std::shared_ptr<Expr> v) : keyword(kw), value(v) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitThrowStmt(std::static_pointer_cast<ThrowStmt>(shared_from_this()), context);
}
};
// import module [as alias]
struct ImportStmt : Stmt {
Token importToken; // IMPORT
Token moduleName; // IDENTIFIER
bool hasAlias = false;
Token alias; // IDENTIFIER if hasAlias
ImportStmt(Token kw, Token mod, bool ha, Token al)
: importToken(kw), moduleName(mod), hasAlias(ha), alias(al) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitImportStmt(std::static_pointer_cast<ImportStmt>(shared_from_this()), context);
}
};
// from module import name [as alias], name2 ...
struct FromImportStmt : Stmt {
Token fromToken; // FROM
Token moduleName; // IDENTIFIER or STRING
struct ImportItem { Token name; bool hasAlias; Token alias; };
std::vector<ImportItem> items;
bool importAll = false; // true for: from module import *;
FromImportStmt(Token kw, Token mod, std::vector<ImportItem> it)
: fromToken(kw), moduleName(mod), items(std::move(it)), importAll(false) {}
FromImportStmt(Token kw, Token mod, bool all)
: fromToken(kw), moduleName(mod), importAll(all) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitFromImportStmt(std::static_pointer_cast<FromImportStmt>(shared_from_this()), context);
}
};

View File

@ -1,35 +0,0 @@
#pragma once
#include "Value.h"
#include "Lexer.h"
// Utility to compute the result of a compound assignment (e.g., +=, -=, etc.)
// Leaves error reporting to callers; throws std::runtime_error on unknown operator
inline Value computeCompoundAssignment(const Value& currentValue, TokenType opType, const Value& rhs) {
switch (opType) {
case PLUS_EQUAL:
return currentValue + rhs;
case MINUS_EQUAL:
return currentValue - rhs;
case STAR_EQUAL:
return currentValue * rhs;
case SLASH_EQUAL:
return currentValue / rhs;
case PERCENT_EQUAL:
return currentValue % rhs;
case BIN_AND_EQUAL:
return currentValue & rhs;
case BIN_OR_EQUAL:
return currentValue | rhs;
case BIN_XOR_EQUAL:
return currentValue ^ rhs;
case BIN_SLEFT_EQUAL:
return currentValue << rhs;
case BIN_SRIGHT_EQUAL:
return currentValue >> rhs;
default:
throw std::runtime_error("Unknown compound assignment operator");
}
}

View File

@ -3,7 +3,6 @@
#include <unordered_map>
#include <string>
#include <memory>
#include <unordered_set>
#include "Value.h"
#include "Lexer.h"
@ -30,7 +29,6 @@ public:
void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
}
ErrorReporter* getErrorReporter() const { return errorReporter; }
// Optimized define with inline
inline void define(const std::string& name, const Value& value) {
@ -43,18 +41,23 @@ public:
// Enhanced get with error reporting
Value get(const Token& name);
// Get by string name with error reporting
Value get(const std::string& name);
// Prune heavy containers in a snapshot to avoid capture cycles
void pruneForClosureCapture();
std::shared_ptr<Environment> getParent() const { return parent; }
// Export all variables (shallow copy) for module namespace
std::unordered_map<std::string, Value> getAll() const { return variables; }
inline void clear() { variables.clear(); }
// Set parent environment for TCO environment reuse
inline void setParent(std::shared_ptr<Environment> newParent) {
parent = newParent;
}
private:
std::unordered_map<std::string, Value> variables;
std::shared_ptr<Environment> parent;
ErrorReporter* errorReporter;
std::unordered_set<std::string> constBindings;
};

View File

@ -7,8 +7,4 @@ struct ExecutionContext {
Value returnValue = NONE_VALUE;
bool shouldBreak = false;
bool shouldContinue = false;
bool hasThrow = false;
Value thrownValue = NONE_VALUE;
int throwLine = 0;
int throwColumn = 0;
};

View File

@ -38,12 +38,6 @@ public:
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
void visitClassStmt(const std::shared_ptr<ClassStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context = nullptr) override;
void visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context = nullptr) override;
void visitImportStmt(const std::shared_ptr<ImportStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFromImportStmt(const std::shared_ptr<FromImportStmt>& statement, ExecutionContext* context = nullptr) override;
private:
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);

View File

@ -1,4 +1,15 @@
#pragma once
#include "Expression.h"
#include "Statement.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Environment.h"
#include "Value.h"
#include "BobStdLib.h"
#include "ErrorReporter.h"
#include "ExecutionContext.h"
#include "RuntimeDiagnostics.h"
#include <vector>
#include <memory>
#include <unordered_map>
@ -6,24 +17,6 @@
#include <optional>
#include <functional>
#include "Value.h"
#include "TypeWrapper.h"
#include "RuntimeDiagnostics.h"
#include "ModuleRegistry.h"
#include <unordered_set>
struct Expr;
struct Stmt;
struct Environment;
struct BuiltinFunction;
struct Function;
struct Thunk;
class ErrorReporter;
struct ExecutionContext;
struct CallExpr;
// Forward declaration
class Evaluator;
@ -69,41 +62,17 @@ private:
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
std::vector<std::shared_ptr<Function>> functions;
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
// Global extension registries
std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<Function>>> classExtensions;
std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<Function>>> builtinExtensions; // keys: "string","array","dict","any"
std::unordered_map<std::string, std::string> classParents; // child -> parent
std::unordered_map<std::string, std::unordered_map<std::string, Value>> classTemplates; // className -> template dict
// Field initializers per class in source order (to evaluate across inheritance chain)
std::unordered_map<std::string, std::vector<std::pair<std::string, std::shared_ptr<Expr>>>> classFieldInitializers; // className -> [(field, expr)]
ErrorReporter* errorReporter;
bool inThunkExecution = false;
// Automatic cleanup tracking
int functionCreationCount = 0;
int thunkCreationCount = 0;
static const int CLEANUP_THRESHOLD = 10000;
static const int CLEANUP_THRESHOLD = 1000000;
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
std::unique_ptr<Evaluator> evaluator;
std::unique_ptr<Executor> executor;
// Module cache: module key -> module dict value
std::unordered_map<std::string, Value> moduleCache;
// Builtin module registry
ModuleRegistry builtinModules;
// Import policy flags
bool allowFileImports = true;
bool preferFileOverBuiltin = true;
bool allowBuiltinImports = true;
std::vector<std::string> moduleSearchPaths; // e.g., BOBPATH
// Pending throw propagation from expression evaluation
bool hasPendingThrow = false;
Value pendingThrow = NONE_VALUE;
int pendingThrowLine = 0;
int pendingThrowColumn = 0;
int lastErrorLine = 0;
int lastErrorColumn = 0;
int tryDepth = 0;
bool inlineErrorReported = false;
public:
explicit Interpreter(bool isInteractive);
@ -124,75 +93,25 @@ public:
bool isInteractiveMode() const;
std::shared_ptr<Environment> getEnvironment();
void setEnvironment(std::shared_ptr<Environment> env);
ErrorReporter* getErrorReporter() const { return errorReporter; }
void addThunk(std::shared_ptr<Thunk> thunk);
void addFunction(std::shared_ptr<Function> function);
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
void cleanupUnusedFunctions();
void cleanupUnusedThunks();
void forceCleanup();
// Extension APIs
void registerExtension(const std::string& targetName, const std::string& methodName, std::shared_ptr<Function> fn);
std::shared_ptr<Function> lookupExtension(const std::string& targetName, const std::string& methodName);
void registerClass(const std::string& className, const std::string& parentName);
std::string getParentClass(const std::string& className) const;
void setClassTemplate(const std::string& className, const std::unordered_map<std::string, Value>& tmpl);
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
// Field initializer APIs
void setClassFieldInitializers(const std::string& className, const std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& inits) { classFieldInitializers[className] = inits; }
bool getClassFieldInitializers(const std::string& className, std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& out) const {
auto it = classFieldInitializers.find(className);
if (it == classFieldInitializers.end()) return false; out = it->second; return true;
}
void addStdLibFunctions();
// Module APIs
Value importModule(const std::string& spec, int line, int column); // returns module dict
bool fromImport(const std::string& spec, const std::vector<std::pair<std::string, std::string>>& items, int line, int column); // name->alias
void setModulePolicy(bool allowFiles, bool preferFiles, const std::vector<std::string>& searchPaths);
void setBuiltinModulePolicy(bool allowBuiltins) { allowBuiltinImports = allowBuiltins; builtinModules.setPolicy(allowBuiltins); }
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) { builtinModules.setAllowList(allowed); }
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) { builtinModules.setDenyList(denied); }
void registerBuiltinModule(const std::string& name, std::function<Value(Interpreter&)> factory) { builtinModules.registerFactory(name, std::move(factory)); }
// Simple module registration API
using ModuleBuilder = ModuleRegistry::ModuleBuilder;
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
builtinModules.registerModule(name, std::move(init));
}
// Global environment helpers
bool defineGlobalVar(const std::string& name, const Value& value);
bool tryGetGlobalVar(const std::string& name, Value& out) const;
// Throw propagation helpers
void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
bool consumePendingThrow(Value& out, int* lineOut = nullptr, int* colOut = nullptr) { if (!hasPendingThrow) return false; out = pendingThrow; if (lineOut) *lineOut = pendingThrowLine; if (colOut) *colOut = pendingThrowColumn; hasPendingThrow = false; pendingThrow = NONE_VALUE; pendingThrowLine = 0; pendingThrowColumn = 0; return true; }
// Try tracking
void enterTry() { tryDepth++; }
void exitTry() { if (tryDepth > 0) tryDepth--; }
bool isInTry() const { return tryDepth > 0; }
void markInlineErrorReported() { inlineErrorReported = true; }
bool hasInlineErrorReported() const { return inlineErrorReported; }
void clearInlineErrorReported() { inlineErrorReported = false; }
bool hasReportedError() const;
// Last error site tracking
void setLastErrorSite(int line, int column) { lastErrorLine = line; lastErrorColumn = column; }
int getLastErrorLine() const { return lastErrorLine; }
int getLastErrorColumn() const { return lastErrorColumn; }
// Process/host metadata (for sys module)
void setArgv(const std::vector<std::string>& args, const std::string& executablePath) { argvData = args; executableFile = executablePath; }
std::vector<std::string> getArgv() const { return argvData; }
std::string getExecutablePath() const { return executableFile; }
std::unordered_map<std::string, Value> getModuleCacheSnapshot() const { return moduleCache; }
// Function creation count management
void incrementFunctionCreationCount();
int getFunctionCreationCount() const;
void resetFunctionCreationCount();
int getCleanupThreshold() const;
// Public access for Evaluator
bool& getInThunkExecutionRef() { return inThunkExecution; }
private:
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
void addStdLibFunctions();
Value runTrampoline(Value initialResult);
// Stored argv/executable for sys module
std::vector<std::string> argvData;
std::string executableFile;
};

View File

@ -1,14 +0,0 @@
// ModuleDef.h
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include "Value.h"
struct Module {
std::string name;
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
};

View File

@ -1,70 +0,0 @@
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <memory>
#include "TypeWrapper.h" // BuiltinFunction, Value
class Interpreter; // fwd
class ModuleRegistry {
public:
struct ModuleBuilder {
std::string moduleName;
Interpreter& interpreterRef;
std::unordered_map<std::string, Value> exports;
ModuleBuilder(const std::string& n, Interpreter& i) : moduleName(n), interpreterRef(i) {}
void fn(const std::string& name, std::function<Value(std::vector<Value>, int, int)> func) {
exports[name] = Value(std::make_shared<BuiltinFunction>(name, func));
}
void val(const std::string& name, const Value& v) { exports[name] = v; }
};
using Factory = std::function<Value(Interpreter&)>;
void registerFactory(const std::string& name, Factory factory) {
factories[name] = std::move(factory);
}
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
registerFactory(name, [name, init](Interpreter& I) -> Value {
ModuleBuilder b(name, I);
init(b);
auto m = std::make_shared<Module>(name, b.exports);
return Value(m);
});
}
bool has(const std::string& name) const {
auto it = factories.find(name);
if (it == factories.end()) return false;
// Respect policy for presence checks to optionally cloak denied modules
if (!allowBuiltins) return false;
if (!allowList.empty() && allowList.find(name) == allowList.end()) return false;
if (denyList.find(name) != denyList.end()) return false;
return true;
}
Value create(const std::string& name, Interpreter& I) const {
auto it = factories.find(name);
if (it == factories.end()) return NONE_VALUE;
if (!allowBuiltins) return NONE_VALUE;
if (!allowList.empty() && allowList.find(name) == allowList.end()) return NONE_VALUE;
if (denyList.find(name) != denyList.end()) return NONE_VALUE;
return it->second(I);
}
void setPolicy(bool allow) { allowBuiltins = allow; }
void setAllowList(const std::vector<std::string>& allowed) { allowList = std::unordered_set<std::string>(allowed.begin(), allowed.end()); }
void setDenyList(const std::vector<std::string>& denied) { denyList = std::unordered_set<std::string>(denied.begin(), denied.end()); }
private:
std::unordered_map<std::string, Factory> factories;
std::unordered_set<std::string> allowList;
std::unordered_set<std::string> denyList;
bool allowBuiltins = true;
};

View File

@ -28,6 +28,8 @@ public:
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions);
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks);
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
std::vector<std::shared_ptr<Function>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks);

View File

@ -10,22 +10,51 @@
struct Stmt;
struct Environment;
struct Function
struct Object
{
virtual ~Object(){};
};
struct Number : Object
{
double value;
explicit Number(double value) : value(value) {}
};
struct String : Object
{
std::string value;
explicit String(std::string str) : value(str) {}
~String(){
}
};
struct Boolean : Object
{
bool value;
explicit Boolean(bool value) : value(value) {}
};
struct None : public Object
{
};
struct Function : public Object
{
const std::string name;
const std::vector<std::string> params;
const std::vector<std::shared_ptr<Stmt>> body;
const std::shared_ptr<Environment> closure;
const std::string ownerClass; // empty for non-methods
Function(std::string name, std::vector<std::string> params,
std::vector<std::shared_ptr<Stmt>> body,
std::shared_ptr<Environment> closure,
std::string ownerClass = "")
: name(name), params(params), body(body), closure(closure), ownerClass(ownerClass) {}
std::shared_ptr<Environment> closure)
: name(name), params(params), body(body), closure(closure) {}
};
struct BuiltinFunction
struct BuiltinFunction : public Object
{
const std::string name;
const std::function<Value(std::vector<Value>, int, int)> func;

View File

@ -3,7 +3,6 @@
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <utility>
#include <cmath>
#include <stdexcept>
@ -14,7 +13,6 @@ struct Environment;
struct Function;
struct BuiltinFunction;
struct Thunk;
struct Module;
// Type tags for the Value union
enum ValueType {
@ -26,12 +24,9 @@ enum ValueType {
VAL_BUILTIN_FUNCTION,
VAL_THUNK,
VAL_ARRAY,
VAL_DICT,
VAL_MODULE
VAL_DICT
};
// (moved below Value)
// Tagged value system (like Lua) - no heap allocation for simple values
struct Value {
union {
@ -42,7 +37,6 @@ struct Value {
std::string string_value; // Store strings outside the union for safety
std::shared_ptr<std::vector<Value> > array_value; // Store arrays as shared_ptr for mutability
std::shared_ptr<std::unordered_map<std::string, Value> > dict_value; // Store dictionaries as shared_ptr for mutability
std::shared_ptr<Module> module_value; // Module object
// Store functions as shared_ptr for proper reference counting
std::shared_ptr<Function> function;
@ -64,7 +58,6 @@ struct Value {
Value(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(arr))) {}
Value(const std::unordered_map<std::string, Value>& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(dict)) {}
Value(std::unordered_map<std::string, Value>&& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(std::move(dict))) {}
Value(std::shared_ptr<Module> m) : type(ValueType::VAL_MODULE), module_value(std::move(m)) {}
// Destructor to clean up functions and thunks
~Value() {
@ -77,7 +70,7 @@ struct Value {
// Move constructor
Value(Value&& other) noexcept
: type(other.type), string_value(std::move(other.string_value)), array_value(std::move(other.array_value)), dict_value(std::move(other.dict_value)),
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)), module_value(std::move(other.module_value)) {
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)) {
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
number = other.number; // Copy the union
@ -101,8 +94,6 @@ struct Value {
builtin_function = std::move(other.builtin_function);
} else if (type == ValueType::VAL_THUNK) {
thunk = std::move(other.thunk);
} else if (type == ValueType::VAL_MODULE) {
module_value = std::move(other.module_value);
} else {
number = other.number;
}
@ -126,8 +117,6 @@ struct Value {
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_THUNK) {
thunk = other.thunk; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_MODULE) {
module_value = other.module_value; // shared module
} else {
number = other.number;
}
@ -157,8 +146,6 @@ struct Value {
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_THUNK) {
thunk = other.thunk; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_MODULE) {
module_value = other.module_value;
} else {
number = other.number;
}
@ -174,7 +161,6 @@ struct Value {
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
inline bool isDict() const { return type == ValueType::VAL_DICT; }
inline bool isModule() const { return type == ValueType::VAL_MODULE; }
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
inline bool isNone() const { return type == ValueType::VAL_NONE; }
@ -190,7 +176,6 @@ struct Value {
case ValueType::VAL_THUNK: return "thunk";
case ValueType::VAL_ARRAY: return "array";
case ValueType::VAL_DICT: return "dict";
case ValueType::VAL_MODULE: return "module";
default: return "unknown";
}
}
@ -213,7 +198,6 @@ struct Value {
inline std::unordered_map<std::string, Value>& asDict() {
return *dict_value;
}
inline Module* asModule() const { return isModule() ? module_value.get() : nullptr; }
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
@ -230,7 +214,6 @@ struct Value {
case ValueType::VAL_THUNK: return thunk != nullptr;
case ValueType::VAL_ARRAY: return !array_value->empty();
case ValueType::VAL_DICT: return !dict_value->empty();
case ValueType::VAL_MODULE: return module_value != nullptr;
default: return false;
}
}
@ -313,12 +296,6 @@ struct Value {
result += "}";
return result;
}
case ValueType::VAL_MODULE: {
// Avoid accessing Module fields when it's still an incomplete type in some TUs.
// Delegate formatting to a small helper defined out-of-line in Value.cpp.
extern std::string formatModuleForToString(const std::shared_ptr<Module>&);
return formatModuleForToString(module_value);
}
default: return "unknown";
}
}
@ -443,16 +420,9 @@ struct Value {
}
};
// Define Module after Value so it can hold Value in exports without incomplete type issues
struct Module {
std::string name;
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
Module() = default;
Module(const std::string& n, const std::unordered_map<std::string, Value>& dict)
: name(n), exports(std::make_shared<std::unordered_map<std::string, Value>>(dict)) {}
};
// Global constants for common values
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;
extern const Value ZERO_VALUE;
extern const Value ONE_VALUE;

434
src/runtime/Evaluator.cpp Normal file
View File

@ -0,0 +1,434 @@
#include "Evaluator.h"
#include "Interpreter.h"
#include "helperFunctions/HelperFunctions.h"
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
if (expr->isNull) {
return NONE_VALUE;
}
if (expr->isNumber) {
double num;
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
num = binaryStringToLong(expr->value);
} else {
num = std::stod(expr->value);
}
return Value(num);
}
if (expr->isBoolean) {
if (expr->value == "true") return TRUE_VALUE;
if (expr->value == "false") return FALSE_VALUE;
}
return Value(expr->value);
}
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
return interpreter->evaluate(expression->expression);
}
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
{
Value right = interpreter->evaluate(expression->right);
switch (expression->oper.type) {
case MINUS:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(-right.asNumber());
case BANG:
return Value(!interpreter->isTruthy(right));
case BIN_NOT:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
default:
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
}
}
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
Value left = interpreter->evaluate(expression->left);
Value right = interpreter->evaluate(expression->right);
// Handle logical operators (AND, OR) - these work with any types
if (expression->oper.type == AND) {
return interpreter->isTruthy(left) ? right : left;
}
if (expression->oper.type == OR) {
return interpreter->isTruthy(left) ? left : right;
}
// Handle equality operators - these work with any types
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
bool equal = interpreter->isEqual(left, right);
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
}
// Handle comparison operators - only work with numbers
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
if (left.isNumber() && right.isNumber()) {
double leftNum = left.asNumber();
double rightNum = right.asNumber();
switch (expression->oper.type) {
case GREATER: return Value(leftNum > rightNum);
case GREATER_EQUAL: return Value(leftNum >= rightNum);
case LESS: return Value(leftNum < rightNum);
case LESS_EQUAL: return Value(leftNum <= rightNum);
default: break; // Unreachable
}
}
// Error for non-number comparisons
std::string opName;
switch (expression->oper.type) {
case GREATER: opName = ">"; break;
case GREATER_EQUAL: opName = ">="; break;
case LESS: opName = "<"; break;
case LESS_EQUAL: opName = "<="; break;
default: break; // Unreachable
}
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
}
// Handle all other operators using Value's operator overloads
try {
switch (expression->oper.type) {
case PLUS: return left + right;
case MINUS: return left - right;
case STAR: return left * right;
case SLASH: return left / right;
case PERCENT: return left % right;
case BIN_AND: return left & right;
case BIN_OR: return left | right;
case BIN_XOR: return left ^ right;
case BIN_SLEFT: return left << right;
case BIN_SRIGHT: return left >> right;
default:
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
}
} catch (const std::runtime_error& e) {
// The Value operators provide good error messages, just add context
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
e.what(), expression->oper.lexeme);
throw;
}
}
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
{
return interpreter->getEnvironment()->get(expression->name);
}
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
// Get the current value of the operand
Value currentValue = interpreter->evaluate(expression->operand);
if (!currentValue.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
}
double currentNum = currentValue.asNumber();
double newValue;
// Determine the operation based on the operator
if (expression->oper.type == PLUS_PLUS) {
newValue = currentNum + 1.0;
} else if (expression->oper.type == MINUS_MINUS) {
newValue = currentNum - 1.0;
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Invalid increment/decrement operator.", "");
throw std::runtime_error("Invalid increment/decrement operator.");
}
// Update the variable or array element
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
// Handle array indexing increment/decrement
Value array = interpreter->evaluate(arrayExpr->array);
Value index = interpreter->evaluate(arrayExpr->index);
if (!array.isArray()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Can only index arrays", "");
throw std::runtime_error("Can only index arrays");
}
if (!index.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
"Runtime Error", "Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
// Update the array element
arr[idx] = Value(newValue);
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
}
// Return the appropriate value based on prefix/postfix
if (expression->isPrefix) {
return Value(newValue); // Prefix: return new value
} else {
return currentValue; // Postfix: return old value
}
}
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = interpreter->evaluate(expression->value);
switch (expression->op.type) {
case PLUS_EQUAL:
case MINUS_EQUAL:
case STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case BIN_AND_EQUAL:
case BIN_OR_EQUAL:
case BIN_XOR_EQUAL:
case BIN_SLEFT_EQUAL:
case BIN_SRIGHT_EQUAL: {
Value currentValue = interpreter->getEnvironment()->get(expression->name);
// ... (rest of compound assignment logic) ...
break;
}
default:
break;
}
interpreter->getEnvironment()->assign(expression->name, value);
return value;
}
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
Value condition = interpreter->evaluate(expression->condition);
if (interpreter->isTruthy(condition)) {
return interpreter->evaluate(expression->thenExpr);
} else {
return interpreter->evaluate(expression->elseExpr);
}
}
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
Value callee = expression->callee->accept(this);
std::vector<Value> arguments;
for (const auto& argument : expression->arguments) {
arguments.push_back(argument->accept(this));
}
if (callee.isFunction()) {
Function* function = callee.asFunction();
// Check arity
if (arguments.size() != function->params.size()) {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
std::to_string(arguments.size()) + ".", "");
throw std::runtime_error("Wrong number of arguments.");
}
// Create new environment for function call
auto environment = std::make_shared<Environment>(function->closure);
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
}
// Execute function body
auto previous = interpreter->getEnvironment();
interpreter->setEnvironment(environment);
ExecutionContext context;
context.isFunctionBody = true;
try {
for (const auto& stmt : function->body) {
interpreter->execute(stmt, &context);
if (context.hasReturn) {
interpreter->setEnvironment(previous);
return context.returnValue;
}
}
} catch (...) {
interpreter->setEnvironment(previous);
throw;
}
interpreter->setEnvironment(previous);
return NONE_VALUE;
} else if (callee.isBuiltinFunction()) {
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
} else {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Can only call functions and classes.", "");
throw std::runtime_error("Can only call functions and classes.");
}
}
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
std::vector<Value> elements;
for (const auto& element : expr->elements) {
elements.push_back(interpreter->evaluate(element));
}
return Value(elements);
}
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
Value array = expr->array->accept(this);
Value index = expr->index->accept(this);
if (array.isArray()) {
// Handle array indexing
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
const std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
return arr[idx];
} else if (array.isDict()) {
// Handle dictionary indexing
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
throw std::runtime_error("Dictionary key must be a string");
}
std::string key = index.asString();
const std::unordered_map<std::string, Value>& dict = array.asDict();
auto it = dict.find(key);
if (it != dict.end()) {
return it->second;
} else {
return NONE_VALUE; // Return none for missing keys
}
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only index arrays and dictionaries", "");
throw std::runtime_error("Can only index arrays and dictionaries");
}
}
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
Value array = expr->array->accept(this);
Value index = expr->index->accept(this);
Value value = expr->value->accept(this);
if (array.isArray()) {
// Handle array assignment
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
arr[idx] = value;
return value;
} else if (array.isDict()) {
// Handle dictionary assignment
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
throw std::runtime_error("Dictionary key must be a string");
}
std::string key = index.asString();
std::unordered_map<std::string, Value>& dict = array.asDict();
dict[key] = value;
return value;
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only assign to array or dictionary elements", "");
throw std::runtime_error("Can only assign to array or dictionary elements");
}
}
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
std::unordered_map<std::string, Value> dict;
for (const auto& pair : expr->pairs) {
Value value = interpreter->evaluate(pair.second);
dict[pair.first] = value;
}
return Value(dict);
}
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
std::vector<std::string> paramNames;
for (const Token& param : expression->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
interpreter->addFunction(function);
return Value(function);
}

245
src/runtime/Executor.cpp Normal file
View File

@ -0,0 +1,245 @@
#include "Executor.h"
#include "Evaluator.h"
#include "Interpreter.h"
#include <iostream>
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
: interpreter(interpreter), evaluator(evaluator) {}
Executor::~Executor() {}
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
for (const auto& statement : statements) {
execute(statement, nullptr);
}
}
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
statement->accept(this, context);
}
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
interpreter->setEnvironment(env);
for (const auto& statement : statements) {
execute(statement, context);
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
interpreter->setEnvironment(previous);
return;
}
}
interpreter->setEnvironment(previous);
}
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
executeBlock(statement->statements, newEnv, context);
}
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
Value value = statement->expression->accept(evaluator);
if (interpreter->isInteractiveMode())
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
}
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
Value value = NONE_VALUE;
if (statement->initializer != nullptr) {
value = statement->initializer->accept(evaluator);
}
interpreter->getEnvironment()->define(statement->name.lexeme, value);
}
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
std::vector<std::string> paramNames;
for (const Token& param : statement->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>(statement->name.lexeme,
paramNames,
statement->body,
interpreter->getEnvironment());
interpreter->addFunction(function);
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function));
}
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
Value value = NONE_VALUE;
if (statement->value != nullptr) {
value = statement->value->accept(evaluator);
}
if (context && context->isFunctionBody) {
context->hasReturn = true;
context->returnValue = value;
}
}
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch, context);
}
}
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
}
}
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
do {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
}
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
if (statement->initializer != nullptr) {
execute(statement->initializer, context);
}
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
}
continue;
}
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
}
}
}
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
if (context) {
context->shouldBreak = true;
}
}
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
if (context) {
context->shouldContinue = true;
}
}
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
interpreter->getEnvironment()->assign(statement->name, value);
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(statement->name);
Value newValue;
switch (statement->op.type) {
case PLUS_EQUAL:
newValue = currentValue + value;
break;
case MINUS_EQUAL:
newValue = currentValue - value;
break;
case STAR_EQUAL:
newValue = currentValue * value;
break;
case SLASH_EQUAL:
newValue = currentValue / value;
break;
case PERCENT_EQUAL:
newValue = currentValue % value;
break;
case BIN_AND_EQUAL:
newValue = currentValue & value;
break;
case BIN_OR_EQUAL:
newValue = currentValue | value;
break;
case BIN_XOR_EQUAL:
newValue = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
newValue = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
newValue = currentValue >> value;
break;
default:
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
"Unknown assignment operator: " + statement->op.lexeme, "");
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
}
interpreter->getEnvironment()->assign(statement->name, newValue);
}
}

View File

@ -1,41 +0,0 @@
#include "base64_module.h"
#include "Interpreter.h"
#include <string>
static const char* B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static std::string b64encode(const std::string& in){
std::string out; out.reserve(((in.size()+2)/3)*4);
int val=0, valb=-6;
for (unsigned char c : in){
val = (val<<8) + c;
valb += 8;
while (valb >= 0){ out.push_back(B64[(val>>valb)&0x3F]); valb -= 6; }
}
if (valb>-6) out.push_back(B64[((val<<8)>>(valb+8))&0x3F]);
while (out.size()%4) out.push_back('=');
return out;
}
static std::string b64decode(const std::string& in){
std::vector<int> T(256,-1); for (int i=0;i<64;i++) T[(unsigned char)B64[i]]=i;
std::string out; out.reserve((in.size()*3)/4);
int val=0, valb=-8;
for (unsigned char c : in){ if (T[c]==-1) break; val=(val<<6)+T[c]; valb+=6; if (valb>=0){ out.push_back(char((val>>valb)&0xFF)); valb-=8; } }
return out;
}
void registerBase64Module(Interpreter& interpreter) {
interpreter.registerModule("base64", [](Interpreter::ModuleBuilder& m) {
m.fn("encode", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(b64encode(a[0].asString()));
});
m.fn("decode", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(b64decode(a[0].asString()));
});
});
}

View File

@ -1,64 +0,0 @@
#include "eval.h"
#include "Interpreter.h"
#include "ErrorReporter.h"
#include "Lexer.h"
#include "Parser.h"
#include <fstream>
#include <sstream>
void registerEvalModule(Interpreter& interpreter) {
interpreter.registerModule("eval", [](Interpreter::ModuleBuilder& m) {
ErrorReporter* er = m.interpreterRef.getErrorReporter();
m.fn("eval", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 || !args[0].isString()) {
if (er) er->reportError(line, column, "Invalid Arguments", "eval expects exactly 1 string argument", "eval");
throw std::runtime_error("eval expects exactly 1 string argument");
}
std::string code = args[0].asString();
std::string evalName = "<eval>";
try {
if (er) er->pushSource(code, evalName);
Lexer lx; if (er) lx.setErrorReporter(er);
auto toks = lx.Tokenize(code);
Parser p(toks); if (er) p.setErrorReporter(er);
auto stmts = p.parse();
I.interpret(stmts);
return NONE_VALUE;
} catch (...) {
if (er) er->popSource();
throw;
}
if (er) er->popSource();
});
m.fn("evalFile", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 || !args[0].isString()) {
if (er) er->reportError(line, column, "Invalid Arguments", "evalFile expects exactly 1 string argument (path)", "evalFile");
throw std::runtime_error("evalFile expects exactly 1 string argument (path)");
}
std::string filename = args[0].asString();
std::ifstream f(filename);
if (!f.is_open()) {
if (er) er->reportError(line, column, "StdLib Error", "Could not open file: " + filename, "");
throw std::runtime_error("Could not open file: " + filename);
}
std::stringstream buf; buf << f.rdbuf(); f.close();
std::string code = buf.str();
try {
if (er) er->pushSource(code, filename);
Lexer lx; if (er) lx.setErrorReporter(er);
auto toks = lx.Tokenize(code);
Parser p(toks); if (er) p.setErrorReporter(er);
auto stmts = p.parse();
I.interpret(stmts);
return NONE_VALUE;
} catch (...) {
if (er) er->popSource();
throw;
}
if (er) er->popSource();
});
});
}

View File

@ -1,72 +0,0 @@
#include <fstream>
#include <sstream>
#include "io.h"
#include "Interpreter.h"
#include "ErrorReporter.h"
void registerIoModule(Interpreter& interpreter) {
interpreter.registerModule("io", [](Interpreter::ModuleBuilder& m) {
ErrorReporter* er = m.interpreterRef.getErrorReporter();
m.fn("readFile", [er](std::vector<Value> a, int line, int col) -> Value {
if (a.empty() || !a[0].isString() || a.size() > 2 || (a.size() == 2 && !a[1].isString())) {
if (er) er->reportError(line, col, "Invalid Arguments", "readFile(path[, mode]) expects 1-2 args (strings)", "readFile");
throw std::runtime_error("readFile(path[, mode]) expects 1-2 string args");
}
std::string mode = (a.size() == 2) ? a[1].asString() : std::string("r");
std::ios_base::openmode om = std::ios::in;
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
std::ifstream f(a[0].asString(), om);
if (!f.is_open()) {
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
throw std::runtime_error("Could not open file");
}
std::stringstream buf; buf << f.rdbuf(); f.close();
return Value(buf.str());
});
m.fn("writeFile", [er](std::vector<Value> a, int line, int col) -> Value {
if (a.size() < 2 || a.size() > 3 || !a[0].isString() || !a[1].isString() || (a.size() == 3 && !a[2].isString())) {
if (er) er->reportError(line, col, "Invalid Arguments", "writeFile(path, data[, mode]) expects 2-3 args (strings)", "writeFile");
throw std::runtime_error("writeFile(path, data[, mode]) expects 2-3 string args");
}
std::string mode = (a.size() == 3) ? a[2].asString() : std::string("w");
std::ios_base::openmode om = std::ios::out;
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
if (mode.find('a') != std::string::npos) om |= std::ios::app; else om |= std::ios::trunc;
std::ofstream f(a[0].asString(), om);
if (!f.is_open()) {
if (er) er->reportError(line, col, "StdLib Error", "Could not create file", a[0].asString());
throw std::runtime_error("Could not create file");
}
f << a[1].asString(); f.close();
return NONE_VALUE;
});
m.fn("readLines", [er](std::vector<Value> a, int line, int col) -> Value {
if (a.size() != 1 || !a[0].isString()) {
if (er) er->reportError(line, col, "Invalid Arguments", "readLines(path) expects 1 string arg", "readLines");
throw std::runtime_error("readLines(path) expects 1 string arg");
}
std::ifstream f(a[0].asString());
if (!f.is_open()) {
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
throw std::runtime_error("Could not open file");
}
std::vector<Value> lines; std::string s;
while (std::getline(f, s)) lines.emplace_back(s);
f.close();
return Value(lines);
});
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
std::ifstream f(a[0].asString()); bool ok = f.good(); f.close();
return Value(ok);
});
// input remains a global in stdlib; not provided here
});
}

View File

@ -1,83 +0,0 @@
#include "json.h"
#include "Interpreter.h"
#include <string>
#include <cctype>
// Minimal JSON parser/stringifier (numbers, strings, booleans, null, arrays, objects)
namespace {
struct Cursor { const std::string* s; size_t i = 0; };
void skipWs(Cursor& c){ while (c.i < c.s->size() && std::isspace(static_cast<unsigned char>((*c.s)[c.i]))) ++c.i; }
bool match(Cursor& c, char ch){ skipWs(c); if (c.i < c.s->size() && (*c.s)[c.i]==ch){ ++c.i; return true;} return false; }
std::string parseString(Cursor& c){
if (!match(c,'"')) return {};
std::string out; while (c.i < c.s->size()){
char ch = (*c.s)[c.i++];
if (ch=='"') break;
if (ch=='\\' && c.i < c.s->size()){
char e = (*c.s)[c.i++];
switch(e){ case '"': out+='"'; break; case '\\': out+='\\'; break; case '/': out+='/'; break; case 'b': out+='\b'; break; case 'f': out+='\f'; break; case 'n': out+='\n'; break; case 'r': out+='\r'; break; case 't': out+='\t'; break; default: out+=e; }
} else out+=ch;
}
return out;
}
double parseNumber(Cursor& c){ skipWs(c); size_t start=c.i; while (c.i<c.s->size() && (std::isdigit((*c.s)[c.i])||(*c.s)[c.i]=='-'||(*c.s)[c.i]=='+'||(*c.s)[c.i]=='.'||(*c.s)[c.i]=='e'||(*c.s)[c.i]=='E')) ++c.i; return std::stod(c.s->substr(start,c.i-start)); }
Value parseValue(Cursor& c);
Value parseArray(Cursor& c){
match(c,'['); std::vector<Value> arr; skipWs(c); if (match(c,']')) return Value(arr);
while (true){ arr.push_back(parseValue(c)); skipWs(c); if (match(c,']')) break; match(c,','); }
return Value(arr);
}
Value parseObject(Cursor& c){
match(c,'{'); std::unordered_map<std::string,Value> obj; skipWs(c); if (match(c,'}')) return Value(obj);
while (true){ std::string k = parseString(c); match(c,':'); Value v = parseValue(c); obj.emplace(k, v); skipWs(c); if (match(c,'}')) break; match(c,','); }
return Value(obj);
}
Value parseValue(Cursor& c){ skipWs(c); if (c.i>=c.s->size()) return NONE_VALUE; char ch=(*c.s)[c.i];
if (ch=='"') return Value(parseString(c));
if (ch=='[') return parseArray(c);
if (ch=='{') return parseObject(c);
if (!c.s->compare(c.i,4,"true")) { c.i+=4; return Value(true);}
if (!c.s->compare(c.i,5,"false")) { c.i+=5; return Value(false);}
if (!c.s->compare(c.i,4,"null")) { c.i+=4; return NONE_VALUE;}
return Value(parseNumber(c));
}
std::string escapeString(const std::string& s){
std::string out; out.reserve(s.size()+2); out.push_back('"');
for(char ch: s){
switch(ch){ case '"': out+="\\\""; break; case '\\': out+="\\\\"; break; case '\n': out+="\\n"; break; case '\r': out+="\\r"; break; case '\t': out+="\\t"; break; default: out+=ch; }
}
out.push_back('"'); return out;
}
std::string stringifyValue(const Value& v){
switch(v.type){
case VAL_NONE: return "null";
case VAL_BOOLEAN: return v.asBoolean()?"true":"false";
case VAL_NUMBER: return v.toString();
case VAL_STRING: return escapeString(v.asString());
case VAL_ARRAY: {
const auto& a=v.asArray(); std::string out="["; for(size_t i=0;i<a.size();++i){ if(i) out+=","; out+=stringifyValue(a[i]); } out+="]"; return out;
}
case VAL_DICT: {
const auto& d=v.asDict(); std::string out="{"; bool first=true; for(const auto& kv:d){ if(!first) out+=","; first=false; out+=escapeString(kv.first); out+=":"; out+=stringifyValue(kv.second);} out+="}"; return out;
}
default: return "null";
}
}
}
void registerJsonModule(Interpreter& interpreter) {
interpreter.registerModule("json", [](Interpreter::ModuleBuilder& m) {
m.fn("parse", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return NONE_VALUE;
Cursor c{&a[0].asString(), 0};
return parseValue(c);
});
m.fn("stringify", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1) return Value(std::string("null"));
return Value(stringifyValue(a[0]));
});
});
}

View File

@ -1,56 +0,0 @@
#include "math_module.h"
#include "Interpreter.h"
#include <cmath>
static Value unary_math(std::vector<Value> a, double(*fn)(double)){
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
return Value(fn(a[0].asNumber()));
}
void registerMathModule(Interpreter& interpreter) {
interpreter.registerModule("math", [](Interpreter::ModuleBuilder& m) {
m.fn("sin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sin); });
m.fn("cos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cos); });
m.fn("tan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tan); });
m.fn("asin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::asin); });
m.fn("acos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::acos); });
m.fn("atan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::atan); });
m.fn("sinh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sinh); });
m.fn("cosh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cosh); });
m.fn("tanh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tanh); });
m.fn("exp", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::exp); });
m.fn("log", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log); });
m.fn("log10", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log10); });
m.fn("sqrt", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sqrt); });
m.fn("ceil", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::ceil); });
m.fn("floor", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::floor); });
m.fn("round", [](std::vector<Value> a, int, int)->Value{
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
return Value(std::round(a[0].asNumber()));
});
m.fn("abs", [](std::vector<Value> a, int, int)->Value{
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
return Value(std::fabs(a[0].asNumber()));
});
m.fn("pow", [](std::vector<Value> a, int, int)->Value{
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
return Value(std::pow(a[0].asNumber(), a[1].asNumber()));
});
m.fn("min", [](std::vector<Value> a, int, int)->Value{
if (a.empty()) return NONE_VALUE;
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::min(mval, a[i].asNumber()); }
return Value(mval);
});
m.fn("max", [](std::vector<Value> a, int, int)->Value{
if (a.empty()) return NONE_VALUE;
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::max(mval, a[i].asNumber()); }
return Value(mval);
});
m.val("pi", Value(3.14159265358979323846));
m.val("e", Value(2.71828182845904523536));
});
}

View File

@ -1,132 +0,0 @@
#include "os.h"
#include "Interpreter.h"
#include "Lexer.h"
#include <vector>
#include <string>
#include <filesystem>
#if defined(_WIN32)
#include <windows.h>
#include <direct.h>
#include <limits.h>
#include <io.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#else
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#endif
namespace fs = std::filesystem;
void registerOsModule(Interpreter& interpreter) {
interpreter.registerModule("os", [](Interpreter::ModuleBuilder& m) {
// Process
m.fn("getcwd", [](std::vector<Value>, int, int) -> Value {
char buf[PATH_MAX];
#if defined(_WIN32)
if (_getcwd(buf, sizeof(buf))) return Value(std::string(buf));
#else
if (getcwd(buf, sizeof(buf))) return Value(std::string(buf));
#endif
return NONE_VALUE;
});
m.fn("chdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
#if defined(_WIN32)
int rc = ::_chdir(a[0].asString().c_str());
#else
int rc = ::chdir(a[0].asString().c_str());
#endif
return Value(rc == 0);
});
m.fn("getpid", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(static_cast<double>(GetCurrentProcessId()));
#else
return Value(static_cast<double>(getpid()));
#endif
});
m.fn("getppid", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return NONE_VALUE; // not directly available; could use Toolhelp32Snapshot if needed
#else
return Value(static_cast<double>(getppid()));
#endif
});
m.fn("name", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("nt"));
#else
return Value(std::string("posix"));
#endif
});
// Filesystem
m.fn("listdir", [](std::vector<Value> a, int, int) -> Value {
std::string path = ".";
if (!a.empty() && a[0].isString()) path = a[0].asString();
std::vector<Value> out;
try {
for (const auto& entry : fs::directory_iterator(path)) {
out.push_back(Value(entry.path().filename().string()));
}
} catch (...) {}
return Value(out);
});
m.fn("mkdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::create_directory(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("rmdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("remove", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::exists(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("isfile", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::is_regular_file(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("isdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::is_directory(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("rename", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 2 || !a[0].isString() || !a[1].isString()) return Value(false);
try { fs::rename(a[0].asString(), a[1].asString()); return Value(true); } catch (...) { return Value(false); }
});
// Separators
m.fn("sep", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("\\"));
#else
return Value(std::string("/"));
#endif
});
m.fn("pathsep", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string(";"));
#else
return Value(std::string(":"));
#endif
});
m.fn("linesep", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("\r\n"));
#else
return Value(std::string("\n"));
#endif
});
});
}

View File

@ -1,63 +0,0 @@
#include "path_module.h"
#include "Interpreter.h"
#include <filesystem>
#include <cctype>
namespace fs = std::filesystem;
static std::string join_impl(const std::vector<Value>& parts){
if (parts.empty()) return std::string();
fs::path p;
for (const auto& v : parts) if (v.isString()) p /= v.asString();
return p.generic_string();
}
static bool isabs_impl(const std::string& s) {
#if defined(_WIN32)
if (s.size() >= 2 && (s[0] == '/' || s[0] == '\\')) return true; // root-relative on current drive
if (s.rfind("\\\\", 0) == 0) return true; // UNC path
if (s.size() >= 3 && std::isalpha(static_cast<unsigned char>(s[0])) && s[1] == ':' && (s[2] == '/' || s[2] == '\\')) return true; // C:\ or C:/
return false;
#else
return !s.empty() && s[0] == '/';
#endif
}
void registerPathModule(Interpreter& interpreter) {
interpreter.registerModule("path", [](Interpreter::ModuleBuilder& m) {
m.fn("join", [](std::vector<Value> a, int, int) -> Value {
return Value(join_impl(a));
});
m.fn("dirname", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(fs::path(a[0].asString()).parent_path().generic_string());
});
m.fn("basename", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(fs::path(a[0].asString()).filename().generic_string());
});
m.fn("splitext", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
fs::path p(a[0].asString());
std::string ext = p.has_extension() ? p.extension().generic_string() : std::string("");
fs::path basePath = p.has_extension() ? (p.parent_path() / p.stem()) : p;
return Value(std::vector<Value>{ Value(basePath.generic_string()), Value(ext) });
});
m.fn("normalize", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(fs::path(a[0].asString()).lexically_normal().generic_string());
});
m.fn("isabs", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return Value(false);
return Value(isabs_impl(a[0].asString()));
});
m.fn("relpath", [](std::vector<Value> a, int, int) -> Value {
if (a.size()<1 || a.size()>2 || !a[0].isString() || (a.size()==2 && !a[1].isString())) return NONE_VALUE;
fs::path target(a[0].asString());
fs::path base = (a.size()==2)? fs::path(a[1].asString()) : fs::current_path();
return Value(fs::relative(target, base).generic_string());
});
});
}

View File

@ -1,35 +0,0 @@
#include "rand.h"
#include "Interpreter.h"
#include <random>
void registerRandModule(Interpreter& interpreter) {
interpreter.registerModule("rand", [](Interpreter::ModuleBuilder& m) {
static std::mt19937_64 rng{std::random_device{}()};
m.fn("seed", [](std::vector<Value> a, int, int) -> Value {
if (a.size() == 1 && a[0].isNumber()) {
rng.seed(static_cast<uint64_t>(a[0].asNumber()));
}
return NONE_VALUE;
});
m.fn("random", [](std::vector<Value>, int, int) -> Value {
std::uniform_real_distribution<double> dist(0.0, 1.0);
return Value(dist(rng));
});
m.fn("randint", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
long long lo = static_cast<long long>(a[0].asNumber());
long long hi = static_cast<long long>(a[1].asNumber());
if (hi < lo) std::swap(lo, hi);
std::uniform_int_distribution<long long> dist(lo, hi);
return Value(static_cast<double>(dist(rng)));
});
m.fn("choice", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isArray() || a[0].asArray().empty()) return NONE_VALUE;
const auto& arr = a[0].asArray();
std::uniform_int_distribution<size_t> dist(0, arr.size() - 1);
return arr[dist(rng)];
});
});
}

View File

@ -1,25 +0,0 @@
#include "register.h"
#include "sys.h"
#include "os.h"
#include "eval.h"
#include "io.h"
#include "time_module.h"
#include "rand.h"
#include "math_module.h"
#include "path_module.h"
#include "base64_module.h"
void registerAllBuiltinModules(Interpreter& interpreter) {
registerSysModule(interpreter);
registerOsModule(interpreter);
registerEvalModule(interpreter);
registerIoModule(interpreter);
registerTimeModule(interpreter);
registerRandModule(interpreter);
registerMathModule(interpreter);
registerPathModule(interpreter);
registerBase64Module(interpreter);
// registerJsonModule(interpreter); // deferred pending extensive testing
}

View File

@ -1,99 +0,0 @@
#include "sys.h"
#include "Interpreter.h"
#include "Environment.h"
#include "Lexer.h" // for Token and IDENTIFIER
#if defined(_WIN32)
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <limits.h>
#include <cstdlib>
#include <cstring>
#include <vector>
// Platform-specific includes for memoryUsage()
#if defined(__APPLE__) && defined(__MACH__)
#include <mach/mach.h>
#elif defined(__linux__)
#include <fstream>
#include <sstream>
#elif defined(_WIN32)
#define NOMINMAX
#include <windows.h>
#include <psapi.h>
#endif
void registerSysModule(Interpreter& interpreter) {
interpreter.registerModule("sys", [](Interpreter::ModuleBuilder& m) {
Interpreter& I = m.interpreterRef;
m.fn("platform", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("win32"));
#elif defined(__APPLE__)
return Value(std::string("darwin"));
#elif defined(__linux__)
return Value(std::string("linux"));
#else
return Value(std::string("unknown"));
#endif
});
m.fn("version", [](std::vector<Value>, int, int) -> Value { return Value(std::string("0.0.3")); });
// argv(): array of strings
m.fn("argv", [&I](std::vector<Value>, int, int) -> Value {
std::vector<Value> out;
for (const auto& s : I.getArgv()) out.push_back(Value(s));
return Value(out);
});
// executable(): absolute path to the running binary (host-provided)
m.fn("executable", [&I](std::vector<Value>, int, int) -> Value { return Value(I.getExecutablePath()); });
// modules(): read-only snapshot of module cache
m.fn("modules", [&I](std::vector<Value>, int, int) -> Value {
Value dictVal = Value(std::unordered_map<std::string, Value>{});
auto snapshot = I.getModuleCacheSnapshot();
return Value(snapshot);
});
// memoryUsage(): process RSS in MB (best effort per-platform)
m.fn("memoryUsage", [](std::vector<Value> a, int, int) -> Value {
if (!a.empty()) return NONE_VALUE;
size_t memoryBytes = 0;
#if defined(__APPLE__) && defined(__MACH__)
// macOS
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
memoryBytes = info.resident_size;
}
#elif defined(__linux__)
// Linux
std::ifstream statusFile("/proc/self/status");
std::string line;
while (std::getline(statusFile, line)) {
if (line.substr(0, 6) == "VmRSS:") {
std::istringstream iss(line);
std::string label, value, unit;
iss >> label >> value >> unit;
memoryBytes = std::stoull(value) * 1024; // KB -> bytes
break;
}
}
#elif defined(_WIN32)
// Windows
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
memoryBytes = pmc.WorkingSetSize;
}
#endif
double memoryMB = static_cast<double>(memoryBytes) / (1024.0 * 1024.0);
return Value(memoryMB);
});
m.fn("exit", [](std::vector<Value> a, int, int) -> Value {
int code = 0; if (!a.empty() && a[0].isNumber()) code = static_cast<int>(a[0].asNumber());
std::exit(code);
return NONE_VALUE;
});
// env/cwd/pid moved to os; keep sys minimal
});
}

View File

@ -1,31 +0,0 @@
#include "time_module.h"
#include "Interpreter.h"
#include "Environment.h"
#include <chrono>
#include <thread>
void registerTimeModule(Interpreter& interpreter) {
interpreter.registerModule("time", [](Interpreter::ModuleBuilder& m) {
m.fn("now", [](std::vector<Value>, int, int) -> Value {
using namespace std::chrono;
auto now = system_clock::now().time_since_epoch();
auto us = duration_cast<microseconds>(now).count();
return Value(static_cast<double>(us));
});
m.fn("monotonic", [](std::vector<Value>, int, int) -> Value {
using namespace std::chrono;
auto now = steady_clock::now().time_since_epoch();
auto us = duration_cast<microseconds>(now).count();
return Value(static_cast<double>(us));
});
m.fn("sleep", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
double seconds = a[0].asNumber();
if (seconds < 0) return NONE_VALUE;
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(seconds * 1000)));
return NONE_VALUE;
});
});
}

View File

@ -3,23 +3,35 @@
#include "bob.h"
#include "Parser.h"
void Bob::ensureInterpreter(bool interactive) {
if (!interpreter) interpreter = msptr(Interpreter)(interactive);
applyPendingConfigs();
}
void Bob::runFile(const std::string& path)
{
ensureInterpreter(false);
interpreter->addStdLibFunctions();
if (!evalFile(path)) {
std::cout << "Execution failed\n";
this->interpreter = msptr(Interpreter)(false);
std::ifstream file = std::ifstream(path);
std::string source;
if(file.is_open()){
source = std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
else
{
std::cout << "File not found\n";
return;
}
// Load source code into error reporter for context
errorReporter.loadSource(source, path);
// Connect error reporter to interpreter
interpreter->setErrorReporter(&errorReporter);
this->run(source);
}
void Bob::runPrompt()
{
ensureInterpreter(true);
this->interpreter = msptr(Interpreter)(true);
std::cout << "Bob v" << VERSION << ", 2025\n";
while(true)
{
@ -34,40 +46,50 @@ void Bob::runPrompt()
// Reset error state before each REPL command
errorReporter.resetErrorState();
interpreter->addStdLibFunctions();
(void)evalString(line, "REPL");
// Load source code into error reporter for context
errorReporter.loadSource(line, "REPL");
// Connect error reporter to interpreter
interpreter->setErrorReporter(&errorReporter);
this->run(line);
}
}
bool Bob::evalFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) return false;
std::string src((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
errorReporter.loadSource(src, path);
interpreter->setErrorReporter(&errorReporter);
void Bob::run(std::string source)
{
try {
// Connect error reporter to lexer
lexer.setErrorReporter(&errorReporter);
auto tokens = lexer.Tokenize(src);
Parser p(tokens);
p.setErrorReporter(&errorReporter);
auto statements = p.parse();
interpreter->interpret(statements);
return true;
} catch (...) { return false; }
}
bool Bob::evalString(const std::string& code, const std::string& filename) {
errorReporter.loadSource(code, filename);
interpreter->setErrorReporter(&errorReporter);
try {
lexer.setErrorReporter(&errorReporter);
auto tokens = lexer.Tokenize(code);
std::vector<Token> tokens = lexer.Tokenize(std::move(source));
Parser p(tokens);
// Connect error reporter to parser
p.setErrorReporter(&errorReporter);
auto statements = p.parse();
std::vector<sptr(Stmt)> statements = p.parse();
interpreter->interpret(statements);
return true;
} catch (...) { return false; }
}
catch(std::exception &e)
{
// Only suppress errors that have already been reported by the error reporter
if (errorReporter.hasReportedError()) {
return;
}
// For errors that weren't reported (like parser errors, undefined variables, etc.)
// print them normally
std::cout << "Error: " << e.what() << '\n';
return;
}
catch(const std::exception& e)
{
// Unknown error - report it since it wasn't handled by the interpreter
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred: " + std::string(e.what()));
return;
}
}

View File

@ -2,27 +2,13 @@
//
#include "bob.h"
#include "Interpreter.h"
int main(int argc, char* argv[]){
Bob bobLang;
// Enable open preset (all builtins, file imports allowed)
bobLang.setSafetyPreset("open");
if(argc > 1) {
// Seed argv/executable for sys module
std::vector<std::string> args; for (int i = 2; i < argc; ++i) args.emplace_back(argv[i]);
bobLang.registerModule("__configure_sys_argv__", [args, execPath = std::string(argv[0])](ModuleRegistry::ModuleBuilder& m){
m.interpreterRef.setArgv(args, execPath);
});
bobLang.runFile(argv[1]);
} else {
// For REPL, use interactive mode and seed empty argv
std::vector<std::string> args;
bobLang.registerModule("__configure_sys_argv__", [args, execPath = std::string(argv[0])](ModuleRegistry::ModuleBuilder& m){
m.interpreterRef.setArgv(args, execPath);
});
bobLang.runPrompt();
}

View File

@ -56,29 +56,6 @@ void ErrorReporter::loadSource(const std::string& source, const std::string& fil
}
}
void ErrorReporter::pushSource(const std::string& source, const std::string& fileName) {
// Save current
sourceStack.push_back(sourceLines);
fileNameStack.push_back(currentFileName);
// Load new
loadSource(source, fileName);
}
void ErrorReporter::popSource() {
if (!sourceStack.empty()) {
sourceLines = sourceStack.back();
sourceStack.pop_back();
} else {
sourceLines.clear();
}
if (!fileNameStack.empty()) {
currentFileName = fileNameStack.back();
fileNameStack.pop_back();
} else {
currentFileName.clear();
}
}
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
hadError = true;
displaySourceContext(line, column, errorType, message, operator_, showArrow);
@ -98,8 +75,6 @@ void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
if (!context.fileName.empty()) {
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
} else if (!currentFileName.empty()) {
std::cout << colorize("File: ", Colors::BOLD) << colorize(currentFileName, Colors::CYAN) << "\n";
}
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +

View File

@ -363,14 +363,18 @@ std::vector<Token> Lexer::Tokenize(std::string source){
}
if(!isNotation) {
// Only treat '.' as part of the number if followed by a digit
if (src.size() > 1 && src[0] == '.' && std::isdigit(src[1])) {
advance(); // consume '.'
if (!src.empty() && src[0] == '.') {
advance();
if (!src.empty() && std::isdigit(src[0])) {
num += '.';
while (!src.empty() && std::isdigit(src[0])) {
num += src[0];
advance();
}
} else {
throw std::runtime_error("LEXER: malformed number at: " + std::to_string(this->line));
}
}
}
else

View File

@ -252,35 +252,21 @@ sptr(Expr) Parser::postfix()
{
sptr(Expr) expr = primary();
while (true) {
if (match({OPEN_PAREN})) {
expr = finishCall(expr);
continue;
}
if (match({OPEN_BRACKET})) {
expr = finishArrayIndex(expr);
continue;
}
if (match({DOT})) {
Token name = consume(IDENTIFIER, "Expected property name after '.'.");
expr = msptr(PropertyExpr)(expr, name);
continue;
}
// Check for postfix increment/decrement
if (match({PLUS_PLUS, MINUS_MINUS})) {
Token oper = previous();
// Ensure the expression is a variable or array indexing
if (!std::dynamic_pointer_cast<VarExpr>(expr) &&
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr) &&
!std::dynamic_pointer_cast<PropertyExpr>(expr)) {
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr)) {
if (errorReporter) {
errorReporter->reportError(oper.line, oper.column, "Parse Error",
"Postfix increment/decrement can only be applied to variables or array elements", "");
}
throw std::runtime_error("Postfix increment/decrement can only be applied to variables or array elements.");
}
expr = msptr(IncrementExpr)(expr, oper, false);
continue;
}
break;
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
}
return expr;
@ -295,15 +281,8 @@ sptr(Expr) Parser::primary()
if(match({NUMBER})) return msptr(LiteralExpr)(previous().lexeme, true, false, false);
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
if(match( {IDENTIFIER, THIS, SUPER})) {
Token ident = previous();
if (ident.type == THIS) {
return msptr(VarExpr)(Token{IDENTIFIER, "this", ident.line, ident.column});
}
if (ident.type == SUPER) {
return msptr(VarExpr)(Token{IDENTIFIER, "super", ident.line, ident.column});
}
return msptr(VarExpr)(ident);
if(match( {IDENTIFIER})) {
return call();
}
if(match({OPEN_PAREN}))
@ -381,13 +360,7 @@ sptr(Expr) Parser::dictLiteral()
sptr(Expr) Parser::call()
{
Token ident = previous();
sptr(Expr) expr;
if (ident.type == THIS) {
expr = msptr(VarExpr)(Token{IDENTIFIER, "this", ident.line, ident.column});
} else {
expr = msptr(VarExpr)(ident);
}
sptr(Expr) expr = msptr(VarExpr)(previous());
while (true) {
if (match({OPEN_PAREN})) {
@ -423,8 +396,6 @@ sptr(Stmt) Parser::declaration()
try{
if(match({VAR})) return varDeclaration();
if(match({FUNCTION})) return functionDeclaration();
if(match({CLASS})) return classDeclaration();
if(match({EXTENSION})) return extensionDeclaration();
return statement();
}
catch(std::runtime_error& e)
@ -434,86 +405,6 @@ sptr(Stmt) Parser::declaration()
}
}
sptr(Stmt) Parser::classDeclaration() {
Token name = consume(IDENTIFIER, "Expected class name.");
bool hasParent = false;
Token parentName{};
if (match({EXTENDS})) {
hasParent = true;
parentName = consume(IDENTIFIER, "Expected parent class name after 'extends'.");
}
consume(OPEN_BRACE, "Expected '{' after class declaration.");
std::vector<ClassField> fields;
std::vector<std::shared_ptr<FunctionStmt>> methods;
while (!check(CLOSE_BRACE) && !isAtEnd()) {
if (match({VAR})) {
Token fieldName = consume(IDENTIFIER, "Expected field name.");
std::shared_ptr<Expr> init = nullptr;
if (match({EQUAL})) {
init = expression();
}
consume(SEMICOLON, "Expected ';' after field declaration.");
fields.emplace_back(fieldName, init);
} else if (match({FUNCTION})) {
Token methodName = consume(IDENTIFIER, "Expected method name.");
consume(OPEN_PAREN, "Expected '(' after method name.");
std::vector<Token> parameters;
if (!check(CLOSE_PAREN)) {
do {
parameters.push_back(consume(IDENTIFIER, "Expected parameter name."));
} while (match({COMMA}));
}
consume(CLOSE_PAREN, "Expected ')' after parameters.");
consume(OPEN_BRACE, "Expected '{' before method body.");
enterFunction();
std::vector<std::shared_ptr<Stmt>> body = block();
exitFunction();
methods.push_back(msptr(FunctionStmt)(methodName, parameters, body));
} else {
if (errorReporter) {
errorReporter->reportError(peek().line, peek().column, "Parse Error", "Expected 'var' or 'func' in class body", "");
}
throw std::runtime_error("Invalid class member");
}
}
consume(CLOSE_BRACE, "Expected '}' after class body.");
return msptr(ClassStmt)(name, hasParent, parentName, fields, methods);
}
sptr(Stmt) Parser::extensionDeclaration() {
Token target = consume(IDENTIFIER, "Expected extension target (class/builtin/any).");
consume(OPEN_BRACE, "Expected '{' after extension target.");
std::vector<std::shared_ptr<FunctionStmt>> methods;
while (!check(CLOSE_BRACE) && !isAtEnd()) {
if (match({FUNCTION})) {
Token methodName = consume(IDENTIFIER, "Expected method name.");
consume(OPEN_PAREN, "Expected '(' after method name.");
std::vector<Token> parameters;
if (!check(CLOSE_PAREN)) {
do {
parameters.push_back(consume(IDENTIFIER, "Expected parameter name."));
} while (match({COMMA}));
}
consume(CLOSE_PAREN, "Expected ')' after parameters.");
consume(OPEN_BRACE, "Expected '{' before method body.");
enterFunction();
std::vector<std::shared_ptr<Stmt>> body = block();
exitFunction();
methods.push_back(msptr(FunctionStmt)(methodName, parameters, body));
} else {
if (errorReporter) {
errorReporter->reportError(peek().line, peek().column, "Parse Error", "Expected 'func' in extension body", "");
}
throw std::runtime_error("Invalid extension member");
}
}
consume(CLOSE_BRACE, "Expected '}' after extension body.");
return msptr(ExtensionStmt)(target, methods);
}
sptr(Stmt) Parser::varDeclaration()
{
Token name = consume(IDENTIFIER, "Expected variable name.");
@ -586,13 +477,6 @@ std::shared_ptr<Expr> Parser::functionExpression() {
sptr(Stmt) Parser::statement()
{
if(match({RETURN})) return returnStatement();
if(match({IMPORT})) return importStatement();
// Fallback if lexer didn't classify keyword: detect by lexeme
if (check(IDENTIFIER) && peek().lexeme == "import") { advance(); return importStatement(); }
if(match({FROM})) return fromImportStatement();
if (check(IDENTIFIER) && peek().lexeme == "from") { advance(); return fromImportStatement(); }
if(match({TRY})) return tryStatement();
if(match({THROW})) return throwStatement();
if(match({IF})) return ifStatement();
if(match({DO})) return doWhileStatement();
if(match({WHILE})) return whileStatement();
@ -602,7 +486,7 @@ sptr(Stmt) Parser::statement()
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
// Check for assignment statement - simplified approach
if(check(IDENTIFIER) || check(THIS)) {
if(check(IDENTIFIER)) {
// Try to parse as assignment expression first
int currentPos = current;
try {
@ -627,43 +511,6 @@ sptr(Stmt) Parser::statement()
return expressionStatement();
}
std::shared_ptr<Stmt> Parser::importStatement() {
Token importTok = previous();
// import Name [as Alias] | import "path"
bool isString = check(STRING);
Token mod = isString ? advance() : consume(IDENTIFIER, "Expected module name or path string after 'import'.");
// Keep IDENTIFIER for name-based imports; resolver will try file and then builtin
bool hasAlias = false; Token alias;
if (match({AS})) {
hasAlias = true;
alias = consume(IDENTIFIER, "Expected alias identifier after 'as'.");
}
consume(SEMICOLON, "Expected ';' after import statement.");
return msptr(ImportStmt)(importTok, mod, hasAlias, alias);
}
std::shared_ptr<Stmt> Parser::fromImportStatement() {
Token fromTok = previous();
bool isString = check(STRING);
Token mod = isString ? advance() : consume(IDENTIFIER, "Expected module name or path string after 'from'.");
// Keep IDENTIFIER for name-based from-imports
consume(IMPORT, "Expected 'import' after module name.");
// Support star-import: from module import *;
if (match({STAR})) {
consume(SEMICOLON, "Expected ';' after from-import statement.");
return msptr(FromImportStmt)(fromTok, mod, true);
}
std::vector<FromImportStmt::ImportItem> items;
do {
Token name = consume(IDENTIFIER, "Expected name to import.");
bool hasAlias = false; Token alias;
if (match({AS})) { hasAlias = true; alias = consume(IDENTIFIER, "Expected alias identifier after 'as'."); }
items.push_back({name, hasAlias, alias});
} while (match({COMMA}));
consume(SEMICOLON, "Expected ';' after from-import statement.");
return msptr(FromImportStmt)(fromTok, mod, items);
}
sptr(Stmt) Parser::assignmentStatement()
{
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
@ -829,35 +676,6 @@ std::vector<sptr(Stmt)> Parser::block()
return statements;
}
std::shared_ptr<Stmt> Parser::tryStatement() {
// try { ... } (catch (e) { ... })? (finally { ... })?
auto tryBlock = statement();
Token catchVar{IDENTIFIER, "", previous().line, previous().column};
std::shared_ptr<Stmt> catchBlock = nullptr;
std::shared_ptr<Stmt> finallyBlock = nullptr;
if (match({CATCH})) {
consume(OPEN_PAREN, "Expected '(' after 'catch'.");
// Allow optional identifier: catch() or catch(e)
if (!check(CLOSE_PAREN)) {
Token var = consume(IDENTIFIER, "Expected identifier for catch variable.");
catchVar = var;
}
consume(CLOSE_PAREN, "Expected ')' after catch variable.");
catchBlock = statement();
}
if (match({FINALLY})) {
finallyBlock = statement();
}
return msptr(TryStmt)(tryBlock, catchVar, catchBlock, finallyBlock);
}
std::shared_ptr<Stmt> Parser::throwStatement() {
Token kw = previous();
auto val = expression();
consume(SEMICOLON, "Expected ';' after throw expression.");
return msptr(ThrowStmt)(kw, val);
}
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
std::vector<sptr(Expr)> arguments;

View File

@ -2,15 +2,6 @@
#include "ErrorReporter.h"
void Environment::assign(const Token& name, const Value& value) {
// Disallow reassignment of module bindings (immutability of module variable)
auto itv = variables.find(name.lexeme);
if (itv != variables.end() && itv->second.isModule()) {
if (errorReporter) {
errorReporter->reportError(name.line, name.column, "Import Error",
"Cannot reassign module binding '" + name.lexeme + "'", "");
}
throw std::runtime_error("Cannot reassign module binding '" + name.lexeme + "'");
}
auto it = variables.find(name.lexeme);
if (it != variables.end()) {
it->second = value;
@ -22,9 +13,7 @@ void Environment::assign(const Token& name, const Value& value) {
return;
}
// Report only if not within a try; otherwise let try/catch handle
if (errorReporter) {
// We cannot check tryDepth here directly; rely on Executor to suppress double-reporting
errorReporter->reportError(name.line, name.column, "Runtime Error",
"Undefined variable '" + name.lexeme + "'", "");
}
@ -48,14 +37,27 @@ Value Environment::get(const Token& name) {
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
}
Value Environment::get(const std::string& name) {
auto it = variables.find(name);
if (it != variables.end()) {
return it->second;
}
if (parent != nullptr) {
return parent->get(name);
}
throw std::runtime_error("Undefined variable '" + name + "'");
}
void Environment::pruneForClosureCapture() {
for (auto &entry : variables) {
Value &v = entry.second;
if (v.isArray()) {
// Replace with a new empty array to avoid mutating original shared storage
entry.second = Value(std::vector<Value>{});
} else if (v.isDict()) {
// Replace with a new empty dict to avoid mutating original shared storage
entry.second = Value(std::unordered_map<std::string, Value>{});
}
}

View File

@ -1,7 +1,5 @@
#include "Evaluator.h"
#include "Interpreter.h"
#include "Environment.h"
#include "AssignmentUtils.h"
#include "helperFunctions/HelperFunctions.h"
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
@ -37,6 +35,8 @@ Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
switch (expression->oper.type) {
case MINUS:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(-right.asNumber());
@ -46,6 +46,8 @@ Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
case BIN_NOT:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
@ -102,6 +104,8 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
default: break; // Unreachable
}
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
}
@ -111,23 +115,8 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
case PLUS: return left + right;
case MINUS: return left - right;
case STAR: return left * right;
case SLASH: {
if (right.isNumber() && right.asNumber() == 0.0) {
// Report precise site for division by zero
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Division by zero", "/");
throw std::runtime_error("Division by zero");
}
return left / right;
}
case PERCENT: {
if (right.isNumber() && right.asNumber() == 0.0) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Modulo by zero", "%");
throw std::runtime_error("Modulo by zero");
}
return left % right;
}
case SLASH: return left / right;
case PERCENT: return left % right;
case BIN_AND: return left & right;
case BIN_OR: return left | right;
case BIN_XOR: return left ^ right;
@ -139,7 +128,10 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
}
} catch (const std::runtime_error& e) {
throw; // Propagate to statement driver (try/catch) without reporting here
// The Value operators provide good error messages, just add context
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
e.what(), expression->oper.lexeme);
throw;
}
}
@ -172,7 +164,7 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
throw std::runtime_error("Invalid increment/decrement operator.");
}
// Update the variable, array element, or object property
// Update the variable or array element
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
@ -203,14 +195,6 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
// Update the array element
arr[idx] = Value(newValue);
} else if (auto propExpr = std::dynamic_pointer_cast<PropertyExpr>(expression->operand)) {
// obj.prop++ / obj.prop--
Value object = interpreter->evaluate(propExpr->object);
if (!object.isDict()) {
throw std::runtime_error("Can only increment/decrement properties on objects");
}
std::unordered_map<std::string, Value>& dict = object.asDict();
dict[propExpr->name.lexeme] = Value(newValue);
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
@ -230,24 +214,74 @@ Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression)
Value value = interpreter->evaluate(expression->value);
if (expression->op.type == EQUAL) {
// Assign first to release references held by the old values
interpreter->getEnvironment()->assign(expression->name, value);
// Perform cleanup on any reassignment
interpreter->forceCleanup();
return value;
try {
// Check if the variable existed and whether it held a collection
bool existed = false;
bool wasCollection = false;
try {
Value oldValue = interpreter->getEnvironment()->get(expression->name);
existed = true;
wasCollection = oldValue.isArray() || oldValue.isDict();
} catch (...) {
existed = false;
}
// Compound assignment operators
// Assign first to release references held by the old values
interpreter->getEnvironment()->assign(expression->name, value);
// Now that the old values are released, perform cleanup on any reassignment
interpreter->forceCleanup();
} catch (const std::exception& e) {
std::cerr << "Error during assignment: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
}
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(expression->name);
try {
Value newValue = computeCompoundAssignment(currentValue, expression->op.type, value);
interpreter->getEnvironment()->assign(expression->name, newValue);
return newValue;
} catch (const std::runtime_error&) {
Value newValue;
switch (expression->op.type) {
case PLUS_EQUAL:
newValue = currentValue + value;
break;
case MINUS_EQUAL:
newValue = currentValue - value;
break;
case STAR_EQUAL:
newValue = currentValue * value;
break;
case SLASH_EQUAL:
newValue = currentValue / value;
break;
case PERCENT_EQUAL:
newValue = currentValue % value;
break;
case BIN_AND_EQUAL:
newValue = currentValue & value;
break;
case BIN_OR_EQUAL:
newValue = currentValue | value;
break;
case BIN_XOR_EQUAL:
newValue = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
newValue = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
newValue = currentValue >> value;
break;
default:
interpreter->reportError(expression->op.line, expression->op.column, "Runtime Error",
"Unknown assignment operator: " + expression->op.lexeme, "");
throw;
throw std::runtime_error("Unknown assignment operator: " + expression->op.lexeme);
}
interpreter->getEnvironment()->assign(expression->name, newValue);
return newValue;
}
return value;
}
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
@ -284,7 +318,6 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
interpreter->markInlineErrorReported();
throw std::runtime_error("Array index must be a number");
}
@ -294,7 +327,6 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
interpreter->markInlineErrorReported();
throw std::runtime_error("Array index out of bounds");
}
@ -305,7 +337,6 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
interpreter->markInlineErrorReported();
throw std::runtime_error("Dictionary key must be a string");
}
@ -322,7 +353,6 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only index arrays and dictionaries", "");
interpreter->markInlineErrorReported();
throw std::runtime_error("Can only index arrays and dictionaries");
}
}
@ -331,142 +361,13 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
Value object = expr->object->accept(this);
std::string propertyName = expr->name.lexeme;
if (object.isModule()) {
// Forward to module exports
auto* mod = object.asModule();
if (mod && mod->exports) {
auto it = mod->exports->find(propertyName);
if (it != mod->exports->end()) return it->second;
}
return NONE_VALUE;
} else if (object.isDict()) {
Value v = getDictProperty(object, propertyName);
if (!v.isNone()) {
// If this is an inherited inline method, prefer a current-class extension override
if (v.isFunction()) {
const auto& d = object.asDict();
std::string curCls;
auto itc = d.find("__class");
if (itc != d.end() && itc->second.isString()) curCls = itc->second.asString();
Function* f = v.asFunction();
if (f && !curCls.empty() && !f->ownerClass.empty() && f->ownerClass != curCls) {
if (auto ext = interpreter->lookupExtension(curCls, propertyName)) {
return Value(ext);
}
}
}
return v;
}
// Fallback to class extensions with inheritance walk
const auto& d = object.asDict();
std::string cls = "";
auto it = d.find("__class");
if (it != d.end() && it->second.isString()) cls = it->second.asString();
if (!cls.empty()) {
std::string cur = cls;
while (!cur.empty()) {
if (auto fn = interpreter->lookupExtension(cur, propertyName)) return Value(fn);
cur = interpreter->getParentClass(cur);
}
}
// Provide method-style builtins on dict
if (propertyName == "len") {
auto bf = std::make_shared<BuiltinFunction>("dict.len", [object](std::vector<Value>, int, int){
return Value(static_cast<double>(object.asDict().size()));
});
return Value(bf);
} else if (propertyName == "keys") {
auto bf = std::make_shared<BuiltinFunction>("dict.keys", [object](std::vector<Value>, int, int){
std::vector<Value> keys; const auto& m = object.asDict();
for (const auto& kv : m) keys.push_back(Value(kv.first));
return Value(keys);
});
return Value(bf);
} else if (propertyName == "values") {
auto bf = std::make_shared<BuiltinFunction>("dict.values", [object](std::vector<Value>, int, int){
std::vector<Value> vals; const auto& m = object.asDict();
for (const auto& kv : m) vals.push_back(kv.second);
return Value(vals);
});
return Value(bf);
} else if (propertyName == "has") {
auto bf = std::make_shared<BuiltinFunction>("dict.has", [object](std::vector<Value> args, int, int){
if (args.size() != 1 || !args[0].isString()) return Value(false);
const auto& m = object.asDict();
return Value(m.find(args[0].asString()) != m.end());
});
return Value(bf);
}
// Fallback to dict and any extensions
if (auto fn = interpreter->lookupExtension("dict", propertyName)) return Value(fn);
if (auto anyFn = interpreter->lookupExtension("any", propertyName)) return Value(anyFn);
return NONE_VALUE;
if (object.isDict()) {
return getDictProperty(object, propertyName);
} else if (object.isArray()) {
Value v = getArrayProperty(object, propertyName);
if (!v.isNone()) return v;
// Provide method-style builtins on array
if (propertyName == "len") {
auto bf = std::make_shared<BuiltinFunction>("array.len", [object](std::vector<Value>, int, int){
return Value(static_cast<double>(object.asArray().size()));
});
return Value(bf);
} else if (propertyName == "push") {
auto bf = std::make_shared<BuiltinFunction>("array.push", [object](std::vector<Value> args, int, int){
std::vector<Value>& arr = const_cast<std::vector<Value>&>(object.asArray());
for (size_t i = 0; i < args.size(); ++i) arr.push_back(args[i]);
return object;
});
return Value(bf);
} else if (propertyName == "pop") {
auto bf = std::make_shared<BuiltinFunction>("array.pop", [object](std::vector<Value>, int, int){
std::vector<Value>& arr = const_cast<std::vector<Value>&>(object.asArray());
if (arr.empty()) return NONE_VALUE;
Value v = arr.back();
arr.pop_back();
return v;
});
return Value(bf);
}
// Fallback to array extensions
if (auto fn = interpreter->lookupExtension("array", propertyName)) return Value(fn);
if (auto anyFn = interpreter->lookupExtension("any", propertyName)) return Value(anyFn);
return NONE_VALUE;
return getArrayProperty(object, propertyName);
} else {
// Try extension dispatch for built-ins and any
std::string target;
if (object.isString()) target = "string";
else if (object.isNumber()) target = "number";
else if (object.isArray()) target = "array"; // handled above, but keep for completeness
else if (object.isDict()) target = "dict"; // handled above
else target = object.isModule() ? "any" : "any";
// Provide method-style builtins for string/number
if (object.isString() && propertyName == "len") {
auto bf = std::make_shared<BuiltinFunction>("string.len", [object](std::vector<Value>, int, int){
return Value(static_cast<double>(object.asString().length()));
});
return Value(bf);
}
if (object.isNumber() && propertyName == "toInt") {
auto bf = std::make_shared<BuiltinFunction>("number.toInt", [object](std::vector<Value>, int, int){
return Value(static_cast<double>(static_cast<long long>(object.asNumber())));
});
return Value(bf);
}
if (object.isModule()) {
// Modules are immutable and have no dynamic methods
return NONE_VALUE;
}
auto fn = interpreter->lookupExtension(target, propertyName);
if (!object.isModule() && fn) { return Value(fn); }
if (auto anyFn = interpreter->lookupExtension("any", propertyName)) {
return Value(anyFn);
}
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
"Cannot access property '" + propertyName + "' on this type", "");
interpreter->markInlineErrorReported();
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
}
}
@ -479,11 +380,8 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
if (array.isArray()) {
// Handle array assignment
if (!index.isNumber()) {
if (!interpreter->isInTry()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Array index must be a number");
}
@ -491,11 +389,8 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
if (!interpreter->isInTry()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Array index out of bounds");
}
@ -505,11 +400,8 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
} else if (array.isDict()) {
// Handle dictionary assignment
if (!index.isString()) {
if (!interpreter->isInTry()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Dictionary key must be a string");
}
@ -520,11 +412,8 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
return value;
} else {
if (!interpreter->isInTry()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only assign to array or dictionary elements", "");
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Can only assign to array or dictionary elements");
}
}
@ -546,25 +435,14 @@ Value Evaluator::visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExp
Value value = expr->value->accept(this);
std::string propertyName = expr->name.lexeme;
if (object.isModule()) {
// Modules are immutable: disallow setting properties
if (!interpreter->isInTry()) {
interpreter->reportError(expr->name.line, expr->name.column, "Import Error",
"Cannot assign property '" + propertyName + "' on module (immutable)", "");
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Cannot assign property on module (immutable)");
} else if (object.isDict()) {
if (object.isDict()) {
// Modify the dictionary in place
std::unordered_map<std::string, Value>& dict = object.asDict();
dict[propertyName] = value;
return value; // Return the assigned value
} else {
if (!interpreter->isInTry()) {
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
"Cannot assign property '" + propertyName + "' on non-object", "");
interpreter->markInlineErrorReported();
}
throw std::runtime_error("Cannot assign property '" + propertyName + "' on non-object");
}
}
@ -575,8 +453,10 @@ Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expressi
paramNames.push_back(param.lexeme);
}
// Capture a snapshot of the current environment so loop vars like 'i' are frozen per iteration
auto closureEnv = std::make_shared<Environment>(*interpreter->getEnvironment());
closureEnv->pruneForClosureCapture();
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
return Value(function);
}

View File

@ -1,9 +1,6 @@
#include "Executor.h"
#include "Evaluator.h"
#include "Interpreter.h"
#include "Environment.h"
#include "Parser.h"
#include "AssignmentUtils.h"
#include <iostream>
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
@ -12,47 +9,13 @@ Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
Executor::~Executor() {}
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
ExecutionContext top;
for (const auto& statement : statements) {
execute(statement, &top);
if (top.hasThrow) break;
}
if (top.hasThrow) {
// If already reported inline, don't double-report here
if (!interpreter->hasInlineErrorReported()) {
std::string msg = "Uncaught exception";
if (top.thrownValue.isString()) msg = top.thrownValue.asString();
if (top.thrownValue.isDict()) {
auto& d = top.thrownValue.asDict();
auto it = d.find("message");
if (it != d.end() && it->second.isString()) msg = it->second.asString();
}
int line = top.throwLine;
int col = top.throwColumn;
if (line == 0 && col == 0) { line = interpreter->getLastErrorLine(); col = interpreter->getLastErrorColumn(); }
interpreter->reportError(line, col, "Runtime Error", msg, "");
}
// Clear inline marker after handling
interpreter->clearInlineErrorReported();
execute(statement, nullptr);
}
}
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
try {
statement->accept(this, context);
} catch (const std::runtime_error& e) {
if (context) {
std::unordered_map<std::string, Value> err;
err["type"] = Value(std::string("RuntimeError"));
err["message"] = Value(std::string(e.what()));
context->hasThrow = true;
context->thrownValue = Value(err);
if (context->throwLine == 0 && context->throwColumn == 0) {
context->throwLine = interpreter->getLastErrorLine();
context->throwColumn = interpreter->getLastErrorColumn();
}
}
}
}
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
@ -61,14 +24,7 @@ void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements
for (const auto& statement : statements) {
execute(statement, context);
// Bridge any pending throws from expression evaluation into the context
Value pending; int pl=0, pc=0;
if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
if (context) { context->hasThrow = true; context->thrownValue = pending; context->throwLine = pl; context->throwColumn = pc; }
}
// If an inline reporter already handled this error and we are at top level (no try),
// avoid reporting it again here.
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue || context->hasThrow)) {
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
interpreter->setEnvironment(previous);
return;
}
@ -83,11 +39,6 @@ void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, Execu
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
Value value = statement->expression->accept(evaluator);
Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) {
if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; }
return;
}
if (interpreter->isInteractiveMode())
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
@ -97,8 +48,6 @@ void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, Execution
Value value = NONE_VALUE;
if (statement->initializer != nullptr) {
value = statement->initializer->accept(evaluator);
Value thrownInit; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrownInit, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrownInit; context->throwLine = tl; context->throwColumn = tc; } return; }
}
interpreter->getEnvironment()->define(statement->name.lexeme, value);
}
@ -130,10 +79,7 @@ void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, Exe
}
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
Value condValIf = statement->condition->accept(evaluator);
Value thrownIf; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrownIf, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrownIf; context->throwLine = tl; context->throwColumn = tc; } return; }
if (interpreter->isTruthy(condValIf)) {
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch, context);
@ -148,7 +94,7 @@ void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, Execu
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; context->throwLine = loopContext.throwLine; context->throwColumn = loopContext.throwColumn; } break; }
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
@ -174,17 +120,26 @@ void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, E
loopContext.isFunctionBody = context->isFunctionBody;
}
while (true) {
do {
execute(statement->body, &loopContext);
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; context->throwLine = loopContext.throwLine; context->throwColumn = loopContext.throwColumn; } break; }
if (loopContext.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = loopContext.returnValue; } break; }
if (loopContext.shouldBreak) { break; }
if (loopContext.shouldContinue) { loopContext.shouldContinue = false; }
Value c = statement->condition->accept(evaluator);
Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
if (!interpreter->isTruthy(c)) break;
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
}
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
@ -197,15 +152,9 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
loopContext.isFunctionBody = context->isFunctionBody;
}
while (true) {
if (statement->condition != nullptr) {
Value c = statement->condition->accept(evaluator);
Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
if (!interpreter->isTruthy(c)) break;
}
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; } break; }
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
@ -222,16 +171,12 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
loopContext.shouldContinue = false;
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
}
continue;
}
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
Value thrown; int tl=0, tc=0;
if (interpreter->consumePendingThrow(thrown, &tl, &tc)) { if (context) { context->hasThrow = true; context->thrownValue = thrown; context->throwLine = tl; context->throwColumn = tc; } break; }
}
}
}
@ -248,250 +193,67 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
}
}
void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context) {
interpreter->enterTry();
// Temporarily detach the reporter so any direct uses (e.g., in Environment) won't print inline
auto savedReporter = interpreter->getErrorReporter();
interpreter->setErrorReporter(nullptr);
ExecutionContext inner;
if (context) inner.isFunctionBody = context->isFunctionBody;
execute(statement->tryBlock, &inner);
// Also capture any pending throw signaled by expressions
Value pending; int pl=0, pc=0;
if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
inner.hasThrow = true;
inner.thrownValue = pending;
inner.throwLine = pl;
inner.throwColumn = pc;
}
// If thrown, handle catch
if (inner.hasThrow && statement->catchBlock) {
auto saved = interpreter->getEnvironment();
auto env = std::make_shared<Environment>(saved);
env->setErrorReporter(nullptr);
// Bind catch var if provided
if (!statement->catchVar.lexeme.empty()) {
env->define(statement->catchVar.lexeme, inner.thrownValue);
}
interpreter->setEnvironment(env);
ExecutionContext catchCtx;
catchCtx.isFunctionBody = inner.isFunctionBody;
execute(statement->catchBlock, &catchCtx);
inner.hasThrow = catchCtx.hasThrow;
inner.thrownValue = catchCtx.thrownValue;
inner.throwLine = catchCtx.throwLine;
inner.throwColumn = catchCtx.throwColumn;
interpreter->setEnvironment(saved);
}
// finally always
if (statement->finallyBlock) {
ExecutionContext fctx;
fctx.isFunctionBody = inner.isFunctionBody;
execute(statement->finallyBlock, &fctx);
if (fctx.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = fctx.returnValue; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (fctx.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = fctx.thrownValue; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (fctx.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (fctx.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
}
// propagate remaining control flow
if (inner.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = inner.returnValue; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (inner.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = inner.thrownValue; context->throwLine = inner.throwLine; context->throwColumn = inner.throwColumn; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (inner.shouldBreak) { if (context) { context->shouldBreak = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
if (inner.shouldContinue) { if (context) { context->shouldContinue = true; } interpreter->setErrorReporter(savedReporter); interpreter->exitTry(); return; }
interpreter->setErrorReporter(savedReporter);
interpreter->exitTry();
}
void Executor::visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context) {
Value v = statement->value ? statement->value->accept(evaluator) : NONE_VALUE;
if (context) {
context->hasThrow = true;
context->thrownValue = v;
context->throwLine = statement->keyword.line;
context->throwColumn = statement->keyword.column;
}
}
void Executor::visitImportStmt(const std::shared_ptr<ImportStmt>& statement, ExecutionContext* context) {
// Determine spec (string literal or identifier)
std::string spec = statement->moduleName.lexeme; // already STRING with .bob from parser if name-based
Value mod = interpreter->importModule(spec, statement->importToken.line, statement->importToken.column);
std::string bindName;
if (statement->hasAlias) {
bindName = statement->alias.lexeme;
} else {
// Derive default binding name from module path: basename without extension
std::string path = statement->moduleName.lexeme;
// Strip directories
size_t pos = path.find_last_of("/\\");
std::string base = (pos == std::string::npos) ? path : path.substr(pos + 1);
// Strip .bob
if (base.size() > 4 && base.substr(base.size() - 4) == ".bob") {
base = base.substr(0, base.size() - 4);
}
bindName = base;
}
interpreter->getEnvironment()->define(bindName, mod);
}
void Executor::visitFromImportStmt(const std::shared_ptr<FromImportStmt>& statement, ExecutionContext* context) {
std::string spec = statement->moduleName.lexeme; // already STRING with .bob from parser if name-based
// Star-import case
if (statement->importAll) {
// Import the module and bind all public exports into current environment
Value mod = interpreter->importModule(spec, statement->fromToken.line, statement->fromToken.column);
const std::unordered_map<std::string, Value>* src = nullptr;
if (mod.isModule()) src = mod.asModule()->exports.get(); else if (mod.isDict()) src = &mod.asDict();
if (!src) { throw std::runtime_error("from-import * on non-module"); }
for (const auto& kv : *src) {
const std::string& name = kv.first;
if (!name.empty() && name[0] == '_') continue; // skip private
interpreter->getEnvironment()->define(name, kv.second);
}
return;
}
// Build item list name->alias
std::vector<std::pair<std::string,std::string>> items;
for (const auto& it : statement->items) {
items.emplace_back(it.name.lexeme, it.hasAlias ? it.alias.lexeme : it.name.lexeme);
}
if (!interpreter->fromImport(spec, items, statement->fromToken.line, statement->fromToken.column)) {
throw std::runtime_error("from-import failed");
}
}
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
try {
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
try {
// Assign first to release references held by the old value
interpreter->getEnvironment()->assign(statement->name, value);
// Clean up on any reassignment
interpreter->forceCleanup();
return;
}
// Compound assignment operators
// Clean up on any reassignment, regardless of old/new type
interpreter->forceCleanup();
} catch (const std::exception& e) {
std::cerr << "Error during assignment: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
}
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(statement->name);
try {
Value newValue = computeCompoundAssignment(currentValue, statement->op.type, value);
interpreter->getEnvironment()->assign(statement->name, newValue);
} catch (const std::runtime_error&) {
Value newValue;
switch (statement->op.type) {
case PLUS_EQUAL:
newValue = currentValue + value;
break;
case MINUS_EQUAL:
newValue = currentValue - value;
break;
case STAR_EQUAL:
newValue = currentValue * value;
break;
case SLASH_EQUAL:
newValue = currentValue / value;
break;
case PERCENT_EQUAL:
newValue = currentValue % value;
break;
case BIN_AND_EQUAL:
newValue = currentValue & value;
break;
case BIN_OR_EQUAL:
newValue = currentValue | value;
break;
case BIN_XOR_EQUAL:
newValue = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
newValue = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
newValue = currentValue >> value;
break;
default:
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
"Unknown assignment operator: " + statement->op.lexeme, "");
throw;
}
}
void Executor::visitClassStmt(const std::shared_ptr<ClassStmt>& statement, ExecutionContext* context) {
std::unordered_map<std::string, Value> classDict;
// If parent exists, copy parent's methods as defaults (single inheritance prototype copy)
if (statement->hasParent) {
interpreter->registerClass(statement->name.lexeme, statement->parentName.lexeme);
} else {
interpreter->registerClass(statement->name.lexeme, "");
}
// Predefine fields as none, capture this class's field initializers, and register them for inheritance evaluation
std::vector<std::pair<std::string, std::shared_ptr<Expr>>> fieldInitializers;
for (const auto& f : statement->fields) {
classDict[f.name.lexeme] = NONE_VALUE;
fieldInitializers.emplace_back(f.name.lexeme, f.initializer);
}
// Attach methods as functions closed over a prototype env
auto protoEnv = std::make_shared<Environment>(interpreter->getEnvironment());
protoEnv->pruneForClosureCapture();
for (const auto& method : statement->methods) {
std::vector<std::string> paramNames;
for (const Token& p : method->params) paramNames.push_back(p.lexeme);
auto fn = std::make_shared<Function>(method->name.lexeme, paramNames, method->body, protoEnv, statement->name.lexeme);
classDict[method->name.lexeme] = Value(fn);
}
// Save template to interpreter so instances can include inherited fields/methods
interpreter->setClassTemplate(statement->name.lexeme, classDict);
// Register field initializers for this class
interpreter->setClassFieldInitializers(statement->name.lexeme, fieldInitializers);
// Define a constructor function Name(...) that builds an instance dict
auto ctorName = statement->name.lexeme;
auto ctor = std::make_shared<BuiltinFunction>(ctorName, [runtime=interpreter, className=statement->name.lexeme, fieldInitializers](std::vector<Value> args, int line, int col) -> Value {
Value instance(std::unordered_map<std::string, Value>{});
auto& dictRef = instance.asDict();
// Merge class template including inherited members
std::unordered_map<std::string, Value> tmpl = runtime->buildMergedTemplate(className);
for (const auto& kv : tmpl) dictRef[kv.first] = kv.second;
// Tag instance with class name for extension lookup
dictRef["__class"] = Value(className);
// Evaluate field initializers across inheritance chain (parent-to-child order)
{
// Build chain from base to current
std::vector<std::string> chain;
std::string cur = className;
while (!cur.empty()) { chain.push_back(cur); cur = runtime->getParentClass(cur); }
std::reverse(chain.begin(), chain.end());
auto saved = runtime->getEnvironment();
auto env = std::make_shared<Environment>(saved);
env->setErrorReporter(nullptr);
env->define("this", instance);
runtime->setEnvironment(env);
for (const auto& cls : chain) {
std::vector<std::pair<std::string, std::shared_ptr<Expr>>> inits;
if (runtime->getClassFieldInitializers(cls, inits)) {
for (const auto& kv : inits) {
const std::string& fieldName = kv.first;
const auto& expr = kv.second;
if (expr) {
Value v = runtime->evaluate(expr);
dictRef[fieldName] = v;
// Expose field names as locals for subsequent initializers
env->define(fieldName, v);
}
}
}
}
runtime->setEnvironment(saved);
}
// Auto-call init if present: create call env with this and params
auto it = dictRef.find("init");
if (it != dictRef.end() && it->second.isFunction()) {
Function* fn = it->second.asFunction();
// New environment from closure
std::shared_ptr<Environment> newEnv = std::make_shared<Environment>(fn->closure);
newEnv->setErrorReporter(nullptr);
newEnv->define("this", instance);
// Seed current class for proper super resolution within init
if (fn && !fn->ownerClass.empty()) {
newEnv->define("__currentClass", Value(fn->ownerClass));
}
// Bind params
size_t n = std::min(fn->params.size(), args.size());
for (size_t i = 0; i < n; ++i) {
newEnv->define(fn->params[i], args[i]);
}
// Execute body
auto envSaved = runtime->getEnvironment();
runtime->setEnvironment(newEnv);
ExecutionContext ctx; ctx.isFunctionBody = true;
for (const auto& stmt : fn->body) {
runtime->execute(stmt, &ctx);
if (ctx.hasReturn) break;
}
runtime->setEnvironment(envSaved);
}
return instance;
});
interpreter->getEnvironment()->define(statement->name.lexeme, Value(ctor));
}
void Executor::visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context) {
auto target = statement->target.lexeme;
for (const auto& method : statement->methods) {
std::vector<std::string> params;
for (const Token& p : method->params) params.push_back(p.lexeme);
auto fn = std::make_shared<Function>(method->name.lexeme, params, method->body, interpreter->getEnvironment(), target);
interpreter->registerExtension(target, method->name.lexeme, fn);
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
}
interpreter->getEnvironment()->assign(statement->name, newValue);
}
} catch (const std::exception& e) {
std::cerr << "Error in visitAssignStmt: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
}
}

View File

@ -1,19 +1,7 @@
#include "Interpreter.h"
#include "register.h"
#include "Evaluator.h"
#include "Executor.h"
#include "BobStdLib.h"
#include "ErrorReporter.h"
#include "Environment.h"
#include "Expression.h"
#include "Parser.h"
#include <filesystem>
#if defined(_WIN32)
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <fstream>
#include <iostream>
Interpreter::Interpreter(bool isInteractive)
@ -21,11 +9,6 @@ Interpreter::Interpreter(bool isInteractive)
evaluator = std::make_unique<Evaluator>(this);
executor = std::make_unique<Executor>(this, evaluator.get());
environment = std::make_shared<Environment>();
// Default module search paths: current dir and tests
moduleSearchPaths = { ".", "tests" };
// Register all builtin modules via aggregator
registerAllBuiltinModules(*this);
}
Interpreter::~Interpreter() = default;
@ -49,25 +32,6 @@ Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
}
return runTrampoline(result);
}
bool Interpreter::defineGlobalVar(const std::string& name, const Value& value) {
if (!environment) return false;
try {
environment->define(name, value);
return true;
} catch (...) { return false; }
}
bool Interpreter::tryGetGlobalVar(const std::string& name, Value& out) const {
if (!environment) return false;
try {
out = environment->get(Token{IDENTIFIER, name, 0, 0});
return true;
} catch (...) { return false; }
}
bool Interpreter::hasReportedError() const {
return inlineErrorReported;
}
Value Interpreter::runTrampoline(Value initialResult) {
Value current = initialResult;
@ -92,169 +56,14 @@ std::string Interpreter::stringify(Value object) {
void Interpreter::addStdLibFunctions() {
BobStdLib::addToEnvironment(environment, *this, errorReporter);
}
void Interpreter::setModulePolicy(bool allowFiles, bool preferFiles, const std::vector<std::string>& searchPaths) {
allowFileImports = allowFiles;
preferFileOverBuiltin = preferFiles;
moduleSearchPaths = searchPaths;
}
static std::string joinPath(const std::string& baseDir, const std::string& rel) {
namespace fs = std::filesystem;
fs::path p = fs::path(baseDir) / fs::path(rel);
return fs::path(p).lexically_normal().string();
}
static std::string locateModuleFile(const std::string& baseDir, const std::vector<std::string>& searchPaths, const std::string& nameDotBob) {
namespace fs = std::filesystem;
// Only search relative to the importing file's directory
// 1) baseDir/name.bob
if (!baseDir.empty()) {
std::string p = joinPath(baseDir, nameDotBob);
if (fs::exists(fs::path(p))) return p;
}
// 2) baseDir/searchPath/name.bob (search paths are relative to baseDir)
for (const auto& sp : searchPaths) {
if (!baseDir.empty()) {
std::string pb = joinPath(baseDir, joinPath(sp, nameDotBob));
if (fs::exists(fs::path(pb))) return pb;
}
}
return "";
}
Value Interpreter::importModule(const std::string& spec, int line, int column) {
// Determine if spec is a path string (heuristic: contains '/' or ends with .bob)
bool looksPath = spec.find('/') != std::string::npos || (spec.size() >= 4 && spec.rfind(".bob") == spec.size() - 4) || spec.find("..") != std::string::npos;
// Cache key resolution
std::string key = spec;
std::string baseDir = "";
if (errorReporter && !errorReporter->getCurrentFileName().empty()) {
std::filesystem::path p(errorReporter->getCurrentFileName());
baseDir = p.has_parent_path() ? p.parent_path().string() : baseDir;
}
if (looksPath) {
if (!allowFileImports) {
reportError(line, column, "Import Error", "File imports are disabled by policy", spec);
throw std::runtime_error("File imports disabled");
}
// Resolve STRING path specs:
// - Absolute: use as-is
// - Starts with ./ or ../: resolve relative to the importing file directory (baseDir)
// - Otherwise: resolve relative to current working directory
if (!spec.empty() && spec[0] == '/') {
key = spec;
} else if (spec.rfind("./", 0) == 0 || spec.rfind("../", 0) == 0) {
key = joinPath(baseDir, spec);
} else {
// Resolve all non-absolute paths relative to the importing file directory only
key = joinPath(baseDir, spec);
}
} else {
// Name import: try file in baseDir or search paths; else builtin
if (preferFileOverBuiltin && allowFileImports) {
std::string found = locateModuleFile(baseDir, moduleSearchPaths, spec + ".bob");
if (!found.empty()) { key = found; looksPath = true; }
}
if (!looksPath && allowBuiltinImports && builtinModules.has(spec)) {
key = std::string("builtin:") + spec;
}
}
// Return from cache
auto it = moduleCache.find(key);
if (it != moduleCache.end()) return it->second;
// If still not a path, it must be builtin or missing
if (!looksPath) {
if (!builtinModules.has(spec)) {
reportError(line, column, "Import Error", "Module not found: " + spec + ".bob", spec);
throw std::runtime_error("Module not found");
}
// Builtin: return from cache or construct and cache
auto itc = moduleCache.find(key);
if (itc != moduleCache.end()) return itc->second;
Value v = builtinModules.create(spec, *this);
if (v.isNone()) { // cloaked by policy
reportError(line, column, "Import Error", "Module not found: " + spec + ".bob", spec);
throw std::runtime_error("Module not found");
}
moduleCache[key] = v;
return v;
}
// File module: read and execute in isolated env
std::ifstream file(key);
if (!file.is_open()) {
reportError(line, column, "Import Error", "Could not open module file: " + key, spec);
throw std::runtime_error("Module file open failed");
}
std::string code((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
// Prepare reporter with module source
if (errorReporter) errorReporter->pushSource(code, key);
// New lexer and parser
Lexer lx; if (errorReporter) lx.setErrorReporter(errorReporter);
std::vector<Token> toks = lx.Tokenize(code);
Parser p(toks); if (errorReporter) p.setErrorReporter(errorReporter);
std::vector<std::shared_ptr<Stmt>> stmts = p.parse();
// Isolated environment
auto saved = getEnvironment();
auto modEnv = std::make_shared<Environment>(saved);
modEnv->setErrorReporter(errorReporter);
setEnvironment(modEnv);
// Execute
executor->interpret(stmts);
// Build module object from env
std::unordered_map<std::string, Value> exported = modEnv->getAll();
// Derive module name from key basename
std::string modName = key;
size_t pos = modName.find_last_of("/\\"); if (pos != std::string::npos) modName = modName.substr(pos+1);
if (modName.size() > 4 && modName.substr(modName.size()-4) == ".bob") modName = modName.substr(0, modName.size()-4);
auto m = std::make_shared<Module>(modName, exported);
Value moduleVal(m);
// Cache
moduleCache[key] = moduleVal;
// Restore env and reporter
setEnvironment(saved);
if (errorReporter) errorReporter->popSource();
return moduleVal;
}
bool Interpreter::fromImport(const std::string& spec, const std::vector<std::pair<std::string, std::string>>& items, int line, int column) {
Value mod = importModule(spec, line, column);
if (!(mod.isModule() || mod.isDict())) {
reportError(line, column, "Import Error", "Module did not evaluate to a module", spec);
return false;
}
std::unordered_map<std::string, Value> const* src = nullptr;
std::unordered_map<std::string, Value> temp;
if (mod.isModule()) {
// Module exports
src = mod.asModule()->exports.get();
} else {
src = &mod.asDict();
}
for (const auto& [name, alias] : items) {
auto it = src->find(name);
if (it == src->end()) {
reportError(line, column, "Import Error", "Name not found in module: " + name, spec);
return false;
}
environment->define(alias, it->second);
}
return true;
}
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
builtinFunctions.push_back(func);
}
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
thunks.push_back(thunk);
}
void Interpreter::addFunction(std::shared_ptr<Function> function) {
functions.push_back(function);
@ -265,6 +74,7 @@ void Interpreter::setErrorReporter(ErrorReporter* reporter) {
if (environment) {
environment->setErrorReporter(reporter);
}
addStdLibFunctions();
}
bool Interpreter::isInteractiveMode() const {
@ -280,12 +90,6 @@ void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
}
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
// Always track last error site
setLastErrorSite(line, column);
// Suppress inline printing while inside try; error will propagate to catch/finally
if (isInTry()) {
return;
}
if (errorReporter) {
errorReporter->reportError(line, column, errorType, message, lexeme);
}
@ -303,277 +107,22 @@ void Interpreter::forceCleanup() {
diagnostics.forceCleanup(builtinFunctions, functions, thunks);
}
void Interpreter::registerExtension(const std::string& targetName, const std::string& methodName, std::shared_ptr<Function> fn) {
// Builtin targets routed to builtinExtensions
if (targetName == "string" || targetName == "array" || targetName == "dict" || targetName == "any" || targetName == "number") {
builtinExtensions[targetName][methodName] = fn;
return;
}
// Otherwise treat as user class name
classExtensions[targetName][methodName] = fn;
}
std::shared_ptr<Function> Interpreter::lookupExtension(const std::string& targetName, const std::string& methodName) {
// If this is a user class name, prefer class extensions
auto cit = classExtensions.find(targetName);
if (cit != classExtensions.end()) {
auto mit = cit->second.find(methodName);
if (mit != cit->second.end()) return mit->second;
// If not on class, fall through to any
auto anyIt2 = builtinExtensions.find("any");
if (anyIt2 != builtinExtensions.end()) {
auto am = anyIt2->second.find(methodName);
if (am != anyIt2->second.end()) return am->second;
}
return nullptr;
}
// Builtin targets
auto bit = builtinExtensions.find(targetName);
if (bit != builtinExtensions.end()) {
auto mit = bit->second.find(methodName);
if (mit != bit->second.end()) return mit->second;
}
// any fallback for builtins and unknowns
auto anyIt = builtinExtensions.find("any");
if (anyIt != builtinExtensions.end()) {
auto mit = anyIt->second.find(methodName);
if (mit != anyIt->second.end()) return mit->second;
}
return nullptr;
}
void Interpreter::registerClass(const std::string& className, const std::string& parentName) {
if (!parentName.empty()) {
classParents[className] = parentName;
}
}
std::string Interpreter::getParentClass(const std::string& className) const {
auto it = classParents.find(className);
if (it != classParents.end()) return it->second;
return "";
}
void Interpreter::setClassTemplate(const std::string& className, const std::unordered_map<std::string, Value>& tmpl) {
classTemplates[className] = tmpl;
}
bool Interpreter::getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const {
auto it = classTemplates.find(className);
if (it == classTemplates.end()) return false;
out = it->second;
return true;
}
std::unordered_map<std::string, Value> Interpreter::buildMergedTemplate(const std::string& className) const {
std::unordered_map<std::string, Value> merged;
// Merge parent chain first
std::string cur = className;
std::vector<std::string> chain;
while (!cur.empty()) { chain.push_back(cur); cur = getParentClass(cur); }
for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
auto ct = classTemplates.find(*it);
if (ct != classTemplates.end()) {
for (const auto& kv : ct->second) merged[kv.first] = kv.second;
}
}
return merged;
}
Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression) {
bool isMethodCall = false;
bool isSuperCall = false;
std::string methodName;
Value receiver = NONE_VALUE;
if (auto prop = std::dynamic_pointer_cast<PropertyExpr>(expression->callee)) {
if (auto varObj = std::dynamic_pointer_cast<VarExpr>(prop->object)) {
if (varObj->name.lexeme == "super") {
isSuperCall = true;
if (environment) {
try {
receiver = environment->get(Token{IDENTIFIER, "this", prop->name.line, prop->name.column});
} catch (...) {
receiver = NONE_VALUE;
}
}
}
}
if (!isSuperCall) {
receiver = evaluate(prop->object);
}
methodName = prop->name.lexeme;
isMethodCall = true;
}
Value callee = NONE_VALUE;
if (!isSuperCall) {
callee = evaluate(expression->callee);
}
// If property wasn't found as a callable, try extension lookup
if (isMethodCall && !(callee.isFunction() || callee.isBuiltinFunction())) {
if (isSuperCall && !receiver.isDict()) {
std::string errorMsg = "super can only be used inside class methods";
if (errorReporter) {
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
errorMsg, "");
}
throw std::runtime_error(errorMsg);
}
if (!methodName.empty()) {
// Built-ins direct
if (isSuperCall && receiver.isDict()) {
// Resolve using current executing class context if available
std::string curClass;
if (environment) {
try {
Value cc = environment->get(Token{IDENTIFIER, "__currentClass", expression->paren.line, expression->paren.column});
if (cc.isString()) curClass = cc.asString();
} catch (...) {}
}
// If not set yet (e.g., resolving before entering callee), inspect callee ownerClass if available
if (curClass.empty()) {
// Try child class extension to determine current context
const auto& d = receiver.asDict();
auto itc = d.find("__class");
if (itc != d.end() && itc->second.isString()) {
std::string child = itc->second.asString();
if (auto childExt = lookupExtension(child, methodName)) {
curClass = child; // use child as current class for super
}
}
}
if (curClass.empty() && callee.isFunction()) {
Function* cf = callee.asFunction();
if (cf && !cf->ownerClass.empty()) curClass = cf->ownerClass;
}
if (curClass.empty()) {
const auto& d = receiver.asDict();
auto itc = d.find("__class");
if (itc != d.end() && itc->second.isString()) curClass = itc->second.asString();
}
std::string cur = getParentClass(curClass);
int guard = 0;
while (!cur.empty() && guard++ < 64) {
auto tmplIt = classTemplates.find(cur);
if (tmplIt != classTemplates.end()) {
auto vIt = tmplIt->second.find(methodName);
if (vIt != tmplIt->second.end() && vIt->second.isFunction()) {
callee = vIt->second;
break;
}
}
if (auto fn = lookupExtension(cur, methodName)) { callee = Value(fn); break; }
cur = getParentClass(cur);
}
// If still not found, try built-in fallbacks to keep behavior consistent
if (!callee.isFunction()) {
if (auto dictFn = lookupExtension("dict", methodName)) callee = Value(dictFn);
else if (auto anyFn = lookupExtension("any", methodName)) callee = Value(anyFn);
}
} else if (receiver.isArray()) {
if (auto fn = lookupExtension("array", methodName)) callee = Value(fn);
else if (methodName == "len") {
auto bf = std::make_shared<BuiltinFunction>("array.len", [receiver](std::vector<Value> args, int, int){
return Value(static_cast<double>(receiver.asArray().size()));
});
callee = Value(bf);
} else if (methodName == "push") {
auto bf = std::make_shared<BuiltinFunction>("array.push", [receiver](std::vector<Value> args, int, int){
std::vector<Value>& arr = const_cast<std::vector<Value>&>(receiver.asArray());
for (size_t i = 0; i < args.size(); ++i) arr.push_back(args[i]);
return receiver;
});
callee = Value(bf);
} else if (methodName == "pop") {
auto bf = std::make_shared<BuiltinFunction>("array.pop", [receiver](std::vector<Value> args, int, int){
std::vector<Value>& arr = const_cast<std::vector<Value>&>(receiver.asArray());
if (arr.empty()) return NONE_VALUE;
Value v = arr.back();
arr.pop_back();
return v;
});
callee = Value(bf);
} else if (auto anyFn = lookupExtension("any", methodName)) callee = Value(anyFn);
} else if (receiver.isString()) {
if (auto fn = lookupExtension("string", methodName)) callee = Value(fn);
else if (methodName == "len") {
auto bf = std::make_shared<BuiltinFunction>("string.len", [receiver](std::vector<Value> args, int, int){
return Value(static_cast<double>(receiver.asString().length()));
});
callee = Value(bf);
} else if (auto anyFn = lookupExtension("any", methodName)) callee = Value(anyFn);
} else if (receiver.isNumber()) {
if (auto fn = lookupExtension("number", methodName)) callee = Value(fn);
else if (methodName == "toInt") {
auto bf = std::make_shared<BuiltinFunction>("number.toInt", [receiver](std::vector<Value> args, int, int){
return Value(static_cast<double>(static_cast<long long>(receiver.asNumber())));
});
callee = Value(bf);
} else if (auto anyFn = lookupExtension("any", methodName)) callee = Value(anyFn);
} else if (receiver.isDict()) {
const auto& d = receiver.asDict();
std::string cls;
auto it = d.find("__class");
if (it != d.end() && it->second.isString()) cls = it->second.asString();
// Walk class chain first
std::string cur = cls;
while (!cur.empty()) {
if (auto fn = lookupExtension(cur, methodName)) { callee = Value(fn); break; }
cur = getParentClass(cur);
}
// Fallbacks
if (!callee.isFunction()) {
if (auto dictFn = lookupExtension("dict", methodName)) callee = Value(dictFn);
else if (methodName == "len") {
auto bf = std::make_shared<BuiltinFunction>("dict.len", [receiver](std::vector<Value> args, int, int){
return Value(static_cast<double>(receiver.asDict().size()));
});
callee = Value(bf);
} else if (methodName == "keys") {
auto bf = std::make_shared<BuiltinFunction>("dict.keys", [receiver](std::vector<Value> args, int, int){
std::vector<Value> keys; const auto& m = receiver.asDict();
for (const auto& kv : m) keys.push_back(Value(kv.first));
return Value(keys);
});
callee = Value(bf);
} else if (methodName == "values") {
auto bf = std::make_shared<BuiltinFunction>("dict.values", [receiver](std::vector<Value> args, int, int){
std::vector<Value> vals; const auto& m = receiver.asDict();
for (const auto& kv : m) vals.push_back(kv.second);
return Value(vals);
});
callee = Value(bf);
} else if (methodName == "has") {
auto bf = std::make_shared<BuiltinFunction>("dict.has", [receiver](std::vector<Value> args, int, int){
if (args.size() != 1 || !args[0].isString()) return Value(false);
const auto& m = receiver.asDict();
return Value(m.find(args[0].asString()) != m.end());
});
callee = Value(bf);
}
else if (auto anyFn = lookupExtension("any", methodName)) callee = Value(anyFn);
}
} else {
if (auto anyFn = lookupExtension("any", methodName)) callee = Value(anyFn);
}
}
}
Value callee = evaluate(expression->callee); // Direct call instead of through evaluator
if (callee.isBuiltinFunction()) {
// Handle builtin functions with direct evaluation
std::vector<Value> arguments;
for (const auto& argument : expression->arguments) {
arguments.push_back(evaluate(argument));
arguments.push_back(evaluate(argument)); // Direct call
}
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
}
if (!callee.isFunction()) {
std::string errorMsg = isSuperCall ? ("Undefined super method '" + methodName + "'")
: ("Can only call functions, got " + callee.getType());
// Provide better error message with type information (like original)
std::string errorMsg = "Can only call functions, got " + callee.getType();
if (errorReporter) {
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
errorMsg, "");
@ -585,7 +134,7 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
std::vector<Value> arguments;
for (const auto& argument : expression->arguments) {
arguments.push_back(evaluate(argument));
arguments.push_back(evaluate(argument)); // Direct call instead of through evaluator
}
// Check arity (like original)
@ -601,18 +150,12 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
// Check if this is a tail call for inline TCO
if (expression->isTailCall) {
auto thunk = std::make_shared<Thunk>([this, function, arguments, isMethodCall, receiver, isSuperCall]() -> Value {
// Create a thunk for tail call optimization - original inline version
auto thunk = std::make_shared<Thunk>([this, function, arguments]() -> Value {
// Use RAII to manage environment (exactly like original)
ScopedEnv _env(environment);
environment = std::make_shared<Environment>(function->closure);
environment->setErrorReporter(errorReporter);
if (isMethodCall) {
environment->define("this", receiver);
if (isSuperCall) environment->define("super", receiver);
if (function && !function->ownerClass.empty()) {
environment->define("__currentClass", Value(function->ownerClass));
}
}
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
@ -621,11 +164,12 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
ExecutionContext context;
context.isFunctionBody = true;
// Use RAII to manage thunk execution flag
ScopedThunkFlag _inThunk(inThunkExecution);
// Execute function body (inline like original - direct accept for performance)
for (const auto& stmt : function->body) {
stmt->accept(executor.get(), &context);
if (context.hasThrow) { setPendingThrow(context.thrownValue, context.throwLine, context.throwColumn); return NONE_VALUE; }
stmt->accept(executor.get(), &context); // Direct call like original
if (context.hasReturn) {
return context.returnValue;
}
@ -634,8 +178,10 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
return context.returnValue;
});
// Store the thunk to keep it alive and return as Value (exactly like original)
thunks.push_back(thunk);
// Automatic cleanup check
thunkCreationCount++;
if (thunkCreationCount >= CLEANUP_THRESHOLD) {
cleanupUnusedThunks();
@ -644,16 +190,10 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
return Value(thunk);
} else {
// Normal function call - create new environment (exactly like original)
ScopedEnv _env(environment);
environment = std::make_shared<Environment>(function->closure);
environment->setErrorReporter(errorReporter);
if (isMethodCall) {
environment->define("this", receiver);
if (isSuperCall) environment->define("super", receiver);
if (function && !function->ownerClass.empty()) {
environment->define("__currentClass", Value(function->ownerClass));
}
}
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
@ -662,13 +202,31 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
ExecutionContext context;
context.isFunctionBody = true;
// Execute function body (exactly like original - direct accept for performance)
for (const auto& stmt : function->body) {
stmt->accept(executor.get(), &context);
if (context.hasThrow) { setPendingThrow(context.thrownValue, context.throwLine, context.throwColumn); return NONE_VALUE; }
if (context.hasReturn) { return context.returnValue; }
stmt->accept(executor.get(), &context); // Direct call like original
if (context.hasReturn) {
return context.returnValue;
}
}
return context.returnValue;
}
}
// Function creation count management
void Interpreter::incrementFunctionCreationCount() {
functionCreationCount++;
}
int Interpreter::getFunctionCreationCount() const {
return functionCreationCount;
}
void Interpreter::resetFunctionCreationCount() {
functionCreationCount = 0;
}
int Interpreter::getCleanupThreshold() const {
return 1000000; // Same as CLEANUP_THRESHOLD used for thunks
}

View File

@ -13,19 +13,122 @@
#include <algorithm>
bool RuntimeDiagnostics::isTruthy(Value object) { return object.isTruthy(); }
bool RuntimeDiagnostics::isTruthy(Value object) {
if(object.isBoolean()) {
return object.asBoolean();
}
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
if (a.isNumber() && b.isBoolean()) {
return b.asBoolean() ? (a.asNumber() != 0.0) : (a.asNumber() == 0.0);
if(object.isNone()) {
return false;
}
if (a.isBoolean() && b.isNumber()) {
return a.asBoolean() ? (b.asNumber() != 0.0) : (b.asNumber() == 0.0);
if(object.isNumber()) {
return object.asNumber() != 0;
}
return a.equals(b);
if(object.isString()) {
return object.asString().length() > 0;
}
return true;
}
std::string RuntimeDiagnostics::stringify(Value object) { return object.toString(); }
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
// Handle none comparisons first
if (a.isNone() || b.isNone()) {
return a.isNone() && b.isNone();
}
// Handle same type comparisons
if (a.isNumber() && b.isNumber()) {
return a.asNumber() == b.asNumber();
}
if (a.isBoolean() && b.isBoolean()) {
return a.asBoolean() == b.asBoolean();
}
if (a.isString() && b.isString()) {
return a.asString() == b.asString();
}
if (a.isArray() && b.isArray()) {
const std::vector<Value>& arrA = a.asArray();
const std::vector<Value>& arrB = b.asArray();
if (arrA.size() != arrB.size()) {
return false;
}
for (size_t i = 0; i < arrA.size(); i++) {
if (!isEqual(arrA[i], arrB[i])) {
return false;
}
}
return true;
}
if (a.isFunction() && b.isFunction()) {
// Functions are equal only if they are the same object
return a.asFunction() == b.asFunction();
}
if (a.isBuiltinFunction() && b.isBuiltinFunction()) {
// Builtin functions are equal only if they are the same object
return a.asBuiltinFunction() == b.asBuiltinFunction();
}
// Cross-type comparisons that make sense
if (a.isNumber() && b.isBoolean()) {
// Numbers and booleans: 0 and false are equal, non-zero and true are equal
if (b.asBoolean()) {
return a.asNumber() != 0.0;
} else {
return a.asNumber() == 0.0;
}
}
if (a.isBoolean() && b.isNumber()) {
// Same as above, but reversed
if (a.asBoolean()) {
return b.asNumber() != 0.0;
} else {
return b.asNumber() == 0.0;
}
}
// For all other type combinations, return false
return false;
}
std::string RuntimeDiagnostics::stringify(Value object) {
if(object.isNone()) {
return "none";
}
else if(object.isNumber()) {
return formatNumber(object.asNumber());
}
else if(object.isString()) {
return object.asString();
}
else if(object.isBoolean()) {
return object.asBoolean() == 1 ? "true" : "false";
}
else if(object.isFunction()) {
return "<function " + object.asFunction()->name + ">";
}
else if(object.isBuiltinFunction()) {
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
}
else if(object.isArray()) {
return formatArray(object.asArray());
}
else if(object.isDict()) {
return formatDict(object.asDict());
}
throw std::runtime_error("Could not convert object to string");
}
std::string RuntimeDiagnostics::formatNumber(double value) {
double integral = value;
@ -47,9 +150,31 @@ std::string RuntimeDiagnostics::formatNumber(double value) {
}
}
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) { return Value(arr).toString(); }
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
std::string result = "[";
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) { return Value(dict).toString(); }
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0) result += ", ";
result += stringify(arr[i]);
}
result += "]";
return result;
}
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) {
std::string result = "{";
bool first = true;
for (const auto& pair : dict) {
if (!first) result += ", ";
result += "\"" + pair.first + "\": " + stringify(pair.second);
first = false;
}
result += "}";
return result;
}
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
@ -87,7 +212,25 @@ void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>
);
}
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks) {
// More aggressive cleanup when breaking array references
functions.erase(
std::remove_if(functions.begin(), functions.end(),
[](const std::shared_ptr<BuiltinFunction>& func) {
return func.use_count() <= 2; // More aggressive than == 1
}),
functions.end()
);
thunks.erase(
std::remove_if(thunks.begin(), thunks.end(),
[](const std::shared_ptr<Thunk>& thunk) {
return thunk.use_count() <= 2; // More aggressive than == 1
}),
thunks.end()
);
}
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
std::vector<std::shared_ptr<Function>>& functions,

View File

@ -1,4 +1,4 @@
#include "TypeWrapper.h"
#include "TypeWrapper.h"
#include <iostream>

View File

@ -4,12 +4,5 @@
const Value NONE_VALUE = Value();
const Value TRUE_VALUE = Value(true);
const Value FALSE_VALUE = Value(false);
// Helper to format module string safely with complete type available in this TU
std::string formatModuleForToString(const std::shared_ptr<Module>& mod) {
if (mod && !mod->name.empty()) {
return std::string("<module '") + mod->name + "'>";
}
return std::string("<module>");
}
const Value ZERO_VALUE = Value(0.0);
const Value ONE_VALUE = Value(1.0);

View File

@ -76,7 +76,104 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(printRawFunc);
// Create a built-in len function for arrays and strings
auto lenFunc = std::make_shared<BuiltinFunction>("len",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (args[0].isArray()) {
return Value(static_cast<double>(args[0].asArray().size()));
} else if (args[0].isString()) {
return Value(static_cast<double>(args[0].asString().length()));
} else if (args[0].isDict()) {
return Value(static_cast<double>(args[0].asDict().size()));
} else {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"len() can only be used on arrays, strings, and dictionaries", "", true);
}
throw std::runtime_error("len() can only be used on arrays, strings, and dictionaries");
}
});
env->define("len", Value(lenFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(lenFunc);
// Create a built-in push function for arrays
auto pushFunc = std::make_shared<BuiltinFunction>("push",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() < 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected at least 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected at least 2 arguments but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isArray()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"First argument to push() must be an array", "", true);
}
throw std::runtime_error("First argument to push() must be an array");
}
// Get the array and modify it in place
std::vector<Value>& arr = args[0].asArray();
// Add all arguments except the first one (which is the array)
for (size_t i = 1; i < args.size(); i++) {
arr.push_back(args[i]);
}
return args[0]; // Return the modified array
});
env->define("push", Value(pushFunc));
interpreter.addBuiltinFunction(pushFunc);
// Create a built-in pop function for arrays
auto popFunc = std::make_shared<BuiltinFunction>("pop",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isArray()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"pop() can only be used on arrays", "", true);
}
throw std::runtime_error("pop() can only be used on arrays");
}
std::vector<Value>& arr = args[0].asArray();
if (arr.empty()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Cannot pop from empty array", "", true);
}
throw std::runtime_error("Cannot pop from empty array");
}
// Get the last element and remove it from the array
Value lastElement = arr.back();
arr.pop_back();
return lastElement; // Return the popped element
});
env->define("pop", Value(popFunc));
interpreter.addBuiltinFunction(popFunc);
// Create a built-in assert function
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
@ -119,7 +216,27 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(assertFunc);
// time-related globals moved into builtin time module
// Create a built-in time function (returns microseconds since Unix epoch)
auto timeFunc = std::make_shared<BuiltinFunction>("time",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
return Value(static_cast<double>(microseconds));
});
env->define("time", Value(timeFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(timeFunc);
// Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input",
@ -176,8 +293,6 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
typeName = "array";
} else if (args[0].isDict()) {
typeName = "dict";
} else if (args[0].isModule()) {
typeName = "module";
} else {
typeName = "unknown";
}
@ -290,60 +405,444 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toBooleanFunc);
// exit moved to sys module
// Create a built-in exit function to terminate the program
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
[](std::vector<Value> args, int line, int column) -> Value {
int exitCode = 0; // Default exit code
// sleep moved into builtin time module
if (args.size() > 0) {
if (args[0].isNumber()) {
exitCode = static_cast<int>(args[0].asNumber());
}
// If not a number, just use default exit code 0
}
// Introspection: dir(obj) and functions(obj)
auto dirFunc = std::make_shared<BuiltinFunction>("dir",
[](std::vector<Value> args, int, int) -> Value {
if (args.size() != 1) return Value(std::vector<Value>{});
Value obj = args[0];
std::vector<Value> out;
if (obj.isModule()) {
auto* mod = obj.asModule();
if (mod && mod->exports) {
for (const auto& kv : *mod->exports) out.push_back(Value(kv.first));
}
} else if (obj.isDict()) {
const auto& d = obj.asDict();
for (const auto& kv : d) out.push_back(Value(kv.first));
}
return Value(out);
std::exit(exitCode);
});
env->define("dir", Value(dirFunc));
interpreter.addBuiltinFunction(dirFunc);
env->define("exit", Value(exitFunc));
auto functionsFunc = std::make_shared<BuiltinFunction>("functions",
[](std::vector<Value> args, int, int) -> Value {
if (args.size() != 1) return Value(std::vector<Value>{});
Value obj = args[0];
std::vector<Value> out;
auto pushIfFn = [&out](const std::pair<const std::string, Value>& kv){
if (kv.second.isFunction() || kv.second.isBuiltinFunction()) out.push_back(Value(kv.first));
};
if (obj.isModule()) {
auto* mod = obj.asModule();
if (mod && mod->exports) {
for (const auto& kv : *mod->exports) pushIfFn(kv);
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(exitFunc);
// Create a built-in sleep function for animations and timing
auto sleepFunc = std::make_shared<BuiltinFunction>("sleep",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
} else if (obj.isDict()) {
const auto& d = obj.asDict();
for (const auto& kv : d) pushIfFn(kv);
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
return Value(out);
if (!args[0].isNumber()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"sleep() argument must be a number", "", true);
}
throw std::runtime_error("sleep() argument must be a number");
}
double seconds = args[0].asNumber();
if (seconds < 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"sleep() argument cannot be negative", "", true);
}
throw std::runtime_error("sleep() argument cannot be negative");
}
// Convert to milliseconds and sleep
int milliseconds = static_cast<int>(seconds * 1000);
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
return NONE_VALUE;
});
env->define("functions", Value(functionsFunc));
interpreter.addBuiltinFunction(functionsFunc);
env->define("sleep", Value(sleepFunc));
// random moved to rand module
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(sleepFunc);
// (eval and evalFile moved to eval module)
// Create a built-in random function
auto randomFunc = std::make_shared<BuiltinFunction>("random",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
// Seed the random number generator if not already done
static bool seeded = false;
if (!seeded) {
srand(static_cast<unsigned int>(time(nullptr)));
seeded = true;
}
return Value(static_cast<double>(rand()) / RAND_MAX);
});
env->define("random", Value(randomFunc));
// (file I/O moved to io module)
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(randomFunc);
// memoryUsage moved to sys module
// Create a built-in eval function (like Python's eval)
auto evalFunc = std::make_shared<BuiltinFunction>("eval",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "Invalid Arguments",
"eval expects exactly 1 argument (string)", "eval");
}
throw std::runtime_error("eval expects exactly 1 argument");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "Invalid Type",
"eval argument must be a string", "eval");
}
throw std::runtime_error("eval argument must be a string");
}
std::string code = args[0].asString();
try {
// Create a new lexer for the code string
Lexer lexer;
lexer.setErrorReporter(errorReporter);
std::vector<Token> tokens = lexer.Tokenize(code);
// Create a new parser
Parser parser(tokens);
parser.setErrorReporter(errorReporter);
std::vector<std::shared_ptr<Stmt>> statements = parser.parse();
// Execute the statements in the current environment
// Note: This runs in the current scope, so variables are shared
interpreter.interpret(statements);
// For now, return NONE_VALUE since we don't have a way to get the last expression value
// In a more sophisticated implementation, we'd track the last expression result
return NONE_VALUE;
} catch (const std::exception& e) {
if (errorReporter) {
errorReporter->reportError(line, column, "Eval Error",
"Failed to evaluate code: " + std::string(e.what()), code);
}
throw std::runtime_error("eval failed: " + std::string(e.what()));
}
});
env->define("eval", Value(evalFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(evalFunc);
// Create a built-in keys function for dictionaries
auto keysFunc = std::make_shared<BuiltinFunction>("keys",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isDict()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"keys() can only be used on dictionaries", "", true);
}
throw std::runtime_error("keys() can only be used on dictionaries");
}
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
std::vector<Value> keys;
for (const auto& pair : dict) {
keys.push_back(Value(pair.first));
}
return Value(keys);
});
env->define("keys", Value(keysFunc));
interpreter.addBuiltinFunction(keysFunc);
// Create a built-in values function for dictionaries
auto valuesFunc = std::make_shared<BuiltinFunction>("values",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isDict()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"values() can only be used on dictionaries", "", true);
}
throw std::runtime_error("values() can only be used on dictionaries");
}
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
std::vector<Value> values;
for (const auto& pair : dict) {
values.push_back(pair.second);
}
return Value(values);
});
env->define("values", Value(valuesFunc));
interpreter.addBuiltinFunction(valuesFunc);
// Create a built-in has function for dictionaries
auto hasFunc = std::make_shared<BuiltinFunction>("has",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isDict()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"First argument to has() must be a dictionary", "", true);
}
throw std::runtime_error("First argument to has() must be a dictionary");
}
if (!args[1].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Second argument to has() must be a string", "", true);
}
throw std::runtime_error("Second argument to has() must be a string");
}
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
std::string key = args[1].asString();
return Value(dict.find(key) != dict.end());
});
env->define("has", Value(hasFunc));
interpreter.addBuiltinFunction(hasFunc);
// Create a built-in readFile function
auto readFileFunc = std::make_shared<BuiltinFunction>("readFile",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"readFile() argument must be a string", "", true);
}
throw std::runtime_error("readFile() argument must be a string");
}
std::string filename = args[0].asString();
std::ifstream file(filename);
if (!file.is_open()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Could not open file: " + filename, "", true);
}
throw std::runtime_error("Could not open file: " + filename);
}
std::stringstream buffer;
buffer << file.rdbuf();
file.close();
return Value(buffer.str());
});
env->define("readFile", Value(readFileFunc));
interpreter.addBuiltinFunction(readFileFunc);
// Create a built-in writeFile function
auto writeFileFunc = std::make_shared<BuiltinFunction>("writeFile",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"First argument to writeFile() must be a string", "", true);
}
throw std::runtime_error("First argument to writeFile() must be a string");
}
if (!args[1].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Second argument to writeFile() must be a string", "", true);
}
throw std::runtime_error("Second argument to writeFile() must be a string");
}
std::string filename = args[0].asString();
std::string content = args[1].asString();
std::ofstream file(filename);
if (!file.is_open()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Could not create file: " + filename, "", true);
}
throw std::runtime_error("Could not create file: " + filename);
}
file << content;
file.close();
return NONE_VALUE;
});
env->define("writeFile", Value(writeFileFunc));
interpreter.addBuiltinFunction(writeFileFunc);
// Create a built-in readLines function
auto readLinesFunc = std::make_shared<BuiltinFunction>("readLines",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"readLines() argument must be a string", "", true);
}
throw std::runtime_error("readLines() argument must be a string");
}
std::string filename = args[0].asString();
std::ifstream file(filename);
if (!file.is_open()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Could not open file: " + filename, "", true);
}
throw std::runtime_error("Could not open file: " + filename);
}
std::vector<Value> lines;
std::string line_content;
while (std::getline(file, line_content)) {
lines.push_back(Value(line_content));
}
file.close();
return Value(lines);
});
env->define("readLines", Value(readLinesFunc));
interpreter.addBuiltinFunction(readLinesFunc);
// Create a built-in fileExists function
auto fileExistsFunc = std::make_shared<BuiltinFunction>("fileExists",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"fileExists() argument must be a string", "", true);
}
throw std::runtime_error("fileExists() argument must be a string");
}
std::string filename = args[0].asString();
std::ifstream file(filename);
bool exists = file.good();
file.close();
return Value(exists);
});
env->define("fileExists", Value(fileExistsFunc));
interpreter.addBuiltinFunction(fileExistsFunc);
// Create a built-in memoryUsage function (platform-specific, best effort)
auto memoryUsageFunc = std::make_shared<BuiltinFunction>("memoryUsage",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
// Platform-specific memory usage detection
size_t memoryBytes = 0;
#if defined(__APPLE__) && defined(__MACH__)
// macOS
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
memoryBytes = info.resident_size;
}
#elif defined(__linux__)
// Linux - read from /proc/self/status
std::ifstream statusFile("/proc/self/status");
std::string line;
while (std::getline(statusFile, line)) {
if (line.substr(0, 6) == "VmRSS:") {
std::istringstream iss(line);
std::string label, value, unit;
iss >> label >> value >> unit;
memoryBytes = std::stoull(value) * 1024; // Convert KB to bytes
break;
}
}
#elif defined(_WIN32)
// Windows
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
memoryBytes = pmc.WorkingSetSize;
}
#endif
// Return memory usage in MB for readability
double memoryMB = static_cast<double>(memoryBytes) / (1024.0 * 1024.0);
return Value(memoryMB);
});
env->define("memoryUsage", Value(memoryUsageFunc));
interpreter.addBuiltinFunction(memoryUsageFunc);
}

View File

@ -786,9 +786,9 @@ print("Comprehensive number tests: PASS");
// TEST 29: TIME FUNCTION
// ========================================
print("\n--- Test 29: Time Function ---");
import time;
var start_time = time.now();
var end_time = time.now();
var start_time = time();
var end_time = time();
var duration = end_time - start_time;
assert(start_time > 0, "Start time should be positive");
assert(end_time >= start_time, "End time should be >= start time");
@ -2310,15 +2310,15 @@ print("\n--- Test 50: Arrays ---");
// Array creation
var emptyArray = [];
assert(emptyArray.len() == 0, "Empty array length");
assert(len(emptyArray) == 0, "Empty array length");
var numberArray = [1, 2, 3, 4, 5];
assert(numberArray.len() == 5, "Number array length");
assert(len(numberArray) == 5, "Number array length");
assert(numberArray[0] == 1, "Array indexing - first element");
assert(numberArray[4] == 5, "Array indexing - last element");
var mixedArray = [1, "hello", true, 3.14];
assert(mixedArray.len() == 4, "Mixed array length");
assert(len(mixedArray) == 4, "Mixed array length");
assert(mixedArray[0] == 1, "Mixed array - number");
assert(mixedArray[1] == "hello", "Mixed array - string");
assert(mixedArray[2] == true, "Mixed array - boolean");
@ -2333,35 +2333,35 @@ assert(mixedArray[1] == "world", "Array string assignment");
// Array operations
var testArray = [1, 2, 3];
testArray.push(4);
assert(testArray.len() == 4, "Array push - length");
push(testArray, 4);
assert(len(testArray) == 4, "Array push - length");
assert(testArray[3] == 4, "Array push - value");
var poppedValue = testArray.pop();
var poppedValue = pop(testArray);
assert(poppedValue == 4, "Array pop - returned value");
assert(testArray.len() == 3, "Array pop - length");
assert(len(testArray) == 3, "Array pop - length");
// Array with nested arrays
var nestedArray = [[1, 2], [3, 4], [5, 6]];
assert(nestedArray.len() == 3, "Nested array length");
assert(len(nestedArray) == 3, "Nested array length");
assert(nestedArray[0][0] == 1, "Nested array indexing");
// Array with function calls
var funcArray = [func() { return 42; }, func() { return "hello"; }];
assert(funcArray.len() == 2, "Function array length");
assert(len(funcArray) == 2, "Function array length");
assert(funcArray[0]() == 42, "Function array - first function");
assert(funcArray[1]() == "hello", "Function array - second function");
// Array edge cases
var singleElement = [42];
assert(singleElement.len() == 1, "Single element array");
assert(len(singleElement) == 1, "Single element array");
assert(singleElement[0] == 42, "Single element access");
var largeArray = [];
for (var i = 0; i < 100; i = i + 1) {
largeArray.push(i);
push(largeArray, i);
}
assert(largeArray.len() == 100, "Large array creation");
assert(len(largeArray) == 100, "Large array creation");
assert(largeArray[50] == 50, "Large array access");
print("Arrays: PASS");
@ -2373,40 +2373,40 @@ print("\n--- Test 51: Array Built-in Functions ---");
// len() function
var testLenArray = [1, 2, 3, 4, 5];
assert(testLenArray.len() == 5, "len() with array");
assert(len(testLenArray) == 5, "len() with array");
var emptyLenArray = [];
assert(emptyLenArray.len() == 0, "len() with empty array");
assert(len(emptyLenArray) == 0, "len() with empty array");
// len() with strings
assert("hello".len() == 5, "len() with string");
assert("".len() == 0, "len() with empty string");
assert(len("hello") == 5, "len() with string");
assert(len("") == 0, "len() with empty string");
// push() function
var pushArray = [1, 2, 3];
pushArray.push(4);
assert(pushArray.len() == 4, "push() - length check");
push(pushArray, 4);
assert(len(pushArray) == 4, "push() - length check");
assert(pushArray[3] == 4, "push() - value check");
pushArray.push("hello");
assert(pushArray.len() == 5, "push() - mixed types");
push(pushArray, "hello");
assert(len(pushArray) == 5, "push() - mixed types");
assert(pushArray[4] == "hello", "push() - string value");
// pop() function
var popArray = [1, 2, 3, 4];
var popped1 = popArray.pop();
var popped1 = pop(popArray);
assert(popped1 == 4, "pop() - returned value");
assert(popArray.len() == 3, "pop() - length after pop");
assert(len(popArray) == 3, "pop() - length after pop");
var popped2 = popArray.pop();
var popped2 = pop(popArray);
assert(popped2 == 3, "pop() - second pop");
assert(popArray.len() == 2, "pop() - length after second pop");
assert(len(popArray) == 2, "pop() - length after second pop");
// pop() edge cases
var singlePopArray = [42];
var singlePopped = singlePopArray.pop();
var singlePopped = pop(singlePopArray);
assert(singlePopped == 42, "pop() - single element");
assert(singlePopArray.len() == 0, "pop() - empty after single pop");
assert(len(singlePopArray) == 0, "pop() - empty after single pop");
print("Array built-in functions: PASS");
@ -2416,17 +2416,15 @@ print("Array built-in functions: PASS");
print("\n--- Test 52: New Built-in Functions ---");
// sleep() function
import time as T;
var startTime = T.now();
T.sleep(0.01); // Sleep briefly to ensure elapsed time
var endTime = T.now();
var startTime = time();
sleep(0.001); // Sleep for 1ms (much shorter for testing)
var endTime = time();
assert(endTime > startTime, "sleep() - time elapsed");
// random() function - test proper seeding
import rand as R;
var random1 = R.random();
var random2 = R.random();
var random3 = R.random();
var random1 = random();
var random2 = random();
var random3 = random();
assert(random1 >= 0 && random1 <= 1, "random() - range check 1");
assert(random2 >= 0 && random2 <= 1, "random() - range check 2");
assert(random3 >= 0 && random3 <= 1, "random() - range check 3");
@ -2434,44 +2432,30 @@ assert(random3 >= 0 && random3 <= 1, "random() - range check 3");
assert(random1 != random2 || random2 != random3 || random1 != random3, "random() - different values");
// Test random number generation in different ranges
var randomRange1 = R.random() * 10;
var randomRange2 = R.random() * 100;
var randomRange1 = random() * 10;
var randomRange2 = random() * 100;
assert(randomRange1 >= 0 && randomRange1 <= 10, "random() - range 0-10");
assert(randomRange2 >= 0 && randomRange2 <= 100, "random() - range 0-100");
// eval() function (now via eval module)
import eval as E;
E.eval("var evalVar = 42;");
// eval() function
eval("var evalVar = 42;");
assert(evalVar == 42, "eval() - variable creation");
E.eval("print(\"eval test\");"); // Should print "eval test"
eval("print(\"eval test\");"); // Should print "eval test"
var evalResult = E.eval("2 + 2;");
var evalResult = eval("2 + 2;");
// eval() currently returns none, so we just test it doesn't crash
// Test eval with complex expressions
E.eval("var complexVar = [1, 2, 3];");
assert(complexVar.len() == 3, "eval() - complex expression");
eval("var complexVar = [1, 2, 3];");
assert(len(complexVar) == 3, "eval() - complex expression");
// Test eval with function definitions
E.eval("func evalFunc(x) { return x * 2; }");
eval("func evalFunc(x) { return x * 2; }");
assert(evalFunc(5) == 10, "eval() - function definition");
print("New built-in functions: PASS");
// Module tests
import io as IO;
var p1 = IO.exists("tests/test_path.bob") ? "tests/test_path.bob" : "../tests/test_path.bob";
E.evalFile(p1);
var p2 = IO.exists("tests/test_base64.bob") ? "tests/test_base64.bob" : "../tests/test_base64.bob";
E.evalFile(p2);
var p3 = IO.exists("tests/test_math.bob") ? "tests/test_math.bob" : "../tests/test_math.bob";
E.evalFile(p3);
var p4 = IO.exists("tests/test_rand.bob") ? "tests/test_rand.bob" : "../tests/test_rand.bob";
E.evalFile(p4);
var p5 = IO.exists("tests/test_time.bob") ? "tests/test_time.bob" : "../tests/test_time.bob";
E.evalFile(p5);
// ========================================
// TEST 52.5: EXIT FUNCTION
// ========================================
@ -2495,9 +2479,9 @@ print("\n--- Test 52.6: Enhanced Dictionary Tests ---");
// Test dictionary with none values
var dictWithNone = {"name": "Bob", "age": none, "city": "SF"};
assert(dictWithNone.has("name"), "Dictionary has() - existing key");
assert(dictWithNone.has("age"), "Dictionary has() - none value");
assert(!dictWithNone.has("phone"), "Dictionary has() - missing key");
assert(has(dictWithNone, "name"), "Dictionary has() - existing key");
assert(has(dictWithNone, "age"), "Dictionary has() - none value");
assert(!has(dictWithNone, "phone"), "Dictionary has() - missing key");
// Test setting keys to none
dictWithNone["age"] = none;
@ -2505,18 +2489,18 @@ assert(dictWithNone["age"] == none, "Dictionary - setting key to none");
// Test dictionary with all none values
var allNoneDict = {"a": none, "b": none, "c": none};
var allKeys = ({"a": none, "b": none, "c": none}).keys();
var allValues = ({"a": none, "b": none, "c": none}).values();
assert(allKeys.len() == 3, "Dictionary - all none keys count");
assert(allValues.len() == 3, "Dictionary - all none values count");
var allKeys = keys(allNoneDict);
var allValues = values(allNoneDict);
assert(len(allKeys) == 3, "Dictionary - all none keys count");
assert(len(allValues) == 3, "Dictionary - all none values count");
// Test dictionary stress test
var stressDict = {};
for (var i = 0; i < 100; i = i + 1) {
stressDict["key" + toString(i)] = i;
}
assert(stressDict.keys().len() == 100, "Dictionary stress test - keys");
assert(stressDict.values().len() == 100, "Dictionary stress test - values");
assert(len(keys(stressDict)) == 100, "Dictionary stress test - keys");
assert(len(values(stressDict)) == 100, "Dictionary stress test - values");
print("Enhanced dictionary tests: PASS");
@ -2567,15 +2551,15 @@ assert(emptyDict.length == 0, "Empty dict length");
assert(emptyDict.empty == true, "Empty dict empty");
// Test dict.keys and dict.values properties
assert(dictWithProps.keys.len() == 3, "Dict keys property length");
assert(dictWithProps.values.len() == 3, "Dict values property length");
assert(emptyDict.keys.len() == 0, "Empty dict keys length");
assert(emptyDict.values.len() == 0, "Empty dict values length");
assert(len(dictWithProps.keys) == 3, "Dict keys property length");
assert(len(dictWithProps.values) == 3, "Dict values property length");
assert(len(emptyDict.keys) == 0, "Empty dict keys length");
assert(len(emptyDict.values) == 0, "Empty dict values length");
// Test equivalence with old functions
var testDict = {"x": 10, "y": 20};
assert(testDict.keys.len() == testDict.keys.len(), "Dict keys property works");
assert(testDict.values.len() == testDict.values.len(), "Dict values property works");
assert(len(testDict.keys) == len(keys(testDict)), "Dict keys equivalent to keys() function");
assert(len(testDict.values) == len(values(testDict)), "Dict values equivalent to values() function");
print(" Dict builtin properties: PASS");
// Nested property access and assignment
@ -2645,7 +2629,7 @@ print("\n--- Test 52.8: Memory Management ---");
for (var i = 0; i < 1000; i = i + 1) {
var largeArray = [];
for (var j = 0; j < 100; j = j + 1) {
largeArray.push("string" + toString(j));
push(largeArray, "string" + toString(j));
}
var largeDict = {};
@ -2719,7 +2703,7 @@ var errorArray = [1, 2, 3];
// Test array bounds checking
// Note: These would cause runtime errors in the actual implementation
// For now, we test that the array operations work correctly
assert(errorArray.len() == 3, "Array bounds - valid length");
assert(len(errorArray) == 3, "Array bounds - valid length");
// Test with valid indices
assert(errorArray[0] == 1, "Array bounds - valid index 0");
@ -2734,15 +2718,15 @@ print("\n--- Test 54: Array Performance ---");
// Test large array operations
var perfArray = [];
import time as T; var startTime = T.now();
var startTime = time();
// Create large array
for (var i = 0; i < 1000; i = i + 1) {
perfArray.push(i);
push(perfArray, i);
}
var midTime = T.now();
assert(perfArray.len() == 1000, "Array performance - creation");
var midTime = time();
assert(len(perfArray) == 1000, "Array performance - creation");
// Access elements
for (var j = 0; j < 100; j = j + 1) {
@ -2750,7 +2734,7 @@ for (var j = 0; j < 100; j = j + 1) {
assert(value == j * 10, "Array performance - access");
}
var endTime = T.now();
var endTime = time();
assert(endTime > startTime, "Array performance - time check");
print("Array performance: PASS");
@ -2767,7 +2751,7 @@ var funcArray2 = [
func() { return 3; }
];
assert(funcArray2.len() == 3, "Function array length");
assert(len(funcArray2) == 3, "Function array length");
assert(funcArray2[0]() == 1, "Function array - call 1");
assert(funcArray2[1]() == 2, "Function array - call 2");
assert(funcArray2[2]() == 3, "Function array - call 3");
@ -2778,18 +2762,18 @@ func createArray() {
}
var returnedArray = createArray();
assert(returnedArray.len() == 5, "Function returning array");
assert(len(returnedArray) == 5, "Function returning array");
assert(returnedArray[0] == 1, "Function returning array - first element");
// Function that modifies array
func modifyArray(arr) {
arr.push(999);
push(arr, 999);
return arr;
}
var modArray = [1, 2, 3];
var modifiedArray = modifyArray(modArray);
assert(modifiedArray.len() == 4, "Function modifying array");
assert(len(modifiedArray) == 4, "Function modifying array");
assert(modifiedArray[3] == 999, "Function modifying array - new value");
print("Array with functions: PASS");
@ -2801,11 +2785,11 @@ print("\n--- Test 56: Array Edge Cases and Advanced Features ---");
// Array with none values
var noneArray = [1, none, 3, none, 5];
assert(noneArray.len() == 5, "Array with none values - length");
assert(len(noneArray) == 5, "Array with none values - length");
// Note: Bob doesn't support comparing none values with ==
// We can test that the array contains the expected number of elements
var noneCount = 0;
for (var i = 0; i < noneArray.len(); i = i + 1) {
for (var i = 0; i < len(noneArray); i = i + 1) {
if (type(noneArray[i]) == "none") {
noneCount = noneCount + 1;
}
@ -2814,7 +2798,7 @@ assert(noneCount == 2, "Array with none values - none count");
// Array with complex expressions
var complexArray = [1 + 1, 2 * 3, 10 / 2, 5 - 2];
assert(complexArray.len() == 4, "Complex array - length");
assert(len(complexArray) == 4, "Complex array - length");
assert(complexArray[0] == 2, "Complex array - addition");
assert(complexArray[1] == 6, "Complex array - multiplication");
assert(complexArray[2] == 5, "Complex array - division");
@ -2822,7 +2806,7 @@ assert(complexArray[3] == 3, "Complex array - subtraction");
// Array with string operations
var stringArray = ["hello" + " world", "test" * 2, "a" + "b" + "c"];
assert(stringArray.len() == 3, "String array - length");
assert(len(stringArray) == 3, "String array - length");
assert(stringArray[0] == "hello world", "String array - concatenation");
assert(stringArray[1] == "testtest", "String array - multiplication");
assert(stringArray[2] == "abc", "String array - multiple concatenation");
@ -2832,14 +2816,14 @@ func getValue() { return 42; }
func getString() { return "hello"; }
var funcResultArray = [getValue(), getString(), 1 + 2];
assert(funcResultArray.len() == 3, "Function result array - length");
assert(len(funcResultArray) == 3, "Function result array - length");
assert(funcResultArray[0] == 42, "Function result array - function call");
assert(funcResultArray[1] == "hello", "Function result array - string function");
assert(funcResultArray[2] == 3, "Function result array - expression");
// Array with ternary operators
var ternaryArray = [true ? 1 : 0, false ? "yes" : "no", 5 > 3 ? "big" : "small"];
assert(ternaryArray.len() == 3, "Ternary array - length");
assert(len(ternaryArray) == 3, "Ternary array - length");
assert(ternaryArray[0] == 1, "Ternary array - true condition");
assert(ternaryArray[1] == "no", "Ternary array - false condition");
assert(ternaryArray[2] == "big", "Ternary array - comparison condition");
@ -2851,13 +2835,13 @@ assert(nestedOpArray[0][0] == 99, "Nested array operations - assignment");
// Array with push/pop in expressions
var exprArray = [1, 2, 3];
var pushResult = exprArray.push(4);
assert(exprArray.len() == 4, "Array push in expression");
var pushResult = push(exprArray, 4);
assert(len(exprArray) == 4, "Array push in expression");
assert(exprArray[3] == 4, "Array push result");
var popResult = exprArray.pop();
var popResult = pop(exprArray);
assert(popResult == 4, "Array pop result");
assert(exprArray.len() == 3, "Array length after pop");
assert(len(exprArray) == 3, "Array length after pop");
print("Array edge cases and advanced features: PASS");
@ -2868,31 +2852,31 @@ print("\n--- Test 57: Array Stress Testing ---");
// Large array creation and manipulation
var stressArray = [];
import time as T2; var startTime = T2.now();
var startTime = time();
// Create large array with mixed types
for (var i = 0; i < 500; i = i + 1) {
if (i % 3 == 0) {
stressArray.push(i);
push(stressArray, i);
} else if (i % 3 == 1) {
stressArray.push("string" + toString(i));
push(stressArray, "string" + toString(i));
} else {
stressArray.push(i > 250);
push(stressArray, i > 250);
}
}
var midTime = T2.now();
assert(stressArray.len() == 500, "Stress test - array creation");
var midTime = time();
assert(len(stressArray) == 500, "Stress test - array creation");
// Access and modify elements
for (var j = 0; j < 100; j = j + 1) {
var index = j * 5;
if (index < stressArray.len()) {
if (index < len(stressArray)) {
stressArray[index] = "modified" + toString(j);
}
}
var endTime = T2.now();
var endTime = time();
assert(endTime > startTime, "Stress test - time validation");
// Verify modifications
@ -3008,8 +2992,7 @@ assert(toInt(-0.0) == 0, "toInt(-0.0) should be 0");
// Test with random numbers
for(var i = 0; i < 5; i++) {
import rand as Rn;
var randomFloat = Rn.random() * 10;
var randomFloat = random() * 10;
var randomInt = toInt(randomFloat);
assert(randomInt >= 0 && randomInt <= 9, "toInt(random) should be in range 0-9");
}
@ -3288,61 +3271,5 @@ print("- Interactive Mode Features");
print(" * REPL functionality");
print(" * Error reporting in both modes");
// Additional Tests: Classes and Extensions
print("\n--- Additional Tests: Classes and Extensions ---");
import io as IO; // for file existence checks
var path1 = IO.exists("tests/test_method_calls.bob") ? "tests/test_method_calls.bob" : "../tests/test_method_calls.bob";
E.evalFile(path1);
var path2 = IO.exists("tests/test_class_basic.bob") ? "tests/test_class_basic.bob" : "../tests/test_class_basic.bob";
E.evalFile(path2);
var path3 = IO.exists("tests/test_class_with_this.bob") ? "tests/test_class_with_this.bob" : "../tests/test_class_with_this.bob";
E.evalFile(path3);
var path4 = IO.exists("tests/test_class_init.bob") ? "tests/test_class_init.bob" : "../tests/test_class_init.bob";
E.evalFile(path4);
var path5 = IO.exists("tests/test_class_extension_user.bob") ? "tests/test_class_extension_user.bob" : "../tests/test_class_extension_user.bob";
E.evalFile(path5);
var path6 = IO.exists("tests/test_extension_methods.bob") ? "tests/test_extension_methods.bob" : "../tests/test_extension_methods.bob";
E.evalFile(path6);
var path7 = IO.exists("tests/test_class_inheritance.bob") ? "tests/test_class_inheritance.bob" : "../tests/test_class_inheritance.bob";
E.evalFile(path7);
var path8 = IO.exists("tests/test_classes_comprehensive.bob") ? "tests/test_classes_comprehensive.bob" : "../tests/test_classes_comprehensive.bob";
E.evalFile(path8);
var path9 = IO.exists("tests/test_class_super.bob") ? "tests/test_class_super.bob" : "../tests/test_class_super.bob";
E.evalFile(path9);
var path10 = IO.exists("tests/test_classes_extensive.bob") ? "tests/test_classes_extensive.bob" : "../tests/test_classes_extensive.bob";
E.evalFile(path10);
var path11 = IO.exists("tests/test_class_edge_cases.bob") ? "tests/test_class_edge_cases.bob" : "../tests/test_class_edge_cases.bob";
E.evalFile(path11);
var path12 = IO.exists("tests/test_polymorphism.bob") ? "tests/test_polymorphism.bob" : "../tests/test_polymorphism.bob";
E.evalFile(path12);
var path13 = IO.exists("tests/test_polymorphism_practical.bob") ? "tests/test_polymorphism_practical.bob" : "../tests/test_polymorphism_practical.bob";
E.evalFile(path13);
var path14 = IO.exists("tests/test_builtin_methods_style.bob") ? "tests/test_builtin_methods_style.bob" : "../tests/test_builtin_methods_style.bob";
E.evalFile(path14);
var path15 = IO.exists("tests/test_try_catch.bob") ? "tests/test_try_catch.bob" : "../tests/test_try_catch.bob";
E.evalFile(path15);
var path15a = IO.exists("tests/test_try_catch_runtime.bob") ? "tests/test_try_catch_runtime.bob" : "../tests/test_try_catch_runtime.bob";
E.evalFile(path15a);
var path15b = IO.exists("tests/test_try_catch_extensive.bob") ? "tests/test_try_catch_extensive.bob" : "../tests/test_try_catch_extensive.bob";
E.evalFile(path15b);
var path15c = IO.exists("tests/test_try_catch_edge_cases2.bob") ? "tests/test_try_catch_edge_cases2.bob" : "../tests/test_try_catch_edge_cases2.bob";
E.evalFile(path15c);
var path15d = IO.exists("tests/test_try_catch_cross_function.bob") ? "tests/test_try_catch_cross_function.bob" : "../tests/test_try_catch_cross_function.bob";
E.evalFile(path15d);
var path15e = IO.exists("tests/test_try_catch_loop_interactions.bob") ? "tests/test_try_catch_loop_interactions.bob" : "../tests/test_try_catch_loop_interactions.bob";
E.evalFile(path15e);
// Modules: basic imports suite
var pathMods = IO.exists("tests/test_imports_basic.bob") ? "tests/test_imports_basic.bob" : "../tests/test_imports_basic.bob";
E.evalFile(pathMods);
var pathModsB = IO.exists("tests/test_imports_builtin.bob") ? "tests/test_imports_builtin.bob" : "../tests/test_imports_builtin.bob";
E.evalFile(pathModsB);
var pathOs = IO.exists("tests/test_os_basic.bob") ? "tests/test_os_basic.bob" : "../tests/test_os_basic.bob";
E.evalFile(pathOs);
print("\nAll tests passed.");
print("Test suite complete.");

139
tests.bob
View File

@ -1,96 +1,53 @@
// // var a = [];
var a = [];
// // for(var i = 0; i < 1000000; i++){
// // print(i);
for(var i = 0; i < 1000000; i++){
print(i);
// // // Create nested structures with functions at different levels
// // if (i % 4 == 0) {
// // // Nested array with function
// // push(a, [
// // func(){print("Array nested func i=" + i); return i;},
// // [func(){return "Deep array func " + i;}],
// // i
// // ]);
// // } else if (i % 4 == 1) {
// // // Nested dict with function
// // push(a, {
// // "func": func(){print("Dict func i=" + i); return i;},
// // "nested": {"deepFunc": func(){return "Deep dict func " + i;}},
// // "value": i
// // });
// // } else if (i % 4 == 2) {
// // // Mixed nested array/dict with functions
// // push(a, [
// // {"arrayInDict": func(){return "Mixed " + i;}},
// // [func(){return "Array in array " + i;}, {"more": func(){return i;}}],
// // func(){print("Top level in mixed i=" + i); return i;}
// // ]);
// // } else {
// // // Simple function (original test case)
// // push(a, func(){print("Simple func i=" + i); return toString(i);});
// // }
// // }
// Create nested structures with functions at different levels
if (i % 4 == 0) {
// Nested array with function
push(a, [
func(){print("Array nested func i=" + i); return i;},
[func(){return "Deep array func " + i;}],
i
]);
} else if (i % 4 == 1) {
// Nested dict with function
push(a, {
"func": func(){print("Dict func i=" + i); return i;},
"nested": {"deepFunc": func(){return "Deep dict func " + i;}},
"value": i
});
} else if (i % 4 == 2) {
// Mixed nested array/dict with functions
push(a, [
{"arrayInDict": func(){return "Mixed " + i;}},
[func(){return "Array in array " + i;}, {"more": func(){return i;}}],
func(){print("Top level in mixed i=" + i); return i;}
]);
} else {
// Simple function (original test case)
push(a, func(){print("Simple func i=" + i); return toString(i);});
}
}
// // print("Before: " + len(a));
// // print("Memory usage: " + memoryUsage() + " MB");
print("Before: " + len(a));
print("Memory usage: " + memoryUsage() + " MB");
// // // Test different types of nested function calls
// // a[3691](); // Simple function
// // if (len(a[3692]) > 0) {
// // a[3692][0](); // Nested array function
// // }
// // if (a[3693]["func"]) {
// // a[3693]["func"](); // Nested dict function
// // }
// // //print(a);
// // //writeFile("array_contents.txt", toString(a));
// // print("Array contents written to array_contents.txt");
// // print("Memory before cleanup: " + memoryUsage() + " MB");
// // input("Press any key to free memory");
// Test different types of nested function calls
a[3691](); // Simple function
if (len(a[3692]) > 0) {
a[3692][0](); // Nested array function
}
if (a[3693]["func"]) {
a[3693]["func"](); // Nested dict function
}
print(a);
//writeFile("array_contents.txt", toString(a));
print("Array contents written to array_contents.txt");
print("Memory before cleanup: " + memoryUsage() + " MB");
input("Press any key to free memory");
// // a = none;
// // print("Memory after cleanup: " + memoryUsage() + " MB");
// // input("waiting...");
// class Test {
// func init() {
// print("Test init" + this.a);
// }
// var a = 10;
// func test() {
// //print(a);
// print(this.a);
// }
// }
// var arr = [];
// for(var i = 0; i < 100; i++){
// arr.push(i);
// }
// var counter = 0;
// try{
// while(true){
// print(arr[counter]);
// counter++;
// //sleep(0.01);
// }
// }catch(){}
// try{
// assert(false);
// }catch(){}
// print("done");
// Modules: basic imports suite
var pathMods = fileExists("tests/test_imports_basic.bob") ? "tests/test_imports_basic.bob" : "../tests/test_imports_basic.bob";
evalFile(pathMods);
print("done");
a = none;
print("Memory after cleanup: " + memoryUsage() + " MB");
input("waiting...");

View File

@ -1,5 +0,0 @@
extension any { func tag() { return "<" + toString(this) + ">"; } }
var plain = { "x": 1 };
print(plain.tag());

View File

@ -1,7 +0,0 @@
class Animal { var name; func init(n) { this.name = n; } }
extension Animal { func speak() { return this.name + "?"; } }
class Dog extends Animal { }
var d = Dog("fido");
print(d.speak());

View File

@ -1,3 +0,0 @@
class P { func init(f,l) { print(f + ":" + l); } }
var p = P("A","B");

View File

@ -1,8 +0,0 @@
class T {
var a = "";
func init(x, y) { this.a = x + y; }
}
var t = T("A","B");
print(t.a);

View File

@ -1,6 +0,0 @@
class M0 { func name() { return "M0"; } }
class M1 extends M0 {}
extension M1 { func name() { return super.name() + "*"; } }
print(M1().name());

View File

@ -1,12 +0,0 @@
class A {}
class B extends A {}
class C extends B {}
extension A { func v() { return 1; } }
extension B { func v() { return super.v() + 1; } }
extension C { func v() { return super.v() + 1; } }
var c = C();
print(c.v());

View File

@ -1,10 +0,0 @@
class A {}
class B extends A {}
extension A { func v() { return 1; } }
extension B { func v() { return super.v() + 1; } }
var b = B();
print(b.v());

View File

@ -1,5 +0,0 @@
// uses mod_hello without importing it here; relies on prior import in same interpreter
var ok1 = (mod_hello.greet("Z") == "hi Z");
assert(ok1, "imported module binding visible across eval files in same interpreter");
print("import user: PASS");

View File

@ -1,3 +0,0 @@
var X = 5;
func greet(name) { return "hi " + name; }

View File

@ -1,11 +0,0 @@
print("\n--- Test: base64 module ---");
import base64;
var s = "hello world";
var enc = base64.encode(s);
assert(enc == "aGVsbG8gd29ybGQ=", "base64.encode");
var dec = base64.decode(enc);
assert(dec == s, "base64.decode roundtrip");
print("base64: PASS");

View File

@ -1,24 +0,0 @@
// Method-style builtins on arrays, strings, dicts, and numbers
var a = [1, 2];
assert(a.len() == 2, "array.len()");
a.push(3, 4);
assert(a.len() == 4, "array.push(...)");
assert(a.pop() == 4, "array.pop() value");
assert(a.len() == 3, "array.pop() length");
assert(a[2] == 3, "array after push/pop");
var s = "hello";
assert(s.len() == 5, "string.len()");
var d = {"x": 1, "y": 2};
assert(d.len() == 2, "dict.len()");
var ks = d.keys();
var vs = d.values();
assert(ks.len() == 2, "dict.keys().len()");
assert(vs.len() == 2, "dict.values().len()");
assert(d.has("x"), "dict.has(true)");
assert(!d.has("z"), "dict.has(false)");
var n = 3.9;
assert(n.toInt() == 3, "number.toInt()");

View File

@ -1,77 +0,0 @@
print("\n--- Class + Extension Syntax Proposal (Spec Tests) ---");
// These tests define the intended behavior and syntax. The parser/runtime
// will be implemented to satisfy them.
// Class declaration
class Person {
var name;
var age;
func init(name, age) {
this.name = name;
this.age = age;
}
func greet() {
print("Hi, I'm " + this.name + " (" + toString(this.age) + ")");
}
func birthday() {
this.age = this.age + 1;
}
}
// Instantiation + method calls
// var p = Person("Bob", 30);
// p.greet(); // "Hi, I'm Bob (30)"
// p.birthday();
// p.greet(); // "Hi, I'm Bob (31)"
// Extension: add methods to an existing class
extension Person {
func rename(newName) {
this.name = newName;
}
}
// p.rename("Robert");
// p.greet(); // "Hi, I'm Robert (31)"
// Extension on built-ins (string, array, dict are the type names)
extension string {
func repeat(n) {
var out = "";
var i = 0;
while (i < n) {
out = out + this;
i = i + 1;
}
return out;
}
}
// assert("ha".repeat(3) == "hahaha", "string.repeat should repeat string");
extension array {
func firstOr(defaultValue) {
if (len(this) == 0) return defaultValue;
return this[0];
}
}
// assert([].firstOr("none") == "none", "array.firstOr should return default on empty");
// Extension on any: adds a method to all objects
extension any {
func debug() {
return type(this) + ":" + toString(this);
}
}
// assert(42.debug() == "number:42", "any.debug should work on numbers");
// assert("x".debug() == "string:x", "any.debug should work on strings");
print("Class + Extension spec tests defined (pending parser/runtime support).");

View File

@ -1,20 +0,0 @@
print("\n--- Test: Basic Class (no init, no this) ---");
class Foo {
var x;
func ping() { print("ping"); }
}
var a = Foo();
a.ping();
// Field default should be none
assert(type(a.x) == "none", "default field value should be none");
// Property assignment on instance
a.x = 42;
assert(a.x == 42, "instance property set/get should work");
print("Basic class: PASS");

View File

@ -1,52 +0,0 @@
print("\n--- Test: Class Edge Cases ---");
// 1) 'this' inside extension should read/write fields
class E1 { var x = 1; }
extension E1 { func bump() { this.x = this.x + 1; return this.x; } }
var e1 = E1();
assert(e1.bump() == 2, "extension writes this");
assert(e1.x == 2, "field reflects extension write");
// 2) Property overshadowing method: user property wins
class ShM { func m() { return 10; } }
var shm = ShM();
shm.m = 7;
assert(shm.m == 7, "property overshadows method value lookup");
// 3) Field name same as method; property has precedence
class FvM { var id = 1; func id() { return 99; } }
var fvm = FvM();
assert(fvm.id == 1, "property shadows method with same name");
// Replace with property; still property
fvm.id = 5;
assert(fvm.id == 5, "property remains in precedence after reassignment");
// 4) Late extension overrides previous extension
class Redef { }
extension Redef { func v() { return 1; } }
assert(Redef().v() == 1, "initial ext");
extension Redef { func v() { return 2; } }
assert(Redef().v() == 2, "redefined ext");
// 5) Super in multi-level extension chain already covered; add deeper chain guard
class S0 {}
class S1 extends S0 {}
class S2 extends S1 {}
class S3 extends S2 {}
extension S0 { func v() { return 1; } }
extension S1 { func v() { return super.v() + 1; } }
extension S2 { func v() { return super.v() + 1; } }
extension S3 { func v() { return super.v() + 1; } }
assert(S3().v() == 4, "deep super chain");
// 6) Built-in type extension coexists with class extension precedence
class N1 {}
extension number { func plus1() { return this + 1; } }
extension N1 { func plus1() { return 100; } }
var n1 = N1();
assert(5.plus1() == 6, "builtin number ext works");
assert(n1.plus1() == 100, "class ext wins over any/builtin");
print("Class edge cases: PASS");

View File

@ -1,21 +0,0 @@
print("\n--- Test: User class extensions ---");
class Box {
var v;
func init(x) { this.v = x; }
func get() { return this.v; }
}
extension Box {
func inc() { this.v = this.v + 1; }
func label(s) { return s + ":" + toString(this.v); }
}
var b = Box(10);
b.inc();
assert(b.get() == 11, "Box.inc should increment value");
assert(b.label("val") == "val:11", "Box.label should return formatted string");
print("User class extensions: PASS");

View File

@ -1,40 +0,0 @@
print("\n--- Test: Class Inheritance (extensions) ---");
class Animal { var name; func init(n) { this.name = n; } }
extension Animal { func speak() { return this.name + "?"; } }
class Dog extends Animal { }
extension Dog { func bark() { return this.name + " woof"; } }
var a = Animal("crit");
var d = Dog("fido");
assert(a.speak() == "crit?", "Animal.speak works");
assert(d.speak() == "fido?", "Dog inherits Animal.speak via extension chain");
assert(d.bark() == "fido woof", "Dog.bark works");
print("Inheritance via extensions: PASS");
print("\n--- Test: Class Inheritance (inline methods) ---");
class Animal2 {
var name;
func init(n) { this.name = n; }
func speak() { return this.name + "!"; }
}
class Dog2 extends Animal2 {
func bark() { return this.name + " woof"; }
}
var a2 = Animal2("crit");
var d2 = Dog2("fido");
assert(a2.speak() == "crit!", "Animal2.speak works (inline)");
assert(d2.speak() == "fido!", "Dog2 inherits Animal2.speak (inline)");
assert(d2.bark() == "fido woof", "Dog2.bark works (inline)");
print("Inheritance via inline methods: PASS");

View File

@ -1,15 +0,0 @@
print("\n--- Test: Class init auto-call with args ---");
class Person {
var name;
var age;
func init(n, a) { this.name = n; this.age = a; }
func who() { return this.name + ":" + toString(this.age); }
}
var p = Person("Bob", 33);
assert(p.who() == "Bob:33", "init should set fields from constructor args");
print("Class init: PASS");

View File

@ -1,85 +0,0 @@
print("\n--- Test: Class System Stress + Memory Observation ---");
// Class with fields and methods using `this`
class Node {
var id;
var payload;
func setId(v) { this.id = v; }
func setPayload(p) { this.payload = p; }
func sum() {
if (this.payload == none) return 0;
if (this.payload.len() == 0) return 0;
var i = 0;
var s = 0;
while (i < this.payload.len()) {
s = s + this.payload[i];
i = i + 1;
}
return s;
}
func describe() {
return "Node(" + toString(this.id) + ", sum=" + toString(this.sum()) + ")";
}
}
// Basic functional checks
var a = Node();
a.setId(7);
a.setPayload([1, 2, 4]);
assert(a.sum() == 7, "sum() should sum payload");
assert(type(a.describe()) == "string", "describe() returns string");
// Memory observation before creating many objects
var mbBefore = memoryUsage();
print("Memory before allocations (MB): " + toString(mbBefore));
// Create many objects and store them
var COUNT = 40000; // adjust as needed for your machine
var nodes = [];
var i = 0;
while (i < COUNT) {
var n = Node();
n.setId(i);
// small payload to keep variety, not too heavy
n.setPayload([i % 10, (i * 2) % 10, (i * 3) % 10]);
nodes.push(n);
i = i + 1;
}
// Spot-check a few nodes
assert(nodes[0].sum() == (0 + 0 + 0), "node0 sum");
assert(nodes[1].sum() == (1 + 2 + 3), "node1 sum");
assert(type(nodes[100].describe()) == "string", "describe() type");
// Memory after allocation
var mbAfter = memoryUsage();
print("Memory after allocations (MB): " + toString(mbAfter));
// Verify each object is callable and alive: call sum() on every node
var verify = 0;
i = 0;
while (i < COUNT) {
verify = verify + nodes[i].sum();
print(nodes[i].describe());
i = i + 1;
}
print("Verification sum: " + toString(verify));
// Pause to allow manual inspection via Activity Monitor, etc.
print("Press Enter to clear references and observe memory...");
input();
// Clear references to allow GC/release by refcounts
nodes = [];
// Observe memory after clearing
var mbCleared = memoryUsage();
print("Memory after clear (MB): " + toString(mbCleared));
print("Class stress + memory observation: DONE");

View File

@ -1,21 +0,0 @@
print("\n--- Test: super calls ---");
class A { }
class B extends A { }
class C extends B { }
extension A { func v() { return 1; } }
extension B { func v() { return super.v() + 1; } }
extension C { func v() { return super.v() + 1; } }
var a = A();
var b = B();
var c = C();
assert(a.v() == 1, "A.v");
assert(b.v() == 2, "B.v via super");
assert(c.v() == 3, "C.v via super chain");
print("super calls: PASS");

View File

@ -1,15 +0,0 @@
print("\n--- Test: Class methods with this (assignment allowed) ---");
class Person {
var name;
func setName(n) { this.name = n; }
func getName() { return this.name; }
}
var p = Person();
p.setName("Bob");
assert(p.getName() == "Bob", "this-based methods should read/write instance fields");
print("Class with this: PASS");

View File

@ -1,94 +0,0 @@
print("\n--- Test: Classes Comprehensive ---");
// Basic class with field and method using this
class Person {
var name;
func init(n) { this.name = n; }
func greet() { return "Hello " + this.name; }
}
var p = Person("Bob");
assert(p.name == "Bob", "init sets name");
assert(p.greet() == "Hello Bob", "method with this works");
// Field initializers (constant)
class Box {
var width = 2;
var height = 3;
func area() { return this.width * this.height; }
}
var b = Box();
assert(b.width == 2, "field initializer width");
assert(b.height == 3, "field initializer height");
assert(b.area() == 6, "method sees initialized fields");
// Inline method precedence over extensions
class Animal {
var name;
func init(n) { this.name = n; }
}
extension Animal { func speak() { return this.name + "?"; } }
var a = Animal("crit");
assert(a.speak() == "crit?", "Animal.speak via extension");
class Dog extends Animal {
func speak() { return this.name + "!"; }
}
var d = Dog("fido");
assert(d.speak() == "fido!", "Dog inline speak overrides Animal extension");
// Inheritance chain for extensions: define on parent only
class Cat extends Animal {}
var c = Cat("mew");
assert(c.speak() == "mew?", "Cat inherits Animal.speak extension");
// Add extension later and ensure it applies retroactively
class Bird { var name; func init(n) { this.name = n; } }
var bird = Bird("tweet");
extension Bird { func speak() { return this.name + "~"; } }
assert(bird.speak() == "tweet~", "Late extension attaches to existing instances");
// any fallback extension when nothing else matches
extension any { func tag() { return "<" + toString(this) + ">"; } }
var plain = { "x": 1 };
assert(plain.tag() == "<{" + "\"x\": 1}" + ">", "any fallback extension works on dict");
// Ensure property value shadows extension lookup
class Shadow { }
extension Shadow { func value() { return 42; } }
var sh = Shadow();
sh.value = 7;
assert(sh.value == 7, "user property shadows extension method");
// Ensure instance method shadows extension
class Shadow2 { func m() { return 1; } }
extension Shadow2 { func m() { return 2; } }
var sh2 = Shadow2();
assert(sh2.m() == 1, "instance method shadows extension method");
// Method call injection of this; method reference does not auto-bind
class Ref {
var v = 5;
func get() { return this.v; }
}
var r = Ref();
var m = r.get;
// Calling m() should fail due to missing this; instead call through property again
assert(r.get() == 5, "method call via property injects this");
// Inheritance of inline methods
class Base { func id() { return 1; } }
class Child extends Base { }
var ch = Child();
assert(ch.id() == 1, "inherit inline method");
// Parent chain precedence: Child has no say(), extension on Base should work
extension Base { func say() { return "base"; } }
assert(ch.say() == "base", "inherit extension from parent");
// Verify __class tag exists for instances (check with method that reads this)
assert(Ref().get() == 5, "instance constructed correctly");
print("Classes comprehensive: PASS");

View File

@ -1,92 +0,0 @@
print("\n--- Test: Classes Extensive ---");
// Basic class with multiple fields, initializers, and methods
class Person {
var first = "Bob";
var last = "Lucero";
var full = "Hello Bob Lucero";
func name() { return this.first + " " + this.last; }
func greet() { return "Hi " + this.name(); }
}
var p = Person();
assert(p.first == "Bob", "default field");
assert(p.last == "Lucero", "default field 2");
assert(p.name() == "Bob Lucero", "method calling another method");
assert(p.full == "Hello Bob Lucero", "field initializer");
// Separate init param binding check
class PInit { var a = ""; func init(x, y) { this.a = x + y; } }
assert(PInit("Ada","L").a == "AdaL", "init binds parameters");
// Inheritance: inline methods inherited and overridden; super to parent inline
class Animal { var n; func init(n) { this.n = n; } func speak() { return this.n + ":..."; } }
class Dog extends Animal { func speak() { return super.speak() + " woof"; } }
class LoudDog extends Dog { func speak() { return super.speak() + "!"; } }
var a = Animal("thing");
var d = Dog("fido");
var ld = LoudDog("rex");
assert(a.speak() == "thing:...", "parent inline");
assert(d.speak() == "fido:... woof", "override + super inline");
assert(ld.speak() == "rex:... woof!", "super chain inline 3 levels");
// Class extensions on parent and child, resolution order and super to extension
class Base {}
class Mid extends Base {}
class Leaf extends Mid {}
extension Base { func v() { return 1; } }
extension Mid { func v() { return super.v() + 1; } }
extension Leaf { func v() { return super.v() + 1; } }
assert(Base().v() == 1, "base ext");
assert(Mid().v() == 2, "mid super->base ext");
assert(Leaf().v() == 3, "leaf super->mid->base ext");
// Instance method shadows extension; super from child instance method to parent extension
class C1 {}
class C2 extends C1 { func m() { return super.m() + 10; } }
extension C1 { func m() { return 5; } }
assert(C2().m() == 15, "instance method super to parent extension");
// User property shadows extension method
class Shadow {}
extension Shadow { func val() { return 42; } }
var sh = Shadow(); sh.val = 7; assert(sh.val == 7, "property shadows extension");
// Late extension attaches to existing instances
class Later { var x = 2; }
var l = Later();
extension Later { func dbl() { return this.x * 2; } }
assert(l.dbl() == 4, "late extension visible");
// Polymorphism array: mixed classes share method name
class PBase { func id() { return "base"; } }
class PChild extends PBase { func id() { return "child"; } }
class PLeaf extends PChild {}
var poly = [PBase(), PChild(), PLeaf()];
assert(poly[0].id() == "base", "poly base");
assert(poly[1].id() == "child", "poly override");
assert(poly[2].id() == "child", "poly inherited override");
// any fallback not taken if class or parent provides method
class AF {}
extension any { func tag() { return "<" + toString(this) + ">"; } }
extension AF { func tag() { return "af"; } }
assert(AF().tag() == "af", "prefer class extension over any");
// Built-in extensions still work
extension number { func neg() { return -this; } }
assert(5.neg() == -5, "number extension");
// Method reference semantics: not auto-bound
class Ref { var v = 9; func get() { return this.v; } }
var r = Ref();
var rf = r.get; // function reference
assert(r.get() == 9, "call via property injects this");
// Not calling rf() directly to avoid unbound-this error; contract is explicit injection via property call
print("Classes extensive: PASS");

View File

@ -1,35 +0,0 @@
print("\n--- Test: Extension methods on built-ins ---");
extension array {
func sum() {
var i = 0;
var s = 0;
while (i < this.len()) {
s = s + this[i];
i = i + 1;
}
return s;
}
}
extension dict {
func size() { return this.len(); }
}
extension string {
func shout() { return toString(this) + "!"; }
}
extension any {
func tag() { return "<" + type(this) + ">"; }
}
assert([1,2,3].sum() == 6, "array.sum should sum elements");
assert({"a":1,"b":2}.size() == 2, "dict.size should return count");
assert("hi".shout() == "hi!", "string.shout should append !");
assert(toInt(42).tag() == "<number>", "any.tag works on numbers");
assert("x".tag() == "<string>", "any.tag works on strings");
print("Extension methods on built-ins: PASS");

View File

@ -1,10 +0,0 @@
print("\n--- Test: Extension Syntax (spec only) ---");
// Parsing should fail until extension is implemented; keep as spec placeholder
// extension Foo {
// func hello() { print("hello"); }
// }
print("Extension syntax spec placeholder.");

View File

@ -1,5 +0,0 @@
print("\n--- Test: hash module ---");
// Placeholder: implement when hash module is added
print("hash: SKIPPED");

View File

@ -1,48 +0,0 @@
print("\n--- Test: basic imports ---");
// Import by path (with alias) - relative to this file's directory
import "mod_hello.bob" as H;
assert(type(H) == "module", "module is module");
assert(H.X == 5, "exported var");
assert(H.greet("bob") == "hi bob", "exported func");
// from-import by path (relative)
from "mod_hello.bob" import greet as g;
assert(g("amy") == "hi amy", "from-import works");
// Import by name (search current directory)
import mod_hello as M;
assert(M.X == 5 && M.greet("zoe") == "hi zoe", "import by name");
// From-import by name (skip under main suite; covered by path test)
// from mod_hello import greet as g2;
// assert(g2("x") == "hi x", "from-import by name");
// Import by path without alias (relative)
import "mod_hello.bob";
assert(type(mod_hello) == "module" && mod_hello.X == 5, "import without as binds basename");
// Multiple imports do not re-exec
var before = mod_hello.X;
import "mod_hello.bob";
var after = mod_hello.X;
assert(before == after, "module executed once (cached)");
// Cross-file visibility in same interpreter: eval user file after importing here
import eval as E;
E.evalFile("tests/import_user_of_mod_hello.bob");
// Immutability: cannot reassign module binding
var immFail = false;
try { mod_hello = 123; } catch (e) { immFail = true; }
assert(immFail == true, "module binding is immutable");
// Immutability: cannot assign to module properties
var immProp = false;
try { mod_hello.newProp = 1; } catch (e) { immProp = true; }
assert(immProp == true, "module properties are immutable");
// Type should be module
assert(type(mod_hello) == "module", "module type tag");
print("basic imports: PASS");

View File

@ -1,21 +0,0 @@
print("\n--- Test: builtin imports ---");
import sys;
assert(type(sys) == "module", "sys is module");
from sys import memoryUsage as mem, platform, version;
var m = mem();
assert(type(m) == "number" || type(m) == "none", "memoryUsage returns number or none");
var plat = platform();
assert(type(plat) == "string", "platform returns string");
var ver = version();
assert(type(ver) == "string", "version returns string");
// OS functions are in os module now
import os;
var dir = os.getcwd();
assert(type(dir) == "string" || type(dir) == "none", "getcwd returns string/none");
print("builtin imports: PASS");

Some files were not shown because too many files have changed in this diff Show More