Compare commits

...

13 Commits

Author SHA1 Message Date
f70c6abd77 Property Expression, Fixed memory leaks 2025-08-08 19:03:49 -04:00
87d56bbb13 Moved to cmake and ninja, updated docs 2025-08-07 19:09:25 -04:00
6c17ce96f0 Cleaned up project structure 2025-08-07 17:11:27 -04:00
2104fbe1f5 Clean up test files 2025-08-07 02:07:54 -04:00
32910b1e57 Add string indexing and comprehensive regression test 2025-08-07 02:06:52 -04:00
b97715e549 General fixes 2025-08-07 00:33:16 -04:00
eacb86ec77 feat: comprehensive language enhancements and code quality improvements
Major additions and improvements across the Bob language ecosystem:

Core Language Features:

- Add comprehensive dictionary support with full CRUD operations

- Implement built-in functions: keys(), values(), has() for dictionaries

- Add string multiplication operator (string * number)

- Enhance error reporting with detailed context and call stacks

- Add ternary operator support (condition ? true_expr : false_expr)

- Implement do-while loops with break/continue support

- Add array increment/decrement operators (++, --)

- Add cross-type comparison operators with proper type coercion

- Implement toInt() function for float-to-integer conversion

- Add float array index auto-truncation (like JavaScript/Lua)

Code Quality & Linter Fixes:

- Remove all "using namespace std;" statements (best practice)

- Add proper std:: prefixes throughout codebase

- Fix const correctness in helper functions

- Resolve class/struct declaration mismatches

- Fix sign comparison warnings in array indexing

- Remove unused lambda captures in built-in functions

- Fix brace initialization warnings in parser

Documentation & Tooling:

- Significantly expand BOB_LANGUAGE_REFERENCE.md with new features

- Update VS Code extension with enhanced syntax highlighting

- Add comprehensive code snippets for new language features

- Update version information and package metadata

Test Suite:

- Add extensive dictionary functionality tests

- Add tests for new operators and built-in functions

- Add comprehensive copy behavior tests (by value vs by reference)

- Add performance and edge case testing

Architecture Improvements:

- Enhance Value system with proper move semantics

- Improve memory management with shared_ptr for complex types

- Add trampoline-based tail call optimization

- Implement proper error context propagation

This represents a major milestone in Bob language development with production-ready dictionary support, comprehensive testing, and significantly improved code quality.
2025-08-07 00:12:04 -04:00
43c5f081d7 Error reporting fix 2025-08-06 21:46:52 -04:00
17c15e5bad Various changes
Arrays, loops, ternary, and other major features. Check doc
2025-08-06 21:42:09 -04:00
72a1b82b43 More things
- Add while, for, and do-while loops with break/continue
- Implement assignment statements (prevents if(x=10) bugs)
- Keep assignment expressions only for for-loop clauses
- Fix critical memory management bug (dangling pointers in cleanup)
- Add automatic memory cleanup with conservative reference counting
- Consolidate documentation into single reference file
- Add comprehensive test coverage for all loop types and edge cases
- VSCode extension for bob highlighting and snippets
2025-08-06 00:57:36 -04:00
671f8a6350 RAII env 2025-08-05 20:40:40 -04:00
b87b342dff Tail call testing 2025-08-05 19:06:52 -04:00
313c996edd Cross platform fixes 2025-08-05 01:42:38 -04:00
87 changed files with 9481 additions and 2365 deletions

BIN
.DS_Store vendored

Binary file not shown.

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
/.vscode /.vscode
build/ build/
.DS_Store
build-ninja

View File

@ -1,361 +0,0 @@
# Bob Language Reference
## Overview
Bob is a dynamically typed programming language with a focus on simplicity and expressiveness. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages.
## Table of Contents
1. [Data Types](#data-types)
2. [Variables](#variables)
3. [Operators](#operators)
4. [Functions](#functions)
5. [Control Flow](#control-flow)
6. [Standard Library](#standard-library)
7. [Error Handling](#error-handling)
8. [Examples](#examples)
9. [Language Nuances](#language-nuances)
## Data Types
### Numbers
- **Integers**: `42`, `-10`, `0`
- **Floats**: `3.14`, `2.718`, `-1.5`
- **Automatic conversion**: Numbers are stored as doubles internally
### Strings
- **Literal strings**: `"Hello, World!"`
- **Empty strings**: `""`
- **Escape sequences**: Not currently supported
### Booleans
- **True**: `true`
- **False**: `false`
### None
- **Null value**: `none` (represents absence of value)
## Variables
### Declaration
```bob
var name = "Bob";
var age = 25;
var isActive = true;
```
### Assignment
```bob
var x = 10;
x = 20; // Reassignment
```
### Scoping
- **Global scope**: Variables declared at top level
- **Local scope**: Variables declared inside functions
- **Shadowing**: Local variables can shadow global variables
- **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration
### Variable Behavior
```bob
var globalVar = 100;
func testScope() {
var localVar = 50; // Local variable
return localVar + globalVar; // Can access global
}
```
## Operators
### Arithmetic Operators
- **Addition**: `+`
- **Subtraction**: `-`
- **Multiplication**: `*`
- **Division**: `/`
- **Modulo**: `%`
### Comparison Operators
- **Equal**: `==`
- **Not equal**: `!=`
- **Greater than**: `>`
- **Less than**: `<`
- **Greater than or equal**: `>=`
- **Less than or equal**: `<=`
### String Operators
#### Concatenation
Bob supports bidirectional string + number concatenation with automatic type conversion:
```bob
// String + Number
"Hello " + 42; // → "Hello 42"
"Pi: " + 3.14; // → "Pi: 3.14"
// Number + String
42 + " items"; // → "42 items"
3.14 + " is pi"; // → "3.14 is pi"
```
#### String Multiplication
```bob
"hello" * 3; // → "hellohellohello"
3 * "hello"; // → "hellohellohello"
```
**Note**: String multiplication requires whole numbers.
### Number Formatting
Bob automatically formats numbers to show only significant digits:
```bob
"Count: " + 2.0; // → "Count: 2" (no trailing zeros)
"Pi: " + 3.14; // → "Pi: 3.14" (exact precision)
"Integer: " + 42; // → "Integer: 42" (no decimal)
```
## Functions
### Function Declaration
```bob
func add(a, b) {
return a + b;
}
```
### Function Call
```bob
var result = add(2, 3); // result = 5
```
### Parameters
- **Any number of parameters** supported
- **No default parameters** (not implemented)
- **No keyword arguments** (not implemented)
### Return Values
- **Explicit return**: `return value;`
- **Implicit return**: Functions return `none` if no return statement
- **Early return**: Functions can return from anywhere
### Closures
Bob supports lexical closures with variable capture:
```bob
var outerVar = "Outer";
func makeGreeter(greeting) {
return greeting + " " + outerVar;
}
var greeter = makeGreeter("Hello");
// greeter captures outerVar from its lexical scope
```
### Nested Functions
```bob
func outer() {
func inner() {
return 42;
}
return inner();
}
```
## Control Flow
### Current Status
**Control flow statements are NOT implemented yet:**
- `if` statements
- `while` loops
- `for` loops
- `else` clauses
### Planned Features
- Conditional execution
- Looping constructs
- Logical operators (`and`, `or`, `not`)
## Standard Library
### Print Function
```bob
print("Hello, World!");
print(42);
print(true);
```
**Usage**: `print(expression)`
- Prints the result of any expression
- Automatically converts values to strings
- Adds newline after output
### Assert Function
```bob
assert(condition, "optional message");
```
**Usage**:
- `assert(true)` - passes silently
- `assert(false)` - throws error and stops execution
- `assert(condition, "message")` - includes custom error message
**Behavior**:
- Terminates program execution on failure
- No exception handling mechanism (yet)
- Useful for testing and validation
## Error Handling
### Current Error Types
- **Division by zero**: `DivisionByZeroError`
- **Type errors**: `Operands must be of same type`
- **String multiplication**: `String multiplier must be whole number`
- **Assertion failures**: `Assertion failed: condition is false`
### Error Behavior
- **No try-catch**: Exception handling not implemented
- **Program termination**: Errors stop execution immediately
- **Error messages**: Descriptive error messages printed to console
### Common Error Scenarios
```bob
// Division by zero
10 / 0; // Error: DivisionByZeroError
// Type mismatch
"hello" - "world"; // Error: Cannot use '-' on two strings
// Invalid string multiplication
"hello" * 3.5; // Error: String multiplier must be whole number
// Undefined variable
undefinedVar; // Error: Undefined variable
```
## Examples
### Basic Calculator
```bob
func add(a, b) {
return a + b;
}
func multiply(a, b) {
return a * b;
}
var result = add(5, multiply(3, 4));
print("Result: " + result); // Result: 17
```
### String Processing
```bob
func greet(name) {
return "Hello, " + name + "!";
}
func repeat(str, count) {
return str * count;
}
var greeting = greet("Bob");
var repeated = repeat("Ha", 3);
print(greeting + " " + repeated); // Hello, Bob! HaHaHa
```
### Variable Scoping Example
```bob
var globalCounter = 0;
func increment() {
var localCounter = 1;
globalCounter = globalCounter + localCounter;
return globalCounter;
}
print("Before: " + globalCounter); // Before: 0
increment();
print("After: " + globalCounter); // After: 1
```
## Language Nuances
### Type System
- **Dynamic typing**: Variables can hold any type
- **Automatic conversion**: Numbers and strings convert automatically
- **No type annotations**: Types are inferred at runtime
### Memory Management
- **Reference counting**: Uses `std::shared_ptr` for automatic memory management
- **No manual memory management**: No `delete` or `free` needed
- **Garbage collection**: Automatic cleanup of unused objects
### Performance Characteristics
- **Interpreted**: Code is executed by an interpreter
- **AST-based**: Abstract Syntax Tree for execution
- **No compilation**: Direct interpretation of source code
### Syntax Rules
- **Semicolons**: Required at end of statements
- **Parentheses**: Required for function calls
- **Curly braces**: Required for function bodies
- **Case sensitive**: `var` and `Var` are different
### Comparison with Other Languages
#### vs Python
- **Similar**: Dynamic typing, functions, closures
- **Different**: No `global` keyword, automatic string conversion, different error handling
#### vs JavaScript
- **Similar**: Automatic type conversion, string concatenation
- **Different**: No `undefined`, different syntax, no `null`
#### vs Lua
- **Similar**: Dynamic typing, functions
- **Different**: No `local` keyword, different scoping rules
### Limitations
- **No control flow**: No if/while/for statements
- **No logical operators**: No `and`/`or`/`not`
- **No exception handling**: No try-catch blocks
- **No modules**: No import/export system
- **No classes**: No object-oriented features
- **No arrays/lists**: No built-in collection types
- **No dictionaries**: No key-value data structures
### Future Features
- Control flow statements
- Logical operators
- Exception handling
- Collection types (arrays, dictionaries)
- Modules and imports
- Object-oriented programming
- Standard library expansion
## Getting Started
### Running Bob Code
```bash
# Compile the interpreter
make
# Run a Bob file
./build/bob your_file.bob
# Run the comprehensive test suite
./build/bob test_bob_language.bob
```
### File Extension
- **`.bob`**: Standard file extension for Bob source code
### Interactive Mode
- **Not implemented**: No REPL (Read-Eval-Print Loop) yet
- **File-based**: All code must be in `.bob` files
---
*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.*

160
CMakeLists.txt Normal file
View File

@ -0,0 +1,160 @@
cmake_minimum_required(VERSION 3.20)
# Project definition
project(bob
VERSION 0.0.3
DESCRIPTION "Bob Language Interpreter"
LANGUAGES CXX
)
# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build type defaults
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# Output directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# Compiler-specific options
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(BOB_COMPILE_OPTIONS
-Wall -Wextra
-Wno-unused-variable
-Wno-unused-parameter
-Wno-switch
)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
list(APPEND BOB_COMPILE_OPTIONS -O3 -march=native)
endif()
elseif(MSVC)
set(BOB_COMPILE_OPTIONS
/W4
/wd4100 # unreferenced formal parameter
/wd4101 # unreferenced local variable
)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
list(APPEND BOB_COMPILE_OPTIONS /O2)
endif()
endif()
# Collect source files
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.cpp")
file(GLOB_RECURSE BOB_PARSING_SOURCES "src/sources/parsing/*.cpp")
file(GLOB_RECURSE BOB_STDLIB_SOURCES "src/sources/stdlib/*.cpp")
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.cpp")
# All source files
set(BOB_ALL_SOURCES
${BOB_RUNTIME_SOURCES}
${BOB_PARSING_SOURCES}
${BOB_STDLIB_SOURCES}
${BOB_CLI_SOURCES}
)
# Create the executable
add_executable(bob ${BOB_ALL_SOURCES})
# Include directories
target_include_directories(bob PRIVATE
src/headers/runtime
src/headers/parsing
src/headers/stdlib
src/headers/cli
src/headers/common
)
# 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
target_compile_definitions(bob PRIVATE _CRT_SECURE_NO_WARNINGS)
elseif(UNIX AND NOT APPLE)
# Linux-specific settings
target_link_libraries(bob PRIVATE pthread)
elseif(APPLE)
# macOS-specific settings
set_target_properties(bob PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist
)
endif()
# Generate compile_commands.json for language servers
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Testing support
enable_testing()
# Add test for the main test suite
add_test(
NAME bob_test_suite
COMMAND bob test_bob_language.bob
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
# Custom target for running tests with verbose output
add_custom_target(test_verbose
COMMAND ${CMAKE_CTEST_COMMAND} --verbose
DEPENDS bob
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Install rules
install(TARGETS bob
RUNTIME DESTINATION bin
COMPONENT Runtime
)
# Install test files (optional)
install(FILES test_bob_language.bob
DESTINATION share/bob/tests
COMPONENT Tests
)
# CPack configuration for packaging
set(CPACK_PACKAGE_NAME "Bob Language")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Bob Language Interpreter")
set(CPACK_PACKAGE_VENDOR "Bob Language Team")
set(CPACK_PACKAGE_CONTACT "your-email@example.com")
if(WIN32)
set(CPACK_GENERATOR "ZIP;NSIS")
elseif(APPLE)
set(CPACK_GENERATOR "TGZ;DragNDrop")
else()
set(CPACK_GENERATOR "TGZ;DEB;RPM")
endif()
include(CPack)
# Print configuration summary
message(STATUS "Bob Language Build Configuration:")
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 " 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

@ -1,48 +0,0 @@
# Makefile
# Compiler
CC = g++
# Compiler flags
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
# Source directory
SRC_DIR = ./source
# Output directory
BUILD_DIR = ./build
# Find all C++ files recursively in the source directory
CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp')
# Generate object file names by replacing the source directory with the build directory
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
# Create directories for object files
$(shell mkdir -p $(dir $(OBJ_FILES)))
# Default target
all: build
# Rule to create necessary directories
$(DIRS):
mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@)
# Rule to compile object files
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CC) $(CFLAGS) -c $< -o $@
# Rule to link object files into the final executable
$(BUILD_DIR)/bob: $(OBJ_FILES)
$(CC) $(CFLAGS) $^ -o $@
run:
./$(BUILD_DIR)/bob
build: clean $(BUILD_DIR)/bob
# Clean build directory
clean:
rm -rf $(BUILD_DIR)/*

View File

@ -1 +1,37 @@
# Bob ```
██████╗ ██████╗ ██████╗
██╔══██╗██╔═══██╗██╔══██╗
██████╔╝██║ ██║██████╔╝
██╔══██╗██║ ██║██╔══██╗
██████╔╝╚██████╔╝██████╔╝
╚═════╝ ╚═════╝ ╚═════╝
```
A modern programming language with all the features/sytax I prefer
## Documentation
- **[Language Reference](Reference/BOB_LANGUAGE_REFERENCE.md)** - Language syntax and features
- **[Build Guide](Reference/BUILD.md)** - How to build Bob
- **[Roadmap](Reference/ROADMAP.md)** - What's done and what might come next
## Features
- **Core Types**: Numbers, strings, booleans, arrays, dictionaries, functions
- **Advanced Functions**: First-class functions, closures, anonymous functions, tail call optimization
- **Control Flow**: If/else statements, while/do-while/for loops, break/continue
- **Operators**: Arithmetic, logical, bitwise, comparison, compound assignment (+=, -=, etc.)
- **Built-in Functions**: print, input, assert, len, push, pop, keys, values, type conversion (toString, toNumber, toInt)
- **Other Stuff**: String interpolation, escape sequences, file I/O, eval, time/sleep, random
- **Memory**: Automatic cleanup, no manual memory management
## Quick Start
```bash
# Build
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
# Run
./build/bin/bob your_file.bob
```

View File

@ -1,203 +0,0 @@
# Bob Language Development Roadmap
## Current Status
- Basic expressions (arithmetic, comparison, logical)
- Variables and assignment
- Print statements (converted to standard library function)
- Block statements
- Environment/scoping
- Function implementation (COMPLETED)
- Return statements (COMPLETED)
- Closures (COMPLETED)
- Assert function (COMPLETED)
- Standard library infrastructure (COMPLETED)
- First-class functions and higher-order functions (COMPLETED)
- String + number concatenation with smart formatting (COMPLETED)
- String multiplication (COMPLETED)
- Alphanumeric identifiers (COMPLETED)
- Comprehensive testing framework (COMPLETED)
## Phase 1: Core Language Features (High Priority)
### 1. Control Flow
```bob
// If statements
if (x > 10) {
print "big";
} else {
print "small";
}
// While loops
var i = 0;
while (i < 5) {
print i;
i = i + 1;
}
```
**Implementation:**
- Add `IfStmt` and `WhileStmt` to Statement.h
- Update parser to handle `if` and `while` keywords
- Implement control flow in interpreter
### 2. Logical Operators
```bob
// Currently missing: and, or, not operators
if (x > 0 and y < 10) {
print "valid range";
}
```
**Implementation:**
- Add `and`, `or`, `not` operator parsing
- Implement logical operator evaluation in interpreter
### 3. Better Error Handling
```bob
// Current: Basic error messages
// Goal: Line numbers, better context
Error at line 5: Expected ';' after expression
print 42
^
```
## Phase 2: Data Structures (Medium Priority)
### 4. Arrays/Lists
```bob
var numbers = [1, 2, 3, 4];
print numbers[0]; // 1
numbers[1] = 42;
```
### 5. Maps/Dictionaries
```bob
var person = {"name": "Bob", "age": 25};
print person["name"];
person["city"] = "NYC";
```
## Phase 3: Standard Library (Medium Priority)
### 6. Additional Built-in Functions
```bob
len("hello"); // String length
input("Enter name: "); // User input
random(1, 100); // Random numbers
type(42); // Type checking
```
### 7. File I/O
```bob
var content = readFile("data.txt");
writeFile("output.txt", "Hello World");
```
## Phase 4: Advanced Features (Lower Priority)
### 8. Classes & Objects
```bob
class Person {
init(name, age) {
this.name = name;
this.age = age;
}
greet() {
print "Hello, I'm " + this.name;
}
}
var bob = Person("Bob", 25);
bob.greet();
```
### 9. Modules/Imports
```bob
import "math.bob";
import "utils.bob";
```
### 10. Type System
```bob
// Optional type annotations
fun add(a: number, b: number): number {
return a + b;
}
```
## Implementation Tips
### For Each Feature:
1. Lexer: Add new tokens if needed
2. Parser: Add new expression/statement types
3. AST: Define new node types
4. Interpreter: Implement evaluation logic
5. Test: Create test cases using assert function
### Testing Strategy:
```bob
// Use the new assert function for comprehensive testing
assert(add(2, 3) == 5, "add(2, 3) should equal 5");
assert(x > 0, "x should be positive");
```
## Recommended Next Steps
1. Add if statements (fundamental control flow)
2. Add while loops (enables iteration)
3. Implement logical operators (and, or, not)
4. Improve error messages (better developer experience)
5. Add arrays (most useful data structure)
## Success Metrics
- [x] Can write simple functions
- [x] Can use return statements
- [x] Can use closures
- [x] Has assert function for testing
- [x] Has standard library infrastructure
- [x] Supports first-class functions
- [x] Has comprehensive testing framework
- [ ] Can use if/else statements
- [ ] Can use while loops
- [ ] Can use logical operators
- [ ] Can work with arrays
- [ ] Can read/write files
- [ ] Has good error messages
## Resources
- [Crafting Interpreters](https://craftinginterpreters.com/) - Excellent resource for language implementation
- [Bob's current source code](./source/) - Your implementation
- [Test files](./*.bob) - Examples of current functionality
## Recent Achievements
### Function Implementation (COMPLETED)
- Function declarations with parameters
- Function calls with arguments
- Return statements
- Proper scoping and closures
- Nested function calls
### Standard Library (COMPLETED)
- `print()` function (converted from statement)
- `assert()` function with custom messages
- Extensible architecture for adding more functions
### Testing Framework (COMPLETED)
- Comprehensive test suite using assert
- Tests for all language features
- Proper error handling and execution stopping
### Advanced Language Features (COMPLETED)
- First-class functions and higher-order functions
- Function passing as arguments
- Function composition patterns
- Callback patterns and function storage
- String + number concatenation with smart formatting
- String multiplication (string * number, number * string)
- Alphanumeric identifiers support
- Stress testing with 100-parameter functions

View File

@ -0,0 +1,382 @@
# Bob Language Reference
## Quick Start
```bash
# Build Bob
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
# Run a file
./build/bin/bob script.bob
# Interactive mode
./build/bin/bob
```
## Data Types
### Numbers
```go
var integer = 42;
var float = 3.14;
var negative = -10;
```
### Strings
```go
var text = "Hello, World!";
var empty = "";
var escaped = "Line 1\nLine 2\t\"quoted\"";
var concat = "Hello" + " " + "World";
var repeat = "hi" * 3; // "hihihi"
```
### Booleans
```go
var yes = true;
var no = false;
```
### None
```go
var nothing = none;
```
### Arrays
```go
var numbers = [1, 2, 3];
var mixed = [42, "hello", true];
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 (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
```go
var x = 10; // Declaration
x = 20; // Reassignment
y += 5; // Compound assignment
z++; // Increment (for array elements)
```
## Operators
### Arithmetic
```go
+ - * / % // Basic math
-x // Unary minus
```
### Comparison
```go
== != < > <= >= // Comparisons
```
### Logical
```go
&& || ! // AND, OR, NOT (short-circuit)
```
### Bitwise
```go
& | ^ << >> ~ // Bitwise operations
```
### Compound Assignment
```go
+= -= *= /= %= // Arithmetic compound
&= |= ^= <<= >>= // Bitwise compound
```
### Ternary
```go
var result = condition ? "yes" : "no";
```
## Control Flow
### If Statements
```go
if (x > 0) {
print("positive");
} else if (x < 0) {
print("negative");
} else {
print("zero");
}
```
### While Loops
```go
while (i < 10) {
print(i);
i = i + 1;
}
```
### Do-While Loops
```go
do {
print(i);
i = i + 1;
} while (i < 10);
```
### For Loops
```go
for (var i = 0; i < 10; i = i + 1) {
print(i);
}
// Break and continue work in all loops
for (var i = 0; i < 10; i = i + 1) {
if (i == 5) break;
if (i % 2 == 0) continue;
print(i);
}
```
## Functions
### Basic Functions
```go
func greet(name) {
return "Hello, " + name;
}
var message = greet("Alice");
```
### Anonymous Functions
```go
var square = func(x) { return x * x; };
var result = square(5);
```
### Closures
```go
func makeCounter() {
var count = 0;
return func() {
count = count + 1;
return count;
};
}
var counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
```
### First-Class Functions
```go
func apply(fn, x) {
return fn(x);
}
var result = apply(square, 10); // 100
```
## Built-in Functions
### I/O
```go
print("Hello"); // Output with newline
printRaw("No newline"); // Output without newline
var input = input("Enter something: ");
```
### Type Conversion
```go
toString(42); // "42"
toNumber("3.14"); // 3.14
toInt(3.9); // 3
toBoolean(1); // true
type(42); // "number"
```
### Arrays & Strings
```go
len([1, 2, 3]); // 3
len("hello"); // 5
push(array, value); // Add to end
pop(array); // Remove from end
```
### Dictionaries
```go
keys(dict); // Array of keys
values(dict); // Array of values
has(dict, "key"); // Check if key exists
```
### Utility
```go
assert(condition, "message"); // Testing
time(); // Current time in microseconds
sleep(1.5); // Sleep for 1.5 seconds
random(); // Random number 0-1
eval("print('Hello');"); // Execute string as code
exit(0); // Exit program
```
### File I/O
```go
var content = readFile("data.txt");
writeFile("output.txt", "Hello");
var lines = readLines("config.txt");
var exists = fileExists("test.txt");
```
## Advanced Features
### String Interpolation
```go
var name = "Alice";
var age = 30;
var message = "Name: " + name + ", Age: " + age;
```
### Tail Call Optimization
```go
func factorial(n, acc) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // Tail call optimized
}
```
### Assignment System
Bob has a unique assignment system that prevents common bugs:
```go
// Assignment statements (everywhere)
var x = 5;
x = 10;
y += 5;
// Assignment expressions (only in for loops)
for (var i = 0; i < 5; i = i + 1) { } // OK
for (j = 0; j < 5; j += 1) { } // OK
// This prevents bugs like:
if (x = 10) { } // PARSE ERROR - prevents accidental assignment
```
## Memory Management
Bob automatically manages memory - no manual allocation or deallocation needed. Objects are cleaned up when no longer referenced.
## Error Handling
Bob provides helpful error messages with context:
```go
// Runtime errors show line numbers and context
var x = undefined_variable; // Error: Undefined variable 'undefined_variable' at line 2
// Type errors are caught
var result = "hello" / 5; // Error: Cannot divide string by number
```
## Interactive Mode (REPL)
Bob includes an interactive mode for experimenting:
```bash
$ ./build/bin/bob
Bob Interactive Mode
> var x = 42;
> print(x * 2);
84
> exit();
```
## Examples
### Fibonacci with Tail Call Optimization
```go
func fib(n, a, b) {
if (n == 0) return a;
if (n == 1) return b;
return fib(n - 1, b, a + b);
}
print(fib(40, 0, 1)); // Fast even for large numbers
```
### Working with Data Structures
```go
var people = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
];
for (var i = 0; i < len(people); i = i + 1) {
var person = people[i];
print(person["name"] + " is " + person["age"] + " years old");
}
```
### File Processing
```go
var lines = readLines("data.txt");
var processed = [];
for (var i = 0; i < len(lines); i = i + 1) {
var line = lines[i];
if (len(line) > 0) {
push(processed, "Processed: " + line);
}
}
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);
```
---
*For more examples, see the comprehensive test suite in `test_bob_language.bob`*

248
Reference/BUILD.md Normal file
View File

@ -0,0 +1,248 @@
# Bob Language - Build Guide
This guide describes how to build the Bob language interpreter using CMake + Ninja across different platforms.
## 🚀 Quick Start
### Prerequisites
- **CMake** 3.20 or later
- **Ninja** build system
- **C++17** compatible compiler
### Platform-Specific Setup
#### 🍎 macOS
```bash
# Install via Homebrew
brew install cmake ninja
# Or via MacPorts
sudo port install cmake ninja
```
#### 🐧 Linux
**Ubuntu/Debian:**
```bash
sudo apt update
sudo apt install cmake ninja-build build-essential
```
**RHEL/CentOS/Fedora:**
```bash
# RHEL/CentOS
sudo yum install cmake ninja-build gcc-c++
# Fedora
sudo dnf install cmake ninja-build gcc-c++
```
**Arch Linux:**
```bash
sudo pacman -S cmake ninja gcc
```
#### 🪟 Windows
**Option 1: Visual Studio (Recommended)**
- Install Visual Studio 2019+ with C++ workload
- Install CMake via Visual Studio Installer
- Install Ninja: `winget install Ninja-build.Ninja`
**Option 2: MSYS2/MinGW**
```bash
# In MSYS2 terminal
pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc
```
**Option 3: Chocolatey**
```powershell
# In Administrator PowerShell
choco install cmake ninja visualstudio2022buildtools
```
## 🔨 Build Commands
### Standard Build
**Release Build (Optimized):**
```bash
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
```
**Debug Build (Development):**
```bash
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Debug
ninja -C build
```
### Platform-Specific Examples
#### macOS/Linux
```bash
# Configure and build
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
# Run interpreter
./build/bin/bob
# Run test suite
./build/bin/bob test_bob_language.bob
# Run with custom script
./build/bin/bob your_script.bob
```
#### Windows (PowerShell/CMD)
```powershell
# Configure and build
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
# Run interpreter
.\build\bin\bob.exe
# Run test suite
.\build\bin\bob.exe test_bob_language.bob
# Run with custom script
.\build\bin\bob.exe your_script.bob
```
#### Windows (MSYS2/Git Bash)
```bash
# Same as macOS/Linux
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
./build/bin/bob.exe test_bob_language.bob
```
## 🧪 Testing
### Automated Testing
**All Platforms:**
```bash
# Build first
ninja -C build
# Run tests via CTest
cd build
ctest --output-on-failure
# Or run tests verbosely
ctest --verbose
```
**Windows PowerShell:**
```powershell
ninja -C build
cd build
ctest --output-on-failure
```
### Manual Testing
**Interactive Mode:**
```bash
# Unix-like systems
./build/bin/bob
# Windows
.\build\bin\bob.exe
```
**Script Execution:**
```bash
# Unix-like systems
./build/bin/bob examples/hello.bob
# Windows
.\build\bin\bob.exe examples\hello.bob
```
## ⚡ Performance
**CMake + Ninja** provides fast, cross-platform builds with excellent incremental compilation.
## 🔧 Advanced Configuration
### Custom Install Location
**Unix-like:**
```bash
cmake -G Ninja -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/opt/bob
ninja -C build install
```
**Windows:**
```powershell
cmake -G Ninja -B build `
-DCMAKE_BUILD_TYPE=Release `
-DCMAKE_INSTALL_PREFIX="C:\Program Files\Bob"
ninja -C build install
```
### Compiler Selection
**GCC:**
```bash
cmake -G Ninja -B build \
-DCMAKE_CXX_COMPILER=g++ \
-DCMAKE_BUILD_TYPE=Release
```
**Clang:**
```bash
cmake -G Ninja -B build \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_BUILD_TYPE=Release
```
**MSVC (Windows):**
```powershell
cmake -G Ninja -B build `
-DCMAKE_CXX_COMPILER=cl `
-DCMAKE_BUILD_TYPE=Release
```
## 🐛 Troubleshooting
### Common Issues
**CMake not found:**
- **macOS**: `brew install cmake`
- **Ubuntu**: `sudo apt install cmake`
- **Windows**: Install via Visual Studio or winget
**Ninja not found:**
- **macOS**: `brew install ninja`
- **Ubuntu**: `sudo apt install ninja-build`
- **Windows**: `winget install Ninja-build.Ninja`
**Compiler errors:**
- Ensure C++17 compiler is installed
- **Linux**: `sudo apt install build-essential`
- **Windows**: Install Visual Studio Build Tools
**Permission denied (Windows):**
- Run PowerShell as Administrator for system-wide installs
### Build Cache Issues
**Clean build:**
```bash
# Remove build directory
rm -rf build # Unix-like
rmdir /s build # Windows CMD
Remove-Item -Recurse build # PowerShell
# Reconfigure
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
```

312
Reference/ROADMAP.md Normal file
View File

@ -0,0 +1,312 @@
# Bob Language Development Roadmap
## Current Status
Bob is a mature, working programming language with a modern architecture and comprehensive feature set.
### ✅ **Core Language Features (Complete)**
#### **Data Types & Variables**
- **Numbers**: Integers, floats, automatic conversion
- **Strings**: Literals, concatenation, multiplication, escape sequences
- **Booleans**: `true`, `false`
- **None**: Null value representation
- **Arrays**: Dynamic arrays with indexing, assignment, and built-in functions
- **Dictionaries**: Hash maps with string keys and mixed-type values
- **Functions**: First-class functions as values
- **Variables**: Declaration, assignment, scoping
- **Assignment System**: Dual system (statements + expressions for loops only)
#### **Operators (Complete)**
- **Arithmetic**: `+`, `-`, `*`, `/`, `%`, unary `-`
- **Comparison**: `==`, `!=`, `>`, `<`, `>=`, `<=`
- **Logical**: `&&`, `||`, `!` (with short-circuit evaluation)
- **Bitwise**: `&`, `|`, `^`, `<<`, `>>`, `~`
- **Compound Assignment**: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
- **Ternary**: `condition ? valueIfTrue : valueIfFalse`
- **Increment/Decrement**: `++`, `--` (for array elements)
#### **Control Flow (Complete)**
- **If Statements**: `if`, `else`, `else if` chains
- **While Loops**: Basic, nested, complex conditions
- **Do-While Loops**: Basic, nested, break/continue support
- **For Loops**: All clause variations, nested loops
- **Break/Continue**: Full support in all loop types
#### **Functions (Complete)**
- **Function Declaration**: `func name(params) { body }`
- **Parameters**: Any number of parameters (tested up to 100)
- **Return Values**: Explicit and implicit returns
- **Closures**: Lexical scoping with variable capture
- **First-Class Functions**: Functions as values, parameters, return values
- **Anonymous Functions**: `func(params) { body }`
- **Nested Functions**: Functions defined inside other functions
- **Recursion**: Full support including deep recursion
- **Tail Call Optimization**: Trampoline-based optimization preventing stack overflow
#### **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, 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)**
- **I/O Functions**: `print()`, `printRaw()`, `input()`
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
- **Testing**: `assert()` with custom error messages
- **Timing**: `time()` (microsecond precision), `sleep()`
- **Utility**: `random()` (properly seeded), `eval()`, `exit()`
- **Data Structure**: `len()`, `push()`, `pop()`, `keys()`, `values()`, `has()`
- **File I/O**: `readFile()`, `writeFile()`, `readLines()`, `fileExists()`
#### **Advanced Features (Complete)**
- **String Operations**: Bidirectional string + number concatenation
- **Number Formatting**: Smart significant digits handling
- **Memory Management**: Automatic cleanup with reference counting
- **Error Handling**: Comprehensive error reporting with context
- **Testing Framework**: Built-in assert function with 70+ comprehensive tests
- **Operator Precedence**: Full precedence hierarchy implementation
- **Variable Shadowing**: Proper lexical scoping rules
- **Interactive Mode**: Full REPL with error handling
- **Cross-Type Comparisons**: Smart equality for all types
- **Copy Semantics**: Value vs reference copying for different types
### ✅ **Architecture & Infrastructure (Complete)**
#### **Modern Build System**
- **CMake**: Cross-platform build configuration
- **Ninja**: High-speed build system (3.1x faster than Make)
- **CTest**: Integrated testing framework
- **Cross-Platform**: Windows, macOS, Linux support
- **Performance**: Optimized build times and incremental compilation
#### **Clean Architecture**
- **Modular Design**: Separated parsing, runtime, stdlib, and CLI
- **Tier Separation**: Clear boundaries between language components
- **Header Organization**: Organized by functional area
- **Source Structure**: `src/headers/` and `src/sources/` organization
#### **Refactored Interpreter**
- **Evaluator**: Expression evaluation (visitor pattern)
- **Executor**: Statement execution and control flow
- **RuntimeDiagnostics**: Utility functions and type checking
- **Memory Management**: Smart pointer usage throughout
- **Error System**: Centralized error reporting
### **Current Architecture Status**
```
Bob Language
├── Parsing Layer
│ ├── Lexer (tokenization)
│ ├── Parser (AST generation)
│ └── ErrorReporter (syntax errors)
├── Runtime Layer
│ ├── Evaluator (expression visitor)
│ ├── Executor (statement visitor)
│ ├── Interpreter (orchestration)
│ ├── Environment (variable scoping)
│ ├── Value (type system)
│ └── RuntimeDiagnostics (utilities)
├── Standard Library
│ └── BobStdLib (built-in functions)
└── CLI Interface
└── Bob (command-line interface)
```
## **Future Development Phases**
### **Phase 1: Advanced Language Features (Medium Priority)**
#### **Exception Handling System**
```bob
try {
var result = 10 / 0;
} catch (error) {
print("Error: " + error.message);
} finally {
print("Cleanup");
}
```
**Implementation Plan:**
- Add `try`/`catch`/`finally` syntax to parser
- Implement exception objects with stack traces
- Add `throw` statement for custom exceptions
- Integrate with existing error system
#### **Pattern Matching**
```bob
match value {
case 0: "zero"
case 1 | 2: "small"
case x if x > 10: "large"
default: "other"
}
```
**Implementation Plan:**
- Add `match`/`case` syntax
- Implement pattern matching logic
- Support guards with `if` conditions
- Add destructuring for arrays/dictionaries
### **Phase 2: Object System (Lower Priority)**
#### **Simple Objects**
```bob
var person = {
name: "Alice",
age: 30,
greet: func() {
return "Hello, I'm " + this.name;
}
};
```
**Implementation Plan:**
- Add object literal syntax
- Implement `this` binding
- Support method calls
- ✅ Add property access/assignment (completed - dot notation for dictionaries and arrays)
#### **Classes (Optional)**
```bob
class Person {
init(name, age) {
this.name = name;
this.age = age;
}
greet() {
return "Hello, I'm " + this.name;
}
}
```
**Implementation Plan:**
- Add `class` keyword and syntax
- Implement constructors with `init()`
- Support inheritance with `extends`
- Add method definitions
### **Phase 3: Module System (Lower Priority)**
#### **Simple Modules**
```bob
// math.bob
func sqrt(x) { return x ** 0.5; }
func max(a, b) { return a > b ? a : b; }
// main.bob
import "math.bob" as math;
var result = math.sqrt(16);
```
**Implementation Plan:**
- Add `import` statement syntax
- Implement module loading from files
- Support namespace aliases
- Create standard library modules
### **Phase 4: Language Enhancements (Optional)**
#### **Enhanced Standard Library**
```bob
// Additional string functions
var parts = "a,b,c".split(",");
var joined = ["a", "b", "c"].join("-");
var upper = "hello".toUpper();
// Math library
var result = Math.sqrt(16);
var max = Math.max(5, 10, 3);
```
#### **Async/Await (Advanced)**
```bob
async func fetchData() {
var response = await http.get("api.com/data");
return response.json();
}
```
## **Implementation Guidelines**
### **For Each New Feature:**
1. **Design**: Plan syntax and semantics carefully
2. **Lexer**: Add new tokens if needed
3. **Parser**: Add new expression/statement types
4. **AST**: Define new node types in Expression.h/Statement.h
5. **Evaluator/Executor**: Implement evaluation logic
6. **Testing**: Write tests for the new feature
7. **Documentation**: Update language reference
### **Development Approach:**
- **Testing**: Write tests for new features
- **Error Messages**: Make errors helpful and clear
- **Memory**: Use smart pointers to avoid leaks
- **Performance**: Don't make things unnecessarily slow
- **Code Style**: Keep it readable and maintainable
- **Portability**: Make sure it works on different platforms
## **Success Metrics**
### **What's Done ✅**
- [x] Core language syntax and semantics
- [x] All operators and expressions
- [x] Control flow (if, while, for, do-while)
- [x] Functions, closures, and tail call optimization
- [x] Arrays and dictionaries
- [x] Standard library (25+ built-in functions)
- [x] File I/O operations
- [x] Interactive REPL
- [x] Test suite with 70+ tests
- [x] Error handling and reporting
- [x] Memory management
- [x] CMake + Ninja build system
- [x] Modular architecture
- [x] Cross-platform support
- [x] Various optimizations
### **Might Add Later 📋**
- [ ] Exception handling (try/catch)
- [ ] Pattern matching
- [ ] Simple objects
- [ ] Module/import system
- [ ] More built-in functions
- [ ] Debugging tools
## **Resources**
- **[Language Reference](BOB_LANGUAGE_REFERENCE.md)** - Language documentation
- **[Build Guide](BUILD.md)** - How to build Bob
- **[Test Suite](../test_bob_language.bob)** - 70+ tests
- **[Crafting Interpreters](https://craftinginterpreters.com/)** - Helpful book for language implementation
## **Recent Work**
### **Architecture Cleanup (2025)**
- Split the interpreter into separate components (Evaluator/Executor/RuntimeDiagnostics)
- Switched to CMake + Ninja build system (3x faster builds)
- Reorganized code into cleaner modules
- Added Windows/macOS/Linux build support
### **Feature Completion**
- Added file I/O and type conversion functions
- Implemented all the operators I wanted (bitwise, compound assignment, etc.)
- Got arrays and dictionaries working properly
- Added tail call optimization and closures
### **Testing & Polish**
- Wrote 70+ tests covering pretty much everything
- Improved error messages to be more helpful
- Fixed memory leaks using smart pointers
- Various performance improvements
---
Bob works well for what I wanted - a programming language with the features and syntax I prefer.
*Last updated: January 2025*

View File

@ -1,30 +0,0 @@
import time
# Test the time function
print("Testing time function:")
time1 = time.time()
print(f"Time 1: {time1}")
time2 = time.time()
print(f"Time 2: {time2}")
time3 = time.time()
print(f"Time 3: {time3}")
diff1 = time2 - time1
diff2 = time3 - time2
print(f"Difference 1-2: {diff1} seconds")
print(f"Difference 2-3: {diff2} seconds")
# Test with some work in between
start = time.time()
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
end = time.time()
duration = end - start
print(f"Work duration: {duration} seconds")
print(f"Sum: {sum_val}")
print("Time function analysis complete!")

View File

@ -0,0 +1,10 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

View File

@ -0,0 +1 @@
bob-language-0.2.0.vsix

View File

@ -0,0 +1,97 @@
# Bob Language Extension Installation Guide
## Quick Installation
### Option 1: Automatic Installation (Recommended)
```bash
cd bob-language-extension
./install.sh
```
### Option 2: Manual Installation
1. Copy the extension files to your VS Code extensions directory:
- **macOS**: `~/.vscode/extensions/bob-language-0.1.0/`
- **Linux**: `~/.vscode/extensions/bob-language-0.1.0/`
- **Windows**: `%APPDATA%\Code\User\extensions\bob-language-0.1.0\`
2. Restart VS Code/Cursor
### Option 3: VSIX Package
```bash
cd bob-language-extension
./package-vsix.sh
```
Then install the generated `.vsix` file in VS Code/Cursor.
## Features Included
### ✅ Syntax Highlighting
- Keywords: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, `var`, `func`
- Built-in functions: `print`, `assert`, `input`, `type`, `toString`, `toNumber`, `time`
- Data types: numbers, strings, booleans, `none`
- Operators: arithmetic, comparison, logical, bitwise, compound assignment
### ✅ Code Snippets
Type these prefixes and press `Tab`:
- `func` - Function definition
- `if` - If statement
- `while` - While loop
- `for` - For loop
- `var` - Variable declaration
- `print` - Print statement
- `assert` - Assert statement
- `anon` - Anonymous function
- `test` - Test function
### ✅ Language Features
- Auto-closing brackets and quotes
- Smart indentation
- Comment support (`//` and `/* */`)
- Code folding
- Hover information for built-in functions
- IntelliSense completion
### ✅ Color Theme
- "Bob Dark" theme included
- Optimized colors for Bob syntax
## Testing the Extension
1. Open VS Code/Cursor
2. Open the `example.bob` file in the extension directory
3. You should see syntax highlighting, code completion, and snippets working
## File Association
Files with `.bob` extension will automatically be recognized as Bob language files.
## Troubleshooting
### Extension not working?
1. Check that the extension is installed in the correct directory
2. Restart VS Code/Cursor completely
3. Check the Extensions panel (Ctrl+Shift+X) to see if the extension is listed
### Syntax highlighting not working?
1. Make sure your file has a `.bob` extension
2. Check the language mode in the bottom-right corner of VS Code
3. Manually select "Bob" as the language mode if needed
### Snippets not working?
1. Type the snippet prefix (e.g., `func`)
2. Press `Ctrl+Space` to trigger suggestions
3. Select the snippet and press `Tab`
## Development
To modify the extension:
1. Edit the files in the extension directory
2. Run `npm run compile` to rebuild TypeScript
3. Reload VS Code/Cursor to see changes
## Support
For issues or questions:
1. Check the README.md file
2. Look at the example.bob file for syntax examples
3. Review the TextMate grammar in `syntaxes/bob.tmLanguage.json`

View File

@ -0,0 +1,127 @@
# Bob Language Extension for VS Code
This extension provides syntax highlighting and language support for the Bob programming language in Visual Studio Code and Cursor.
## Features
- **Syntax Highlighting**: Full syntax highlighting for Bob language constructs
- **Code Snippets**: Useful code snippets for common Bob patterns
- **Auto-closing Brackets**: Automatic bracket and quote pairing
- **Indentation**: Smart indentation for Bob code blocks
- **Comments**: Support for line and block comments
- **Folding**: Code folding support with region markers
## Supported Syntax
### Keywords
- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return`
- Variable declaration: `var`
- Function declaration: `func`
- Logical operators: `and`, `or`, `not`
### Built-in Functions
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `time()`
- `sleep()`, `printRaw()`, `len()`, `push()`, `pop()`, `random()`, `eval()`
### Data Types
- Numbers (integers, floats, binary `0b1010`, hex `0xFF`)
- Strings (single and double quoted)
- Booleans (`true`, `false`)
- None value (`none`)
- Arrays (`[1, 2, 3]`)
### Operators
- Arithmetic: `+`, `-`, `*`, `/`, `%`
- Comparison: `==`, `!=`, `<`, `>`, `<=`, `>=`
- Logical: `&&`, `||`, `!`
- Bitwise: `&`, `|`, `^`, `<<`, `>>`, `~`
- Compound assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
- Ternary: `condition ? valueIfTrue : valueIfFalse`
- String multiplication: `"hello" * 3`
## Installation
### From Source
1. Clone this repository
2. Run `npm install` to install dependencies
3. Run `npm run compile` to build the extension
4. Press `F5` in VS Code to launch the extension in a new window
### Manual Installation
1. Copy the extension files to your VS Code extensions directory
2. Restart VS Code
3. Open a `.bob` file to see syntax highlighting
## Usage
### Code Snippets
Type the following prefixes and press `Tab` to insert code snippets:
- `func` - Function definition
- `if` - If statement
- `ifelse` - If-else statement
- `while` - While loop
- `for` - For loop
- `var` - Variable declaration
- `print` - Print statement
- `assert` - Assert statement
- `anon` - Anonymous function
- `return` - Return statement
- `break` - Break statement
- `continue` - Continue statement
- `comment` - Comment block
- `test` - Test function
- `array` - Array declaration
- `arrayaccess` - Array access
- `arrayassign` - Array assignment
- `len` - Array length
- `push` - Array push
- `pop` - Array pop
- `random` - Random number
- `sleep` - Sleep function
- `printraw` - Print raw
- `eval` - Eval function
### File Association
Files with the `.bob` extension will automatically be recognized as Bob language files.
## Example
```go
// This is a comment
var message = "Hello, Bob!";
print(message);
// Array operations
var numbers = [1, 2, 3, 4, 5];
print("Array length: " + len(numbers));
print("First element: " + numbers[0]);
// Function with ternary operator
func factorial(n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
var result = factorial(5);
var status = result > 100 ? "large" : "small";
assert(result == 120, "Factorial calculation failed");
// String multiplication
var repeated = "hello" * 3; // "hellohellohello"
```
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This extension is licensed under the MIT License.
## Links
- [Bob Language Repository](https://github.com/bob-lang/bob)
- [VS Code Extension API](https://code.visualstudio.com/api)

Binary file not shown.

View File

@ -0,0 +1,44 @@
#!/bin/bash
# Create a simple VSIX package without npm
echo "Creating Bob Language Extension VSIX package..."
# Read version from package.json
VERSION=$(grep '"version"' package.json | sed 's/.*"version": "\([^"]*\)".*/\1/')
echo "Building version: $VERSION"
# Create a temporary directory for the package
TEMP_DIR=$(mktemp -d)
PACKAGE_DIR="$TEMP_DIR/bob-language-$VERSION"
# Create the extension directory structure
mkdir -p "$TEMP_DIR/extension"
# Copy all extension files to the extension directory
cp package.json "$TEMP_DIR/extension/"
cp language-configuration.json "$TEMP_DIR/extension/"
cp -r syntaxes "$TEMP_DIR/extension/"
cp -r snippets "$TEMP_DIR/extension/"
cp -r themes "$TEMP_DIR/extension/"
cp README.md "$TEMP_DIR/extension/"
# Create the VSIX file (simple zip with .vsix extension)
cd "$TEMP_DIR"
zip -r "bob-language-$VERSION.vsix" extension/
# Move to the extension directory
mv "bob-language-$VERSION.vsix" /Users/bobbylucero/Developer/Bob/bob-language-extension/
# Clean up
rm -rf "$TEMP_DIR"
echo "VSIX package created: bob-language-$VERSION.vsix"
echo ""
echo "To install in Cursor:"
echo "1. Open Cursor"
echo "2. Go to Extensions (Cmd+Shift+X)"
echo "3. Click the '...' menu and select 'Install from VSIX...'"
echo "4. Select the bob-language-$VERSION.vsix file"
echo ""
echo "Or simply restart Cursor - the extension should already be working!"

View File

@ -0,0 +1,111 @@
// Bob Language Example - Demonstrates syntax highlighting
// Variable declarations
var message = "Hello, Bob!";
var number = 42;
var pi = 3.14159;
var isActive = true;
var empty = none;
// Binary and hex numbers
var binaryNum = 0b1010; // 10 in decimal
var hexNum = 0xFF; // 255 in decimal
// Print statements
print(message);
print("Number: " + toString(number));
print("Pi: " + toString(pi));
// Array operations
var numbers = [1, 2, 3, 4, 5];
var fruits = ["apple", "banana", "cherry"];
print("Array length: " + len(numbers));
print("First element: " + numbers[0]);
numbers[2] = 99; // Array assignment
push(numbers, 6); // Add element
var lastElement = pop(numbers); // Remove and get last element
// Function definition
func factorial(n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
// While loop with break
var counter = 0;
while (counter < 10) {
if (counter == 5) {
break;
}
counter = counter + 1;
}
// For loop with continue
var sum = 0;
for (var i = 0; i < 10; i = i + 1) {
if (i % 2 == 0) {
continue;
}
sum = sum + i;
}
// Do-while loop
var doCounter = 0;
do {
doCounter = doCounter + 1;
} while (doCounter < 5);
// Anonymous function
var double = func(x) {
return x * 2;
};
// Ternary operator
var max = 10 > 5 ? 10 : 5;
var status = age >= 18 ? "adult" : "minor";
// String multiplication
var repeated = "hello" * 3; // "hellohellohello"
var numRepeated = 3 * "hi"; // "hihihi"
// Logical operators
var a = true && false;
var b = true || false;
var c = !false;
// Bitwise operators
var d = 5 & 3;
var e = 5 | 3;
var f = 5 ^ 3;
var g = 5 << 1;
var h = 5 >> 1;
// Compound assignment
var value = 10;
value += 5;
value *= 2;
value -= 3;
// New built-in functions
var randomValue = random();
sleep(100); // Sleep for 100ms
printRaw("No newline here");
eval("print('Dynamic code execution!');");
// Assertions
assert(factorial(5) == 120, "Factorial calculation failed");
assert(sum == 25, "Sum calculation failed");
// Type checking
var typeOfMessage = type(message);
var typeOfNumber = type(number);
var typeOfArray = type(numbers);
// Time function
var currentTime = time();
print("All tests passed!");

View File

@ -0,0 +1,66 @@
#!/bin/bash
# Bob Language Extension Installer for VS Code/Cursor
echo "Installing Bob Language Extension..."
# Get VS Code extensions directory
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions"
CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions"
CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions"
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
# Windows
VSCODE_EXTENSIONS_DIR="$APPDATA/Code/User/extensions"
CURSOR_EXTENSIONS_DIR="$APPDATA/Cursor/User/extensions"
else
echo "Unsupported operating system: $OSTYPE"
exit 1
fi
# Create extension directory
EXTENSION_NAME="bob-language-0.1.0"
EXTENSION_DIR="$VSCODE_EXTENSIONS_DIR/$EXTENSION_NAME"
echo "Installing to: $EXTENSION_DIR"
# Create directories
mkdir -p "$EXTENSION_DIR"
# Copy extension files
cp -r package.json "$EXTENSION_DIR/"
cp -r language-configuration.json "$EXTENSION_DIR/"
cp -r syntaxes "$EXTENSION_DIR/"
cp -r snippets "$EXTENSION_DIR/"
cp -r README.md "$EXTENSION_DIR/"
# Compile TypeScript if available
if command -v npm &> /dev/null; then
echo "Compiling TypeScript..."
npm install
npm run compile
cp -r out "$EXTENSION_DIR/"
else
echo "npm not found, skipping TypeScript compilation"
fi
echo "Bob Language Extension installed successfully!"
echo ""
echo "To use the extension:"
echo "1. Restart VS Code/Cursor"
echo "2. Open a .bob file"
echo "3. Enjoy syntax highlighting and code snippets!"
echo ""
echo "Code snippets available:"
echo "- func: Function definition"
echo "- if: If statement"
echo "- while: While loop"
echo "- for: For loop"
echo "- var: Variable declaration"
echo "- print: Print statement"
echo "- assert: Assert statement"
echo "- And many more!"

View File

@ -0,0 +1,35 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"indentationRules": {
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for)\\b.*$",
"decreaseIndentPattern": "^\\s*[})]"
},
"folding": {
"markers": {
"start": "^\\s*//\\s*#?region\\b",
"end": "^\\s*//\\s*#?endregion\\b"
}
}
}

View File

@ -0,0 +1,27 @@
#!/bin/bash
# Package Bob Language Extension as VSIX
echo "Packaging Bob Language Extension..."
# Check if vsce is installed
if ! command -v vsce &> /dev/null; then
echo "Installing vsce..."
npm install -g @vscode/vsce
fi
# Install dependencies
npm install
# Compile TypeScript
npm run compile
# Package the extension
vsce package
echo "Extension packaged successfully!"
echo "You can now install the .vsix file in VS Code/Cursor:"
echo "1. Open VS Code/Cursor"
echo "2. Go to Extensions (Ctrl+Shift+X)"
echo "3. Click the '...' menu and select 'Install from VSIX...'"
echo "4. Select the generated .vsix file"

View File

@ -0,0 +1,74 @@
{
"name": "bob-language",
"displayName": "Bob Language",
"description": "Syntax highlighting and language support for the Bob programming language - featuring arrays, dictionaries, auto-truncating float indices, increment/decrement operators, cross-type comparisons, and comprehensive built-in functions",
"version": "0.4.0",
"engines": {
"vscode": "^1.60.0"
},
"categories": [
"Programming Languages"
],
"keywords": [
"bob",
"programming",
"language",
"syntax",
"highlighting"
],
"publisher": "bob-lang",
"repository": {
"type": "git",
"url": "https://github.com/bob-lang/bob-vscode-extension"
},
"license": "MIT",
"main": "./out/extension.js",
"activationEvents": [
"onLanguage:bob"
],
"contributes": {
"languages": [
{
"id": "bob",
"aliases": [
"Bob",
"bob"
],
"extensions": [
".bob"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "bob",
"scopeName": "source.bob",
"path": "./syntaxes/bob.tmLanguage.json"
}
],
"snippets": [
{
"language": "bob",
"path": "./snippets/bob.json"
}
],
"themes": [
{
"label": "Bob Dark",
"uiTheme": "vs-dark",
"path": "./themes/bob-dark.json"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./"
},
"devDependencies": {
"@types/vscode": "^1.60.0",
"@types/node": "^16.0.0",
"typescript": "^4.5.0"
}
}

View File

@ -0,0 +1,29 @@
#!/bin/bash
echo "Attempting to reload Bob language extension..."
# Try to reload the extension using VS Code CLI if available
if command -v code &> /dev/null; then
echo "Found VS Code CLI, attempting to reload window..."
code --command "workbench.action.reloadWindow"
echo "Reload command sent to VS Code/Cursor"
elif command -v cursor &> /dev/null; then
echo "Found Cursor CLI, attempting to reload window..."
cursor --command "workbench.action.reloadWindow"
echo "Reload command sent to Cursor"
else
echo "VS Code/Cursor CLI not found in PATH"
echo ""
echo "Manual reload required:"
echo "1. Open Cursor"
echo "2. Press Cmd+Shift+P (or Ctrl+Shift+P on Windows/Linux)"
echo "3. Type 'Developer: Reload Window' and press Enter"
echo ""
echo "Or alternatively:"
echo "1. Press Cmd+Shift+X to open Extensions"
echo "2. Find 'Bob Language' extension"
echo "3. Click the reload button (circular arrow icon)"
fi
echo ""
echo "Extension should now be reloaded with updated syntax highlighting!"

View File

@ -0,0 +1,462 @@
{
"Function Definition": {
"prefix": "func",
"body": [
"func ${1:functionName}(${2:parameters}) {",
"\t$0",
"}"
],
"description": "Create a new function"
},
"If Statement": {
"prefix": "if",
"body": [
"if (${1:condition}) {",
"\t$0",
"}"
],
"description": "Create an if statement"
},
"If-Else Statement": {
"prefix": "ifelse",
"body": [
"if (${1:condition}) {",
"\t$2",
"} else {",
"\t$0",
"}"
],
"description": "Create an if-else statement"
},
"While Loop": {
"prefix": "while",
"body": [
"while (${1:condition}) {",
"\t$0",
"}"
],
"description": "Create a while loop"
},
"For Loop": {
"prefix": "for",
"body": [
"for (${1:initialization}; ${2:condition}; ${3:increment}) {",
"\t$0",
"}"
],
"description": "Create a for loop"
},
"Variable Declaration": {
"prefix": "var",
"body": [
"var ${1:variableName} = ${2:value};"
],
"description": "Declare a variable"
},
"Print Statement": {
"prefix": "print",
"body": [
"print(${1:expression});"
],
"description": "Print a value"
},
"Assert Statement": {
"prefix": "assert",
"body": [
"assert(${1:condition}, \"${2:message}\");"
],
"description": "Create an assertion"
},
"Anonymous Function": {
"prefix": "anon",
"body": [
"func(${1:parameters}) {",
"\t$0",
"}"
],
"description": "Create an anonymous function"
},
"Return Statement": {
"prefix": "return",
"body": [
"return ${1:value};"
],
"description": "Return a value from function"
},
"Break Statement": {
"prefix": "break",
"body": [
"break;"
],
"description": "Break out of loop"
},
"Continue Statement": {
"prefix": "continue",
"body": [
"continue;"
],
"description": "Continue to next iteration"
},
"Comment Block": {
"prefix": "comment",
"body": [
"/*",
" * ${1:comment}",
" */"
],
"description": "Create a comment block"
},
"Test Function": {
"prefix": "test",
"body": [
"func test${1:TestName}() {",
"\tvar result = ${2:testExpression};",
"\tassert(result == ${3:expectedValue}, \"${4:test message}\");",
"\tprint(\"${1:TestName}: PASS\");",
"}"
],
"description": "Create a test function"
},
"Higher-Order Function": {
"prefix": "hof",
"body": [
"func ${1:functionName}(${2:callback}) {",
"\treturn func(${3:params}) {",
"\t\t$0",
"\t};",
"}"
],
"description": "Create a higher-order function"
},
"Closure": {
"prefix": "closure",
"body": [
"func ${1:outerFunction}(${2:param}) {",
"\tvar ${3:capturedVar} = ${4:value};",
"\treturn func(${5:innerParam}) {",
"\t\treturn ${3:capturedVar} + ${5:innerParam};",
"\t};",
"}"
],
"description": "Create a closure"
},
"Counter Pattern": {
"prefix": "counter",
"body": [
"func createCounter() {",
"\tvar count = 0;",
"\treturn func() {",
"\t\tcount = count + 1;",
"\t\treturn count;",
"\t};",
"}"
],
"description": "Create a counter function"
},
"Loop with Break": {
"prefix": "loopbreak",
"body": [
"while (${1:condition}) {",
"\tif (${2:breakCondition}) {",
"\t\tbreak;",
"\t}",
"\t$0",
"}"
],
"description": "Create a loop with break condition"
},
"Loop with Continue": {
"prefix": "loopcontinue",
"body": [
"for (${1:initialization}; ${2:condition}; ${3:increment}) {",
"\tif (${4:continueCondition}) {",
"\t\tcontinue;",
"\t}",
"\t$0",
"}"
],
"description": "Create a loop with continue condition"
},
"Nested Loop": {
"prefix": "nestedloop",
"body": [
"for (var i = 0; i < ${1:outerLimit}; i = i + 1) {",
"\tfor (var j = 0; j < ${2:innerLimit}; j = j + 1) {",
"\t\t$0",
"\t}",
"}"
],
"description": "Create nested loops"
},
"Function with Early Return": {
"prefix": "earlyreturn",
"body": [
"func ${1:functionName}(${2:param}) {",
"\tif (${3:condition}) {",
"\t\treturn ${4:earlyValue};",
"\t}",
"\t$0",
"\treturn ${5:defaultValue};",
"}"
],
"description": "Create a function with early return"
},
"String Concatenation": {
"prefix": "concat",
"body": [
"var result = \"${1:first}\" + \"${2:second}\";"
],
"description": "Concatenate strings"
},
"Number Operations": {
"prefix": "math",
"body": [
"var result = ${1:expression};"
],
"description": "Mathematical expression"
},
"Boolean Logic": {
"prefix": "bool",
"body": [
"var result = ${1:condition} == ${2:value};"
],
"description": "Boolean comparison"
},
"Type Check": {
"prefix": "type",
"body": [
"var typeResult = type(${1:expression});"
],
"description": "Check type of expression"
},
"Array Declaration": {
"prefix": "array",
"body": [
"var ${1:arrayName} = [${2:element1}, ${3:element2}];"
],
"description": "Declare an array"
},
"Array Access": {
"prefix": "arrayaccess",
"body": [
"var element = ${1:arrayName}[${2:index}];"
],
"description": "Access array element"
},
"Array Assignment": {
"prefix": "arrayassign",
"body": [
"${1:arrayName}[${2:index}] = ${3:value};"
],
"description": "Assign value to array element"
},
"Array Length": {
"prefix": "len",
"body": [
"var length = len(${1:arrayName});"
],
"description": "Get array length"
},
"Array Push": {
"prefix": "push",
"body": [
"push(${1:arrayName}, ${2:value});"
],
"description": "Add element to array"
},
"Array Pop": {
"prefix": "pop",
"body": [
"var element = pop(${1:arrayName});"
],
"description": "Remove and return last element"
},
"Random Number": {
"prefix": "random",
"body": [
"var randomValue = random();"
],
"description": "Generate random number"
},
"Sleep": {
"prefix": "sleep",
"body": [
"sleep(${1:milliseconds});"
],
"description": "Sleep for specified milliseconds"
},
"Print Raw": {
"prefix": "printraw",
"body": [
"printRaw(${1:expression});"
],
"description": "Print without newline"
},
"Eval": {
"prefix": "eval",
"body": [
"eval(\"${1:code}\");"
],
"description": "Evaluate dynamic code"
},
"ToString": {
"prefix": "tostring",
"body": [
"var stringResult = toString(${1:expression});"
],
"description": "Convert to string"
},
"Test Suite Header": {
"prefix": "testsuite",
"body": [
"// ========================================",
"// ${1:TEST SUITE NAME}",
"// ========================================",
"// ${2:Description}",
"",
"print(\"${1:TEST SUITE NAME}\");",
"print(\"Running tests...\");",
"",
"$0",
"",
"print(\"All tests passed!\");"
],
"description": "Create a test suite header"
},
"Test Section": {
"prefix": "testsection",
"body": [
"// ========================================",
"// TEST ${1:NUMBER}: ${2:TEST NAME}",
"// ========================================",
"print(\"\\n--- Test ${1:NUMBER}: ${2:TEST NAME} ---\");",
"",
"$0",
"",
"print(\"${2:TEST NAME}: PASS\");"
],
"description": "Create a test section"
},
"Debug Print": {
"prefix": "debug",
"body": [
"print(\"DEBUG: ${1:variable} = \" + toString(${1:variable}));"
],
"description": "Debug print statement"
},
"Error Message": {
"prefix": "error",
"body": [
"print(\"ERROR: ${1:error message}\");"
],
"description": "Error message print"
},
"Success Message": {
"prefix": "success",
"body": [
"print(\"SUCCESS: ${1:success message}\");"
],
"description": "Success message print"
},
"ToInt": {
"prefix": "toint",
"body": [
"var intValue = toInt(${1:floatValue});"
],
"description": "Convert float to integer"
},
"Compound Assignment": {
"prefix": "compound",
"body": [
"${1:variable} += ${2:value};"
],
"description": "Compound assignment operator"
},
"Increment": {
"prefix": "inc",
"body": [
"${1:variable}++;"
],
"description": "Increment variable"
},
"Decrement": {
"prefix": "dec",
"body": [
"${1:variable}--;"
],
"description": "Decrement variable"
},
"Array Increment": {
"prefix": "arrayinc",
"body": [
"${1:arrayName}[${2:index}]++;"
],
"description": "Increment array element"
},
"Array Decrement": {
"prefix": "arraydec",
"body": [
"${1:arrayName}[${2:index}]--;"
],
"description": "Decrement array element"
},
"Cross-Type Comparison": {
"prefix": "crosscomp",
"body": [
"var result = ${1:value1} == ${2:value2}; // Works with any types"
],
"description": "Cross-type comparison"
},
"Float Array Index": {
"prefix": "floatindex",
"body": [
"var element = ${1:arrayName}[${2:floatIndex}]; // Auto-truncates to int"
],
"description": "Array access with float index (auto-truncates)"
},
"Dictionary Literal": {
"prefix": "dict",
"body": [
"var ${1:dictName} = {",
"\t\"${2:key1}\": ${3:value1},",
"\t\"${4:key2}\": ${5:value2}",
"};"
],
"description": "Create a dictionary literal"
},
"Dictionary Access": {
"prefix": "dictaccess",
"body": [
"var value = ${1:dictName}[\"${2:key}\"];"
],
"description": "Access dictionary value"
},
"Dictionary Assignment": {
"prefix": "dictassign",
"body": [
"${1:dictName}[\"${2:key}\"] = ${3:value};"
],
"description": "Assign value to dictionary key"
},
"Dictionary Keys": {
"prefix": "keys",
"body": [
"var keys = keys(${1:dictionary});"
],
"description": "Get all keys from dictionary"
},
"Dictionary Values": {
"prefix": "values",
"body": [
"var values = values(${1:dictionary});"
],
"description": "Get all values from dictionary"
},
"Dictionary Has": {
"prefix": "has",
"body": [
"var exists = has(${1:dictionary}, \"${2:key}\");"
],
"description": "Check if dictionary has key"
}
}

View File

@ -0,0 +1,104 @@
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
console.log('Bob language extension is now active!');
// Register language configuration
const bobLanguageConfig = vscode.languages.setLanguageConfiguration('bob', {
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"', notIn: ['string'] },
{ open: "'", close: "'", notIn: ['string'] }
],
surroundingPairs: [
['{', '}'],
['[', ']'],
['(', ')'],
['"', '"'],
["'", "'"]
],
indentationRules: {
increaseIndentPattern: /\{[^}]*$|\b(func|if|else|while|for)\b.*$/,
decreaseIndentPattern: /^\s*[})]/
}
});
context.subscriptions.push(bobLanguageConfig);
// Register hover provider for built-in functions
const hoverProvider = vscode.languages.registerHoverProvider('bob', {
provideHover(document, position, token) {
const range = document.getWordRangeAtPosition(position);
const word = document.getText(range);
const builtinFunctions = {
'print': 'Prints a value to the console',
'assert': 'Asserts a condition and throws an error if false',
'input': 'Gets user input from the console',
'type': 'Returns the type of a value',
'toString': 'Converts a value to a string',
'toNumber': 'Converts a value to a number',
'time': 'Returns the current time in microseconds'
};
if (builtinFunctions[word]) {
return new vscode.Hover(`**${word}()** - ${builtinFunctions[word]}`);
}
return null;
}
});
context.subscriptions.push(hoverProvider);
// Register completion provider
const completionProvider = vscode.languages.registerCompletionItemProvider('bob', {
provideCompletionItems(document, position, token, context) {
const completions = [];
// Keywords
const keywords = ['if', 'else', 'while', 'for', 'break', 'continue', 'return', 'var', 'func'];
keywords.forEach(keyword => {
const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword);
item.detail = 'Bob keyword';
completions.push(item);
});
// Built-in functions
const builtins = ['print', 'assert', 'input', 'type', 'toString', 'toNumber', 'time'];
builtins.forEach(func => {
const item = new vscode.CompletionItem(func, vscode.CompletionItemKind.Function);
item.detail = 'Built-in function';
item.insertText = func + '()';
completions.push(item);
});
// Constants
const constants = ['true', 'false', 'none'];
constants.forEach(constant => {
const item = new vscode.CompletionItem(constant, vscode.CompletionItemKind.Constant);
item.detail = 'Bob constant';
completions.push(item);
});
return completions;
}
}, '.');
context.subscriptions.push(completionProvider);
}
export function deactivate() {
console.log('Bob language extension is now deactivated!');
}

View File

@ -0,0 +1,273 @@
{
"name": "Bob",
"scopeName": "source.bob",
"patterns": [
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#numbers"
},
{
"include": "#arrays"
},
{
"include": "#dictionaries"
},
{
"include": "#keywords"
},
{
"include": "#functions"
},
{
"include": "#variables"
},
{
"include": "#operators"
}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.bob",
"match": "//.*$"
},
{
"name": "comment.block.bob",
"begin": "/\\*",
"end": "\\*/"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.bob",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.bob",
"match": "\\\\[nt\"\\\\e]"
}
]
},
{
"name": "string.quoted.single.bob",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.bob",
"match": "\\\\[nt'\\\\e]"
}
]
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.integer.bob",
"match": "\\b\\d+\\b"
},
{
"name": "constant.numeric.float.bob",
"match": "\\b\\d+\\.\\d+\\b"
},
{
"name": "constant.numeric.binary.bob",
"match": "\\b0b[01]+\\b"
},
{
"name": "constant.numeric.hex.bob",
"match": "\\b0x[0-9a-fA-F]+\\b"
}
]
},
"arrays": {
"patterns": [
{
"name": "meta.array.bob",
"begin": "\\[",
"end": "\\]",
"patterns": [
{
"include": "#expressions"
}
]
},
{
"name": "variable.other.array-index.bob",
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\[([^\\]]+)\\]",
"captures": {
"1": { "name": "variable.other.bob" },
"2": { "name": "constant.numeric.integer.bob" }
}
}
]
},
"dictionaries": {
"patterns": [
{
"name": "meta.dictionary.bob",
"begin": "\\{",
"end": "\\}",
"patterns": [
{
"name": "string.quoted.double.bob",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.bob",
"match": "\\\\[nt\"\\\\e]"
}
]
},
{
"name": "keyword.operator.bob",
"match": ":"
},
{
"include": "#expressions"
}
]
},
{
"name": "variable.other.dictionary-index.bob",
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\{([^\\}]+)\\}",
"captures": {
"1": { "name": "variable.other.bob" },
"2": { "name": "string.quoted.double.bob" }
}
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.bob",
"match": "\\b(if|else|while|do|for|break|continue|return|var|func)\\b"
},
{
"name": "keyword.operator.bob",
"match": "\\b(and|or|not)\\b"
},
{
"name": "constant.language.bob",
"match": "\\b(true|false|none)\\b"
}
]
},
"functions": {
"patterns": [
{
"name": "entity.name.function.bob",
"match": "\\b(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
"captures": {
"1": { "name": "keyword.control.bob" },
"2": { "name": "entity.name.function.bob" }
}
},
{
"name": "support.function.builtin.bob",
"match": "\\b(print|assert|input|type|toString|toNumber|toInt|time|sleep|printRaw|len|push|pop|random|eval|keys|values|has)\\b"
}
]
},
"variables": {
"patterns": [
{
"name": "variable.other.bob",
"match": "\\bvar\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
"captures": {
"1": { "name": "keyword.control.bob" },
"2": { "name": "variable.other.bob" }
}
},
{
"name": "variable.other.bob",
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)(?=\\s*=)",
"captures": {
"1": { "name": "variable.other.bob" }
}
}
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.increment.bob",
"match": "\\+\\+|--"
},
{
"name": "keyword.operator.compound.bob",
"match": "\\+=|-=|\\*=|/=|%=|&=|\\|=|\\^=|<<=|>>="
},
{
"name": "keyword.operator.comparison.bob",
"match": "==|!=|<=|>="
},
{
"name": "keyword.operator.logical.bob",
"match": "&&|\\|\\||!"
},
{
"name": "keyword.operator.bitwise.bob",
"match": "<<|>>|&|\\||\\^|~"
},
{
"name": "keyword.operator.arithmetic.bob",
"match": "\\+|-|\\*|/|%"
},
{
"name": "keyword.operator.comparison.bob",
"match": "<|>"
},
{
"name": "keyword.operator.assignment.bob",
"match": "="
},
{
"name": "keyword.operator.punctuation.bob",
"match": "\\(|\\)|\\{|\\}|\\[|\\]|,|;|\\."
},
{
"name": "keyword.operator.conditional.bob",
"match": "\\?|:"
}
]
},
"expressions": {
"patterns": [
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#numbers"
},
{
"include": "#keywords"
},
{
"include": "#functions"
},
{
"include": "#variables"
},
{
"include": "#operators"
}
]
}
}
}

View File

@ -0,0 +1,58 @@
// Test file for operator syntax highlighting
var x = 5;
var y = 10;
var z = x + y;
var w = x - y;
var a = x * y;
var b = x / y;
var c = x % y;
// Comparison operators
var eq = x == y;
var ne = x != y;
var lt = x < y;
var gt = x > y;
var le = x <= y;
var ge = x >= y;
// Increment/decrement
x++;
y--;
// Compound assignment
x += 5;
y -= 3;
z *= 2;
w /= 4;
a %= 3;
// Logical operators
var and = true && false;
var or = true || false;
var not = !true;
// Bitwise operators
var bit_and = x & y;
var bit_or = x | y;
var bit_xor = x ^ y;
var left_shift = x << 2;
var right_shift = x >> 1;
// Compound bitwise assignment
x &= y;
y |= z;
z ^= w;
w <<= 2;
a >>= 1;
// Function with operators
func test_operators() {
var result = 0;
if (x >= 0 && y <= 100) {
result = x + y * 2;
if (result != 0) {
result++;
}
}
return result;
}

View File

@ -0,0 +1,79 @@
{
"name": "Bob Dark",
"type": "dark",
"colors": {
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4",
"editor.lineHighlightBackground": "#2a2a2a",
"editor.selectionBackground": "#264f78",
"editor.inactiveSelectionBackground": "#3a3d41"
},
"tokenColors": [
{
"name": "Comments",
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "#6a9955"
}
},
{
"name": "Strings",
"scope": ["string", "string.quoted"],
"settings": {
"foreground": "#ce9178"
}
},
{
"name": "Numbers",
"scope": ["constant.numeric"],
"settings": {
"foreground": "#b5cea8"
}
},
{
"name": "Keywords",
"scope": ["keyword.control", "keyword.operator"],
"settings": {
"foreground": "#569cd6"
}
},
{
"name": "Functions",
"scope": ["entity.name.function", "support.function.builtin"],
"settings": {
"foreground": "#dcdcaa"
}
},
{
"name": "Variables",
"scope": ["variable.other"],
"settings": {
"foreground": "#9cdcfe"
}
},
{
"name": "Constants",
"scope": ["constant.language"],
"settings": {
"foreground": "#4ec9b0"
}
},
{
"name": "Operators",
"scope": [
"keyword.operator.arithmetic",
"keyword.operator.comparison",
"keyword.operator.logical",
"keyword.operator.bitwise",
"keyword.operator.assignment",
"keyword.operator.compound",
"keyword.operator.increment",
"keyword.operator.punctuation",
"keyword.operator.conditional"
],
"settings": {
"foreground": "#d4d4d4"
}
}
]
}

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": [
"ES2020"
],
"sourceMap": true,
"rootDir": "src",
"strict": true
},
"exclude": [
"node_modules",
".vscode-test"
]
}

View File

@ -0,0 +1,108 @@
# Bob Language Extension v0.4.0
## What's New
### ✨ New Features Added
#### **Dictionary Support**
- **Dictionary literals**: `{"key": "value", "number": 42}`
- **Dictionary indexing**: `dict{"key"}` (returns `none` for missing keys)
- **Dictionary assignment**: `dict{"key"} = value`
- **Nested dictionaries**: `{"user": {"name": "Bob", "age": 30}}`
- **Mixed type values**: Any type can be stored as dictionary values
#### **Dictionary Built-in Functions**
- `keys(dict)` - Returns array of all keys
- `values(dict)` - Returns array of all values
- `has(dict, key)` - Returns boolean if key exists
#### **Dictionary Code Snippets**
- `dict` - Create dictionary literal
- `dictaccess` - Access dictionary value
- `dictassign` - Assign to dictionary key
- `keys`, `values`, `has` - Built-in function snippets
### 🎨 Syntax Highlighting Improvements
- Dictionary literal syntax highlighting
- Dictionary indexing syntax support
- Built-in function highlighting for `keys`, `values`, `has`
### 📝 Documentation Updates
- Complete dictionary documentation with examples
- Dictionary built-in functions documentation
- Updated language reference with dictionary section
- Array and dictionary built-in functions documentation
---
## Previous Version (v0.3.0)
### ✨ New Features Added
#### **Enhanced Array Support**
- **Auto-truncating float indices**: `array[3.14]``array[3]` (like JavaScript/Lua)
- **Increment/decrement on array elements**: `array[0]++`, `++array[1]`
- **Improved array operations**: Better error handling and bounds checking
#### **New Built-in Functions**
- `toInt()` - convert floats to integers (truncates decimals)
- Enhanced error reporting for all built-in functions
#### **Increment/Decrement Operators**
- **Prefix increment**: `++x`
- **Postfix increment**: `x++`
- **Prefix decrement**: `--x`
- **Postfix decrement**: `x--`
- **Works on variables and array elements**
#### **Cross-Type Comparisons**
- **Equality operators** (`==`, `!=`) work with any types
- **Comparison operators** (`>`, `<`, `>=`, `<=`) only work with numbers
- **Clear error messages** for type mismatches
#### **Compound Assignment Operators**
- **Enhanced error reporting** with correct operator names
- **Consistent behavior** across all compound operators
- **Better type checking** before operations
#### **New Code Snippets**
- `toint` - Convert float to integer
- `compound` - Compound assignment operators
- `inc` - Increment variable
- `dec` - Decrement variable
- `arrayinc` - Increment array element
- `arraydec` - Decrement array element
- `crosscomp` - Cross-type comparison
- `floatindex` - Array access with float index
### 🎨 Syntax Highlighting Improvements
- Support for `toInt()` built-in function
- Enhanced operator recognition for increment/decrement
- Better array indexing syntax support
### 📝 Documentation Updates
- Comprehensive array documentation with auto-truncation examples
- New built-in function documentation (`toInt`, enhanced error reporting)
- Cross-type comparison behavior documentation
- Increment/decrement operator documentation
- Compound assignment operator documentation
### 🐛 Bug Fixes
- **Fixed array printing** - arrays no longer show as "unknown"
- **Enhanced error reporting** - all errors now use the error reporter system
- **Improved type checking** - better error messages for type mismatches
- **Memory management** - better cleanup of unused functions and arrays
## Installation
To create the VSIX package:
1. Install Node.js and npm
2. Run `npm install -g vsce`
3. Run `./package-vsix.sh`
The extension will be packaged as `bob-language-0.4.0.vsix`
## Compatibility
- VS Code 1.60.0+
- Cursor (VS Code compatible)
- All platforms (Windows, macOS, Linux)

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

View File

View File

@ -1,72 +0,0 @@
#pragma once
#include "Expression.h"
#include "Statement.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Environment.h"
#include "Value.h"
#include "StdLib.h"
#include "ErrorReporter.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include <stack>
class Interpreter : public ExprVisitor, public StmtVisitor {
public:
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
void interpret(std::vector<std::shared_ptr<Stmt> > statements);
explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){
environment = std::make_shared<Environment>();
}
virtual ~Interpreter() = default;
private:
std::shared_ptr<Environment> environment;
bool IsInteractive;
std::vector<std::shared_ptr<BuiltinFunction> > builtinFunctions;
std::vector<std::shared_ptr<Function> > functions;
ErrorReporter* errorReporter;
Value evaluate(const std::shared_ptr<Expr>& expr);
bool isEqual(Value a, Value b);
bool isWholeNumer(double num);
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 addStdLibFunctions();
public:
bool isTruthy(Value object);
std::string stringify(Value object);
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
// Error reporting
void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
if (environment) {
environment->setErrorReporter(reporter);
}
// Add standard library functions after error reporter is set
addStdLibFunctions();
}
};

View File

@ -1,120 +0,0 @@
#pragma once
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Expression.h"
struct ExpressionStmt;
struct VarStmt;
struct BlockStmt;
struct FunctionStmt;
struct ReturnStmt;
struct IfStmt;
struct ExecutionContext {
bool isFunctionBody = false;
bool hasReturn = false;
Value returnValue;
};
struct StmtVisitor
{
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
};
struct Stmt : public std::enable_shared_from_this<Stmt>
{
std::shared_ptr<Expr> expression;
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
virtual ~Stmt(){};
};
struct BlockStmt : Stmt
{
std::vector<std::shared_ptr<Stmt> > statements;
explicit BlockStmt(std::vector<std::shared_ptr<Stmt> > statements) : statements(statements)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
}
};
struct ExpressionStmt : Stmt
{
std::shared_ptr<Expr> expression;
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
}
};
struct VarStmt : Stmt
{
Token name;
std::shared_ptr<Expr> initializer;
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
}
};
struct FunctionStmt : Stmt
{
const Token name;
const std::vector<Token> params;
std::vector<std::shared_ptr<Stmt> > body;
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt> > body)
: name(name), params(params), body(body) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
}
};
struct ReturnStmt : Stmt
{
const Token keyword;
std::shared_ptr<Expr> value;
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
}
};
struct IfStmt : Stmt
{
std::shared_ptr<Expr> condition;
std::shared_ptr<Stmt> thenBranch;
std::shared_ptr<Stmt> elseBranch;
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
}
};

View File

@ -1,274 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <cmath>
#include <stdexcept>
#include <algorithm>
// Forward declarations
class Environment;
class Function;
class BuiltinFunction;
// Type tags for the Value union
enum ValueType : uint8_t {
VAL_NONE,
VAL_NUMBER,
VAL_BOOLEAN,
VAL_STRING,
VAL_FUNCTION,
VAL_BUILTIN_FUNCTION
};
// Tagged value system (like Lua) - no heap allocation for simple values
struct Value {
union {
double number;
bool boolean;
Function* function;
BuiltinFunction* builtin_function;
};
ValueType type;
std::string string_value; // Store strings outside the union for safety
// Constructors
Value() : number(0.0), type(ValueType::VAL_NONE) {}
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
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) {}
// Move constructor
Value(Value&& other) noexcept
: type(other.type), string_value(std::move(other.string_value)) {
if (type != ValueType::VAL_STRING) {
number = other.number; // Copy the union
}
other.type = ValueType::VAL_NONE;
}
// Move assignment
Value& operator=(Value&& other) noexcept {
if (this != &other) {
type = other.type;
if (type == ValueType::VAL_STRING) {
string_value = std::move(other.string_value);
} else {
number = other.number; // Copy the union
}
other.type = ValueType::VAL_NONE;
}
return *this;
}
// Copy constructor (only when needed)
Value(const Value& other) : type(other.type) {
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else {
number = other.number; // Copy the union
}
}
// Copy assignment (only when needed)
Value& operator=(const Value& other) {
if (this != &other) {
type = other.type;
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else {
number = other.number; // Copy the union
}
}
return *this;
}
// Type checking (fast, no dynamic casting) - inline for performance
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
inline bool isString() const { return type == ValueType::VAL_STRING; }
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
inline bool isNone() const { return type == ValueType::VAL_NONE; }
// Value extraction (safe, with type checking) - inline for performance
inline double asNumber() const { return isNumber() ? number : 0.0; }
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
inline const std::string& asString() const { return string_value; }
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
// Truthiness check - inline for performance
inline bool isTruthy() const {
switch (type) {
case ValueType::VAL_NONE: return false;
case ValueType::VAL_BOOLEAN: return boolean;
case ValueType::VAL_NUMBER: return number != 0.0;
case ValueType::VAL_STRING: return !string_value.empty();
case ValueType::VAL_FUNCTION: return function != nullptr;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
default: return false;
}
}
// Equality comparison - inline for performance
inline bool equals(const Value& other) const {
if (type != other.type) return false;
switch (type) {
case ValueType::VAL_NONE: return true;
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
case ValueType::VAL_NUMBER: return number == other.number;
case ValueType::VAL_STRING: return string_value == other.string_value;
case ValueType::VAL_FUNCTION: return function == other.function;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
default: return false;
}
}
// String representation
std::string toString() const {
switch (type) {
case ValueType::VAL_NONE: return "none";
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
case ValueType::VAL_NUMBER: {
// Format numbers like the original stringify function
if (number == std::floor(number)) {
return std::to_string(static_cast<long long>(number));
} else {
std::string str = std::to_string(number);
// Remove trailing zeros
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') str.pop_back();
return str;
}
}
case ValueType::VAL_STRING: return string_value;
case ValueType::VAL_FUNCTION: return "<function>";
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
default: return "unknown";
}
}
// Arithmetic operators
Value operator+(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number + other.number);
}
if (isString() && other.isString()) {
return Value(string_value + other.string_value);
}
if (isString() && other.isNumber()) {
return Value(string_value + other.toString());
}
if (isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
// Handle none values by converting to string
if (isString() && other.isNone()) {
return Value(string_value + "none");
}
if (isNone() && other.isString()) {
return Value("none" + other.string_value);
}
if (isString() && !other.isString() && !other.isNumber()) {
return Value(string_value + other.toString());
}
if (!isString() && !isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
throw std::runtime_error("Invalid operands for + operator");
}
Value operator-(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number - other.number);
}
throw std::runtime_error("Invalid operands for - operator");
}
Value operator*(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number * other.number);
}
if (isString() && other.isNumber()) {
std::string result;
for (int i = 0; i < static_cast<int>(other.number); ++i) {
result += string_value;
}
return Value(result);
}
if (isNumber() && other.isString()) {
std::string result;
for (int i = 0; i < static_cast<int>(number); ++i) {
result += other.string_value;
}
return Value(result);
}
throw std::runtime_error("Invalid operands for * operator");
}
Value operator/(const Value& other) const {
if (isNumber() && other.isNumber()) {
if (other.number == 0) {
throw std::runtime_error("Division by zero");
}
return Value(number / other.number);
}
throw std::runtime_error("Invalid operands for / operator");
}
Value operator%(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(fmod(number, other.number));
}
throw std::runtime_error("Invalid operands for % operator");
}
Value operator&(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for & operator");
}
Value operator|(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for | operator");
}
Value operator^(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for ^ operator");
}
Value operator<<(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for << operator");
}
Value operator>>(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for >> operator");
}
};
// Global constants for common values
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;
extern const Value ZERO_VALUE;
extern const Value ONE_VALUE;

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

BIN
source/.DS_Store vendored

Binary file not shown.

View File

@ -1,5 +0,0 @@
//
// Created by Bobby Lucero on 5/21/23.
//
#include "../headers/Expression.h"

View File

@ -1,878 +0,0 @@
//
// Created by Bobby Lucero on 5/27/23.
//
#include <utility>
#include <sstream>
#include <cmath>
#include <iomanip>
#include <limits>
#include <cmath>
#include "../headers/Interpreter.h"
#include "../headers/helperFunctions/HelperFunctions.h"
#include <unordered_map>
#include "../headers/Interpreter.h"
#include "../headers/StdLib.h"
#include <iostream>
#include <chrono>
#include <cmath>
#include <stdexcept>
#include <algorithm>
struct ReturnContext {
Value returnValue;
bool hasReturn;
ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {}
};
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
if(expr->isNull) return NONE_VALUE;
if(expr->isNumber){
double num;
if(expr->value[1] == 'b')
{
num = binaryStringToLong(expr->value);
}
else
{
num = std::stod(expr->value);
}
return Value(num);
}
if(expr->isBoolean) {
if(expr->value == "true") return TRUE_VALUE;
if(expr->value == "false") return FALSE_VALUE;
}
return Value(expr->value);
}
Value Interpreter::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
return evaluate(expression->expression);
}
Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
{
Value right = evaluate(expression->right);
if(expression->oper.type == MINUS)
{
if(right.isNumber())
{
double value = right.asNumber();
return Value(-value);
}
else
{
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
}
if(expression->oper.type == BANG)
{
return Value(!isTruthy(right));
}
if(expression->oper.type == BIN_NOT)
{
if(right.isNumber())
{
double value = right.asNumber();
return Value(static_cast<double>(~(static_cast<long>(value))));
}
else
{
throw std::runtime_error("Operand must be an int when using: " + expression->oper.lexeme);
}
}
//unreachable
throw std::runtime_error("Invalid unary expression");
}
Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
Value left = evaluate(expression->left);
Value right = evaluate(expression->right);
if (left.isNumber() && right.isNumber()) {
double leftNum = left.asNumber();
double rightNum = right.asNumber();
switch (expression->oper.type) {
case PLUS: return Value(leftNum + rightNum);
case MINUS: return Value(leftNum - rightNum);
case SLASH: {
if (rightNum == 0) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero",
"Cannot divide by zero", expression->oper.lexeme);
}
throw std::runtime_error("Division by zero");
}
return Value(leftNum / rightNum);
}
case STAR: return Value(leftNum * rightNum);
case PERCENT: {
if (rightNum == 0) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero",
"Cannot perform modulo operation with zero", expression->oper.lexeme);
}
throw std::runtime_error("Modulo by zero");
}
return Value(std::fmod(leftNum, rightNum));
}
case GREATER: return Value(leftNum > rightNum);
case GREATER_EQUAL: return Value(leftNum >= rightNum);
case LESS: return Value(leftNum < rightNum);
case LESS_EQUAL: return Value(leftNum <= rightNum);
case DOUBLE_EQUAL: return Value(leftNum == rightNum);
case BANG_EQUAL: return Value(leftNum != rightNum);
case BIN_AND: return Value(static_cast<double>(static_cast<int>(leftNum) & static_cast<int>(rightNum)));
case BIN_OR: return Value(static_cast<double>(static_cast<int>(leftNum) | static_cast<int>(rightNum)));
case BIN_XOR: return Value(static_cast<double>(static_cast<int>(leftNum) ^ static_cast<int>(rightNum)));
case BIN_SLEFT: return Value(static_cast<double>(static_cast<int>(leftNum) << static_cast<int>(rightNum)));
case BIN_SRIGHT: return Value(static_cast<double>(static_cast<int>(leftNum) >> static_cast<int>(rightNum)));
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
}
}
if (left.isString() && right.isString()) {
std::string left_string = left.asString();
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return Value(left_string + right_string);
case DOUBLE_EQUAL: return Value(left_string == right_string);
case BANG_EQUAL: return Value(left_string != right_string);
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
default:
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Cannot use '" + expression->oper.lexeme + "' on two strings", expression->oper.lexeme);
}
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings");
}
}
if (left.isString() && right.isNumber()) {
std::string left_string = left.asString();
double right_num = right.asNumber();
switch (expression->oper.type) {
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(right_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number", expression->oper.lexeme);
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(right_num); i++) {
result += left_string;
}
return Value(result);
}
}
}
if (left.isNumber() && right.isString()) {
double left_num = left.asNumber();
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(left_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number", expression->oper.lexeme);
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(left_num); i++) {
result += right_string;
}
return Value(result);
}
}
}
if (left.isBoolean() && right.isBoolean()) {
bool left_bool = left.asBoolean();
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case AND: return Value(left_bool && right_bool);
case OR: return Value(left_bool || right_bool);
case DOUBLE_EQUAL: return Value(left_bool == right_bool);
case BANG_EQUAL: return Value(left_bool != right_bool);
}
}
if (left.isBoolean() && right.isString()) {
bool left_bool = left.asBoolean();
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
}
}
if (left.isString() && right.isBoolean()) {
std::string left_string = left.asString();
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case PLUS: return left + right;
}
}
if (left.isNumber() && right.isBoolean()) {
double left_num = left.asNumber();
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
}
}
if (left.isBoolean() && right.isNumber()) {
bool left_bool = left.asBoolean();
double right_num = right.asNumber();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
}
}
// Mixed-type logical operations (string && boolean, etc.)
if (left.isString() && right.isBoolean()) {
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
}
}
if (left.isBoolean() && right.isString()) {
bool left_bool = left.asBoolean();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
}
}
if (left.isString() && right.isNumber()) {
double right_num = right.asNumber();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(right_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number");
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(right_num); i++) {
result += left.asString();
}
return Value(result);
}
}
}
if (left.isNumber() && right.isString()) {
double left_num = left.asNumber();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(left_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number");
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(left_num); i++) {
result += right.asString();
}
return Value(result);
}
}
}
if (left.isNone() && right.isString()) {
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
}
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Cannot use '" + expression->oper.lexeme + "' on none and a string", expression->oper.lexeme);
}
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
}
if (left.isString() && right.isNone()) {
std::string left_string = left.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
}
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Cannot use '" + expression->oper.lexeme + "' on a string and none", expression->oper.lexeme);
}
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none");
}
else
{
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operands must be of same type when using: " + expression->oper.lexeme, expression->oper.lexeme);
}
throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme);
}
}
Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
{
return environment->get(expression->name);
}
Value Interpreter::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
// Get the current value of the operand
Value currentValue = evaluate(expression->operand);
if (!currentValue.isNumber()) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
}
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
}
double currentNum = currentValue.asNumber();
double newValue;
// Determine the operation based on the operator
if (expression->oper.type == PLUS_PLUS) {
newValue = currentNum + 1.0;
} else if (expression->oper.type == MINUS_MINUS) {
newValue = currentNum - 1.0;
} else {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Invalid increment/decrement operator.", "");
}
throw std::runtime_error("Invalid increment/decrement operator.");
}
// Update the variable if it's a variable expression
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
environment->assign(varExpr->name, Value(newValue));
} else {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to variables.", "");
}
throw std::runtime_error("Increment/decrement can only be applied to variables.");
}
// Return the appropriate value based on prefix/postfix
if (expression->isPrefix) {
return Value(newValue); // Prefix: return new value
} else {
return currentValue; // Postfix: return old value
}
}
void Interpreter::addStdLibFunctions() {
// Add standard library functions to the environment
StdLib::addToEnvironment(environment, *this, errorReporter);
}
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
builtinFunctions.push_back(func);
}
Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = 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 = environment->get(expression->name.lexeme);
switch (expression->op.type) {
case PLUS_EQUAL:
value = currentValue + value;
break;
case MINUS_EQUAL:
value = currentValue - value;
break;
case STAR_EQUAL:
value = currentValue * value;
break;
case SLASH_EQUAL:
value = currentValue / value;
break;
case PERCENT_EQUAL:
value = currentValue % value;
break;
case BIN_AND_EQUAL:
value = currentValue & value;
break;
case BIN_OR_EQUAL:
value = currentValue | value;
break;
case BIN_XOR_EQUAL:
value = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
value = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
value = currentValue >> value;
break;
default:
break;
}
break;
}
default:
break;
}
environment->assign(expression->name, value);
return value;
}
Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
Value callee = evaluate(expression->callee);
std::vector<Value> arguments;
for (const std::shared_ptr<Expr>& argument : expression->arguments) {
arguments.push_back(evaluate(argument));
}
if (callee.isBuiltinFunction()) {
// Builtin functions now work directly with Value and receive line and column
return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column);
}
if (callee.isFunction()) {
Function* function = callee.asFunction();
if (arguments.size() != function->params.size()) {
throw std::runtime_error("Expected " + std::to_string(function->params.size()) +
" arguments but got " + std::to_string(arguments.size()) + ".");
}
auto previousEnv = 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;
for (const auto& stmt : function->body) {
execute(stmt, &context);
if (context.hasReturn) {
environment = previousEnv;
return context.returnValue;
}
}
environment = previousEnv;
return context.returnValue;
}
throw std::runtime_error("Can only call functions and classes.");
}
Value Interpreter::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
// Convert Token parameters to string parameters
std::vector<std::string> paramNames;
for (const Token& param : expression->params) {
paramNames.push_back(param.lexeme);
}
auto function = msptr(Function)("anonymous", paramNames, expression->body, environment);
functions.push_back(function); // Keep the shared_ptr alive
return Value(function.get());
}
void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
auto newEnv = std::make_shared<Environment>(environment);
newEnv->setErrorReporter(errorReporter);
executeBlock(statement->statements, newEnv, context);
}
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
Value value = evaluate(statement->expression);
if(IsInteractive)
std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m" << std::endl;
}
void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context)
{
Value value = NONE_VALUE;
if(statement->initializer != nullptr)
{
value = evaluate(statement->initializer);
}
//std::cout << "Visit var stmt: " << statement->name.lexeme << " set to: " << stringify(value) << std::endl;
environment->define(statement->name.lexeme, value);
}
void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context)
{
// Convert Token parameters to string parameters
std::vector<std::string> paramNames;
for (const Token& param : statement->params) {
paramNames.push_back(param.lexeme);
}
auto function = msptr(Function)(statement->name.lexeme,
paramNames,
statement->body,
environment);
functions.push_back(function); // Keep the shared_ptr alive
environment->define(statement->name.lexeme, Value(function.get()));
}
void Interpreter::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context)
{
Value value = NONE_VALUE;
if (statement->value != nullptr) {
value = evaluate(statement->value);
}
if (context && context->isFunctionBody) {
context->hasReturn = true;
context->returnValue = value;
}
// If no context or not in function body, this is a top-level return (ignored)
}
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context)
{
if (isTruthy(evaluate(statement->condition))) {
execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch, context);
}
}
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt> > statements) {
for(const std::shared_ptr<Stmt>& s : statements)
{
execute(s, nullptr); // No context needed for top-level execution
}
}
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context)
{
statement->accept(this, context);
}
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context)
{
std::shared_ptr<Environment> previous = this->environment;
this->environment = env;
for(const std::shared_ptr<Stmt>& s : statements)
{
execute(s, context);
if (context && context->hasReturn) {
this->environment = previous;
return;
}
}
this->environment = previous;
}
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
return expr->accept(this);
}
bool Interpreter::isTruthy(Value object) {
if(object.isBoolean())
{
return object.asBoolean();
}
if(object.isNone())
{
return false;
}
if(object.isNumber())
{
return object.asNumber() != 0;
}
if(object.isString())
{
return object.asString().length() > 0;
}
return true;
}
bool Interpreter::isEqual(Value a, Value b) {
if(a.isNumber())
{
if(b.isNumber())
{
return a.asNumber() == b.asNumber();
}
return false;
}
else if(a.isBoolean())
{
if(b.isBoolean())
{
return a.asBoolean() == b.asBoolean();
}
return false;
}
else if(a.isString())
{
if(b.isString())
{
return a.asString() == b.asString();
}
return false;
}
else if(a.isNone())
{
if(b.isNone())
{
return true;
}
return false;
}
throw std::runtime_error("Invalid isEqual compariosn");
}
std::string Interpreter::stringify(Value object) {
if(object.isNone())
{
return "none";
}
else if(object.isNumber())
{
double integral = object.asNumber();
double fractional = std::modf(object.asNumber(), &integral);
std::stringstream ss;
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
{
ss << std::fixed << std::setprecision(0) << integral;
return ss.str();
}
else
{
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << object.asNumber();
std::string str = ss.str();
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') {
str.pop_back();
}
return str;
}
}
else if(object.isString())
{
return object.asString();
}
else if(object.isBoolean())
{
return object.asBoolean() == 1 ? "true" : "false";
}
else if(object.isFunction())
{
return "<function " + object.asFunction()->name + ">";
}
else if(object.isBuiltinFunction())
{
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
}
throw std::runtime_error("Could not convert object to string");
}
bool Interpreter::isWholeNumer(double num) {
double integral = num;
double fractional = std::modf(num, &integral);
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
{
return true;
}
else
{
return false;
}
}

View File

@ -1,261 +0,0 @@
#include "../headers/StdLib.h"
#include "../headers/Interpreter.h"
#include "../headers/ErrorReporter.h"
#include <chrono>
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
// Create a built-in toString function
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
return Value(interpreter.stringify(args[0]));
});
env->define("toString", Value(toStringFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toStringFunc);
// Create a built-in print function
auto printFunc = std::make_shared<BuiltinFunction>("print",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Use the interpreter's stringify function
std::cout << interpreter.stringify(args[0]) << std::endl;
return NONE_VALUE;
});
env->define("print", Value(printFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(printFunc);
// Create a built-in assert function
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 && args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
}
// Simple truthy check without calling interpreter.isTruthy
bool isTruthy = false;
if (args[0].isBoolean()) {
isTruthy = args[0].asBoolean();
} else if (args[0].isNone()) {
isTruthy = false;
} else {
isTruthy = true; // Numbers, strings, functions are truthy
}
if (!isTruthy) {
std::string message = "Assertion failed: condition is false";
if (args.size() == 2) {
if (args[1].isString()) {
message += " - " + std::string(args[1].asString());
}
}
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
}
throw std::runtime_error(message);
}
return NONE_VALUE;
});
env->define("assert", Value(assertFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(assertFunc);
// Create a built-in time function (returns microseconds since Unix epoch)
auto timeFunc = std::make_shared<BuiltinFunction>("time",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
return Value(static_cast<double>(microseconds));
});
env->define("time", Value(timeFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(timeFunc);
// Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() > 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
}
// Optional prompt
if (args.size() == 1) {
std::cout << interpreter.stringify(args[0]);
}
// Get user input
std::string userInput;
std::getline(std::cin, userInput);
return Value(userInput);
});
env->define("input", Value(inputFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(inputFunc);
// Create a built-in type function
auto typeFunc = std::make_shared<BuiltinFunction>("type",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
std::string typeName;
if (args[0].isNumber()) {
typeName = "number";
} else if (args[0].isString()) {
typeName = "string";
} else if (args[0].isBoolean()) {
typeName = "boolean";
} else if (args[0].isNone()) {
typeName = "none";
} else if (args[0].isFunction()) {
typeName = "function";
} else if (args[0].isBuiltinFunction()) {
typeName = "builtin_function";
} else {
typeName = "unknown";
}
return Value(typeName);
});
env->define("type", Value(typeFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(typeFunc);
// Create a built-in toNumber function for string-to-number conversion
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
[](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
return NONE_VALUE; // Return none for wrong argument count
}
if (!args[0].isString()) {
return NONE_VALUE; // Return none for wrong type
}
std::string str = args[0].asString();
// Remove leading/trailing whitespace
str.erase(0, str.find_first_not_of(" \t\n\r"));
str.erase(str.find_last_not_of(" \t\n\r") + 1);
if (str.empty()) {
return NONE_VALUE; // Return none for empty string
}
try {
double value = std::stod(str);
return Value(value);
} catch (const std::invalid_argument&) {
return NONE_VALUE; // Return none for invalid conversion
} catch (const std::out_of_range&) {
return NONE_VALUE; // Return none for out of range
}
});
env->define("toNumber", Value(toNumberFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toNumberFunc);
// Create a built-in toBoolean function for explicit boolean conversion
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Use the same logic as isTruthy() for consistency
Value value = args[0];
if (value.isNone()) {
return Value(false);
}
if (value.isBoolean()) {
return value; // Already a boolean
}
if (value.isNumber()) {
return Value(value.asNumber() != 0.0);
}
if (value.isString()) {
return Value(!value.asString().empty());
}
// For any other type (functions, etc.), consider them truthy
return Value(true);
});
env->define("toBoolean", Value(toBooleanFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toBooleanFunc);
// Create a built-in exit function to terminate the program
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
[](std::vector<Value> args, int line, int column) -> Value {
int exitCode = 0; // Default exit code
if (args.size() > 0) {
if (args[0].isNumber()) {
exitCode = static_cast<int>(args[0].asNumber());
}
// If not a number, just use default exit code 0
}
std::exit(exitCode);
return NONE_VALUE; // This line should never be reached
});
env->define("exit", Value(exitFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(exitFunc);
}

View File

@ -1,6 +0,0 @@
//
// Created by Bobby Lucero on 5/27/23.
//
#include "../headers/TypeWrapper.h"
#include <iostream>

44
src/headers/Executor.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "Statement.h"
class Evaluator; // Forward declaration
class Interpreter; // Forward declaration
/**
* @class Executor
* @brief Handles the execution of statements and control flow.
*
* Implements the StmtVisitor pattern. It is responsible for executing statements,
* managing environments, and handling control flow constructs like loops and
* conditionals. It uses the Evaluator to evaluate expressions when needed.
*/
class Executor : public StmtVisitor {
private:
Interpreter* interpreter; // Back-pointer to access interpreter services
Evaluator* evaluator; // For evaluating expressions
public:
Executor(Interpreter* interpreter, Evaluator* evaluator);
virtual ~Executor();
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
// Statement Visitors
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
private:
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
};

View File

@ -3,12 +3,12 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include "../headers/Lexer.h" #include "Lexer.h"
#include "../headers/Interpreter.h" #include "Interpreter.h"
#include "../headers/helperFunctions/ShortHands.h" #include "helperFunctions/ShortHands.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#define VERSION "0.0.1" #define VERSION "0.0.3"
class Bob class Bob
{ {

View File

@ -0,0 +1,10 @@
#pragma once
#include <string>
// Common error message utilities
namespace ErrorUtils {
// Generate consistent operator error messages with single quotes
inline std::string makeOperatorError(const std::string& op, const std::string& leftType, const std::string& rightType) {
return "'" + op + "' is not supported between '" + leftType + "' and '" + rightType + "'";
}
}

View File

@ -4,7 +4,7 @@
#include <vector> #include <vector>
#include <bitset> #include <bitset>
inline std::vector<std::string> splitString(const std::string& input, std::string delimiter) { inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
std::vector<std::string> tokens; std::vector<std::string> tokens;
std::string token; std::string token;
size_t start = 0; size_t start = 0;

View File

@ -48,6 +48,9 @@ public:
// Check if an error has been reported // Check if an error has been reported
bool hasReportedError() const { return hadError; } bool hasReportedError() const { return hadError; }
// Reset error state (call this between REPL commands)
void resetErrorState() { hadError = false; }
// Report errors with full context // Report errors with full context
void reportErrorWithContext(const ErrorContext& context); void reportErrorWithContext(const ErrorContext& context);

View File

@ -1,6 +1,4 @@
// // Expression AST nodes for Bob language
// Created by Bobby Lucero on 5/21/23.
//
#pragma once #pragma once
#include <iostream> #include <iostream>
@ -13,6 +11,16 @@
// Forward declarations // Forward declarations
struct FunctionExpr; struct FunctionExpr;
struct IncrementExpr; struct IncrementExpr;
struct TernaryExpr;
struct ArrayLiteralExpr;
struct ArrayIndexExpr;
struct PropertyExpr;
struct ArrayAssignExpr;
struct PropertyAssignExpr;
struct DictLiteralExpr;
struct DictIndexExpr;
struct DictAssignExpr;
struct ExprVisitor; struct ExprVisitor;
struct AssignExpr; struct AssignExpr;
@ -35,6 +43,15 @@ struct ExprVisitor
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0; virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0; virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0; virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
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;
}; };
struct Expr : public std::enable_shared_from_this<Expr> { struct Expr : public std::enable_shared_from_this<Expr> {
@ -152,3 +169,101 @@ struct IncrementExpr : Expr
} }
}; };
struct TernaryExpr : Expr
{
std::shared_ptr<Expr> condition;
std::shared_ptr<Expr> thenExpr;
std::shared_ptr<Expr> elseExpr;
TernaryExpr(std::shared_ptr<Expr> condition, std::shared_ptr<Expr> thenExpr, std::shared_ptr<Expr> elseExpr)
: condition(condition), thenExpr(thenExpr), elseExpr(elseExpr) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitTernaryExpr(std::static_pointer_cast<TernaryExpr>(shared_from_this()));
}
};
struct ArrayLiteralExpr : Expr
{
std::vector<std::shared_ptr<Expr>> elements;
explicit ArrayLiteralExpr(const std::vector<std::shared_ptr<Expr>>& elements)
: elements(elements) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitArrayLiteralExpr(std::static_pointer_cast<ArrayLiteralExpr>(shared_from_this()));
}
};
struct ArrayIndexExpr : Expr
{
std::shared_ptr<Expr> array;
std::shared_ptr<Expr> index;
Token bracket; // The closing bracket token for error reporting
ArrayIndexExpr(std::shared_ptr<Expr> array, std::shared_ptr<Expr> index, Token bracket)
: array(array), index(index), bracket(bracket) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitArrayIndexExpr(std::static_pointer_cast<ArrayIndexExpr>(shared_from_this()));
}
};
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
{
std::shared_ptr<Expr> array;
std::shared_ptr<Expr> index;
std::shared_ptr<Expr> value;
Token bracket; // The closing bracket token for error reporting
ArrayAssignExpr(std::shared_ptr<Expr> array, std::shared_ptr<Expr> index, std::shared_ptr<Expr> value, Token bracket)
: array(array), index(index), value(value), bracket(bracket) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitArrayAssignExpr(std::static_pointer_cast<ArrayAssignExpr>(shared_from_this()));
}
};
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;
explicit DictLiteralExpr(const std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& pairs)
: pairs(pairs) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitDictLiteralExpr(std::static_pointer_cast<DictLiteralExpr>(shared_from_this()));
}
};

View File

@ -6,6 +6,7 @@
enum TokenType{ enum TokenType{
OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE, OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE,
OPEN_BRACKET, CLOSE_BRACKET, // Array brackets
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT, COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT,
BIN_OR, BIN_AND, BIN_NOT, BIN_XOR, BIN_SLEFT, BIN_SRIGHT, BIN_OR, BIN_AND, BIN_NOT, BIN_XOR, BIN_SLEFT, BIN_SRIGHT,
@ -15,13 +16,16 @@ enum TokenType{
GREATER, GREATER_EQUAL, GREATER, GREATER_EQUAL,
LESS, LESS_EQUAL, LESS, LESS_EQUAL,
// Ternary operator
QUESTION, COLON,
// Increment/decrement operators // Increment/decrement operators
PLUS_PLUS, MINUS_MINUS, PLUS_PLUS, MINUS_MINUS,
IDENTIFIER, STRING, NUMBER, BOOL, IDENTIFIER, STRING, NUMBER, BOOL,
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR, AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN, WHILE, DO, VAR, CLASS, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
// Compound assignment operators // Compound assignment operators
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
@ -33,6 +37,7 @@ enum TokenType{
}; };
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE", inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
"OPEN_BRACKET", "CLOSE_BRACKET", // Array brackets
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT", "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT", "BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
@ -42,12 +47,14 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
"GREATER", "GREATER_EQUAL", "GREATER", "GREATER_EQUAL",
"LESS", "LESS_EQUAL", "LESS", "LESS_EQUAL",
"QUESTION", "COLON",
"PLUS_PLUS", "MINUS_MINUS", "PLUS_PLUS", "MINUS_MINUS",
"IDENTIFIER", "STRING", "NUMBER", "BOOL", "IDENTIFIER", "STRING", "NUMBER", "BOOL",
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR", "AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "WHILE", "DO", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
// Compound assignment operators // Compound assignment operators
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL", "PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
@ -67,12 +74,15 @@ const std::map<std::string, TokenType> KEYWORDS {
{"func", FUNCTION}, {"func", FUNCTION},
{"for", FOR}, {"for", FOR},
{"while", WHILE}, {"while", WHILE},
{"do", DO},
{"var", VAR}, {"var", VAR},
{"class", CLASS}, {"class", CLASS},
{"super", SUPER}, {"super", SUPER},
{"this", THIS}, {"this", THIS},
{"none", NONE}, {"none", NONE},
{"return", RETURN}, {"return", RETURN},
{"break", BREAK},
{"continue", CONTINUE},
}; };
struct Token struct Token

View File

@ -24,6 +24,7 @@ public:
private: private:
sptr(Expr) expression(); sptr(Expr) expression();
sptr(Expr) logical_or(); sptr(Expr) logical_or();
sptr(Expr) ternary();
sptr(Expr) logical_and(); sptr(Expr) logical_and();
sptr(Expr) bitwise_or(); sptr(Expr) bitwise_or();
sptr(Expr) bitwise_xor(); sptr(Expr) bitwise_xor();
@ -56,6 +57,16 @@ private:
std::shared_ptr<Stmt> ifStatement(); std::shared_ptr<Stmt> ifStatement();
std::shared_ptr<Stmt> whileStatement();
std::shared_ptr<Stmt> doWhileStatement();
std::shared_ptr<Stmt> forStatement();
std::shared_ptr<Stmt> breakStatement();
std::shared_ptr<Stmt> continueStatement();
std::shared_ptr<Stmt> declaration(); std::shared_ptr<Stmt> declaration();
std::shared_ptr<Stmt> varDeclaration(); std::shared_ptr<Stmt> varDeclaration();
@ -63,13 +74,22 @@ private:
std::shared_ptr<Stmt> functionDeclaration(); std::shared_ptr<Stmt> functionDeclaration();
std::shared_ptr<Expr> functionExpression(); std::shared_ptr<Expr> functionExpression();
std::shared_ptr<Stmt> assignmentStatement();
sptr(Expr) assignment(); sptr(Expr) assignment();
sptr(Expr) assignmentExpression(); // For for loop increment clauses
sptr(Expr) increment(); // Parse increment/decrement expressions sptr(Expr) increment(); // Parse increment/decrement expressions
sptr(Expr) postfix(); // Parse postfix operators sptr(Expr) postfix(); // Parse postfix operators
std::vector<std::shared_ptr<Stmt>> block(); std::vector<std::shared_ptr<Stmt>> block();
sptr(Expr) finishCall(sptr(Expr) callee); sptr(Expr) finishCall(sptr(Expr) callee);
sptr(Expr) finishArrayIndex(sptr(Expr) array);
sptr(Expr) finishArrayAssign(sptr(Expr) array, sptr(Expr) index, sptr(Expr) value);
sptr(Expr) finishDictIndex(sptr(Expr) dict);
sptr(Expr) finishDictAssign(sptr(Expr) dict, sptr(Expr) key, sptr(Expr) value);
sptr(Expr) arrayLiteral();
sptr(Expr) dictLiteral();
sptr(Expr) call(); // Handle call chains (function calls, array indexing, and dict indexing)
// Helper methods for function scope tracking // Helper methods for function scope tracking
void enterFunction() { functionDepth++; } void enterFunction() { functionDepth++; }

View File

@ -0,0 +1,212 @@
#pragma once
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Expression.h"
struct ExpressionStmt;
struct VarStmt;
struct BlockStmt;
struct FunctionStmt;
struct ReturnStmt;
struct IfStmt;
struct WhileStmt;
struct DoWhileStmt;
struct ForStmt;
struct BreakStmt;
struct ContinueStmt;
struct AssignStmt;
#include "ExecutionContext.h"
struct StmtVisitor
{
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitWhileStmt(const std::shared_ptr<WhileStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitForStmt(const std::shared_ptr<ForStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitBreakStmt(const std::shared_ptr<BreakStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitContinueStmt(const std::shared_ptr<ContinueStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitAssignStmt(const std::shared_ptr<AssignStmt>& stmt, ExecutionContext* context = nullptr) = 0;
};
struct Stmt : public std::enable_shared_from_this<Stmt>
{
std::shared_ptr<Expr> expression;
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
virtual ~Stmt(){};
};
struct BlockStmt : Stmt
{
std::vector<std::shared_ptr<Stmt>> statements;
explicit BlockStmt(std::vector<std::shared_ptr<Stmt>> statements) : statements(statements)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
}
};
struct ExpressionStmt : Stmt
{
std::shared_ptr<Expr> expression;
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
}
};
struct VarStmt : Stmt
{
Token name;
std::shared_ptr<Expr> initializer;
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
}
};
struct FunctionStmt : Stmt
{
const Token name;
const std::vector<Token> params;
std::vector<std::shared_ptr<Stmt>> body;
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt>> body)
: name(name), params(params), body(body) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
}
};
struct ReturnStmt : Stmt
{
const Token keyword;
std::shared_ptr<Expr> value;
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
}
};
struct IfStmt : Stmt
{
std::shared_ptr<Expr> condition;
std::shared_ptr<Stmt> thenBranch;
std::shared_ptr<Stmt> elseBranch;
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
}
};
struct WhileStmt : Stmt
{
std::shared_ptr<Expr> condition;
std::shared_ptr<Stmt> body;
WhileStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> body)
: condition(condition), body(body) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitWhileStmt(std::static_pointer_cast<WhileStmt>(shared_from_this()), context);
}
};
struct DoWhileStmt : Stmt
{
std::shared_ptr<Stmt> body;
std::shared_ptr<Expr> condition;
DoWhileStmt(std::shared_ptr<Stmt> body, std::shared_ptr<Expr> condition)
: body(body), condition(condition) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitDoWhileStmt(std::static_pointer_cast<DoWhileStmt>(shared_from_this()), context);
}
};
struct ForStmt : Stmt
{
std::shared_ptr<Stmt> initializer;
std::shared_ptr<Expr> condition;
std::shared_ptr<Expr> increment;
std::shared_ptr<Stmt> body;
ForStmt(std::shared_ptr<Stmt> initializer, std::shared_ptr<Expr> condition,
std::shared_ptr<Expr> increment, std::shared_ptr<Stmt> body)
: initializer(initializer), condition(condition), increment(increment), body(body) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitForStmt(std::static_pointer_cast<ForStmt>(shared_from_this()), context);
}
};
struct BreakStmt : Stmt
{
const Token keyword;
BreakStmt(Token keyword) : keyword(keyword) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitBreakStmt(std::static_pointer_cast<BreakStmt>(shared_from_this()), context);
}
};
struct ContinueStmt : Stmt
{
const Token keyword;
ContinueStmt(Token keyword) : keyword(keyword) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitContinueStmt(std::static_pointer_cast<ContinueStmt>(shared_from_this()), context);
}
};
struct AssignStmt : Stmt
{
const Token name;
const Token op;
std::shared_ptr<Expr> value;
AssignStmt(Token name, Token op, std::shared_ptr<Expr> value)
: name(name), op(op), value(value) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
}
};

View File

@ -9,11 +9,22 @@
// Forward declaration // Forward declaration
class ErrorReporter; class ErrorReporter;
class Environment { struct Environment {
public: public:
Environment() : parent(nullptr), errorReporter(nullptr) {} Environment() : parent(nullptr), errorReporter(nullptr) {}
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {} Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
// Copy constructor for closure snapshots - creates a deep copy of the environment chain
Environment(const Environment& other) : parent(nullptr), errorReporter(other.errorReporter) {
// Copy all variables normally - arrays will be handled by forceCleanup
variables = other.variables;
// Create a deep copy of the parent environment chain
if (other.parent) {
parent = std::make_shared<Environment>(*other.parent);
}
}
// Set error reporter for enhanced error reporting // Set error reporter for enhanced error reporting
void setErrorReporter(ErrorReporter* reporter) { void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter; errorReporter = reporter;
@ -33,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(); }
@ -46,3 +60,4 @@ private:
std::shared_ptr<Environment> parent; std::shared_ptr<Environment> parent;
ErrorReporter* errorReporter; ErrorReporter* errorReporter;
}; };

View File

@ -0,0 +1,45 @@
#pragma once
#include "Expression.h"
#include "Value.h"
class Interpreter; // Forward declaration for the back-pointer
/**
* @class Evaluator
* @brief Handles the logic for visiting and evaluating Expression AST nodes.
*
* Implements the Visitor pattern for all Expression types. This class
* contains the core evaluation logic for expressions, returning a Value.
*/
class Evaluator : public ExprVisitor {
private:
Interpreter* interpreter; // Back-pointer to access interpreter services like isTruthy, etc.
public:
explicit Evaluator(Interpreter* interpreter);
virtual ~Evaluator() = default;
// Expression Visitors
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
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

@ -0,0 +1,10 @@
#pragma once
#include "Value.h"
struct ExecutionContext {
bool isFunctionBody = false;
bool hasReturn = false;
Value returnValue = NONE_VALUE;
bool shouldBreak = false;
bool shouldContinue = false;
};

View File

@ -0,0 +1,44 @@
#pragma once
#include "Statement.h"
class Evaluator; // Forward declaration
class Interpreter; // Forward declaration
/**
* @class Executor
* @brief Handles the execution of statements and control flow.
*
* Implements the StmtVisitor pattern. It is responsible for executing statements,
* managing environments, and handling control flow constructs like loops and
* conditionals. It uses the Evaluator to evaluate expressions when needed.
*/
class Executor : public StmtVisitor {
private:
Interpreter* interpreter; // Back-pointer to access interpreter services
Evaluator* evaluator; // For evaluating expressions
public:
Executor(Interpreter* interpreter, Evaluator* evaluator);
virtual ~Executor();
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
// Statement Visitors
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
private:
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
};

View File

@ -0,0 +1,117 @@
#pragma once
#include "Expression.h"
#include "Statement.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Environment.h"
#include "Value.h"
#include "BobStdLib.h"
#include "ErrorReporter.h"
#include "ExecutionContext.h"
#include "RuntimeDiagnostics.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include <stack>
#include <optional>
#include <functional>
// Forward declaration
class Evaluator;
// RAII helper for thunk execution flag
struct ScopedThunkFlag {
bool& flag;
bool prev;
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
~ScopedThunkFlag() { flag = prev; }
};
// RAII helper for environment management
struct ScopedEnv {
std::shared_ptr<Environment>& target;
std::shared_ptr<Environment> prev;
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
~ScopedEnv() { target = prev; }
};
// Thunk class for trampoline-based tail call optimization
struct Thunk {
public:
using ThunkFunction = std::function<Value()>;
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
Value execute() const {
return func();
}
bool isThunk() const { return true; }
private:
ThunkFunction func;
};
class Executor;
class Interpreter {
private:
std::shared_ptr<Environment> environment;
bool isInteractive;
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
std::vector<std::shared_ptr<Function>> functions;
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
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;
public:
explicit Interpreter(bool isInteractive);
virtual ~Interpreter();
// Public interface for main
void interpret(std::vector<std::shared_ptr<Stmt>> statements);
void setErrorReporter(ErrorReporter* reporter);
// 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);
bool isEqual(Value a, Value b);
std::string stringify(Value object);
bool isInteractiveMode() const;
std::shared_ptr<Environment> getEnvironment();
void setEnvironment(std::shared_ptr<Environment> env);
void addThunk(std::shared_ptr<Thunk> thunk);
void addFunction(std::shared_ptr<Function> function);
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
void cleanupUnusedFunctions();
void cleanupUnusedThunks();
void forceCleanup();
// Function creation count management
void incrementFunctionCreationCount();
int getFunctionCreationCount() const;
void resetFunctionCreationCount();
int getCleanupThreshold() const;
// Public access for Evaluator
bool& getInThunkExecutionRef() { return inThunkExecution; }
private:
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
void addStdLibFunctions();
Value runTrampoline(Value initialResult);
};

View File

@ -0,0 +1,42 @@
#pragma once
#include "Value.h"
#include <string>
#include <memory>
// Forward declarations from Value.h
struct Function;
struct BuiltinFunction;
struct Thunk;
/**
* RuntimeDiagnostics - Utility functions for runtime operations
*
* This class handles value conversion, equality checking, string representation,
* and other diagnostic utilities that don't belong in core evaluation logic.
*/
class RuntimeDiagnostics {
public:
RuntimeDiagnostics() = default;
// Value utility functions
bool isTruthy(Value object);
bool isEqual(Value a, Value b);
std::string stringify(Value object);
// 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
std::string formatNumber(double value);
std::string formatArray(const std::vector<Value>& arr);
std::string formatDict(const std::unordered_map<std::string, Value>& dict);
};

428
src/headers/runtime/Value.h Normal file
View File

@ -0,0 +1,428 @@
#pragma once
#include "helperFunctions/ErrorUtils.h"
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <cmath>
#include <stdexcept>
#include <algorithm>
// Forward declarations
struct Environment;
struct Function;
struct BuiltinFunction;
struct Thunk;
// Type tags for the Value union
enum ValueType {
VAL_NONE,
VAL_NUMBER,
VAL_BOOLEAN,
VAL_STRING,
VAL_FUNCTION,
VAL_BUILTIN_FUNCTION,
VAL_THUNK,
VAL_ARRAY,
VAL_DICT
};
// Tagged value system (like Lua) - no heap allocation for simple values
struct Value {
union {
double number;
bool boolean;
};
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) {}
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
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(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)),
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;
}
// Move assignment
Value& operator=(Value&& other) noexcept {
if (this != &other) {
type = other.type;
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);
} else if (type == ValueType::VAL_DICT) {
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;
}
other.type = ValueType::VAL_NONE;
}
return *this;
}
// Copy constructor (only when needed)
Value(const Value& other) : 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
} 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 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;
} 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;
}
}
return *this;
}
// Type checking (fast, no dynamic casting) - inline for performance
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
inline bool isString() const { return type == ValueType::VAL_STRING; }
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
inline bool isDict() const { return type == ValueType::VAL_DICT; }
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
inline bool isNone() const { return type == ValueType::VAL_NONE; }
// Get type name as string for error messages
inline std::string getType() const {
switch (type) {
case ValueType::VAL_NONE: return "none";
case ValueType::VAL_NUMBER: return "number";
case ValueType::VAL_BOOLEAN: return "boolean";
case ValueType::VAL_STRING: return "string";
case ValueType::VAL_FUNCTION: return "function";
case ValueType::VAL_BUILTIN_FUNCTION: return "builtin_function";
case ValueType::VAL_THUNK: return "thunk";
case ValueType::VAL_ARRAY: return "array";
case ValueType::VAL_DICT: return "dict";
default: return "unknown";
}
}
// Value extraction (safe, with type checking) - inline for performance
inline double asNumber() const { return isNumber() ? number : 0.0; }
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
inline const std::string& asString() const { return string_value; }
inline const std::vector<Value>& asArray() const {
return *array_value;
}
inline std::vector<Value>& asArray() {
return *array_value;
}
inline const std::unordered_map<std::string, Value>& asDict() const {
return *dict_value;
}
inline std::unordered_map<std::string, Value>& asDict() {
return *dict_value;
}
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 {
switch (type) {
case ValueType::VAL_NONE: return false;
case ValueType::VAL_BOOLEAN: return boolean;
case ValueType::VAL_NUMBER: return number != 0.0;
case ValueType::VAL_STRING: return !string_value.empty();
case ValueType::VAL_FUNCTION: return function != nullptr;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
case ValueType::VAL_THUNK: return thunk != nullptr;
case ValueType::VAL_ARRAY: return !array_value->empty();
case ValueType::VAL_DICT: return !dict_value->empty();
default: return false;
}
}
// Equality comparison - inline for performance
inline bool equals(const Value& other) const {
if (type != other.type) return false;
switch (type) {
case ValueType::VAL_NONE: return true;
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
case ValueType::VAL_NUMBER: return number == other.number;
case ValueType::VAL_STRING: return string_value == other.string_value;
case ValueType::VAL_FUNCTION: return function == other.function;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
case ValueType::VAL_THUNK: return thunk == other.thunk;
case ValueType::VAL_ARRAY: {
if (array_value->size() != other.array_value->size()) return false;
for (size_t i = 0; i < array_value->size(); i++) {
if (!(*array_value)[i].equals((*other.array_value)[i])) return false;
}
return true;
}
case ValueType::VAL_DICT: {
if (dict_value->size() != other.dict_value->size()) return false;
for (const auto& pair : *dict_value) {
auto it = other.dict_value->find(pair.first);
if (it == other.dict_value->end() || !pair.second.equals(it->second)) return false;
}
return true;
}
default: return false;
}
}
// String representation
std::string toString() const {
switch (type) {
case ValueType::VAL_NONE: return "none";
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
case ValueType::VAL_NUMBER: {
// Format numbers like the original stringify function
if (number == std::floor(number)) {
return std::to_string(static_cast<long long>(number));
} else {
std::string str = std::to_string(number);
// Remove trailing zeros
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') str.pop_back();
return str;
}
}
case ValueType::VAL_STRING: return string_value;
case ValueType::VAL_FUNCTION: return "<function>";
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
case ValueType::VAL_THUNK: return "<thunk>";
case ValueType::VAL_ARRAY: {
const std::vector<Value>& arr = *array_value;
std::string result = "[";
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0) result += ", ";
result += arr[i].toString();
}
result += "]";
return result;
}
case ValueType::VAL_DICT: {
const std::unordered_map<std::string, Value>& dict = *dict_value;
std::string result = "{";
bool first = true;
for (const auto& pair : dict) {
if (!first) result += ", ";
result += "\"" + pair.first + "\": " + pair.second.toString();
first = false;
}
result += "}";
return result;
}
default: return "unknown";
}
}
// Equality operator
bool operator==(const Value& other) const {
return equals(other);
}
bool operator!=(const Value& other) const {
return !equals(other);
}
// Arithmetic operators
Value operator+(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number + other.number);
}
if (isString() && other.isString()) {
return Value(string_value + other.string_value);
}
if (isString() && other.isNumber()) {
return Value(string_value + other.toString());
}
if (isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
// Handle none values by converting to string
if (isString() && other.isNone()) {
return Value(string_value + "none");
}
if (isNone() && other.isString()) {
return Value("none" + other.string_value);
}
if (isString() && !other.isString() && !other.isNumber()) {
return Value(string_value + other.toString());
}
if (!isString() && !isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("+", getType(), other.getType()));
}
Value operator-(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number - other.number);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("-", getType(), other.getType()));
}
Value operator*(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number * other.number);
}
if (isString() && other.isNumber()) {
std::string result;
for (int i = 0; i < static_cast<int>(other.number); ++i) {
result += string_value;
}
return Value(result);
}
if (isNumber() && other.isString()) {
std::string result;
for (int i = 0; i < static_cast<int>(number); ++i) {
result += other.string_value;
}
return Value(result);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("*", getType(), other.getType()));
}
Value operator/(const Value& other) const {
if (isNumber() && other.isNumber()) {
if (other.number == 0) {
throw std::runtime_error("Division by zero");
}
return Value(number / other.number);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("/", getType(), other.getType()));
}
Value operator%(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(fmod(number, other.number));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("%", getType(), other.getType()));
}
Value operator&(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("&", getType(), other.getType()));
}
Value operator|(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("|", getType(), other.getType()));
}
Value operator^(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("^", getType(), other.getType()));
}
Value operator<<(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("<<", getType(), other.getType()));
}
Value operator>>(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError(">>", getType(), other.getType()));
}
};
// Global constants for common values
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;
extern const Value ZERO_VALUE;
extern const Value ONE_VALUE;

View File

@ -7,7 +7,7 @@
class Interpreter; class Interpreter;
class ErrorReporter; class ErrorReporter;
class StdLib { class BobStdLib {
public: public:
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr); static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
}; };

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

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

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

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

View File

@ -1,22 +1,21 @@
#include <utility> #include <utility>
#include "../headers/bob.h" #include "bob.h"
#include "../headers/Parser.h" #include "Parser.h"
using namespace std;
void Bob::runFile(const string& path) void Bob::runFile(const std::string& path)
{ {
this->interpreter = msptr(Interpreter)(false); this->interpreter = msptr(Interpreter)(false);
ifstream file = ifstream(path); std::ifstream file = std::ifstream(path);
string source; std::string source;
if(file.is_open()){ if(file.is_open()){
source = string(istreambuf_iterator<char>(file), istreambuf_iterator<char>()); source = std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
} }
else else
{ {
cout << "File not found" << endl; std::cout << "File not found\n";
return; return;
} }
@ -33,11 +32,11 @@ void Bob::runPrompt()
{ {
this->interpreter = msptr(Interpreter)(true); this->interpreter = msptr(Interpreter)(true);
cout << "Bob v" << VERSION << ", 2023" << endl; std::cout << "Bob v" << VERSION << ", 2025\n";
for(;;) while(true)
{ {
string line; std::string line;
cout << "\033[0;36m" << "-> " << "\033[0;37m"; std::cout << "\033[0;36m" << "-> " << "\033[0;37m";
std::getline(std::cin, line); std::getline(std::cin, line);
if(std::cin.eof()) if(std::cin.eof())
@ -45,6 +44,9 @@ void Bob::runPrompt()
break; break;
} }
// Reset error state before each REPL command
errorReporter.resetErrorState();
// Load source code into error reporter for context // Load source code into error reporter for context
errorReporter.loadSource(line, "REPL"); errorReporter.loadSource(line, "REPL");
@ -55,19 +57,19 @@ void Bob::runPrompt()
} }
} }
void Bob::run(string source) void Bob::run(std::string source)
{ {
try { try {
// Connect error reporter to lexer // Connect error reporter to lexer
lexer.setErrorReporter(&errorReporter); lexer.setErrorReporter(&errorReporter);
vector<Token> tokens = lexer.Tokenize(std::move(source)); std::vector<Token> tokens = lexer.Tokenize(std::move(source));
Parser p(tokens); Parser p(tokens);
// Connect error reporter to parser // Connect error reporter to parser
p.setErrorReporter(&errorReporter); p.setErrorReporter(&errorReporter);
vector<sptr(Stmt)> statements = p.parse(); std::vector<sptr(Stmt)> statements = p.parse();
interpreter->interpret(statements); interpreter->interpret(statements);
} }
catch(std::exception &e) catch(std::exception &e)
@ -79,13 +81,13 @@ void Bob::run(string source)
// For errors that weren't reported (like parser errors, undefined variables, etc.) // For errors that weren't reported (like parser errors, undefined variables, etc.)
// print them normally // print them normally
std::cout << "Error: " << e.what() << std::endl; std::cout << "Error: " << e.what() << '\n';
return; return;
} }
catch(...) catch(const std::exception& e)
{ {
// Unknown error - report it since it wasn't handled by the interpreter // Unknown error - report it since it wasn't handled by the interpreter
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred"); errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred: " + std::string(e.what()));
return; return;
} }
} }

View File

@ -1,7 +1,7 @@
// //
// Created by Bobby Lucero on 5/21/23.
// //
#include "../headers/bob.h" #include "bob.h"
int main(int argc, char* argv[]){ int main(int argc, char* argv[]){
Bob bobLang; Bob bobLang;

View File

@ -1,4 +1,4 @@
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
#include <string> #include <string>
@ -59,6 +59,7 @@ void ErrorReporter::loadSource(const std::string& source, const std::string& fil
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) { void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
hadError = true; hadError = true;
displaySourceContext(line, column, errorType, message, operator_, showArrow); displaySourceContext(line, column, errorType, message, operator_, showArrow);
std::cout.flush(); // Ensure output is flushed before any exception is thrown
} }
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) { void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
@ -106,7 +107,8 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
return; return;
} }
int maxWidth = 65; static const int ERROR_DISPLAY_MAX_WIDTH = 65;
int maxWidth = ERROR_DISPLAY_MAX_WIDTH;
int startLine = std::max(1, line - 4); int startLine = std::max(1, line - 4);
int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2); int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2);
@ -119,7 +121,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length()); int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length());
maxWidth = std::max(maxWidth, errorLineWidth); maxWidth = std::max(maxWidth, errorLineWidth);
maxWidth = std::max(maxWidth, 65); maxWidth = std::max(maxWidth, ERROR_DISPLAY_MAX_WIDTH);
std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n"; std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n";
std::cout << colorize("" + std::string(maxWidth, '-') + "", Colors::BLUE) << "\n"; std::cout << colorize("" + std::string(maxWidth, '-') + "", Colors::BLUE) << "\n";
@ -162,7 +164,8 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) { void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) {
if (callStack.empty()) return; if (callStack.empty()) return;
int maxWidth = 65; static const int CALL_STACK_MAX_WIDTH = 65;
int maxWidth = CALL_STACK_MAX_WIDTH;
for (const auto& func : callStack) { for (const auto& func : callStack) {
int funcWidth = static_cast<int>(func.length()) + 6; int funcWidth = static_cast<int>(func.length()) + 6;
maxWidth = std::max(maxWidth, funcWidth); maxWidth = std::max(maxWidth, funcWidth);

View File

@ -0,0 +1,3 @@
#include "Expression.h"

View File

@ -1,10 +1,10 @@
#include "../headers/Lexer.h" #include "Lexer.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#include "../headers/helperFunctions/HelperFunctions.h" #include "helperFunctions/HelperFunctions.h"
#include <cctype> #include <cctype>
#include <stdexcept> #include <stdexcept>
using namespace std;
std::vector<Token> Lexer::Tokenize(std::string source){ std::vector<Token> Lexer::Tokenize(std::string source){
std::vector<Token> tokens; std::vector<Token> tokens;
@ -35,6 +35,16 @@ std::vector<Token> Lexer::Tokenize(std::string source){
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column}); tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column});
advance(); advance();
} }
else if(t == '[')
{
tokens.push_back(Token{OPEN_BRACKET, std::string(1, t), line, column});
advance();
}
else if(t == ']')
{
tokens.push_back(Token{CLOSE_BRACKET, std::string(1, t), line, column});
advance();
}
else if(t == ',') else if(t == ',')
{ {
tokens.push_back(Token{COMMA, std::string(1, t), line, column}); tokens.push_back(Token{COMMA, std::string(1, t), line, column});
@ -115,6 +125,16 @@ std::vector<Token> Lexer::Tokenize(std::string source){
tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1}); tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1});
advance(); advance();
} }
else if(t == '?')
{
tokens.push_back(Token{QUESTION, std::string(1, t), line, column});
advance();
}
else if(t == ':')
{
tokens.push_back(Token{COLON, std::string(1, t), line, column});
advance();
}
else if(t == '=') else if(t == '=')
{ {
std::string token = std::string(1, t); std::string token = std::string(1, t);
@ -421,7 +441,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
} }
} }
tokens.push_back({END_OF_FILE, "eof", line}); tokens.push_back({END_OF_FILE, "eof", line, column});
return tokens; return tokens;
} }
@ -473,8 +493,11 @@ char Lexer::peekNext()
std::string Lexer::parseEscapeCharacters(const std::string& input) { std::string Lexer::parseEscapeCharacters(const std::string& input) {
std::string output; std::string output;
bool escapeMode = false; bool escapeMode = false;
size_t i = 0;
while (i < input.length()) {
char c = input[i];
for (char c : input) {
if (escapeMode) { if (escapeMode) {
switch (c) { switch (c) {
case 'n': case 'n':
@ -489,8 +512,30 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
case '\\': case '\\':
output += '\\'; output += '\\';
break; break;
case '0':
output += '\0';
break;
case 'r':
output += '\r';
break;
case 'a':
output += '\a';
break;
case 'b':
output += '\b';
break;
case 'f':
output += '\f';
break;
case 'v':
output += '\v';
break;
case 'e':
// ANSI escape sequence
output += '\033';
break;
default: default:
throw runtime_error("Invalid escape character: " + std::string(1, c)); throw std::runtime_error("Invalid escape character: " + std::string(1, c));
} }
escapeMode = false; escapeMode = false;
} else if (c == '\\') { } else if (c == '\\') {
@ -498,6 +543,7 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
} else { } else {
output += c; output += c;
} }
i++;
} }
return output; return output;

View File

@ -1,12 +1,10 @@
//
// Created by Bobby Lucero on 5/26/23. #include "Parser.h"
//
#include "../headers/Parser.h"
#include <stdexcept> #include <stdexcept>
// Precedence // Operator Precedence Rules
// to all the morons on facebook who don't know what pemdas is, fuck you // Following standard mathematical order of operations
/////////////////////////////////////////// ///////////////////////////////////////////
sptr(Expr) Parser::expression() sptr(Expr) Parser::expression()
@ -16,18 +14,32 @@ sptr(Expr) Parser::expression()
sptr(Expr) Parser::logical_or() sptr(Expr) Parser::logical_or()
{ {
sptr(Expr) expr = logical_and(); sptr(Expr) expr = ternary();
while(match({OR})) while(match({OR}))
{ {
Token op = previous(); Token op = previous();
sptr(Expr) right = logical_and(); sptr(Expr) right = ternary();
expr = msptr(BinaryExpr)(expr, op, right); expr = msptr(BinaryExpr)(expr, op, right);
} }
return expr; return expr;
} }
sptr(Expr) Parser::ternary()
{
sptr(Expr) expr = logical_and();
if (match({QUESTION})) {
sptr(Expr) thenExpr = expression();
consume(COLON, "Expected ':' after ternary condition");
sptr(Expr) elseExpr = expression();
expr = msptr(TernaryExpr)(expr, thenExpr, elseExpr);
}
return expr;
}
sptr(Expr) Parser::logical_and() sptr(Expr) Parser::logical_and()
{ {
sptr(Expr) expr = equality(); sptr(Expr) expr = equality();
@ -101,18 +113,42 @@ sptr(Expr) Parser::shift()
sptr(Expr) Parser::assignment() sptr(Expr) Parser::assignment()
{ {
// Assignments are now statements, not expressions
// This function should only handle expressions that are not assignments
return increment();
}
sptr(Expr) Parser::assignmentExpression()
{
// This allows assignments as expressions (for for loop increment clauses)
sptr(Expr) expr = increment(); sptr(Expr) expr = increment();
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, 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})) BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL}))
{ {
Token op = previous(); Token op = previous();
sptr(Expr) value = assignment(); sptr(Expr) value = assignmentExpression();
if(std::dynamic_pointer_cast<VarExpr>(expr)) if(std::dynamic_pointer_cast<VarExpr>(expr))
{ {
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name; Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
return msptr(AssignExpr)(name, op, value); return msptr(AssignExpr)(name, op, 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);
}
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) {
errorReporter->reportError(op.line, op.column, "Parse Error", errorReporter->reportError(op.line, op.column, "Parse Error",
@ -194,13 +230,14 @@ sptr(Expr) Parser::unary()
// Handle prefix increment/decrement // Handle prefix increment/decrement
if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) { if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) {
// Ensure the operand is a variable // Ensure the operand is a variable or array indexing
if (!std::dynamic_pointer_cast<VarExpr>(right)) { if (!std::dynamic_pointer_cast<VarExpr>(right) &&
!std::dynamic_pointer_cast<ArrayIndexExpr>(right)) {
if (errorReporter) { if (errorReporter) {
errorReporter->reportError(op.line, op.column, "Parse Error", errorReporter->reportError(op.line, op.column, "Parse Error",
"Prefix increment/decrement can only be applied to variables", ""); "Prefix increment/decrement can only be applied to variables or array elements", "");
} }
throw std::runtime_error("Prefix increment/decrement can only be applied to variables."); throw std::runtime_error("Prefix increment/decrement can only be applied to variables or array elements.");
} }
return msptr(IncrementExpr)(right, op, true); // true = prefix return msptr(IncrementExpr)(right, op, true); // true = prefix
} }
@ -219,13 +256,14 @@ sptr(Expr) Parser::postfix()
if (match({PLUS_PLUS, MINUS_MINUS})) { if (match({PLUS_PLUS, MINUS_MINUS})) {
Token oper = previous(); Token oper = previous();
// Ensure the expression is a variable // Ensure the expression is a variable or array indexing
if (!std::dynamic_pointer_cast<VarExpr>(expr)) { if (!std::dynamic_pointer_cast<VarExpr>(expr) &&
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr)) {
if (errorReporter) { if (errorReporter) {
errorReporter->reportError(oper.line, oper.column, "Parse Error", errorReporter->reportError(oper.line, oper.column, "Parse Error",
"Postfix increment/decrement can only be applied to variables", ""); "Postfix increment/decrement can only be applied to variables or array elements", "");
} }
throw std::runtime_error("Postfix increment/decrement can only be applied to variables."); throw std::runtime_error("Postfix increment/decrement can only be applied to variables or array elements.");
} }
return msptr(IncrementExpr)(expr, oper, false); // false = postfix return msptr(IncrementExpr)(expr, oper, false); // false = postfix
@ -244,10 +282,7 @@ sptr(Expr) Parser::primary()
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false); if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
if(match( {IDENTIFIER})) { if(match( {IDENTIFIER})) {
if (check(OPEN_PAREN)) { return call();
return finishCall(msptr(VarExpr)(previous()));
}
return msptr(VarExpr)(previous());
} }
if(match({OPEN_PAREN})) if(match({OPEN_PAREN}))
@ -264,6 +299,14 @@ sptr(Expr) Parser::primary()
return functionExpression(); return functionExpression();
} }
if(match({OPEN_BRACKET})) {
return arrayLiteral();
}
if(match({OPEN_BRACE})) {
return dictLiteral();
}
if (errorReporter) { if (errorReporter) {
errorReporter->reportError(peek().line, peek().column, "Parse Error", errorReporter->reportError(peek().line, peek().column, "Parse Error",
"Expression expected", ""); "Expression expected", "");
@ -271,7 +314,69 @@ sptr(Expr) Parser::primary()
throw std::runtime_error("Expression expected at: " + std::to_string(peek().line)); throw std::runtime_error("Expression expected at: " + std::to_string(peek().line));
} }
/////////////////////////////////////////// sptr(Expr) Parser::arrayLiteral()
{
std::vector<sptr(Expr)> elements;
if (!check(CLOSE_BRACKET)) {
do {
elements.push_back(expression());
} while (match({COMMA}));
}
consume(CLOSE_BRACKET, "Expected ']' after array elements.");
return msptr(ArrayLiteralExpr)(elements);
}
sptr(Expr) Parser::dictLiteral()
{
std::vector<std::pair<std::string, sptr(Expr)>> pairs;
if (!check(CLOSE_BRACE)) {
do {
// Parse key (must be a string literal)
if (!match({STRING})) {
if (errorReporter) {
errorReporter->reportError(peek().line, peek().column, "Parse Error",
"Dictionary key must be a string literal", "");
}
throw std::runtime_error("Dictionary key must be a string literal");
}
std::string key = previous().lexeme;
// Parse colon
consume(COLON, "Expected ':' after dictionary key");
// Parse value
sptr(Expr) value = expression();
pairs.emplace_back(key, value);
} while (match({COMMA}));
}
consume(CLOSE_BRACE, "Expected '}' after dictionary pairs.");
return msptr(DictLiteralExpr)(pairs);
}
sptr(Expr) Parser::call()
{
sptr(Expr) expr = msptr(VarExpr)(previous());
while (true) {
if (match({OPEN_PAREN})) {
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;
}
}
return expr;
}
std::vector<sptr(Stmt)> Parser::parse() { std::vector<sptr(Stmt)> Parser::parse() {
@ -344,12 +449,13 @@ std::shared_ptr<Expr> Parser::functionExpression() {
std::vector<Token> parameters; std::vector<Token> parameters;
if (!check(CLOSE_PAREN)) { if (!check(CLOSE_PAREN)) {
do { do {
if (parameters.size() >= 255) { static const size_t MAX_FUNCTION_PARAMETERS = 255;
if (parameters.size() >= MAX_FUNCTION_PARAMETERS) {
if (errorReporter) { if (errorReporter) {
errorReporter->reportError(peek().line, 0, "Parse Error", errorReporter->reportError(peek().line, 0, "Parse Error",
"Cannot have more than 255 parameters", ""); "Cannot have more than " + std::to_string(MAX_FUNCTION_PARAMETERS) + " parameters", "");
} }
throw std::runtime_error("Cannot have more than 255 parameters."); throw std::runtime_error("Cannot have more than " + std::to_string(MAX_FUNCTION_PARAMETERS) + " parameters.");
} }
parameters.push_back(consume(IDENTIFIER, "Expect parameter name.")); parameters.push_back(consume(IDENTIFIER, "Expect parameter name."));
} while (match({COMMA})); } while (match({COMMA}));
@ -372,11 +478,56 @@ sptr(Stmt) Parser::statement()
{ {
if(match({RETURN})) return returnStatement(); if(match({RETURN})) return returnStatement();
if(match({IF})) return ifStatement(); if(match({IF})) return ifStatement();
if(match({DO})) return doWhileStatement();
if(match({WHILE})) return whileStatement();
if(match({FOR})) return forStatement();
if(match({BREAK})) return breakStatement();
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 - simplified approach
if(check(IDENTIFIER)) {
// Try to parse as assignment expression first
int currentPos = current;
try {
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.");
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;
}
}
return expressionStatement(); return expressionStatement();
} }
sptr(Stmt) Parser::assignmentStatement()
{
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
// Consume any assignment operator
Token op;
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})) {
op = previous();
} else {
throw std::runtime_error("Expected assignment operator.");
}
sptr(Expr) value = expression();
consume(SEMICOLON, "Expected ';' after assignment.");
return msptr(AssignStmt)(name, op, value);
}
sptr(Stmt) Parser::ifStatement() sptr(Stmt) Parser::ifStatement()
{ {
@ -394,6 +545,78 @@ sptr(Stmt) Parser::ifStatement()
return msptr(IfStmt)(condition, thenBranch, elseBranch); return msptr(IfStmt)(condition, thenBranch, elseBranch);
} }
sptr(Stmt) Parser::whileStatement()
{
consume(OPEN_PAREN, "Expected '(' after 'while'.");
sptr(Expr) condition = expression();
consume(CLOSE_PAREN, "Expected ')' after while condition.");
sptr(Stmt) body = statement();
return msptr(WhileStmt)(condition, body);
}
sptr(Stmt) Parser::doWhileStatement()
{
sptr(Stmt) body = statement();
consume(WHILE, "Expected 'while' after do-while body.");
consume(OPEN_PAREN, "Expected '(' after 'while'.");
sptr(Expr) condition = expression();
consume(CLOSE_PAREN, "Expected ')' after while condition.");
consume(SEMICOLON, "Expected ';' after do-while condition.");
return msptr(DoWhileStmt)(body, condition);
}
sptr(Stmt) Parser::forStatement()
{
consume(OPEN_PAREN, "Expected '(' after 'for'.");
sptr(Stmt) initializer;
if (match({SEMICOLON})) {
initializer = nullptr;
} else if (match({VAR})) {
initializer = varDeclaration();
} else {
// Allow assignment expressions in for loop initializer
sptr(Expr) expr = assignmentExpression();
consume(SEMICOLON, "Expected ';' after for loop initializer.");
initializer = msptr(ExpressionStmt)(expr);
}
sptr(Expr) condition = nullptr;
if (!check(SEMICOLON)) {
condition = expression();
}
consume(SEMICOLON, "Expected ';' after for loop condition.");
sptr(Expr) increment = nullptr;
if (!check(CLOSE_PAREN)) {
increment = assignmentExpression();
}
consume(CLOSE_PAREN, "Expected ')' after for clauses.");
sptr(Stmt) body = statement();
// Return the for statement directly instead of desugaring
return msptr(ForStmt)(initializer, condition, increment, body);
}
sptr(Stmt) Parser::breakStatement()
{
Token keyword = previous();
consume(SEMICOLON, "Expected ';' after 'break'.");
return msptr(BreakStmt)(keyword);
}
sptr(Stmt) Parser::continueStatement()
{
Token keyword = previous();
consume(SEMICOLON, "Expected ';' after 'continue'.");
return msptr(ContinueStmt)(keyword);
}
// Helper function to detect if an expression is a tail call // Helper function to detect if an expression is a tail call
bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) { bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) {
// Check if this is a direct function call (no operations on the result) // Check if this is a direct function call (no operations on the result)
@ -456,9 +679,6 @@ std::vector<sptr(Stmt)> Parser::block()
sptr(Expr) Parser::finishCall(sptr(Expr) callee) { sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
std::vector<sptr(Expr)> arguments; std::vector<sptr(Expr)> arguments;
// Consume the opening parenthesis
consume(OPEN_PAREN, "Expected '(' after function name.");
// Parse arguments if there are any // Parse arguments if there are any
if (!check(CLOSE_PAREN)) { if (!check(CLOSE_PAREN)) {
do { do {
@ -470,6 +690,14 @@ sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
return msptr(CallExpr)(callee, paren, arguments); return msptr(CallExpr)(callee, paren, arguments);
} }
sptr(Expr) Parser::finishArrayIndex(sptr(Expr) array) {
sptr(Expr) index = expression();
Token bracket = consume(CLOSE_BRACKET, "Expected ']' after index.");
return msptr(ArrayIndexExpr)(array, index, bracket);
}
bool Parser::match(const std::vector<TokenType>& types) { bool Parser::match(const std::vector<TokenType>& types) {
for(TokenType t : types) for(TokenType t : types)
{ {

View File

@ -1,5 +1,5 @@
#include "../headers/Environment.h" #include "Environment.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
void Environment::assign(const Token& name, const Value& value) { void Environment::assign(const Token& name, const Value& value) {
auto it = variables.find(name.lexeme); auto it = variables.find(name.lexeme);
@ -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

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

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

View File

@ -0,0 +1,232 @@
#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);
}
}
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

@ -0,0 +1,268 @@
#include "RuntimeDiagnostics.h"
#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();
}
if(object.isNone()) {
return false;
}
if(object.isNumber()) {
return object.asNumber() != 0;
}
if(object.isString()) {
return object.asString().length() > 0;
}
return true;
}
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
// Handle none comparisons first
if (a.isNone() || b.isNone()) {
return a.isNone() && b.isNone();
}
// Handle same type comparisons
if (a.isNumber() && b.isNumber()) {
return a.asNumber() == b.asNumber();
}
if (a.isBoolean() && b.isBoolean()) {
return a.asBoolean() == b.asBoolean();
}
if (a.isString() && b.isString()) {
return a.asString() == b.asString();
}
if (a.isArray() && b.isArray()) {
const std::vector<Value>& arrA = a.asArray();
const std::vector<Value>& arrB = b.asArray();
if (arrA.size() != arrB.size()) {
return false;
}
for (size_t i = 0; i < arrA.size(); i++) {
if (!isEqual(arrA[i], arrB[i])) {
return false;
}
}
return true;
}
if (a.isFunction() && b.isFunction()) {
// Functions are equal only if they are the same object
return a.asFunction() == b.asFunction();
}
if (a.isBuiltinFunction() && b.isBuiltinFunction()) {
// Builtin functions are equal only if they are the same object
return a.asBuiltinFunction() == b.asBuiltinFunction();
}
// Cross-type comparisons that make sense
if (a.isNumber() && b.isBoolean()) {
// Numbers and booleans: 0 and false are equal, non-zero and true are equal
if (b.asBoolean()) {
return a.asNumber() != 0.0;
} else {
return a.asNumber() == 0.0;
}
}
if (a.isBoolean() && b.isNumber()) {
// Same as above, but reversed
if (a.asBoolean()) {
return b.asNumber() != 0.0;
} else {
return b.asNumber() == 0.0;
}
}
// For all other type combinations, return false
return false;
}
std::string RuntimeDiagnostics::stringify(Value object) {
if(object.isNone()) {
return "none";
}
else if(object.isNumber()) {
return formatNumber(object.asNumber());
}
else if(object.isString()) {
return object.asString();
}
else if(object.isBoolean()) {
return object.asBoolean() == 1 ? "true" : "false";
}
else if(object.isFunction()) {
return "<function " + object.asFunction()->name + ">";
}
else if(object.isBuiltinFunction()) {
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
}
else if(object.isArray()) {
return formatArray(object.asArray());
}
else if(object.isDict()) {
return formatDict(object.asDict());
}
throw std::runtime_error("Could not convert object to string");
}
std::string RuntimeDiagnostics::formatNumber(double value) {
double integral = value;
double fractional = std::modf(value, &integral);
std::stringstream ss;
if(std::abs(fractional) < std::numeric_limits<double>::epsilon()) {
ss << std::fixed << std::setprecision(0) << integral;
return ss.str();
}
else {
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << value;
std::string str = ss.str();
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') {
str.pop_back();
}
return str;
}
}
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
std::string result = "[";
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0) result += ", ";
result += stringify(arr[i]);
}
result += "]";
return result;
}
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) {
std::string result = "{";
bool first = true;
for (const auto& pair : dict) {
if (!first) result += ", ";
result += "\"" + pair.first + "\": " + stringify(pair.second);
first = false;
}
result += "}";
return result;
}
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
// This is more conservative to prevent dangling pointer issues
functions.erase(
std::remove_if(functions.begin(), functions.end(),
[](const std::shared_ptr<BuiltinFunction>& func) {
return func.use_count() == 1; // Only referenced by this vector, nowhere else
}),
functions.end()
);
}
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
thunks.erase(
std::remove_if(thunks.begin(), thunks.end(),
[](const std::shared_ptr<Thunk>& thunk) {
return thunk.use_count() == 1; // Only referenced by this vector, nowhere else
}),
thunks.end()
);
}
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks) {
// More aggressive cleanup when breaking array references
functions.erase(
std::remove_if(functions.begin(), functions.end(),
[](const std::shared_ptr<BuiltinFunction>& func) {
return func.use_count() <= 2; // More aggressive than == 1
}),
functions.end()
);
thunks.erase(
std::remove_if(thunks.begin(), thunks.end(),
[](const std::shared_ptr<Thunk>& thunk) {
return thunk.use_count() <= 2; // More aggressive than == 1
}),
thunks.end()
);
}
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
std::vector<std::shared_ptr<Function>>& functions,
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

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

View File

@ -1,4 +1,4 @@
#include "../headers/Value.h" #include "Value.h"
// Global constants for common values (no heap allocation) // Global constants for common values (no heap allocation)
const Value NONE_VALUE = Value(); const Value NONE_VALUE = Value();

View File

@ -0,0 +1,848 @@
#include "BobStdLib.h"
#include "Interpreter.h"
#include "ErrorReporter.h"
#include "Lexer.h"
#include "Parser.h"
#include <chrono>
#include <thread>
#include <ctime>
#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",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
return Value(interpreter.stringify(args[0]));
});
env->define("toString", Value(toStringFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toStringFunc);
// Create a built-in print function
auto printFunc = std::make_shared<BuiltinFunction>("print",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Use the interpreter's stringify function
std::cout << interpreter.stringify(args[0]) << '\n';
return NONE_VALUE;
});
env->define("print", Value(printFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(printFunc);
// Create a built-in printRaw function (no newline, for ANSI escape sequences)
auto printRawFunc = std::make_shared<BuiltinFunction>("printRaw",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Print without newline and flush immediately for ANSI escape sequences
std::cout << interpreter.stringify(args[0]) << std::flush;
return NONE_VALUE;
});
env->define("printRaw", Value(printRawFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(printRawFunc);
// Create a built-in len function for arrays and strings
auto lenFunc = std::make_shared<BuiltinFunction>("len",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (args[0].isArray()) {
return Value(static_cast<double>(args[0].asArray().size()));
} else if (args[0].isString()) {
return Value(static_cast<double>(args[0].asString().length()));
} else if (args[0].isDict()) {
return Value(static_cast<double>(args[0].asDict().size()));
} else {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"len() can only be used on arrays, strings, and dictionaries", "", true);
}
throw std::runtime_error("len() can only be used on arrays, strings, and dictionaries");
}
});
env->define("len", Value(lenFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(lenFunc);
// Create a built-in push function for arrays
auto pushFunc = std::make_shared<BuiltinFunction>("push",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() < 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected at least 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected at least 2 arguments but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isArray()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"First argument to push() must be an array", "", true);
}
throw std::runtime_error("First argument to push() must be an array");
}
// Get the array and modify it in place
std::vector<Value>& arr = args[0].asArray();
// Add all arguments except the first one (which is the array)
for (size_t i = 1; i < args.size(); i++) {
arr.push_back(args[i]);
}
return args[0]; // Return the modified array
});
env->define("push", Value(pushFunc));
interpreter.addBuiltinFunction(pushFunc);
// Create a built-in pop function for arrays
auto popFunc = std::make_shared<BuiltinFunction>("pop",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isArray()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"pop() can only be used on arrays", "", true);
}
throw std::runtime_error("pop() can only be used on arrays");
}
std::vector<Value>& arr = args[0].asArray();
if (arr.empty()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Cannot pop from empty array", "", true);
}
throw std::runtime_error("Cannot pop from empty array");
}
// Get the last element and remove it from the array
Value lastElement = arr.back();
arr.pop_back();
return lastElement; // Return the popped element
});
env->define("pop", Value(popFunc));
interpreter.addBuiltinFunction(popFunc);
// Create a built-in assert function
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 && args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
}
// Simple truthy check without calling interpreter.isTruthy
bool isTruthy = false;
if (args[0].isBoolean()) {
isTruthy = args[0].asBoolean();
} else if (args[0].isNone()) {
isTruthy = false;
} else {
isTruthy = true; // Numbers, strings, functions are truthy
}
if (!isTruthy) {
std::string message = "Assertion failed: condition is false";
if (args.size() == 2) {
if (args[1].isString()) {
message += " - " + std::string(args[1].asString());
}
}
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
}
throw std::runtime_error(message);
}
return NONE_VALUE;
});
env->define("assert", Value(assertFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(assertFunc);
// Create a built-in time function (returns microseconds since Unix epoch)
auto timeFunc = std::make_shared<BuiltinFunction>("time",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
return Value(static_cast<double>(microseconds));
});
env->define("time", Value(timeFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(timeFunc);
// Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() > 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
}
// Optional prompt
if (args.size() == 1) {
std::cout << interpreter.stringify(args[0]);
}
// Get user input
std::string userInput;
std::getline(std::cin, userInput);
return Value(userInput);
});
env->define("input", Value(inputFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(inputFunc);
// Create a built-in type function
auto typeFunc = std::make_shared<BuiltinFunction>("type",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
std::string typeName;
if (args[0].isNumber()) {
typeName = "number";
} else if (args[0].isString()) {
typeName = "string";
} else if (args[0].isBoolean()) {
typeName = "boolean";
} else if (args[0].isNone()) {
typeName = "none";
} else if (args[0].isFunction()) {
typeName = "function";
} else if (args[0].isBuiltinFunction()) {
typeName = "builtin_function";
} else if (args[0].isArray()) {
typeName = "array";
} else if (args[0].isDict()) {
typeName = "dict";
} else {
typeName = "unknown";
}
return Value(typeName);
});
env->define("type", Value(typeFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(typeFunc);
// Create a built-in toNumber function for string-to-number conversion
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
[](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
return NONE_VALUE; // Return none for wrong argument count
}
if (!args[0].isString()) {
return NONE_VALUE; // Return none for wrong type
}
std::string str = args[0].asString();
// Remove leading/trailing whitespace
str.erase(0, str.find_first_not_of(" \t\n\r"));
str.erase(str.find_last_not_of(" \t\n\r") + 1);
if (str.empty()) {
return NONE_VALUE; // Return none for empty string
}
try {
double value = std::stod(str);
return Value(value);
} catch (const std::invalid_argument&) {
return NONE_VALUE; // Return none for invalid conversion
} catch (const std::out_of_range&) {
return NONE_VALUE; // Return none for out of range
}
});
env->define("toNumber", Value(toNumberFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toNumberFunc);
// Create a built-in toInt function for float-to-integer conversion
auto toIntFunc = std::make_shared<BuiltinFunction>("toInt",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isNumber()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"toInt() can only be used on numbers", "", true);
}
throw std::runtime_error("toInt() can only be used on numbers");
}
// Convert to integer by truncating (same as | 0)
double value = args[0].asNumber();
return Value(static_cast<double>(static_cast<long long>(value)));
});
env->define("toInt", Value(toIntFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toIntFunc);
// Create a built-in toBoolean function for explicit boolean conversion
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Use the same logic as isTruthy() for consistency
Value value = args[0];
if (value.isNone()) {
return Value(false);
}
if (value.isBoolean()) {
return value; // Already a boolean
}
if (value.isNumber()) {
return Value(value.asNumber() != 0.0);
}
if (value.isString()) {
return Value(!value.asString().empty());
}
// For any other type (functions, etc.), consider them truthy
return Value(true);
});
env->define("toBoolean", Value(toBooleanFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toBooleanFunc);
// Create a built-in exit function to terminate the program
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
[](std::vector<Value> args, int line, int column) -> Value {
int exitCode = 0; // Default exit code
if (args.size() > 0) {
if (args[0].isNumber()) {
exitCode = static_cast<int>(args[0].asNumber());
}
// If not a number, just use default exit code 0
}
std::exit(exitCode);
});
env->define("exit", Value(exitFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(exitFunc);
// Create a built-in sleep function for animations and timing
auto sleepFunc = std::make_shared<BuiltinFunction>("sleep",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isNumber()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"sleep() argument must be a number", "", true);
}
throw std::runtime_error("sleep() argument must be a number");
}
double seconds = args[0].asNumber();
if (seconds < 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"sleep() argument cannot be negative", "", true);
}
throw std::runtime_error("sleep() argument cannot be negative");
}
// Convert to milliseconds and sleep
int milliseconds = static_cast<int>(seconds * 1000);
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
return NONE_VALUE;
});
env->define("sleep", Value(sleepFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(sleepFunc);
// Create a built-in random function
auto randomFunc = std::make_shared<BuiltinFunction>("random",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
// Seed the random number generator if not already done
static bool seeded = false;
if (!seeded) {
srand(static_cast<unsigned int>(time(nullptr)));
seeded = true;
}
return Value(static_cast<double>(rand()) / RAND_MAX);
});
env->define("random", Value(randomFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(randomFunc);
// Create a built-in eval function (like Python's eval)
auto evalFunc = std::make_shared<BuiltinFunction>("eval",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "Invalid Arguments",
"eval expects exactly 1 argument (string)", "eval");
}
throw std::runtime_error("eval expects exactly 1 argument");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "Invalid Type",
"eval argument must be a string", "eval");
}
throw std::runtime_error("eval argument must be a string");
}
std::string code = args[0].asString();
try {
// Create a new lexer for the code string
Lexer lexer;
lexer.setErrorReporter(errorReporter);
std::vector<Token> tokens = lexer.Tokenize(code);
// Create a new parser
Parser parser(tokens);
parser.setErrorReporter(errorReporter);
std::vector<std::shared_ptr<Stmt>> statements = parser.parse();
// Execute the statements in the current environment
// Note: This runs in the current scope, so variables are shared
interpreter.interpret(statements);
// For now, return NONE_VALUE since we don't have a way to get the last expression value
// In a more sophisticated implementation, we'd track the last expression result
return NONE_VALUE;
} catch (const std::exception& e) {
if (errorReporter) {
errorReporter->reportError(line, column, "Eval Error",
"Failed to evaluate code: " + std::string(e.what()), code);
}
throw std::runtime_error("eval failed: " + std::string(e.what()));
}
});
env->define("eval", Value(evalFunc));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(evalFunc);
// Create a built-in keys function for dictionaries
auto keysFunc = std::make_shared<BuiltinFunction>("keys",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isDict()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"keys() can only be used on dictionaries", "", true);
}
throw std::runtime_error("keys() can only be used on dictionaries");
}
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
std::vector<Value> keys;
for (const auto& pair : dict) {
keys.push_back(Value(pair.first));
}
return Value(keys);
});
env->define("keys", Value(keysFunc));
interpreter.addBuiltinFunction(keysFunc);
// Create a built-in values function for dictionaries
auto valuesFunc = std::make_shared<BuiltinFunction>("values",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isDict()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"values() can only be used on dictionaries", "", true);
}
throw std::runtime_error("values() can only be used on dictionaries");
}
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
std::vector<Value> values;
for (const auto& pair : dict) {
values.push_back(pair.second);
}
return Value(values);
});
env->define("values", Value(valuesFunc));
interpreter.addBuiltinFunction(valuesFunc);
// Create a built-in has function for dictionaries
auto hasFunc = std::make_shared<BuiltinFunction>("has",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isDict()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"First argument to has() must be a dictionary", "", true);
}
throw std::runtime_error("First argument to has() must be a dictionary");
}
if (!args[1].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Second argument to has() must be a string", "", true);
}
throw std::runtime_error("Second argument to has() must be a string");
}
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
std::string key = args[1].asString();
return Value(dict.find(key) != dict.end());
});
env->define("has", Value(hasFunc));
interpreter.addBuiltinFunction(hasFunc);
// Create a built-in readFile function
auto readFileFunc = std::make_shared<BuiltinFunction>("readFile",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"readFile() argument must be a string", "", true);
}
throw std::runtime_error("readFile() argument must be a string");
}
std::string filename = args[0].asString();
std::ifstream file(filename);
if (!file.is_open()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Could not open file: " + filename, "", true);
}
throw std::runtime_error("Could not open file: " + filename);
}
std::stringstream buffer;
buffer << file.rdbuf();
file.close();
return Value(buffer.str());
});
env->define("readFile", Value(readFileFunc));
interpreter.addBuiltinFunction(readFileFunc);
// Create a built-in writeFile function
auto writeFileFunc = std::make_shared<BuiltinFunction>("writeFile",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"First argument to writeFile() must be a string", "", true);
}
throw std::runtime_error("First argument to writeFile() must be a string");
}
if (!args[1].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Second argument to writeFile() must be a string", "", true);
}
throw std::runtime_error("Second argument to writeFile() must be a string");
}
std::string filename = args[0].asString();
std::string content = args[1].asString();
std::ofstream file(filename);
if (!file.is_open()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Could not create file: " + filename, "", true);
}
throw std::runtime_error("Could not create file: " + filename);
}
file << content;
file.close();
return NONE_VALUE;
});
env->define("writeFile", Value(writeFileFunc));
interpreter.addBuiltinFunction(writeFileFunc);
// Create a built-in readLines function
auto readLinesFunc = std::make_shared<BuiltinFunction>("readLines",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"readLines() argument must be a string", "", true);
}
throw std::runtime_error("readLines() argument must be a string");
}
std::string filename = args[0].asString();
std::ifstream file(filename);
if (!file.is_open()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Could not open file: " + filename, "", true);
}
throw std::runtime_error("Could not open file: " + filename);
}
std::vector<Value> lines;
std::string line_content;
while (std::getline(file, line_content)) {
lines.push_back(Value(line_content));
}
file.close();
return Value(lines);
});
env->define("readLines", Value(readLinesFunc));
interpreter.addBuiltinFunction(readLinesFunc);
// Create a built-in fileExists function
auto fileExistsFunc = std::make_shared<BuiltinFunction>("fileExists",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
if (!args[0].isString()) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"fileExists() argument must be a string", "", true);
}
throw std::runtime_error("fileExists() argument must be a string");
}
std::string filename = args[0].asString();
std::ifstream file(filename);
bool exists = file.good();
file.close();
return Value(exists);
});
env->define("fileExists", Value(fileExistsFunc));
interpreter.addBuiltinFunction(fileExistsFunc);
// Create a built-in memoryUsage function (platform-specific, best effort)
auto memoryUsageFunc = std::make_shared<BuiltinFunction>("memoryUsage",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
// Platform-specific memory usage detection
size_t memoryBytes = 0;
#if defined(__APPLE__) && defined(__MACH__)
// macOS
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
memoryBytes = info.resident_size;
}
#elif defined(__linux__)
// Linux - read from /proc/self/status
std::ifstream statusFile("/proc/self/status");
std::string line;
while (std::getline(statusFile, line)) {
if (line.substr(0, 6) == "VmRSS:") {
std::istringstream iss(line);
std::string label, value, unit;
iss >> label >> value >> unit;
memoryBytes = std::stoull(value) * 1024; // Convert KB to bytes
break;
}
}
#elif defined(_WIN32)
// Windows
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
memoryBytes = pmc.WorkingSetSize;
}
#endif
// Return memory usage in MB for readability
double memoryMB = static_cast<double>(memoryBytes) / (1024.0 * 1024.0);
return Value(memoryMB);
});
env->define("memoryUsage", Value(memoryUsageFunc));
interpreter.addBuiltinFunction(memoryUsageFunc);
}

View File

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,25 @@
var counter = 0; var counter = 0;
func fib(n) { func fib(n) {
if (n <= 1) { return fibTail(n, 0, 1);
return n; }
func fibTail(n, a, b) {
if (n <= 0) {
return a;
} }
counter++; // Only increment for recursive calls counter++; // Only increment for recursive calls
return fib(n - 1) + fib(n - 2); return fibTail(n - 1, b, a + b);
} }
print("Fibonacci test:"); print("Fibonacci test:");
var fib_result = fib(30); var fib_result = fib(50000000);
print("Result: " + fib_result); print("Result: " + fib_result);
print("Counter: " + counter); print("Counter: " + counter);
func test_fib() {
}

53
tests.bob Normal file
View File

@ -0,0 +1,53 @@
var a = [];
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);});
}
}
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...");

View File

@ -1,5 +1,5 @@
// //
// Created by Bobby Lucero on 5/21/23.
// //
#include <iostream> #include <iostream>
#include <vector> #include <vector>