Property Expression, Fixed memory leaks

This commit is contained in:
Bobby Lucero 2025-08-08 19:03:49 -04:00
parent 87d56bbb13
commit f70c6abd77
29 changed files with 1652 additions and 345 deletions

View File

@ -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}")

View File

@ -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);
``` ```
--- ---

View File

@ -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

View 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
View 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.

View 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 ===");

View 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 ===");

View 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 ===");

View 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 ===");

View 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
View 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)"

View File

@ -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;

View File

@ -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(); }

View File

@ -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);
}; };

View File

@ -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();

View File

@ -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

View File

@ -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 {

View File

@ -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);
} }

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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;
} }

View File

@ -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
}
} }

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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);
} }

View File

@ -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)");

View File

@ -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;}},
"value": i
});
} else if (i % 4 == 2) {
// Mixed nested array/dict with functions
push(a, [
{"arrayInDict": func(){return "Mixed " + i;}},
[func(){return "Array in array " + i;}, {"more": func(){return i;}}],
func(){print("Top level in mixed i=" + i); return i;}
]);
} else { } else {
arr[0]--; // 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...");