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
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
if(WIN32)
# 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 " C++ Standard: ${CMAKE_CXX_STANDARD}")
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
print(numbers[0]); // 1
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
```go
var person = {"name": "Alice", "age": 30};
// Access and modify
// Access and modify (bracket notation)
print(person["name"]); // Alice
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
@ -229,7 +254,7 @@ assert(condition, "message"); // Testing
time(); // Current time in microseconds
sleep(1.5); // Sleep for 1.5 seconds
random(); // Random number 0-1
eval("1 + 2"); // Evaluate string as code
eval("print('Hello');"); // Execute string as code
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)**
- **Arrays**: Full support with indexing, assignment, nested arrays
- **Dictionaries**: Full support with key-value pairs, nested dictionaries
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment, properties (`length`, `first`, `last`, `empty`)
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment, dot notation (`obj.prop`)
- **Mixed Types**: Arrays and dictionaries can hold any value types
#### **Standard Library (Complete)**
@ -169,7 +169,7 @@ var person = {
- Add object literal syntax
- Implement `this` binding
- Support method calls
- Add property access/assignment
- Add property access/assignment (completed - dot notation for dictionaries and arrays)
#### **Classes (Optional)**
```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 ArrayLiteralExpr;
struct ArrayIndexExpr;
struct PropertyExpr;
struct ArrayAssignExpr;
struct PropertyAssignExpr;
struct DictLiteralExpr;
struct DictIndexExpr;
struct DictAssignExpr;
@ -44,8 +46,10 @@ struct ExprVisitor
virtual Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expr) = 0;
virtual Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& 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 visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& 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
{
@ -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
{
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
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; }
inline void clear() { variables.clear(); }

View File

@ -33,6 +33,13 @@ public:
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& 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 visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& 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
ErrorReporter* errorReporter;
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
std::unique_ptr<Evaluator> evaluator;
std::unique_ptr<Executor> executor;
@ -78,6 +84,7 @@ public:
// Methods needed by Evaluator
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 executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
bool isTruthy(Value object);
@ -93,6 +100,15 @@ public:
void cleanupUnusedFunctions();
void cleanupUnusedThunks();
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:
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);

View File

@ -26,9 +26,13 @@ public:
// Memory management utilities
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions);
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks);
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
std::vector<std::shared_ptr<Function>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks);
private:
// Helper methods for stringify

View File

@ -32,14 +32,17 @@ struct Value {
union {
double number;
bool boolean;
Function* function;
BuiltinFunction* builtin_function;
Thunk* thunk;
};
ValueType type;
std::string string_value; // Store strings outside the union for safety
std::shared_ptr<std::vector<Value> > array_value; // Store arrays as shared_ptr for mutability
std::shared_ptr<std::unordered_map<std::string, Value> > dict_value; // Store dictionaries as shared_ptr for mutability
// 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
Value() : number(0.0), type(ValueType::VAL_NONE) {}
@ -48,20 +51,28 @@ struct Value {
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(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
Value(Thunk* t) : thunk(t), type(ValueType::VAL_THUNK) {}
Value(std::shared_ptr<Function> f) : function(f), type(ValueType::VAL_FUNCTION) {}
Value(std::shared_ptr<BuiltinFunction> bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
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(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(arr))) {}
Value(const std::unordered_map<std::string, Value>& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(dict)) {}
Value(std::unordered_map<std::string, Value>&& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(std::move(dict))) {}
// 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
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)) {
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT) {
: type(other.type), string_value(std::move(other.string_value)), array_value(std::move(other.array_value)), dict_value(std::move(other.dict_value)),
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)) {
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
}
other.type = ValueType::VAL_NONE;
@ -74,12 +85,19 @@ struct Value {
if (type == ValueType::VAL_STRING) {
string_value = std::move(other.string_value);
} 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) {
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 {
number = other.number; // Copy the union
number = other.number;
}
other.type = ValueType::VAL_NONE;
}
return *this;
@ -93,23 +111,43 @@ struct Value {
array_value = other.array_value; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_DICT) {
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 {
number = other.number; // Copy the union
number = other.number;
}
}
// Copy assignment (only when needed)
Value& operator=(const Value& 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;
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else if (type == ValueType::VAL_ARRAY) {
array_value = other.array_value; // shared_ptr automatically handles sharing
} 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 {
number = other.number; // Copy the union
number = other.number;
}
}
return *this;
@ -160,9 +198,9 @@ struct Value {
inline std::unordered_map<std::string, Value>& asDict() {
return *dict_value;
}
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
inline Thunk* asThunk() const { return isThunk() ? thunk : nullptr; }
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
// Truthiness check - inline for performance
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());
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,
interpreter->getEnvironment());
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) {

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);
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) {
@ -357,6 +367,9 @@ sptr(Expr) Parser::call()
expr = finishCall(expr);
} else if (match({OPEN_BRACKET})) {
expr = finishArrayIndex(expr);
} else if (match({DOT})) {
Token name = consume(IDENTIFIER, "Expected property name after '.'.");
expr = msptr(PropertyExpr)(expr, name);
} else {
break;
}
@ -472,33 +485,27 @@ sptr(Stmt) Parser::statement()
if(match({CONTINUE})) return continueStatement();
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
// Check for assignment statement
// Check for assignment statement - simplified approach
if(check(IDENTIFIER)) {
// Look ahead to see if this is an assignment
// Try to parse as assignment expression first
int currentPos = current;
advance(); // consume identifier
// 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;
try {
sptr(Expr) expr = assignmentExpression();
consume(SEMICOLON, "Expected ';' after assignment.");
return msptr(ExpressionStmt)(expr);
// 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.");
return msptr(ExpressionStmt)(expr);
}
// If it's not an assignment, reset and parse as expression statement
current = currentPos;
} catch (...) {
// If assignment parsing failed, reset and parse as expression statement
current = currentPos;
}
// Reset position and parse as expression statement
current = currentPos;
}
return expressionStatement();

View File

@ -48,4 +48,20 @@ Value Environment::get(const std::string& 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 value = interpreter->evaluate(expression->value);
switch (expression->op.type) {
case PLUS_EQUAL:
case MINUS_EQUAL:
case STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case BIN_AND_EQUAL:
case BIN_OR_EQUAL:
case BIN_XOR_EQUAL:
case BIN_SLEFT_EQUAL:
case BIN_SRIGHT_EQUAL: {
Value currentValue = interpreter->getEnvironment()->get(expression->name);
// ... (rest of compound assignment logic) ...
break;
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
}
default:
break;
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(expression->name);
Value newValue;
switch (expression->op.type) {
case PLUS_EQUAL:
newValue = currentValue + value;
break;
case MINUS_EQUAL:
newValue = currentValue - value;
break;
case STAR_EQUAL:
newValue = currentValue * value;
break;
case SLASH_EQUAL:
newValue = currentValue / value;
break;
case PERCENT_EQUAL:
newValue = currentValue % value;
break;
case BIN_AND_EQUAL:
newValue = currentValue & value;
break;
case BIN_OR_EQUAL:
newValue = currentValue | value;
break;
case BIN_XOR_EQUAL:
newValue = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
newValue = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
newValue = currentValue >> value;
break;
default:
interpreter->reportError(expression->op.line, expression->op.column, "Runtime Error",
"Unknown assignment operator: " + expression->op.lexeme, "");
throw std::runtime_error("Unknown assignment operator: " + expression->op.lexeme);
}
interpreter->getEnvironment()->assign(expression->name, newValue);
return newValue;
}
interpreter->getEnvironment()->assign(expression->name, 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 callee = expression->callee->accept(this);
std::vector<Value> arguments;
for (const auto& argument : expression->arguments) {
arguments.push_back(argument->accept(this));
}
if (callee.isFunction()) {
Function* function = callee.asFunction();
// Check arity
if (arguments.size() != function->params.size()) {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
std::to_string(arguments.size()) + ".", "");
throw std::runtime_error("Wrong number of arguments.");
}
// Create new environment for function call
auto environment = std::make_shared<Environment>(function->closure);
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
}
// Execute function body
auto previous = interpreter->getEnvironment();
interpreter->setEnvironment(environment);
ExecutionContext context;
context.isFunctionBody = true;
try {
for (const auto& stmt : function->body) {
interpreter->execute(stmt, &context);
if (context.hasReturn) {
interpreter->setEnvironment(previous);
return context.returnValue;
}
}
} catch (...) {
interpreter->setEnvironment(previous);
throw;
}
interpreter->setEnvironment(previous);
return NONE_VALUE;
} else if (callee.isBuiltinFunction()) {
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
} else {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Can only call functions and classes.", "");
throw std::runtime_error("Can only call functions and classes.");
}
// Delegate to inline implementation in Interpreter for performance
return interpreter->evaluateCallExprInline(expression);
}
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 array = expr->array->accept(this);
Value index = expr->index->accept(this);
@ -422,13 +430,95 @@ Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& ex
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) {
std::vector<std::string> paramNames;
for (const Token& param : expression->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
interpreter->addFunction(function);
return Value(function.get());
// Capture a snapshot of the current environment so loop vars like 'i' are frozen per iteration
auto closureEnv = std::make_shared<Environment>(*interpreter->getEnvironment());
closureEnv->pruneForClosureCapture();
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
return Value(function);
}
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,
interpreter->getEnvironment());
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) {
@ -194,11 +194,21 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
}
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
interpreter->getEnvironment()->assign(statement->name, value);
} else {
try {
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
try {
// Assign first to release references held by the old value
interpreter->getEnvironment()->assign(statement->name, value);
// Clean up on any reassignment, regardless of old/new type
interpreter->forceCleanup();
} catch (const std::exception& e) {
std::cerr << "Error during assignment: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
}
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(statement->name);
Value newValue;
@ -242,4 +252,8 @@ void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, Exe
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);
}
}
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 "TypeWrapper.h" // For Function and BuiltinFunction definitions
#include <sstream>
#if defined(__linux__)
#include <malloc.h>
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#endif
#include <iomanip>
#include <limits>
#include <cmath>
#include <algorithm>
bool RuntimeDiagnostics::isTruthy(Value object) {
if(object.isBoolean()) {
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) {
// Only remove thunks that are definitely not referenced anywhere (use_count == 1)
// This is more conservative to prevent dangling pointer issues
@ -212,4 +230,39 @@ void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunctio
}),
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 <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) {
// Create a built-in toString function
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]));
});
env->define("toString", Value(toStringFunc.get()));
env->define("toString", Value(toStringFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toStringFunc);
@ -42,7 +52,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
std::cout << interpreter.stringify(args[0]) << '\n';
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
interpreter.addBuiltinFunction(printFunc);
@ -61,7 +71,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
std::cout << interpreter.stringify(args[0]) << std::flush;
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
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");
}
});
env->define("len", Value(lenFunc.get()));
env->define("len", Value(lenFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(lenFunc);
@ -125,7 +135,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return args[0]; // Return the modified array
});
env->define("push", Value(pushFunc.get()));
env->define("push", Value(pushFunc));
interpreter.addBuiltinFunction(pushFunc);
// 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
});
env->define("pop", Value(popFunc.get()));
env->define("pop", Value(popFunc));
interpreter.addBuiltinFunction(popFunc);
// Create a built-in assert function
@ -201,7 +211,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
interpreter.addBuiltinFunction(assertFunc);
@ -223,7 +233,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
interpreter.addBuiltinFunction(timeFunc);
@ -250,7 +260,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
interpreter.addBuiltinFunction(inputFunc);
@ -289,7 +299,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
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
}
});
env->define("toNumber", Value(toNumberFunc.get()));
env->define("toNumber", Value(toNumberFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toNumberFunc);
@ -352,7 +362,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
double value = args[0].asNumber();
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
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
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
interpreter.addBuiltinFunction(toBooleanFunc);
@ -409,7 +419,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
interpreter.addBuiltinFunction(exitFunc);
@ -448,7 +458,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
interpreter.addBuiltinFunction(sleepFunc);
@ -473,7 +483,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
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
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()));
}
});
env->define("eval", Value(evalFunc.get()));
env->define("eval", Value(evalFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(evalFunc);
@ -559,7 +569,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return Value(keys);
});
env->define("keys", Value(keysFunc.get()));
env->define("keys", Value(keysFunc));
interpreter.addBuiltinFunction(keysFunc);
// Create a built-in values function for dictionaries
@ -590,7 +600,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return Value(values);
});
env->define("values", Value(valuesFunc.get()));
env->define("values", Value(valuesFunc));
interpreter.addBuiltinFunction(valuesFunc);
// 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());
});
env->define("has", Value(hasFunc.get()));
env->define("has", Value(hasFunc));
interpreter.addBuiltinFunction(hasFunc);
// Create a built-in readFile function
@ -664,7 +674,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return Value(buffer.str());
});
env->define("readFile", Value(readFileFunc.get()));
env->define("readFile", Value(readFileFunc));
interpreter.addBuiltinFunction(readFileFunc);
// Create a built-in writeFile function
@ -711,7 +721,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return NONE_VALUE;
});
env->define("writeFile", Value(writeFileFunc.get()));
env->define("writeFile", Value(writeFileFunc));
interpreter.addBuiltinFunction(writeFileFunc);
// Create a built-in readLines function
@ -754,7 +764,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
file.close();
return Value(lines);
});
env->define("readLines", Value(readLinesFunc.get()));
env->define("readLines", Value(readLinesFunc));
interpreter.addBuiltinFunction(readLinesFunc);
// Create a built-in fileExists function
@ -783,7 +793,56 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return Value(exists);
});
env->define("fileExists", Value(fileExistsFunc.get()));
env->define("fileExists", Value(fileExistsFunc));
interpreter.addBuiltinFunction(fileExistsFunc);
// Create a built-in memoryUsage function (platform-specific, best effort)
auto memoryUsageFunc = std::make_shared<BuiltinFunction>("memoryUsage",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
// Platform-specific memory usage detection
size_t memoryBytes = 0;
#if defined(__APPLE__) && defined(__MACH__)
// macOS
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
memoryBytes = info.resident_size;
}
#elif defined(__linux__)
// Linux - read from /proc/self/status
std::ifstream statusFile("/proc/self/status");
std::string line;
while (std::getline(statusFile, line)) {
if (line.substr(0, 6) == "VmRSS:") {
std::istringstream iss(line);
std::string label, value, unit;
iss >> label >> value >> unit;
memoryBytes = std::stoull(value) * 1024; // Convert KB to bytes
break;
}
}
#elif defined(_WIN32)
// Windows
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
memoryBytes = pmc.WorkingSetSize;
}
#endif
// Return memory usage in MB for readability
double memoryMB = static_cast<double>(memoryBytes) / (1024.0 * 1024.0);
return Value(memoryMB);
});
env->define("memoryUsage", Value(memoryUsageFunc));
interpreter.addBuiltinFunction(memoryUsageFunc);
}

View File

@ -2505,9 +2505,88 @@ assert(len(values(stressDict)) == 100, "Dictionary stress test - values");
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
func factorial(n, acc) {
@ -2964,58 +3043,6 @@ var testVar = 42;
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)
print("Test 67: Copy Behavior - Primitive Types (By Value)");
@ -3215,6 +3242,14 @@ print(" * Nested dictionaries");
print(" * Mixed type values");
print(" * Dictionary stress testing");
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(" * Primitive types copied by value (numbers, strings, booleans)");
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++){
// push(a, func(){print(\"I is: \" + i); return (toString(i) + \": \") * 5;});
// }
// 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);";
// print(code);
// eval(code);
// print(0xFF);
var flag = true;
var arr = [0];
while(true){
if(flag){
arr[0]++;
}else{
arr[0]--;
for(var i = 0; i < 1000000; i++){
print(i);
// Create nested structures with functions at different levels
if (i % 4 == 0) {
// Nested array with function
push(a, [
func(){print("Array nested func i=" + i); return i;},
[func(){return "Deep array func " + i;}],
i
]);
} else if (i % 4 == 1) {
// Nested dict with function
push(a, {
"func": func(){print("Dict func i=" + i); return i;},
"nested": {"deepFunc": func(){return "Deep dict func " + i;}},
"value": i
});
} else if (i % 4 == 2) {
// Mixed nested array/dict with functions
push(a, [
{"arrayInDict": func(){return "Mixed " + i;}},
[func(){return "Array in array " + i;}, {"more": func(){return i;}}],
func(){print("Top level in mixed i=" + i); return i;}
]);
} else {
// Simple function (original test case)
push(a, func(){print("Simple func i=" + i); return toString(i);});
}
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...");