Property Expression, Fixed memory leaks
This commit is contained in:
parent
87d56bbb13
commit
f70c6abd77
@ -73,6 +73,18 @@ target_include_directories(bob PRIVATE
|
|||||||
# Apply compiler options
|
# Apply compiler options
|
||||||
target_compile_options(bob PRIVATE ${BOB_COMPILE_OPTIONS})
|
target_compile_options(bob PRIVATE ${BOB_COMPILE_OPTIONS})
|
||||||
|
|
||||||
|
# Enable Link Time Optimization (LTO) for Release builds
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
include(CheckIPOSupported)
|
||||||
|
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
||||||
|
if(ipo_supported)
|
||||||
|
message(STATUS "IPO/LTO enabled")
|
||||||
|
set_property(TARGET bob PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||||
|
else()
|
||||||
|
message(WARNING "IPO/LTO not supported: ${ipo_error}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Platform-specific settings
|
# Platform-specific settings
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# Windows-specific settings
|
# Windows-specific settings
|
||||||
@ -142,4 +154,7 @@ message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
|
|||||||
message(STATUS " Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
message(STATUS " Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||||
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
||||||
message(STATUS " Install Prefix: ${CMAKE_INSTALL_PREFIX}")
|
message(STATUS " Install Prefix: ${CMAKE_INSTALL_PREFIX}")
|
||||||
message(STATUS " Source Files Found: ${BOB_ALL_SOURCES}")
|
message(STATUS " Runtime Sources: ${BOB_RUNTIME_SOURCES}")
|
||||||
|
message(STATUS " Parsing Sources: ${BOB_PARSING_SOURCES}")
|
||||||
|
message(STATUS " Stdlib Sources: ${BOB_STDLIB_SOURCES}")
|
||||||
|
message(STATUS " CLI Sources: ${BOB_CLI_SOURCES}")
|
||||||
@ -52,15 +52,40 @@ var nested = [[1, 2], [3, 4]];
|
|||||||
// Access and modify
|
// Access and modify
|
||||||
print(numbers[0]); // 1
|
print(numbers[0]); // 1
|
||||||
numbers[1] = 99;
|
numbers[1] = 99;
|
||||||
|
|
||||||
|
// Array properties (read-only)
|
||||||
|
print(numbers.length); // 3
|
||||||
|
print(numbers.first); // 1
|
||||||
|
print(numbers.last); // 3
|
||||||
|
print(numbers.empty); // false
|
||||||
|
|
||||||
|
var empty = [];
|
||||||
|
print(empty.length); // 0
|
||||||
|
print(empty.first); // none
|
||||||
|
print(empty.empty); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dictionaries
|
### Dictionaries
|
||||||
```go
|
```go
|
||||||
var person = {"name": "Alice", "age": 30};
|
var person = {"name": "Alice", "age": 30};
|
||||||
|
|
||||||
// Access and modify
|
// Access and modify (bracket notation)
|
||||||
print(person["name"]); // Alice
|
print(person["name"]); // Alice
|
||||||
person["city"] = "NYC";
|
person["city"] = "NYC";
|
||||||
|
|
||||||
|
// Access and modify (dot notation - cleaner syntax)
|
||||||
|
print(person.name); // Alice
|
||||||
|
person.city = "NYC";
|
||||||
|
person.age = 31;
|
||||||
|
|
||||||
|
// Both notations are equivalent
|
||||||
|
assert(person.name == person["name"]);
|
||||||
|
|
||||||
|
// Dictionary properties (built-in)
|
||||||
|
print(person.length); // 3 (number of key-value pairs)
|
||||||
|
print(person.empty); // false
|
||||||
|
print(person.keys); // ["name", "age", "city"]
|
||||||
|
print(person.values); // ["Alice", 31, "NYC"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
@ -229,7 +254,7 @@ 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
|
random(); // Random number 0-1
|
||||||
eval("1 + 2"); // Evaluate string as code
|
eval("print('Hello');"); // Execute string as code
|
||||||
exit(0); // Exit program
|
exit(0); // Exit program
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -342,7 +367,14 @@ for (var i = 0; i < len(lines); i = i + 1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile("output.txt", join(processed, "\n"));
|
var output = "";
|
||||||
|
for (var i = 0; i < len(processed); i = i + 1) {
|
||||||
|
output = output + processed[i];
|
||||||
|
if (i < len(processed) - 1) {
|
||||||
|
output = output + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeFile("output.txt", output);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -47,8 +47,8 @@ Bob is a mature, working programming language with a modern architecture and com
|
|||||||
#### **Data Structures (Complete)**
|
#### **Data Structures (Complete)**
|
||||||
- **Arrays**: Full support with indexing, assignment, nested arrays
|
- **Arrays**: Full support with indexing, assignment, nested arrays
|
||||||
- **Dictionaries**: Full support with key-value pairs, nested dictionaries
|
- **Dictionaries**: Full support with key-value pairs, nested dictionaries
|
||||||
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment
|
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment, properties (`length`, `first`, `last`, `empty`)
|
||||||
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment
|
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment, dot notation (`obj.prop`)
|
||||||
- **Mixed Types**: Arrays and dictionaries can hold any value types
|
- **Mixed Types**: Arrays and dictionaries can hold any value types
|
||||||
|
|
||||||
#### **Standard Library (Complete)**
|
#### **Standard Library (Complete)**
|
||||||
@ -169,7 +169,7 @@ var person = {
|
|||||||
- Add object literal syntax
|
- Add object literal syntax
|
||||||
- Implement `this` binding
|
- Implement `this` binding
|
||||||
- Support method calls
|
- Support method calls
|
||||||
- Add property access/assignment
|
- ✅ Add property access/assignment (completed - dot notation for dictionaries and arrays)
|
||||||
|
|
||||||
#### **Classes (Optional)**
|
#### **Classes (Optional)**
|
||||||
```bob
|
```bob
|
||||||
|
|||||||
56
examples/demo_features.bob
Normal file
56
examples/demo_features.bob
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// ===========================================================
|
||||||
|
// Bob feature-tour demo
|
||||||
|
// – dot-notation for dictionaries & arrays
|
||||||
|
// – property assignment (incl. nested)
|
||||||
|
// – built-in properties: dict.keys / dict.values
|
||||||
|
// arr.length / arr.first / arr.last / arr.empty
|
||||||
|
// – tail-call-optimised (TCO) recursion
|
||||||
|
// ===========================================================
|
||||||
|
|
||||||
|
// ---------- 1. Dictionary property access / assignment ----------
|
||||||
|
var person = { "name": "Alice", "age": 30 };
|
||||||
|
print(person.name);
|
||||||
|
print(person.age);
|
||||||
|
|
||||||
|
// add a brand-new user property
|
||||||
|
person.country = "Canada";
|
||||||
|
print(person.country);
|
||||||
|
|
||||||
|
// nested property assignment
|
||||||
|
var team = { "lead": { "name": "Bob", "age": 40 } };
|
||||||
|
team.lead.age = 41;
|
||||||
|
print(team.lead.age);
|
||||||
|
|
||||||
|
// built-in dictionary properties
|
||||||
|
print("dict length = " + team.length);
|
||||||
|
print(team.keys);
|
||||||
|
print(team.values[0].name);
|
||||||
|
|
||||||
|
// ---------- 2. Array dot-properties ----------
|
||||||
|
var nums = [ 10, 20, 30, 40 ];
|
||||||
|
print("len = " + nums.length);
|
||||||
|
print("first = " + nums.first);
|
||||||
|
print("last = " + nums.last);
|
||||||
|
print("empty? " + nums.empty);
|
||||||
|
|
||||||
|
// dot-properties are read-only; assignment still via index
|
||||||
|
nums[0] = 99;
|
||||||
|
print(nums.first);
|
||||||
|
|
||||||
|
// ---------- 3. Tail-call-optimised recursion ----------
|
||||||
|
func fibTail(n, a, b) {
|
||||||
|
return n <= 0 ? a : fibTail(n - 1, b, a + b);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Fast TCO fib(90) = " + fibTail(90, 0, 1));
|
||||||
|
print("Fast TCO fib(5000) = " + fibTail(5000, 0, 1));
|
||||||
|
|
||||||
|
// ---------- 4. Compare non-TCO version (stack grows!) ----------
|
||||||
|
func fibPlain(n) {
|
||||||
|
return n <= 1 ? n : fibPlain(n - 1) + fibPlain(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Slow recursive fib(35) = " + fibPlain(35));
|
||||||
|
|
||||||
|
// ---------- 5. Summary ----------
|
||||||
|
print("All new features demonstrated!");
|
||||||
95
leakTests/README.md
Normal file
95
leakTests/README.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Bob Memory Leak Test Suite
|
||||||
|
|
||||||
|
This directory contains comprehensive memory leak tests for the Bob programming language. Each test file focuses on different scenarios that could potentially cause memory leaks.
|
||||||
|
|
||||||
|
## Test Files
|
||||||
|
|
||||||
|
### `leaktest_functions.bob`
|
||||||
|
Tests function-related memory scenarios:
|
||||||
|
- Recursive function closures
|
||||||
|
- Function factories (functions returning functions)
|
||||||
|
- Deep function nesting
|
||||||
|
- Circular function references
|
||||||
|
- Expected behavior: Memory should be freed when functions are cleared
|
||||||
|
|
||||||
|
### `leaktest_collections.bob`
|
||||||
|
Tests collection (arrays/dictionaries) memory scenarios:
|
||||||
|
- Large nested arrays
|
||||||
|
- Large nested dictionaries
|
||||||
|
- Mixed array/dict structures
|
||||||
|
- Self-referencing structures
|
||||||
|
- Large string collections
|
||||||
|
- Expected behavior: Collections should be properly freed when reassigned
|
||||||
|
|
||||||
|
### `leaktest_mixed.bob`
|
||||||
|
Tests mixed type scenarios and edge cases:
|
||||||
|
- Functions capturing collections
|
||||||
|
- Collections containing functions and mixed types
|
||||||
|
- Dynamic property assignment patterns
|
||||||
|
- Type reassignment chains
|
||||||
|
- Rapid allocation/deallocation cycles
|
||||||
|
- Expected behavior: Memory should be freed regardless of type mixing
|
||||||
|
|
||||||
|
### `leaktest_loops.bob`
|
||||||
|
Tests memory behavior in loops and repetitive operations:
|
||||||
|
- Nested loop allocation
|
||||||
|
- While loop accumulation
|
||||||
|
- Variable reassignment in loops
|
||||||
|
- Do-while function creation
|
||||||
|
- Complex loop control flow
|
||||||
|
- Memory churn tests
|
||||||
|
- Expected behavior: Loop-created objects should be freed when variables are reassigned
|
||||||
|
|
||||||
|
### `leaktest_builtin.bob`
|
||||||
|
Tests builtin function and stdlib memory behavior:
|
||||||
|
- Heavy string operations
|
||||||
|
- Type conversion stress tests
|
||||||
|
- Array/Dict builtin operations
|
||||||
|
- Eval function stress tests
|
||||||
|
- File I/O operations
|
||||||
|
- Random number generation
|
||||||
|
- Expected behavior: Builtin operations should not leak memory
|
||||||
|
|
||||||
|
## How to Run Tests
|
||||||
|
|
||||||
|
Run each test individually and monitor memory usage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitor memory before, during, and after each test
|
||||||
|
./build-ninja/bin/bob leakTests/leaktest_functions.bob
|
||||||
|
./build-ninja/bin/bob leakTests/leaktest_collections.bob
|
||||||
|
./build-ninja/bin/bob leakTests/leaktest_mixed.bob
|
||||||
|
./build-ninja/bin/bob leakTests/leaktest_loops.bob
|
||||||
|
./build-ninja/bin/bob leakTests/leaktest_builtin.bob
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
After the memory leak fixes:
|
||||||
|
1. **Memory should increase** during object creation phases
|
||||||
|
2. **Memory should decrease** significantly when objects are cleared (set to `none`, `[]`, different types, etc.)
|
||||||
|
3. **Memory should return close to baseline** after each test section
|
||||||
|
4. **No gradual memory increase** across multiple test cycles
|
||||||
|
|
||||||
|
## Memory Monitoring
|
||||||
|
|
||||||
|
Use system tools to monitor memory:
|
||||||
|
- **macOS**: Activity Monitor or `top -pid $(pgrep bob)`
|
||||||
|
- **Linux**: `top`, `htop`, or `ps aux | grep bob`
|
||||||
|
- **Windows**: Task Manager or Process Monitor
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
- Memory spikes during creation phases ✅ Expected
|
||||||
|
- Memory drops after "cleared" messages ✅ Expected
|
||||||
|
- Memory staying high after clearing ❌ Potential leak
|
||||||
|
- Gradual increase across test cycles ❌ Potential leak
|
||||||
|
|
||||||
|
## Test Scenarios Covered
|
||||||
|
|
||||||
|
- **Object Types**: Functions, Arrays, Dictionaries, Strings, Numbers, Booleans
|
||||||
|
- **Memory Patterns**: Allocation, Deallocation, Reassignment, Type Changes
|
||||||
|
- **Edge Cases**: Circular references, Deep nesting, Self-references, Mixed types
|
||||||
|
- **Operations**: Loops, Builtin functions, File I/O, Type conversions
|
||||||
|
- **Cleanup Triggers**: Setting to `none`, `[]`, `{}`, different types, string values
|
||||||
|
|
||||||
|
This comprehensive test suite should help identify any remaining memory leak scenarios in the Bob interpreter.
|
||||||
172
leakTests/leaktest_builtin.bob
Normal file
172
leakTests/leaktest_builtin.bob
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// Memory leak test: Builtin function and stdlib scenarios
|
||||||
|
// Test memory behavior with builtin functions and standard library
|
||||||
|
|
||||||
|
print("=== Builtin Function Memory Leak Tests ===");
|
||||||
|
print("Initial memory: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Test 1: Heavy string operations
|
||||||
|
print("Test 1: Heavy string operations");
|
||||||
|
var stringData = [];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
var str = toString(i) + "_" + toString(i * 2) + "_" + toString(i * 3);
|
||||||
|
push(stringData, {
|
||||||
|
"original": str,
|
||||||
|
"upper": str, // Bob doesn't have toUpper, but test string storage
|
||||||
|
"length": len(str),
|
||||||
|
"type": type(str)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(stringData) + " string operation results");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear string data...");
|
||||||
|
stringData = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 2: Type conversion stress test
|
||||||
|
print("Test 2: Type conversion stress");
|
||||||
|
var conversions = [];
|
||||||
|
for (var i = 0; i < 200000; i++) {
|
||||||
|
var num = i * 1.5;
|
||||||
|
var str = toString(num);
|
||||||
|
var backToNum = toNumber(str);
|
||||||
|
var intVal = toInt(num);
|
||||||
|
var boolVal = toBoolean(i % 2);
|
||||||
|
|
||||||
|
push(conversions, [
|
||||||
|
num, str, backToNum, intVal, boolVal,
|
||||||
|
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
print("Created " + len(conversions) + " type conversion results");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear conversions...");
|
||||||
|
conversions = [];
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 3: Array/Dict builtin operations
|
||||||
|
print("Test 3: Collection builtin operations");
|
||||||
|
var collections = [];
|
||||||
|
for (var i = 0; i < 50000; i++) {
|
||||||
|
var arr = [i, i+1, i+2];
|
||||||
|
var dict = {"a": i, "b": i+1, "c": i+2};
|
||||||
|
|
||||||
|
// Use builtin functions heavily
|
||||||
|
var arrLen = len(arr);
|
||||||
|
push(arr, i+3);
|
||||||
|
var popped = pop(arr);
|
||||||
|
|
||||||
|
var dictKeys = keys(dict);
|
||||||
|
var dictValues = values(dict);
|
||||||
|
var hasA = has(dict, "a");
|
||||||
|
|
||||||
|
push(collections, {
|
||||||
|
"array": arr,
|
||||||
|
"dict": dict,
|
||||||
|
"arrLen": arrLen,
|
||||||
|
"popped": popped,
|
||||||
|
"keys": dictKeys,
|
||||||
|
"values": dictValues,
|
||||||
|
"hasA": hasA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(collections) + " collection operation results");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear collections...");
|
||||||
|
collections = "cleared";
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 4: Eval function stress test
|
||||||
|
print("Test 4: Eval function stress");
|
||||||
|
var evalResults = [];
|
||||||
|
for (var i = 0; i < 10000; i++) {
|
||||||
|
var expression = toString(i) + " * 2 + 1;";
|
||||||
|
var result = eval(expression);
|
||||||
|
|
||||||
|
var funcExpr = "func() { return " + toString(i) + "; };";
|
||||||
|
var evalFunc = eval(funcExpr);
|
||||||
|
|
||||||
|
push(evalResults, {
|
||||||
|
"expr": expression,
|
||||||
|
"result": result,
|
||||||
|
"func": evalFunc,
|
||||||
|
"funcResult": evalFunc()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(evalResults) + " eval results");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear eval results...");
|
||||||
|
evalResults = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 5: File I/O operations (if supported)
|
||||||
|
print("Test 5: File I/O operations");
|
||||||
|
var fileData = [];
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
var filename = "temp_" + toString(i) + ".txt";
|
||||||
|
var content = "This is test data for file " + toString(i) + "\n";
|
||||||
|
content = content + "Line 2: " + toString(i * 2) + "\n";
|
||||||
|
content = content + "Line 3: " + toString(i * 3) + "\n";
|
||||||
|
|
||||||
|
// Write file
|
||||||
|
writeFile(filename, content);
|
||||||
|
|
||||||
|
// Check if exists
|
||||||
|
var exists = fileExists(filename);
|
||||||
|
|
||||||
|
// Read back
|
||||||
|
var readContent = readFile(filename);
|
||||||
|
var lines = readLines(filename);
|
||||||
|
|
||||||
|
push(fileData, {
|
||||||
|
"filename": filename,
|
||||||
|
"exists": exists,
|
||||||
|
"content": readContent,
|
||||||
|
"lines": lines,
|
||||||
|
"lineCount": len(lines)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(fileData) + " file operation results");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear file data...");
|
||||||
|
fileData = [];
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Clean up test files
|
||||||
|
print("Cleaning up test files...");
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
var filename = "temp_" + toString(i) + ".txt";
|
||||||
|
if (fileExists(filename)) {
|
||||||
|
// Bob doesn't have deleteFile, but we created them
|
||||||
|
print("Note: Test file " + filename + " still exists");
|
||||||
|
if (i > 10) break; // Don't spam too many messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input("File data cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 6: Random number generation
|
||||||
|
print("Test 6: Random number stress");
|
||||||
|
var randomData = [];
|
||||||
|
for (var i = 0; i < 200000; i++) {
|
||||||
|
var rand1 = random();
|
||||||
|
var rand2 = random();
|
||||||
|
var sum = rand1 + rand2;
|
||||||
|
|
||||||
|
push(randomData, {
|
||||||
|
"rand1": rand1,
|
||||||
|
"rand2": rand2,
|
||||||
|
"sum": sum,
|
||||||
|
"product": rand1 * rand2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(randomData) + " random number results");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear random data...");
|
||||||
|
randomData = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
print("=== Builtin Function Tests Complete ===");
|
||||||
110
leakTests/leaktest_collections.bob
Normal file
110
leakTests/leaktest_collections.bob
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Memory leak test: Collection-related scenarios
|
||||||
|
// Test arrays, dictionaries, and nested structures
|
||||||
|
|
||||||
|
print("=== Collection Memory Leak Tests ===");
|
||||||
|
print("Initial memory: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Test 1: Large nested arrays
|
||||||
|
print("Test 1: Large nested arrays");
|
||||||
|
var nestedArrays = [];
|
||||||
|
for (var i = 0; i < 50000; i++) {
|
||||||
|
push(nestedArrays, [
|
||||||
|
[i, i+1, i+2],
|
||||||
|
[i*2, i*3, i*4],
|
||||||
|
[[i, [i+1, [i+2]]], i*5]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
print("Created " + len(nestedArrays) + " nested array structures");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear nested arrays...");
|
||||||
|
nestedArrays = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 2: Large nested dictionaries
|
||||||
|
print("Test 2: Large nested dictionaries");
|
||||||
|
var nestedDicts = [];
|
||||||
|
for (var i = 0; i < 50000; i++) {
|
||||||
|
push(nestedDicts, {
|
||||||
|
"id": i,
|
||||||
|
"data": {
|
||||||
|
"value": i * 2,
|
||||||
|
"nested": {
|
||||||
|
"deep": {
|
||||||
|
"deeper": i * 3,
|
||||||
|
"info": "test" + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"created": i,
|
||||||
|
"tags": ["tag" + i, "tag" + (i+1)]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(nestedDicts) + " nested dictionary structures");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear nested dicts...");
|
||||||
|
nestedDicts = [];
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 3: Mixed array/dict structures
|
||||||
|
print("Test 3: Mixed array/dict structures");
|
||||||
|
var mixedStructures = [];
|
||||||
|
for (var i = 0; i < 30000; i++) {
|
||||||
|
push(mixedStructures, [
|
||||||
|
{"arrays": [[i, i+1], [i+2, i+3]]},
|
||||||
|
[{"dicts": {"a": i, "b": i+1}}, {"more": [i, i+1]}],
|
||||||
|
{
|
||||||
|
"complex": [
|
||||||
|
{"nested": [i, {"deep": i*2}]},
|
||||||
|
[{"very": {"deep": [i, i+1, {"final": i*3}]}}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
print("Created " + len(mixedStructures) + " mixed structures");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear mixed structures...");
|
||||||
|
mixedStructures = "cleared";
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 4: Self-referencing structures (potential cycles)
|
||||||
|
print("Test 4: Self-referencing structures");
|
||||||
|
var selfRef = [];
|
||||||
|
for (var i = 0; i < 1000000; i++) {
|
||||||
|
var item = {"id": i, "value": i * 2};
|
||||||
|
// Create a structure that references itself
|
||||||
|
item["self"] = [item, {"parent": item}];
|
||||||
|
push(selfRef, item);
|
||||||
|
}
|
||||||
|
print("Created " + len(selfRef) + " self-referencing structures");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear self-ref structures...");
|
||||||
|
selfRef = 123;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 5: Large string collections
|
||||||
|
print("Test 5: Large string collections");
|
||||||
|
var stringCollections = [];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
var longString = "";
|
||||||
|
for (var j = 0; j < 100; j++) {
|
||||||
|
longString = longString + "data" + i + "_" + j + " ";
|
||||||
|
}
|
||||||
|
push(stringCollections, {
|
||||||
|
"content": longString,
|
||||||
|
"words": [longString, longString + "_copy", longString + "_backup"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(stringCollections) + " string collections");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear string collections...");
|
||||||
|
stringCollections = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
print("=== Collection Tests Complete ===");
|
||||||
89
leakTests/leaktest_functions.bob
Normal file
89
leakTests/leaktest_functions.bob
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Memory leak test: Function-related scenarios
|
||||||
|
// Test various function patterns that could cause memory leaks
|
||||||
|
|
||||||
|
print("=== Function Memory Leak Tests ===");
|
||||||
|
print("Initial memory: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Test 1: Recursive function closures
|
||||||
|
print("Test 1: Recursive function closures");
|
||||||
|
var recursiveFuncs = [];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
push(recursiveFuncs, func() {
|
||||||
|
var capturedI = i;
|
||||||
|
return func() {
|
||||||
|
if (capturedI > 0) {
|
||||||
|
return capturedI * capturedI;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(recursiveFuncs) + " recursive closure functions");
|
||||||
|
print("Memory after creation: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear recursive functions...");
|
||||||
|
recursiveFuncs = none;
|
||||||
|
print("Memory after cleanup: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 2: Functions returning functions (factory pattern)
|
||||||
|
print("Test 2: Function factories");
|
||||||
|
var factories = [];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
push(factories, func() {
|
||||||
|
var multiplier = i;
|
||||||
|
return func(x) {
|
||||||
|
return x * multiplier;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(factories) + " function factories");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear factories...");
|
||||||
|
factories = [];
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 3: Deep function nesting
|
||||||
|
print("Test 3: Deep function nesting");
|
||||||
|
var deepNested = [];
|
||||||
|
for (var i = 0; i < 50000; i++) {
|
||||||
|
push(deepNested, func() {
|
||||||
|
return func() {
|
||||||
|
return func() {
|
||||||
|
return func() {
|
||||||
|
return func() {
|
||||||
|
return i * 42;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(deepNested) + " deeply nested functions");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear deep nested...");
|
||||||
|
deepNested = "test";
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 4: Circular function references
|
||||||
|
print("Test 4: Circular function references");
|
||||||
|
var circularFuncs = [];
|
||||||
|
for (var i = 0; i < 1000000; i++) {
|
||||||
|
var funcA = func() {
|
||||||
|
return "A" + i;
|
||||||
|
};
|
||||||
|
var funcB = func() {
|
||||||
|
return "B" + i;
|
||||||
|
};
|
||||||
|
// Store both in same array element to create potential circular refs
|
||||||
|
push(circularFuncs, [funcA, funcB, func() { return funcA; }]);
|
||||||
|
}
|
||||||
|
print("Created " + len(circularFuncs) + " circular function structures");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear circular functions...");
|
||||||
|
circularFuncs = 42;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
print("=== Function Tests Complete ===");
|
||||||
145
leakTests/leaktest_loops.bob
Normal file
145
leakTests/leaktest_loops.bob
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// Memory leak test: Loop and repetitive operation scenarios
|
||||||
|
// Test memory behavior in various loop patterns
|
||||||
|
|
||||||
|
print("=== Loop Memory Leak Tests ===");
|
||||||
|
print("Initial memory: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Test 1: Nested loop memory allocation
|
||||||
|
print("Test 1: Nested loop allocation");
|
||||||
|
var nestedData = [];
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
var row = [];
|
||||||
|
for (var j = 0; j < 1000; j++) {
|
||||||
|
push(row, {
|
||||||
|
"i": i,
|
||||||
|
"j": j,
|
||||||
|
"func": func() { return i * j; },
|
||||||
|
"data": [i, j, i+j]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
push(nestedData, row);
|
||||||
|
}
|
||||||
|
print("Created " + len(nestedData) + "x" + len(nestedData[0]) + " nested structure");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear nested data...");
|
||||||
|
nestedData = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 2: While loop with accumulation
|
||||||
|
print("Test 2: While loop accumulation");
|
||||||
|
var accumulator = [];
|
||||||
|
var counter = 0;
|
||||||
|
while (counter < 500000) {
|
||||||
|
push(accumulator, {
|
||||||
|
"count": counter,
|
||||||
|
"func": func() { return counter * 2; },
|
||||||
|
"meta": ["item" + counter, counter % 100]
|
||||||
|
});
|
||||||
|
counter = counter + 1;
|
||||||
|
}
|
||||||
|
print("Accumulated " + len(accumulator) + " items in while loop");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear accumulator...");
|
||||||
|
accumulator = [];
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 3: For loop with variable reassignment
|
||||||
|
print("Test 3: Variable reassignment in loops");
|
||||||
|
var reassignVar = none;
|
||||||
|
for (var i = 0; i < 200000; i++) {
|
||||||
|
// Constantly reassign to different types
|
||||||
|
if (i % 4 == 0) {
|
||||||
|
reassignVar = [i, func() { return i; }];
|
||||||
|
} else if (i % 4 == 1) {
|
||||||
|
reassignVar = {"id": i, "func": func() { return i*2; }};
|
||||||
|
} else if (i % 4 == 2) {
|
||||||
|
reassignVar = func() { return i*3; };
|
||||||
|
} else {
|
||||||
|
reassignVar = "string" + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("Completed " + 200000 + " reassignments, final type: " + type(reassignVar));
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear reassignment var...");
|
||||||
|
reassignVar = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 4: Do-while with function creation
|
||||||
|
print("Test 4: Do-while function creation");
|
||||||
|
var doWhileFuncs = [];
|
||||||
|
var dwCounter = 0;
|
||||||
|
do {
|
||||||
|
push(doWhileFuncs, func() {
|
||||||
|
var captured = dwCounter;
|
||||||
|
return func() {
|
||||||
|
return captured * captured;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
dwCounter = dwCounter + 1;
|
||||||
|
} while (dwCounter < 100000);
|
||||||
|
print("Created " + len(doWhileFuncs) + " functions in do-while");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear do-while functions...");
|
||||||
|
doWhileFuncs = "cleared";
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 5: Loop with early breaks and continues
|
||||||
|
print("Test 5: Complex loop control flow");
|
||||||
|
var complexData = [];
|
||||||
|
for (var i = 0; i < 500000; i++) {
|
||||||
|
if (i % 7 == 0) {
|
||||||
|
continue; // Skip some iterations
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 400000 && i % 100 == 0) {
|
||||||
|
// Create larger objects near the end
|
||||||
|
push(complexData, {
|
||||||
|
"large": [
|
||||||
|
func() { return i; },
|
||||||
|
[i, i+1, i+2, i+3],
|
||||||
|
{"nested": {"deep": func() { return i*2; }}}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
push(complexData, func() { return i; });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 450000 && len(complexData) > 350000) {
|
||||||
|
break; // Early exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("Complex loop created " + len(complexData) + " items");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear complex data...");
|
||||||
|
complexData = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 6: Memory churn test (create and destroy in same loop)
|
||||||
|
print("Test 6: Memory churn test");
|
||||||
|
for (var cycle = 0; cycle < 100; cycle++) {
|
||||||
|
var churnData = [];
|
||||||
|
|
||||||
|
// Create lots of data
|
||||||
|
for (var i = 0; i < 10000; i++) {
|
||||||
|
push(churnData, [
|
||||||
|
func() { return i + cycle; },
|
||||||
|
{"cycle": cycle, "item": i}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear it immediately
|
||||||
|
churnData = none;
|
||||||
|
|
||||||
|
if (cycle % 10 == 0) {
|
||||||
|
print("Completed churn cycle " + cycle + ", Memory: " + memoryUsage() + " MB");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("Final memory after churn test: " + memoryUsage() + " MB");
|
||||||
|
input("Completed memory churn test. Check memory usage...");
|
||||||
|
|
||||||
|
print("=== Loop Tests Complete ===");
|
||||||
113
leakTests/leaktest_mixed.bob
Normal file
113
leakTests/leaktest_mixed.bob
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Memory leak test: Mixed type scenarios and edge cases
|
||||||
|
// Test combinations and edge cases that might cause leaks
|
||||||
|
|
||||||
|
print("=== Mixed Type Memory Leak Tests ===");
|
||||||
|
print("Initial memory: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Test 1: Functions with collection captures
|
||||||
|
print("Test 1: Functions capturing collections");
|
||||||
|
var funcWithCollections = [];
|
||||||
|
for (var i = 0; i < 50000; i++) {
|
||||||
|
var capturedArray = [i, i+1, i+2, "data" + i];
|
||||||
|
var capturedDict = {"id": i, "values": [i*2, i*3]};
|
||||||
|
|
||||||
|
push(funcWithCollections, func() {
|
||||||
|
// Capture both collections
|
||||||
|
var localArray = capturedArray;
|
||||||
|
var localDict = capturedDict;
|
||||||
|
return func() {
|
||||||
|
return len(localArray) + len(localDict);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print("Created " + len(funcWithCollections) + " functions with collection captures");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear function collections...");
|
||||||
|
funcWithCollections = [];
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 2: Collections containing functions and other collections
|
||||||
|
print("Test 2: Collections with mixed content");
|
||||||
|
var mixedContent = [];
|
||||||
|
for (var i = 0; i < 30000; i++) {
|
||||||
|
push(mixedContent, [
|
||||||
|
func() { return i; }, // Function
|
||||||
|
[i, i+1, func() { return i*2; }], // Array with function
|
||||||
|
{"value": i, "func": func() { return i*3; }}, // Dict with function
|
||||||
|
"string" + i, // String
|
||||||
|
i * 1.5, // Number
|
||||||
|
i % 2 == 0 // Boolean
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
print("Created " + len(mixedContent) + " mixed content collections");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear mixed content...");
|
||||||
|
mixedContent = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 3: Property assignment patterns
|
||||||
|
print("Test 3: Property assignment patterns");
|
||||||
|
var propObjects = [];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
var obj = {"base": i};
|
||||||
|
// Dynamic property assignment
|
||||||
|
obj["prop" + i] = func() { return i; };
|
||||||
|
obj["nested"] = {"deep": {"value": i}};
|
||||||
|
obj["array"] = [1, 2, 3];
|
||||||
|
obj["array"][0] = func() { return i * 2; };
|
||||||
|
|
||||||
|
push(propObjects, obj);
|
||||||
|
}
|
||||||
|
print("Created " + len(propObjects) + " objects with dynamic properties");
|
||||||
|
print("Memory: " + memoryUsage() + " MB");
|
||||||
|
input("Press Enter to clear property objects...");
|
||||||
|
propObjects = "cleared";
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 4: Type reassignment chains
|
||||||
|
print("Test 4: Type reassignment chains");
|
||||||
|
var typeChains = [];
|
||||||
|
for (var i = 0; i < 200000; i++) {
|
||||||
|
push(typeChains, i);
|
||||||
|
}
|
||||||
|
print("Memory after number array: " + memoryUsage() + " MB");
|
||||||
|
input("Created number array. Press Enter to convert to functions...");
|
||||||
|
|
||||||
|
// Reassign all elements to functions
|
||||||
|
for (var i = 0; i < len(typeChains); i++) {
|
||||||
|
typeChains[i] = func() { return i; };
|
||||||
|
}
|
||||||
|
print("Memory after function conversion: " + memoryUsage() + " MB");
|
||||||
|
input("Converted to functions. Press Enter to convert to dicts...");
|
||||||
|
|
||||||
|
// Reassign all elements to dicts
|
||||||
|
for (var i = 0; i < len(typeChains); i++) {
|
||||||
|
typeChains[i] = {"id": i, "func": func() { return i; }};
|
||||||
|
}
|
||||||
|
print("Memory after dict conversion: " + memoryUsage() + " MB");
|
||||||
|
input("Converted to dicts. Press Enter to clear...");
|
||||||
|
typeChains = none;
|
||||||
|
print("Memory after clear: " + memoryUsage() + " MB");
|
||||||
|
input("Cleared. Check memory usage...");
|
||||||
|
|
||||||
|
// Test 5: Rapid allocation/deallocation
|
||||||
|
print("Test 5: Rapid allocation/deallocation");
|
||||||
|
for (var round = 0; round < 10; round++) {
|
||||||
|
var temp = [];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
push(temp, [
|
||||||
|
func() { return i; },
|
||||||
|
{"data": [i, i+1, func() { return i*2; }]}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
print("Round " + round + ": Created " + len(temp) + " items, Memory: " + memoryUsage() + " MB");
|
||||||
|
temp = none; // Clear immediately
|
||||||
|
if (round % 2 == 1) print("After clear round " + round + ": " + memoryUsage() + " MB");
|
||||||
|
}
|
||||||
|
print("Final memory after all cycles: " + memoryUsage() + " MB");
|
||||||
|
input("Completed rapid allocation cycles. Check memory usage...");
|
||||||
|
|
||||||
|
print("=== Mixed Type Tests Complete ===");
|
||||||
52
run_leak_tests.sh
Executable file
52
run_leak_tests.sh
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Bob Memory Leak Test Runner
|
||||||
|
# Runs all leak tests in sequence with memory monitoring
|
||||||
|
|
||||||
|
echo "🧪 Bob Memory Leak Test Suite"
|
||||||
|
echo "============================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Build first
|
||||||
|
echo "📦 Building Bob..."
|
||||||
|
ninja -C build-ninja
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Build failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Build successful"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to run a test and show memory info
|
||||||
|
run_test() {
|
||||||
|
local test_file=$1
|
||||||
|
local test_name=$2
|
||||||
|
|
||||||
|
echo "🔬 Running: $test_name"
|
||||||
|
echo "File: $test_file"
|
||||||
|
echo "Memory monitoring: Use Activity Monitor (macOS) or top/htop (Linux)"
|
||||||
|
echo "Press Ctrl+C during test to abort, or follow prompts to continue"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
./build-ninja/bin/bob "$test_file"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Completed: $test_name"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
run_test "leakTests/leaktest_functions.bob" "Function Memory Tests"
|
||||||
|
run_test "leakTests/leaktest_collections.bob" "Collection Memory Tests"
|
||||||
|
run_test "leakTests/leaktest_mixed.bob" "Mixed Type Memory Tests"
|
||||||
|
run_test "leakTests/leaktest_loops.bob" "Loop Memory Tests"
|
||||||
|
run_test "leakTests/leaktest_builtin.bob" "Builtin Function Memory Tests"
|
||||||
|
|
||||||
|
echo "🎉 All memory leak tests completed!"
|
||||||
|
echo ""
|
||||||
|
echo "💡 Memory Monitoring Tips:"
|
||||||
|
echo "- Memory should spike during object creation"
|
||||||
|
echo "- Memory should drop after 'cleared' messages"
|
||||||
|
echo "- Memory should return close to baseline between tests"
|
||||||
|
echo "- Watch for gradual increases across test cycles (indicates leaks)"
|
||||||
@ -14,8 +14,10 @@ struct IncrementExpr;
|
|||||||
struct TernaryExpr;
|
struct TernaryExpr;
|
||||||
struct ArrayLiteralExpr;
|
struct ArrayLiteralExpr;
|
||||||
struct ArrayIndexExpr;
|
struct ArrayIndexExpr;
|
||||||
|
struct PropertyExpr;
|
||||||
|
|
||||||
struct ArrayAssignExpr;
|
struct ArrayAssignExpr;
|
||||||
|
struct PropertyAssignExpr;
|
||||||
struct DictLiteralExpr;
|
struct DictLiteralExpr;
|
||||||
struct DictIndexExpr;
|
struct DictIndexExpr;
|
||||||
struct DictAssignExpr;
|
struct DictAssignExpr;
|
||||||
@ -44,8 +46,10 @@ struct ExprVisitor
|
|||||||
virtual Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expr) = 0;
|
virtual Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expr) = 0;
|
||||||
virtual Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) = 0;
|
virtual Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) = 0;
|
||||||
virtual Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) = 0;
|
virtual Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) = 0;
|
||||||
|
virtual Value visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) = 0;
|
||||||
|
|
||||||
virtual Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) = 0;
|
virtual Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) = 0;
|
||||||
|
virtual Value visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expr) = 0;
|
||||||
virtual Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) = 0;
|
virtual Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -205,7 +209,19 @@ struct ArrayIndexExpr : Expr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PropertyExpr : Expr
|
||||||
|
{
|
||||||
|
std::shared_ptr<Expr> object;
|
||||||
|
Token name;
|
||||||
|
|
||||||
|
PropertyExpr(std::shared_ptr<Expr> object, Token name)
|
||||||
|
: object(object), name(name) {}
|
||||||
|
|
||||||
|
Value accept(ExprVisitor* visitor) override
|
||||||
|
{
|
||||||
|
return visitor->visitPropertyExpr(std::static_pointer_cast<PropertyExpr>(shared_from_this()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct ArrayAssignExpr : Expr
|
struct ArrayAssignExpr : Expr
|
||||||
{
|
{
|
||||||
@ -222,6 +238,21 @@ struct ArrayAssignExpr : Expr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PropertyAssignExpr : Expr
|
||||||
|
{
|
||||||
|
std::shared_ptr<Expr> object;
|
||||||
|
Token name;
|
||||||
|
std::shared_ptr<Expr> value;
|
||||||
|
|
||||||
|
PropertyAssignExpr(std::shared_ptr<Expr> object, Token name, std::shared_ptr<Expr> value)
|
||||||
|
: object(object), name(name), value(value) {}
|
||||||
|
|
||||||
|
Value accept(ExprVisitor* visitor) override
|
||||||
|
{
|
||||||
|
return visitor->visitPropertyAssignExpr(std::static_pointer_cast<PropertyAssignExpr>(shared_from_this()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct DictLiteralExpr : Expr
|
struct DictLiteralExpr : Expr
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::string, std::shared_ptr<Expr>>> pairs;
|
std::vector<std::pair<std::string, std::shared_ptr<Expr>>> pairs;
|
||||||
|
|||||||
@ -44,6 +44,9 @@ public:
|
|||||||
// Get by string name with error reporting
|
// Get by string name with error reporting
|
||||||
Value get(const std::string& name);
|
Value get(const std::string& name);
|
||||||
|
|
||||||
|
// Prune heavy containers in a snapshot to avoid capture cycles
|
||||||
|
void pruneForClosureCapture();
|
||||||
|
|
||||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||||
inline void clear() { variables.clear(); }
|
inline void clear() { variables.clear(); }
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,13 @@ public:
|
|||||||
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
|
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
|
||||||
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
|
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
|
||||||
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
|
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
|
||||||
|
Value visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expression) override;
|
||||||
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
|
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
|
||||||
|
Value visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expression) override;
|
||||||
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
|
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Helper methods for builtin properties
|
||||||
|
Value getArrayProperty(const Value& array, const std::string& propertyName);
|
||||||
|
Value getDictProperty(const Value& dict, const std::string& propertyName);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -64,6 +64,12 @@ private:
|
|||||||
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
|
||||||
ErrorReporter* errorReporter;
|
ErrorReporter* errorReporter;
|
||||||
bool inThunkExecution = false;
|
bool inThunkExecution = false;
|
||||||
|
|
||||||
|
// Automatic cleanup tracking
|
||||||
|
int functionCreationCount = 0;
|
||||||
|
int thunkCreationCount = 0;
|
||||||
|
static const int CLEANUP_THRESHOLD = 1000000;
|
||||||
|
|
||||||
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;
|
||||||
@ -78,6 +84,7 @@ public:
|
|||||||
|
|
||||||
// Methods needed by Evaluator
|
// Methods needed by Evaluator
|
||||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
Value evaluate(const std::shared_ptr<Expr>& expr);
|
||||||
|
Value evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression); // Inline TCO for performance
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
||||||
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
||||||
bool isTruthy(Value object);
|
bool isTruthy(Value object);
|
||||||
@ -94,6 +101,15 @@ public:
|
|||||||
void cleanupUnusedThunks();
|
void cleanupUnusedThunks();
|
||||||
void forceCleanup();
|
void forceCleanup();
|
||||||
|
|
||||||
|
// 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);
|
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
|
||||||
void addStdLibFunctions();
|
void addStdLibFunctions();
|
||||||
|
|||||||
@ -26,9 +26,13 @@ public:
|
|||||||
|
|
||||||
// Memory management utilities
|
// Memory management utilities
|
||||||
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 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,
|
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks);
|
std::vector<std::shared_ptr<Thunk>>& thunks);
|
||||||
|
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
||||||
|
std::vector<std::shared_ptr<Function>>& functions,
|
||||||
|
std::vector<std::shared_ptr<Thunk>>& thunks);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper methods for stringify
|
// Helper methods for stringify
|
||||||
|
|||||||
@ -32,15 +32,18 @@ struct Value {
|
|||||||
union {
|
union {
|
||||||
double number;
|
double number;
|
||||||
bool boolean;
|
bool boolean;
|
||||||
Function* function;
|
|
||||||
BuiltinFunction* builtin_function;
|
|
||||||
Thunk* thunk;
|
|
||||||
};
|
};
|
||||||
ValueType type;
|
ValueType type;
|
||||||
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
|
||||||
|
|
||||||
|
// Store functions as shared_ptr for proper reference counting
|
||||||
|
std::shared_ptr<Function> function;
|
||||||
|
std::shared_ptr<BuiltinFunction> builtin_function;
|
||||||
|
std::shared_ptr<Thunk> thunk;
|
||||||
|
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
||||||
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
||||||
@ -48,20 +51,28 @@ struct Value {
|
|||||||
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
||||||
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
||||||
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
||||||
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
Value(std::shared_ptr<Function> f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
||||||
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
Value(std::shared_ptr<BuiltinFunction> bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
||||||
Value(Thunk* t) : thunk(t), type(ValueType::VAL_THUNK) {}
|
Value(std::shared_ptr<Thunk> t) : thunk(t), type(ValueType::VAL_THUNK) {}
|
||||||
Value(const std::vector<Value>& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(arr)) {}
|
Value(const std::vector<Value>& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(arr)) {}
|
||||||
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))) {}
|
||||||
|
|
||||||
|
// Destructor to clean up functions and thunks
|
||||||
|
~Value() {
|
||||||
|
// Functions and thunks are managed by the Interpreter, so we don't delete them
|
||||||
|
// Arrays and dictionaries are managed by shared_ptr, so they clean up automatically
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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)),
|
||||||
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT) {
|
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)) {
|
||||||
|
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
|
||||||
|
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
|
||||||
number = other.number; // Copy the union
|
number = other.number; // Copy the union
|
||||||
}
|
}
|
||||||
other.type = ValueType::VAL_NONE;
|
other.type = ValueType::VAL_NONE;
|
||||||
@ -74,12 +85,19 @@ struct Value {
|
|||||||
if (type == ValueType::VAL_STRING) {
|
if (type == ValueType::VAL_STRING) {
|
||||||
string_value = std::move(other.string_value);
|
string_value = std::move(other.string_value);
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
} else if (type == ValueType::VAL_ARRAY) {
|
||||||
array_value = std::move(other.array_value); // shared_ptr automatically handles moving
|
array_value = std::move(other.array_value);
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
} else if (type == ValueType::VAL_DICT) {
|
||||||
dict_value = std::move(other.dict_value); // shared_ptr automatically handles moving
|
dict_value = std::move(other.dict_value);
|
||||||
|
} else if (type == ValueType::VAL_FUNCTION) {
|
||||||
|
function = std::move(other.function);
|
||||||
|
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
||||||
|
builtin_function = std::move(other.builtin_function);
|
||||||
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
|
thunk = std::move(other.thunk);
|
||||||
} else {
|
} else {
|
||||||
number = other.number; // Copy the union
|
number = other.number;
|
||||||
}
|
}
|
||||||
|
|
||||||
other.type = ValueType::VAL_NONE;
|
other.type = ValueType::VAL_NONE;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
@ -93,23 +111,43 @@ struct Value {
|
|||||||
array_value = other.array_value; // shared_ptr automatically handles sharing
|
array_value = other.array_value; // shared_ptr automatically handles sharing
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
} else if (type == ValueType::VAL_DICT) {
|
||||||
dict_value = other.dict_value; // shared_ptr automatically handles sharing
|
dict_value = other.dict_value; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_FUNCTION) {
|
||||||
|
function = other.function; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
||||||
|
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
|
thunk = other.thunk; // shared_ptr automatically handles sharing
|
||||||
} else {
|
} else {
|
||||||
number = other.number; // Copy the union
|
number = other.number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy assignment (only when needed)
|
// Copy assignment (only when needed)
|
||||||
Value& operator=(const Value& other) {
|
Value& operator=(const Value& other) {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
|
// First, clear all old shared_ptr members to release references
|
||||||
|
array_value.reset();
|
||||||
|
dict_value.reset();
|
||||||
|
function.reset();
|
||||||
|
builtin_function.reset();
|
||||||
|
thunk.reset();
|
||||||
|
|
||||||
|
// Then set the new type and value
|
||||||
type = other.type;
|
type = other.type;
|
||||||
if (type == ValueType::VAL_STRING) {
|
if (type == ValueType::VAL_STRING) {
|
||||||
string_value = other.string_value;
|
string_value = other.string_value;
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
} else if (type == ValueType::VAL_ARRAY) {
|
||||||
array_value = other.array_value; // shared_ptr automatically handles sharing
|
array_value = other.array_value; // shared_ptr automatically handles sharing
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
} else if (type == ValueType::VAL_DICT) {
|
||||||
dict_value = other.dict_value; // shared_ptr automatically handles sharing
|
dict_value = other.dict_value;
|
||||||
|
} else if (type == ValueType::VAL_FUNCTION) {
|
||||||
|
function = other.function; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
||||||
|
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
|
thunk = other.thunk; // shared_ptr automatically handles sharing
|
||||||
} else {
|
} else {
|
||||||
number = other.number; // Copy the union
|
number = other.number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
@ -160,9 +198,9 @@ 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 Function* asFunction() const { return isFunction() ? function : nullptr; }
|
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
||||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
|
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
||||||
inline Thunk* asThunk() const { return isThunk() ? thunk : nullptr; }
|
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
||||||
|
|
||||||
// Truthiness check - inline for performance
|
// Truthiness check - inline for performance
|
||||||
inline bool isTruthy() const {
|
inline bool isTruthy() const {
|
||||||
|
|||||||
@ -430,5 +430,5 @@ Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expressi
|
|||||||
|
|
||||||
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
|
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
|
||||||
interpreter->addFunction(function);
|
interpreter->addFunction(function);
|
||||||
return Value(function.get());
|
return Value(function);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement,
|
|||||||
statement->body,
|
statement->body,
|
||||||
interpreter->getEnvironment());
|
interpreter->getEnvironment());
|
||||||
interpreter->addFunction(function);
|
interpreter->addFunction(function);
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function.get()));
|
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
||||||
|
|||||||
@ -1,96 +0,0 @@
|
|||||||
#include "Interpreter.h"
|
|
||||||
#include "Evaluator.h"
|
|
||||||
#include "Executor.h"
|
|
||||||
#include "BobStdLib.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
Interpreter::Interpreter(bool isInteractive)
|
|
||||||
: isInteractive(isInteractive), errorReporter(nullptr) {
|
|
||||||
evaluator = std::make_unique<Evaluator>(this);
|
|
||||||
executor = std::make_unique<Executor>(this, evaluator.get());
|
|
||||||
environment = std::make_shared<Environment>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Interpreter::~Interpreter() = default;
|
|
||||||
|
|
||||||
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
|
|
||||||
executor->interpret(statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
|
||||||
statement->accept(executor.get(), context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
|
||||||
executor->executeBlock(statements, env, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
|
||||||
Value result = expr->accept(evaluator.get());
|
|
||||||
if (inThunkExecution) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return runTrampoline(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Interpreter::runTrampoline(Value initialResult) {
|
|
||||||
Value current = initialResult;
|
|
||||||
while (current.isThunk()) {
|
|
||||||
current = current.asThunk()->execute();
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Interpreter::isTruthy(Value object) {
|
|
||||||
return diagnostics.isTruthy(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Interpreter::isEqual(Value a, Value b) {
|
|
||||||
return diagnostics.isEqual(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Interpreter::stringify(Value object) {
|
|
||||||
return diagnostics.stringify(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addStdLibFunctions() {
|
|
||||||
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
|
||||||
builtinFunctions.push_back(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
|
|
||||||
thunks.push_back(thunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addFunction(std::shared_ptr<Function> function) {
|
|
||||||
functions.push_back(function);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::setErrorReporter(ErrorReporter* reporter) {
|
|
||||||
errorReporter = reporter;
|
|
||||||
if (environment) {
|
|
||||||
environment->setErrorReporter(reporter);
|
|
||||||
}
|
|
||||||
addStdLibFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Interpreter::isInteractiveMode() const {
|
|
||||||
return isInteractive;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Environment> Interpreter::getEnvironment() {
|
|
||||||
return environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
|
|
||||||
environment = env;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, errorType, message, lexeme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -138,6 +138,16 @@ sptr(Expr) Parser::assignmentExpression()
|
|||||||
auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expr);
|
auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expr);
|
||||||
return msptr(ArrayAssignExpr)(arrayExpr->array, arrayExpr->index, value, arrayExpr->bracket);
|
return msptr(ArrayAssignExpr)(arrayExpr->array, arrayExpr->index, value, arrayExpr->bracket);
|
||||||
}
|
}
|
||||||
|
else if(std::dynamic_pointer_cast<PropertyExpr>(expr))
|
||||||
|
{
|
||||||
|
auto propertyExpr = std::dynamic_pointer_cast<PropertyExpr>(expr);
|
||||||
|
return msptr(PropertyAssignExpr)(propertyExpr->object, propertyExpr->name, value);
|
||||||
|
}
|
||||||
|
else if(std::dynamic_pointer_cast<ArrayIndexExpr>(expr))
|
||||||
|
{
|
||||||
|
auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expr);
|
||||||
|
return msptr(ArrayAssignExpr)(arrayExpr->array, arrayExpr->index, value, arrayExpr->bracket);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
@ -357,6 +367,9 @@ sptr(Expr) Parser::call()
|
|||||||
expr = finishCall(expr);
|
expr = finishCall(expr);
|
||||||
} else if (match({OPEN_BRACKET})) {
|
} else if (match({OPEN_BRACKET})) {
|
||||||
expr = finishArrayIndex(expr);
|
expr = finishArrayIndex(expr);
|
||||||
|
} else if (match({DOT})) {
|
||||||
|
Token name = consume(IDENTIFIER, "Expected property name after '.'.");
|
||||||
|
expr = msptr(PropertyExpr)(expr, name);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -472,33 +485,27 @@ sptr(Stmt) Parser::statement()
|
|||||||
if(match({CONTINUE})) return continueStatement();
|
if(match({CONTINUE})) return continueStatement();
|
||||||
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
||||||
|
|
||||||
// Check for assignment statement
|
// Check for assignment statement - simplified approach
|
||||||
if(check(IDENTIFIER)) {
|
if(check(IDENTIFIER)) {
|
||||||
// Look ahead to see if this is an assignment
|
// Try to parse as assignment expression first
|
||||||
int currentPos = current;
|
int currentPos = current;
|
||||||
advance(); // consume identifier
|
try {
|
||||||
|
|
||||||
// Check for simple variable assignment
|
|
||||||
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
|
||||||
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL})) {
|
|
||||||
// Reset position and parse as assignment statement
|
|
||||||
current = currentPos;
|
|
||||||
return assignmentStatement();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for array assignment (identifier followed by [)
|
|
||||||
if(match({OPEN_BRACKET})) {
|
|
||||||
// Reset position and parse as assignment expression
|
|
||||||
current = currentPos;
|
|
||||||
sptr(Expr) expr = assignmentExpression();
|
sptr(Expr) expr = assignmentExpression();
|
||||||
|
|
||||||
|
// If we successfully parsed an assignment expression, it's an assignment statement
|
||||||
|
if(std::dynamic_pointer_cast<AssignExpr>(expr) ||
|
||||||
|
std::dynamic_pointer_cast<ArrayAssignExpr>(expr) ||
|
||||||
|
std::dynamic_pointer_cast<PropertyAssignExpr>(expr)) {
|
||||||
consume(SEMICOLON, "Expected ';' after assignment.");
|
consume(SEMICOLON, "Expected ';' after assignment.");
|
||||||
return msptr(ExpressionStmt)(expr);
|
return msptr(ExpressionStmt)(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's not an assignment, reset and parse as expression statement
|
||||||
|
|
||||||
// Reset position and parse as expression statement
|
|
||||||
current = currentPos;
|
current = currentPos;
|
||||||
|
} catch (...) {
|
||||||
|
// If assignment parsing failed, reset and parse as expression statement
|
||||||
|
current = currentPos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return expressionStatement();
|
return expressionStatement();
|
||||||
|
|||||||
@ -49,3 +49,19 @@ Value Environment::get(const std::string& name) {
|
|||||||
|
|
||||||
throw std::runtime_error("Undefined variable '" + name + "'");
|
throw std::runtime_error("Undefined variable '" + name + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Environment::pruneForClosureCapture() {
|
||||||
|
for (auto &entry : variables) {
|
||||||
|
Value &v = entry.second;
|
||||||
|
if (v.isArray()) {
|
||||||
|
// Replace with a new empty array to avoid mutating original shared storage
|
||||||
|
entry.second = Value(std::vector<Value>{});
|
||||||
|
} else if (v.isDict()) {
|
||||||
|
// Replace with a new empty dict to avoid mutating original shared storage
|
||||||
|
entry.second = Value(std::unordered_map<std::string, Value>{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
parent->pruneForClosureCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -213,27 +213,74 @@ 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) {
|
||||||
|
try {
|
||||||
|
// Check if the variable existed and whether it held a collection
|
||||||
|
bool existed = false;
|
||||||
|
bool wasCollection = false;
|
||||||
|
try {
|
||||||
|
Value oldValue = interpreter->getEnvironment()->get(expression->name);
|
||||||
|
existed = true;
|
||||||
|
wasCollection = oldValue.isArray() || oldValue.isDict();
|
||||||
|
} catch (...) {
|
||||||
|
existed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
switch (expression->op.type) {
|
||||||
case PLUS_EQUAL:
|
case PLUS_EQUAL:
|
||||||
|
newValue = currentValue + value;
|
||||||
|
break;
|
||||||
case MINUS_EQUAL:
|
case MINUS_EQUAL:
|
||||||
|
newValue = currentValue - value;
|
||||||
|
break;
|
||||||
case STAR_EQUAL:
|
case STAR_EQUAL:
|
||||||
|
newValue = currentValue * value;
|
||||||
|
break;
|
||||||
case SLASH_EQUAL:
|
case SLASH_EQUAL:
|
||||||
|
newValue = currentValue / value;
|
||||||
|
break;
|
||||||
case PERCENT_EQUAL:
|
case PERCENT_EQUAL:
|
||||||
|
newValue = currentValue % value;
|
||||||
|
break;
|
||||||
case BIN_AND_EQUAL:
|
case BIN_AND_EQUAL:
|
||||||
|
newValue = currentValue & value;
|
||||||
|
break;
|
||||||
case BIN_OR_EQUAL:
|
case BIN_OR_EQUAL:
|
||||||
|
newValue = currentValue | value;
|
||||||
|
break;
|
||||||
case BIN_XOR_EQUAL:
|
case BIN_XOR_EQUAL:
|
||||||
|
newValue = currentValue ^ value;
|
||||||
|
break;
|
||||||
case BIN_SLEFT_EQUAL:
|
case BIN_SLEFT_EQUAL:
|
||||||
case BIN_SRIGHT_EQUAL: {
|
newValue = currentValue << value;
|
||||||
Value currentValue = interpreter->getEnvironment()->get(expression->name);
|
break;
|
||||||
|
case BIN_SRIGHT_EQUAL:
|
||||||
// ... (rest of compound assignment logic) ...
|
newValue = currentValue >> value;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
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, value);
|
|
||||||
|
interpreter->getEnvironment()->assign(expression->name, newValue);
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,62 +295,8 @@ Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression
|
|||||||
}
|
}
|
||||||
|
|
||||||
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||||
Value callee = expression->callee->accept(this);
|
// Delegate to inline implementation in Interpreter for performance
|
||||||
|
return interpreter->evaluateCallExprInline(expression);
|
||||||
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) {
|
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
|
||||||
@ -364,6 +357,21 @@ Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
||||||
|
Value object = expr->object->accept(this);
|
||||||
|
std::string propertyName = expr->name.lexeme;
|
||||||
|
|
||||||
|
if (object.isDict()) {
|
||||||
|
return getDictProperty(object, propertyName);
|
||||||
|
} else if (object.isArray()) {
|
||||||
|
return getArrayProperty(object, propertyName);
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
||||||
|
"Cannot access property '" + propertyName + "' on this type", "");
|
||||||
|
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
|
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
|
||||||
Value array = expr->array->accept(this);
|
Value array = expr->array->accept(this);
|
||||||
Value index = expr->index->accept(this);
|
Value index = expr->index->accept(this);
|
||||||
@ -422,13 +430,95 @@ Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& ex
|
|||||||
return Value(dict);
|
return Value(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expr) {
|
||||||
|
Value object = expr->object->accept(this);
|
||||||
|
Value value = expr->value->accept(this);
|
||||||
|
std::string propertyName = expr->name.lexeme;
|
||||||
|
|
||||||
|
if (object.isDict()) {
|
||||||
|
// Modify the dictionary in place
|
||||||
|
std::unordered_map<std::string, Value>& dict = object.asDict();
|
||||||
|
dict[propertyName] = value;
|
||||||
|
return value; // Return the assigned value
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
||||||
|
"Cannot assign property '" + propertyName + "' on non-object", "");
|
||||||
|
throw std::runtime_error("Cannot assign property '" + propertyName + "' on non-object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
||||||
std::vector<std::string> paramNames;
|
std::vector<std::string> paramNames;
|
||||||
for (const Token& param : expression->params) {
|
for (const Token& param : expression->params) {
|
||||||
paramNames.push_back(param.lexeme);
|
paramNames.push_back(param.lexeme);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
|
// Capture a snapshot of the current environment so loop vars like 'i' are frozen per iteration
|
||||||
interpreter->addFunction(function);
|
auto closureEnv = std::make_shared<Environment>(*interpreter->getEnvironment());
|
||||||
return Value(function.get());
|
closureEnv->pruneForClosureCapture();
|
||||||
|
|
||||||
|
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
|
||||||
|
return Value(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::getArrayProperty(const Value& arrayValue, const std::string& propertyName) {
|
||||||
|
const std::vector<Value>& arr = arrayValue.asArray();
|
||||||
|
|
||||||
|
// Create builtin array properties as an actual dictionary
|
||||||
|
std::unordered_map<std::string, Value> arrayProperties;
|
||||||
|
arrayProperties["length"] = Value(static_cast<double>(arr.size()));
|
||||||
|
arrayProperties["empty"] = Value(arr.empty());
|
||||||
|
|
||||||
|
if (!arr.empty()) {
|
||||||
|
arrayProperties["first"] = arr[0];
|
||||||
|
arrayProperties["last"] = arr[arr.size() - 1];
|
||||||
|
} else {
|
||||||
|
arrayProperties["first"] = NONE_VALUE;
|
||||||
|
arrayProperties["last"] = NONE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the requested property
|
||||||
|
auto it = arrayProperties.find(propertyName);
|
||||||
|
if (it != arrayProperties.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return NONE_VALUE; // Unknown property
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::getDictProperty(const Value& dictValue, const std::string& propertyName) {
|
||||||
|
const std::unordered_map<std::string, Value>& dict = dictValue.asDict();
|
||||||
|
|
||||||
|
// First check if it's a user-defined property
|
||||||
|
auto userProp = dict.find(propertyName);
|
||||||
|
if (userProp != dict.end()) {
|
||||||
|
return userProp->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found, check for builtin dictionary properties
|
||||||
|
std::unordered_map<std::string, Value> dictProperties;
|
||||||
|
dictProperties["length"] = Value(static_cast<double>(dict.size()));
|
||||||
|
dictProperties["empty"] = Value(dict.empty());
|
||||||
|
|
||||||
|
// Create keys array
|
||||||
|
std::vector<Value> keysArray;
|
||||||
|
for (const auto& pair : dict) {
|
||||||
|
keysArray.push_back(Value(pair.first));
|
||||||
|
}
|
||||||
|
dictProperties["keys"] = Value(keysArray);
|
||||||
|
|
||||||
|
// Create values array
|
||||||
|
std::vector<Value> valuesArray;
|
||||||
|
for (const auto& pair : dict) {
|
||||||
|
valuesArray.push_back(pair.second);
|
||||||
|
}
|
||||||
|
dictProperties["values"] = Value(valuesArray);
|
||||||
|
|
||||||
|
auto builtinProp = dictProperties.find(propertyName);
|
||||||
|
if (builtinProp != dictProperties.end()) {
|
||||||
|
return builtinProp->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property not found
|
||||||
|
return NONE_VALUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement,
|
|||||||
statement->body,
|
statement->body,
|
||||||
interpreter->getEnvironment());
|
interpreter->getEnvironment());
|
||||||
interpreter->addFunction(function);
|
interpreter->addFunction(function);
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function.get()));
|
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
||||||
@ -194,10 +194,20 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
||||||
|
try {
|
||||||
Value value = statement->value->accept(evaluator);
|
Value value = statement->value->accept(evaluator);
|
||||||
|
|
||||||
if (statement->op.type == EQUAL) {
|
if (statement->op.type == EQUAL) {
|
||||||
|
try {
|
||||||
|
// Assign first to release references held by the old value
|
||||||
interpreter->getEnvironment()->assign(statement->name, value);
|
interpreter->getEnvironment()->assign(statement->name, value);
|
||||||
|
|
||||||
|
// Clean up on any reassignment, regardless of old/new type
|
||||||
|
interpreter->forceCleanup();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error during assignment: " << e.what() << std::endl;
|
||||||
|
throw; // Re-throw to see the full stack trace
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle compound assignment operators
|
// Handle compound assignment operators
|
||||||
Value currentValue = interpreter->getEnvironment()->get(statement->name);
|
Value currentValue = interpreter->getEnvironment()->get(statement->name);
|
||||||
@ -242,4 +252,8 @@ void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, Exe
|
|||||||
|
|
||||||
interpreter->getEnvironment()->assign(statement->name, newValue);
|
interpreter->getEnvironment()->assign(statement->name, newValue);
|
||||||
}
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error in visitAssignStmt: " << e.what() << std::endl;
|
||||||
|
throw; // Re-throw to see the full stack trace
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,3 +94,139 @@ void Interpreter::reportError(int line, int column, const std::string& errorType
|
|||||||
errorReporter->reportError(line, column, errorType, message, lexeme);
|
errorReporter->reportError(line, column, errorType, message, lexeme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interpreter::cleanupUnusedFunctions() {
|
||||||
|
diagnostics.cleanupUnusedFunctions(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::cleanupUnusedThunks() {
|
||||||
|
diagnostics.cleanupUnusedThunks(thunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::forceCleanup() {
|
||||||
|
diagnostics.forceCleanup(builtinFunctions, functions, thunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression) {
|
||||||
|
Value callee = evaluate(expression->callee); // Direct call instead of through evaluator
|
||||||
|
|
||||||
|
if (callee.isBuiltinFunction()) {
|
||||||
|
// Handle builtin functions with direct evaluation
|
||||||
|
std::vector<Value> arguments;
|
||||||
|
for (const auto& argument : expression->arguments) {
|
||||||
|
arguments.push_back(evaluate(argument)); // Direct call
|
||||||
|
}
|
||||||
|
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
||||||
|
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callee.isFunction()) {
|
||||||
|
// Provide better error message with type information (like original)
|
||||||
|
std::string errorMsg = "Can only call functions, got " + callee.getType();
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
||||||
|
errorMsg, "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Function* function = callee.asFunction();
|
||||||
|
|
||||||
|
std::vector<Value> arguments;
|
||||||
|
for (const auto& argument : expression->arguments) {
|
||||||
|
arguments.push_back(evaluate(argument)); // Direct call instead of through evaluator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check arity (like original)
|
||||||
|
if (arguments.size() != function->params.size()) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->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("Expected " + std::to_string(function->params.size()) +
|
||||||
|
" arguments but got " + std::to_string(arguments.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a tail call for inline TCO
|
||||||
|
if (expression->isTailCall) {
|
||||||
|
// Create a thunk for tail call optimization - original inline version
|
||||||
|
auto thunk = std::make_shared<Thunk>([this, function, arguments]() -> Value {
|
||||||
|
// Use RAII to manage environment (exactly like original)
|
||||||
|
ScopedEnv _env(environment);
|
||||||
|
environment = std::make_shared<Environment>(function->closure);
|
||||||
|
environment->setErrorReporter(errorReporter);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
|
environment->define(function->params[i], arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext context;
|
||||||
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
|
// Use RAII to manage thunk execution flag
|
||||||
|
ScopedThunkFlag _inThunk(inThunkExecution);
|
||||||
|
|
||||||
|
// Execute function body (inline like original - direct accept for performance)
|
||||||
|
for (const auto& stmt : function->body) {
|
||||||
|
stmt->accept(executor.get(), &context); // Direct call like original
|
||||||
|
if (context.hasReturn) {
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.returnValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store the thunk to keep it alive and return as Value (exactly like original)
|
||||||
|
thunks.push_back(thunk);
|
||||||
|
|
||||||
|
// Automatic cleanup check
|
||||||
|
thunkCreationCount++;
|
||||||
|
if (thunkCreationCount >= CLEANUP_THRESHOLD) {
|
||||||
|
cleanupUnusedThunks();
|
||||||
|
thunkCreationCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(thunk);
|
||||||
|
} else {
|
||||||
|
// Normal function call - create new environment (exactly like original)
|
||||||
|
ScopedEnv _env(environment);
|
||||||
|
environment = std::make_shared<Environment>(function->closure);
|
||||||
|
environment->setErrorReporter(errorReporter);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
|
environment->define(function->params[i], arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext context;
|
||||||
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
|
// Execute function body (exactly like original - direct accept for performance)
|
||||||
|
for (const auto& stmt : function->body) {
|
||||||
|
stmt->accept(executor.get(), &context); // Direct call like original
|
||||||
|
if (context.hasReturn) {
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function creation count management
|
||||||
|
void Interpreter::incrementFunctionCreationCount() {
|
||||||
|
functionCreationCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Interpreter::getFunctionCreationCount() const {
|
||||||
|
return functionCreationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::resetFunctionCreationCount() {
|
||||||
|
functionCreationCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Interpreter::getCleanupThreshold() const {
|
||||||
|
return 1000000; // Same as CLEANUP_THRESHOLD used for thunks
|
||||||
|
}
|
||||||
|
|||||||
@ -2,11 +2,17 @@
|
|||||||
#include "Value.h"
|
#include "Value.h"
|
||||||
#include "TypeWrapper.h" // For Function and BuiltinFunction definitions
|
#include "TypeWrapper.h" // For Function and BuiltinFunction definitions
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <malloc.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <malloc/malloc.h>
|
||||||
|
#endif
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
bool RuntimeDiagnostics::isTruthy(Value object) {
|
bool RuntimeDiagnostics::isTruthy(Value object) {
|
||||||
if(object.isBoolean()) {
|
if(object.isBoolean()) {
|
||||||
return object.asBoolean();
|
return object.asBoolean();
|
||||||
@ -182,6 +188,18 @@ void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<Buil
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions) {
|
||||||
|
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
|
||||||
|
// This is more conservative to prevent dangling pointer issues
|
||||||
|
functions.erase(
|
||||||
|
std::remove_if(functions.begin(), functions.end(),
|
||||||
|
[](const std::shared_ptr<Function>& func) {
|
||||||
|
return func.use_count() == 1; // Only referenced by this vector, nowhere else
|
||||||
|
}),
|
||||||
|
functions.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks) {
|
void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks) {
|
||||||
// Only remove thunks that are definitely not referenced anywhere (use_count == 1)
|
// Only remove thunks that are definitely not referenced anywhere (use_count == 1)
|
||||||
// This is more conservative to prevent dangling pointer issues
|
// This is more conservative to prevent dangling pointer issues
|
||||||
@ -213,3 +231,38 @@ void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunctio
|
|||||||
thunks.end()
|
thunks.end()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
||||||
|
std::vector<std::shared_ptr<Function>>& functions,
|
||||||
|
std::vector<std::shared_ptr<Thunk>>& thunks) {
|
||||||
|
try {
|
||||||
|
// Remove functions only when they are exclusively held by the interpreter vector
|
||||||
|
functions.erase(
|
||||||
|
std::remove_if(functions.begin(), functions.end(),
|
||||||
|
[](const std::shared_ptr<Function>& func) {
|
||||||
|
return func.use_count() == 1;
|
||||||
|
}),
|
||||||
|
functions.end()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also cleanup builtin functions and thunks
|
||||||
|
builtinFunctions.erase(
|
||||||
|
std::remove_if(builtinFunctions.begin(), builtinFunctions.end(),
|
||||||
|
[](const std::shared_ptr<BuiltinFunction>& func) {
|
||||||
|
return func.use_count() <= 1; // Only referenced by Interpreter
|
||||||
|
}),
|
||||||
|
builtinFunctions.end()
|
||||||
|
);
|
||||||
|
|
||||||
|
thunks.erase(
|
||||||
|
std::remove_if(thunks.begin(), thunks.end(),
|
||||||
|
[](const std::shared_ptr<Thunk>& thunk) {
|
||||||
|
return thunk.use_count() <= 1; // Only referenced by Interpreter
|
||||||
|
}),
|
||||||
|
thunks.end()
|
||||||
|
);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Exception in forceCleanup: " << e.what() << std::endl;
|
||||||
|
throw; // Re-throw to let the caller handle it
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,16 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
// Platform-specific includes for memory usage
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#elif defined(__linux__)
|
||||||
|
// Uses /proc/self/status, no extra includes needed
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
||||||
// Create a built-in toString function
|
// Create a built-in toString function
|
||||||
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
||||||
@ -23,7 +33,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(interpreter.stringify(args[0]));
|
return Value(interpreter.stringify(args[0]));
|
||||||
});
|
});
|
||||||
env->define("toString", Value(toStringFunc.get()));
|
env->define("toString", Value(toStringFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toStringFunc);
|
interpreter.addBuiltinFunction(toStringFunc);
|
||||||
@ -42,7 +52,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
std::cout << interpreter.stringify(args[0]) << '\n';
|
std::cout << interpreter.stringify(args[0]) << '\n';
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("print", Value(printFunc.get()));
|
env->define("print", Value(printFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(printFunc);
|
interpreter.addBuiltinFunction(printFunc);
|
||||||
@ -61,7 +71,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
std::cout << interpreter.stringify(args[0]) << std::flush;
|
std::cout << interpreter.stringify(args[0]) << std::flush;
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("printRaw", Value(printRawFunc.get()));
|
env->define("printRaw", Value(printRawFunc));
|
||||||
|
|
||||||
// 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);
|
||||||
@ -91,7 +101,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
throw std::runtime_error("len() can only be used on arrays, strings, and dictionaries");
|
throw std::runtime_error("len() can only be used on arrays, strings, and dictionaries");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
env->define("len", Value(lenFunc.get()));
|
env->define("len", Value(lenFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(lenFunc);
|
interpreter.addBuiltinFunction(lenFunc);
|
||||||
@ -125,7 +135,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return args[0]; // Return the modified array
|
return args[0]; // Return the modified array
|
||||||
});
|
});
|
||||||
env->define("push", Value(pushFunc.get()));
|
env->define("push", Value(pushFunc));
|
||||||
interpreter.addBuiltinFunction(pushFunc);
|
interpreter.addBuiltinFunction(pushFunc);
|
||||||
|
|
||||||
// Create a built-in pop function for arrays
|
// Create a built-in pop function for arrays
|
||||||
@ -162,7 +172,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return lastElement; // Return the popped element
|
return lastElement; // Return the popped element
|
||||||
});
|
});
|
||||||
env->define("pop", Value(popFunc.get()));
|
env->define("pop", Value(popFunc));
|
||||||
interpreter.addBuiltinFunction(popFunc);
|
interpreter.addBuiltinFunction(popFunc);
|
||||||
|
|
||||||
// Create a built-in assert function
|
// Create a built-in assert function
|
||||||
@ -201,7 +211,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("assert", Value(assertFunc.get()));
|
env->define("assert", Value(assertFunc));
|
||||||
|
|
||||||
// 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);
|
||||||
@ -223,7 +233,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(static_cast<double>(microseconds));
|
return Value(static_cast<double>(microseconds));
|
||||||
});
|
});
|
||||||
env->define("time", Value(timeFunc.get()));
|
env->define("time", Value(timeFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(timeFunc);
|
interpreter.addBuiltinFunction(timeFunc);
|
||||||
@ -250,7 +260,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(userInput);
|
return Value(userInput);
|
||||||
});
|
});
|
||||||
env->define("input", Value(inputFunc.get()));
|
env->define("input", Value(inputFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(inputFunc);
|
interpreter.addBuiltinFunction(inputFunc);
|
||||||
@ -289,7 +299,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(typeName);
|
return Value(typeName);
|
||||||
});
|
});
|
||||||
env->define("type", Value(typeFunc.get()));
|
env->define("type", Value(typeFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(typeFunc);
|
interpreter.addBuiltinFunction(typeFunc);
|
||||||
@ -324,7 +334,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
return NONE_VALUE; // Return none for out of range
|
return NONE_VALUE; // Return none for out of range
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
env->define("toNumber", Value(toNumberFunc.get()));
|
env->define("toNumber", Value(toNumberFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toNumberFunc);
|
interpreter.addBuiltinFunction(toNumberFunc);
|
||||||
@ -352,7 +362,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
double value = args[0].asNumber();
|
double value = args[0].asNumber();
|
||||||
return Value(static_cast<double>(static_cast<long long>(value)));
|
return Value(static_cast<double>(static_cast<long long>(value)));
|
||||||
});
|
});
|
||||||
env->define("toInt", Value(toIntFunc.get()));
|
env->define("toInt", Value(toIntFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toIntFunc);
|
interpreter.addBuiltinFunction(toIntFunc);
|
||||||
@ -390,7 +400,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
// For any other type (functions, etc.), consider them truthy
|
// For any other type (functions, etc.), consider them truthy
|
||||||
return Value(true);
|
return Value(true);
|
||||||
});
|
});
|
||||||
env->define("toBoolean", Value(toBooleanFunc.get()));
|
env->define("toBoolean", Value(toBooleanFunc));
|
||||||
|
|
||||||
// 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);
|
||||||
@ -409,7 +419,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
std::exit(exitCode);
|
std::exit(exitCode);
|
||||||
});
|
});
|
||||||
env->define("exit", Value(exitFunc.get()));
|
env->define("exit", Value(exitFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(exitFunc);
|
interpreter.addBuiltinFunction(exitFunc);
|
||||||
@ -448,7 +458,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("sleep", Value(sleepFunc.get()));
|
env->define("sleep", Value(sleepFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(sleepFunc);
|
interpreter.addBuiltinFunction(sleepFunc);
|
||||||
@ -473,7 +483,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(static_cast<double>(rand()) / RAND_MAX);
|
return Value(static_cast<double>(rand()) / RAND_MAX);
|
||||||
});
|
});
|
||||||
env->define("random", Value(randomFunc.get()));
|
env->define("random", Value(randomFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(randomFunc);
|
interpreter.addBuiltinFunction(randomFunc);
|
||||||
@ -526,7 +536,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
throw std::runtime_error("eval failed: " + std::string(e.what()));
|
throw std::runtime_error("eval failed: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
env->define("eval", Value(evalFunc.get()));
|
env->define("eval", Value(evalFunc));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(evalFunc);
|
interpreter.addBuiltinFunction(evalFunc);
|
||||||
@ -559,7 +569,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(keys);
|
return Value(keys);
|
||||||
});
|
});
|
||||||
env->define("keys", Value(keysFunc.get()));
|
env->define("keys", Value(keysFunc));
|
||||||
interpreter.addBuiltinFunction(keysFunc);
|
interpreter.addBuiltinFunction(keysFunc);
|
||||||
|
|
||||||
// Create a built-in values function for dictionaries
|
// Create a built-in values function for dictionaries
|
||||||
@ -590,7 +600,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(values);
|
return Value(values);
|
||||||
});
|
});
|
||||||
env->define("values", Value(valuesFunc.get()));
|
env->define("values", Value(valuesFunc));
|
||||||
interpreter.addBuiltinFunction(valuesFunc);
|
interpreter.addBuiltinFunction(valuesFunc);
|
||||||
|
|
||||||
// Create a built-in has function for dictionaries
|
// Create a built-in has function for dictionaries
|
||||||
@ -625,7 +635,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(dict.find(key) != dict.end());
|
return Value(dict.find(key) != dict.end());
|
||||||
});
|
});
|
||||||
env->define("has", Value(hasFunc.get()));
|
env->define("has", Value(hasFunc));
|
||||||
interpreter.addBuiltinFunction(hasFunc);
|
interpreter.addBuiltinFunction(hasFunc);
|
||||||
|
|
||||||
// Create a built-in readFile function
|
// Create a built-in readFile function
|
||||||
@ -664,7 +674,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(buffer.str());
|
return Value(buffer.str());
|
||||||
});
|
});
|
||||||
env->define("readFile", Value(readFileFunc.get()));
|
env->define("readFile", Value(readFileFunc));
|
||||||
interpreter.addBuiltinFunction(readFileFunc);
|
interpreter.addBuiltinFunction(readFileFunc);
|
||||||
|
|
||||||
// Create a built-in writeFile function
|
// Create a built-in writeFile function
|
||||||
@ -711,7 +721,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("writeFile", Value(writeFileFunc.get()));
|
env->define("writeFile", Value(writeFileFunc));
|
||||||
interpreter.addBuiltinFunction(writeFileFunc);
|
interpreter.addBuiltinFunction(writeFileFunc);
|
||||||
|
|
||||||
// Create a built-in readLines function
|
// Create a built-in readLines function
|
||||||
@ -754,7 +764,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
file.close();
|
file.close();
|
||||||
return Value(lines);
|
return Value(lines);
|
||||||
});
|
});
|
||||||
env->define("readLines", Value(readLinesFunc.get()));
|
env->define("readLines", Value(readLinesFunc));
|
||||||
interpreter.addBuiltinFunction(readLinesFunc);
|
interpreter.addBuiltinFunction(readLinesFunc);
|
||||||
|
|
||||||
// Create a built-in fileExists function
|
// Create a built-in fileExists function
|
||||||
@ -783,7 +793,56 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(exists);
|
return Value(exists);
|
||||||
});
|
});
|
||||||
env->define("fileExists", Value(fileExistsFunc.get()));
|
env->define("fileExists", Value(fileExistsFunc));
|
||||||
interpreter.addBuiltinFunction(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);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -2505,9 +2505,88 @@ assert(len(values(stressDict)) == 100, "Dictionary stress test - values");
|
|||||||
print("Enhanced dictionary tests: PASS");
|
print("Enhanced dictionary tests: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 52.7: TAIL CALL OPTIMIZATION
|
// TEST 52.7: DOT NOTATION AND PROPERTY ACCESS
|
||||||
// ========================================
|
// ========================================
|
||||||
print("\n--- Test 52.7: Tail Call Optimization ---");
|
print("\n--- Test 52.7: Dot Notation and Property Access ---");
|
||||||
|
|
||||||
|
// Basic dictionary property access (read)
|
||||||
|
var person = {"name": "Alice", "age": 30, "city": "NYC"};
|
||||||
|
assert(person.name == "Alice", "Basic dict property access - name");
|
||||||
|
assert(person.age == 30, "Basic dict property access - age");
|
||||||
|
assert(person.city == "NYC", "Basic dict property access - city");
|
||||||
|
print(" Basic dict property access: PASS");
|
||||||
|
|
||||||
|
// Dictionary property assignment (write)
|
||||||
|
var config = {"theme": "light", "lang": "en"};
|
||||||
|
config.theme = "dark";
|
||||||
|
config.lang = "es";
|
||||||
|
config.fontSize = 16; // New property
|
||||||
|
assert(config.theme == "dark", "Dict property assignment - theme");
|
||||||
|
assert(config.lang == "es", "Dict property assignment - lang");
|
||||||
|
assert(config.fontSize == 16, "Dict property assignment - new property");
|
||||||
|
print(" Dict property assignment: PASS");
|
||||||
|
|
||||||
|
// Array properties (read-only)
|
||||||
|
var arr = [10, 20, 30, 40, 50];
|
||||||
|
assert(arr.length == 5, "Array length property");
|
||||||
|
assert(arr.first == 10, "Array first property");
|
||||||
|
assert(arr.last == 50, "Array last property");
|
||||||
|
assert(arr.empty == false, "Array empty property");
|
||||||
|
print(" Array properties: PASS");
|
||||||
|
|
||||||
|
// Array edge cases
|
||||||
|
var emptyArr = [];
|
||||||
|
assert(emptyArr.length == 0, "Empty array length");
|
||||||
|
assert(emptyArr.first == none, "Empty array first");
|
||||||
|
assert(emptyArr.last == none, "Empty array last");
|
||||||
|
assert(emptyArr.empty == true, "Empty array empty");
|
||||||
|
print(" Array edge cases: PASS");
|
||||||
|
|
||||||
|
// Dictionary builtin properties
|
||||||
|
var dictWithProps = {"a": 1, "b": 2, "c": 3};
|
||||||
|
assert(dictWithProps.length == 3, "Dict builtin length property");
|
||||||
|
assert(dictWithProps.empty == false, "Dict builtin empty property");
|
||||||
|
var emptyDict = {};
|
||||||
|
assert(emptyDict.length == 0, "Empty dict length");
|
||||||
|
assert(emptyDict.empty == true, "Empty dict empty");
|
||||||
|
|
||||||
|
// Test dict.keys and dict.values properties
|
||||||
|
assert(len(dictWithProps.keys) == 3, "Dict keys property length");
|
||||||
|
assert(len(dictWithProps.values) == 3, "Dict values property length");
|
||||||
|
assert(len(emptyDict.keys) == 0, "Empty dict keys length");
|
||||||
|
assert(len(emptyDict.values) == 0, "Empty dict values length");
|
||||||
|
|
||||||
|
// Test equivalence with old functions
|
||||||
|
var testDict = {"x": 10, "y": 20};
|
||||||
|
assert(len(testDict.keys) == len(keys(testDict)), "Dict keys equivalent to keys() function");
|
||||||
|
assert(len(testDict.values) == len(values(testDict)), "Dict values equivalent to values() function");
|
||||||
|
print(" Dict builtin properties: PASS");
|
||||||
|
|
||||||
|
// Nested property access and assignment
|
||||||
|
var nested = {
|
||||||
|
"database": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 5432
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert(nested.database.host == "localhost", "Nested property access");
|
||||||
|
nested.database.port = 3306;
|
||||||
|
nested.database.ssl = true; // New nested property
|
||||||
|
assert(nested.database.port == 3306, "Nested property assignment");
|
||||||
|
assert(nested.database.ssl == true, "New nested property assignment");
|
||||||
|
print(" Nested properties: PASS");
|
||||||
|
|
||||||
|
// Property/bracket equivalence
|
||||||
|
assert(person.name == person["name"], "Property/bracket equivalence - name");
|
||||||
|
assert(config.theme == config["theme"], "Property/bracket equivalence - theme");
|
||||||
|
print(" Property/bracket equivalence: PASS");
|
||||||
|
|
||||||
|
print("Dot notation and property access: PASS");
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TEST 52.8: TAIL CALL OPTIMIZATION
|
||||||
|
// ========================================
|
||||||
|
print("\n--- Test 52.8: Tail Call Optimization ---");
|
||||||
|
|
||||||
// Test deep recursion that would stack overflow without TCO
|
// Test deep recursion that would stack overflow without TCO
|
||||||
func factorial(n, acc) {
|
func factorial(n, acc) {
|
||||||
@ -2964,58 +3043,6 @@ var testVar = 42;
|
|||||||
|
|
||||||
print("Enhanced error reporting: PASS (manual verification required)");
|
print("Enhanced error reporting: PASS (manual verification required)");
|
||||||
|
|
||||||
// Test 62: Dictionary Creation and Access
|
|
||||||
print("Test 62: Dictionary Creation and Access");
|
|
||||||
var person = {"name": "Alice", "age": 30, "city": "New York"};
|
|
||||||
assert(person["name"] == "Alice");
|
|
||||||
assert(person["age"] == 30);
|
|
||||||
assert(person["city"] == "New York");
|
|
||||||
assert(person["missing"] == none);
|
|
||||||
print("Dictionary creation and access test complete");
|
|
||||||
|
|
||||||
// Test 63: Dictionary Assignment
|
|
||||||
print("Test 63: Dictionary Assignment");
|
|
||||||
person["email"] = "alice@example.com";
|
|
||||||
person["age"] = 31;
|
|
||||||
assert(person["email"] == "alice@example.com");
|
|
||||||
assert(person["age"] == 31);
|
|
||||||
print("Dictionary assignment test complete");
|
|
||||||
|
|
||||||
// Test 64: Dictionary Built-in Functions
|
|
||||||
print("Test 64: Dictionary Built-in Functions");
|
|
||||||
var keys = keys(person);
|
|
||||||
var values = values(person);
|
|
||||||
assert(len(keys) == 4);
|
|
||||||
assert(len(values) == 4);
|
|
||||||
assert(has(person, "name") == true);
|
|
||||||
assert(has(person, "missing") == false);
|
|
||||||
print("Dictionary built-in functions test complete");
|
|
||||||
|
|
||||||
// Test 65: Nested Dictionaries
|
|
||||||
print("Test 65: Nested Dictionaries");
|
|
||||||
var gameState = {
|
|
||||||
"player": {"health": 100, "level": 5},
|
|
||||||
"world": {"name": "Bobland", "difficulty": "hard"}
|
|
||||||
};
|
|
||||||
assert(gameState["player"]["health"] == 100);
|
|
||||||
assert(gameState["world"]["name"] == "Bobland");
|
|
||||||
print("Nested dictionaries test complete");
|
|
||||||
|
|
||||||
// Test 66: Dictionary with Mixed Types
|
|
||||||
print("Test 66: Dictionary with Mixed Types");
|
|
||||||
var mixed = {
|
|
||||||
"string": "hello",
|
|
||||||
"number": 42,
|
|
||||||
"boolean": true,
|
|
||||||
"array": [1, 2, 3],
|
|
||||||
"none": none
|
|
||||||
};
|
|
||||||
assert(mixed["string"] == "hello");
|
|
||||||
assert(mixed["number"] == 42);
|
|
||||||
assert(mixed["boolean"] == true);
|
|
||||||
assert(mixed["array"][0] == 1);
|
|
||||||
assert(mixed["none"] == none);
|
|
||||||
print("Dictionary with mixed types test complete");
|
|
||||||
|
|
||||||
// Test 67: Copy Behavior - Primitive Types (By Value)
|
// Test 67: Copy Behavior - Primitive Types (By Value)
|
||||||
print("Test 67: Copy Behavior - Primitive Types (By Value)");
|
print("Test 67: Copy Behavior - Primitive Types (By Value)");
|
||||||
@ -3215,6 +3242,14 @@ print(" * Nested dictionaries");
|
|||||||
print(" * Mixed type values");
|
print(" * Mixed type values");
|
||||||
print(" * Dictionary stress testing");
|
print(" * Dictionary stress testing");
|
||||||
print(" * Dictionary with none values");
|
print(" * Dictionary with none values");
|
||||||
|
print("- Dot notation and property access");
|
||||||
|
print(" * Dictionary property access (obj.prop)");
|
||||||
|
print(" * Dictionary property assignment (obj.prop = value)");
|
||||||
|
print(" * Nested property access (obj.a.b.c)");
|
||||||
|
print(" * Array builtin properties (length, first, last, empty)");
|
||||||
|
print(" * Dictionary builtin properties (length, empty, keys, values)");
|
||||||
|
print(" * Property/bracket notation equivalence");
|
||||||
|
print(" * User property precedence over builtins");
|
||||||
print("- Copy behavior (by value vs by reference)");
|
print("- Copy behavior (by value vs by reference)");
|
||||||
print(" * Primitive types copied by value (numbers, strings, booleans)");
|
print(" * Primitive types copied by value (numbers, strings, booleans)");
|
||||||
print(" * Complex types copied by reference (arrays, dictionaries)");
|
print(" * Complex types copied by reference (arrays, dictionaries)");
|
||||||
|
|||||||
83
tests.bob
83
tests.bob
@ -1,38 +1,53 @@
|
|||||||
// var code = "var a = [];
|
var a = [];
|
||||||
|
|
||||||
// for(var i = 0; i < 1000; i++){
|
for(var i = 0; i < 1000000; i++){
|
||||||
// push(a, func(){print(\"I is: \" + i); return (toString(i) + \": \") * 5;});
|
print(i);
|
||||||
// }
|
|
||||||
// var total_ops = 0;
|
|
||||||
// // while(len(a) > 0){
|
|
||||||
// // var fun = pop(a);
|
|
||||||
// // print(\"Function returns: \" + fun());
|
|
||||||
// // total_ops++;
|
|
||||||
// // }
|
|
||||||
// print(len(a));
|
|
||||||
// a = [];
|
|
||||||
// print(len(a));
|
|
||||||
// input(\"Waiting for input\");
|
|
||||||
|
|
||||||
// print(\"Total operations: \" + total_ops);";
|
// Create nested structures with functions at different levels
|
||||||
|
if (i % 4 == 0) {
|
||||||
|
// Nested array with function
|
||||||
// print(code);
|
push(a, [
|
||||||
|
func(){print("Array nested func i=" + i); return i;},
|
||||||
// eval(code);
|
[func(){return "Deep array func " + i;}],
|
||||||
|
i
|
||||||
// print(0xFF);
|
]);
|
||||||
var flag = true;
|
} else if (i % 4 == 1) {
|
||||||
var arr = [0];
|
// Nested dict with function
|
||||||
while(true){
|
push(a, {
|
||||||
if(flag){
|
"func": func(){print("Dict func i=" + i); return i;},
|
||||||
arr[0]++;
|
"nested": {"deepFunc": func(){return "Deep dict func " + i;}},
|
||||||
}else{
|
"value": i
|
||||||
arr[0]--;
|
});
|
||||||
|
} else if (i % 4 == 2) {
|
||||||
|
// Mixed nested array/dict with functions
|
||||||
|
push(a, [
|
||||||
|
{"arrayInDict": func(){return "Mixed " + i;}},
|
||||||
|
[func(){return "Array in array " + i;}, {"more": func(){return i;}}],
|
||||||
|
func(){print("Top level in mixed i=" + i); return i;}
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Simple function (original test case)
|
||||||
|
push(a, func(){print("Simple func i=" + i); return toString(i);});
|
||||||
}
|
}
|
||||||
if(arr[0] == 0 || arr[0] == 10){
|
|
||||||
flag = !flag;
|
|
||||||
}
|
|
||||||
print(arr[0]);
|
|
||||||
sleep(0.01);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("Before: " + len(a));
|
||||||
|
print("Memory usage: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// Test different types of nested function calls
|
||||||
|
a[3691](); // Simple function
|
||||||
|
if (len(a[3692]) > 0) {
|
||||||
|
a[3692][0](); // Nested array function
|
||||||
|
}
|
||||||
|
if (a[3693]["func"]) {
|
||||||
|
a[3693]["func"](); // Nested dict function
|
||||||
|
}
|
||||||
|
print(a);
|
||||||
|
//writeFile("array_contents.txt", toString(a));
|
||||||
|
print("Array contents written to array_contents.txt");
|
||||||
|
print("Memory before cleanup: " + memoryUsage() + " MB");
|
||||||
|
input("Press any key to free memory");
|
||||||
|
|
||||||
|
a = none;
|
||||||
|
print("Memory after cleanup: " + memoryUsage() + " MB");
|
||||||
|
input("waiting...");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user