Compare commits

..

No commits in common. "development" and "master" have entirely different histories.

162 changed files with 2904 additions and 12805 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.gitignore vendored
View File

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

361
BOB_LANGUAGE_REFERENCE.md Normal file
View File

@ -0,0 +1,361 @@
# 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.*

View File

@ -1,163 +0,0 @@
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 CONFIGURE_DEPENDS "src/sources/runtime/*.cpp")
file(GLOB_RECURSE BOB_PARSING_SOURCES CONFIGURE_DEPENDS "src/sources/parsing/*.cpp")
file(GLOB_RECURSE BOB_STDLIB_SOURCES CONFIGURE_DEPENDS "src/sources/stdlib/*.cpp")
file(GLOB_RECURSE BOB_BUILTIN_SOURCES CONFIGURE_DEPENDS "src/sources/builtinModules/*.cpp")
file(GLOB_RECURSE BOB_CLI_SOURCES CONFIGURE_DEPENDS "src/sources/cli/*.cpp")
# All source files
set(BOB_ALL_SOURCES
${BOB_RUNTIME_SOURCES}
${BOB_PARSING_SOURCES}
${BOB_STDLIB_SOURCES}
${BOB_BUILTIN_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/builtinModules
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}")

48
Makefile Normal file
View File

@ -0,0 +1,48 @@
# 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,37 +1 @@
``` # 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
```

203
ROADMAP.md Normal file
View File

@ -0,0 +1,203 @@
# 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

@ -1,474 +0,0 @@
# 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, and Dictionaries: Method style (preferred)
```go
[1, 2, 3].len(); // 3
"hello".len(); // 5
var a = [1, 2]; a.push(3); // a is now [1, 2, 3]
var v = a.pop(); // v == 3
var d = {"a": 1, "b": 2};
d.len(); // 2
d.keys(); // ["a", "b"]
d.values(); // [1, 2]
d.has("a"); // true
```
Note: Global forms like `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)` have been removed. Use method style.
### Numbers
```go
toInt(3.9); // 3 (global)
(3.9).toInt(); // 3 (method on number)
```
### Utility
```go
assert(condition, "message"); // Testing
time(); // Current time in microseconds
sleep(1.5); // Sleep for 1.5 seconds
rand.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");
```
## Standard Library Reference
The following built-ins are available by default. Unless specified, functions throw on invalid argument counts/types.
- print(x): prints x with newline
- printRaw(x): prints x without newline
- input(prompt?): reads a line from stdin (optional prompt)
- toString(x): returns string representation
- toNumber(s): parses string to number or returns none
- toInt(n): truncates number to integer
- toBoolean(x): converts to boolean using truthiness rules
- type(x): returns the type name as string
- len(x) / x.len(): length of array/string/dict
- push(arr, ...values) / arr.push(...values): appends values to array in place, returns arr
- pop(arr) / arr.pop(): removes and returns last element
- keys(dict) / dict.keys(): returns array of keys
- values(dict) / dict.values(): returns array of values
- has(dict, key) / dict.has(key): returns true if key exists
- readFile(path): returns entire file contents as string
- writeFile(path, content): writes content to file
- readLines(path): returns array of lines
- fileExists(path): boolean
- time(): microseconds since Unix epoch
- sleep(seconds): pauses execution
- rand.random(): float in [0,1)
- eval(code): executes code string in current environment
- exit(code?): terminates the program
Notes:
- Arrays support properties: length, first, last, empty
- Dicts support properties: length, empty, keys, values
- Method-style builtins on arrays/strings/dicts are preferred; global forms remain for compatibility.
## Advanced Features
### Classes (Phase 1)
```go
// Declare a class with fields and methods
class Person {
var name;
var age;
// Methods can use implicit `this`
func setName(n) { this.name = n; }
func greet() { print("Hi, I'm " + this.name); }
}
// Construct via the class name
var p = Person();
p.setName("Bob");
p.greet();
// Fields are stored on the instance (a dictionary under the hood)
p.age = 30;
```
Notes:
- Instances are plain dictionaries; methods are shared functions placed on the instance.
- On a property call like `obj.method(...)`, the interpreter injects `this = obj` into the call frame (no argument injection).
- Taking a method reference and calling it later does not autobind `this`; call via `obj.method(...)` when needed.
### Extensions (Builtins and Classes)
Extend existing types (including builtins) with new methods:
```go
extension array {
func sum() {
var i = 0; var s = 0;
while (i < len(this)) { s = s + this[i]; i = i + 1; }
return s;
}
}
extension dict { func size() { return len(this); } }
extension string { func shout() { return toString(this) + "!"; } }
extension any { func tag() { return "<" + type(this) + ">"; } }
assert([1,2,3].sum() == 6);
assert({"a":1,"b":2}.size() == 2);
assert("hi".shout() == "hi!");
assert(42.tag() == "<number>");
```
Notes:
- Lookup order for `obj.method(...)`: instance dictionary → class extensions (for user classes) → builtin extensions (string/array/dict) → `any`.
- `this` is injected for property calls.
### 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 < people.len(); 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 < lines.len(); i = i + 1) {
var line = lines[i];
if (line.len() > 0) {
processed.push("Processed: " + line);
}
}
var output = "";
for (var i = 0; i < processed.len(); i = i + 1) {
output = output + processed[i];
if (i < processed.len() - 1) {
output = output + "\n";
}
}
writeFile("output.txt", output);
```
---
*For more examples, see the comprehensive test suite in `test_bob_language.bob`*

View File

@ -1,248 +0,0 @@
# 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
```

View File

@ -1,113 +0,0 @@
Embedding Bob: Public API Guide
================================
This document explains how to embed the Bob interpreter in your C++ application, register custom modules, and control sandbox policies.
Quick Start
-----------
```cpp
#include "cli/bob.h"
#include "ModuleRegistry.h"
int main() {
Bob bob;
// Optional: configure policies or modules before first use
bob.setBuiltinModulePolicy(true); // allow builtin modules (default)
bob.setBuiltinModuleDenyList({/* e.g., "sys" */});
// Register a custom builtin module called "demo"
bob.registerModule("demo", [](ModuleRegistry::ModuleBuilder& m) {
m.fn("hello", [](std::vector<Value> args, int, int) -> Value {
std::string who = (args.size() >= 1 && args[0].isString()) ? args[0].asString() : "world";
return Value(std::string("hello ") + who);
});
m.val("meaning", Value(42.0));
});
// Evaluate code from a string
bob.evalString("import demo; print(demo.hello(\"Bob\"));", "<host>");
// Evaluate a file (imports inside resolve relative to the file's directory)
bob.evalFile("script.bob");
}
```
API Overview
------------
Bob exposes a single high-level object with a minimal, consistent API. It self-manages an internal interpreter and applies configuration on first use.
- Program execution
- `bool evalString(const std::string& code, const std::string& filename = "<eval>")`
- `bool evalFile(const std::string& path)`
- `void runFile(const std::string& path)` (CLI convenience delegates to `evalFile`)
- `void runPrompt()` (interactive CLI delegates each line to `evalString`)
- Module registration and sandboxing
- `void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init)`
- `void setBuiltinModulePolicy(bool allow)`
- `void setBuiltinModuleAllowList(const std::vector<std::string>& allowed)`
- `void setBuiltinModuleDenyList(const std::vector<std::string>& denied)`
- Global environment helpers
- `bool defineGlobal(const std::string& name, const Value& value)`
- `bool tryGetGlobal(const std::string& name, Value& out) const`
All configuration calls are safe to use before any evaluation they are queued and applied automatically when the interpreter is first created.
Registering Custom Builtin Modules
----------------------------------
Use the builder convenience to create a module:
```cpp
bob.registerModule("raylib", [](ModuleRegistry::ModuleBuilder& m) {
m.fn("init", [](std::vector<Value> args, int line, int col) -> Value {
// call into your library here; validate args, return Value
return NONE_VALUE;
});
m.val("VERSION", Value(std::string("5.0")));
});
```
At runtime:
```bob
import raylib;
raylib.init();
print(raylib.VERSION);
```
Notes
-----
- Modules are immutable, first-class objects. `type(module)` is "module" and `toString(module)` prints `<module 'name'>`.
- Reassigning a module binding or setting module properties throws an error.
Builtin Modules and Sandboxing
------------------------------
- Builtin modules (e.g., `sys`) are registered during interpreter construction.
- File imports are always resolved relative to the importing file's directory.
- Policies:
- `setBuiltinModulePolicy(bool allow)` enable/disable all builtin modules.
- `setBuiltinModuleAllowList(vector<string>)` allow only listed modules (deny others).
- `setBuiltinModuleDenyList(vector<string>)` explicitly deny listed modules.
- Denied/disabled modules are cloaked: `import name` reports "Module not found".
Error Reporting
---------------
- `evalString`/`evalFile` set file context for error reporting so line/column references point to the real source.
- Both return `true` on success and `false` if execution failed (errors are reported via the internal error reporter).
CLI vs Embedding
----------------
- CLI builds include `main.cpp` (entry point), which uses `Bob::runFile` or `Bob::runPrompt`.
- Embedded hosts do not use `main.cpp`; instead they instantiate `Bob` and call `evalString`/`evalFile` directly.

View File

@ -1,312 +0,0 @@
# 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**: `rand.random()` (properly seeded), `eval.eval()`, `sys.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*

30
benchmark.py Normal file
View File

@ -0,0 +1,30 @@
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

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

View File

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

View File

@ -1,97 +0,0 @@
# 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

@ -1,132 +0,0 @@
# 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`
- Classes and OOP: `class`, `extends`, `extension`, `this`, `super`
- Logical operators: `and`, `or`, `not`
### Built-in Functions
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `toInt()`, `time()`, `sleep()`, `printRaw()`
- Arrays/Dictionaries (preferred method style): `arr.len()`, `arr.push(...)`, `arr.pop()`, `dict.len()`, `dict.keys()`, `dict.values()`, `dict.has()`
- Global forms still available: `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)`
- Misc: `rand.random()`, `eval.eval()`
### Data Types
- 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 (method style)
var numbers = [1, 2, 3, 4, 5];
print("Array length: " + numbers.len());
numbers.push(6);
print("Popped: " + numbers.pop());
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)

View File

@ -1,44 +0,0 @@
#!/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

@ -1,111 +0,0 @@
// 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: " + numbers.len());
print("First element: " + numbers[0]);
numbers[2] = 99; // Array assignment
numbers.push(6); // Add element
var lastElement = numbers.pop(); // 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
import rand; var randomValue = rand.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

@ -1,66 +0,0 @@
#!/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

@ -1,35 +0,0 @@
{
"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|class|extension)\\b.*$",
"decreaseIndentPattern": "^\\s*[})]"
},
"folding": {
"markers": {
"start": "^\\s*//\\s*#?region\\b",
"end": "^\\s*//\\s*#?endregion\\b"
}
}
}

View File

@ -1,27 +0,0 @@
#!/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

@ -1,74 +0,0 @@
{
"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.5.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

@ -1,29 +0,0 @@
#!/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

@ -1,488 +0,0 @@
{
"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"
},
"Class Declaration": {
"prefix": "class",
"body": [
"class ${1:ClassName} ${2:extends ${3:Parent}} {",
" var ${4:field} = ${5:none};",
" func init(${6:args}) {",
" this.${4:field} = ${7:value};",
" }",
" func ${8:method}(${9:params}) {",
" $0",
" }",
"}"
],
"description": "Create a class with optional extends, fields, init, and method"
},
"Extension Block": {
"prefix": "extension",
"body": [
"extension ${1:Target} {",
" func ${2:method}(${3:params}) {",
" $0",
" }",
"}"
],
"description": "Create an extension for a class or builtin (string, array, dict, number, any)"
}
"Print Statement": {
"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": [
"import rand; var randomValue = rand.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

@ -1,104 +0,0 @@
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

@ -1,273 +0,0 @@
{
"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|class|extends|extension|this|super)\\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

@ -1,58 +0,0 @@
// 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

@ -1,79 +0,0 @@
{
"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

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

View File

@ -1,108 +0,0 @@
# 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

@ -1,13 +0,0 @@
class A {
var inner = 10;
func test(){
print(this.inner);
}
}
func hello(){
print("hello");
}

View File

@ -1,56 +0,0 @@
// ===========================================================
// 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!");

0
headers/AST.h Normal file
View File

View File

@ -3,34 +3,21 @@
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
#include <memory> #include <memory>
#include <unordered_set>
#include "Value.h" #include "Value.h"
#include "Lexer.h" #include "Lexer.h"
// Forward declaration // Forward declaration
class ErrorReporter; class ErrorReporter;
struct Environment { class 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;
} }
ErrorReporter* getErrorReporter() const { return errorReporter; }
// Optimized define with inline // Optimized define with inline
inline void define(const std::string& name, const Value& value) { inline void define(const std::string& name, const Value& value) {
@ -43,18 +30,19 @@ public:
// Enhanced get with error reporting // Enhanced get with error reporting
Value get(const Token& name); Value get(const Token& name);
// Prune heavy containers in a snapshot to avoid capture cycles // Get by string name with error reporting
void pruneForClosureCapture(); Value get(const std::string& name);
std::shared_ptr<Environment> getParent() const { return parent; } std::shared_ptr<Environment> getParent() const { return parent; }
// Export all variables (shallow copy) for module namespace inline void clear() { variables.clear(); }
std::unordered_map<std::string, Value> getAll() const { return variables; }
// Set parent environment for TCO environment reuse
inline void setParent(std::shared_ptr<Environment> newParent) {
parent = newParent;
}
private: private:
std::unordered_map<std::string, Value> variables; std::unordered_map<std::string, Value> variables;
std::shared_ptr<Environment> parent; std::shared_ptr<Environment> parent;
ErrorReporter* errorReporter; ErrorReporter* errorReporter;
std::unordered_set<std::string> constBindings;
}; };

View File

@ -34,9 +34,6 @@ private:
std::string currentFileName; std::string currentFileName;
std::vector<std::string> callStack; std::vector<std::string> callStack;
bool hadError = false; bool hadError = false;
// Support nested sources (e.g., eval of external files)
std::vector<std::vector<std::string>> sourceStack;
std::vector<std::string> fileNameStack;
public: public:
ErrorReporter() = default; ErrorReporter() = default;
@ -51,9 +48,6 @@ 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);
@ -61,11 +55,6 @@ public:
void pushCallStack(const std::string& functionName); void pushCallStack(const std::string& functionName);
void popCallStack(); void popCallStack();
// Source push/pop for eval
void pushSource(const std::string& source, const std::string& fileName);
void popSource();
const std::string& getCurrentFileName() const { return currentFileName; }
private: private:
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true); void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
void displayCallStack(const std::vector<std::string>& callStack); void displayCallStack(const std::vector<std::string>& callStack);

View File

@ -1,4 +1,6 @@
// Expression AST nodes for Bob language //
// Created by Bobby Lucero on 5/21/23.
//
#pragma once #pragma once
#include <iostream> #include <iostream>
@ -11,16 +13,6 @@
// 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;
@ -43,15 +35,6 @@ 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> {
@ -169,101 +152,3 @@ 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()));
}
};

72
headers/Interpreter.h Normal file
View File

@ -0,0 +1,72 @@
#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

@ -6,7 +6,6 @@
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,
@ -16,18 +15,13 @@ 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, KW_BOOL, IDENTIFIER, STRING, NUMBER, BOOL,
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR, AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE, WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
IMPORT, FROM, AS,
TRY, CATCH, FINALLY, THROW,
// Compound assignment operators // Compound assignment operators
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
@ -39,7 +33,6 @@ 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",
@ -49,16 +42,12 @@ 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", "KW_BOOL", "IDENTIFIER", "STRING", "NUMBER", "BOOL",
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR", "AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE", "WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
"IMPORT", "FROM", "AS",
"TRY", "CATCH", "FINALLY", "THROW",
// Compound assignment operators // Compound assignment operators
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL", "PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
@ -78,24 +67,12 @@ 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},
{"extends", EXTENDS},
{"extension", EXTENSION},
{"super", SUPER}, {"super", SUPER},
{"this", THIS}, {"this", THIS},
{"none", NONE}, {"none", NONE},
{"return", RETURN}, {"return", RETURN},
{"break", BREAK},
{"continue", CONTINUE},
{"import", IMPORT},
{"from", FROM},
{"as", AS},
{"try", TRY},
{"catch", CATCH},
{"finally", FINALLY},
{"throw", THROW},
}; };
struct Token struct Token

View File

@ -24,7 +24,6 @@ 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();
@ -57,45 +56,20 @@ 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> classDeclaration();
std::shared_ptr<Stmt> extensionDeclaration();
std::shared_ptr<Stmt> tryStatement();
std::shared_ptr<Stmt> throwStatement();
std::shared_ptr<Stmt> importStatement();
std::shared_ptr<Stmt> fromImportStatement();
std::shared_ptr<Stmt> varDeclaration(); std::shared_ptr<Stmt> varDeclaration();
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++; }

120
headers/Statement.h Normal file
View File

@ -0,0 +1,120 @@
#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

@ -7,7 +7,7 @@
class Interpreter; class Interpreter;
class ErrorReporter; class ErrorReporter;
class BobStdLib { class StdLib {
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);
}; };

View File

@ -10,22 +10,51 @@
struct Stmt; struct Stmt;
struct Environment; struct Environment;
struct Function struct Object
{
virtual ~Object(){};
};
struct Number : Object
{
double value;
explicit Number(double value) : value(value) {}
};
struct String : Object
{
std::string value;
explicit String(std::string str) : value(str) {}
~String(){
}
};
struct Boolean : Object
{
bool value;
explicit Boolean(bool value) : value(value) {}
};
struct None : public Object
{
};
struct Function : public Object
{ {
const std::string name; const std::string name;
const std::vector<std::string> params; const std::vector<std::string> params;
const std::vector<std::shared_ptr<Stmt>> body; const std::vector<std::shared_ptr<Stmt>> body;
const std::shared_ptr<Environment> closure; const std::shared_ptr<Environment> closure;
const std::string ownerClass; // empty for non-methods
Function(std::string name, std::vector<std::string> params, Function(std::string name, std::vector<std::string> params,
std::vector<std::shared_ptr<Stmt>> body, std::vector<std::shared_ptr<Stmt>> body,
std::shared_ptr<Environment> closure, std::shared_ptr<Environment> closure)
std::string ownerClass = "") : name(name), params(params), body(body), closure(closure) {}
: name(name), params(params), body(body), closure(closure), ownerClass(ownerClass) {}
}; };
struct BuiltinFunction struct BuiltinFunction : public Object
{ {
const std::string name; const std::string name;
const std::function<Value(std::vector<Value>, int, int)> func; const std::function<Value(std::vector<Value>, int, int)> func;

274
headers/Value.h Normal file
View File

@ -0,0 +1,274 @@
#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;

29
headers/bob.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include "../headers/Lexer.h"
#include "../headers/Interpreter.h"
#include "../headers/helperFunctions/ShortHands.h"
#include "../headers/ErrorReporter.h"
#define VERSION "0.0.1"
class Bob
{
public:
Lexer lexer;
sptr(Interpreter) interpreter;
ErrorReporter errorReporter;
~Bob() = default;
public:
void runFile(const std::string& path);
void runPrompt();
private:
void run(std::string source);
};

View File

@ -3,9 +3,8 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <bitset> #include <bitset>
#include <cctype>
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) { inline std::vector<std::string> splitString(const std::string& input, 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;
@ -57,9 +56,9 @@ inline bool isHexDigit(char c) {
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c))); return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
} }
inline unsigned long long binaryStringToLong(const std::string& binaryString) { inline u_long binaryStringToLong(const std::string& binaryString) {
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
unsigned long long result = 0; u_long result = 0;
for (char ch : binaryDigits) { for (char ch : binaryDigits) {
result <<= 1; result <<= 1;
result += (ch - '0'); result += (ch - '0');

View File

@ -1,95 +0,0 @@
# 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

@ -1,173 +0,0 @@
// 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);
stringData.push({
"original": str,
"upper": str, // Bob doesn't have toUpper, but test string storage
"length": str.len(),
"type": type(str)
});
}
print("Created " + stringData.len() + " 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);
conversions.push([
num, str, backToNum, intVal, boolVal,
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
]);
}
print("Created " + conversions.len() + " 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 = arr.len();
arr.push(i+3);
var popped = arr.pop();
var dictKeys = dict.keys();
var dictValues = dict.values();
var hasA = dict.has("a");
collections.push({
"array": arr,
"dict": dict,
"arrLen": arrLen,
"popped": popped,
"keys": dictKeys,
"values": dictValues,
"hasA": hasA
});
}
print("Created " + collections.len() + " 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);
evalResults.push({
"expr": expression,
"result": result,
"func": evalFunc,
"funcResult": evalFunc()
});
}
print("Created " + evalResults.len() + " 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);
fileData.push({
"filename": filename,
"exists": exists,
"content": readContent,
"lines": lines,
"lineCount": lines.len()
});
}
print("Created " + fileData.len() + " 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++) {
import rand as RLeak;
var rand1 = RLeak.random();
var rand2 = RLeak.random();
var sum = rand1 + rand2;
randomData.push({
"rand1": rand1,
"rand2": rand2,
"sum": sum,
"product": rand1 * rand2
});
}
print("Created " + randomData.len() + " 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

@ -1,114 +0,0 @@
// 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++) {
nestedArrays.push([
[i, i+1, i+2],
[i*2, i*3, i*4],
[[i, [i+1, [i+2]]], i*5]
]);
}
print("Created " + nestedArrays.len() + " 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++) {
nestedDicts.push({
"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 " + nestedDicts.len() + " 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++) {
mixedStructures.push([
{"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 " + mixedStructures.len() + " 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}];
selfRef.push(item);
}
print("Created " + selfRef.len() + " self-referencing structures");
print("Memory: " + memoryUsage() + " MB");
input("Press Enter to clear self-ref structures...");
// Break cycles explicitly so reference counting can reclaim memory deterministically
for (var i = 0; i < selfRef.len(); i++) {
selfRef[i]["self"] = none;
}
selfRef = 123;
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 + " ";
}
stringCollections.push({
"content": longString,
"words": [longString, longString + "_copy", longString + "_backup"]
});
}
print("Created " + stringCollections.len() + " 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

@ -1,89 +0,0 @@
// 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++) {
recursiveFuncs.push(func() {
var capturedI = i;
return func() {
if (capturedI > 0) {
return capturedI * capturedI;
}
return 0;
};
});
}
print("Created " + recursiveFuncs.len() + " 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++) {
factories.push(func() {
var multiplier = i;
return func(x) {
return x * multiplier;
};
});
}
print("Created " + factories.len() + " 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++) {
deepNested.push(func() {
return func() {
return func() {
return func() {
return func() {
return i * 42;
};
};
};
};
});
}
print("Created " + deepNested.len() + " 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
circularFuncs.push([funcA, funcB, func() { return funcA; }]);
}
print("Created " + circularFuncs.len() + " 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

@ -1,145 +0,0 @@
// 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++) {
row.push({
"i": i,
"j": j,
"func": func() { return i * j; },
"data": [i, j, i+j]
});
}
nestedData.push(row);
}
print("Created " + nestedData.len() + "x" + nestedData[0].len() + " 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) {
accumulator.push({
"count": counter,
"func": func() { return counter * 2; },
"meta": ["item" + counter, counter % 100]
});
counter = counter + 1;
}
print("Accumulated " + accumulator.len() + " 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 {
doWhileFuncs.push(func() {
var captured = dwCounter;
return func() {
return captured * captured;
};
});
dwCounter = dwCounter + 1;
} while (dwCounter < 100000);
print("Created " + doWhileFuncs.len() + " 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
complexData.push({
"large": [
func() { return i; },
[i, i+1, i+2, i+3],
{"nested": {"deep": func() { return i*2; }}}
]
});
} else {
complexData.push(func() { return i; });
}
if (i > 450000 && complexData.len() > 350000) {
break; // Early exit
}
}
print("Complex loop created " + complexData.len() + " 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++) {
churnData.push([
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

@ -1,113 +0,0 @@
// 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]};
funcWithCollections.push(func() {
// Capture both collections
var localArray = capturedArray;
var localDict = capturedDict;
return func() {
return localArray.len() + localDict.len();
};
});
}
print("Created " + funcWithCollections.len() + " 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++) {
mixedContent.push([
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 " + mixedContent.len() + " 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; };
propObjects.push(obj);
}
print("Created " + propObjects.len() + " 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++) {
typeChains.push(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 < typeChains.len(); 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 < typeChains.len(); 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++) {
temp.push([
func() { return i; },
{"data": [i, i+1, func() { return i*2; }]}
]);
}
print("Round " + round + ": Created " + temp.len() + " 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 ===");

View File

@ -1,52 +0,0 @@
#!/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 Normal file

Binary file not shown.

51
source/Environment.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "../headers/Environment.h"
#include "../headers/ErrorReporter.h"
void Environment::assign(const Token& name, const Value& value) {
auto it = variables.find(name.lexeme);
if (it != variables.end()) {
it->second = value;
return;
}
if (parent != nullptr) {
parent->assign(name, value);
return;
}
if (errorReporter) {
errorReporter->reportError(name.line, name.column, "Runtime Error",
"Undefined variable '" + name.lexeme + "'", "");
}
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
}
Value Environment::get(const Token& name) {
auto it = variables.find(name.lexeme);
if (it != variables.end()) {
return it->second;
}
if (parent != nullptr) {
return parent->get(name);
}
if (errorReporter) {
errorReporter->reportError(name.line, name.column, "Runtime Error",
"Undefined variable '" + name.lexeme + "'", "");
}
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
}
Value Environment::get(const std::string& name) {
auto it = variables.find(name);
if (it != variables.end()) {
return it->second;
}
if (parent != nullptr) {
return parent->get(name);
}
throw std::runtime_error("Undefined variable '" + name + "'");
}

View File

@ -1,4 +1,4 @@
#include "ErrorReporter.h" #include "../headers/ErrorReporter.h"
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
#include <string> #include <string>
@ -56,33 +56,9 @@ void ErrorReporter::loadSource(const std::string& source, const std::string& fil
} }
} }
void ErrorReporter::pushSource(const std::string& source, const std::string& fileName) {
// Save current
sourceStack.push_back(sourceLines);
fileNameStack.push_back(currentFileName);
// Load new
loadSource(source, fileName);
}
void ErrorReporter::popSource() {
if (!sourceStack.empty()) {
sourceLines = sourceStack.back();
sourceStack.pop_back();
} else {
sourceLines.clear();
}
if (!fileNameStack.empty()) {
currentFileName = fileNameStack.back();
fileNameStack.pop_back();
} else {
currentFileName.clear();
}
}
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) { void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
hadError = true; hadError = true;
displaySourceContext(line, column, errorType, message, operator_, showArrow); displaySourceContext(line, column, errorType, message, operator_, showArrow);
std::cout.flush(); // Ensure output is flushed before any exception is thrown
} }
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) { void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
@ -98,8 +74,6 @@ void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
if (!context.fileName.empty()) { if (!context.fileName.empty()) {
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n"; std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
} else if (!currentFileName.empty()) {
std::cout << colorize("File: ", Colors::BOLD) << colorize(currentFileName, Colors::CYAN) << "\n";
} }
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) + std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +
@ -132,8 +106,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
return; return;
} }
static const int ERROR_DISPLAY_MAX_WIDTH = 65; int maxWidth = 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);
@ -146,7 +119,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, ERROR_DISPLAY_MAX_WIDTH); maxWidth = std::max(maxWidth, 65);
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";
@ -189,8 +162,7 @@ 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;
static const int CALL_STACK_MAX_WIDTH = 65; int maxWidth = 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);

5
source/Expression.cpp Normal file
View File

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

878
source/Interpreter.cpp Normal file
View File

@ -0,0 +1,878 @@
//
// 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,10 +1,10 @@
#include "Lexer.h" #include "../headers/Lexer.h"
#include "ErrorReporter.h" #include "../headers/ErrorReporter.h"
#include "helperFunctions/HelperFunctions.h" #include "../headers/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,16 +35,6 @@ 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});
@ -125,16 +115,6 @@ 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);
@ -363,14 +343,18 @@ std::vector<Token> Lexer::Tokenize(std::string source){
} }
if(!isNotation) { if(!isNotation) {
// Only treat '.' as part of the number if followed by a digit if (!src.empty() && src[0] == '.') {
if (src.size() > 1 && src[0] == '.' && std::isdigit(src[1])) { advance();
advance(); // consume '.' if (!src.empty() && std::isdigit(src[0])) {
num += '.'; num += '.';
while (!src.empty() && std::isdigit(src[0])) { while (!src.empty() && std::isdigit(src[0])) {
num += src[0]; num += src[0];
advance(); advance();
}
} else {
throw std::runtime_error("LEXER: malformed number at: " + std::to_string(this->line));
} }
} }
} }
else else
@ -437,7 +421,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
} }
} }
tokens.push_back({END_OF_FILE, "eof", line, column}); tokens.push_back({END_OF_FILE, "eof", line});
return tokens; return tokens;
} }
@ -489,11 +473,8 @@ 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':
@ -508,30 +489,8 @@ 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 std::runtime_error("Invalid escape character: " + std::string(1, c)); throw runtime_error("Invalid escape character: " + std::string(1, c));
} }
escapeMode = false; escapeMode = false;
} else if (c == '\\') { } else if (c == '\\') {
@ -539,7 +498,6 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
} else { } else {
output += c; output += c;
} }
i++;
} }
return output; return output;

554
source/Parser.cpp Normal file
View File

@ -0,0 +1,554 @@
//
// Created by Bobby Lucero on 5/26/23.
//
#include "../headers/Parser.h"
#include <stdexcept>
// Precedence
// to all the morons on facebook who don't know what pemdas is, fuck you
///////////////////////////////////////////
sptr(Expr) Parser::expression()
{
return assignment();
}
sptr(Expr) Parser::logical_or()
{
sptr(Expr) expr = logical_and();
while(match({OR}))
{
Token op = previous();
sptr(Expr) right = logical_and();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::logical_and()
{
sptr(Expr) expr = equality();
while(match({AND}))
{
Token op = previous();
sptr(Expr) right = equality();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
// bitwise_or now calls comparison (not bitwise_xor)
sptr(Expr) Parser::bitwise_or()
{
sptr(Expr) expr = bitwise_xor();
while(match({BIN_OR}))
{
Token op = previous();
sptr(Expr) right = bitwise_xor();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::bitwise_xor()
{
sptr(Expr) expr = bitwise_and();
while(match({BIN_XOR}))
{
Token op = previous();
sptr(Expr) right = bitwise_and();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::bitwise_and()
{
sptr(Expr) expr = shift();
while(match({BIN_AND}))
{
Token op = previous();
sptr(Expr) right = shift();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::shift()
{
sptr(Expr) expr = term();
while(match({BIN_SLEFT, BIN_SRIGHT}))
{
Token op = previous();
sptr(Expr) right = term();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::assignment()
{
sptr(Expr) expr = increment();
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}))
{
Token op = previous();
sptr(Expr) value = assignment();
if(std::dynamic_pointer_cast<VarExpr>(expr))
{
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
return msptr(AssignExpr)(name, op, value);
}
if (errorReporter) {
errorReporter->reportError(op.line, op.column, "Parse Error",
"Invalid assignment target", "");
}
throw std::runtime_error("Invalid assignment target.");
}
return expr;
}
sptr(Expr) Parser::increment()
{
return logical_or();
}
sptr(Expr) Parser::equality()
{
sptr(Expr) expr = comparison();
while(match({BANG_EQUAL, DOUBLE_EQUAL}))
{
Token op = previous();
sptr(Expr) right = comparison();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::comparison()
{
sptr(Expr) expr = bitwise_or();
while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL}))
{
Token op = previous();
sptr(Expr) right = bitwise_or();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::term()
{
sptr(Expr) expr = factor();
while(match({MINUS, PLUS}))
{
Token op = previous();
sptr(Expr) right = factor();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::factor()
{
sptr(Expr) expr = unary();
while(match({SLASH, STAR, PERCENT}))
{
Token op = previous();
sptr(Expr) right = unary();
expr = msptr(BinaryExpr)(expr, op, right);
}
return expr;
}
sptr(Expr) Parser::unary()
{
if(match({BANG, MINUS, BIN_NOT, PLUS_PLUS, MINUS_MINUS}))
{
Token op = previous();
sptr(Expr) right = unary();
// Handle prefix increment/decrement
if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) {
// Ensure the operand is a variable
if (!std::dynamic_pointer_cast<VarExpr>(right)) {
if (errorReporter) {
errorReporter->reportError(op.line, op.column, "Parse Error",
"Prefix increment/decrement can only be applied to variables", "");
}
throw std::runtime_error("Prefix increment/decrement can only be applied to variables.");
}
return msptr(IncrementExpr)(right, op, true); // true = prefix
}
return msptr(UnaryExpr)(op, right);
}
return postfix();
}
sptr(Expr) Parser::postfix()
{
sptr(Expr) expr = primary();
// Check for postfix increment/decrement
if (match({PLUS_PLUS, MINUS_MINUS})) {
Token oper = previous();
// Ensure the expression is a variable
if (!std::dynamic_pointer_cast<VarExpr>(expr)) {
if (errorReporter) {
errorReporter->reportError(oper.line, oper.column, "Parse Error",
"Postfix increment/decrement can only be applied to variables", "");
}
throw std::runtime_error("Postfix increment/decrement can only be applied to variables.");
}
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
}
return expr;
}
sptr(Expr) Parser::primary()
{
if(match({FALSE})) return msptr(LiteralExpr)("false", false, false, true);
if(match({TRUE})) return msptr(LiteralExpr)("true", false, false, true);
if(match({NONE})) return msptr(LiteralExpr)("none", false, true, false);
if(match({NUMBER})) return msptr(LiteralExpr)(previous().lexeme, true, false, false);
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
if(match( {IDENTIFIER})) {
if (check(OPEN_PAREN)) {
return finishCall(msptr(VarExpr)(previous()));
}
return msptr(VarExpr)(previous());
}
if(match({OPEN_PAREN}))
{
sptr(Expr) expr = expression();
consume(CLOSE_PAREN, "Expected ')' after expression on line " + std::to_string(peek().line));
if (check(OPEN_PAREN)) {
return finishCall(msptr(GroupingExpr)(expr));
}
return msptr(GroupingExpr)(expr);
}
if(match({FUNCTION})) {
return functionExpression();
}
if (errorReporter) {
errorReporter->reportError(peek().line, peek().column, "Parse Error",
"Expression expected", "");
}
throw std::runtime_error("Expression expected at: " + std::to_string(peek().line));
}
///////////////////////////////////////////
std::vector<sptr(Stmt)> Parser::parse() {
std::vector<sptr(Stmt)> statements;
while(!isAtEnd())
{
statements.push_back(declaration());
}
return statements;
}
sptr(Stmt) Parser::declaration()
{
try{
if(match({VAR})) return varDeclaration();
if(match({FUNCTION})) return functionDeclaration();
return statement();
}
catch(std::runtime_error& e)
{
sync();
throw std::runtime_error(e.what());
}
}
sptr(Stmt) Parser::varDeclaration()
{
Token name = consume(IDENTIFIER, "Expected variable name.");
sptr(Expr) initializer = msptr(LiteralExpr)("none", false, true, false);
if(match({EQUAL}))
{
initializer = expression();
}
consume(SEMICOLON, "Expected ';' after variable declaration.");
return msptr(VarStmt)(name, initializer);
}
sptr(Stmt) Parser::functionDeclaration()
{
Token name = consume(IDENTIFIER, "Expected function name.");
consume(OPEN_PAREN, "Expected '(' after function name.");
std::vector<Token> parameters;
if (!check(CLOSE_PAREN)) {
do {
parameters.push_back(consume(IDENTIFIER, "Expected parameter name."));
} while (match({COMMA}));
}
consume(CLOSE_PAREN, "Expected ')' after parameters.");
consume(OPEN_BRACE, "Expected '{' before function body.");
// Enter function scope
enterFunction();
std::vector<sptr(Stmt)> body = block();
// Exit function scope
exitFunction();
return msptr(FunctionStmt)(name, parameters, body);
}
std::shared_ptr<Expr> Parser::functionExpression() {
consume(OPEN_PAREN, "Expect '(' after 'func'.");
std::vector<Token> parameters;
if (!check(CLOSE_PAREN)) {
do {
if (parameters.size() >= 255) {
if (errorReporter) {
errorReporter->reportError(peek().line, 0, "Parse Error",
"Cannot have more than 255 parameters", "");
}
throw std::runtime_error("Cannot have more than 255 parameters.");
}
parameters.push_back(consume(IDENTIFIER, "Expect parameter name."));
} while (match({COMMA}));
}
consume(CLOSE_PAREN, "Expect ')' after parameters.");
consume(OPEN_BRACE, "Expect '{' before function body.");
// Enter function scope
enterFunction();
std::vector<std::shared_ptr<Stmt>> body = block();
// Exit function scope
exitFunction();
return msptr(FunctionExpr)(parameters, body);
}
sptr(Stmt) Parser::statement()
{
if(match({RETURN})) return returnStatement();
if(match({IF})) return ifStatement();
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
return expressionStatement();
}
sptr(Stmt) Parser::ifStatement()
{
consume(OPEN_PAREN, "Expected '(' after 'if'.");
sptr(Expr) condition = expression();
consume(CLOSE_PAREN, "Expected ')' after if condition.");
sptr(Stmt) thenBranch = statement();
sptr(Stmt) elseBranch = nullptr;
if (match({ELSE})) {
elseBranch = statement();
}
return msptr(IfStmt)(condition, thenBranch, elseBranch);
}
// Helper function to detect if an expression is a tail call
bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) {
// Check if this is a direct function call (no operations on the result)
if (auto callExpr = std::dynamic_pointer_cast<CallExpr>(expr)) {
return true; // Direct function call in return statement
}
return false;
}
sptr(Stmt) Parser::returnStatement()
{
Token keyword = previous();
// Check if we're inside a function
if (!isInFunction()) {
if (errorReporter) {
errorReporter->reportError(keyword.line, 0, "Parse Error",
"Cannot return from outside a function", "");
}
throw std::runtime_error("Cannot return from outside a function");
}
sptr(Expr) value = msptr(LiteralExpr)("none", false, true, false);
if (!check(SEMICOLON)) {
value = expression();
// Check if this is a tail call and mark it
if (isTailCall(value)) {
if (auto callExpr = std::dynamic_pointer_cast<CallExpr>(value)) {
callExpr->isTailCall = true;
}
}
}
consume(SEMICOLON, "Expected ';' after return value.");
return msptr(ReturnStmt)(keyword, value);
}
sptr(Stmt) Parser::expressionStatement()
{
sptr(Expr) expr = expression();
consume(SEMICOLON, "Expected ';' after expression.");
return msptr(ExpressionStmt)(expr);
}
std::vector<sptr(Stmt)> Parser::block()
{
std::vector<sptr(Stmt)> statements;
while(!check(CLOSE_BRACE) && !isAtEnd())
{
statements.push_back(declaration());
}
consume(CLOSE_BRACE, "Expected '}' after block.");
return statements;
}
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
std::vector<sptr(Expr)> arguments;
// Consume the opening parenthesis
consume(OPEN_PAREN, "Expected '(' after function name.");
// Parse arguments if there are any
if (!check(CLOSE_PAREN)) {
do {
arguments.push_back(expression());
} while (match({COMMA}));
}
Token paren = consume(CLOSE_PAREN, "Expected ')' after arguments.");
return msptr(CallExpr)(callee, paren, arguments);
}
bool Parser::match(const std::vector<TokenType>& types) {
for(TokenType t : types)
{
if(check(t))
{
advance();
return true;
}
}
return false;
}
bool Parser::check(TokenType type) {
if(isAtEnd()) return false;
return peek().type == type;
}
bool Parser::isAtEnd() {
return peek().type == END_OF_FILE;
}
Token Parser::advance() {
if(!isAtEnd()) current++;
return previous();
}
Token Parser::peek() {
return tokens[current];
}
Token Parser::previous() {
return tokens[current - 1];
}
Token Parser::consume(TokenType type, const std::string& message) {
if(check(type)) return advance();
if (errorReporter) {
// Use the precise column information from the token
int errorColumn = peek().column;
// For missing closing parenthesis, point to where it should be
if (type == CLOSE_PAREN) {
// The closing parenthesis should be right after the previous token
errorColumn = previous().column + previous().lexeme.length();
// For string tokens, add 2 to account for the opening and closing quotes
if (previous().type == STRING) {
errorColumn += 2;
}
}
errorReporter->reportError(peek().line, errorColumn, "Parse Error",
"Unexpected symbol '" + peek().lexeme + "': " + message, "");
}
throw std::runtime_error("Unexpected symbol '" + peek().lexeme +"': "+ message);
}
void Parser::sync()
{
advance();
while(!isAtEnd())
{
if(previous().type == SEMICOLON) return;
switch (peek().type) {
case CLASS:
case FUNCTION:
case VAR:
case FOR:
case IF:
case WHILE:
case RETURN:
return;
}
advance();
}
}

View File

@ -1,25 +1,9 @@
#include "BobStdLib.h" #include "../headers/StdLib.h"
#include "Interpreter.h" #include "../headers/Interpreter.h"
#include "ErrorReporter.h" #include "../headers/ErrorReporter.h"
#include "Lexer.h"
#include "Parser.h"
#include <chrono> #include <chrono>
#include <thread>
#include <ctime>
#include <fstream>
#include <sstream>
// Platform-specific includes for memory usage void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
#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 // Create a built-in toString function
auto toStringFunc = std::make_shared<BuiltinFunction>("toString", auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value { [&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
@ -33,7 +17,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return Value(interpreter.stringify(args[0])); return Value(interpreter.stringify(args[0]));
}); });
env->define("toString", Value(toStringFunc)); env->define("toString", Value(toStringFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toStringFunc); interpreter.addBuiltinFunction(toStringFunc);
@ -49,35 +33,14 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
} }
// Use the interpreter's stringify function // Use the interpreter's stringify function
std::cout << interpreter.stringify(args[0]) << '\n'; std::cout << interpreter.stringify(args[0]) << std::endl;
return NONE_VALUE; return NONE_VALUE;
}); });
env->define("print", Value(printFunc)); env->define("print", Value(printFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(printFunc); interpreter.addBuiltinFunction(printFunc);
// 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 assert function // Create a built-in assert function
auto assertFunc = std::make_shared<BuiltinFunction>("assert", auto assertFunc = std::make_shared<BuiltinFunction>("assert",
[errorReporter](std::vector<Value> args, int line, int column) -> Value { [errorReporter](std::vector<Value> args, int line, int column) -> Value {
@ -114,12 +77,32 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return NONE_VALUE; return NONE_VALUE;
}); });
env->define("assert", Value(assertFunc)); env->define("assert", Value(assertFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(assertFunc); interpreter.addBuiltinFunction(assertFunc);
// time-related globals moved into builtin time module // 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 // Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input", auto inputFunc = std::make_shared<BuiltinFunction>("input",
@ -143,7 +126,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return Value(userInput); return Value(userInput);
}); });
env->define("input", Value(inputFunc)); env->define("input", Value(inputFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(inputFunc); interpreter.addBuiltinFunction(inputFunc);
@ -172,19 +155,13 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
typeName = "function"; typeName = "function";
} else if (args[0].isBuiltinFunction()) { } else if (args[0].isBuiltinFunction()) {
typeName = "builtin_function"; typeName = "builtin_function";
} else if (args[0].isArray()) {
typeName = "array";
} else if (args[0].isDict()) {
typeName = "dict";
} else if (args[0].isModule()) {
typeName = "module";
} else { } else {
typeName = "unknown"; typeName = "unknown";
} }
return Value(typeName); return Value(typeName);
}); });
env->define("type", Value(typeFunc)); env->define("type", Value(typeFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(typeFunc); interpreter.addBuiltinFunction(typeFunc);
@ -219,39 +196,11 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
return NONE_VALUE; // Return none for out of range return NONE_VALUE; // Return none for out of range
} }
}); });
env->define("toNumber", Value(toNumberFunc)); env->define("toNumber", Value(toNumberFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toNumberFunc); interpreter.addBuiltinFunction(toNumberFunc);
// 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 // Create a built-in toBoolean function for explicit boolean conversion
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean", auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
[errorReporter](std::vector<Value> args, int line, int column) -> Value { [errorReporter](std::vector<Value> args, int line, int column) -> Value {
@ -285,65 +234,28 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
// For any other type (functions, etc.), consider them truthy // For any other type (functions, etc.), consider them truthy
return Value(true); return Value(true);
}); });
env->define("toBoolean", Value(toBooleanFunc)); env->define("toBoolean", Value(toBooleanFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toBooleanFunc); interpreter.addBuiltinFunction(toBooleanFunc);
// exit moved to sys module // 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
// sleep moved into builtin time module if (args.size() > 0) {
if (args[0].isNumber()) {
// Introspection: dir(obj) and functions(obj) exitCode = static_cast<int>(args[0].asNumber());
auto dirFunc = std::make_shared<BuiltinFunction>("dir",
[](std::vector<Value> args, int, int) -> Value {
if (args.size() != 1) return Value(std::vector<Value>{});
Value obj = args[0];
std::vector<Value> out;
if (obj.isModule()) {
auto* mod = obj.asModule();
if (mod && mod->exports) {
for (const auto& kv : *mod->exports) out.push_back(Value(kv.first));
} }
} else if (obj.isDict()) { // If not a number, just use default exit code 0
const auto& d = obj.asDict();
for (const auto& kv : d) out.push_back(Value(kv.first));
} }
return Value(out);
std::exit(exitCode);
return NONE_VALUE; // This line should never be reached
}); });
env->define("dir", Value(dirFunc)); env->define("exit", Value(exitFunc.get()));
interpreter.addBuiltinFunction(dirFunc);
auto functionsFunc = std::make_shared<BuiltinFunction>("functions",
[](std::vector<Value> args, int, int) -> Value {
if (args.size() != 1) return Value(std::vector<Value>{});
Value obj = args[0];
std::vector<Value> out;
auto pushIfFn = [&out](const std::pair<const std::string, Value>& kv){
if (kv.second.isFunction() || kv.second.isBuiltinFunction()) out.push_back(Value(kv.first));
};
if (obj.isModule()) {
auto* mod = obj.asModule();
if (mod && mod->exports) {
for (const auto& kv : *mod->exports) pushIfFn(kv);
}
} else if (obj.isDict()) {
const auto& d = obj.asDict();
for (const auto& kv : d) pushIfFn(kv);
}
return Value(out);
});
env->define("functions", Value(functionsFunc));
interpreter.addBuiltinFunction(functionsFunc);
// random moved to rand module
// (eval and evalFile moved to eval module)
// (file I/O moved to io module)
// memoryUsage moved to sys module
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(exitFunc);
} }

6
source/TypeWrapper.cpp Normal file
View File

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

8
source/Value.cpp Normal file
View File

@ -0,0 +1,8 @@
#include "../headers/Value.h"
// Global constants for common values (no heap allocation)
const Value NONE_VALUE = Value();
const Value TRUE_VALUE = Value(true);
const Value FALSE_VALUE = Value(false);
const Value ZERO_VALUE = Value(0.0);
const Value ONE_VALUE = Value(1.0);

93
source/bob.cpp Normal file
View File

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

16
source/main.cpp Normal file
View File

@ -0,0 +1,16 @@
//
// Created by Bobby Lucero on 5/21/23.
//
#include "../headers/bob.h"
int main(int argc, char* argv[]){
Bob bobLang;
if(argc > 1) {
bobLang.runFile(argv[1]);
} else {
bobLang.runPrompt();
}
return 0;
}

View File

@ -1,44 +0,0 @@
#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

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'base64' module
void registerBase64Module(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'eval' module providing eval(code) and evalFile(path)
void registerEvalModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'io' module (file I/O and input)
void registerIoModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'json' module
void registerJsonModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'math' module
void registerMathModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'os' module
void registerOsModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'path' module (path utilities)
void registerPathModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'rand' module
void registerRandModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Registers all builtin modules with the interpreter
void registerAllBuiltinModules(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'sys' module
void registerSysModule(Interpreter& interpreter);

View File

@ -1,8 +0,0 @@
#pragma once
class Interpreter;
// Register the builtin 'time' module (time functions)
void registerTimeModule(Interpreter& interpreter);

View File

@ -1,125 +0,0 @@
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include "Lexer.h"
#include "Interpreter.h"
#include "ModuleRegistry.h"
#include "helperFunctions/ShortHands.h"
#include "ErrorReporter.h"
#define VERSION "0.0.3"
class Bob
{
public:
Lexer lexer;
sptr(Interpreter) interpreter;
ErrorReporter errorReporter;
~Bob() = default;
public:
// Embedding helpers (bridge to internal interpreter)
void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init) {
if (interpreter) interpreter->registerModule(name, init);
else pendingConfigurators.push_back([name, init](Interpreter& I){ I.registerModule(name, init); });
}
void setBuiltinModulePolicy(bool allow) {
if (interpreter) interpreter->setBuiltinModulePolicy(allow);
else pendingConfigurators.push_back([allow](Interpreter& I){ I.setBuiltinModulePolicy(allow); });
}
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) {
if (interpreter) interpreter->setBuiltinModuleAllowList(allowed);
else pendingConfigurators.push_back([allowed](Interpreter& I){ I.setBuiltinModuleAllowList(allowed); });
}
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) {
if (interpreter) interpreter->setBuiltinModuleDenyList(denied);
else pendingConfigurators.push_back([denied](Interpreter& I){ I.setBuiltinModuleDenyList(denied); });
}
bool defineGlobal(const std::string& name, const Value& v) {
if (interpreter) return interpreter->defineGlobalVar(name, v);
pendingConfigurators.push_back([name, v](Interpreter& I){ I.defineGlobalVar(name, v); });
return true;
}
bool tryGetGlobal(const std::string& name, Value& out) const { return interpreter ? interpreter->tryGetGlobalVar(name, out) : false; }
void runFile(const std::string& path);
void runPrompt();
bool evalFile(const std::string& path);
bool evalString(const std::string& code, const std::string& filename = "<eval>");
// Safety policy helpers (public API)
// Set all safety-related policies at once
void setSafetyPolicy(
bool allowBuiltins,
const std::vector<std::string>& allowList,
const std::vector<std::string>& denyList,
bool allowFileImports,
bool preferFileOverBuiltin,
const std::vector<std::string>& searchPaths
) {
if (interpreter) {
interpreter->setBuiltinModulePolicy(allowBuiltins);
interpreter->setBuiltinModuleAllowList(allowList);
interpreter->setBuiltinModuleDenyList(denyList);
interpreter->setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
} else {
pendingConfigurators.push_back([=](Interpreter& I){
I.setBuiltinModulePolicy(allowBuiltins);
I.setBuiltinModuleAllowList(allowList);
I.setBuiltinModuleDenyList(denyList);
I.setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
});
}
}
// Simple presets: "open", "safe", "locked"
void setSafetyPreset(const std::string& preset) {
if (preset == "open") {
setSafetyPolicy(
true, /* allowBuiltins */
{}, /* allowList -> empty means allow all */
{},
true, /* allowFileImports */
true, /* preferFileOverBuiltin */
{} /* searchPaths */
);
} else if (preset == "safe") {
// Allow only pure/harmless modules by default
setSafetyPolicy(
true,
std::vector<std::string>{
"sys", "time", "rand", "math", "path", "base64"
},
std::vector<std::string>{ /* denyList empty when allowList is used */ },
false, /* disallow file-based imports */
true,
{}
);
} else if (preset == "locked") {
// No builtins visible; no file imports
setSafetyPolicy(
false,
{},
{},
false,
true,
{}
);
} else {
// Default to safe
setSafetyPreset("safe");
}
}
private:
void ensureInterpreter(bool interactive);
void applyPendingConfigs() {
if (!interpreter) return;
for (auto& f : pendingConfigurators) { f(*interpreter); }
pendingConfigurators.clear();
}
std::vector<std::function<void(Interpreter&)>> pendingConfigurators;
};

View File

@ -1,10 +0,0 @@
#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

@ -1,305 +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 WhileStmt;
struct DoWhileStmt;
struct ForStmt;
struct BreakStmt;
struct ContinueStmt;
struct AssignStmt;
struct ClassStmt;
struct ExtensionStmt;
#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;
virtual void visitClassStmt(const std::shared_ptr<ClassStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitTryStmt(const std::shared_ptr<struct TryStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitThrowStmt(const std::shared_ptr<struct ThrowStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitImportStmt(const std::shared_ptr<struct ImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitFromImportStmt(const std::shared_ptr<struct FromImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
};
struct Stmt : public std::enable_shared_from_this<Stmt>
{
std::shared_ptr<Expr> expression;
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
virtual ~Stmt(){};
};
struct ClassField {
Token name;
std::shared_ptr<Expr> initializer; // may be null
ClassField(Token name, std::shared_ptr<Expr> init) : name(name), initializer(init) {}
};
struct ClassStmt : Stmt {
const Token name;
bool hasParent;
Token parentName; // valid only if hasParent
std::vector<ClassField> fields;
std::vector<std::shared_ptr<FunctionStmt>> methods;
ClassStmt(Token name, bool hasParent, Token parentName, std::vector<ClassField> fields, std::vector<std::shared_ptr<FunctionStmt>> methods)
: name(name), hasParent(hasParent), parentName(parentName), fields(std::move(fields)), methods(std::move(methods)) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitClassStmt(std::static_pointer_cast<ClassStmt>(shared_from_this()), context);
}
};
struct ExtensionStmt : Stmt {
const Token target;
std::vector<std::shared_ptr<FunctionStmt>> methods;
ExtensionStmt(Token target, std::vector<std::shared_ptr<FunctionStmt>> methods)
: target(target), methods(std::move(methods)) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitExtensionStmt(std::static_pointer_cast<ExtensionStmt>(shared_from_this()), context);
}
};
struct BlockStmt : Stmt
{
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);
}
};
struct TryStmt : Stmt {
std::shared_ptr<Stmt> tryBlock;
Token catchVar; // IDENTIFIER or empty token if no catch
std::shared_ptr<Stmt> catchBlock; // may be null
std::shared_ptr<Stmt> finallyBlock; // may be null
TryStmt(std::shared_ptr<Stmt> t, Token cvar, std::shared_ptr<Stmt> cblk, std::shared_ptr<Stmt> fblk)
: tryBlock(t), catchVar(cvar), catchBlock(cblk), finallyBlock(fblk) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitTryStmt(std::static_pointer_cast<TryStmt>(shared_from_this()), context);
}
};
struct ThrowStmt : Stmt {
const Token keyword;
std::shared_ptr<Expr> value;
ThrowStmt(Token kw, std::shared_ptr<Expr> v) : keyword(kw), value(v) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitThrowStmt(std::static_pointer_cast<ThrowStmt>(shared_from_this()), context);
}
};
// import module [as alias]
struct ImportStmt : Stmt {
Token importToken; // IMPORT
Token moduleName; // IDENTIFIER
bool hasAlias = false;
Token alias; // IDENTIFIER if hasAlias
ImportStmt(Token kw, Token mod, bool ha, Token al)
: importToken(kw), moduleName(mod), hasAlias(ha), alias(al) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitImportStmt(std::static_pointer_cast<ImportStmt>(shared_from_this()), context);
}
};
// from module import name [as alias], name2 ...
struct FromImportStmt : Stmt {
Token fromToken; // FROM
Token moduleName; // IDENTIFIER or STRING
struct ImportItem { Token name; bool hasAlias; Token alias; };
std::vector<ImportItem> items;
bool importAll = false; // true for: from module import *;
FromImportStmt(Token kw, Token mod, std::vector<ImportItem> it)
: fromToken(kw), moduleName(mod), items(std::move(it)), importAll(false) {}
FromImportStmt(Token kw, Token mod, bool all)
: fromToken(kw), moduleName(mod), importAll(all) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
visitor->visitFromImportStmt(std::static_pointer_cast<FromImportStmt>(shared_from_this()), context);
}
};

View File

@ -1,35 +0,0 @@
#pragma once
#include "Value.h"
#include "Lexer.h"
// Utility to compute the result of a compound assignment (e.g., +=, -=, etc.)
// Leaves error reporting to callers; throws std::runtime_error on unknown operator
inline Value computeCompoundAssignment(const Value& currentValue, TokenType opType, const Value& rhs) {
switch (opType) {
case PLUS_EQUAL:
return currentValue + rhs;
case MINUS_EQUAL:
return currentValue - rhs;
case STAR_EQUAL:
return currentValue * rhs;
case SLASH_EQUAL:
return currentValue / rhs;
case PERCENT_EQUAL:
return currentValue % rhs;
case BIN_AND_EQUAL:
return currentValue & rhs;
case BIN_OR_EQUAL:
return currentValue | rhs;
case BIN_XOR_EQUAL:
return currentValue ^ rhs;
case BIN_SLEFT_EQUAL:
return currentValue << rhs;
case BIN_SRIGHT_EQUAL:
return currentValue >> rhs;
default:
throw std::runtime_error("Unknown compound assignment operator");
}
}

View File

@ -1,45 +0,0 @@
#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

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

View File

@ -1,50 +0,0 @@
#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;
void visitClassStmt(const std::shared_ptr<ClassStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context = nullptr) override;
void visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context = nullptr) override;
void visitImportStmt(const std::shared_ptr<ImportStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFromImportStmt(const std::shared_ptr<FromImportStmt>& statement, ExecutionContext* context = nullptr) override;
private:
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
};

View File

@ -1,198 +0,0 @@
#pragma once
#include <vector>
#include <memory>
#include <unordered_map>
#include <stack>
#include <optional>
#include <functional>
#include "Value.h"
#include "TypeWrapper.h"
#include "RuntimeDiagnostics.h"
#include "ModuleRegistry.h"
#include <unordered_set>
struct Expr;
struct Stmt;
struct Environment;
struct BuiltinFunction;
struct Function;
struct Thunk;
class ErrorReporter;
struct ExecutionContext;
struct CallExpr;
// Forward declaration
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
// Global extension registries
std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<Function>>> classExtensions;
std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<Function>>> builtinExtensions; // keys: "string","array","dict","any"
std::unordered_map<std::string, std::string> classParents; // child -> parent
std::unordered_map<std::string, std::unordered_map<std::string, Value>> classTemplates; // className -> template dict
// Field initializers per class in source order (to evaluate across inheritance chain)
std::unordered_map<std::string, std::vector<std::pair<std::string, std::shared_ptr<Expr>>>> classFieldInitializers; // className -> [(field, expr)]
ErrorReporter* errorReporter;
bool inThunkExecution = false;
// Automatic cleanup tracking
int thunkCreationCount = 0;
static const int CLEANUP_THRESHOLD = 10000;
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
std::unique_ptr<Evaluator> evaluator;
std::unique_ptr<Executor> executor;
// Module cache: module key -> module dict value
std::unordered_map<std::string, Value> moduleCache;
// Builtin module registry
ModuleRegistry builtinModules;
// Import policy flags
bool allowFileImports = true;
bool preferFileOverBuiltin = true;
bool allowBuiltinImports = true;
std::vector<std::string> moduleSearchPaths; // e.g., BOBPATH
// Pending throw propagation from expression evaluation
bool hasPendingThrow = false;
Value pendingThrow = NONE_VALUE;
int pendingThrowLine = 0;
int pendingThrowColumn = 0;
int lastErrorLine = 0;
int lastErrorColumn = 0;
int tryDepth = 0;
bool inlineErrorReported = false;
public:
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);
ErrorReporter* getErrorReporter() const { return errorReporter; }
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();
// Extension APIs
void registerExtension(const std::string& targetName, const std::string& methodName, std::shared_ptr<Function> fn);
std::shared_ptr<Function> lookupExtension(const std::string& targetName, const std::string& methodName);
void registerClass(const std::string& className, const std::string& parentName);
std::string getParentClass(const std::string& className) const;
void setClassTemplate(const std::string& className, const std::unordered_map<std::string, Value>& tmpl);
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
// Field initializer APIs
void setClassFieldInitializers(const std::string& className, const std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& inits) { classFieldInitializers[className] = inits; }
bool getClassFieldInitializers(const std::string& className, std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& out) const {
auto it = classFieldInitializers.find(className);
if (it == classFieldInitializers.end()) return false; out = it->second; return true;
}
void addStdLibFunctions();
// Module APIs
Value importModule(const std::string& spec, int line, int column); // returns module dict
bool fromImport(const std::string& spec, const std::vector<std::pair<std::string, std::string>>& items, int line, int column); // name->alias
void setModulePolicy(bool allowFiles, bool preferFiles, const std::vector<std::string>& searchPaths);
void setBuiltinModulePolicy(bool allowBuiltins) { allowBuiltinImports = allowBuiltins; builtinModules.setPolicy(allowBuiltins); }
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) { builtinModules.setAllowList(allowed); }
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) { builtinModules.setDenyList(denied); }
void registerBuiltinModule(const std::string& name, std::function<Value(Interpreter&)> factory) { builtinModules.registerFactory(name, std::move(factory)); }
// Simple module registration API
using ModuleBuilder = ModuleRegistry::ModuleBuilder;
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
builtinModules.registerModule(name, std::move(init));
}
// Global environment helpers
bool defineGlobalVar(const std::string& name, const Value& value);
bool tryGetGlobalVar(const std::string& name, Value& out) const;
// Throw propagation helpers
void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
bool consumePendingThrow(Value& out, int* lineOut = nullptr, int* colOut = nullptr) { if (!hasPendingThrow) return false; out = pendingThrow; if (lineOut) *lineOut = pendingThrowLine; if (colOut) *colOut = pendingThrowColumn; hasPendingThrow = false; pendingThrow = NONE_VALUE; pendingThrowLine = 0; pendingThrowColumn = 0; return true; }
// Try tracking
void enterTry() { tryDepth++; }
void exitTry() { if (tryDepth > 0) tryDepth--; }
bool isInTry() const { return tryDepth > 0; }
void markInlineErrorReported() { inlineErrorReported = true; }
bool hasInlineErrorReported() const { return inlineErrorReported; }
void clearInlineErrorReported() { inlineErrorReported = false; }
bool hasReportedError() const;
// Last error site tracking
void setLastErrorSite(int line, int column) { lastErrorLine = line; lastErrorColumn = column; }
int getLastErrorLine() const { return lastErrorLine; }
int getLastErrorColumn() const { return lastErrorColumn; }
// Process/host metadata (for sys module)
void setArgv(const std::vector<std::string>& args, const std::string& executablePath) { argvData = args; executableFile = executablePath; }
std::vector<std::string> getArgv() const { return argvData; }
std::string getExecutablePath() const { return executableFile; }
std::unordered_map<std::string, Value> getModuleCacheSnapshot() const { return moduleCache; }
private:
Value runTrampoline(Value initialResult);
// Stored argv/executable for sys module
std::vector<std::string> argvData;
std::string executableFile;
};

View File

@ -1,14 +0,0 @@
// ModuleDef.h
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include "Value.h"
struct Module {
std::string name;
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
};

View File

@ -1,70 +0,0 @@
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <memory>
#include "TypeWrapper.h" // BuiltinFunction, Value
class Interpreter; // fwd
class ModuleRegistry {
public:
struct ModuleBuilder {
std::string moduleName;
Interpreter& interpreterRef;
std::unordered_map<std::string, Value> exports;
ModuleBuilder(const std::string& n, Interpreter& i) : moduleName(n), interpreterRef(i) {}
void fn(const std::string& name, std::function<Value(std::vector<Value>, int, int)> func) {
exports[name] = Value(std::make_shared<BuiltinFunction>(name, func));
}
void val(const std::string& name, const Value& v) { exports[name] = v; }
};
using Factory = std::function<Value(Interpreter&)>;
void registerFactory(const std::string& name, Factory factory) {
factories[name] = std::move(factory);
}
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
registerFactory(name, [name, init](Interpreter& I) -> Value {
ModuleBuilder b(name, I);
init(b);
auto m = std::make_shared<Module>(name, b.exports);
return Value(m);
});
}
bool has(const std::string& name) const {
auto it = factories.find(name);
if (it == factories.end()) return false;
// Respect policy for presence checks to optionally cloak denied modules
if (!allowBuiltins) return false;
if (!allowList.empty() && allowList.find(name) == allowList.end()) return false;
if (denyList.find(name) != denyList.end()) return false;
return true;
}
Value create(const std::string& name, Interpreter& I) const {
auto it = factories.find(name);
if (it == factories.end()) return NONE_VALUE;
if (!allowBuiltins) return NONE_VALUE;
if (!allowList.empty() && allowList.find(name) == allowList.end()) return NONE_VALUE;
if (denyList.find(name) != denyList.end()) return NONE_VALUE;
return it->second(I);
}
void setPolicy(bool allow) { allowBuiltins = allow; }
void setAllowList(const std::vector<std::string>& allowed) { allowList = std::unordered_set<std::string>(allowed.begin(), allowed.end()); }
void setDenyList(const std::vector<std::string>& denied) { denyList = std::unordered_set<std::string>(denied.begin(), denied.end()); }
private:
std::unordered_map<std::string, Factory> factories;
std::unordered_set<std::string> allowList;
std::unordered_set<std::string> denyList;
bool allowBuiltins = true;
};

View File

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

View File

@ -1,458 +0,0 @@
#pragma once
#include "helperFunctions/ErrorUtils.h"
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <utility>
#include <cmath>
#include <stdexcept>
#include <algorithm>
// Forward declarations
struct Environment;
struct Function;
struct BuiltinFunction;
struct Thunk;
struct Module;
// 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,
VAL_MODULE
};
// (moved below Value)
// 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
std::shared_ptr<Module> module_value; // Module object
// 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))) {}
Value(std::shared_ptr<Module> m) : type(ValueType::VAL_MODULE), module_value(std::move(m)) {}
// 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)), module_value(std::move(other.module_value)) {
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 if (type == ValueType::VAL_MODULE) {
module_value = std::move(other.module_value);
} 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 if (type == ValueType::VAL_MODULE) {
module_value = other.module_value; // shared module
} 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 if (type == ValueType::VAL_MODULE) {
module_value = other.module_value;
} 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 isModule() const { return type == ValueType::VAL_MODULE; }
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";
case ValueType::VAL_MODULE: return "module";
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 Module* asModule() const { return isModule() ? module_value.get() : nullptr; }
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
// Truthiness check - inline for performance
inline bool isTruthy() const {
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();
case ValueType::VAL_MODULE: return module_value != 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;
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;
}
case ValueType::VAL_MODULE: {
// Avoid accessing Module fields when it's still an incomplete type in some TUs.
// Delegate formatting to a small helper defined out-of-line in Value.cpp.
extern std::string formatModuleForToString(const std::shared_ptr<Module>&);
return formatModuleForToString(module_value);
}
default: return "unknown";
}
}
// 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()));
}
};
// Define Module after Value so it can hold Value in exports without incomplete type issues
struct Module {
std::string name;
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
Module() = default;
Module(const std::string& n, const std::unordered_map<std::string, Value>& dict)
: name(n), exports(std::make_shared<std::unordered_map<std::string, Value>>(dict)) {}
};
// Global constants for common values
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;

View File

@ -1,41 +0,0 @@
#include "base64_module.h"
#include "Interpreter.h"
#include <string>
static const char* B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static std::string b64encode(const std::string& in){
std::string out; out.reserve(((in.size()+2)/3)*4);
int val=0, valb=-6;
for (unsigned char c : in){
val = (val<<8) + c;
valb += 8;
while (valb >= 0){ out.push_back(B64[(val>>valb)&0x3F]); valb -= 6; }
}
if (valb>-6) out.push_back(B64[((val<<8)>>(valb+8))&0x3F]);
while (out.size()%4) out.push_back('=');
return out;
}
static std::string b64decode(const std::string& in){
std::vector<int> T(256,-1); for (int i=0;i<64;i++) T[(unsigned char)B64[i]]=i;
std::string out; out.reserve((in.size()*3)/4);
int val=0, valb=-8;
for (unsigned char c : in){ if (T[c]==-1) break; val=(val<<6)+T[c]; valb+=6; if (valb>=0){ out.push_back(char((val>>valb)&0xFF)); valb-=8; } }
return out;
}
void registerBase64Module(Interpreter& interpreter) {
interpreter.registerModule("base64", [](Interpreter::ModuleBuilder& m) {
m.fn("encode", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(b64encode(a[0].asString()));
});
m.fn("decode", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(b64decode(a[0].asString()));
});
});
}

View File

@ -1,64 +0,0 @@
#include "eval.h"
#include "Interpreter.h"
#include "ErrorReporter.h"
#include "Lexer.h"
#include "Parser.h"
#include <fstream>
#include <sstream>
void registerEvalModule(Interpreter& interpreter) {
interpreter.registerModule("eval", [](Interpreter::ModuleBuilder& m) {
ErrorReporter* er = m.interpreterRef.getErrorReporter();
m.fn("eval", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 || !args[0].isString()) {
if (er) er->reportError(line, column, "Invalid Arguments", "eval expects exactly 1 string argument", "eval");
throw std::runtime_error("eval expects exactly 1 string argument");
}
std::string code = args[0].asString();
std::string evalName = "<eval>";
try {
if (er) er->pushSource(code, evalName);
Lexer lx; if (er) lx.setErrorReporter(er);
auto toks = lx.Tokenize(code);
Parser p(toks); if (er) p.setErrorReporter(er);
auto stmts = p.parse();
I.interpret(stmts);
return NONE_VALUE;
} catch (...) {
if (er) er->popSource();
throw;
}
if (er) er->popSource();
});
m.fn("evalFile", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 || !args[0].isString()) {
if (er) er->reportError(line, column, "Invalid Arguments", "evalFile expects exactly 1 string argument (path)", "evalFile");
throw std::runtime_error("evalFile expects exactly 1 string argument (path)");
}
std::string filename = args[0].asString();
std::ifstream f(filename);
if (!f.is_open()) {
if (er) er->reportError(line, column, "StdLib Error", "Could not open file: " + filename, "");
throw std::runtime_error("Could not open file: " + filename);
}
std::stringstream buf; buf << f.rdbuf(); f.close();
std::string code = buf.str();
try {
if (er) er->pushSource(code, filename);
Lexer lx; if (er) lx.setErrorReporter(er);
auto toks = lx.Tokenize(code);
Parser p(toks); if (er) p.setErrorReporter(er);
auto stmts = p.parse();
I.interpret(stmts);
return NONE_VALUE;
} catch (...) {
if (er) er->popSource();
throw;
}
if (er) er->popSource();
});
});
}

View File

@ -1,72 +0,0 @@
#include <fstream>
#include <sstream>
#include "io.h"
#include "Interpreter.h"
#include "ErrorReporter.h"
void registerIoModule(Interpreter& interpreter) {
interpreter.registerModule("io", [](Interpreter::ModuleBuilder& m) {
ErrorReporter* er = m.interpreterRef.getErrorReporter();
m.fn("readFile", [er](std::vector<Value> a, int line, int col) -> Value {
if (a.empty() || !a[0].isString() || a.size() > 2 || (a.size() == 2 && !a[1].isString())) {
if (er) er->reportError(line, col, "Invalid Arguments", "readFile(path[, mode]) expects 1-2 args (strings)", "readFile");
throw std::runtime_error("readFile(path[, mode]) expects 1-2 string args");
}
std::string mode = (a.size() == 2) ? a[1].asString() : std::string("r");
std::ios_base::openmode om = std::ios::in;
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
std::ifstream f(a[0].asString(), om);
if (!f.is_open()) {
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
throw std::runtime_error("Could not open file");
}
std::stringstream buf; buf << f.rdbuf(); f.close();
return Value(buf.str());
});
m.fn("writeFile", [er](std::vector<Value> a, int line, int col) -> Value {
if (a.size() < 2 || a.size() > 3 || !a[0].isString() || !a[1].isString() || (a.size() == 3 && !a[2].isString())) {
if (er) er->reportError(line, col, "Invalid Arguments", "writeFile(path, data[, mode]) expects 2-3 args (strings)", "writeFile");
throw std::runtime_error("writeFile(path, data[, mode]) expects 2-3 string args");
}
std::string mode = (a.size() == 3) ? a[2].asString() : std::string("w");
std::ios_base::openmode om = std::ios::out;
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
if (mode.find('a') != std::string::npos) om |= std::ios::app; else om |= std::ios::trunc;
std::ofstream f(a[0].asString(), om);
if (!f.is_open()) {
if (er) er->reportError(line, col, "StdLib Error", "Could not create file", a[0].asString());
throw std::runtime_error("Could not create file");
}
f << a[1].asString(); f.close();
return NONE_VALUE;
});
m.fn("readLines", [er](std::vector<Value> a, int line, int col) -> Value {
if (a.size() != 1 || !a[0].isString()) {
if (er) er->reportError(line, col, "Invalid Arguments", "readLines(path) expects 1 string arg", "readLines");
throw std::runtime_error("readLines(path) expects 1 string arg");
}
std::ifstream f(a[0].asString());
if (!f.is_open()) {
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
throw std::runtime_error("Could not open file");
}
std::vector<Value> lines; std::string s;
while (std::getline(f, s)) lines.emplace_back(s);
f.close();
return Value(lines);
});
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
std::ifstream f(a[0].asString()); bool ok = f.good(); f.close();
return Value(ok);
});
// input remains a global in stdlib; not provided here
});
}

View File

@ -1,83 +0,0 @@
#include "json.h"
#include "Interpreter.h"
#include <string>
#include <cctype>
// Minimal JSON parser/stringifier (numbers, strings, booleans, null, arrays, objects)
namespace {
struct Cursor { const std::string* s; size_t i = 0; };
void skipWs(Cursor& c){ while (c.i < c.s->size() && std::isspace(static_cast<unsigned char>((*c.s)[c.i]))) ++c.i; }
bool match(Cursor& c, char ch){ skipWs(c); if (c.i < c.s->size() && (*c.s)[c.i]==ch){ ++c.i; return true;} return false; }
std::string parseString(Cursor& c){
if (!match(c,'"')) return {};
std::string out; while (c.i < c.s->size()){
char ch = (*c.s)[c.i++];
if (ch=='"') break;
if (ch=='\\' && c.i < c.s->size()){
char e = (*c.s)[c.i++];
switch(e){ case '"': out+='"'; break; case '\\': out+='\\'; break; case '/': out+='/'; break; case 'b': out+='\b'; break; case 'f': out+='\f'; break; case 'n': out+='\n'; break; case 'r': out+='\r'; break; case 't': out+='\t'; break; default: out+=e; }
} else out+=ch;
}
return out;
}
double parseNumber(Cursor& c){ skipWs(c); size_t start=c.i; while (c.i<c.s->size() && (std::isdigit((*c.s)[c.i])||(*c.s)[c.i]=='-'||(*c.s)[c.i]=='+'||(*c.s)[c.i]=='.'||(*c.s)[c.i]=='e'||(*c.s)[c.i]=='E')) ++c.i; return std::stod(c.s->substr(start,c.i-start)); }
Value parseValue(Cursor& c);
Value parseArray(Cursor& c){
match(c,'['); std::vector<Value> arr; skipWs(c); if (match(c,']')) return Value(arr);
while (true){ arr.push_back(parseValue(c)); skipWs(c); if (match(c,']')) break; match(c,','); }
return Value(arr);
}
Value parseObject(Cursor& c){
match(c,'{'); std::unordered_map<std::string,Value> obj; skipWs(c); if (match(c,'}')) return Value(obj);
while (true){ std::string k = parseString(c); match(c,':'); Value v = parseValue(c); obj.emplace(k, v); skipWs(c); if (match(c,'}')) break; match(c,','); }
return Value(obj);
}
Value parseValue(Cursor& c){ skipWs(c); if (c.i>=c.s->size()) return NONE_VALUE; char ch=(*c.s)[c.i];
if (ch=='"') return Value(parseString(c));
if (ch=='[') return parseArray(c);
if (ch=='{') return parseObject(c);
if (!c.s->compare(c.i,4,"true")) { c.i+=4; return Value(true);}
if (!c.s->compare(c.i,5,"false")) { c.i+=5; return Value(false);}
if (!c.s->compare(c.i,4,"null")) { c.i+=4; return NONE_VALUE;}
return Value(parseNumber(c));
}
std::string escapeString(const std::string& s){
std::string out; out.reserve(s.size()+2); out.push_back('"');
for(char ch: s){
switch(ch){ case '"': out+="\\\""; break; case '\\': out+="\\\\"; break; case '\n': out+="\\n"; break; case '\r': out+="\\r"; break; case '\t': out+="\\t"; break; default: out+=ch; }
}
out.push_back('"'); return out;
}
std::string stringifyValue(const Value& v){
switch(v.type){
case VAL_NONE: return "null";
case VAL_BOOLEAN: return v.asBoolean()?"true":"false";
case VAL_NUMBER: return v.toString();
case VAL_STRING: return escapeString(v.asString());
case VAL_ARRAY: {
const auto& a=v.asArray(); std::string out="["; for(size_t i=0;i<a.size();++i){ if(i) out+=","; out+=stringifyValue(a[i]); } out+="]"; return out;
}
case VAL_DICT: {
const auto& d=v.asDict(); std::string out="{"; bool first=true; for(const auto& kv:d){ if(!first) out+=","; first=false; out+=escapeString(kv.first); out+=":"; out+=stringifyValue(kv.second);} out+="}"; return out;
}
default: return "null";
}
}
}
void registerJsonModule(Interpreter& interpreter) {
interpreter.registerModule("json", [](Interpreter::ModuleBuilder& m) {
m.fn("parse", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return NONE_VALUE;
Cursor c{&a[0].asString(), 0};
return parseValue(c);
});
m.fn("stringify", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1) return Value(std::string("null"));
return Value(stringifyValue(a[0]));
});
});
}

View File

@ -1,56 +0,0 @@
#include "math_module.h"
#include "Interpreter.h"
#include <cmath>
static Value unary_math(std::vector<Value> a, double(*fn)(double)){
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
return Value(fn(a[0].asNumber()));
}
void registerMathModule(Interpreter& interpreter) {
interpreter.registerModule("math", [](Interpreter::ModuleBuilder& m) {
m.fn("sin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sin); });
m.fn("cos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cos); });
m.fn("tan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tan); });
m.fn("asin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::asin); });
m.fn("acos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::acos); });
m.fn("atan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::atan); });
m.fn("sinh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sinh); });
m.fn("cosh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cosh); });
m.fn("tanh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tanh); });
m.fn("exp", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::exp); });
m.fn("log", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log); });
m.fn("log10", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log10); });
m.fn("sqrt", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sqrt); });
m.fn("ceil", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::ceil); });
m.fn("floor", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::floor); });
m.fn("round", [](std::vector<Value> a, int, int)->Value{
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
return Value(std::round(a[0].asNumber()));
});
m.fn("abs", [](std::vector<Value> a, int, int)->Value{
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
return Value(std::fabs(a[0].asNumber()));
});
m.fn("pow", [](std::vector<Value> a, int, int)->Value{
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
return Value(std::pow(a[0].asNumber(), a[1].asNumber()));
});
m.fn("min", [](std::vector<Value> a, int, int)->Value{
if (a.empty()) return NONE_VALUE;
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::min(mval, a[i].asNumber()); }
return Value(mval);
});
m.fn("max", [](std::vector<Value> a, int, int)->Value{
if (a.empty()) return NONE_VALUE;
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::max(mval, a[i].asNumber()); }
return Value(mval);
});
m.val("pi", Value(3.14159265358979323846));
m.val("e", Value(2.71828182845904523536));
});
}

View File

@ -1,132 +0,0 @@
#include "os.h"
#include "Interpreter.h"
#include "Lexer.h"
#include <vector>
#include <string>
#include <filesystem>
#if defined(_WIN32)
#include <windows.h>
#include <direct.h>
#include <limits.h>
#include <io.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#else
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#endif
namespace fs = std::filesystem;
void registerOsModule(Interpreter& interpreter) {
interpreter.registerModule("os", [](Interpreter::ModuleBuilder& m) {
// Process
m.fn("getcwd", [](std::vector<Value>, int, int) -> Value {
char buf[PATH_MAX];
#if defined(_WIN32)
if (_getcwd(buf, sizeof(buf))) return Value(std::string(buf));
#else
if (getcwd(buf, sizeof(buf))) return Value(std::string(buf));
#endif
return NONE_VALUE;
});
m.fn("chdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
#if defined(_WIN32)
int rc = ::_chdir(a[0].asString().c_str());
#else
int rc = ::chdir(a[0].asString().c_str());
#endif
return Value(rc == 0);
});
m.fn("getpid", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(static_cast<double>(GetCurrentProcessId()));
#else
return Value(static_cast<double>(getpid()));
#endif
});
m.fn("getppid", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return NONE_VALUE; // not directly available; could use Toolhelp32Snapshot if needed
#else
return Value(static_cast<double>(getppid()));
#endif
});
m.fn("name", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("nt"));
#else
return Value(std::string("posix"));
#endif
});
// Filesystem
m.fn("listdir", [](std::vector<Value> a, int, int) -> Value {
std::string path = ".";
if (!a.empty() && a[0].isString()) path = a[0].asString();
std::vector<Value> out;
try {
for (const auto& entry : fs::directory_iterator(path)) {
out.push_back(Value(entry.path().filename().string()));
}
} catch (...) {}
return Value(out);
});
m.fn("mkdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::create_directory(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("rmdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("remove", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::exists(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("isfile", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::is_regular_file(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("isdir", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isString()) return Value(false);
try { return Value(fs::is_directory(a[0].asString())); } catch (...) { return Value(false); }
});
m.fn("rename", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 2 || !a[0].isString() || !a[1].isString()) return Value(false);
try { fs::rename(a[0].asString(), a[1].asString()); return Value(true); } catch (...) { return Value(false); }
});
// Separators
m.fn("sep", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("\\"));
#else
return Value(std::string("/"));
#endif
});
m.fn("pathsep", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string(";"));
#else
return Value(std::string(":"));
#endif
});
m.fn("linesep", [](std::vector<Value>, int, int) -> Value {
#if defined(_WIN32)
return Value(std::string("\r\n"));
#else
return Value(std::string("\n"));
#endif
});
});
}

View File

@ -1,63 +0,0 @@
#include "path_module.h"
#include "Interpreter.h"
#include <filesystem>
#include <cctype>
namespace fs = std::filesystem;
static std::string join_impl(const std::vector<Value>& parts){
if (parts.empty()) return std::string();
fs::path p;
for (const auto& v : parts) if (v.isString()) p /= v.asString();
return p.generic_string();
}
static bool isabs_impl(const std::string& s) {
#if defined(_WIN32)
if (s.size() >= 2 && (s[0] == '/' || s[0] == '\\')) return true; // root-relative on current drive
if (s.rfind("\\\\", 0) == 0) return true; // UNC path
if (s.size() >= 3 && std::isalpha(static_cast<unsigned char>(s[0])) && s[1] == ':' && (s[2] == '/' || s[2] == '\\')) return true; // C:\ or C:/
return false;
#else
return !s.empty() && s[0] == '/';
#endif
}
void registerPathModule(Interpreter& interpreter) {
interpreter.registerModule("path", [](Interpreter::ModuleBuilder& m) {
m.fn("join", [](std::vector<Value> a, int, int) -> Value {
return Value(join_impl(a));
});
m.fn("dirname", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(fs::path(a[0].asString()).parent_path().generic_string());
});
m.fn("basename", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(fs::path(a[0].asString()).filename().generic_string());
});
m.fn("splitext", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
fs::path p(a[0].asString());
std::string ext = p.has_extension() ? p.extension().generic_string() : std::string("");
fs::path basePath = p.has_extension() ? (p.parent_path() / p.stem()) : p;
return Value(std::vector<Value>{ Value(basePath.generic_string()), Value(ext) });
});
m.fn("normalize", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
return Value(fs::path(a[0].asString()).lexically_normal().generic_string());
});
m.fn("isabs", [](std::vector<Value> a, int, int) -> Value {
if (a.size()!=1 || !a[0].isString()) return Value(false);
return Value(isabs_impl(a[0].asString()));
});
m.fn("relpath", [](std::vector<Value> a, int, int) -> Value {
if (a.size()<1 || a.size()>2 || !a[0].isString() || (a.size()==2 && !a[1].isString())) return NONE_VALUE;
fs::path target(a[0].asString());
fs::path base = (a.size()==2)? fs::path(a[1].asString()) : fs::current_path();
return Value(fs::relative(target, base).generic_string());
});
});
}

View File

@ -1,35 +0,0 @@
#include "rand.h"
#include "Interpreter.h"
#include <random>
void registerRandModule(Interpreter& interpreter) {
interpreter.registerModule("rand", [](Interpreter::ModuleBuilder& m) {
static std::mt19937_64 rng{std::random_device{}()};
m.fn("seed", [](std::vector<Value> a, int, int) -> Value {
if (a.size() == 1 && a[0].isNumber()) {
rng.seed(static_cast<uint64_t>(a[0].asNumber()));
}
return NONE_VALUE;
});
m.fn("random", [](std::vector<Value>, int, int) -> Value {
std::uniform_real_distribution<double> dist(0.0, 1.0);
return Value(dist(rng));
});
m.fn("randint", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
long long lo = static_cast<long long>(a[0].asNumber());
long long hi = static_cast<long long>(a[1].asNumber());
if (hi < lo) std::swap(lo, hi);
std::uniform_int_distribution<long long> dist(lo, hi);
return Value(static_cast<double>(dist(rng)));
});
m.fn("choice", [](std::vector<Value> a, int, int) -> Value {
if (a.size() != 1 || !a[0].isArray() || a[0].asArray().empty()) return NONE_VALUE;
const auto& arr = a[0].asArray();
std::uniform_int_distribution<size_t> dist(0, arr.size() - 1);
return arr[dist(rng)];
});
});
}

View File

@ -1,25 +0,0 @@
#include "register.h"
#include "sys.h"
#include "os.h"
#include "eval.h"
#include "io.h"
#include "time_module.h"
#include "rand.h"
#include "math_module.h"
#include "path_module.h"
#include "base64_module.h"
void registerAllBuiltinModules(Interpreter& interpreter) {
registerSysModule(interpreter);
registerOsModule(interpreter);
registerEvalModule(interpreter);
registerIoModule(interpreter);
registerTimeModule(interpreter);
registerRandModule(interpreter);
registerMathModule(interpreter);
registerPathModule(interpreter);
registerBase64Module(interpreter);
// registerJsonModule(interpreter); // deferred pending extensive testing
}

Some files were not shown because too many files have changed in this diff Show More