Compare commits
10 Commits
f70c6abd77
...
ec4b5afa9c
| Author | SHA1 | Date | |
|---|---|---|---|
| ec4b5afa9c | |||
| 6e3379b5b8 | |||
| 7f7c6e438d | |||
| 8cdccae214 | |||
| fc63c3e46f | |||
| 227586c583 | |||
| 3138f6fb92 | |||
| 7a9c0b7ea9 | |||
| 266cca5b42 | |||
| 85d3381575 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ build/
|
|||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build-ninja
|
build-ninja
|
||||||
|
build-release
|
||||||
|
|||||||
@ -45,16 +45,18 @@ elseif(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Collect source files
|
# Collect source files
|
||||||
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.cpp")
|
file(GLOB_RECURSE BOB_RUNTIME_SOURCES CONFIGURE_DEPENDS "src/sources/runtime/*.cpp")
|
||||||
file(GLOB_RECURSE BOB_PARSING_SOURCES "src/sources/parsing/*.cpp")
|
file(GLOB_RECURSE BOB_PARSING_SOURCES CONFIGURE_DEPENDS "src/sources/parsing/*.cpp")
|
||||||
file(GLOB_RECURSE BOB_STDLIB_SOURCES "src/sources/stdlib/*.cpp")
|
file(GLOB_RECURSE BOB_STDLIB_SOURCES CONFIGURE_DEPENDS "src/sources/stdlib/*.cpp")
|
||||||
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.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")
|
||||||
|
|
||||||
# All source files
|
# All source files
|
||||||
set(BOB_ALL_SOURCES
|
set(BOB_ALL_SOURCES
|
||||||
${BOB_RUNTIME_SOURCES}
|
${BOB_RUNTIME_SOURCES}
|
||||||
${BOB_PARSING_SOURCES}
|
${BOB_PARSING_SOURCES}
|
||||||
${BOB_STDLIB_SOURCES}
|
${BOB_STDLIB_SOURCES}
|
||||||
|
${BOB_BUILTIN_SOURCES}
|
||||||
${BOB_CLI_SOURCES}
|
${BOB_CLI_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,6 +68,7 @@ target_include_directories(bob PRIVATE
|
|||||||
src/headers/runtime
|
src/headers/runtime
|
||||||
src/headers/parsing
|
src/headers/parsing
|
||||||
src/headers/stdlib
|
src/headers/stdlib
|
||||||
|
src/headers/builtinModules
|
||||||
src/headers/cli
|
src/headers/cli
|
||||||
src/headers/common
|
src/headers/common
|
||||||
)
|
)
|
||||||
|
|||||||
@ -233,19 +233,26 @@ toBoolean(1); // true
|
|||||||
type(42); // "number"
|
type(42); // "number"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Arrays & Strings
|
### Arrays, Strings, and Dictionaries: Method style (preferred)
|
||||||
```go
|
```go
|
||||||
len([1, 2, 3]); // 3
|
[1, 2, 3].len(); // 3
|
||||||
len("hello"); // 5
|
"hello".len(); // 5
|
||||||
push(array, value); // Add to end
|
var a = [1, 2]; a.push(3); // a is now [1, 2, 3]
|
||||||
pop(array); // Remove from end
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dictionaries
|
Note: Global forms like `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)` have been removed. Use method style.
|
||||||
|
|
||||||
|
### Numbers
|
||||||
```go
|
```go
|
||||||
keys(dict); // Array of keys
|
toInt(3.9); // 3 (global)
|
||||||
values(dict); // Array of values
|
(3.9).toInt(); // 3 (method on number)
|
||||||
has(dict, "key"); // Check if key exists
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Utility
|
### Utility
|
||||||
@ -253,7 +260,7 @@ has(dict, "key"); // Check if key exists
|
|||||||
assert(condition, "message"); // Testing
|
assert(condition, "message"); // Testing
|
||||||
time(); // Current time in microseconds
|
time(); // Current time in microseconds
|
||||||
sleep(1.5); // Sleep for 1.5 seconds
|
sleep(1.5); // Sleep for 1.5 seconds
|
||||||
random(); // Random number 0-1
|
rand.random(); // Random number 0-1
|
||||||
eval("print('Hello');"); // Execute string as code
|
eval("print('Hello');"); // Execute string as code
|
||||||
exit(0); // Exit program
|
exit(0); // Exit program
|
||||||
```
|
```
|
||||||
@ -266,8 +273,93 @@ var lines = readLines("config.txt");
|
|||||||
var exists = fileExists("test.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
|
## 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 auto‑bind `this`; call via `obj.method(...)` when needed.
|
||||||
|
|
||||||
|
### Extensions (Built‑ins and Classes)
|
||||||
|
Extend existing types (including built‑ins) 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) → built‑in extensions (string/array/dict) → `any`.
|
||||||
|
- `this` is injected for property calls.
|
||||||
|
|
||||||
### String Interpolation
|
### String Interpolation
|
||||||
```go
|
```go
|
||||||
var name = "Alice";
|
var name = "Alice";
|
||||||
@ -349,7 +441,7 @@ var people = [
|
|||||||
{"name": "Bob", "age": 25}
|
{"name": "Bob", "age": 25}
|
||||||
];
|
];
|
||||||
|
|
||||||
for (var i = 0; i < len(people); i = i + 1) {
|
for (var i = 0; i < people.len(); i = i + 1) {
|
||||||
var person = people[i];
|
var person = people[i];
|
||||||
print(person["name"] + " is " + person["age"] + " years old");
|
print(person["name"] + " is " + person["age"] + " years old");
|
||||||
}
|
}
|
||||||
@ -360,17 +452,17 @@ for (var i = 0; i < len(people); i = i + 1) {
|
|||||||
var lines = readLines("data.txt");
|
var lines = readLines("data.txt");
|
||||||
var processed = [];
|
var processed = [];
|
||||||
|
|
||||||
for (var i = 0; i < len(lines); i = i + 1) {
|
for (var i = 0; i < lines.len(); i = i + 1) {
|
||||||
var line = lines[i];
|
var line = lines[i];
|
||||||
if (len(line) > 0) {
|
if (line.len() > 0) {
|
||||||
push(processed, "Processed: " + line);
|
processed.push("Processed: " + line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var output = "";
|
var output = "";
|
||||||
for (var i = 0; i < len(processed); i = i + 1) {
|
for (var i = 0; i < processed.len(); i = i + 1) {
|
||||||
output = output + processed[i];
|
output = output + processed[i];
|
||||||
if (i < len(processed) - 1) {
|
if (i < processed.len() - 1) {
|
||||||
output = output + "\n";
|
output = output + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
113
Reference/EMBEDDING.md
Normal file
113
Reference/EMBEDDING.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ Bob is a mature, working programming language with a modern architecture and com
|
|||||||
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
|
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
|
||||||
- **Testing**: `assert()` with custom error messages
|
- **Testing**: `assert()` with custom error messages
|
||||||
- **Timing**: `time()` (microsecond precision), `sleep()`
|
- **Timing**: `time()` (microsecond precision), `sleep()`
|
||||||
- **Utility**: `random()` (properly seeded), `eval()`, `exit()`
|
- **Utility**: `rand.random()` (properly seeded), `eval.eval()`, `sys.exit()`
|
||||||
- **Data Structure**: `len()`, `push()`, `pop()`, `keys()`, `values()`, `has()`
|
- **Data Structure**: `len()`, `push()`, `pop()`, `keys()`, `values()`, `has()`
|
||||||
- **File I/O**: `readFile()`, `writeFile()`, `readLines()`, `fileExists()`
|
- **File I/O**: `readFile()`, `writeFile()`, `readLines()`, `fileExists()`
|
||||||
|
|
||||||
|
|||||||
@ -17,11 +17,14 @@ This extension provides syntax highlighting and language support for the Bob pro
|
|||||||
- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return`
|
- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return`
|
||||||
- Variable declaration: `var`
|
- Variable declaration: `var`
|
||||||
- Function declaration: `func`
|
- Function declaration: `func`
|
||||||
|
- Classes and OOP: `class`, `extends`, `extension`, `this`, `super`
|
||||||
- Logical operators: `and`, `or`, `not`
|
- Logical operators: `and`, `or`, `not`
|
||||||
|
|
||||||
### Built-in Functions
|
### Built-in Functions
|
||||||
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `time()`
|
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `toInt()`, `time()`, `sleep()`, `printRaw()`
|
||||||
- `sleep()`, `printRaw()`, `len()`, `push()`, `pop()`, `random()`, `eval()`
|
- 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()`
|
||||||
|
|
||||||
### Data Types
|
### Data Types
|
||||||
- Numbers (integers, floats, binary `0b1010`, hex `0xFF`)
|
- Numbers (integers, floats, binary `0b1010`, hex `0xFF`)
|
||||||
@ -92,9 +95,11 @@ Files with the `.bob` extension will automatically be recognized as Bob language
|
|||||||
var message = "Hello, Bob!";
|
var message = "Hello, Bob!";
|
||||||
print(message);
|
print(message);
|
||||||
|
|
||||||
// Array operations
|
// Array operations (method style)
|
||||||
var numbers = [1, 2, 3, 4, 5];
|
var numbers = [1, 2, 3, 4, 5];
|
||||||
print("Array length: " + len(numbers));
|
print("Array length: " + numbers.len());
|
||||||
|
numbers.push(6);
|
||||||
|
print("Popped: " + numbers.pop());
|
||||||
print("First element: " + numbers[0]);
|
print("First element: " + numbers[0]);
|
||||||
|
|
||||||
// Function with ternary operator
|
// Function with ternary operator
|
||||||
|
|||||||
BIN
bob-language-extension/bob-language-0.5.0.vsix
Normal file
BIN
bob-language-extension/bob-language-0.5.0.vsix
Normal file
Binary file not shown.
@ -20,12 +20,12 @@ print("Pi: " + toString(pi));
|
|||||||
var numbers = [1, 2, 3, 4, 5];
|
var numbers = [1, 2, 3, 4, 5];
|
||||||
var fruits = ["apple", "banana", "cherry"];
|
var fruits = ["apple", "banana", "cherry"];
|
||||||
|
|
||||||
print("Array length: " + len(numbers));
|
print("Array length: " + numbers.len());
|
||||||
print("First element: " + numbers[0]);
|
print("First element: " + numbers[0]);
|
||||||
|
|
||||||
numbers[2] = 99; // Array assignment
|
numbers[2] = 99; // Array assignment
|
||||||
push(numbers, 6); // Add element
|
numbers.push(6); // Add element
|
||||||
var lastElement = pop(numbers); // Remove and get last element
|
var lastElement = numbers.pop(); // Remove and get last element
|
||||||
|
|
||||||
// Function definition
|
// Function definition
|
||||||
func factorial(n) {
|
func factorial(n) {
|
||||||
@ -91,7 +91,7 @@ value *= 2;
|
|||||||
value -= 3;
|
value -= 3;
|
||||||
|
|
||||||
// New built-in functions
|
// New built-in functions
|
||||||
var randomValue = random();
|
import rand; var randomValue = rand.random();
|
||||||
sleep(100); // Sleep for 100ms
|
sleep(100); // Sleep for 100ms
|
||||||
printRaw("No newline here");
|
printRaw("No newline here");
|
||||||
eval("print('Dynamic code execution!');");
|
eval("print('Dynamic code execution!');");
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
["'", "'"]
|
["'", "'"]
|
||||||
],
|
],
|
||||||
"indentationRules": {
|
"indentationRules": {
|
||||||
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for)\\b.*$",
|
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for|class|extension)\\b.*$",
|
||||||
"decreaseIndentPattern": "^\\s*[})]"
|
"decreaseIndentPattern": "^\\s*[})]"
|
||||||
},
|
},
|
||||||
"folding": {
|
"folding": {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "bob-language",
|
"name": "bob-language",
|
||||||
"displayName": "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",
|
"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.4.0",
|
"version": "0.5.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.60.0"
|
"vscode": "^1.60.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -53,6 +53,32 @@
|
|||||||
],
|
],
|
||||||
"description": "Declare a variable"
|
"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": {
|
"Print Statement": {
|
||||||
"prefix": "print",
|
"prefix": "print",
|
||||||
"body": [
|
"body": [
|
||||||
@ -274,7 +300,7 @@
|
|||||||
"Random Number": {
|
"Random Number": {
|
||||||
"prefix": "random",
|
"prefix": "random",
|
||||||
"body": [
|
"body": [
|
||||||
"var randomValue = random();"
|
"import rand; var randomValue = rand.random();"
|
||||||
],
|
],
|
||||||
"description": "Generate random number"
|
"description": "Generate random number"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -153,7 +153,7 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "keyword.control.bob",
|
"name": "keyword.control.bob",
|
||||||
"match": "\\b(if|else|while|do|for|break|continue|return|var|func)\\b"
|
"match": "\\b(if|else|while|do|for|break|continue|return|var|func|class|extends|extension|this|super)\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.bob",
|
"name": "keyword.operator.bob",
|
||||||
|
|||||||
13
bobby.bob
Normal file
13
bobby.bob
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class A {
|
||||||
|
var inner = 10;
|
||||||
|
|
||||||
|
func test(){
|
||||||
|
print(this.inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func hello(){
|
||||||
|
print("hello");
|
||||||
|
}
|
||||||
|
|
||||||
@ -9,14 +9,14 @@ print("Test 1: Heavy string operations");
|
|||||||
var stringData = [];
|
var stringData = [];
|
||||||
for (var i = 0; i < 100000; i++) {
|
for (var i = 0; i < 100000; i++) {
|
||||||
var str = toString(i) + "_" + toString(i * 2) + "_" + toString(i * 3);
|
var str = toString(i) + "_" + toString(i * 2) + "_" + toString(i * 3);
|
||||||
push(stringData, {
|
stringData.push({
|
||||||
"original": str,
|
"original": str,
|
||||||
"upper": str, // Bob doesn't have toUpper, but test string storage
|
"upper": str, // Bob doesn't have toUpper, but test string storage
|
||||||
"length": len(str),
|
"length": str.len(),
|
||||||
"type": type(str)
|
"type": type(str)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(stringData) + " string operation results");
|
print("Created " + stringData.len() + " string operation results");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear string data...");
|
input("Press Enter to clear string data...");
|
||||||
stringData = none;
|
stringData = none;
|
||||||
@ -33,12 +33,12 @@ for (var i = 0; i < 200000; i++) {
|
|||||||
var intVal = toInt(num);
|
var intVal = toInt(num);
|
||||||
var boolVal = toBoolean(i % 2);
|
var boolVal = toBoolean(i % 2);
|
||||||
|
|
||||||
push(conversions, [
|
conversions.push([
|
||||||
num, str, backToNum, intVal, boolVal,
|
num, str, backToNum, intVal, boolVal,
|
||||||
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
|
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
print("Created " + len(conversions) + " type conversion results");
|
print("Created " + conversions.len() + " type conversion results");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear conversions...");
|
input("Press Enter to clear conversions...");
|
||||||
conversions = [];
|
conversions = [];
|
||||||
@ -53,15 +53,15 @@ for (var i = 0; i < 50000; i++) {
|
|||||||
var dict = {"a": i, "b": i+1, "c": i+2};
|
var dict = {"a": i, "b": i+1, "c": i+2};
|
||||||
|
|
||||||
// Use builtin functions heavily
|
// Use builtin functions heavily
|
||||||
var arrLen = len(arr);
|
var arrLen = arr.len();
|
||||||
push(arr, i+3);
|
arr.push(i+3);
|
||||||
var popped = pop(arr);
|
var popped = arr.pop();
|
||||||
|
|
||||||
var dictKeys = keys(dict);
|
var dictKeys = dict.keys();
|
||||||
var dictValues = values(dict);
|
var dictValues = dict.values();
|
||||||
var hasA = has(dict, "a");
|
var hasA = dict.has("a");
|
||||||
|
|
||||||
push(collections, {
|
collections.push({
|
||||||
"array": arr,
|
"array": arr,
|
||||||
"dict": dict,
|
"dict": dict,
|
||||||
"arrLen": arrLen,
|
"arrLen": arrLen,
|
||||||
@ -71,7 +71,7 @@ for (var i = 0; i < 50000; i++) {
|
|||||||
"hasA": hasA
|
"hasA": hasA
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(collections) + " collection operation results");
|
print("Created " + collections.len() + " collection operation results");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear collections...");
|
input("Press Enter to clear collections...");
|
||||||
collections = "cleared";
|
collections = "cleared";
|
||||||
@ -88,14 +88,14 @@ for (var i = 0; i < 10000; i++) {
|
|||||||
var funcExpr = "func() { return " + toString(i) + "; };";
|
var funcExpr = "func() { return " + toString(i) + "; };";
|
||||||
var evalFunc = eval(funcExpr);
|
var evalFunc = eval(funcExpr);
|
||||||
|
|
||||||
push(evalResults, {
|
evalResults.push({
|
||||||
"expr": expression,
|
"expr": expression,
|
||||||
"result": result,
|
"result": result,
|
||||||
"func": evalFunc,
|
"func": evalFunc,
|
||||||
"funcResult": evalFunc()
|
"funcResult": evalFunc()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(evalResults) + " eval results");
|
print("Created " + evalResults.len() + " eval results");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear eval results...");
|
input("Press Enter to clear eval results...");
|
||||||
evalResults = none;
|
evalResults = none;
|
||||||
@ -121,15 +121,15 @@ for (var i = 0; i < 1000; i++) {
|
|||||||
var readContent = readFile(filename);
|
var readContent = readFile(filename);
|
||||||
var lines = readLines(filename);
|
var lines = readLines(filename);
|
||||||
|
|
||||||
push(fileData, {
|
fileData.push({
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
"exists": exists,
|
"exists": exists,
|
||||||
"content": readContent,
|
"content": readContent,
|
||||||
"lines": lines,
|
"lines": lines,
|
||||||
"lineCount": len(lines)
|
"lineCount": lines.len()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(fileData) + " file operation results");
|
print("Created " + fileData.len() + " file operation results");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear file data...");
|
input("Press Enter to clear file data...");
|
||||||
fileData = [];
|
fileData = [];
|
||||||
@ -151,18 +151,19 @@ input("File data cleared. Check memory usage...");
|
|||||||
print("Test 6: Random number stress");
|
print("Test 6: Random number stress");
|
||||||
var randomData = [];
|
var randomData = [];
|
||||||
for (var i = 0; i < 200000; i++) {
|
for (var i = 0; i < 200000; i++) {
|
||||||
var rand1 = random();
|
import rand as RLeak;
|
||||||
var rand2 = random();
|
var rand1 = RLeak.random();
|
||||||
|
var rand2 = RLeak.random();
|
||||||
var sum = rand1 + rand2;
|
var sum = rand1 + rand2;
|
||||||
|
|
||||||
push(randomData, {
|
randomData.push({
|
||||||
"rand1": rand1,
|
"rand1": rand1,
|
||||||
"rand2": rand2,
|
"rand2": rand2,
|
||||||
"sum": sum,
|
"sum": sum,
|
||||||
"product": rand1 * rand2
|
"product": rand1 * rand2
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(randomData) + " random number results");
|
print("Created " + randomData.len() + " random number results");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear random data...");
|
input("Press Enter to clear random data...");
|
||||||
randomData = none;
|
randomData = none;
|
||||||
|
|||||||
@ -8,13 +8,13 @@ print("Initial memory: " + memoryUsage() + " MB");
|
|||||||
print("Test 1: Large nested arrays");
|
print("Test 1: Large nested arrays");
|
||||||
var nestedArrays = [];
|
var nestedArrays = [];
|
||||||
for (var i = 0; i < 50000; i++) {
|
for (var i = 0; i < 50000; i++) {
|
||||||
push(nestedArrays, [
|
nestedArrays.push([
|
||||||
[i, i+1, i+2],
|
[i, i+1, i+2],
|
||||||
[i*2, i*3, i*4],
|
[i*2, i*3, i*4],
|
||||||
[[i, [i+1, [i+2]]], i*5]
|
[[i, [i+1, [i+2]]], i*5]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
print("Created " + len(nestedArrays) + " nested array structures");
|
print("Created " + nestedArrays.len() + " nested array structures");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear nested arrays...");
|
input("Press Enter to clear nested arrays...");
|
||||||
nestedArrays = none;
|
nestedArrays = none;
|
||||||
@ -25,7 +25,7 @@ input("Cleared. Check memory usage...");
|
|||||||
print("Test 2: Large nested dictionaries");
|
print("Test 2: Large nested dictionaries");
|
||||||
var nestedDicts = [];
|
var nestedDicts = [];
|
||||||
for (var i = 0; i < 50000; i++) {
|
for (var i = 0; i < 50000; i++) {
|
||||||
push(nestedDicts, {
|
nestedDicts.push({
|
||||||
"id": i,
|
"id": i,
|
||||||
"data": {
|
"data": {
|
||||||
"value": i * 2,
|
"value": i * 2,
|
||||||
@ -42,7 +42,7 @@ for (var i = 0; i < 50000; i++) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(nestedDicts) + " nested dictionary structures");
|
print("Created " + nestedDicts.len() + " nested dictionary structures");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear nested dicts...");
|
input("Press Enter to clear nested dicts...");
|
||||||
nestedDicts = [];
|
nestedDicts = [];
|
||||||
@ -53,7 +53,7 @@ input("Cleared. Check memory usage...");
|
|||||||
print("Test 3: Mixed array/dict structures");
|
print("Test 3: Mixed array/dict structures");
|
||||||
var mixedStructures = [];
|
var mixedStructures = [];
|
||||||
for (var i = 0; i < 30000; i++) {
|
for (var i = 0; i < 30000; i++) {
|
||||||
push(mixedStructures, [
|
mixedStructures.push([
|
||||||
{"arrays": [[i, i+1], [i+2, i+3]]},
|
{"arrays": [[i, i+1], [i+2, i+3]]},
|
||||||
[{"dicts": {"a": i, "b": i+1}}, {"more": [i, i+1]}],
|
[{"dicts": {"a": i, "b": i+1}}, {"more": [i, i+1]}],
|
||||||
{
|
{
|
||||||
@ -64,7 +64,7 @@ for (var i = 0; i < 30000; i++) {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
print("Created " + len(mixedStructures) + " mixed structures");
|
print("Created " + mixedStructures.len() + " mixed structures");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear mixed structures...");
|
input("Press Enter to clear mixed structures...");
|
||||||
mixedStructures = "cleared";
|
mixedStructures = "cleared";
|
||||||
@ -78,11 +78,15 @@ for (var i = 0; i < 1000000; i++) {
|
|||||||
var item = {"id": i, "value": i * 2};
|
var item = {"id": i, "value": i * 2};
|
||||||
// Create a structure that references itself
|
// Create a structure that references itself
|
||||||
item["self"] = [item, {"parent": item}];
|
item["self"] = [item, {"parent": item}];
|
||||||
push(selfRef, item);
|
selfRef.push(item);
|
||||||
}
|
}
|
||||||
print("Created " + len(selfRef) + " self-referencing structures");
|
print("Created " + selfRef.len() + " self-referencing structures");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear self-ref structures...");
|
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;
|
selfRef = 123;
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
input("Cleared. Check memory usage...");
|
input("Cleared. Check memory usage...");
|
||||||
@ -95,12 +99,12 @@ for (var i = 0; i < 100000; i++) {
|
|||||||
for (var j = 0; j < 100; j++) {
|
for (var j = 0; j < 100; j++) {
|
||||||
longString = longString + "data" + i + "_" + j + " ";
|
longString = longString + "data" + i + "_" + j + " ";
|
||||||
}
|
}
|
||||||
push(stringCollections, {
|
stringCollections.push({
|
||||||
"content": longString,
|
"content": longString,
|
||||||
"words": [longString, longString + "_copy", longString + "_backup"]
|
"words": [longString, longString + "_copy", longString + "_backup"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(stringCollections) + " string collections");
|
print("Created " + stringCollections.len() + " string collections");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear string collections...");
|
input("Press Enter to clear string collections...");
|
||||||
stringCollections = none;
|
stringCollections = none;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ print("Initial memory: " + memoryUsage() + " MB");
|
|||||||
print("Test 1: Recursive function closures");
|
print("Test 1: Recursive function closures");
|
||||||
var recursiveFuncs = [];
|
var recursiveFuncs = [];
|
||||||
for (var i = 0; i < 100000; i++) {
|
for (var i = 0; i < 100000; i++) {
|
||||||
push(recursiveFuncs, func() {
|
recursiveFuncs.push(func() {
|
||||||
var capturedI = i;
|
var capturedI = i;
|
||||||
return func() {
|
return func() {
|
||||||
if (capturedI > 0) {
|
if (capturedI > 0) {
|
||||||
@ -18,7 +18,7 @@ for (var i = 0; i < 100000; i++) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(recursiveFuncs) + " recursive closure functions");
|
print("Created " + recursiveFuncs.len() + " recursive closure functions");
|
||||||
print("Memory after creation: " + memoryUsage() + " MB");
|
print("Memory after creation: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear recursive functions...");
|
input("Press Enter to clear recursive functions...");
|
||||||
recursiveFuncs = none;
|
recursiveFuncs = none;
|
||||||
@ -29,14 +29,14 @@ input("Cleared. Check memory usage...");
|
|||||||
print("Test 2: Function factories");
|
print("Test 2: Function factories");
|
||||||
var factories = [];
|
var factories = [];
|
||||||
for (var i = 0; i < 100000; i++) {
|
for (var i = 0; i < 100000; i++) {
|
||||||
push(factories, func() {
|
factories.push(func() {
|
||||||
var multiplier = i;
|
var multiplier = i;
|
||||||
return func(x) {
|
return func(x) {
|
||||||
return x * multiplier;
|
return x * multiplier;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(factories) + " function factories");
|
print("Created " + factories.len() + " function factories");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear factories...");
|
input("Press Enter to clear factories...");
|
||||||
factories = [];
|
factories = [];
|
||||||
@ -47,7 +47,7 @@ input("Cleared. Check memory usage...");
|
|||||||
print("Test 3: Deep function nesting");
|
print("Test 3: Deep function nesting");
|
||||||
var deepNested = [];
|
var deepNested = [];
|
||||||
for (var i = 0; i < 50000; i++) {
|
for (var i = 0; i < 50000; i++) {
|
||||||
push(deepNested, func() {
|
deepNested.push(func() {
|
||||||
return func() {
|
return func() {
|
||||||
return func() {
|
return func() {
|
||||||
return func() {
|
return func() {
|
||||||
@ -59,7 +59,7 @@ for (var i = 0; i < 50000; i++) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(deepNested) + " deeply nested functions");
|
print("Created " + deepNested.len() + " deeply nested functions");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear deep nested...");
|
input("Press Enter to clear deep nested...");
|
||||||
deepNested = "test";
|
deepNested = "test";
|
||||||
@ -76,10 +76,10 @@ for (var i = 0; i < 1000000; i++) {
|
|||||||
var funcB = func() {
|
var funcB = func() {
|
||||||
return "B" + i;
|
return "B" + i;
|
||||||
};
|
};
|
||||||
// Store both in same array element to create potential circular refs
|
// Store both in same array element to create potential circular refs
|
||||||
push(circularFuncs, [funcA, funcB, func() { return funcA; }]);
|
circularFuncs.push([funcA, funcB, func() { return funcA; }]);
|
||||||
}
|
}
|
||||||
print("Created " + len(circularFuncs) + " circular function structures");
|
print("Created " + circularFuncs.len() + " circular function structures");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear circular functions...");
|
input("Press Enter to clear circular functions...");
|
||||||
circularFuncs = 42;
|
circularFuncs = 42;
|
||||||
|
|||||||
@ -10,16 +10,16 @@ var nestedData = [];
|
|||||||
for (var i = 0; i < 1000; i++) {
|
for (var i = 0; i < 1000; i++) {
|
||||||
var row = [];
|
var row = [];
|
||||||
for (var j = 0; j < 1000; j++) {
|
for (var j = 0; j < 1000; j++) {
|
||||||
push(row, {
|
row.push({
|
||||||
"i": i,
|
"i": i,
|
||||||
"j": j,
|
"j": j,
|
||||||
"func": func() { return i * j; },
|
"func": func() { return i * j; },
|
||||||
"data": [i, j, i+j]
|
"data": [i, j, i+j]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
push(nestedData, row);
|
nestedData.push(row);
|
||||||
}
|
}
|
||||||
print("Created " + len(nestedData) + "x" + len(nestedData[0]) + " nested structure");
|
print("Created " + nestedData.len() + "x" + nestedData[0].len() + " nested structure");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear nested data...");
|
input("Press Enter to clear nested data...");
|
||||||
nestedData = none;
|
nestedData = none;
|
||||||
@ -31,14 +31,14 @@ print("Test 2: While loop accumulation");
|
|||||||
var accumulator = [];
|
var accumulator = [];
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
while (counter < 500000) {
|
while (counter < 500000) {
|
||||||
push(accumulator, {
|
accumulator.push({
|
||||||
"count": counter,
|
"count": counter,
|
||||||
"func": func() { return counter * 2; },
|
"func": func() { return counter * 2; },
|
||||||
"meta": ["item" + counter, counter % 100]
|
"meta": ["item" + counter, counter % 100]
|
||||||
});
|
});
|
||||||
counter = counter + 1;
|
counter = counter + 1;
|
||||||
}
|
}
|
||||||
print("Accumulated " + len(accumulator) + " items in while loop");
|
print("Accumulated " + accumulator.len() + " items in while loop");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear accumulator...");
|
input("Press Enter to clear accumulator...");
|
||||||
accumulator = [];
|
accumulator = [];
|
||||||
@ -72,7 +72,7 @@ print("Test 4: Do-while function creation");
|
|||||||
var doWhileFuncs = [];
|
var doWhileFuncs = [];
|
||||||
var dwCounter = 0;
|
var dwCounter = 0;
|
||||||
do {
|
do {
|
||||||
push(doWhileFuncs, func() {
|
doWhileFuncs.push(func() {
|
||||||
var captured = dwCounter;
|
var captured = dwCounter;
|
||||||
return func() {
|
return func() {
|
||||||
return captured * captured;
|
return captured * captured;
|
||||||
@ -80,7 +80,7 @@ do {
|
|||||||
});
|
});
|
||||||
dwCounter = dwCounter + 1;
|
dwCounter = dwCounter + 1;
|
||||||
} while (dwCounter < 100000);
|
} while (dwCounter < 100000);
|
||||||
print("Created " + len(doWhileFuncs) + " functions in do-while");
|
print("Created " + doWhileFuncs.len() + " functions in do-while");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear do-while functions...");
|
input("Press Enter to clear do-while functions...");
|
||||||
doWhileFuncs = "cleared";
|
doWhileFuncs = "cleared";
|
||||||
@ -97,7 +97,7 @@ for (var i = 0; i < 500000; i++) {
|
|||||||
|
|
||||||
if (i > 400000 && i % 100 == 0) {
|
if (i > 400000 && i % 100 == 0) {
|
||||||
// Create larger objects near the end
|
// Create larger objects near the end
|
||||||
push(complexData, {
|
complexData.push({
|
||||||
"large": [
|
"large": [
|
||||||
func() { return i; },
|
func() { return i; },
|
||||||
[i, i+1, i+2, i+3],
|
[i, i+1, i+2, i+3],
|
||||||
@ -105,14 +105,14 @@ for (var i = 0; i < 500000; i++) {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
push(complexData, func() { return i; });
|
complexData.push(func() { return i; });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i > 450000 && len(complexData) > 350000) {
|
if (i > 450000 && complexData.len() > 350000) {
|
||||||
break; // Early exit
|
break; // Early exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print("Complex loop created " + len(complexData) + " items");
|
print("Complex loop created " + complexData.len() + " items");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear complex data...");
|
input("Press Enter to clear complex data...");
|
||||||
complexData = none;
|
complexData = none;
|
||||||
@ -126,7 +126,7 @@ for (var cycle = 0; cycle < 100; cycle++) {
|
|||||||
|
|
||||||
// Create lots of data
|
// Create lots of data
|
||||||
for (var i = 0; i < 10000; i++) {
|
for (var i = 0; i < 10000; i++) {
|
||||||
push(churnData, [
|
churnData.push([
|
||||||
func() { return i + cycle; },
|
func() { return i + cycle; },
|
||||||
{"cycle": cycle, "item": i}
|
{"cycle": cycle, "item": i}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -11,16 +11,16 @@ for (var i = 0; i < 50000; i++) {
|
|||||||
var capturedArray = [i, i+1, i+2, "data" + i];
|
var capturedArray = [i, i+1, i+2, "data" + i];
|
||||||
var capturedDict = {"id": i, "values": [i*2, i*3]};
|
var capturedDict = {"id": i, "values": [i*2, i*3]};
|
||||||
|
|
||||||
push(funcWithCollections, func() {
|
funcWithCollections.push(func() {
|
||||||
// Capture both collections
|
// Capture both collections
|
||||||
var localArray = capturedArray;
|
var localArray = capturedArray;
|
||||||
var localDict = capturedDict;
|
var localDict = capturedDict;
|
||||||
return func() {
|
return func() {
|
||||||
return len(localArray) + len(localDict);
|
return localArray.len() + localDict.len();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print("Created " + len(funcWithCollections) + " functions with collection captures");
|
print("Created " + funcWithCollections.len() + " functions with collection captures");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear function collections...");
|
input("Press Enter to clear function collections...");
|
||||||
funcWithCollections = [];
|
funcWithCollections = [];
|
||||||
@ -31,7 +31,7 @@ input("Cleared. Check memory usage...");
|
|||||||
print("Test 2: Collections with mixed content");
|
print("Test 2: Collections with mixed content");
|
||||||
var mixedContent = [];
|
var mixedContent = [];
|
||||||
for (var i = 0; i < 30000; i++) {
|
for (var i = 0; i < 30000; i++) {
|
||||||
push(mixedContent, [
|
mixedContent.push([
|
||||||
func() { return i; }, // Function
|
func() { return i; }, // Function
|
||||||
[i, i+1, func() { return i*2; }], // Array with function
|
[i, i+1, func() { return i*2; }], // Array with function
|
||||||
{"value": i, "func": func() { return i*3; }}, // Dict 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
|
i % 2 == 0 // Boolean
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
print("Created " + len(mixedContent) + " mixed content collections");
|
print("Created " + mixedContent.len() + " mixed content collections");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear mixed content...");
|
input("Press Enter to clear mixed content...");
|
||||||
mixedContent = none;
|
mixedContent = none;
|
||||||
@ -58,9 +58,9 @@ for (var i = 0; i < 100000; i++) {
|
|||||||
obj["array"] = [1, 2, 3];
|
obj["array"] = [1, 2, 3];
|
||||||
obj["array"][0] = func() { return i * 2; };
|
obj["array"][0] = func() { return i * 2; };
|
||||||
|
|
||||||
push(propObjects, obj);
|
propObjects.push(obj);
|
||||||
}
|
}
|
||||||
print("Created " + len(propObjects) + " objects with dynamic properties");
|
print("Created " + propObjects.len() + " objects with dynamic properties");
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
input("Press Enter to clear property objects...");
|
input("Press Enter to clear property objects...");
|
||||||
propObjects = "cleared";
|
propObjects = "cleared";
|
||||||
@ -71,20 +71,20 @@ input("Cleared. Check memory usage...");
|
|||||||
print("Test 4: Type reassignment chains");
|
print("Test 4: Type reassignment chains");
|
||||||
var typeChains = [];
|
var typeChains = [];
|
||||||
for (var i = 0; i < 200000; i++) {
|
for (var i = 0; i < 200000; i++) {
|
||||||
push(typeChains, i);
|
typeChains.push(i);
|
||||||
}
|
}
|
||||||
print("Memory after number array: " + memoryUsage() + " MB");
|
print("Memory after number array: " + memoryUsage() + " MB");
|
||||||
input("Created number array. Press Enter to convert to functions...");
|
input("Created number array. Press Enter to convert to functions...");
|
||||||
|
|
||||||
// Reassign all elements to functions
|
// Reassign all elements to functions
|
||||||
for (var i = 0; i < len(typeChains); i++) {
|
for (var i = 0; i < typeChains.len(); i++) {
|
||||||
typeChains[i] = func() { return i; };
|
typeChains[i] = func() { return i; };
|
||||||
}
|
}
|
||||||
print("Memory after function conversion: " + memoryUsage() + " MB");
|
print("Memory after function conversion: " + memoryUsage() + " MB");
|
||||||
input("Converted to functions. Press Enter to convert to dicts...");
|
input("Converted to functions. Press Enter to convert to dicts...");
|
||||||
|
|
||||||
// Reassign all elements to dicts
|
// Reassign all elements to dicts
|
||||||
for (var i = 0; i < len(typeChains); i++) {
|
for (var i = 0; i < typeChains.len(); i++) {
|
||||||
typeChains[i] = {"id": i, "func": func() { return i; }};
|
typeChains[i] = {"id": i, "func": func() { return i; }};
|
||||||
}
|
}
|
||||||
print("Memory after dict conversion: " + memoryUsage() + " MB");
|
print("Memory after dict conversion: " + memoryUsage() + " MB");
|
||||||
@ -98,12 +98,12 @@ print("Test 5: Rapid allocation/deallocation");
|
|||||||
for (var round = 0; round < 10; round++) {
|
for (var round = 0; round < 10; round++) {
|
||||||
var temp = [];
|
var temp = [];
|
||||||
for (var i = 0; i < 100000; i++) {
|
for (var i = 0; i < 100000; i++) {
|
||||||
push(temp, [
|
temp.push([
|
||||||
func() { return i; },
|
func() { return i; },
|
||||||
{"data": [i, i+1, func() { return i*2; }]}
|
{"data": [i, i+1, func() { return i*2; }]}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
print("Round " + round + ": Created " + len(temp) + " items, Memory: " + memoryUsage() + " MB");
|
print("Round " + round + ": Created " + temp.len() + " items, Memory: " + memoryUsage() + " MB");
|
||||||
temp = none; // Clear immediately
|
temp = none; // Clear immediately
|
||||||
if (round % 2 == 1) print("After clear round " + round + ": " + memoryUsage() + " MB");
|
if (round % 2 == 1) print("After clear round " + round + ": " + memoryUsage() + " MB");
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/headers/builtinModules/base64_module.h
Normal file
8
src/headers/builtinModules/base64_module.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'base64' module
|
||||||
|
void registerBase64Module(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/eval.h
Normal file
8
src/headers/builtinModules/eval.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'eval' module providing eval(code) and evalFile(path)
|
||||||
|
void registerEvalModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/io.h
Normal file
8
src/headers/builtinModules/io.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'io' module (file I/O and input)
|
||||||
|
void registerIoModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/json.h
Normal file
8
src/headers/builtinModules/json.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'json' module
|
||||||
|
void registerJsonModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/math_module.h
Normal file
8
src/headers/builtinModules/math_module.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'math' module
|
||||||
|
void registerMathModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/os.h
Normal file
8
src/headers/builtinModules/os.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'os' module
|
||||||
|
void registerOsModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/path_module.h
Normal file
8
src/headers/builtinModules/path_module.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'path' module (path utilities)
|
||||||
|
void registerPathModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/rand.h
Normal file
8
src/headers/builtinModules/rand.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'rand' module
|
||||||
|
void registerRandModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/register.h
Normal file
8
src/headers/builtinModules/register.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Registers all builtin modules with the interpreter
|
||||||
|
void registerAllBuiltinModules(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/sys.h
Normal file
8
src/headers/builtinModules/sys.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'sys' module
|
||||||
|
void registerSysModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/time_module.h
Normal file
8
src/headers/builtinModules/time_module.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'time' module (time functions)
|
||||||
|
void registerTimeModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
@ -5,6 +5,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include "Lexer.h"
|
#include "Lexer.h"
|
||||||
#include "Interpreter.h"
|
#include "Interpreter.h"
|
||||||
|
#include "ModuleRegistry.h"
|
||||||
#include "helperFunctions/ShortHands.h"
|
#include "helperFunctions/ShortHands.h"
|
||||||
#include "ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
@ -20,10 +21,105 @@ public:
|
|||||||
~Bob() = default;
|
~Bob() = default;
|
||||||
|
|
||||||
public:
|
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 runFile(const std::string& path);
|
||||||
void runPrompt();
|
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:
|
private:
|
||||||
void run(std::string source);
|
void ensureInterpreter(bool interactive);
|
||||||
|
void applyPendingConfigs() {
|
||||||
|
if (!interpreter) return;
|
||||||
|
for (auto& f : pendingConfigurators) { f(*interpreter); }
|
||||||
|
pendingConfigurators.clear();
|
||||||
|
}
|
||||||
|
std::vector<std::function<void(Interpreter&)>> pendingConfigurators;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
|
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
@ -56,9 +57,9 @@ inline bool isHexDigit(char c) {
|
|||||||
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
|
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline u_long binaryStringToLong(const std::string& binaryString) {
|
inline unsigned long long binaryStringToLong(const std::string& binaryString) {
|
||||||
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
|
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
|
||||||
u_long result = 0;
|
unsigned long long result = 0;
|
||||||
for (char ch : binaryDigits) {
|
for (char ch : binaryDigits) {
|
||||||
result <<= 1;
|
result <<= 1;
|
||||||
result += (ch - '0');
|
result += (ch - '0');
|
||||||
|
|||||||
@ -34,6 +34,9 @@ private:
|
|||||||
std::string currentFileName;
|
std::string currentFileName;
|
||||||
std::vector<std::string> callStack;
|
std::vector<std::string> callStack;
|
||||||
bool hadError = false;
|
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:
|
public:
|
||||||
ErrorReporter() = default;
|
ErrorReporter() = default;
|
||||||
@ -58,6 +61,11 @@ public:
|
|||||||
void pushCallStack(const std::string& functionName);
|
void pushCallStack(const std::string& functionName);
|
||||||
void popCallStack();
|
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:
|
private:
|
||||||
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
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);
|
void displayCallStack(const std::vector<std::string>& callStack);
|
||||||
|
|||||||
@ -22,10 +22,12 @@ enum TokenType{
|
|||||||
// Increment/decrement operators
|
// Increment/decrement operators
|
||||||
PLUS_PLUS, MINUS_MINUS,
|
PLUS_PLUS, MINUS_MINUS,
|
||||||
|
|
||||||
IDENTIFIER, STRING, NUMBER, BOOL,
|
IDENTIFIER, STRING, NUMBER, KW_BOOL,
|
||||||
|
|
||||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||||
WHILE, DO, VAR, CLASS, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
||||||
|
IMPORT, FROM, AS,
|
||||||
|
TRY, CATCH, FINALLY, THROW,
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||||
@ -51,10 +53,12 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
|||||||
|
|
||||||
"PLUS_PLUS", "MINUS_MINUS",
|
"PLUS_PLUS", "MINUS_MINUS",
|
||||||
|
|
||||||
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
"IDENTIFIER", "STRING", "NUMBER", "KW_BOOL",
|
||||||
|
|
||||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||||
"WHILE", "DO", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
||||||
|
"IMPORT", "FROM", "AS",
|
||||||
|
"TRY", "CATCH", "FINALLY", "THROW",
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||||
@ -77,12 +81,21 @@ const std::map<std::string, TokenType> KEYWORDS {
|
|||||||
{"do", DO},
|
{"do", DO},
|
||||||
{"var", VAR},
|
{"var", VAR},
|
||||||
{"class", CLASS},
|
{"class", CLASS},
|
||||||
|
{"extends", EXTENDS},
|
||||||
|
{"extension", EXTENSION},
|
||||||
{"super", SUPER},
|
{"super", SUPER},
|
||||||
{"this", THIS},
|
{"this", THIS},
|
||||||
{"none", NONE},
|
{"none", NONE},
|
||||||
{"return", RETURN},
|
{"return", RETURN},
|
||||||
{"break", BREAK},
|
{"break", BREAK},
|
||||||
{"continue", CONTINUE},
|
{"continue", CONTINUE},
|
||||||
|
{"import", IMPORT},
|
||||||
|
{"from", FROM},
|
||||||
|
{"as", AS},
|
||||||
|
{"try", TRY},
|
||||||
|
{"catch", CATCH},
|
||||||
|
{"finally", FINALLY},
|
||||||
|
{"throw", THROW},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Token
|
struct Token
|
||||||
|
|||||||
@ -68,6 +68,12 @@ private:
|
|||||||
std::shared_ptr<Stmt> continueStatement();
|
std::shared_ptr<Stmt> continueStatement();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> declaration();
|
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();
|
std::shared_ptr<Stmt> varDeclaration();
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,8 @@ struct ForStmt;
|
|||||||
struct BreakStmt;
|
struct BreakStmt;
|
||||||
struct ContinueStmt;
|
struct ContinueStmt;
|
||||||
struct AssignStmt;
|
struct AssignStmt;
|
||||||
|
struct ClassStmt;
|
||||||
|
struct ExtensionStmt;
|
||||||
|
|
||||||
#include "ExecutionContext.h"
|
#include "ExecutionContext.h"
|
||||||
|
|
||||||
@ -34,6 +36,12 @@ struct StmtVisitor
|
|||||||
virtual void visitBreakStmt(const std::shared_ptr<BreakStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
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 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 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>
|
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||||
@ -43,6 +51,39 @@ struct Stmt : public std::enable_shared_from_this<Stmt>
|
|||||||
virtual ~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
|
struct BlockStmt : Stmt
|
||||||
{
|
{
|
||||||
std::vector<std::shared_ptr<Stmt>> statements;
|
std::vector<std::shared_ptr<Stmt>> statements;
|
||||||
@ -210,3 +251,55 @@ struct AssignStmt : Stmt
|
|||||||
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
35
src/headers/runtime/AssignmentUtils.h
Normal file
35
src/headers/runtime/AssignmentUtils.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3,6 +3,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
#include "Value.h"
|
#include "Value.h"
|
||||||
#include "Lexer.h"
|
#include "Lexer.h"
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ public:
|
|||||||
void setErrorReporter(ErrorReporter* reporter) {
|
void setErrorReporter(ErrorReporter* reporter) {
|
||||||
errorReporter = reporter;
|
errorReporter = reporter;
|
||||||
}
|
}
|
||||||
|
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
||||||
|
|
||||||
// Optimized define with inline
|
// Optimized define with inline
|
||||||
inline void define(const std::string& name, const Value& value) {
|
inline void define(const std::string& name, const Value& value) {
|
||||||
@ -41,23 +43,18 @@ public:
|
|||||||
// Enhanced get with error reporting
|
// Enhanced get with error reporting
|
||||||
Value get(const Token& name);
|
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
|
// Prune heavy containers in a snapshot to avoid capture cycles
|
||||||
void pruneForClosureCapture();
|
void pruneForClosureCapture();
|
||||||
|
|
||||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||||
inline void clear() { variables.clear(); }
|
// Export all variables (shallow copy) for module namespace
|
||||||
|
std::unordered_map<std::string, Value> getAll() const { return variables; }
|
||||||
|
|
||||||
// Set parent environment for TCO environment reuse
|
|
||||||
inline void setParent(std::shared_ptr<Environment> newParent) {
|
|
||||||
parent = newParent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, Value> variables;
|
std::unordered_map<std::string, Value> variables;
|
||||||
std::shared_ptr<Environment> parent;
|
std::shared_ptr<Environment> parent;
|
||||||
ErrorReporter* errorReporter;
|
ErrorReporter* errorReporter;
|
||||||
|
std::unordered_set<std::string> constBindings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,4 +7,8 @@ struct ExecutionContext {
|
|||||||
Value returnValue = NONE_VALUE;
|
Value returnValue = NONE_VALUE;
|
||||||
bool shouldBreak = false;
|
bool shouldBreak = false;
|
||||||
bool shouldContinue = false;
|
bool shouldContinue = false;
|
||||||
|
bool hasThrow = false;
|
||||||
|
Value thrownValue = NONE_VALUE;
|
||||||
|
int throwLine = 0;
|
||||||
|
int throwColumn = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,6 +38,12 @@ public:
|
|||||||
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
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 visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& 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:
|
private:
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
||||||
|
|||||||
@ -1,15 +1,4 @@
|
|||||||
#pragma once
|
#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 <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -17,6 +6,24 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <functional>
|
#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
|
// Forward declaration
|
||||||
class Evaluator;
|
class Evaluator;
|
||||||
|
|
||||||
@ -62,17 +69,41 @@ private:
|
|||||||
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
|
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
|
||||||
std::vector<std::shared_ptr<Function>> functions;
|
std::vector<std::shared_ptr<Function>> functions;
|
||||||
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
|
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;
|
ErrorReporter* errorReporter;
|
||||||
bool inThunkExecution = false;
|
bool inThunkExecution = false;
|
||||||
|
|
||||||
// Automatic cleanup tracking
|
// Automatic cleanup tracking
|
||||||
int functionCreationCount = 0;
|
|
||||||
int thunkCreationCount = 0;
|
int thunkCreationCount = 0;
|
||||||
static const int CLEANUP_THRESHOLD = 1000000;
|
static const int CLEANUP_THRESHOLD = 10000;
|
||||||
|
|
||||||
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
||||||
std::unique_ptr<Evaluator> evaluator;
|
std::unique_ptr<Evaluator> evaluator;
|
||||||
std::unique_ptr<Executor> executor;
|
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:
|
public:
|
||||||
explicit Interpreter(bool isInteractive);
|
explicit Interpreter(bool isInteractive);
|
||||||
@ -93,25 +124,75 @@ public:
|
|||||||
bool isInteractiveMode() const;
|
bool isInteractiveMode() const;
|
||||||
std::shared_ptr<Environment> getEnvironment();
|
std::shared_ptr<Environment> getEnvironment();
|
||||||
void setEnvironment(std::shared_ptr<Environment> env);
|
void setEnvironment(std::shared_ptr<Environment> env);
|
||||||
void addThunk(std::shared_ptr<Thunk> thunk);
|
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
||||||
|
|
||||||
void addFunction(std::shared_ptr<Function> function);
|
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 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 addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
||||||
void cleanupUnusedFunctions();
|
void cleanupUnusedFunctions();
|
||||||
void cleanupUnusedThunks();
|
void cleanupUnusedThunks();
|
||||||
void forceCleanup();
|
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:
|
private:
|
||||||
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
|
|
||||||
void addStdLibFunctions();
|
|
||||||
Value runTrampoline(Value initialResult);
|
Value runTrampoline(Value initialResult);
|
||||||
|
// Stored argv/executable for sys module
|
||||||
|
std::vector<std::string> argvData;
|
||||||
|
std::string executableFile;
|
||||||
};
|
};
|
||||||
|
|||||||
14
src/headers/runtime/ModuleDef.h
Normal file
14
src/headers/runtime/ModuleDef.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
70
src/headers/runtime/ModuleRegistry.h
Normal file
70
src/headers/runtime/ModuleRegistry.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -28,8 +28,6 @@ public:
|
|||||||
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
|
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
|
||||||
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions);
|
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions);
|
||||||
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
|
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,
|
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
||||||
std::vector<std::shared_ptr<Function>>& functions,
|
std::vector<std::shared_ptr<Function>>& functions,
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks);
|
std::vector<std::shared_ptr<Thunk>>& thunks);
|
||||||
|
|||||||
@ -10,51 +10,22 @@
|
|||||||
struct Stmt;
|
struct Stmt;
|
||||||
struct Environment;
|
struct Environment;
|
||||||
|
|
||||||
struct Object
|
struct Function
|
||||||
{
|
|
||||||
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::string name;
|
||||||
const std::vector<std::string> params;
|
const std::vector<std::string> params;
|
||||||
const std::vector<std::shared_ptr<Stmt>> body;
|
const std::vector<std::shared_ptr<Stmt>> body;
|
||||||
const std::shared_ptr<Environment> closure;
|
const std::shared_ptr<Environment> closure;
|
||||||
|
const std::string ownerClass; // empty for non-methods
|
||||||
|
|
||||||
Function(std::string name, std::vector<std::string> params,
|
Function(std::string name, std::vector<std::string> params,
|
||||||
std::vector<std::shared_ptr<Stmt>> body,
|
std::vector<std::shared_ptr<Stmt>> body,
|
||||||
std::shared_ptr<Environment> closure)
|
std::shared_ptr<Environment> closure,
|
||||||
: name(name), params(params), body(body), closure(closure) {}
|
std::string ownerClass = "")
|
||||||
|
: name(name), params(params), body(body), closure(closure), ownerClass(ownerClass) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BuiltinFunction : public Object
|
struct BuiltinFunction
|
||||||
{
|
{
|
||||||
const std::string name;
|
const std::string name;
|
||||||
const std::function<Value(std::vector<Value>, int, int)> func;
|
const std::function<Value(std::vector<Value>, int, int)> func;
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@ -13,6 +14,7 @@ struct Environment;
|
|||||||
struct Function;
|
struct Function;
|
||||||
struct BuiltinFunction;
|
struct BuiltinFunction;
|
||||||
struct Thunk;
|
struct Thunk;
|
||||||
|
struct Module;
|
||||||
|
|
||||||
// Type tags for the Value union
|
// Type tags for the Value union
|
||||||
enum ValueType {
|
enum ValueType {
|
||||||
@ -24,9 +26,12 @@ enum ValueType {
|
|||||||
VAL_BUILTIN_FUNCTION,
|
VAL_BUILTIN_FUNCTION,
|
||||||
VAL_THUNK,
|
VAL_THUNK,
|
||||||
VAL_ARRAY,
|
VAL_ARRAY,
|
||||||
VAL_DICT
|
VAL_DICT,
|
||||||
|
VAL_MODULE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// (moved below Value)
|
||||||
|
|
||||||
// Tagged value system (like Lua) - no heap allocation for simple values
|
// Tagged value system (like Lua) - no heap allocation for simple values
|
||||||
struct Value {
|
struct Value {
|
||||||
union {
|
union {
|
||||||
@ -37,6 +42,7 @@ struct Value {
|
|||||||
std::string string_value; // Store strings outside the union for safety
|
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::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<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
|
// Store functions as shared_ptr for proper reference counting
|
||||||
std::shared_ptr<Function> function;
|
std::shared_ptr<Function> function;
|
||||||
@ -58,6 +64,7 @@ struct Value {
|
|||||||
Value(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(arr))) {}
|
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(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::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
|
// Destructor to clean up functions and thunks
|
||||||
~Value() {
|
~Value() {
|
||||||
@ -70,7 +77,7 @@ struct Value {
|
|||||||
// Move constructor
|
// Move constructor
|
||||||
Value(Value&& other) noexcept
|
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)),
|
: 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)) {
|
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)), module_value(std::move(other.module_value)) {
|
||||||
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
|
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) {
|
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
|
||||||
number = other.number; // Copy the union
|
number = other.number; // Copy the union
|
||||||
@ -94,6 +101,8 @@ struct Value {
|
|||||||
builtin_function = std::move(other.builtin_function);
|
builtin_function = std::move(other.builtin_function);
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
thunk = std::move(other.thunk);
|
thunk = std::move(other.thunk);
|
||||||
|
} else if (type == ValueType::VAL_MODULE) {
|
||||||
|
module_value = std::move(other.module_value);
|
||||||
} else {
|
} else {
|
||||||
number = other.number;
|
number = other.number;
|
||||||
}
|
}
|
||||||
@ -117,6 +126,8 @@ struct Value {
|
|||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
thunk = other.thunk; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_MODULE) {
|
||||||
|
module_value = other.module_value; // shared module
|
||||||
} else {
|
} else {
|
||||||
number = other.number;
|
number = other.number;
|
||||||
}
|
}
|
||||||
@ -146,6 +157,8 @@ struct Value {
|
|||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
thunk = other.thunk; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_MODULE) {
|
||||||
|
module_value = other.module_value;
|
||||||
} else {
|
} else {
|
||||||
number = other.number;
|
number = other.number;
|
||||||
}
|
}
|
||||||
@ -161,6 +174,7 @@ struct Value {
|
|||||||
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
||||||
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
|
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
|
||||||
inline bool isDict() const { return type == ValueType::VAL_DICT; }
|
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 isThunk() const { return type == ValueType::VAL_THUNK; }
|
||||||
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
||||||
|
|
||||||
@ -176,6 +190,7 @@ struct Value {
|
|||||||
case ValueType::VAL_THUNK: return "thunk";
|
case ValueType::VAL_THUNK: return "thunk";
|
||||||
case ValueType::VAL_ARRAY: return "array";
|
case ValueType::VAL_ARRAY: return "array";
|
||||||
case ValueType::VAL_DICT: return "dict";
|
case ValueType::VAL_DICT: return "dict";
|
||||||
|
case ValueType::VAL_MODULE: return "module";
|
||||||
default: return "unknown";
|
default: return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,6 +213,7 @@ struct Value {
|
|||||||
inline std::unordered_map<std::string, Value>& asDict() {
|
inline std::unordered_map<std::string, Value>& asDict() {
|
||||||
return *dict_value;
|
return *dict_value;
|
||||||
}
|
}
|
||||||
|
inline Module* asModule() const { return isModule() ? module_value.get() : nullptr; }
|
||||||
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
||||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
||||||
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
||||||
@ -214,6 +230,7 @@ struct Value {
|
|||||||
case ValueType::VAL_THUNK: return thunk != nullptr;
|
case ValueType::VAL_THUNK: return thunk != nullptr;
|
||||||
case ValueType::VAL_ARRAY: return !array_value->empty();
|
case ValueType::VAL_ARRAY: return !array_value->empty();
|
||||||
case ValueType::VAL_DICT: return !dict_value->empty();
|
case ValueType::VAL_DICT: return !dict_value->empty();
|
||||||
|
case ValueType::VAL_MODULE: return module_value != nullptr;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,6 +313,12 @@ struct Value {
|
|||||||
result += "}";
|
result += "}";
|
||||||
return 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";
|
default: return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,9 +443,16 @@ 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
|
// Global constants for common values
|
||||||
extern const Value NONE_VALUE;
|
extern const Value NONE_VALUE;
|
||||||
extern const Value TRUE_VALUE;
|
extern const Value TRUE_VALUE;
|
||||||
extern const Value FALSE_VALUE;
|
extern const Value FALSE_VALUE;
|
||||||
extern const Value ZERO_VALUE;
|
|
||||||
extern const Value ONE_VALUE;
|
|
||||||
@ -1,434 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@ -1,245 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
src/sources/builtinModules/base64.cpp
Normal file
41
src/sources/builtinModules/base64.cpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#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()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
64
src/sources/builtinModules/eval.cpp
Normal file
64
src/sources/builtinModules/eval.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
72
src/sources/builtinModules/io.cpp
Normal file
72
src/sources/builtinModules/io.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
83
src/sources/builtinModules/json.cpp
Normal file
83
src/sources/builtinModules/json.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#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]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
56
src/sources/builtinModules/math.cpp
Normal file
56
src/sources/builtinModules/math.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
132
src/sources/builtinModules/os.cpp
Normal file
132
src/sources/builtinModules/os.cpp
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
63
src/sources/builtinModules/path.cpp
Normal file
63
src/sources/builtinModules/path.cpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
35
src/sources/builtinModules/rand.cpp
Normal file
35
src/sources/builtinModules/rand.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#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)];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
25
src/sources/builtinModules/register.cpp
Normal file
25
src/sources/builtinModules/register.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
99
src/sources/builtinModules/sys.cpp
Normal file
99
src/sources/builtinModules/sys.cpp
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
31
src/sources/builtinModules/time.cpp
Normal file
31
src/sources/builtinModules/time.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3,35 +3,23 @@
|
|||||||
#include "bob.h"
|
#include "bob.h"
|
||||||
#include "Parser.h"
|
#include "Parser.h"
|
||||||
|
|
||||||
|
void Bob::ensureInterpreter(bool interactive) {
|
||||||
|
if (!interpreter) interpreter = msptr(Interpreter)(interactive);
|
||||||
|
applyPendingConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
void Bob::runFile(const std::string& path)
|
void Bob::runFile(const std::string& path)
|
||||||
{
|
{
|
||||||
this->interpreter = msptr(Interpreter)(false);
|
ensureInterpreter(false);
|
||||||
std::ifstream file = std::ifstream(path);
|
interpreter->addStdLibFunctions();
|
||||||
|
if (!evalFile(path)) {
|
||||||
std::string source;
|
std::cout << "Execution failed\n";
|
||||||
|
|
||||||
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()
|
void Bob::runPrompt()
|
||||||
{
|
{
|
||||||
this->interpreter = msptr(Interpreter)(true);
|
ensureInterpreter(true);
|
||||||
|
|
||||||
std::cout << "Bob v" << VERSION << ", 2025\n";
|
std::cout << "Bob v" << VERSION << ", 2025\n";
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
@ -46,50 +34,40 @@ void Bob::runPrompt()
|
|||||||
|
|
||||||
// Reset error state before each REPL command
|
// Reset error state before each REPL command
|
||||||
errorReporter.resetErrorState();
|
errorReporter.resetErrorState();
|
||||||
|
interpreter->addStdLibFunctions();
|
||||||
// Load source code into error reporter for context
|
(void)evalString(line, "REPL");
|
||||||
errorReporter.loadSource(line, "REPL");
|
|
||||||
|
|
||||||
// Connect error reporter to interpreter
|
|
||||||
interpreter->setErrorReporter(&errorReporter);
|
|
||||||
|
|
||||||
this->run(line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bob::run(std::string source)
|
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);
|
||||||
try {
|
try {
|
||||||
// Connect error reporter to lexer
|
|
||||||
lexer.setErrorReporter(&errorReporter);
|
lexer.setErrorReporter(&errorReporter);
|
||||||
|
auto tokens = lexer.Tokenize(src);
|
||||||
std::vector<Token> tokens = lexer.Tokenize(std::move(source));
|
|
||||||
Parser p(tokens);
|
Parser p(tokens);
|
||||||
|
|
||||||
// Connect error reporter to parser
|
|
||||||
p.setErrorReporter(&errorReporter);
|
p.setErrorReporter(&errorReporter);
|
||||||
|
auto statements = p.parse();
|
||||||
std::vector<sptr(Stmt)> statements = p.parse();
|
|
||||||
interpreter->interpret(statements);
|
interpreter->interpret(statements);
|
||||||
}
|
return true;
|
||||||
catch(std::exception &e)
|
} catch (...) { return false; }
|
||||||
{
|
}
|
||||||
// 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.)
|
bool Bob::evalString(const std::string& code, const std::string& filename) {
|
||||||
// print them normally
|
errorReporter.loadSource(code, filename);
|
||||||
std::cout << "Error: " << e.what() << '\n';
|
interpreter->setErrorReporter(&errorReporter);
|
||||||
return;
|
try {
|
||||||
}
|
lexer.setErrorReporter(&errorReporter);
|
||||||
catch(const std::exception& e)
|
auto tokens = lexer.Tokenize(code);
|
||||||
{
|
Parser p(tokens);
|
||||||
// Unknown error - report it since it wasn't handled by the interpreter
|
p.setErrorReporter(&errorReporter);
|
||||||
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred: " + std::string(e.what()));
|
auto statements = p.parse();
|
||||||
return;
|
interpreter->interpret(statements);
|
||||||
}
|
return true;
|
||||||
|
} catch (...) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,27 @@
|
|||||||
|
|
||||||
//
|
//
|
||||||
#include "bob.h"
|
#include "bob.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]){
|
int main(int argc, char* argv[]){
|
||||||
Bob bobLang;
|
Bob bobLang;
|
||||||
|
// Enable open preset (all builtins, file imports allowed)
|
||||||
|
bobLang.setSafetyPreset("open");
|
||||||
|
|
||||||
|
|
||||||
if(argc > 1) {
|
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]);
|
bobLang.runFile(argv[1]);
|
||||||
} else {
|
} 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();
|
bobLang.runPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,29 @@ 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) {
|
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
||||||
hadError = true;
|
hadError = true;
|
||||||
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
||||||
@ -75,6 +98,8 @@ void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
|||||||
|
|
||||||
if (!context.fileName.empty()) {
|
if (!context.fileName.empty()) {
|
||||||
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
|
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) +
|
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +
|
||||||
|
|||||||
@ -363,18 +363,14 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
|
|
||||||
}
|
}
|
||||||
if(!isNotation) {
|
if(!isNotation) {
|
||||||
if (!src.empty() && src[0] == '.') {
|
// Only treat '.' as part of the number if followed by a digit
|
||||||
advance();
|
if (src.size() > 1 && src[0] == '.' && std::isdigit(src[1])) {
|
||||||
if (!src.empty() && std::isdigit(src[0])) {
|
advance(); // consume '.'
|
||||||
num += '.';
|
num += '.';
|
||||||
while (!src.empty() && std::isdigit(src[0])) {
|
while (!src.empty() && std::isdigit(src[0])) {
|
||||||
num += src[0];
|
num += src[0];
|
||||||
advance();
|
advance();
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("LEXER: malformed number at: " + std::to_string(this->line));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -252,21 +252,35 @@ sptr(Expr) Parser::postfix()
|
|||||||
{
|
{
|
||||||
sptr(Expr) expr = primary();
|
sptr(Expr) expr = primary();
|
||||||
|
|
||||||
// Check for postfix increment/decrement
|
while (true) {
|
||||||
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
if (match({OPEN_PAREN})) {
|
||||||
Token oper = previous();
|
expr = finishCall(expr);
|
||||||
|
continue;
|
||||||
// Ensure the expression is a variable or array indexing
|
|
||||||
if (!std::dynamic_pointer_cast<VarExpr>(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.");
|
|
||||||
}
|
}
|
||||||
|
if (match({OPEN_BRACKET})) {
|
||||||
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
|
expr = finishArrayIndex(expr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (match({DOT})) {
|
||||||
|
Token name = consume(IDENTIFIER, "Expected property name after '.'.");
|
||||||
|
expr = msptr(PropertyExpr)(expr, name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
||||||
|
Token oper = previous();
|
||||||
|
if (!std::dynamic_pointer_cast<VarExpr>(expr) &&
|
||||||
|
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr) &&
|
||||||
|
!std::dynamic_pointer_cast<PropertyExpr>(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 expr;
|
return expr;
|
||||||
@ -281,8 +295,15 @@ sptr(Expr) Parser::primary()
|
|||||||
if(match({NUMBER})) return msptr(LiteralExpr)(previous().lexeme, true, false, false);
|
if(match({NUMBER})) return msptr(LiteralExpr)(previous().lexeme, true, false, false);
|
||||||
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
|
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
|
||||||
|
|
||||||
if(match( {IDENTIFIER})) {
|
if(match( {IDENTIFIER, THIS, SUPER})) {
|
||||||
return call();
|
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({OPEN_PAREN}))
|
if(match({OPEN_PAREN}))
|
||||||
@ -360,7 +381,13 @@ sptr(Expr) Parser::dictLiteral()
|
|||||||
|
|
||||||
sptr(Expr) Parser::call()
|
sptr(Expr) Parser::call()
|
||||||
{
|
{
|
||||||
sptr(Expr) expr = msptr(VarExpr)(previous());
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match({OPEN_PAREN})) {
|
if (match({OPEN_PAREN})) {
|
||||||
@ -396,6 +423,8 @@ sptr(Stmt) Parser::declaration()
|
|||||||
try{
|
try{
|
||||||
if(match({VAR})) return varDeclaration();
|
if(match({VAR})) return varDeclaration();
|
||||||
if(match({FUNCTION})) return functionDeclaration();
|
if(match({FUNCTION})) return functionDeclaration();
|
||||||
|
if(match({CLASS})) return classDeclaration();
|
||||||
|
if(match({EXTENSION})) return extensionDeclaration();
|
||||||
return statement();
|
return statement();
|
||||||
}
|
}
|
||||||
catch(std::runtime_error& e)
|
catch(std::runtime_error& e)
|
||||||
@ -405,6 +434,86 @@ 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()
|
sptr(Stmt) Parser::varDeclaration()
|
||||||
{
|
{
|
||||||
Token name = consume(IDENTIFIER, "Expected variable name.");
|
Token name = consume(IDENTIFIER, "Expected variable name.");
|
||||||
@ -477,6 +586,13 @@ std::shared_ptr<Expr> Parser::functionExpression() {
|
|||||||
sptr(Stmt) Parser::statement()
|
sptr(Stmt) Parser::statement()
|
||||||
{
|
{
|
||||||
if(match({RETURN})) return returnStatement();
|
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({IF})) return ifStatement();
|
||||||
if(match({DO})) return doWhileStatement();
|
if(match({DO})) return doWhileStatement();
|
||||||
if(match({WHILE})) return whileStatement();
|
if(match({WHILE})) return whileStatement();
|
||||||
@ -486,7 +602,7 @@ sptr(Stmt) Parser::statement()
|
|||||||
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
||||||
|
|
||||||
// Check for assignment statement - simplified approach
|
// Check for assignment statement - simplified approach
|
||||||
if(check(IDENTIFIER)) {
|
if(check(IDENTIFIER) || check(THIS)) {
|
||||||
// Try to parse as assignment expression first
|
// Try to parse as assignment expression first
|
||||||
int currentPos = current;
|
int currentPos = current;
|
||||||
try {
|
try {
|
||||||
@ -511,6 +627,43 @@ sptr(Stmt) Parser::statement()
|
|||||||
return expressionStatement();
|
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()
|
sptr(Stmt) Parser::assignmentStatement()
|
||||||
{
|
{
|
||||||
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
|
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
|
||||||
@ -676,6 +829,35 @@ std::vector<sptr(Stmt)> Parser::block()
|
|||||||
return statements;
|
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) {
|
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
||||||
std::vector<sptr(Expr)> arguments;
|
std::vector<sptr(Expr)> arguments;
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,15 @@
|
|||||||
#include "ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
void Environment::assign(const Token& name, const Value& value) {
|
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);
|
auto it = variables.find(name.lexeme);
|
||||||
if (it != variables.end()) {
|
if (it != variables.end()) {
|
||||||
it->second = value;
|
it->second = value;
|
||||||
@ -13,7 +22,9 @@ void Environment::assign(const Token& name, const Value& value) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Report only if not within a try; otherwise let try/catch handle
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
|
// We cannot check tryDepth here directly; rely on Executor to suppress double-reporting
|
||||||
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
||||||
"Undefined variable '" + name.lexeme + "'", "");
|
"Undefined variable '" + name.lexeme + "'", "");
|
||||||
}
|
}
|
||||||
@ -37,27 +48,14 @@ Value Environment::get(const Token& name) {
|
|||||||
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
|
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() {
|
void Environment::pruneForClosureCapture() {
|
||||||
for (auto &entry : variables) {
|
for (auto &entry : variables) {
|
||||||
Value &v = entry.second;
|
Value &v = entry.second;
|
||||||
if (v.isArray()) {
|
if (v.isArray()) {
|
||||||
// Replace with a new empty array to avoid mutating original shared storage
|
|
||||||
entry.second = Value(std::vector<Value>{});
|
entry.second = Value(std::vector<Value>{});
|
||||||
} else if (v.isDict()) {
|
} 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>{});
|
entry.second = Value(std::unordered_map<std::string, Value>{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#include "Evaluator.h"
|
#include "Evaluator.h"
|
||||||
#include "Interpreter.h"
|
#include "Interpreter.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "AssignmentUtils.h"
|
||||||
#include "helperFunctions/HelperFunctions.h"
|
#include "helperFunctions/HelperFunctions.h"
|
||||||
|
|
||||||
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
|
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
|
||||||
@ -35,8 +37,6 @@ Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
|||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case MINUS:
|
case MINUS:
|
||||||
if (!right.isNumber()) {
|
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);
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
}
|
}
|
||||||
return Value(-right.asNumber());
|
return Value(-right.asNumber());
|
||||||
@ -46,8 +46,6 @@ Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
|||||||
|
|
||||||
case BIN_NOT:
|
case BIN_NOT:
|
||||||
if (!right.isNumber()) {
|
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);
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
}
|
}
|
||||||
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
||||||
@ -104,8 +102,6 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
|
|||||||
default: break; // Unreachable
|
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()));
|
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +111,23 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
|
|||||||
case PLUS: return left + right;
|
case PLUS: return left + right;
|
||||||
case MINUS: return left - right;
|
case MINUS: return left - right;
|
||||||
case STAR: return left * right;
|
case STAR: return left * right;
|
||||||
case SLASH: return left / right;
|
case SLASH: {
|
||||||
case PERCENT: return left % right;
|
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 BIN_AND: return left & right;
|
case BIN_AND: return left & right;
|
||||||
case BIN_OR: return left | right;
|
case BIN_OR: return left | right;
|
||||||
case BIN_XOR: return left ^ right;
|
case BIN_XOR: return left ^ right;
|
||||||
@ -128,10 +139,7 @@ Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression)
|
|||||||
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
||||||
}
|
}
|
||||||
} catch (const std::runtime_error& e) {
|
} catch (const std::runtime_error& e) {
|
||||||
// The Value operators provide good error messages, just add context
|
throw; // Propagate to statement driver (try/catch) without reporting here
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
e.what(), expression->oper.lexeme);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +172,7 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
|
|||||||
throw std::runtime_error("Invalid increment/decrement operator.");
|
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the variable or array element
|
// Update the variable, array element, or object property
|
||||||
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||||
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
||||||
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
||||||
@ -195,6 +203,14 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
|
|||||||
|
|
||||||
// Update the array element
|
// Update the array element
|
||||||
arr[idx] = Value(newValue);
|
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 {
|
} else {
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
||||||
@ -213,75 +229,25 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
|
|||||||
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
||||||
Value value = interpreter->evaluate(expression->value);
|
Value value = interpreter->evaluate(expression->value);
|
||||||
|
|
||||||
if (expression->op.type == EQUAL) {
|
if (expression->op.type == EQUAL) {
|
||||||
try {
|
// Assign first to release references held by the old values
|
||||||
// Check if the variable existed and whether it held a collection
|
interpreter->getEnvironment()->assign(expression->name, value);
|
||||||
bool existed = false;
|
// Perform cleanup on any reassignment
|
||||||
bool wasCollection = false;
|
interpreter->forceCleanup();
|
||||||
try {
|
return value;
|
||||||
Value oldValue = interpreter->getEnvironment()->get(expression->name);
|
|
||||||
existed = true;
|
|
||||||
wasCollection = oldValue.isArray() || oldValue.isDict();
|
|
||||||
} catch (...) {
|
|
||||||
existed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
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 std::runtime_error("Unknown assignment operator: " + expression->op.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->getEnvironment()->assign(expression->name, newValue);
|
|
||||||
return newValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
// 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&) {
|
||||||
|
interpreter->reportError(expression->op.line, expression->op.column, "Runtime Error",
|
||||||
|
"Unknown assignment operator: " + expression->op.lexeme, "");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
|
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
|
||||||
@ -316,8 +282,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
if (array.isArray()) {
|
if (array.isArray()) {
|
||||||
// Handle array indexing
|
// Handle array indexing
|
||||||
if (!index.isNumber()) {
|
if (!index.isNumber()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
"Array index must be a number", "");
|
"Array index must be a number", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
throw std::runtime_error("Array index must be a number");
|
throw std::runtime_error("Array index must be a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,8 +292,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
const std::vector<Value>& arr = array.asArray();
|
const std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
"Array index out of bounds", "");
|
"Array index out of bounds", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
throw std::runtime_error("Array index out of bounds");
|
throw std::runtime_error("Array index out of bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,8 +303,9 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
} else if (array.isDict()) {
|
} else if (array.isDict()) {
|
||||||
// Handle dictionary indexing
|
// Handle dictionary indexing
|
||||||
if (!index.isString()) {
|
if (!index.isString()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
"Dictionary key must be a string", "");
|
"Dictionary key must be a string", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,6 +322,7 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
"Can only index arrays and dictionaries", "");
|
"Can only index arrays and dictionaries", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
throw std::runtime_error("Can only index arrays and dictionaries");
|
throw std::runtime_error("Can only index arrays and dictionaries");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,13 +331,142 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
|||||||
Value object = expr->object->accept(this);
|
Value object = expr->object->accept(this);
|
||||||
std::string propertyName = expr->name.lexeme;
|
std::string propertyName = expr->name.lexeme;
|
||||||
|
|
||||||
if (object.isDict()) {
|
if (object.isModule()) {
|
||||||
return getDictProperty(object, propertyName);
|
// 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;
|
||||||
} else if (object.isArray()) {
|
} else if (object.isArray()) {
|
||||||
return getArrayProperty(object, propertyName);
|
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;
|
||||||
} else {
|
} 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",
|
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
||||||
"Cannot access property '" + propertyName + "' on this type", "");
|
"Cannot access property '" + propertyName + "' on this type", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
|
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -380,8 +479,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
if (array.isArray()) {
|
if (array.isArray()) {
|
||||||
// Handle array assignment
|
// Handle array assignment
|
||||||
if (!index.isNumber()) {
|
if (!index.isNumber()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Array index must be a number", "");
|
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");
|
throw std::runtime_error("Array index must be a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,8 +491,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
std::vector<Value>& arr = array.asArray();
|
std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Array index out of bounds", "");
|
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");
|
throw std::runtime_error("Array index out of bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,8 +505,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
} else if (array.isDict()) {
|
} else if (array.isDict()) {
|
||||||
// Handle dictionary assignment
|
// Handle dictionary assignment
|
||||||
if (!index.isString()) {
|
if (!index.isString()) {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Dictionary key must be a string", "");
|
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");
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,8 +520,11 @@ Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& ex
|
|||||||
return value;
|
return value;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Can only assign to array or dictionary elements", "");
|
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");
|
throw std::runtime_error("Can only assign to array or dictionary elements");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,14 +546,25 @@ Value Evaluator::visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExp
|
|||||||
Value value = expr->value->accept(this);
|
Value value = expr->value->accept(this);
|
||||||
std::string propertyName = expr->name.lexeme;
|
std::string propertyName = expr->name.lexeme;
|
||||||
|
|
||||||
if (object.isDict()) {
|
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()) {
|
||||||
// Modify the dictionary in place
|
// Modify the dictionary in place
|
||||||
std::unordered_map<std::string, Value>& dict = object.asDict();
|
std::unordered_map<std::string, Value>& dict = object.asDict();
|
||||||
dict[propertyName] = value;
|
dict[propertyName] = value;
|
||||||
return value; // Return the assigned value
|
return value; // Return the assigned value
|
||||||
} else {
|
} else {
|
||||||
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
if (!interpreter->isInTry()) {
|
||||||
"Cannot assign property '" + propertyName + "' on non-object", "");
|
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");
|
throw std::runtime_error("Cannot assign property '" + propertyName + "' on non-object");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -453,10 +575,8 @@ Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expressi
|
|||||||
paramNames.push_back(param.lexeme);
|
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());
|
auto closureEnv = std::make_shared<Environment>(*interpreter->getEnvironment());
|
||||||
closureEnv->pruneForClosureCapture();
|
closureEnv->pruneForClosureCapture();
|
||||||
|
|
||||||
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
|
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
|
||||||
return Value(function);
|
return Value(function);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
#include "Executor.h"
|
#include "Executor.h"
|
||||||
#include "Evaluator.h"
|
#include "Evaluator.h"
|
||||||
#include "Interpreter.h"
|
#include "Interpreter.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "Parser.h"
|
||||||
|
#include "AssignmentUtils.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
||||||
@ -9,13 +12,47 @@ Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
|||||||
Executor::~Executor() {}
|
Executor::~Executor() {}
|
||||||
|
|
||||||
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
||||||
|
ExecutionContext top;
|
||||||
for (const auto& statement : statements) {
|
for (const auto& statement : statements) {
|
||||||
execute(statement, nullptr);
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
||||||
statement->accept(this, 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) {
|
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
||||||
@ -24,7 +61,14 @@ void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements
|
|||||||
|
|
||||||
for (const auto& statement : statements) {
|
for (const auto& statement : statements) {
|
||||||
execute(statement, context);
|
execute(statement, context);
|
||||||
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
|
// 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)) {
|
||||||
interpreter->setEnvironment(previous);
|
interpreter->setEnvironment(previous);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -39,6 +83,11 @@ void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, Execu
|
|||||||
|
|
||||||
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||||
Value value = statement->expression->accept(evaluator);
|
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())
|
if (interpreter->isInteractiveMode())
|
||||||
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
||||||
@ -48,6 +97,8 @@ void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, Execution
|
|||||||
Value value = NONE_VALUE;
|
Value value = NONE_VALUE;
|
||||||
if (statement->initializer != nullptr) {
|
if (statement->initializer != nullptr) {
|
||||||
value = statement->initializer->accept(evaluator);
|
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);
|
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
||||||
}
|
}
|
||||||
@ -79,7 +130,10 @@ void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, Exe
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
||||||
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
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)) {
|
||||||
execute(statement->thenBranch, context);
|
execute(statement->thenBranch, context);
|
||||||
} else if (statement->elseBranch != nullptr) {
|
} else if (statement->elseBranch != nullptr) {
|
||||||
execute(statement->elseBranch, context);
|
execute(statement->elseBranch, context);
|
||||||
@ -94,7 +148,7 @@ void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, Execu
|
|||||||
|
|
||||||
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
execute(statement->body, &loopContext);
|
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 (loopContext.hasReturn) {
|
||||||
if (context) {
|
if (context) {
|
||||||
context->hasReturn = true;
|
context->hasReturn = true;
|
||||||
@ -120,26 +174,17 @@ void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, E
|
|||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
while (true) {
|
||||||
execute(statement->body, &loopContext);
|
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 (loopContext.hasReturn) { if (context) { context->hasReturn = true; context->returnValue = loopContext.returnValue; } break; }
|
||||||
if (context) {
|
if (loopContext.shouldBreak) { break; }
|
||||||
context->hasReturn = true;
|
if (loopContext.shouldContinue) { loopContext.shouldContinue = false; }
|
||||||
context->returnValue = loopContext.returnValue;
|
Value c = statement->condition->accept(evaluator);
|
||||||
}
|
Value thrown; int tl=0, tc=0;
|
||||||
break;
|
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.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) {
|
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
||||||
@ -152,9 +197,15 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
|
|||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
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;
|
||||||
|
}
|
||||||
execute(statement->body, &loopContext);
|
execute(statement->body, &loopContext);
|
||||||
|
if (loopContext.hasThrow) { if (context) { context->hasThrow = true; context->thrownValue = loopContext.thrownValue; } break; }
|
||||||
if (loopContext.hasReturn) {
|
if (loopContext.hasReturn) {
|
||||||
if (context) {
|
if (context) {
|
||||||
context->hasReturn = true;
|
context->hasReturn = true;
|
||||||
@ -171,12 +222,16 @@ void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, Execution
|
|||||||
loopContext.shouldContinue = false;
|
loopContext.shouldContinue = false;
|
||||||
if (statement->increment != nullptr) {
|
if (statement->increment != nullptr) {
|
||||||
statement->increment->accept(evaluator);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statement->increment != nullptr) {
|
if (statement->increment != nullptr) {
|
||||||
statement->increment->accept(evaluator);
|
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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,67 +248,250 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
void Executor::visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context) {
|
||||||
try {
|
interpreter->enterTry();
|
||||||
Value value = statement->value->accept(evaluator);
|
// Temporarily detach the reporter so any direct uses (e.g., in Environment) won't print inline
|
||||||
|
auto savedReporter = interpreter->getErrorReporter();
|
||||||
if (statement->op.type == EQUAL) {
|
interpreter->setErrorReporter(nullptr);
|
||||||
try {
|
ExecutionContext inner;
|
||||||
// Assign first to release references held by the old value
|
if (context) inner.isFunctionBody = context->isFunctionBody;
|
||||||
interpreter->getEnvironment()->assign(statement->name, value);
|
execute(statement->tryBlock, &inner);
|
||||||
|
// Also capture any pending throw signaled by expressions
|
||||||
// Clean up on any reassignment, regardless of old/new type
|
Value pending; int pl=0, pc=0;
|
||||||
interpreter->forceCleanup();
|
if (interpreter->consumePendingThrow(pending, &pl, &pc)) {
|
||||||
} catch (const std::exception& e) {
|
inner.hasThrow = true;
|
||||||
std::cerr << "Error during assignment: " << e.what() << std::endl;
|
inner.thrownValue = pending;
|
||||||
throw; // Re-throw to see the full stack trace
|
inner.throwLine = pl;
|
||||||
}
|
inner.throwColumn = pc;
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
// If thrown, handle catch
|
||||||
std::cerr << "Error in visitAssignStmt: " << e.what() << std::endl;
|
if (inner.hasThrow && statement->catchBlock) {
|
||||||
throw; // Re-throw to see the full stack trace
|
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) {
|
||||||
|
Value value = statement->value->accept(evaluator);
|
||||||
|
|
||||||
|
if (statement->op.type == EQUAL) {
|
||||||
|
// 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
|
||||||
|
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&) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,19 @@
|
|||||||
#include "Interpreter.h"
|
#include "Interpreter.h"
|
||||||
|
#include "register.h"
|
||||||
#include "Evaluator.h"
|
#include "Evaluator.h"
|
||||||
#include "Executor.h"
|
#include "Executor.h"
|
||||||
#include "BobStdLib.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>
|
#include <iostream>
|
||||||
|
|
||||||
Interpreter::Interpreter(bool isInteractive)
|
Interpreter::Interpreter(bool isInteractive)
|
||||||
@ -9,6 +21,11 @@ Interpreter::Interpreter(bool isInteractive)
|
|||||||
evaluator = std::make_unique<Evaluator>(this);
|
evaluator = std::make_unique<Evaluator>(this);
|
||||||
executor = std::make_unique<Executor>(this, evaluator.get());
|
executor = std::make_unique<Executor>(this, evaluator.get());
|
||||||
environment = std::make_shared<Environment>();
|
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;
|
Interpreter::~Interpreter() = default;
|
||||||
@ -32,6 +49,25 @@ Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
|||||||
}
|
}
|
||||||
return runTrampoline(result);
|
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 Interpreter::runTrampoline(Value initialResult) {
|
||||||
Value current = initialResult;
|
Value current = initialResult;
|
||||||
@ -56,14 +92,169 @@ std::string Interpreter::stringify(Value object) {
|
|||||||
void Interpreter::addStdLibFunctions() {
|
void Interpreter::addStdLibFunctions() {
|
||||||
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
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) {
|
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||||
builtinFunctions.push_back(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) {
|
void Interpreter::addFunction(std::shared_ptr<Function> function) {
|
||||||
functions.push_back(function);
|
functions.push_back(function);
|
||||||
@ -74,7 +265,6 @@ void Interpreter::setErrorReporter(ErrorReporter* reporter) {
|
|||||||
if (environment) {
|
if (environment) {
|
||||||
environment->setErrorReporter(reporter);
|
environment->setErrorReporter(reporter);
|
||||||
}
|
}
|
||||||
addStdLibFunctions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Interpreter::isInteractiveMode() const {
|
bool Interpreter::isInteractiveMode() const {
|
||||||
@ -90,6 +280,12 @@ 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) {
|
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) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(line, column, errorType, message, lexeme);
|
errorReporter->reportError(line, column, errorType, message, lexeme);
|
||||||
}
|
}
|
||||||
@ -107,22 +303,277 @@ void Interpreter::forceCleanup() {
|
|||||||
diagnostics.forceCleanup(builtinFunctions, functions, thunks);
|
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) {
|
Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression) {
|
||||||
Value callee = evaluate(expression->callee); // Direct call instead of through evaluator
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (callee.isBuiltinFunction()) {
|
if (callee.isBuiltinFunction()) {
|
||||||
// Handle builtin functions with direct evaluation
|
// Handle builtin functions with direct evaluation
|
||||||
std::vector<Value> arguments;
|
std::vector<Value> arguments;
|
||||||
for (const auto& argument : expression->arguments) {
|
for (const auto& argument : expression->arguments) {
|
||||||
arguments.push_back(evaluate(argument)); // Direct call
|
arguments.push_back(evaluate(argument));
|
||||||
}
|
}
|
||||||
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
||||||
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!callee.isFunction()) {
|
if (!callee.isFunction()) {
|
||||||
// Provide better error message with type information (like original)
|
std::string errorMsg = isSuperCall ? ("Undefined super method '" + methodName + "'")
|
||||||
std::string errorMsg = "Can only call functions, got " + callee.getType();
|
: ("Can only call functions, got " + callee.getType());
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
||||||
errorMsg, "");
|
errorMsg, "");
|
||||||
@ -134,7 +585,7 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
|
|
||||||
std::vector<Value> arguments;
|
std::vector<Value> arguments;
|
||||||
for (const auto& argument : expression->arguments) {
|
for (const auto& argument : expression->arguments) {
|
||||||
arguments.push_back(evaluate(argument)); // Direct call instead of through evaluator
|
arguments.push_back(evaluate(argument));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check arity (like original)
|
// Check arity (like original)
|
||||||
@ -150,12 +601,18 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
|
|
||||||
// Check if this is a tail call for inline TCO
|
// Check if this is a tail call for inline TCO
|
||||||
if (expression->isTailCall) {
|
if (expression->isTailCall) {
|
||||||
// Create a thunk for tail call optimization - original inline version
|
|
||||||
auto thunk = std::make_shared<Thunk>([this, function, arguments]() -> Value {
|
auto thunk = std::make_shared<Thunk>([this, function, arguments, isMethodCall, receiver, isSuperCall]() -> Value {
|
||||||
// Use RAII to manage environment (exactly like original)
|
|
||||||
ScopedEnv _env(environment);
|
ScopedEnv _env(environment);
|
||||||
environment = std::make_shared<Environment>(function->closure);
|
environment = std::make_shared<Environment>(function->closure);
|
||||||
environment->setErrorReporter(errorReporter);
|
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++) {
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
environment->define(function->params[i], arguments[i]);
|
environment->define(function->params[i], arguments[i]);
|
||||||
@ -164,12 +621,11 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
ExecutionContext context;
|
ExecutionContext context;
|
||||||
context.isFunctionBody = true;
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
// Use RAII to manage thunk execution flag
|
|
||||||
ScopedThunkFlag _inThunk(inThunkExecution);
|
ScopedThunkFlag _inThunk(inThunkExecution);
|
||||||
|
|
||||||
// Execute function body (inline like original - direct accept for performance)
|
|
||||||
for (const auto& stmt : function->body) {
|
for (const auto& stmt : function->body) {
|
||||||
stmt->accept(executor.get(), &context); // Direct call like original
|
stmt->accept(executor.get(), &context);
|
||||||
|
if (context.hasThrow) { setPendingThrow(context.thrownValue, context.throwLine, context.throwColumn); return NONE_VALUE; }
|
||||||
if (context.hasReturn) {
|
if (context.hasReturn) {
|
||||||
return context.returnValue;
|
return context.returnValue;
|
||||||
}
|
}
|
||||||
@ -178,10 +634,8 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
return context.returnValue;
|
return context.returnValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store the thunk to keep it alive and return as Value (exactly like original)
|
|
||||||
thunks.push_back(thunk);
|
thunks.push_back(thunk);
|
||||||
|
|
||||||
// Automatic cleanup check
|
|
||||||
thunkCreationCount++;
|
thunkCreationCount++;
|
||||||
if (thunkCreationCount >= CLEANUP_THRESHOLD) {
|
if (thunkCreationCount >= CLEANUP_THRESHOLD) {
|
||||||
cleanupUnusedThunks();
|
cleanupUnusedThunks();
|
||||||
@ -190,10 +644,16 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
|
|
||||||
return Value(thunk);
|
return Value(thunk);
|
||||||
} else {
|
} else {
|
||||||
// Normal function call - create new environment (exactly like original)
|
|
||||||
ScopedEnv _env(environment);
|
ScopedEnv _env(environment);
|
||||||
environment = std::make_shared<Environment>(function->closure);
|
environment = std::make_shared<Environment>(function->closure);
|
||||||
environment->setErrorReporter(errorReporter);
|
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++) {
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
environment->define(function->params[i], arguments[i]);
|
environment->define(function->params[i], arguments[i]);
|
||||||
@ -202,31 +662,13 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
|
|||||||
ExecutionContext context;
|
ExecutionContext context;
|
||||||
context.isFunctionBody = true;
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
// Execute function body (exactly like original - direct accept for performance)
|
|
||||||
for (const auto& stmt : function->body) {
|
for (const auto& stmt : function->body) {
|
||||||
stmt->accept(executor.get(), &context); // Direct call like original
|
stmt->accept(executor.get(), &context);
|
||||||
if (context.hasReturn) {
|
if (context.hasThrow) { setPendingThrow(context.thrownValue, context.throwLine, context.throwColumn); return NONE_VALUE; }
|
||||||
return context.returnValue;
|
if (context.hasReturn) { return context.returnValue; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -13,122 +13,19 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
bool RuntimeDiagnostics::isTruthy(Value object) {
|
bool RuntimeDiagnostics::isTruthy(Value object) { return object.isTruthy(); }
|
||||||
if(object.isBoolean()) {
|
|
||||||
return object.asBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object.isNone()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object.isNumber()) {
|
|
||||||
return object.asNumber() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object.isString()) {
|
|
||||||
return object.asString().length() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
|
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()) {
|
if (a.isNumber() && b.isBoolean()) {
|
||||||
// Numbers and booleans: 0 and false are equal, non-zero and true are equal
|
return b.asBoolean() ? (a.asNumber() != 0.0) : (a.asNumber() == 0.0);
|
||||||
if (b.asBoolean()) {
|
|
||||||
return a.asNumber() != 0.0;
|
|
||||||
} else {
|
|
||||||
return a.asNumber() == 0.0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.isBoolean() && b.isNumber()) {
|
if (a.isBoolean() && b.isNumber()) {
|
||||||
// Same as above, but reversed
|
return a.asBoolean() ? (b.asNumber() != 0.0) : (b.asNumber() == 0.0);
|
||||||
if (a.asBoolean()) {
|
|
||||||
return b.asNumber() != 0.0;
|
|
||||||
} else {
|
|
||||||
return b.asNumber() == 0.0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return a.equals(b);
|
||||||
// For all other type combinations, return false
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RuntimeDiagnostics::stringify(Value object) {
|
std::string RuntimeDiagnostics::stringify(Value object) { return object.toString(); }
|
||||||
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) {
|
std::string RuntimeDiagnostics::formatNumber(double value) {
|
||||||
double integral = value;
|
double integral = value;
|
||||||
@ -150,31 +47,9 @@ std::string RuntimeDiagnostics::formatNumber(double value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
|
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) { return Value(arr).toString(); }
|
||||||
std::string result = "[";
|
|
||||||
|
|
||||||
for (size_t i = 0; i < arr.size(); i++) {
|
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) { return Value(dict).toString(); }
|
||||||
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) {
|
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
|
||||||
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
|
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
|
||||||
@ -212,25 +87,7 @@ 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,
|
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
||||||
std::vector<std::shared_ptr<Function>>& functions,
|
std::vector<std::shared_ptr<Function>>& functions,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
|
#include "TypeWrapper.h"
|
||||||
#include "TypeWrapper.h"
|
#include "TypeWrapper.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
|||||||
@ -4,5 +4,12 @@
|
|||||||
const Value NONE_VALUE = Value();
|
const Value NONE_VALUE = Value();
|
||||||
const Value TRUE_VALUE = Value(true);
|
const Value TRUE_VALUE = Value(true);
|
||||||
const Value FALSE_VALUE = Value(false);
|
const Value FALSE_VALUE = Value(false);
|
||||||
const Value ZERO_VALUE = Value(0.0);
|
|
||||||
const Value ONE_VALUE = Value(1.0);
|
// 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>");
|
||||||
|
}
|
||||||
|
|
||||||
@ -76,104 +76,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(printRawFunc);
|
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
|
// Create a built-in assert function
|
||||||
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
||||||
@ -216,27 +119,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(assertFunc);
|
interpreter.addBuiltinFunction(assertFunc);
|
||||||
|
|
||||||
// Create a built-in time function (returns microseconds since Unix epoch)
|
// time-related globals moved into builtin time module
|
||||||
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
|
// Create a built-in input function
|
||||||
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
||||||
@ -293,6 +176,8 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
typeName = "array";
|
typeName = "array";
|
||||||
} else if (args[0].isDict()) {
|
} else if (args[0].isDict()) {
|
||||||
typeName = "dict";
|
typeName = "dict";
|
||||||
|
} else if (args[0].isModule()) {
|
||||||
|
typeName = "module";
|
||||||
} else {
|
} else {
|
||||||
typeName = "unknown";
|
typeName = "unknown";
|
||||||
}
|
}
|
||||||
@ -405,444 +290,60 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toBooleanFunc);
|
interpreter.addBuiltinFunction(toBooleanFunc);
|
||||||
|
|
||||||
// Create a built-in exit function to terminate the program
|
// exit moved to sys module
|
||||||
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
|
|
||||||
[](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
int exitCode = 0; // Default exit code
|
|
||||||
|
|
||||||
if (args.size() > 0) {
|
// sleep moved into builtin time module
|
||||||
if (args[0].isNumber()) {
|
|
||||||
exitCode = static_cast<int>(args[0].asNumber());
|
// 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));
|
||||||
}
|
}
|
||||||
// If not a number, just use default exit code 0
|
} 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("exit", Value(exitFunc));
|
env->define("dir", Value(dirFunc));
|
||||||
|
interpreter.addBuiltinFunction(dirFunc);
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
auto functionsFunc = std::make_shared<BuiltinFunction>("functions",
|
||||||
interpreter.addBuiltinFunction(exitFunc);
|
[](std::vector<Value> args, int, int) -> Value {
|
||||||
|
if (args.size() != 1) return Value(std::vector<Value>{});
|
||||||
// Create a built-in sleep function for animations and timing
|
Value obj = args[0];
|
||||||
auto sleepFunc = std::make_shared<BuiltinFunction>("sleep",
|
std::vector<Value> out;
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
auto pushIfFn = [&out](const std::pair<const std::string, Value>& kv){
|
||||||
if (args.size() != 1) {
|
if (kv.second.isFunction() || kv.second.isBuiltinFunction()) out.push_back(Value(kv.first));
|
||||||
if (errorReporter) {
|
};
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
if (obj.isModule()) {
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
auto* mod = obj.asModule();
|
||||||
|
if (mod && mod->exports) {
|
||||||
|
for (const auto& kv : *mod->exports) pushIfFn(kv);
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
} else if (obj.isDict()) {
|
||||||
|
const auto& d = obj.asDict();
|
||||||
|
for (const auto& kv : d) pushIfFn(kv);
|
||||||
}
|
}
|
||||||
|
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("sleep", Value(sleepFunc));
|
env->define("functions", Value(functionsFunc));
|
||||||
|
interpreter.addBuiltinFunction(functionsFunc);
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// random moved to rand module
|
||||||
interpreter.addBuiltinFunction(sleepFunc);
|
|
||||||
|
|
||||||
// Create a built-in random function
|
// (eval and evalFile moved to eval module)
|
||||||
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));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// (file I/O moved to io module)
|
||||||
interpreter.addBuiltinFunction(randomFunc);
|
|
||||||
|
|
||||||
// Create a built-in eval function (like Python's eval)
|
// memoryUsage moved to sys module
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -786,9 +786,9 @@ print("Comprehensive number tests: PASS");
|
|||||||
// TEST 29: TIME FUNCTION
|
// TEST 29: TIME FUNCTION
|
||||||
// ========================================
|
// ========================================
|
||||||
print("\n--- Test 29: Time Function ---");
|
print("\n--- Test 29: Time Function ---");
|
||||||
|
import time;
|
||||||
var start_time = time();
|
var start_time = time.now();
|
||||||
var end_time = time();
|
var end_time = time.now();
|
||||||
var duration = end_time - start_time;
|
var duration = end_time - start_time;
|
||||||
assert(start_time > 0, "Start time should be positive");
|
assert(start_time > 0, "Start time should be positive");
|
||||||
assert(end_time >= start_time, "End time should be >= start time");
|
assert(end_time >= start_time, "End time should be >= start time");
|
||||||
@ -2310,15 +2310,15 @@ print("\n--- Test 50: Arrays ---");
|
|||||||
|
|
||||||
// Array creation
|
// Array creation
|
||||||
var emptyArray = [];
|
var emptyArray = [];
|
||||||
assert(len(emptyArray) == 0, "Empty array length");
|
assert(emptyArray.len() == 0, "Empty array length");
|
||||||
|
|
||||||
var numberArray = [1, 2, 3, 4, 5];
|
var numberArray = [1, 2, 3, 4, 5];
|
||||||
assert(len(numberArray) == 5, "Number array length");
|
assert(numberArray.len() == 5, "Number array length");
|
||||||
assert(numberArray[0] == 1, "Array indexing - first element");
|
assert(numberArray[0] == 1, "Array indexing - first element");
|
||||||
assert(numberArray[4] == 5, "Array indexing - last element");
|
assert(numberArray[4] == 5, "Array indexing - last element");
|
||||||
|
|
||||||
var mixedArray = [1, "hello", true, 3.14];
|
var mixedArray = [1, "hello", true, 3.14];
|
||||||
assert(len(mixedArray) == 4, "Mixed array length");
|
assert(mixedArray.len() == 4, "Mixed array length");
|
||||||
assert(mixedArray[0] == 1, "Mixed array - number");
|
assert(mixedArray[0] == 1, "Mixed array - number");
|
||||||
assert(mixedArray[1] == "hello", "Mixed array - string");
|
assert(mixedArray[1] == "hello", "Mixed array - string");
|
||||||
assert(mixedArray[2] == true, "Mixed array - boolean");
|
assert(mixedArray[2] == true, "Mixed array - boolean");
|
||||||
@ -2333,35 +2333,35 @@ assert(mixedArray[1] == "world", "Array string assignment");
|
|||||||
|
|
||||||
// Array operations
|
// Array operations
|
||||||
var testArray = [1, 2, 3];
|
var testArray = [1, 2, 3];
|
||||||
push(testArray, 4);
|
testArray.push(4);
|
||||||
assert(len(testArray) == 4, "Array push - length");
|
assert(testArray.len() == 4, "Array push - length");
|
||||||
assert(testArray[3] == 4, "Array push - value");
|
assert(testArray[3] == 4, "Array push - value");
|
||||||
|
|
||||||
var poppedValue = pop(testArray);
|
var poppedValue = testArray.pop();
|
||||||
assert(poppedValue == 4, "Array pop - returned value");
|
assert(poppedValue == 4, "Array pop - returned value");
|
||||||
assert(len(testArray) == 3, "Array pop - length");
|
assert(testArray.len() == 3, "Array pop - length");
|
||||||
|
|
||||||
// Array with nested arrays
|
// Array with nested arrays
|
||||||
var nestedArray = [[1, 2], [3, 4], [5, 6]];
|
var nestedArray = [[1, 2], [3, 4], [5, 6]];
|
||||||
assert(len(nestedArray) == 3, "Nested array length");
|
assert(nestedArray.len() == 3, "Nested array length");
|
||||||
assert(nestedArray[0][0] == 1, "Nested array indexing");
|
assert(nestedArray[0][0] == 1, "Nested array indexing");
|
||||||
|
|
||||||
// Array with function calls
|
// Array with function calls
|
||||||
var funcArray = [func() { return 42; }, func() { return "hello"; }];
|
var funcArray = [func() { return 42; }, func() { return "hello"; }];
|
||||||
assert(len(funcArray) == 2, "Function array length");
|
assert(funcArray.len() == 2, "Function array length");
|
||||||
assert(funcArray[0]() == 42, "Function array - first function");
|
assert(funcArray[0]() == 42, "Function array - first function");
|
||||||
assert(funcArray[1]() == "hello", "Function array - second function");
|
assert(funcArray[1]() == "hello", "Function array - second function");
|
||||||
|
|
||||||
// Array edge cases
|
// Array edge cases
|
||||||
var singleElement = [42];
|
var singleElement = [42];
|
||||||
assert(len(singleElement) == 1, "Single element array");
|
assert(singleElement.len() == 1, "Single element array");
|
||||||
assert(singleElement[0] == 42, "Single element access");
|
assert(singleElement[0] == 42, "Single element access");
|
||||||
|
|
||||||
var largeArray = [];
|
var largeArray = [];
|
||||||
for (var i = 0; i < 100; i = i + 1) {
|
for (var i = 0; i < 100; i = i + 1) {
|
||||||
push(largeArray, i);
|
largeArray.push(i);
|
||||||
}
|
}
|
||||||
assert(len(largeArray) == 100, "Large array creation");
|
assert(largeArray.len() == 100, "Large array creation");
|
||||||
assert(largeArray[50] == 50, "Large array access");
|
assert(largeArray[50] == 50, "Large array access");
|
||||||
|
|
||||||
print("Arrays: PASS");
|
print("Arrays: PASS");
|
||||||
@ -2373,40 +2373,40 @@ print("\n--- Test 51: Array Built-in Functions ---");
|
|||||||
|
|
||||||
// len() function
|
// len() function
|
||||||
var testLenArray = [1, 2, 3, 4, 5];
|
var testLenArray = [1, 2, 3, 4, 5];
|
||||||
assert(len(testLenArray) == 5, "len() with array");
|
assert(testLenArray.len() == 5, "len() with array");
|
||||||
|
|
||||||
var emptyLenArray = [];
|
var emptyLenArray = [];
|
||||||
assert(len(emptyLenArray) == 0, "len() with empty array");
|
assert(emptyLenArray.len() == 0, "len() with empty array");
|
||||||
|
|
||||||
// len() with strings
|
// len() with strings
|
||||||
assert(len("hello") == 5, "len() with string");
|
assert("hello".len() == 5, "len() with string");
|
||||||
assert(len("") == 0, "len() with empty string");
|
assert("".len() == 0, "len() with empty string");
|
||||||
|
|
||||||
// push() function
|
// push() function
|
||||||
var pushArray = [1, 2, 3];
|
var pushArray = [1, 2, 3];
|
||||||
push(pushArray, 4);
|
pushArray.push(4);
|
||||||
assert(len(pushArray) == 4, "push() - length check");
|
assert(pushArray.len() == 4, "push() - length check");
|
||||||
assert(pushArray[3] == 4, "push() - value check");
|
assert(pushArray[3] == 4, "push() - value check");
|
||||||
|
|
||||||
push(pushArray, "hello");
|
pushArray.push("hello");
|
||||||
assert(len(pushArray) == 5, "push() - mixed types");
|
assert(pushArray.len() == 5, "push() - mixed types");
|
||||||
assert(pushArray[4] == "hello", "push() - string value");
|
assert(pushArray[4] == "hello", "push() - string value");
|
||||||
|
|
||||||
// pop() function
|
// pop() function
|
||||||
var popArray = [1, 2, 3, 4];
|
var popArray = [1, 2, 3, 4];
|
||||||
var popped1 = pop(popArray);
|
var popped1 = popArray.pop();
|
||||||
assert(popped1 == 4, "pop() - returned value");
|
assert(popped1 == 4, "pop() - returned value");
|
||||||
assert(len(popArray) == 3, "pop() - length after pop");
|
assert(popArray.len() == 3, "pop() - length after pop");
|
||||||
|
|
||||||
var popped2 = pop(popArray);
|
var popped2 = popArray.pop();
|
||||||
assert(popped2 == 3, "pop() - second pop");
|
assert(popped2 == 3, "pop() - second pop");
|
||||||
assert(len(popArray) == 2, "pop() - length after second pop");
|
assert(popArray.len() == 2, "pop() - length after second pop");
|
||||||
|
|
||||||
// pop() edge cases
|
// pop() edge cases
|
||||||
var singlePopArray = [42];
|
var singlePopArray = [42];
|
||||||
var singlePopped = pop(singlePopArray);
|
var singlePopped = singlePopArray.pop();
|
||||||
assert(singlePopped == 42, "pop() - single element");
|
assert(singlePopped == 42, "pop() - single element");
|
||||||
assert(len(singlePopArray) == 0, "pop() - empty after single pop");
|
assert(singlePopArray.len() == 0, "pop() - empty after single pop");
|
||||||
|
|
||||||
print("Array built-in functions: PASS");
|
print("Array built-in functions: PASS");
|
||||||
|
|
||||||
@ -2416,15 +2416,17 @@ print("Array built-in functions: PASS");
|
|||||||
print("\n--- Test 52: New Built-in Functions ---");
|
print("\n--- Test 52: New Built-in Functions ---");
|
||||||
|
|
||||||
// sleep() function
|
// sleep() function
|
||||||
var startTime = time();
|
import time as T;
|
||||||
sleep(0.001); // Sleep for 1ms (much shorter for testing)
|
var startTime = T.now();
|
||||||
var endTime = time();
|
T.sleep(0.01); // Sleep briefly to ensure elapsed time
|
||||||
|
var endTime = T.now();
|
||||||
assert(endTime > startTime, "sleep() - time elapsed");
|
assert(endTime > startTime, "sleep() - time elapsed");
|
||||||
|
|
||||||
// random() function - test proper seeding
|
// random() function - test proper seeding
|
||||||
var random1 = random();
|
import rand as R;
|
||||||
var random2 = random();
|
var random1 = R.random();
|
||||||
var random3 = random();
|
var random2 = R.random();
|
||||||
|
var random3 = R.random();
|
||||||
assert(random1 >= 0 && random1 <= 1, "random() - range check 1");
|
assert(random1 >= 0 && random1 <= 1, "random() - range check 1");
|
||||||
assert(random2 >= 0 && random2 <= 1, "random() - range check 2");
|
assert(random2 >= 0 && random2 <= 1, "random() - range check 2");
|
||||||
assert(random3 >= 0 && random3 <= 1, "random() - range check 3");
|
assert(random3 >= 0 && random3 <= 1, "random() - range check 3");
|
||||||
@ -2432,30 +2434,44 @@ assert(random3 >= 0 && random3 <= 1, "random() - range check 3");
|
|||||||
assert(random1 != random2 || random2 != random3 || random1 != random3, "random() - different values");
|
assert(random1 != random2 || random2 != random3 || random1 != random3, "random() - different values");
|
||||||
|
|
||||||
// Test random number generation in different ranges
|
// Test random number generation in different ranges
|
||||||
var randomRange1 = random() * 10;
|
var randomRange1 = R.random() * 10;
|
||||||
var randomRange2 = random() * 100;
|
var randomRange2 = R.random() * 100;
|
||||||
assert(randomRange1 >= 0 && randomRange1 <= 10, "random() - range 0-10");
|
assert(randomRange1 >= 0 && randomRange1 <= 10, "random() - range 0-10");
|
||||||
assert(randomRange2 >= 0 && randomRange2 <= 100, "random() - range 0-100");
|
assert(randomRange2 >= 0 && randomRange2 <= 100, "random() - range 0-100");
|
||||||
|
|
||||||
// eval() function
|
// eval() function (now via eval module)
|
||||||
eval("var evalVar = 42;");
|
import eval as E;
|
||||||
|
E.eval("var evalVar = 42;");
|
||||||
assert(evalVar == 42, "eval() - variable creation");
|
assert(evalVar == 42, "eval() - variable creation");
|
||||||
|
|
||||||
eval("print(\"eval test\");"); // Should print "eval test"
|
E.eval("print(\"eval test\");"); // Should print "eval test"
|
||||||
|
|
||||||
var evalResult = eval("2 + 2;");
|
var evalResult = E.eval("2 + 2;");
|
||||||
// eval() currently returns none, so we just test it doesn't crash
|
// eval() currently returns none, so we just test it doesn't crash
|
||||||
|
|
||||||
// Test eval with complex expressions
|
// Test eval with complex expressions
|
||||||
eval("var complexVar = [1, 2, 3];");
|
E.eval("var complexVar = [1, 2, 3];");
|
||||||
assert(len(complexVar) == 3, "eval() - complex expression");
|
assert(complexVar.len() == 3, "eval() - complex expression");
|
||||||
|
|
||||||
// Test eval with function definitions
|
// Test eval with function definitions
|
||||||
eval("func evalFunc(x) { return x * 2; }");
|
E.eval("func evalFunc(x) { return x * 2; }");
|
||||||
assert(evalFunc(5) == 10, "eval() - function definition");
|
assert(evalFunc(5) == 10, "eval() - function definition");
|
||||||
|
|
||||||
print("New built-in functions: PASS");
|
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
|
// TEST 52.5: EXIT FUNCTION
|
||||||
// ========================================
|
// ========================================
|
||||||
@ -2479,9 +2495,9 @@ print("\n--- Test 52.6: Enhanced Dictionary Tests ---");
|
|||||||
|
|
||||||
// Test dictionary with none values
|
// Test dictionary with none values
|
||||||
var dictWithNone = {"name": "Bob", "age": none, "city": "SF"};
|
var dictWithNone = {"name": "Bob", "age": none, "city": "SF"};
|
||||||
assert(has(dictWithNone, "name"), "Dictionary has() - existing key");
|
assert(dictWithNone.has("name"), "Dictionary has() - existing key");
|
||||||
assert(has(dictWithNone, "age"), "Dictionary has() - none value");
|
assert(dictWithNone.has("age"), "Dictionary has() - none value");
|
||||||
assert(!has(dictWithNone, "phone"), "Dictionary has() - missing key");
|
assert(!dictWithNone.has("phone"), "Dictionary has() - missing key");
|
||||||
|
|
||||||
// Test setting keys to none
|
// Test setting keys to none
|
||||||
dictWithNone["age"] = none;
|
dictWithNone["age"] = none;
|
||||||
@ -2489,18 +2505,18 @@ assert(dictWithNone["age"] == none, "Dictionary - setting key to none");
|
|||||||
|
|
||||||
// Test dictionary with all none values
|
// Test dictionary with all none values
|
||||||
var allNoneDict = {"a": none, "b": none, "c": none};
|
var allNoneDict = {"a": none, "b": none, "c": none};
|
||||||
var allKeys = keys(allNoneDict);
|
var allKeys = ({"a": none, "b": none, "c": none}).keys();
|
||||||
var allValues = values(allNoneDict);
|
var allValues = ({"a": none, "b": none, "c": none}).values();
|
||||||
assert(len(allKeys) == 3, "Dictionary - all none keys count");
|
assert(allKeys.len() == 3, "Dictionary - all none keys count");
|
||||||
assert(len(allValues) == 3, "Dictionary - all none values count");
|
assert(allValues.len() == 3, "Dictionary - all none values count");
|
||||||
|
|
||||||
// Test dictionary stress test
|
// Test dictionary stress test
|
||||||
var stressDict = {};
|
var stressDict = {};
|
||||||
for (var i = 0; i < 100; i = i + 1) {
|
for (var i = 0; i < 100; i = i + 1) {
|
||||||
stressDict["key" + toString(i)] = i;
|
stressDict["key" + toString(i)] = i;
|
||||||
}
|
}
|
||||||
assert(len(keys(stressDict)) == 100, "Dictionary stress test - keys");
|
assert(stressDict.keys().len() == 100, "Dictionary stress test - keys");
|
||||||
assert(len(values(stressDict)) == 100, "Dictionary stress test - values");
|
assert(stressDict.values().len() == 100, "Dictionary stress test - values");
|
||||||
|
|
||||||
print("Enhanced dictionary tests: PASS");
|
print("Enhanced dictionary tests: PASS");
|
||||||
|
|
||||||
@ -2551,15 +2567,15 @@ assert(emptyDict.length == 0, "Empty dict length");
|
|||||||
assert(emptyDict.empty == true, "Empty dict empty");
|
assert(emptyDict.empty == true, "Empty dict empty");
|
||||||
|
|
||||||
// Test dict.keys and dict.values properties
|
// Test dict.keys and dict.values properties
|
||||||
assert(len(dictWithProps.keys) == 3, "Dict keys property length");
|
assert(dictWithProps.keys.len() == 3, "Dict keys property length");
|
||||||
assert(len(dictWithProps.values) == 3, "Dict values property length");
|
assert(dictWithProps.values.len() == 3, "Dict values property length");
|
||||||
assert(len(emptyDict.keys) == 0, "Empty dict keys length");
|
assert(emptyDict.keys.len() == 0, "Empty dict keys length");
|
||||||
assert(len(emptyDict.values) == 0, "Empty dict values length");
|
assert(emptyDict.values.len() == 0, "Empty dict values length");
|
||||||
|
|
||||||
// Test equivalence with old functions
|
// Test equivalence with old functions
|
||||||
var testDict = {"x": 10, "y": 20};
|
var testDict = {"x": 10, "y": 20};
|
||||||
assert(len(testDict.keys) == len(keys(testDict)), "Dict keys equivalent to keys() function");
|
assert(testDict.keys.len() == testDict.keys.len(), "Dict keys property works");
|
||||||
assert(len(testDict.values) == len(values(testDict)), "Dict values equivalent to values() function");
|
assert(testDict.values.len() == testDict.values.len(), "Dict values property works");
|
||||||
print(" Dict builtin properties: PASS");
|
print(" Dict builtin properties: PASS");
|
||||||
|
|
||||||
// Nested property access and assignment
|
// Nested property access and assignment
|
||||||
@ -2629,7 +2645,7 @@ print("\n--- Test 52.8: Memory Management ---");
|
|||||||
for (var i = 0; i < 1000; i = i + 1) {
|
for (var i = 0; i < 1000; i = i + 1) {
|
||||||
var largeArray = [];
|
var largeArray = [];
|
||||||
for (var j = 0; j < 100; j = j + 1) {
|
for (var j = 0; j < 100; j = j + 1) {
|
||||||
push(largeArray, "string" + toString(j));
|
largeArray.push("string" + toString(j));
|
||||||
}
|
}
|
||||||
|
|
||||||
var largeDict = {};
|
var largeDict = {};
|
||||||
@ -2703,7 +2719,7 @@ var errorArray = [1, 2, 3];
|
|||||||
// Test array bounds checking
|
// Test array bounds checking
|
||||||
// Note: These would cause runtime errors in the actual implementation
|
// Note: These would cause runtime errors in the actual implementation
|
||||||
// For now, we test that the array operations work correctly
|
// For now, we test that the array operations work correctly
|
||||||
assert(len(errorArray) == 3, "Array bounds - valid length");
|
assert(errorArray.len() == 3, "Array bounds - valid length");
|
||||||
|
|
||||||
// Test with valid indices
|
// Test with valid indices
|
||||||
assert(errorArray[0] == 1, "Array bounds - valid index 0");
|
assert(errorArray[0] == 1, "Array bounds - valid index 0");
|
||||||
@ -2718,15 +2734,15 @@ print("\n--- Test 54: Array Performance ---");
|
|||||||
|
|
||||||
// Test large array operations
|
// Test large array operations
|
||||||
var perfArray = [];
|
var perfArray = [];
|
||||||
var startTime = time();
|
import time as T; var startTime = T.now();
|
||||||
|
|
||||||
// Create large array
|
// Create large array
|
||||||
for (var i = 0; i < 1000; i = i + 1) {
|
for (var i = 0; i < 1000; i = i + 1) {
|
||||||
push(perfArray, i);
|
perfArray.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
var midTime = time();
|
var midTime = T.now();
|
||||||
assert(len(perfArray) == 1000, "Array performance - creation");
|
assert(perfArray.len() == 1000, "Array performance - creation");
|
||||||
|
|
||||||
// Access elements
|
// Access elements
|
||||||
for (var j = 0; j < 100; j = j + 1) {
|
for (var j = 0; j < 100; j = j + 1) {
|
||||||
@ -2734,7 +2750,7 @@ for (var j = 0; j < 100; j = j + 1) {
|
|||||||
assert(value == j * 10, "Array performance - access");
|
assert(value == j * 10, "Array performance - access");
|
||||||
}
|
}
|
||||||
|
|
||||||
var endTime = time();
|
var endTime = T.now();
|
||||||
assert(endTime > startTime, "Array performance - time check");
|
assert(endTime > startTime, "Array performance - time check");
|
||||||
|
|
||||||
print("Array performance: PASS");
|
print("Array performance: PASS");
|
||||||
@ -2751,7 +2767,7 @@ var funcArray2 = [
|
|||||||
func() { return 3; }
|
func() { return 3; }
|
||||||
];
|
];
|
||||||
|
|
||||||
assert(len(funcArray2) == 3, "Function array length");
|
assert(funcArray2.len() == 3, "Function array length");
|
||||||
assert(funcArray2[0]() == 1, "Function array - call 1");
|
assert(funcArray2[0]() == 1, "Function array - call 1");
|
||||||
assert(funcArray2[1]() == 2, "Function array - call 2");
|
assert(funcArray2[1]() == 2, "Function array - call 2");
|
||||||
assert(funcArray2[2]() == 3, "Function array - call 3");
|
assert(funcArray2[2]() == 3, "Function array - call 3");
|
||||||
@ -2762,18 +2778,18 @@ func createArray() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var returnedArray = createArray();
|
var returnedArray = createArray();
|
||||||
assert(len(returnedArray) == 5, "Function returning array");
|
assert(returnedArray.len() == 5, "Function returning array");
|
||||||
assert(returnedArray[0] == 1, "Function returning array - first element");
|
assert(returnedArray[0] == 1, "Function returning array - first element");
|
||||||
|
|
||||||
// Function that modifies array
|
// Function that modifies array
|
||||||
func modifyArray(arr) {
|
func modifyArray(arr) {
|
||||||
push(arr, 999);
|
arr.push(999);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modArray = [1, 2, 3];
|
var modArray = [1, 2, 3];
|
||||||
var modifiedArray = modifyArray(modArray);
|
var modifiedArray = modifyArray(modArray);
|
||||||
assert(len(modifiedArray) == 4, "Function modifying array");
|
assert(modifiedArray.len() == 4, "Function modifying array");
|
||||||
assert(modifiedArray[3] == 999, "Function modifying array - new value");
|
assert(modifiedArray[3] == 999, "Function modifying array - new value");
|
||||||
|
|
||||||
print("Array with functions: PASS");
|
print("Array with functions: PASS");
|
||||||
@ -2785,11 +2801,11 @@ print("\n--- Test 56: Array Edge Cases and Advanced Features ---");
|
|||||||
|
|
||||||
// Array with none values
|
// Array with none values
|
||||||
var noneArray = [1, none, 3, none, 5];
|
var noneArray = [1, none, 3, none, 5];
|
||||||
assert(len(noneArray) == 5, "Array with none values - length");
|
assert(noneArray.len() == 5, "Array with none values - length");
|
||||||
// Note: Bob doesn't support comparing none values with ==
|
// Note: Bob doesn't support comparing none values with ==
|
||||||
// We can test that the array contains the expected number of elements
|
// We can test that the array contains the expected number of elements
|
||||||
var noneCount = 0;
|
var noneCount = 0;
|
||||||
for (var i = 0; i < len(noneArray); i = i + 1) {
|
for (var i = 0; i < noneArray.len(); i = i + 1) {
|
||||||
if (type(noneArray[i]) == "none") {
|
if (type(noneArray[i]) == "none") {
|
||||||
noneCount = noneCount + 1;
|
noneCount = noneCount + 1;
|
||||||
}
|
}
|
||||||
@ -2798,7 +2814,7 @@ assert(noneCount == 2, "Array with none values - none count");
|
|||||||
|
|
||||||
// Array with complex expressions
|
// Array with complex expressions
|
||||||
var complexArray = [1 + 1, 2 * 3, 10 / 2, 5 - 2];
|
var complexArray = [1 + 1, 2 * 3, 10 / 2, 5 - 2];
|
||||||
assert(len(complexArray) == 4, "Complex array - length");
|
assert(complexArray.len() == 4, "Complex array - length");
|
||||||
assert(complexArray[0] == 2, "Complex array - addition");
|
assert(complexArray[0] == 2, "Complex array - addition");
|
||||||
assert(complexArray[1] == 6, "Complex array - multiplication");
|
assert(complexArray[1] == 6, "Complex array - multiplication");
|
||||||
assert(complexArray[2] == 5, "Complex array - division");
|
assert(complexArray[2] == 5, "Complex array - division");
|
||||||
@ -2806,7 +2822,7 @@ assert(complexArray[3] == 3, "Complex array - subtraction");
|
|||||||
|
|
||||||
// Array with string operations
|
// Array with string operations
|
||||||
var stringArray = ["hello" + " world", "test" * 2, "a" + "b" + "c"];
|
var stringArray = ["hello" + " world", "test" * 2, "a" + "b" + "c"];
|
||||||
assert(len(stringArray) == 3, "String array - length");
|
assert(stringArray.len() == 3, "String array - length");
|
||||||
assert(stringArray[0] == "hello world", "String array - concatenation");
|
assert(stringArray[0] == "hello world", "String array - concatenation");
|
||||||
assert(stringArray[1] == "testtest", "String array - multiplication");
|
assert(stringArray[1] == "testtest", "String array - multiplication");
|
||||||
assert(stringArray[2] == "abc", "String array - multiple concatenation");
|
assert(stringArray[2] == "abc", "String array - multiple concatenation");
|
||||||
@ -2816,14 +2832,14 @@ func getValue() { return 42; }
|
|||||||
func getString() { return "hello"; }
|
func getString() { return "hello"; }
|
||||||
|
|
||||||
var funcResultArray = [getValue(), getString(), 1 + 2];
|
var funcResultArray = [getValue(), getString(), 1 + 2];
|
||||||
assert(len(funcResultArray) == 3, "Function result array - length");
|
assert(funcResultArray.len() == 3, "Function result array - length");
|
||||||
assert(funcResultArray[0] == 42, "Function result array - function call");
|
assert(funcResultArray[0] == 42, "Function result array - function call");
|
||||||
assert(funcResultArray[1] == "hello", "Function result array - string function");
|
assert(funcResultArray[1] == "hello", "Function result array - string function");
|
||||||
assert(funcResultArray[2] == 3, "Function result array - expression");
|
assert(funcResultArray[2] == 3, "Function result array - expression");
|
||||||
|
|
||||||
// Array with ternary operators
|
// Array with ternary operators
|
||||||
var ternaryArray = [true ? 1 : 0, false ? "yes" : "no", 5 > 3 ? "big" : "small"];
|
var ternaryArray = [true ? 1 : 0, false ? "yes" : "no", 5 > 3 ? "big" : "small"];
|
||||||
assert(len(ternaryArray) == 3, "Ternary array - length");
|
assert(ternaryArray.len() == 3, "Ternary array - length");
|
||||||
assert(ternaryArray[0] == 1, "Ternary array - true condition");
|
assert(ternaryArray[0] == 1, "Ternary array - true condition");
|
||||||
assert(ternaryArray[1] == "no", "Ternary array - false condition");
|
assert(ternaryArray[1] == "no", "Ternary array - false condition");
|
||||||
assert(ternaryArray[2] == "big", "Ternary array - comparison condition");
|
assert(ternaryArray[2] == "big", "Ternary array - comparison condition");
|
||||||
@ -2835,13 +2851,13 @@ assert(nestedOpArray[0][0] == 99, "Nested array operations - assignment");
|
|||||||
|
|
||||||
// Array with push/pop in expressions
|
// Array with push/pop in expressions
|
||||||
var exprArray = [1, 2, 3];
|
var exprArray = [1, 2, 3];
|
||||||
var pushResult = push(exprArray, 4);
|
var pushResult = exprArray.push(4);
|
||||||
assert(len(exprArray) == 4, "Array push in expression");
|
assert(exprArray.len() == 4, "Array push in expression");
|
||||||
assert(exprArray[3] == 4, "Array push result");
|
assert(exprArray[3] == 4, "Array push result");
|
||||||
|
|
||||||
var popResult = pop(exprArray);
|
var popResult = exprArray.pop();
|
||||||
assert(popResult == 4, "Array pop result");
|
assert(popResult == 4, "Array pop result");
|
||||||
assert(len(exprArray) == 3, "Array length after pop");
|
assert(exprArray.len() == 3, "Array length after pop");
|
||||||
|
|
||||||
print("Array edge cases and advanced features: PASS");
|
print("Array edge cases and advanced features: PASS");
|
||||||
|
|
||||||
@ -2852,31 +2868,31 @@ print("\n--- Test 57: Array Stress Testing ---");
|
|||||||
|
|
||||||
// Large array creation and manipulation
|
// Large array creation and manipulation
|
||||||
var stressArray = [];
|
var stressArray = [];
|
||||||
var startTime = time();
|
import time as T2; var startTime = T2.now();
|
||||||
|
|
||||||
// Create large array with mixed types
|
// Create large array with mixed types
|
||||||
for (var i = 0; i < 500; i = i + 1) {
|
for (var i = 0; i < 500; i = i + 1) {
|
||||||
if (i % 3 == 0) {
|
if (i % 3 == 0) {
|
||||||
push(stressArray, i);
|
stressArray.push(i);
|
||||||
} else if (i % 3 == 1) {
|
} else if (i % 3 == 1) {
|
||||||
push(stressArray, "string" + toString(i));
|
stressArray.push("string" + toString(i));
|
||||||
} else {
|
} else {
|
||||||
push(stressArray, i > 250);
|
stressArray.push(i > 250);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var midTime = time();
|
var midTime = T2.now();
|
||||||
assert(len(stressArray) == 500, "Stress test - array creation");
|
assert(stressArray.len() == 500, "Stress test - array creation");
|
||||||
|
|
||||||
// Access and modify elements
|
// Access and modify elements
|
||||||
for (var j = 0; j < 100; j = j + 1) {
|
for (var j = 0; j < 100; j = j + 1) {
|
||||||
var index = j * 5;
|
var index = j * 5;
|
||||||
if (index < len(stressArray)) {
|
if (index < stressArray.len()) {
|
||||||
stressArray[index] = "modified" + toString(j);
|
stressArray[index] = "modified" + toString(j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var endTime = time();
|
var endTime = T2.now();
|
||||||
assert(endTime > startTime, "Stress test - time validation");
|
assert(endTime > startTime, "Stress test - time validation");
|
||||||
|
|
||||||
// Verify modifications
|
// Verify modifications
|
||||||
@ -2992,7 +3008,8 @@ assert(toInt(-0.0) == 0, "toInt(-0.0) should be 0");
|
|||||||
|
|
||||||
// Test with random numbers
|
// Test with random numbers
|
||||||
for(var i = 0; i < 5; i++) {
|
for(var i = 0; i < 5; i++) {
|
||||||
var randomFloat = random() * 10;
|
import rand as Rn;
|
||||||
|
var randomFloat = Rn.random() * 10;
|
||||||
var randomInt = toInt(randomFloat);
|
var randomInt = toInt(randomFloat);
|
||||||
assert(randomInt >= 0 && randomInt <= 9, "toInt(random) should be in range 0-9");
|
assert(randomInt >= 0 && randomInt <= 9, "toInt(random) should be in range 0-9");
|
||||||
}
|
}
|
||||||
@ -3271,5 +3288,61 @@ print("- Interactive Mode Features");
|
|||||||
print(" * REPL functionality");
|
print(" * REPL functionality");
|
||||||
print(" * Error reporting in both modes");
|
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("\nAll tests passed.");
|
||||||
print("Test suite complete.");
|
print("Test suite complete.");
|
||||||
139
tests.bob
139
tests.bob
@ -1,53 +1,96 @@
|
|||||||
var a = [];
|
// // var a = [];
|
||||||
|
|
||||||
for(var i = 0; i < 1000000; i++){
|
// // for(var i = 0; i < 1000000; i++){
|
||||||
print(i);
|
// // print(i);
|
||||||
|
|
||||||
// Create nested structures with functions at different levels
|
// // // Create nested structures with functions at different levels
|
||||||
if (i % 4 == 0) {
|
// // if (i % 4 == 0) {
|
||||||
// Nested array with function
|
// // // Nested array with function
|
||||||
push(a, [
|
// // push(a, [
|
||||||
func(){print("Array nested func i=" + i); return i;},
|
// // func(){print("Array nested func i=" + i); return i;},
|
||||||
[func(){return "Deep array func " + i;}],
|
// // [func(){return "Deep array func " + i;}],
|
||||||
i
|
// // i
|
||||||
]);
|
// // ]);
|
||||||
} else if (i % 4 == 1) {
|
// // } else if (i % 4 == 1) {
|
||||||
// Nested dict with function
|
// // // Nested dict with function
|
||||||
push(a, {
|
// // push(a, {
|
||||||
"func": func(){print("Dict func i=" + i); return i;},
|
// // "func": func(){print("Dict func i=" + i); return i;},
|
||||||
"nested": {"deepFunc": func(){return "Deep dict func " + i;}},
|
// // "nested": {"deepFunc": func(){return "Deep dict func " + i;}},
|
||||||
"value": i
|
// // "value": i
|
||||||
});
|
// // });
|
||||||
} else if (i % 4 == 2) {
|
// // } else if (i % 4 == 2) {
|
||||||
// Mixed nested array/dict with functions
|
// // // Mixed nested array/dict with functions
|
||||||
push(a, [
|
// // push(a, [
|
||||||
{"arrayInDict": func(){return "Mixed " + i;}},
|
// // {"arrayInDict": func(){return "Mixed " + i;}},
|
||||||
[func(){return "Array in array " + i;}, {"more": func(){return i;}}],
|
// // [func(){return "Array in array " + i;}, {"more": func(){return i;}}],
|
||||||
func(){print("Top level in mixed i=" + i); return i;}
|
// // func(){print("Top level in mixed i=" + i); return i;}
|
||||||
]);
|
// // ]);
|
||||||
} else {
|
// // } else {
|
||||||
// Simple function (original test case)
|
// // // Simple function (original test case)
|
||||||
push(a, func(){print("Simple func i=" + i); return toString(i);});
|
// // push(a, func(){print("Simple func i=" + i); return toString(i);});
|
||||||
}
|
// // }
|
||||||
}
|
// // }
|
||||||
|
|
||||||
print("Before: " + len(a));
|
// // print("Before: " + len(a));
|
||||||
print("Memory usage: " + memoryUsage() + " MB");
|
// // print("Memory usage: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
// Test different types of nested function calls
|
// // // Test different types of nested function calls
|
||||||
a[3691](); // Simple function
|
// // a[3691](); // Simple function
|
||||||
if (len(a[3692]) > 0) {
|
// // if (len(a[3692]) > 0) {
|
||||||
a[3692][0](); // Nested array function
|
// // a[3692][0](); // Nested array function
|
||||||
}
|
// // }
|
||||||
if (a[3693]["func"]) {
|
// // if (a[3693]["func"]) {
|
||||||
a[3693]["func"](); // Nested dict function
|
// // a[3693]["func"](); // Nested dict function
|
||||||
}
|
// // }
|
||||||
print(a);
|
// // //print(a);
|
||||||
//writeFile("array_contents.txt", toString(a));
|
// // //writeFile("array_contents.txt", toString(a));
|
||||||
print("Array contents written to array_contents.txt");
|
// // print("Array contents written to array_contents.txt");
|
||||||
print("Memory before cleanup: " + memoryUsage() + " MB");
|
// // print("Memory before cleanup: " + memoryUsage() + " MB");
|
||||||
input("Press any key to free memory");
|
// // input("Press any key to free memory");
|
||||||
|
|
||||||
a = none;
|
// // a = none;
|
||||||
print("Memory after cleanup: " + memoryUsage() + " MB");
|
// // print("Memory after cleanup: " + memoryUsage() + " MB");
|
||||||
input("waiting...");
|
// // 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");
|
||||||
5
tests/debug_any_tag.bob
Normal file
5
tests/debug_any_tag.bob
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
extension any { func tag() { return "<" + toString(this) + ">"; } }
|
||||||
|
var plain = { "x": 1 };
|
||||||
|
print(plain.tag());
|
||||||
|
|
||||||
|
|
||||||
7
tests/debug_inheritance.bob
Normal file
7
tests/debug_inheritance.bob
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
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());
|
||||||
|
|
||||||
|
|
||||||
3
tests/debug_init_params.bob
Normal file
3
tests/debug_init_params.bob
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
class P { func init(f,l) { print(f + ":" + l); } }
|
||||||
|
var p = P("A","B");
|
||||||
|
|
||||||
8
tests/debug_init_person.bob
Normal file
8
tests/debug_init_person.bob
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class T {
|
||||||
|
var a = "";
|
||||||
|
func init(x, y) { this.a = x + y; }
|
||||||
|
}
|
||||||
|
var t = T("A","B");
|
||||||
|
print(t.a);
|
||||||
|
|
||||||
|
|
||||||
6
tests/debug_mixed_super.bob
Normal file
6
tests/debug_mixed_super.bob
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class M0 { func name() { return "M0"; } }
|
||||||
|
class M1 extends M0 {}
|
||||||
|
extension M1 { func name() { return super.name() + "*"; } }
|
||||||
|
print(M1().name());
|
||||||
|
|
||||||
|
|
||||||
12
tests/debug_super_chain.bob
Normal file
12
tests/debug_super_chain.bob
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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());
|
||||||
|
|
||||||
|
|
||||||
10
tests/debug_super_min.bob
Normal file
10
tests/debug_super_min.bob
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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());
|
||||||
|
|
||||||
|
|
||||||
5
tests/import_user_of_mod_hello.bob
Normal file
5
tests/import_user_of_mod_hello.bob
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// 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");
|
||||||
|
|
||||||
3
tests/mod_hello.bob
Normal file
3
tests/mod_hello.bob
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
var X = 5;
|
||||||
|
func greet(name) { return "hi " + name; }
|
||||||
|
|
||||||
11
tests/test_base64.bob
Normal file
11
tests/test_base64.bob
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
24
tests/test_builtin_methods_style.bob
Normal file
24
tests/test_builtin_methods_style.bob
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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()");
|
||||||
77
tests/test_class_and_extension.bob
Normal file
77
tests/test_class_and_extension.bob
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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).");
|
||||||
|
|
||||||
|
|
||||||
20
tests/test_class_basic.bob
Normal file
20
tests/test_class_basic.bob
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
52
tests/test_class_edge_cases.bob
Normal file
52
tests/test_class_edge_cases.bob
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
21
tests/test_class_extension_user.bob
Normal file
21
tests/test_class_extension_user.bob
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
40
tests/test_class_inheritance.bob
Normal file
40
tests/test_class_inheritance.bob
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
15
tests/test_class_init.bob
Normal file
15
tests/test_class_init.bob
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
85
tests/test_class_stress.bob
Normal file
85
tests/test_class_stress.bob
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
21
tests/test_class_super.bob
Normal file
21
tests/test_class_super.bob
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
15
tests/test_class_with_this.bob
Normal file
15
tests/test_class_with_this.bob
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
94
tests/test_classes_comprehensive.bob
Normal file
94
tests/test_classes_comprehensive.bob
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
92
tests/test_classes_extensive.bob
Normal file
92
tests/test_classes_extensive.bob
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
35
tests/test_extension_methods.bob
Normal file
35
tests/test_extension_methods.bob
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
10
tests/test_extension_syntax.bob
Normal file
10
tests/test_extension_syntax.bob
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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.");
|
||||||
|
|
||||||
|
|
||||||
5
tests/test_hash.bob
Normal file
5
tests/test_hash.bob
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
print("\n--- Test: hash module ---");
|
||||||
|
// Placeholder: implement when hash module is added
|
||||||
|
print("hash: SKIPPED");
|
||||||
|
|
||||||
|
|
||||||
48
tests/test_imports_basic.bob
Normal file
48
tests/test_imports_basic.bob
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
21
tests/test_imports_builtin.bob
Normal file
21
tests/test_imports_builtin.bob
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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
Loading…
Reference in New Issue
Block a user