Compare commits
24 Commits
master
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| b220cf70e0 | |||
| ec4b5afa9c | |||
| 6e3379b5b8 | |||
| 7f7c6e438d | |||
| 8cdccae214 | |||
| fc63c3e46f | |||
| 227586c583 | |||
| 3138f6fb92 | |||
| 7a9c0b7ea9 | |||
| 266cca5b42 | |||
| 85d3381575 | |||
| f70c6abd77 | |||
| 87d56bbb13 | |||
| 6c17ce96f0 | |||
| 2104fbe1f5 | |||
| 32910b1e57 | |||
| b97715e549 | |||
| eacb86ec77 | |||
| 43c5f081d7 | |||
| 17c15e5bad | |||
| 72a1b82b43 | |||
| 671f8a6350 | |||
| b87b342dff | |||
| 313c996edd |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
/.vscode
|
||||
build/
|
||||
|
||||
.DS_Store
|
||||
build-ninja
|
||||
build-release
|
||||
|
||||
@ -1,361 +0,0 @@
|
||||
# Bob Language Reference
|
||||
|
||||
## Overview
|
||||
|
||||
Bob is a dynamically typed programming language with a focus on simplicity and expressiveness. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Data Types](#data-types)
|
||||
2. [Variables](#variables)
|
||||
3. [Operators](#operators)
|
||||
4. [Functions](#functions)
|
||||
5. [Control Flow](#control-flow)
|
||||
6. [Standard Library](#standard-library)
|
||||
7. [Error Handling](#error-handling)
|
||||
8. [Examples](#examples)
|
||||
9. [Language Nuances](#language-nuances)
|
||||
|
||||
## Data Types
|
||||
|
||||
### Numbers
|
||||
- **Integers**: `42`, `-10`, `0`
|
||||
- **Floats**: `3.14`, `2.718`, `-1.5`
|
||||
- **Automatic conversion**: Numbers are stored as doubles internally
|
||||
|
||||
### Strings
|
||||
- **Literal strings**: `"Hello, World!"`
|
||||
- **Empty strings**: `""`
|
||||
- **Escape sequences**: Not currently supported
|
||||
|
||||
### Booleans
|
||||
- **True**: `true`
|
||||
- **False**: `false`
|
||||
|
||||
### None
|
||||
- **Null value**: `none` (represents absence of value)
|
||||
|
||||
## Variables
|
||||
|
||||
### Declaration
|
||||
```bob
|
||||
var name = "Bob";
|
||||
var age = 25;
|
||||
var isActive = true;
|
||||
```
|
||||
|
||||
### Assignment
|
||||
```bob
|
||||
var x = 10;
|
||||
x = 20; // Reassignment
|
||||
```
|
||||
|
||||
### Scoping
|
||||
- **Global scope**: Variables declared at top level
|
||||
- **Local scope**: Variables declared inside functions
|
||||
- **Shadowing**: Local variables can shadow global variables
|
||||
- **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration
|
||||
|
||||
### Variable Behavior
|
||||
```bob
|
||||
var globalVar = 100;
|
||||
|
||||
func testScope() {
|
||||
var localVar = 50; // Local variable
|
||||
return localVar + globalVar; // Can access global
|
||||
}
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
### Arithmetic Operators
|
||||
- **Addition**: `+`
|
||||
- **Subtraction**: `-`
|
||||
- **Multiplication**: `*`
|
||||
- **Division**: `/`
|
||||
- **Modulo**: `%`
|
||||
|
||||
### Comparison Operators
|
||||
- **Equal**: `==`
|
||||
- **Not equal**: `!=`
|
||||
- **Greater than**: `>`
|
||||
- **Less than**: `<`
|
||||
- **Greater than or equal**: `>=`
|
||||
- **Less than or equal**: `<=`
|
||||
|
||||
### String Operators
|
||||
|
||||
#### Concatenation
|
||||
Bob supports bidirectional string + number concatenation with automatic type conversion:
|
||||
|
||||
```bob
|
||||
// String + Number
|
||||
"Hello " + 42; // → "Hello 42"
|
||||
"Pi: " + 3.14; // → "Pi: 3.14"
|
||||
|
||||
// Number + String
|
||||
42 + " items"; // → "42 items"
|
||||
3.14 + " is pi"; // → "3.14 is pi"
|
||||
```
|
||||
|
||||
#### String Multiplication
|
||||
```bob
|
||||
"hello" * 3; // → "hellohellohello"
|
||||
3 * "hello"; // → "hellohellohello"
|
||||
```
|
||||
|
||||
**Note**: String multiplication requires whole numbers.
|
||||
|
||||
### Number Formatting
|
||||
Bob automatically formats numbers to show only significant digits:
|
||||
|
||||
```bob
|
||||
"Count: " + 2.0; // → "Count: 2" (no trailing zeros)
|
||||
"Pi: " + 3.14; // → "Pi: 3.14" (exact precision)
|
||||
"Integer: " + 42; // → "Integer: 42" (no decimal)
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### Function Declaration
|
||||
```bob
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
### Function Call
|
||||
```bob
|
||||
var result = add(2, 3); // result = 5
|
||||
```
|
||||
|
||||
### Parameters
|
||||
- **Any number of parameters** supported
|
||||
- **No default parameters** (not implemented)
|
||||
- **No keyword arguments** (not implemented)
|
||||
|
||||
### Return Values
|
||||
- **Explicit return**: `return value;`
|
||||
- **Implicit return**: Functions return `none` if no return statement
|
||||
- **Early return**: Functions can return from anywhere
|
||||
|
||||
### Closures
|
||||
Bob supports lexical closures with variable capture:
|
||||
|
||||
```bob
|
||||
var outerVar = "Outer";
|
||||
|
||||
func makeGreeter(greeting) {
|
||||
return greeting + " " + outerVar;
|
||||
}
|
||||
|
||||
var greeter = makeGreeter("Hello");
|
||||
// greeter captures outerVar from its lexical scope
|
||||
```
|
||||
|
||||
### Nested Functions
|
||||
```bob
|
||||
func outer() {
|
||||
func inner() {
|
||||
return 42;
|
||||
}
|
||||
return inner();
|
||||
}
|
||||
```
|
||||
|
||||
## Control Flow
|
||||
|
||||
### Current Status
|
||||
**Control flow statements are NOT implemented yet:**
|
||||
- `if` statements
|
||||
- `while` loops
|
||||
- `for` loops
|
||||
- `else` clauses
|
||||
|
||||
### Planned Features
|
||||
- Conditional execution
|
||||
- Looping constructs
|
||||
- Logical operators (`and`, `or`, `not`)
|
||||
|
||||
## Standard Library
|
||||
|
||||
### Print Function
|
||||
```bob
|
||||
print("Hello, World!");
|
||||
print(42);
|
||||
print(true);
|
||||
```
|
||||
|
||||
**Usage**: `print(expression)`
|
||||
- Prints the result of any expression
|
||||
- Automatically converts values to strings
|
||||
- Adds newline after output
|
||||
|
||||
### Assert Function
|
||||
```bob
|
||||
assert(condition, "optional message");
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
- `assert(true)` - passes silently
|
||||
- `assert(false)` - throws error and stops execution
|
||||
- `assert(condition, "message")` - includes custom error message
|
||||
|
||||
**Behavior**:
|
||||
- Terminates program execution on failure
|
||||
- No exception handling mechanism (yet)
|
||||
- Useful for testing and validation
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Current Error Types
|
||||
- **Division by zero**: `DivisionByZeroError`
|
||||
- **Type errors**: `Operands must be of same type`
|
||||
- **String multiplication**: `String multiplier must be whole number`
|
||||
- **Assertion failures**: `Assertion failed: condition is false`
|
||||
|
||||
### Error Behavior
|
||||
- **No try-catch**: Exception handling not implemented
|
||||
- **Program termination**: Errors stop execution immediately
|
||||
- **Error messages**: Descriptive error messages printed to console
|
||||
|
||||
### Common Error Scenarios
|
||||
```bob
|
||||
// Division by zero
|
||||
10 / 0; // Error: DivisionByZeroError
|
||||
|
||||
// Type mismatch
|
||||
"hello" - "world"; // Error: Cannot use '-' on two strings
|
||||
|
||||
// Invalid string multiplication
|
||||
"hello" * 3.5; // Error: String multiplier must be whole number
|
||||
|
||||
// Undefined variable
|
||||
undefinedVar; // Error: Undefined variable
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Calculator
|
||||
```bob
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
func multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
var result = add(5, multiply(3, 4));
|
||||
print("Result: " + result); // Result: 17
|
||||
```
|
||||
|
||||
### String Processing
|
||||
```bob
|
||||
func greet(name) {
|
||||
return "Hello, " + name + "!";
|
||||
}
|
||||
|
||||
func repeat(str, count) {
|
||||
return str * count;
|
||||
}
|
||||
|
||||
var greeting = greet("Bob");
|
||||
var repeated = repeat("Ha", 3);
|
||||
print(greeting + " " + repeated); // Hello, Bob! HaHaHa
|
||||
```
|
||||
|
||||
### Variable Scoping Example
|
||||
```bob
|
||||
var globalCounter = 0;
|
||||
|
||||
func increment() {
|
||||
var localCounter = 1;
|
||||
globalCounter = globalCounter + localCounter;
|
||||
return globalCounter;
|
||||
}
|
||||
|
||||
print("Before: " + globalCounter); // Before: 0
|
||||
increment();
|
||||
print("After: " + globalCounter); // After: 1
|
||||
```
|
||||
|
||||
## Language Nuances
|
||||
|
||||
### Type System
|
||||
- **Dynamic typing**: Variables can hold any type
|
||||
- **Automatic conversion**: Numbers and strings convert automatically
|
||||
- **No type annotations**: Types are inferred at runtime
|
||||
|
||||
### Memory Management
|
||||
- **Reference counting**: Uses `std::shared_ptr` for automatic memory management
|
||||
- **No manual memory management**: No `delete` or `free` needed
|
||||
- **Garbage collection**: Automatic cleanup of unused objects
|
||||
|
||||
### Performance Characteristics
|
||||
- **Interpreted**: Code is executed by an interpreter
|
||||
- **AST-based**: Abstract Syntax Tree for execution
|
||||
- **No compilation**: Direct interpretation of source code
|
||||
|
||||
### Syntax Rules
|
||||
- **Semicolons**: Required at end of statements
|
||||
- **Parentheses**: Required for function calls
|
||||
- **Curly braces**: Required for function bodies
|
||||
- **Case sensitive**: `var` and `Var` are different
|
||||
|
||||
### Comparison with Other Languages
|
||||
|
||||
#### vs Python
|
||||
- **Similar**: Dynamic typing, functions, closures
|
||||
- **Different**: No `global` keyword, automatic string conversion, different error handling
|
||||
|
||||
#### vs JavaScript
|
||||
- **Similar**: Automatic type conversion, string concatenation
|
||||
- **Different**: No `undefined`, different syntax, no `null`
|
||||
|
||||
#### vs Lua
|
||||
- **Similar**: Dynamic typing, functions
|
||||
- **Different**: No `local` keyword, different scoping rules
|
||||
|
||||
### Limitations
|
||||
- **No control flow**: No if/while/for statements
|
||||
- **No logical operators**: No `and`/`or`/`not`
|
||||
- **No exception handling**: No try-catch blocks
|
||||
- **No modules**: No import/export system
|
||||
- **No classes**: No object-oriented features
|
||||
- **No arrays/lists**: No built-in collection types
|
||||
- **No dictionaries**: No key-value data structures
|
||||
|
||||
### Future Features
|
||||
- Control flow statements
|
||||
- Logical operators
|
||||
- Exception handling
|
||||
- Collection types (arrays, dictionaries)
|
||||
- Modules and imports
|
||||
- Object-oriented programming
|
||||
- Standard library expansion
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Running Bob Code
|
||||
```bash
|
||||
# Compile the interpreter
|
||||
make
|
||||
|
||||
# Run a Bob file
|
||||
./build/bob your_file.bob
|
||||
|
||||
# Run the comprehensive test suite
|
||||
./build/bob test_bob_language.bob
|
||||
```
|
||||
|
||||
### File Extension
|
||||
- **`.bob`**: Standard file extension for Bob source code
|
||||
|
||||
### Interactive Mode
|
||||
- **Not implemented**: No REPL (Read-Eval-Print Loop) yet
|
||||
- **File-based**: All code must be in `.bob` files
|
||||
|
||||
---
|
||||
|
||||
*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.*
|
||||
163
CMakeLists.txt
Normal file
163
CMakeLists.txt
Normal file
@ -0,0 +1,163 @@
|
||||
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
48
Makefile
@ -1,48 +0,0 @@
|
||||
# Makefile
|
||||
|
||||
# Compiler
|
||||
CC = g++
|
||||
|
||||
# Compiler flags
|
||||
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
|
||||
|
||||
# Source directory
|
||||
SRC_DIR = ./source
|
||||
|
||||
# Output directory
|
||||
BUILD_DIR = ./build
|
||||
|
||||
# Find all C++ files recursively in the source directory
|
||||
CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp')
|
||||
|
||||
# Generate object file names by replacing the source directory with the build directory
|
||||
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||
|
||||
# Create directories for object files
|
||||
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
|
||||
# Rule to create necessary directories
|
||||
$(DIRS):
|
||||
mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@)
|
||||
|
||||
# Rule to compile object files
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Rule to link object files into the final executable
|
||||
$(BUILD_DIR)/bob: $(OBJ_FILES)
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
|
||||
run:
|
||||
./$(BUILD_DIR)/bob
|
||||
|
||||
build: clean $(BUILD_DIR)/bob
|
||||
|
||||
|
||||
# Clean build directory
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)/*
|
||||
38
README.md
38
README.md
@ -1 +1,37 @@
|
||||
# Bob
|
||||
```
|
||||
██████╗ ██████╗ ██████╗
|
||||
██╔══██╗██╔═══██╗██╔══██╗
|
||||
██████╔╝██║ ██║██████╔╝
|
||||
██╔══██╗██║ ██║██╔══██╗
|
||||
██████╔╝╚██████╔╝██████╔╝
|
||||
╚═════╝ ╚═════╝ ╚═════╝
|
||||
```
|
||||
|
||||
A modern programming language with all the features/sytax I prefer
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[Language Reference](Reference/BOB_LANGUAGE_REFERENCE.md)** - Language syntax and features
|
||||
- **[Build Guide](Reference/BUILD.md)** - How to build Bob
|
||||
- **[Roadmap](Reference/ROADMAP.md)** - What's done and what might come next
|
||||
|
||||
## Features
|
||||
|
||||
- **Core Types**: Numbers, strings, booleans, arrays, dictionaries, functions
|
||||
- **Advanced Functions**: First-class functions, closures, anonymous functions, tail call optimization
|
||||
- **Control Flow**: If/else statements, while/do-while/for loops, break/continue
|
||||
- **Operators**: Arithmetic, logical, bitwise, comparison, compound assignment (+=, -=, etc.)
|
||||
- **Built-in Functions**: print, input, assert, len, push, pop, keys, values, type conversion (toString, toNumber, toInt)
|
||||
- **Other Stuff**: String interpolation, escape sequences, file I/O, eval, time/sleep, random
|
||||
- **Memory**: Automatic cleanup, no manual memory management
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Build
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
|
||||
# Run
|
||||
./build/bin/bob your_file.bob
|
||||
```
|
||||
203
ROADMAP.md
203
ROADMAP.md
@ -1,203 +0,0 @@
|
||||
# Bob Language Development Roadmap
|
||||
|
||||
## Current Status
|
||||
- Basic expressions (arithmetic, comparison, logical)
|
||||
- Variables and assignment
|
||||
- Print statements (converted to standard library function)
|
||||
- Block statements
|
||||
- Environment/scoping
|
||||
- Function implementation (COMPLETED)
|
||||
- Return statements (COMPLETED)
|
||||
- Closures (COMPLETED)
|
||||
- Assert function (COMPLETED)
|
||||
- Standard library infrastructure (COMPLETED)
|
||||
- First-class functions and higher-order functions (COMPLETED)
|
||||
- String + number concatenation with smart formatting (COMPLETED)
|
||||
- String multiplication (COMPLETED)
|
||||
- Alphanumeric identifiers (COMPLETED)
|
||||
- Comprehensive testing framework (COMPLETED)
|
||||
|
||||
## Phase 1: Core Language Features (High Priority)
|
||||
|
||||
### 1. Control Flow
|
||||
```bob
|
||||
// If statements
|
||||
if (x > 10) {
|
||||
print "big";
|
||||
} else {
|
||||
print "small";
|
||||
}
|
||||
|
||||
// While loops
|
||||
var i = 0;
|
||||
while (i < 5) {
|
||||
print i;
|
||||
i = i + 1;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Add `IfStmt` and `WhileStmt` to Statement.h
|
||||
- Update parser to handle `if` and `while` keywords
|
||||
- Implement control flow in interpreter
|
||||
|
||||
### 2. Logical Operators
|
||||
```bob
|
||||
// Currently missing: and, or, not operators
|
||||
if (x > 0 and y < 10) {
|
||||
print "valid range";
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Add `and`, `or`, `not` operator parsing
|
||||
- Implement logical operator evaluation in interpreter
|
||||
|
||||
### 3. Better Error Handling
|
||||
```bob
|
||||
// Current: Basic error messages
|
||||
// Goal: Line numbers, better context
|
||||
Error at line 5: Expected ';' after expression
|
||||
print 42
|
||||
^
|
||||
```
|
||||
|
||||
## Phase 2: Data Structures (Medium Priority)
|
||||
|
||||
### 4. Arrays/Lists
|
||||
```bob
|
||||
var numbers = [1, 2, 3, 4];
|
||||
print numbers[0]; // 1
|
||||
numbers[1] = 42;
|
||||
```
|
||||
|
||||
### 5. Maps/Dictionaries
|
||||
```bob
|
||||
var person = {"name": "Bob", "age": 25};
|
||||
print person["name"];
|
||||
person["city"] = "NYC";
|
||||
```
|
||||
|
||||
## Phase 3: Standard Library (Medium Priority)
|
||||
|
||||
### 6. Additional Built-in Functions
|
||||
```bob
|
||||
len("hello"); // String length
|
||||
input("Enter name: "); // User input
|
||||
random(1, 100); // Random numbers
|
||||
type(42); // Type checking
|
||||
```
|
||||
|
||||
### 7. File I/O
|
||||
```bob
|
||||
var content = readFile("data.txt");
|
||||
writeFile("output.txt", "Hello World");
|
||||
```
|
||||
|
||||
## Phase 4: Advanced Features (Lower Priority)
|
||||
|
||||
### 8. Classes & Objects
|
||||
```bob
|
||||
class Person {
|
||||
init(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
greet() {
|
||||
print "Hello, I'm " + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
var bob = Person("Bob", 25);
|
||||
bob.greet();
|
||||
```
|
||||
|
||||
### 9. Modules/Imports
|
||||
```bob
|
||||
import "math.bob";
|
||||
import "utils.bob";
|
||||
```
|
||||
|
||||
### 10. Type System
|
||||
```bob
|
||||
// Optional type annotations
|
||||
fun add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Tips
|
||||
|
||||
### For Each Feature:
|
||||
1. Lexer: Add new tokens if needed
|
||||
2. Parser: Add new expression/statement types
|
||||
3. AST: Define new node types
|
||||
4. Interpreter: Implement evaluation logic
|
||||
5. Test: Create test cases using assert function
|
||||
|
||||
### Testing Strategy:
|
||||
```bob
|
||||
// Use the new assert function for comprehensive testing
|
||||
assert(add(2, 3) == 5, "add(2, 3) should equal 5");
|
||||
assert(x > 0, "x should be positive");
|
||||
```
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
1. Add if statements (fundamental control flow)
|
||||
2. Add while loops (enables iteration)
|
||||
3. Implement logical operators (and, or, not)
|
||||
4. Improve error messages (better developer experience)
|
||||
5. Add arrays (most useful data structure)
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- [x] Can write simple functions
|
||||
- [x] Can use return statements
|
||||
- [x] Can use closures
|
||||
- [x] Has assert function for testing
|
||||
- [x] Has standard library infrastructure
|
||||
- [x] Supports first-class functions
|
||||
- [x] Has comprehensive testing framework
|
||||
- [ ] Can use if/else statements
|
||||
- [ ] Can use while loops
|
||||
- [ ] Can use logical operators
|
||||
- [ ] Can work with arrays
|
||||
- [ ] Can read/write files
|
||||
- [ ] Has good error messages
|
||||
|
||||
## Resources
|
||||
|
||||
- [Crafting Interpreters](https://craftinginterpreters.com/) - Excellent resource for language implementation
|
||||
- [Bob's current source code](./source/) - Your implementation
|
||||
- [Test files](./*.bob) - Examples of current functionality
|
||||
|
||||
## Recent Achievements
|
||||
|
||||
### Function Implementation (COMPLETED)
|
||||
- Function declarations with parameters
|
||||
- Function calls with arguments
|
||||
- Return statements
|
||||
- Proper scoping and closures
|
||||
- Nested function calls
|
||||
|
||||
### Standard Library (COMPLETED)
|
||||
- `print()` function (converted from statement)
|
||||
- `assert()` function with custom messages
|
||||
- Extensible architecture for adding more functions
|
||||
|
||||
### Testing Framework (COMPLETED)
|
||||
- Comprehensive test suite using assert
|
||||
- Tests for all language features
|
||||
- Proper error handling and execution stopping
|
||||
|
||||
### Advanced Language Features (COMPLETED)
|
||||
- First-class functions and higher-order functions
|
||||
- Function passing as arguments
|
||||
- Function composition patterns
|
||||
- Callback patterns and function storage
|
||||
- String + number concatenation with smart formatting
|
||||
- String multiplication (string * number, number * string)
|
||||
- Alphanumeric identifiers support
|
||||
- Stress testing with 100-parameter functions
|
||||
474
Reference/BOB_LANGUAGE_REFERENCE.md
Normal file
474
Reference/BOB_LANGUAGE_REFERENCE.md
Normal file
@ -0,0 +1,474 @@
|
||||
# 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 auto‑bind `this`; call via `obj.method(...)` when needed.
|
||||
|
||||
### Extensions (Built‑ins and Classes)
|
||||
Extend existing types (including built‑ins) 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) → built‑in 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`*
|
||||
248
Reference/BUILD.md
Normal file
248
Reference/BUILD.md
Normal file
@ -0,0 +1,248 @@
|
||||
# Bob Language - Build Guide
|
||||
|
||||
This guide describes how to build the Bob language interpreter using CMake + Ninja across different platforms.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- **CMake** 3.20 or later
|
||||
- **Ninja** build system
|
||||
- **C++17** compatible compiler
|
||||
|
||||
### Platform-Specific Setup
|
||||
|
||||
#### 🍎 macOS
|
||||
```bash
|
||||
# Install via Homebrew
|
||||
brew install cmake ninja
|
||||
|
||||
# Or via MacPorts
|
||||
sudo port install cmake ninja
|
||||
```
|
||||
|
||||
#### 🐧 Linux
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install cmake ninja-build build-essential
|
||||
```
|
||||
|
||||
**RHEL/CentOS/Fedora:**
|
||||
```bash
|
||||
# RHEL/CentOS
|
||||
sudo yum install cmake ninja-build gcc-c++
|
||||
|
||||
# Fedora
|
||||
sudo dnf install cmake ninja-build gcc-c++
|
||||
```
|
||||
|
||||
**Arch Linux:**
|
||||
```bash
|
||||
sudo pacman -S cmake ninja gcc
|
||||
```
|
||||
|
||||
#### 🪟 Windows
|
||||
|
||||
**Option 1: Visual Studio (Recommended)**
|
||||
- Install Visual Studio 2019+ with C++ workload
|
||||
- Install CMake via Visual Studio Installer
|
||||
- Install Ninja: `winget install Ninja-build.Ninja`
|
||||
|
||||
**Option 2: MSYS2/MinGW**
|
||||
```bash
|
||||
# In MSYS2 terminal
|
||||
pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc
|
||||
```
|
||||
|
||||
**Option 3: Chocolatey**
|
||||
```powershell
|
||||
# In Administrator PowerShell
|
||||
choco install cmake ninja visualstudio2022buildtools
|
||||
```
|
||||
|
||||
## 🔨 Build Commands
|
||||
|
||||
### Standard Build
|
||||
|
||||
**Release Build (Optimized):**
|
||||
```bash
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
**Debug Build (Development):**
|
||||
```bash
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Debug
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
### Platform-Specific Examples
|
||||
|
||||
#### macOS/Linux
|
||||
```bash
|
||||
# Configure and build
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
|
||||
# Run interpreter
|
||||
./build/bin/bob
|
||||
|
||||
# Run test suite
|
||||
./build/bin/bob test_bob_language.bob
|
||||
|
||||
# Run with custom script
|
||||
./build/bin/bob your_script.bob
|
||||
```
|
||||
|
||||
#### Windows (PowerShell/CMD)
|
||||
```powershell
|
||||
# Configure and build
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
|
||||
# Run interpreter
|
||||
.\build\bin\bob.exe
|
||||
|
||||
# Run test suite
|
||||
.\build\bin\bob.exe test_bob_language.bob
|
||||
|
||||
# Run with custom script
|
||||
.\build\bin\bob.exe your_script.bob
|
||||
```
|
||||
|
||||
#### Windows (MSYS2/Git Bash)
|
||||
```bash
|
||||
# Same as macOS/Linux
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
./build/bin/bob.exe test_bob_language.bob
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Automated Testing
|
||||
|
||||
**All Platforms:**
|
||||
```bash
|
||||
# Build first
|
||||
ninja -C build
|
||||
|
||||
# Run tests via CTest
|
||||
cd build
|
||||
ctest --output-on-failure
|
||||
|
||||
# Or run tests verbosely
|
||||
ctest --verbose
|
||||
```
|
||||
|
||||
**Windows PowerShell:**
|
||||
```powershell
|
||||
ninja -C build
|
||||
cd build
|
||||
ctest --output-on-failure
|
||||
```
|
||||
|
||||
### Manual Testing
|
||||
|
||||
**Interactive Mode:**
|
||||
```bash
|
||||
# Unix-like systems
|
||||
./build/bin/bob
|
||||
|
||||
# Windows
|
||||
.\build\bin\bob.exe
|
||||
```
|
||||
|
||||
**Script Execution:**
|
||||
```bash
|
||||
# Unix-like systems
|
||||
./build/bin/bob examples/hello.bob
|
||||
|
||||
# Windows
|
||||
.\build\bin\bob.exe examples\hello.bob
|
||||
```
|
||||
|
||||
## ⚡ Performance
|
||||
|
||||
**CMake + Ninja** provides fast, cross-platform builds with excellent incremental compilation.
|
||||
|
||||
## 🔧 Advanced Configuration
|
||||
|
||||
### Custom Install Location
|
||||
|
||||
**Unix-like:**
|
||||
```bash
|
||||
cmake -G Ninja -B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/bob
|
||||
ninja -C build install
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
cmake -G Ninja -B build `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
-DCMAKE_INSTALL_PREFIX="C:\Program Files\Bob"
|
||||
ninja -C build install
|
||||
```
|
||||
|
||||
### Compiler Selection
|
||||
|
||||
**GCC:**
|
||||
```bash
|
||||
cmake -G Ninja -B build \
|
||||
-DCMAKE_CXX_COMPILER=g++ \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
|
||||
**Clang:**
|
||||
```bash
|
||||
cmake -G Ninja -B build \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
|
||||
**MSVC (Windows):**
|
||||
```powershell
|
||||
cmake -G Ninja -B build `
|
||||
-DCMAKE_CXX_COMPILER=cl `
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**CMake not found:**
|
||||
- **macOS**: `brew install cmake`
|
||||
- **Ubuntu**: `sudo apt install cmake`
|
||||
- **Windows**: Install via Visual Studio or winget
|
||||
|
||||
**Ninja not found:**
|
||||
- **macOS**: `brew install ninja`
|
||||
- **Ubuntu**: `sudo apt install ninja-build`
|
||||
- **Windows**: `winget install Ninja-build.Ninja`
|
||||
|
||||
**Compiler errors:**
|
||||
- Ensure C++17 compiler is installed
|
||||
- **Linux**: `sudo apt install build-essential`
|
||||
- **Windows**: Install Visual Studio Build Tools
|
||||
|
||||
**Permission denied (Windows):**
|
||||
- Run PowerShell as Administrator for system-wide installs
|
||||
|
||||
### Build Cache Issues
|
||||
|
||||
**Clean build:**
|
||||
```bash
|
||||
# Remove build directory
|
||||
rm -rf build # Unix-like
|
||||
rmdir /s build # Windows CMD
|
||||
Remove-Item -Recurse build # PowerShell
|
||||
|
||||
# Reconfigure
|
||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
113
Reference/EMBEDDING.md
Normal file
113
Reference/EMBEDDING.md
Normal file
@ -0,0 +1,113 @@
|
||||
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.
|
||||
|
||||
|
||||
|
||||
312
Reference/ROADMAP.md
Normal file
312
Reference/ROADMAP.md
Normal file
@ -0,0 +1,312 @@
|
||||
# Bob Language Development Roadmap
|
||||
|
||||
## Current Status
|
||||
|
||||
Bob is a mature, working programming language with a modern architecture and comprehensive feature set.
|
||||
|
||||
### ✅ **Core Language Features (Complete)**
|
||||
|
||||
#### **Data Types & Variables**
|
||||
- **Numbers**: Integers, floats, automatic conversion
|
||||
- **Strings**: Literals, concatenation, multiplication, escape sequences
|
||||
- **Booleans**: `true`, `false`
|
||||
- **None**: Null value representation
|
||||
- **Arrays**: Dynamic arrays with indexing, assignment, and built-in functions
|
||||
- **Dictionaries**: Hash maps with string keys and mixed-type values
|
||||
- **Functions**: First-class functions as values
|
||||
- **Variables**: Declaration, assignment, scoping
|
||||
- **Assignment System**: Dual system (statements + expressions for loops only)
|
||||
|
||||
#### **Operators (Complete)**
|
||||
- **Arithmetic**: `+`, `-`, `*`, `/`, `%`, unary `-`
|
||||
- **Comparison**: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
||||
- **Logical**: `&&`, `||`, `!` (with short-circuit evaluation)
|
||||
- **Bitwise**: `&`, `|`, `^`, `<<`, `>>`, `~`
|
||||
- **Compound Assignment**: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
||||
- **Ternary**: `condition ? valueIfTrue : valueIfFalse`
|
||||
- **Increment/Decrement**: `++`, `--` (for array elements)
|
||||
|
||||
#### **Control Flow (Complete)**
|
||||
- **If Statements**: `if`, `else`, `else if` chains
|
||||
- **While Loops**: Basic, nested, complex conditions
|
||||
- **Do-While Loops**: Basic, nested, break/continue support
|
||||
- **For Loops**: All clause variations, nested loops
|
||||
- **Break/Continue**: Full support in all loop types
|
||||
|
||||
#### **Functions (Complete)**
|
||||
- **Function Declaration**: `func name(params) { body }`
|
||||
- **Parameters**: Any number of parameters (tested up to 100)
|
||||
- **Return Values**: Explicit and implicit returns
|
||||
- **Closures**: Lexical scoping with variable capture
|
||||
- **First-Class Functions**: Functions as values, parameters, return values
|
||||
- **Anonymous Functions**: `func(params) { body }`
|
||||
- **Nested Functions**: Functions defined inside other functions
|
||||
- **Recursion**: Full support including deep recursion
|
||||
- **Tail Call Optimization**: Trampoline-based optimization preventing stack overflow
|
||||
|
||||
#### **Data Structures (Complete)**
|
||||
- **Arrays**: Full support with indexing, assignment, nested arrays
|
||||
- **Dictionaries**: Full support with key-value pairs, nested dictionaries
|
||||
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment, properties (`length`, `first`, `last`, `empty`)
|
||||
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment, dot notation (`obj.prop`)
|
||||
- **Mixed Types**: Arrays and dictionaries can hold any value types
|
||||
|
||||
#### **Standard Library (Complete)**
|
||||
- **I/O Functions**: `print()`, `printRaw()`, `input()`
|
||||
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
|
||||
- **Testing**: `assert()` with custom error messages
|
||||
- **Timing**: `time()` (microsecond precision), `sleep()`
|
||||
- **Utility**: `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
30
benchmark.py
@ -1,30 +0,0 @@
|
||||
import time
|
||||
|
||||
# Test the time function
|
||||
print("Testing time function:")
|
||||
|
||||
time1 = time.time()
|
||||
print(f"Time 1: {time1}")
|
||||
|
||||
time2 = time.time()
|
||||
print(f"Time 2: {time2}")
|
||||
|
||||
time3 = time.time()
|
||||
print(f"Time 3: {time3}")
|
||||
|
||||
diff1 = time2 - time1
|
||||
diff2 = time3 - time2
|
||||
|
||||
print(f"Difference 1-2: {diff1} seconds")
|
||||
print(f"Difference 2-3: {diff2} seconds")
|
||||
|
||||
# Test with some work in between
|
||||
start = time.time()
|
||||
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
|
||||
end = time.time()
|
||||
duration = end - start
|
||||
|
||||
print(f"Work duration: {duration} seconds")
|
||||
print(f"Sum: {sum_val}")
|
||||
|
||||
print("Time function analysis complete!")
|
||||
10
bob-language-extension/.vscodeignore
Normal file
10
bob-language-extension/.vscodeignore
Normal file
@ -0,0 +1,10 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
src/**
|
||||
.gitignore
|
||||
.yarnrc
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
1
bob-language-extension/.vsix-version
Normal file
1
bob-language-extension/.vsix-version
Normal file
@ -0,0 +1 @@
|
||||
bob-language-0.2.0.vsix
|
||||
97
bob-language-extension/INSTALLATION.md
Normal file
97
bob-language-extension/INSTALLATION.md
Normal file
@ -0,0 +1,97 @@
|
||||
# Bob Language Extension Installation Guide
|
||||
|
||||
## Quick Installation
|
||||
|
||||
### Option 1: Automatic Installation (Recommended)
|
||||
```bash
|
||||
cd bob-language-extension
|
||||
./install.sh
|
||||
```
|
||||
|
||||
### Option 2: Manual Installation
|
||||
1. Copy the extension files to your VS Code extensions directory:
|
||||
- **macOS**: `~/.vscode/extensions/bob-language-0.1.0/`
|
||||
- **Linux**: `~/.vscode/extensions/bob-language-0.1.0/`
|
||||
- **Windows**: `%APPDATA%\Code\User\extensions\bob-language-0.1.0\`
|
||||
|
||||
2. Restart VS Code/Cursor
|
||||
|
||||
### Option 3: VSIX Package
|
||||
```bash
|
||||
cd bob-language-extension
|
||||
./package-vsix.sh
|
||||
```
|
||||
Then install the generated `.vsix` file in VS Code/Cursor.
|
||||
|
||||
## Features Included
|
||||
|
||||
### ✅ Syntax Highlighting
|
||||
- Keywords: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, `var`, `func`
|
||||
- Built-in functions: `print`, `assert`, `input`, `type`, `toString`, `toNumber`, `time`
|
||||
- Data types: numbers, strings, booleans, `none`
|
||||
- Operators: arithmetic, comparison, logical, bitwise, compound assignment
|
||||
|
||||
### ✅ Code Snippets
|
||||
Type these prefixes and press `Tab`:
|
||||
- `func` - Function definition
|
||||
- `if` - If statement
|
||||
- `while` - While loop
|
||||
- `for` - For loop
|
||||
- `var` - Variable declaration
|
||||
- `print` - Print statement
|
||||
- `assert` - Assert statement
|
||||
- `anon` - Anonymous function
|
||||
- `test` - Test function
|
||||
|
||||
### ✅ Language Features
|
||||
- Auto-closing brackets and quotes
|
||||
- Smart indentation
|
||||
- Comment support (`//` and `/* */`)
|
||||
- Code folding
|
||||
- Hover information for built-in functions
|
||||
- IntelliSense completion
|
||||
|
||||
### ✅ Color Theme
|
||||
- "Bob Dark" theme included
|
||||
- Optimized colors for Bob syntax
|
||||
|
||||
## Testing the Extension
|
||||
|
||||
1. Open VS Code/Cursor
|
||||
2. Open the `example.bob` file in the extension directory
|
||||
3. You should see syntax highlighting, code completion, and snippets working
|
||||
|
||||
## File Association
|
||||
|
||||
Files with `.bob` extension will automatically be recognized as Bob language files.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension not working?
|
||||
1. Check that the extension is installed in the correct directory
|
||||
2. Restart VS Code/Cursor completely
|
||||
3. Check the Extensions panel (Ctrl+Shift+X) to see if the extension is listed
|
||||
|
||||
### Syntax highlighting not working?
|
||||
1. Make sure your file has a `.bob` extension
|
||||
2. Check the language mode in the bottom-right corner of VS Code
|
||||
3. Manually select "Bob" as the language mode if needed
|
||||
|
||||
### Snippets not working?
|
||||
1. Type the snippet prefix (e.g., `func`)
|
||||
2. Press `Ctrl+Space` to trigger suggestions
|
||||
3. Select the snippet and press `Tab`
|
||||
|
||||
## Development
|
||||
|
||||
To modify the extension:
|
||||
1. Edit the files in the extension directory
|
||||
2. Run `npm run compile` to rebuild TypeScript
|
||||
3. Reload VS Code/Cursor to see changes
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the README.md file
|
||||
2. Look at the example.bob file for syntax examples
|
||||
3. Review the TextMate grammar in `syntaxes/bob.tmLanguage.json`
|
||||
132
bob-language-extension/README.md
Normal file
132
bob-language-extension/README.md
Normal file
@ -0,0 +1,132 @@
|
||||
# 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)
|
||||
BIN
bob-language-extension/bob-language-0.2.0.vsix
Normal file
BIN
bob-language-extension/bob-language-0.2.0.vsix
Normal file
Binary file not shown.
BIN
bob-language-extension/bob-language-0.5.0.vsix
Normal file
BIN
bob-language-extension/bob-language-0.5.0.vsix
Normal file
Binary file not shown.
44
bob-language-extension/create-vsix.sh
Executable file
44
bob-language-extension/create-vsix.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create a simple VSIX package without npm
|
||||
|
||||
echo "Creating Bob Language Extension VSIX package..."
|
||||
|
||||
# Read version from package.json
|
||||
VERSION=$(grep '"version"' package.json | sed 's/.*"version": "\([^"]*\)".*/\1/')
|
||||
echo "Building version: $VERSION"
|
||||
|
||||
# Create a temporary directory for the package
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
PACKAGE_DIR="$TEMP_DIR/bob-language-$VERSION"
|
||||
|
||||
# Create the extension directory structure
|
||||
mkdir -p "$TEMP_DIR/extension"
|
||||
|
||||
# Copy all extension files to the extension directory
|
||||
cp package.json "$TEMP_DIR/extension/"
|
||||
cp language-configuration.json "$TEMP_DIR/extension/"
|
||||
cp -r syntaxes "$TEMP_DIR/extension/"
|
||||
cp -r snippets "$TEMP_DIR/extension/"
|
||||
cp -r themes "$TEMP_DIR/extension/"
|
||||
cp README.md "$TEMP_DIR/extension/"
|
||||
|
||||
# Create the VSIX file (simple zip with .vsix extension)
|
||||
cd "$TEMP_DIR"
|
||||
zip -r "bob-language-$VERSION.vsix" extension/
|
||||
|
||||
# Move to the extension directory
|
||||
mv "bob-language-$VERSION.vsix" /Users/bobbylucero/Developer/Bob/bob-language-extension/
|
||||
|
||||
# Clean up
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "VSIX package created: bob-language-$VERSION.vsix"
|
||||
echo ""
|
||||
echo "To install in Cursor:"
|
||||
echo "1. Open Cursor"
|
||||
echo "2. Go to Extensions (Cmd+Shift+X)"
|
||||
echo "3. Click the '...' menu and select 'Install from VSIX...'"
|
||||
echo "4. Select the bob-language-$VERSION.vsix file"
|
||||
echo ""
|
||||
echo "Or simply restart Cursor - the extension should already be working!"
|
||||
111
bob-language-extension/example.bob
Normal file
111
bob-language-extension/example.bob
Normal file
@ -0,0 +1,111 @@
|
||||
// Bob Language Example - Demonstrates syntax highlighting
|
||||
|
||||
// Variable declarations
|
||||
var message = "Hello, Bob!";
|
||||
var number = 42;
|
||||
var pi = 3.14159;
|
||||
var isActive = true;
|
||||
var empty = none;
|
||||
|
||||
// Binary and hex numbers
|
||||
var binaryNum = 0b1010; // 10 in decimal
|
||||
var hexNum = 0xFF; // 255 in decimal
|
||||
|
||||
// Print statements
|
||||
print(message);
|
||||
print("Number: " + toString(number));
|
||||
print("Pi: " + toString(pi));
|
||||
|
||||
// Array operations
|
||||
var numbers = [1, 2, 3, 4, 5];
|
||||
var fruits = ["apple", "banana", "cherry"];
|
||||
|
||||
print("Array length: " + 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!");
|
||||
66
bob-language-extension/install.sh
Executable file
66
bob-language-extension/install.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Bob Language Extension Installer for VS Code/Cursor
|
||||
|
||||
echo "Installing Bob Language Extension..."
|
||||
|
||||
# Get VS Code extensions directory
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions"
|
||||
CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions"
|
||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
# Linux
|
||||
VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions"
|
||||
CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions"
|
||||
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
|
||||
# Windows
|
||||
VSCODE_EXTENSIONS_DIR="$APPDATA/Code/User/extensions"
|
||||
CURSOR_EXTENSIONS_DIR="$APPDATA/Cursor/User/extensions"
|
||||
else
|
||||
echo "Unsupported operating system: $OSTYPE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create extension directory
|
||||
EXTENSION_NAME="bob-language-0.1.0"
|
||||
EXTENSION_DIR="$VSCODE_EXTENSIONS_DIR/$EXTENSION_NAME"
|
||||
|
||||
echo "Installing to: $EXTENSION_DIR"
|
||||
|
||||
# Create directories
|
||||
mkdir -p "$EXTENSION_DIR"
|
||||
|
||||
# Copy extension files
|
||||
cp -r package.json "$EXTENSION_DIR/"
|
||||
cp -r language-configuration.json "$EXTENSION_DIR/"
|
||||
cp -r syntaxes "$EXTENSION_DIR/"
|
||||
cp -r snippets "$EXTENSION_DIR/"
|
||||
cp -r README.md "$EXTENSION_DIR/"
|
||||
|
||||
# Compile TypeScript if available
|
||||
if command -v npm &> /dev/null; then
|
||||
echo "Compiling TypeScript..."
|
||||
npm install
|
||||
npm run compile
|
||||
cp -r out "$EXTENSION_DIR/"
|
||||
else
|
||||
echo "npm not found, skipping TypeScript compilation"
|
||||
fi
|
||||
|
||||
echo "Bob Language Extension installed successfully!"
|
||||
echo ""
|
||||
echo "To use the extension:"
|
||||
echo "1. Restart VS Code/Cursor"
|
||||
echo "2. Open a .bob file"
|
||||
echo "3. Enjoy syntax highlighting and code snippets!"
|
||||
echo ""
|
||||
echo "Code snippets available:"
|
||||
echo "- func: Function definition"
|
||||
echo "- if: If statement"
|
||||
echo "- while: While loop"
|
||||
echo "- for: For loop"
|
||||
echo "- var: Variable declaration"
|
||||
echo "- print: Print statement"
|
||||
echo "- assert: Assert statement"
|
||||
echo "- And many more!"
|
||||
35
bob-language-extension/language-configuration.json
Normal file
35
bob-language-extension/language-configuration.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"comments": {
|
||||
"lineComment": "//",
|
||||
"blockComment": ["/*", "*/"]
|
||||
},
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
{ "open": "{", "close": "}" },
|
||||
{ "open": "[", "close": "]" },
|
||||
{ "open": "(", "close": ")" },
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string"] }
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
],
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for|class|extension)\\b.*$",
|
||||
"decreaseIndentPattern": "^\\s*[})]"
|
||||
},
|
||||
"folding": {
|
||||
"markers": {
|
||||
"start": "^\\s*//\\s*#?region\\b",
|
||||
"end": "^\\s*//\\s*#?endregion\\b"
|
||||
}
|
||||
}
|
||||
}
|
||||
27
bob-language-extension/package-vsix.sh
Executable file
27
bob-language-extension/package-vsix.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Package Bob Language Extension as VSIX
|
||||
|
||||
echo "Packaging Bob Language Extension..."
|
||||
|
||||
# Check if vsce is installed
|
||||
if ! command -v vsce &> /dev/null; then
|
||||
echo "Installing vsce..."
|
||||
npm install -g @vscode/vsce
|
||||
fi
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Compile TypeScript
|
||||
npm run compile
|
||||
|
||||
# Package the extension
|
||||
vsce package
|
||||
|
||||
echo "Extension packaged successfully!"
|
||||
echo "You can now install the .vsix file in VS Code/Cursor:"
|
||||
echo "1. Open VS Code/Cursor"
|
||||
echo "2. Go to Extensions (Ctrl+Shift+X)"
|
||||
echo "3. Click the '...' menu and select 'Install from VSIX...'"
|
||||
echo "4. Select the generated .vsix file"
|
||||
74
bob-language-extension/package.json
Normal file
74
bob-language-extension/package.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "bob-language",
|
||||
"displayName": "Bob Language",
|
||||
"description": "Syntax highlighting and language support for the Bob programming language - featuring arrays, dictionaries, auto-truncating float indices, increment/decrement operators, cross-type comparisons, and comprehensive built-in functions",
|
||||
"version": "0.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"
|
||||
}
|
||||
}
|
||||
29
bob-language-extension/reload-extension.sh
Executable file
29
bob-language-extension/reload-extension.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Attempting to reload Bob language extension..."
|
||||
|
||||
# Try to reload the extension using VS Code CLI if available
|
||||
if command -v code &> /dev/null; then
|
||||
echo "Found VS Code CLI, attempting to reload window..."
|
||||
code --command "workbench.action.reloadWindow"
|
||||
echo "Reload command sent to VS Code/Cursor"
|
||||
elif command -v cursor &> /dev/null; then
|
||||
echo "Found Cursor CLI, attempting to reload window..."
|
||||
cursor --command "workbench.action.reloadWindow"
|
||||
echo "Reload command sent to Cursor"
|
||||
else
|
||||
echo "VS Code/Cursor CLI not found in PATH"
|
||||
echo ""
|
||||
echo "Manual reload required:"
|
||||
echo "1. Open Cursor"
|
||||
echo "2. Press Cmd+Shift+P (or Ctrl+Shift+P on Windows/Linux)"
|
||||
echo "3. Type 'Developer: Reload Window' and press Enter"
|
||||
echo ""
|
||||
echo "Or alternatively:"
|
||||
echo "1. Press Cmd+Shift+X to open Extensions"
|
||||
echo "2. Find 'Bob Language' extension"
|
||||
echo "3. Click the reload button (circular arrow icon)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Extension should now be reloaded with updated syntax highlighting!"
|
||||
488
bob-language-extension/snippets/bob.json
Normal file
488
bob-language-extension/snippets/bob.json
Normal file
@ -0,0 +1,488 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
104
bob-language-extension/src/extension.ts
Normal file
104
bob-language-extension/src/extension.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Bob language extension is now active!');
|
||||
|
||||
// Register language configuration
|
||||
const bobLanguageConfig = vscode.languages.setLanguageConfiguration('bob', {
|
||||
comments: {
|
||||
lineComment: '//',
|
||||
blockComment: ['/*', '*/']
|
||||
},
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')']
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"', notIn: ['string'] },
|
||||
{ open: "'", close: "'", notIn: ['string'] }
|
||||
],
|
||||
surroundingPairs: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')'],
|
||||
['"', '"'],
|
||||
["'", "'"]
|
||||
],
|
||||
indentationRules: {
|
||||
increaseIndentPattern: /\{[^}]*$|\b(func|if|else|while|for)\b.*$/,
|
||||
decreaseIndentPattern: /^\s*[})]/
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(bobLanguageConfig);
|
||||
|
||||
// Register hover provider for built-in functions
|
||||
const hoverProvider = vscode.languages.registerHoverProvider('bob', {
|
||||
provideHover(document, position, token) {
|
||||
const range = document.getWordRangeAtPosition(position);
|
||||
const word = document.getText(range);
|
||||
|
||||
const builtinFunctions = {
|
||||
'print': 'Prints a value to the console',
|
||||
'assert': 'Asserts a condition and throws an error if false',
|
||||
'input': 'Gets user input from the console',
|
||||
'type': 'Returns the type of a value',
|
||||
'toString': 'Converts a value to a string',
|
||||
'toNumber': 'Converts a value to a number',
|
||||
'time': 'Returns the current time in microseconds'
|
||||
};
|
||||
|
||||
if (builtinFunctions[word]) {
|
||||
return new vscode.Hover(`**${word}()** - ${builtinFunctions[word]}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(hoverProvider);
|
||||
|
||||
// Register completion provider
|
||||
const completionProvider = vscode.languages.registerCompletionItemProvider('bob', {
|
||||
provideCompletionItems(document, position, token, context) {
|
||||
const completions = [];
|
||||
|
||||
// Keywords
|
||||
const keywords = ['if', 'else', 'while', 'for', 'break', 'continue', 'return', 'var', 'func'];
|
||||
keywords.forEach(keyword => {
|
||||
const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword);
|
||||
item.detail = 'Bob keyword';
|
||||
completions.push(item);
|
||||
});
|
||||
|
||||
// Built-in functions
|
||||
const builtins = ['print', 'assert', 'input', 'type', 'toString', 'toNumber', 'time'];
|
||||
builtins.forEach(func => {
|
||||
const item = new vscode.CompletionItem(func, vscode.CompletionItemKind.Function);
|
||||
item.detail = 'Built-in function';
|
||||
item.insertText = func + '()';
|
||||
completions.push(item);
|
||||
});
|
||||
|
||||
// Constants
|
||||
const constants = ['true', 'false', 'none'];
|
||||
constants.forEach(constant => {
|
||||
const item = new vscode.CompletionItem(constant, vscode.CompletionItemKind.Constant);
|
||||
item.detail = 'Bob constant';
|
||||
completions.push(item);
|
||||
});
|
||||
|
||||
return completions;
|
||||
}
|
||||
}, '.');
|
||||
|
||||
context.subscriptions.push(completionProvider);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
console.log('Bob language extension is now deactivated!');
|
||||
}
|
||||
273
bob-language-extension/syntaxes/bob.tmLanguage.json
Normal file
273
bob-language-extension/syntaxes/bob.tmLanguage.json
Normal file
@ -0,0 +1,273 @@
|
||||
{
|
||||
"name": "Bob",
|
||||
"scopeName": "source.bob",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#comments"
|
||||
},
|
||||
{
|
||||
"include": "#strings"
|
||||
},
|
||||
{
|
||||
"include": "#numbers"
|
||||
},
|
||||
{
|
||||
"include": "#arrays"
|
||||
},
|
||||
{
|
||||
"include": "#dictionaries"
|
||||
},
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{
|
||||
"include": "#functions"
|
||||
},
|
||||
{
|
||||
"include": "#variables"
|
||||
},
|
||||
{
|
||||
"include": "#operators"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"comments": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "comment.line.double-slash.bob",
|
||||
"match": "//.*$"
|
||||
},
|
||||
{
|
||||
"name": "comment.block.bob",
|
||||
"begin": "/\\*",
|
||||
"end": "\\*/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"strings": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "string.quoted.double.bob",
|
||||
"begin": "\"",
|
||||
"end": "\"",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bob",
|
||||
"match": "\\\\[nt\"\\\\e]"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "string.quoted.single.bob",
|
||||
"begin": "'",
|
||||
"end": "'",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bob",
|
||||
"match": "\\\\[nt'\\\\e]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"numbers": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.numeric.integer.bob",
|
||||
"match": "\\b\\d+\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.float.bob",
|
||||
"match": "\\b\\d+\\.\\d+\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.binary.bob",
|
||||
"match": "\\b0b[01]+\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.hex.bob",
|
||||
"match": "\\b0x[0-9a-fA-F]+\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"arrays": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.array.bob",
|
||||
"begin": "\\[",
|
||||
"end": "\\]",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#expressions"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "variable.other.array-index.bob",
|
||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\[([^\\]]+)\\]",
|
||||
"captures": {
|
||||
"1": { "name": "variable.other.bob" },
|
||||
"2": { "name": "constant.numeric.integer.bob" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dictionaries": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.dictionary.bob",
|
||||
"begin": "\\{",
|
||||
"end": "\\}",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "string.quoted.double.bob",
|
||||
"begin": "\"",
|
||||
"end": "\"",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bob",
|
||||
"match": "\\\\[nt\"\\\\e]"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.bob",
|
||||
"match": ":"
|
||||
},
|
||||
{
|
||||
"include": "#expressions"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "variable.other.dictionary-index.bob",
|
||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\{([^\\}]+)\\}",
|
||||
"captures": {
|
||||
"1": { "name": "variable.other.bob" },
|
||||
"2": { "name": "string.quoted.double.bob" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.bob",
|
||||
"match": "\\b(if|else|while|do|for|break|continue|return|var|func|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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
58
bob-language-extension/test-operators.bob
Normal file
58
bob-language-extension/test-operators.bob
Normal file
@ -0,0 +1,58 @@
|
||||
// Test file for operator syntax highlighting
|
||||
var x = 5;
|
||||
var y = 10;
|
||||
var z = x + y;
|
||||
var w = x - y;
|
||||
var a = x * y;
|
||||
var b = x / y;
|
||||
var c = x % y;
|
||||
|
||||
// Comparison operators
|
||||
var eq = x == y;
|
||||
var ne = x != y;
|
||||
var lt = x < y;
|
||||
var gt = x > y;
|
||||
var le = x <= y;
|
||||
var ge = x >= y;
|
||||
|
||||
// Increment/decrement
|
||||
x++;
|
||||
y--;
|
||||
|
||||
// Compound assignment
|
||||
x += 5;
|
||||
y -= 3;
|
||||
z *= 2;
|
||||
w /= 4;
|
||||
a %= 3;
|
||||
|
||||
// Logical operators
|
||||
var and = true && false;
|
||||
var or = true || false;
|
||||
var not = !true;
|
||||
|
||||
// Bitwise operators
|
||||
var bit_and = x & y;
|
||||
var bit_or = x | y;
|
||||
var bit_xor = x ^ y;
|
||||
var left_shift = x << 2;
|
||||
var right_shift = x >> 1;
|
||||
|
||||
// Compound bitwise assignment
|
||||
x &= y;
|
||||
y |= z;
|
||||
z ^= w;
|
||||
w <<= 2;
|
||||
a >>= 1;
|
||||
|
||||
// Function with operators
|
||||
func test_operators() {
|
||||
var result = 0;
|
||||
if (x >= 0 && y <= 100) {
|
||||
result = x + y * 2;
|
||||
if (result != 0) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
79
bob-language-extension/themes/bob-dark.json
Normal file
79
bob-language-extension/themes/bob-dark.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "Bob Dark",
|
||||
"type": "dark",
|
||||
"colors": {
|
||||
"editor.background": "#1e1e1e",
|
||||
"editor.foreground": "#d4d4d4",
|
||||
"editor.lineHighlightBackground": "#2a2a2a",
|
||||
"editor.selectionBackground": "#264f78",
|
||||
"editor.inactiveSelectionBackground": "#3a3d41"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"name": "Comments",
|
||||
"scope": ["comment", "punctuation.definition.comment"],
|
||||
"settings": {
|
||||
"foreground": "#6a9955"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Strings",
|
||||
"scope": ["string", "string.quoted"],
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Numbers",
|
||||
"scope": ["constant.numeric"],
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Keywords",
|
||||
"scope": ["keyword.control", "keyword.operator"],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Functions",
|
||||
"scope": ["entity.name.function", "support.function.builtin"],
|
||||
"settings": {
|
||||
"foreground": "#dcdcaa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Variables",
|
||||
"scope": ["variable.other"],
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Constants",
|
||||
"scope": ["constant.language"],
|
||||
"settings": {
|
||||
"foreground": "#4ec9b0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Operators",
|
||||
"scope": [
|
||||
"keyword.operator.arithmetic",
|
||||
"keyword.operator.comparison",
|
||||
"keyword.operator.logical",
|
||||
"keyword.operator.bitwise",
|
||||
"keyword.operator.assignment",
|
||||
"keyword.operator.compound",
|
||||
"keyword.operator.increment",
|
||||
"keyword.operator.punctuation",
|
||||
"keyword.operator.conditional"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
17
bob-language-extension/tsconfig.json
Normal file
17
bob-language-extension/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2020",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"ES2020"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test"
|
||||
]
|
||||
}
|
||||
108
bob-language-extension/version-info.md
Normal file
108
bob-language-extension/version-info.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Bob Language Extension v0.4.0
|
||||
|
||||
## What's New
|
||||
|
||||
### ✨ New Features Added
|
||||
|
||||
#### **Dictionary Support**
|
||||
- **Dictionary literals**: `{"key": "value", "number": 42}`
|
||||
- **Dictionary indexing**: `dict{"key"}` (returns `none` for missing keys)
|
||||
- **Dictionary assignment**: `dict{"key"} = value`
|
||||
- **Nested dictionaries**: `{"user": {"name": "Bob", "age": 30}}`
|
||||
- **Mixed type values**: Any type can be stored as dictionary values
|
||||
|
||||
#### **Dictionary Built-in Functions**
|
||||
- `keys(dict)` - Returns array of all keys
|
||||
- `values(dict)` - Returns array of all values
|
||||
- `has(dict, key)` - Returns boolean if key exists
|
||||
|
||||
#### **Dictionary Code Snippets**
|
||||
- `dict` - Create dictionary literal
|
||||
- `dictaccess` - Access dictionary value
|
||||
- `dictassign` - Assign to dictionary key
|
||||
- `keys`, `values`, `has` - Built-in function snippets
|
||||
|
||||
### 🎨 Syntax Highlighting Improvements
|
||||
- Dictionary literal syntax highlighting
|
||||
- Dictionary indexing syntax support
|
||||
- Built-in function highlighting for `keys`, `values`, `has`
|
||||
|
||||
### 📝 Documentation Updates
|
||||
- Complete dictionary documentation with examples
|
||||
- Dictionary built-in functions documentation
|
||||
- Updated language reference with dictionary section
|
||||
- Array and dictionary built-in functions documentation
|
||||
|
||||
---
|
||||
|
||||
## Previous Version (v0.3.0)
|
||||
|
||||
### ✨ New Features Added
|
||||
|
||||
#### **Enhanced Array Support**
|
||||
- **Auto-truncating float indices**: `array[3.14]` → `array[3]` (like JavaScript/Lua)
|
||||
- **Increment/decrement on array elements**: `array[0]++`, `++array[1]`
|
||||
- **Improved array operations**: Better error handling and bounds checking
|
||||
|
||||
#### **New Built-in Functions**
|
||||
- `toInt()` - convert floats to integers (truncates decimals)
|
||||
- Enhanced error reporting for all built-in functions
|
||||
|
||||
#### **Increment/Decrement Operators**
|
||||
- **Prefix increment**: `++x`
|
||||
- **Postfix increment**: `x++`
|
||||
- **Prefix decrement**: `--x`
|
||||
- **Postfix decrement**: `x--`
|
||||
- **Works on variables and array elements**
|
||||
|
||||
#### **Cross-Type Comparisons**
|
||||
- **Equality operators** (`==`, `!=`) work with any types
|
||||
- **Comparison operators** (`>`, `<`, `>=`, `<=`) only work with numbers
|
||||
- **Clear error messages** for type mismatches
|
||||
|
||||
#### **Compound Assignment Operators**
|
||||
- **Enhanced error reporting** with correct operator names
|
||||
- **Consistent behavior** across all compound operators
|
||||
- **Better type checking** before operations
|
||||
|
||||
#### **New Code Snippets**
|
||||
- `toint` - Convert float to integer
|
||||
- `compound` - Compound assignment operators
|
||||
- `inc` - Increment variable
|
||||
- `dec` - Decrement variable
|
||||
- `arrayinc` - Increment array element
|
||||
- `arraydec` - Decrement array element
|
||||
- `crosscomp` - Cross-type comparison
|
||||
- `floatindex` - Array access with float index
|
||||
|
||||
### 🎨 Syntax Highlighting Improvements
|
||||
- Support for `toInt()` built-in function
|
||||
- Enhanced operator recognition for increment/decrement
|
||||
- Better array indexing syntax support
|
||||
|
||||
### 📝 Documentation Updates
|
||||
- Comprehensive array documentation with auto-truncation examples
|
||||
- New built-in function documentation (`toInt`, enhanced error reporting)
|
||||
- Cross-type comparison behavior documentation
|
||||
- Increment/decrement operator documentation
|
||||
- Compound assignment operator documentation
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- **Fixed array printing** - arrays no longer show as "unknown"
|
||||
- **Enhanced error reporting** - all errors now use the error reporter system
|
||||
- **Improved type checking** - better error messages for type mismatches
|
||||
- **Memory management** - better cleanup of unused functions and arrays
|
||||
|
||||
## Installation
|
||||
|
||||
To create the VSIX package:
|
||||
1. Install Node.js and npm
|
||||
2. Run `npm install -g vsce`
|
||||
3. Run `./package-vsix.sh`
|
||||
|
||||
The extension will be packaged as `bob-language-0.4.0.vsix`
|
||||
|
||||
## Compatibility
|
||||
- VS Code 1.60.0+
|
||||
- Cursor (VS Code compatible)
|
||||
- All platforms (Windows, macOS, Linux)
|
||||
13
bobby.bob
Normal file
13
bobby.bob
Normal file
@ -0,0 +1,13 @@
|
||||
class A {
|
||||
var inner = 10;
|
||||
|
||||
func test(){
|
||||
print(this.inner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func hello(){
|
||||
print("hello");
|
||||
}
|
||||
|
||||
56
examples/demo_features.bob
Normal file
56
examples/demo_features.bob
Normal file
@ -0,0 +1,56 @@
|
||||
// ===========================================================
|
||||
// Bob feature-tour demo
|
||||
// – dot-notation for dictionaries & arrays
|
||||
// – property assignment (incl. nested)
|
||||
// – built-in properties: dict.keys / dict.values
|
||||
// arr.length / arr.first / arr.last / arr.empty
|
||||
// – tail-call-optimised (TCO) recursion
|
||||
// ===========================================================
|
||||
|
||||
// ---------- 1. Dictionary property access / assignment ----------
|
||||
var person = { "name": "Alice", "age": 30 };
|
||||
print(person.name);
|
||||
print(person.age);
|
||||
|
||||
// add a brand-new user property
|
||||
person.country = "Canada";
|
||||
print(person.country);
|
||||
|
||||
// nested property assignment
|
||||
var team = { "lead": { "name": "Bob", "age": 40 } };
|
||||
team.lead.age = 41;
|
||||
print(team.lead.age);
|
||||
|
||||
// built-in dictionary properties
|
||||
print("dict length = " + team.length);
|
||||
print(team.keys);
|
||||
print(team.values[0].name);
|
||||
|
||||
// ---------- 2. Array dot-properties ----------
|
||||
var nums = [ 10, 20, 30, 40 ];
|
||||
print("len = " + nums.length);
|
||||
print("first = " + nums.first);
|
||||
print("last = " + nums.last);
|
||||
print("empty? " + nums.empty);
|
||||
|
||||
// dot-properties are read-only; assignment still via index
|
||||
nums[0] = 99;
|
||||
print(nums.first);
|
||||
|
||||
// ---------- 3. Tail-call-optimised recursion ----------
|
||||
func fibTail(n, a, b) {
|
||||
return n <= 0 ? a : fibTail(n - 1, b, a + b);
|
||||
}
|
||||
|
||||
print("Fast TCO fib(90) = " + fibTail(90, 0, 1));
|
||||
print("Fast TCO fib(5000) = " + fibTail(5000, 0, 1));
|
||||
|
||||
// ---------- 4. Compare non-TCO version (stack grows!) ----------
|
||||
func fibPlain(n) {
|
||||
return n <= 1 ? n : fibPlain(n - 1) + fibPlain(n - 2);
|
||||
}
|
||||
|
||||
print("Slow recursive fib(35) = " + fibPlain(35));
|
||||
|
||||
// ---------- 5. Summary ----------
|
||||
print("All new features demonstrated!");
|
||||
@ -1,72 +0,0 @@
|
||||
#pragma once
|
||||
#include "Expression.h"
|
||||
#include "Statement.h"
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "Environment.h"
|
||||
#include "Value.h"
|
||||
#include "StdLib.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <stack>
|
||||
|
||||
class Interpreter : public ExprVisitor, public StmtVisitor {
|
||||
|
||||
public:
|
||||
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
||||
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
||||
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
||||
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
||||
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
||||
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
||||
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
||||
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
||||
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
||||
|
||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
|
||||
void interpret(std::vector<std::shared_ptr<Stmt> > statements);
|
||||
|
||||
explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){
|
||||
environment = std::make_shared<Environment>();
|
||||
}
|
||||
virtual ~Interpreter() = default;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Environment> environment;
|
||||
bool IsInteractive;
|
||||
std::vector<std::shared_ptr<BuiltinFunction> > builtinFunctions;
|
||||
std::vector<std::shared_ptr<Function> > functions;
|
||||
ErrorReporter* errorReporter;
|
||||
|
||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
||||
bool isEqual(Value a, Value b);
|
||||
bool isWholeNumer(double num);
|
||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
||||
void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
||||
void addStdLibFunctions();
|
||||
|
||||
public:
|
||||
bool isTruthy(Value object);
|
||||
std::string stringify(Value object);
|
||||
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
||||
|
||||
// Error reporting
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
if (environment) {
|
||||
environment->setErrorReporter(reporter);
|
||||
}
|
||||
|
||||
// Add standard library functions after error reporter is set
|
||||
addStdLibFunctions();
|
||||
}
|
||||
};
|
||||
@ -1,120 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "Expression.h"
|
||||
|
||||
struct ExpressionStmt;
|
||||
struct VarStmt;
|
||||
struct BlockStmt;
|
||||
struct FunctionStmt;
|
||||
struct ReturnStmt;
|
||||
struct IfStmt;
|
||||
|
||||
struct ExecutionContext {
|
||||
bool isFunctionBody = false;
|
||||
bool hasReturn = false;
|
||||
Value returnValue;
|
||||
};
|
||||
|
||||
struct StmtVisitor
|
||||
{
|
||||
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||
};
|
||||
|
||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||
{
|
||||
std::shared_ptr<Expr> expression;
|
||||
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
|
||||
virtual ~Stmt(){};
|
||||
};
|
||||
|
||||
struct BlockStmt : Stmt
|
||||
{
|
||||
std::vector<std::shared_ptr<Stmt> > statements;
|
||||
explicit BlockStmt(std::vector<std::shared_ptr<Stmt> > statements) : statements(statements)
|
||||
{
|
||||
}
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||
{
|
||||
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
|
||||
}
|
||||
};
|
||||
|
||||
struct ExpressionStmt : Stmt
|
||||
{
|
||||
std::shared_ptr<Expr> expression;
|
||||
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
|
||||
{
|
||||
}
|
||||
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||
{
|
||||
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct VarStmt : Stmt
|
||||
{
|
||||
Token name;
|
||||
std::shared_ptr<Expr> initializer;
|
||||
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
|
||||
{
|
||||
}
|
||||
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||
{
|
||||
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionStmt : Stmt
|
||||
{
|
||||
const Token name;
|
||||
const std::vector<Token> params;
|
||||
std::vector<std::shared_ptr<Stmt> > body;
|
||||
|
||||
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt> > body)
|
||||
: name(name), params(params), body(body) {}
|
||||
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||
{
|
||||
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
|
||||
}
|
||||
};
|
||||
|
||||
struct ReturnStmt : Stmt
|
||||
{
|
||||
const Token keyword;
|
||||
std::shared_ptr<Expr> value;
|
||||
|
||||
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
|
||||
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||
{
|
||||
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
|
||||
}
|
||||
};
|
||||
|
||||
struct IfStmt : Stmt
|
||||
{
|
||||
std::shared_ptr<Expr> condition;
|
||||
std::shared_ptr<Stmt> thenBranch;
|
||||
std::shared_ptr<Stmt> elseBranch;
|
||||
|
||||
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
|
||||
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
|
||||
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||
{
|
||||
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
|
||||
}
|
||||
};
|
||||
274
headers/Value.h
274
headers/Value.h
@ -1,274 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
// Forward declarations
|
||||
class Environment;
|
||||
class Function;
|
||||
class BuiltinFunction;
|
||||
|
||||
// Type tags for the Value union
|
||||
enum ValueType : uint8_t {
|
||||
VAL_NONE,
|
||||
VAL_NUMBER,
|
||||
VAL_BOOLEAN,
|
||||
VAL_STRING,
|
||||
VAL_FUNCTION,
|
||||
VAL_BUILTIN_FUNCTION
|
||||
};
|
||||
|
||||
// Tagged value system (like Lua) - no heap allocation for simple values
|
||||
struct Value {
|
||||
union {
|
||||
double number;
|
||||
bool boolean;
|
||||
Function* function;
|
||||
BuiltinFunction* builtin_function;
|
||||
};
|
||||
ValueType type;
|
||||
std::string string_value; // Store strings outside the union for safety
|
||||
|
||||
// Constructors
|
||||
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
||||
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
||||
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
|
||||
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
||||
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
||||
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
||||
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
||||
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
||||
|
||||
// Move constructor
|
||||
Value(Value&& other) noexcept
|
||||
: type(other.type), string_value(std::move(other.string_value)) {
|
||||
if (type != ValueType::VAL_STRING) {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
other.type = ValueType::VAL_NONE;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
Value& operator=(Value&& other) noexcept {
|
||||
if (this != &other) {
|
||||
type = other.type;
|
||||
if (type == ValueType::VAL_STRING) {
|
||||
string_value = std::move(other.string_value);
|
||||
} else {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
other.type = ValueType::VAL_NONE;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Copy constructor (only when needed)
|
||||
Value(const Value& other) : type(other.type) {
|
||||
if (type == ValueType::VAL_STRING) {
|
||||
string_value = other.string_value;
|
||||
} else {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
}
|
||||
|
||||
// Copy assignment (only when needed)
|
||||
Value& operator=(const Value& other) {
|
||||
if (this != &other) {
|
||||
type = other.type;
|
||||
if (type == ValueType::VAL_STRING) {
|
||||
string_value = other.string_value;
|
||||
} else {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Type checking (fast, no dynamic casting) - inline for performance
|
||||
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
|
||||
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
|
||||
inline bool isString() const { return type == ValueType::VAL_STRING; }
|
||||
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
|
||||
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
||||
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
||||
|
||||
// Value extraction (safe, with type checking) - inline for performance
|
||||
inline double asNumber() const { return isNumber() ? number : 0.0; }
|
||||
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
|
||||
inline const std::string& asString() const { return string_value; }
|
||||
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
|
||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
|
||||
|
||||
// Truthiness check - inline for performance
|
||||
inline bool isTruthy() const {
|
||||
switch (type) {
|
||||
case ValueType::VAL_NONE: return false;
|
||||
case ValueType::VAL_BOOLEAN: return boolean;
|
||||
case ValueType::VAL_NUMBER: return number != 0.0;
|
||||
case ValueType::VAL_STRING: return !string_value.empty();
|
||||
case ValueType::VAL_FUNCTION: return function != nullptr;
|
||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Equality comparison - inline for performance
|
||||
inline bool equals(const Value& other) const {
|
||||
if (type != other.type) return false;
|
||||
|
||||
switch (type) {
|
||||
case ValueType::VAL_NONE: return true;
|
||||
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
|
||||
case ValueType::VAL_NUMBER: return number == other.number;
|
||||
case ValueType::VAL_STRING: return string_value == other.string_value;
|
||||
case ValueType::VAL_FUNCTION: return function == other.function;
|
||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// String representation
|
||||
std::string toString() const {
|
||||
switch (type) {
|
||||
case ValueType::VAL_NONE: return "none";
|
||||
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
|
||||
case ValueType::VAL_NUMBER: {
|
||||
// Format numbers like the original stringify function
|
||||
if (number == std::floor(number)) {
|
||||
return std::to_string(static_cast<long long>(number));
|
||||
} else {
|
||||
std::string str = std::to_string(number);
|
||||
// Remove trailing zeros
|
||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||
if (str.back() == '.') str.pop_back();
|
||||
return str;
|
||||
}
|
||||
}
|
||||
case ValueType::VAL_STRING: return string_value;
|
||||
case ValueType::VAL_FUNCTION: return "<function>";
|
||||
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Arithmetic operators
|
||||
Value operator+(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number + other.number);
|
||||
}
|
||||
if (isString() && other.isString()) {
|
||||
return Value(string_value + other.string_value);
|
||||
}
|
||||
if (isString() && other.isNumber()) {
|
||||
return Value(string_value + other.toString());
|
||||
}
|
||||
if (isNumber() && other.isString()) {
|
||||
return Value(toString() + other.string_value);
|
||||
}
|
||||
// Handle none values by converting to string
|
||||
if (isString() && other.isNone()) {
|
||||
return Value(string_value + "none");
|
||||
}
|
||||
if (isNone() && other.isString()) {
|
||||
return Value("none" + other.string_value);
|
||||
}
|
||||
if (isString() && !other.isString() && !other.isNumber()) {
|
||||
return Value(string_value + other.toString());
|
||||
}
|
||||
if (!isString() && !isNumber() && other.isString()) {
|
||||
return Value(toString() + other.string_value);
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for + operator");
|
||||
}
|
||||
|
||||
Value operator-(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number - other.number);
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for - operator");
|
||||
}
|
||||
|
||||
Value operator*(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number * other.number);
|
||||
}
|
||||
if (isString() && other.isNumber()) {
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(other.number); ++i) {
|
||||
result += string_value;
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
if (isNumber() && other.isString()) {
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(number); ++i) {
|
||||
result += other.string_value;
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for * operator");
|
||||
}
|
||||
|
||||
Value operator/(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
if (other.number == 0) {
|
||||
throw std::runtime_error("Division by zero");
|
||||
}
|
||||
return Value(number / other.number);
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for / operator");
|
||||
}
|
||||
|
||||
Value operator%(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(fmod(number, other.number));
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for % operator");
|
||||
}
|
||||
|
||||
Value operator&(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for & operator");
|
||||
}
|
||||
|
||||
Value operator|(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for | operator");
|
||||
}
|
||||
|
||||
Value operator^(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for ^ operator");
|
||||
}
|
||||
|
||||
Value operator<<(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for << operator");
|
||||
}
|
||||
|
||||
Value operator>>(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for >> operator");
|
||||
}
|
||||
};
|
||||
|
||||
// Global constants for common values
|
||||
extern const Value NONE_VALUE;
|
||||
extern const Value TRUE_VALUE;
|
||||
extern const Value FALSE_VALUE;
|
||||
extern const Value ZERO_VALUE;
|
||||
extern const Value ONE_VALUE;
|
||||
@ -1,29 +0,0 @@
|
||||
#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);
|
||||
};
|
||||
|
||||
95
leakTests/README.md
Normal file
95
leakTests/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
# Bob Memory Leak Test Suite
|
||||
|
||||
This directory contains comprehensive memory leak tests for the Bob programming language. Each test file focuses on different scenarios that could potentially cause memory leaks.
|
||||
|
||||
## Test Files
|
||||
|
||||
### `leaktest_functions.bob`
|
||||
Tests function-related memory scenarios:
|
||||
- Recursive function closures
|
||||
- Function factories (functions returning functions)
|
||||
- Deep function nesting
|
||||
- Circular function references
|
||||
- Expected behavior: Memory should be freed when functions are cleared
|
||||
|
||||
### `leaktest_collections.bob`
|
||||
Tests collection (arrays/dictionaries) memory scenarios:
|
||||
- Large nested arrays
|
||||
- Large nested dictionaries
|
||||
- Mixed array/dict structures
|
||||
- Self-referencing structures
|
||||
- Large string collections
|
||||
- Expected behavior: Collections should be properly freed when reassigned
|
||||
|
||||
### `leaktest_mixed.bob`
|
||||
Tests mixed type scenarios and edge cases:
|
||||
- Functions capturing collections
|
||||
- Collections containing functions and mixed types
|
||||
- Dynamic property assignment patterns
|
||||
- Type reassignment chains
|
||||
- Rapid allocation/deallocation cycles
|
||||
- Expected behavior: Memory should be freed regardless of type mixing
|
||||
|
||||
### `leaktest_loops.bob`
|
||||
Tests memory behavior in loops and repetitive operations:
|
||||
- Nested loop allocation
|
||||
- While loop accumulation
|
||||
- Variable reassignment in loops
|
||||
- Do-while function creation
|
||||
- Complex loop control flow
|
||||
- Memory churn tests
|
||||
- Expected behavior: Loop-created objects should be freed when variables are reassigned
|
||||
|
||||
### `leaktest_builtin.bob`
|
||||
Tests builtin function and stdlib memory behavior:
|
||||
- Heavy string operations
|
||||
- Type conversion stress tests
|
||||
- Array/Dict builtin operations
|
||||
- Eval function stress tests
|
||||
- File I/O operations
|
||||
- Random number generation
|
||||
- Expected behavior: Builtin operations should not leak memory
|
||||
|
||||
## How to Run Tests
|
||||
|
||||
Run each test individually and monitor memory usage:
|
||||
|
||||
```bash
|
||||
# Monitor memory before, during, and after each test
|
||||
./build-ninja/bin/bob leakTests/leaktest_functions.bob
|
||||
./build-ninja/bin/bob leakTests/leaktest_collections.bob
|
||||
./build-ninja/bin/bob leakTests/leaktest_mixed.bob
|
||||
./build-ninja/bin/bob leakTests/leaktest_loops.bob
|
||||
./build-ninja/bin/bob leakTests/leaktest_builtin.bob
|
||||
```
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
After the memory leak fixes:
|
||||
1. **Memory should increase** during object creation phases
|
||||
2. **Memory should decrease** significantly when objects are cleared (set to `none`, `[]`, different types, etc.)
|
||||
3. **Memory should return close to baseline** after each test section
|
||||
4. **No gradual memory increase** across multiple test cycles
|
||||
|
||||
## Memory Monitoring
|
||||
|
||||
Use system tools to monitor memory:
|
||||
- **macOS**: Activity Monitor or `top -pid $(pgrep bob)`
|
||||
- **Linux**: `top`, `htop`, or `ps aux | grep bob`
|
||||
- **Windows**: Task Manager or Process Monitor
|
||||
|
||||
Look for:
|
||||
- Memory spikes during creation phases ✅ Expected
|
||||
- Memory drops after "cleared" messages ✅ Expected
|
||||
- Memory staying high after clearing ❌ Potential leak
|
||||
- Gradual increase across test cycles ❌ Potential leak
|
||||
|
||||
## Test Scenarios Covered
|
||||
|
||||
- **Object Types**: Functions, Arrays, Dictionaries, Strings, Numbers, Booleans
|
||||
- **Memory Patterns**: Allocation, Deallocation, Reassignment, Type Changes
|
||||
- **Edge Cases**: Circular references, Deep nesting, Self-references, Mixed types
|
||||
- **Operations**: Loops, Builtin functions, File I/O, Type conversions
|
||||
- **Cleanup Triggers**: Setting to `none`, `[]`, `{}`, different types, string values
|
||||
|
||||
This comprehensive test suite should help identify any remaining memory leak scenarios in the Bob interpreter.
|
||||
173
leakTests/leaktest_builtin.bob
Normal file
173
leakTests/leaktest_builtin.bob
Normal file
@ -0,0 +1,173 @@
|
||||
// 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 ===");
|
||||
114
leakTests/leaktest_collections.bob
Normal file
114
leakTests/leaktest_collections.bob
Normal file
@ -0,0 +1,114 @@
|
||||
// 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 ===");
|
||||
89
leakTests/leaktest_functions.bob
Normal file
89
leakTests/leaktest_functions.bob
Normal file
@ -0,0 +1,89 @@
|
||||
// Memory leak test: Function-related scenarios
|
||||
// Test various function patterns that could cause memory leaks
|
||||
|
||||
print("=== Function Memory Leak Tests ===");
|
||||
print("Initial memory: " + memoryUsage() + " MB");
|
||||
|
||||
// Test 1: Recursive function closures
|
||||
print("Test 1: Recursive function closures");
|
||||
var recursiveFuncs = [];
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
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 ===");
|
||||
145
leakTests/leaktest_loops.bob
Normal file
145
leakTests/leaktest_loops.bob
Normal file
@ -0,0 +1,145 @@
|
||||
// Memory leak test: Loop and repetitive operation scenarios
|
||||
// Test memory behavior in various loop patterns
|
||||
|
||||
print("=== Loop Memory Leak Tests ===");
|
||||
print("Initial memory: " + memoryUsage() + " MB");
|
||||
|
||||
// Test 1: Nested loop memory allocation
|
||||
print("Test 1: Nested loop allocation");
|
||||
var nestedData = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var row = [];
|
||||
for (var j = 0; j < 1000; j++) {
|
||||
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 ===");
|
||||
113
leakTests/leaktest_mixed.bob
Normal file
113
leakTests/leaktest_mixed.bob
Normal file
@ -0,0 +1,113 @@
|
||||
// Memory leak test: Mixed type scenarios and edge cases
|
||||
// Test combinations and edge cases that might cause leaks
|
||||
|
||||
print("=== Mixed Type Memory Leak Tests ===");
|
||||
print("Initial memory: " + memoryUsage() + " MB");
|
||||
|
||||
// Test 1: Functions with collection captures
|
||||
print("Test 1: Functions capturing collections");
|
||||
var funcWithCollections = [];
|
||||
for (var i = 0; i < 50000; i++) {
|
||||
var capturedArray = [i, i+1, i+2, "data" + i];
|
||||
var capturedDict = {"id": i, "values": [i*2, i*3]};
|
||||
|
||||
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 ===");
|
||||
52
run_leak_tests.sh
Executable file
52
run_leak_tests.sh
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Bob Memory Leak Test Runner
|
||||
# Runs all leak tests in sequence with memory monitoring
|
||||
|
||||
echo "🧪 Bob Memory Leak Test Suite"
|
||||
echo "============================"
|
||||
echo ""
|
||||
|
||||
# Build first
|
||||
echo "📦 Building Bob..."
|
||||
ninja -C build-ninja
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Build failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Build successful"
|
||||
echo ""
|
||||
|
||||
# Function to run a test and show memory info
|
||||
run_test() {
|
||||
local test_file=$1
|
||||
local test_name=$2
|
||||
|
||||
echo "🔬 Running: $test_name"
|
||||
echo "File: $test_file"
|
||||
echo "Memory monitoring: Use Activity Monitor (macOS) or top/htop (Linux)"
|
||||
echo "Press Ctrl+C during test to abort, or follow prompts to continue"
|
||||
echo ""
|
||||
|
||||
./build-ninja/bin/bob "$test_file"
|
||||
|
||||
echo ""
|
||||
echo "✅ Completed: $test_name"
|
||||
echo "----------------------------------------"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Run all tests
|
||||
run_test "leakTests/leaktest_functions.bob" "Function Memory Tests"
|
||||
run_test "leakTests/leaktest_collections.bob" "Collection Memory Tests"
|
||||
run_test "leakTests/leaktest_mixed.bob" "Mixed Type Memory Tests"
|
||||
run_test "leakTests/leaktest_loops.bob" "Loop Memory Tests"
|
||||
run_test "leakTests/leaktest_builtin.bob" "Builtin Function Memory Tests"
|
||||
|
||||
echo "🎉 All memory leak tests completed!"
|
||||
echo ""
|
||||
echo "💡 Memory Monitoring Tips:"
|
||||
echo "- Memory should spike during object creation"
|
||||
echo "- Memory should drop after 'cleared' messages"
|
||||
echo "- Memory should return close to baseline between tests"
|
||||
echo "- Watch for gradual increases across test cycles (indicates leaks)"
|
||||
BIN
source/.DS_Store
vendored
BIN
source/.DS_Store
vendored
Binary file not shown.
@ -1,51 +0,0 @@
|
||||
#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 + "'");
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
//
|
||||
// Created by Bobby Lucero on 5/21/23.
|
||||
//
|
||||
|
||||
#include "../headers/Expression.h"
|
||||
@ -1,878 +0,0 @@
|
||||
//
|
||||
// Created by Bobby Lucero on 5/27/23.
|
||||
//
|
||||
#include <utility>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||
#include <unordered_map>
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/StdLib.h"
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
struct ReturnContext {
|
||||
Value returnValue;
|
||||
bool hasReturn;
|
||||
ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {}
|
||||
};
|
||||
|
||||
|
||||
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||
if(expr->isNull) return NONE_VALUE;
|
||||
if(expr->isNumber){
|
||||
double num;
|
||||
if(expr->value[1] == 'b')
|
||||
{
|
||||
num = binaryStringToLong(expr->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
num = std::stod(expr->value);
|
||||
}
|
||||
return Value(num);
|
||||
}
|
||||
if(expr->isBoolean) {
|
||||
if(expr->value == "true") return TRUE_VALUE;
|
||||
if(expr->value == "false") return FALSE_VALUE;
|
||||
}
|
||||
return Value(expr->value);
|
||||
}
|
||||
|
||||
Value Interpreter::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
||||
|
||||
return evaluate(expression->expression);
|
||||
}
|
||||
|
||||
Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
||||
{
|
||||
Value right = evaluate(expression->right);
|
||||
|
||||
if(expression->oper.type == MINUS)
|
||||
{
|
||||
if(right.isNumber())
|
||||
{
|
||||
double value = right.asNumber();
|
||||
return Value(-value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(expression->oper.type == BANG)
|
||||
{
|
||||
return Value(!isTruthy(right));
|
||||
}
|
||||
|
||||
if(expression->oper.type == BIN_NOT)
|
||||
{
|
||||
if(right.isNumber())
|
||||
{
|
||||
double value = right.asNumber();
|
||||
return Value(static_cast<double>(~(static_cast<long>(value))));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Operand must be an int when using: " + expression->oper.lexeme);
|
||||
}
|
||||
}
|
||||
|
||||
//unreachable
|
||||
throw std::runtime_error("Invalid unary expression");
|
||||
|
||||
}
|
||||
|
||||
Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
|
||||
Value left = evaluate(expression->left);
|
||||
Value right = evaluate(expression->right);
|
||||
|
||||
if (left.isNumber() && right.isNumber()) {
|
||||
double leftNum = left.asNumber();
|
||||
double rightNum = right.asNumber();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return Value(leftNum + rightNum);
|
||||
case MINUS: return Value(leftNum - rightNum);
|
||||
case SLASH: {
|
||||
if (rightNum == 0) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero",
|
||||
"Cannot divide by zero", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Division by zero");
|
||||
}
|
||||
return Value(leftNum / rightNum);
|
||||
}
|
||||
case STAR: return Value(leftNum * rightNum);
|
||||
case PERCENT: {
|
||||
if (rightNum == 0) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero",
|
||||
"Cannot perform modulo operation with zero", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Modulo by zero");
|
||||
}
|
||||
return Value(std::fmod(leftNum, rightNum));
|
||||
}
|
||||
case GREATER: return Value(leftNum > rightNum);
|
||||
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
||||
case LESS: return Value(leftNum < rightNum);
|
||||
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
||||
case DOUBLE_EQUAL: return Value(leftNum == rightNum);
|
||||
case BANG_EQUAL: return Value(leftNum != rightNum);
|
||||
case BIN_AND: return Value(static_cast<double>(static_cast<int>(leftNum) & static_cast<int>(rightNum)));
|
||||
case BIN_OR: return Value(static_cast<double>(static_cast<int>(leftNum) | static_cast<int>(rightNum)));
|
||||
case BIN_XOR: return Value(static_cast<double>(static_cast<int>(leftNum) ^ static_cast<int>(rightNum)));
|
||||
case BIN_SLEFT: return Value(static_cast<double>(static_cast<int>(leftNum) << static_cast<int>(rightNum)));
|
||||
case BIN_SRIGHT: return Value(static_cast<double>(static_cast<int>(leftNum) >> static_cast<int>(rightNum)));
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isString() && right.isString()) {
|
||||
std::string left_string = left.asString();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return Value(left_string + right_string);
|
||||
case DOUBLE_EQUAL: return Value(left_string == right_string);
|
||||
case BANG_EQUAL: return Value(left_string != right_string);
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
default:
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||
"Cannot use '" + expression->oper.lexeme + "' on two strings", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings");
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isString() && right.isNumber()) {
|
||||
std::string left_string = left.asString();
|
||||
double right_num = right.asNumber();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return left + right;
|
||||
case STAR: {
|
||||
if (!isWholeNumer(right_num)) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||
"String multiplier must be a whole number", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("String multiplier must be whole number");
|
||||
}
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(right_num); i++) {
|
||||
result += left_string;
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isNumber() && right.isString()) {
|
||||
double left_num = left.asNumber();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return left + right;
|
||||
case STAR: {
|
||||
if (!isWholeNumer(left_num)) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||
"String multiplier must be a whole number", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("String multiplier must be whole number");
|
||||
}
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(left_num); i++) {
|
||||
result += right_string;
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isBoolean() && right.isBoolean()) {
|
||||
bool left_bool = left.asBoolean();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: return Value(left_bool && right_bool);
|
||||
case OR: return Value(left_bool || right_bool);
|
||||
case DOUBLE_EQUAL: return Value(left_bool == right_bool);
|
||||
case BANG_EQUAL: return Value(left_bool != right_bool);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (left.isBoolean() && right.isString()) {
|
||||
bool left_bool = left.asBoolean();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return left + right;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isString() && right.isBoolean()) {
|
||||
std::string left_string = left.asString();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return left + right;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isNumber() && right.isBoolean()) {
|
||||
double left_num = left.asNumber();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isBoolean() && right.isNumber()) {
|
||||
bool left_bool = left.asBoolean();
|
||||
double right_num = right.asNumber();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mixed-type logical operations (string && boolean, etc.)
|
||||
if (left.isString() && right.isBoolean()) {
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case PLUS: return left + right;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isBoolean() && right.isString()) {
|
||||
bool left_bool = left.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case PLUS: return left + right;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isString() && right.isNumber()) {
|
||||
double right_num = right.asNumber();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case PLUS: return left + right;
|
||||
case STAR: {
|
||||
if (!isWholeNumer(right_num)) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||
"String multiplier must be a whole number");
|
||||
}
|
||||
throw std::runtime_error("String multiplier must be whole number");
|
||||
}
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(right_num); i++) {
|
||||
result += left.asString();
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isNumber() && right.isString()) {
|
||||
double left_num = left.asNumber();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case PLUS: return left + right;
|
||||
case STAR: {
|
||||
if (!isWholeNumer(left_num)) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||
"String multiplier must be a whole number");
|
||||
}
|
||||
throw std::runtime_error("String multiplier must be whole number");
|
||||
}
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(left_num); i++) {
|
||||
result += right.asString();
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isNone() && right.isString()) {
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return left + right;
|
||||
}
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||
"Cannot use '" + expression->oper.lexeme + "' on none and a string", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
|
||||
}
|
||||
|
||||
if (left.isString() && right.isNone()) {
|
||||
std::string left_string = left.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return left + right;
|
||||
}
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||
"Cannot use '" + expression->oper.lexeme + "' on a string and none", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||
"Operands must be of same type when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme);
|
||||
}
|
||||
}
|
||||
|
||||
Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
||||
{
|
||||
return environment->get(expression->name);
|
||||
}
|
||||
|
||||
Value Interpreter::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
||||
// Get the current value of the operand
|
||||
Value currentValue = evaluate(expression->operand);
|
||||
|
||||
if (!currentValue.isNumber()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
||||
}
|
||||
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
||||
}
|
||||
|
||||
double currentNum = currentValue.asNumber();
|
||||
double newValue;
|
||||
|
||||
// Determine the operation based on the operator
|
||||
if (expression->oper.type == PLUS_PLUS) {
|
||||
newValue = currentNum + 1.0;
|
||||
} else if (expression->oper.type == MINUS_MINUS) {
|
||||
newValue = currentNum - 1.0;
|
||||
} else {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||
"Runtime Error", "Invalid increment/decrement operator.", "");
|
||||
}
|
||||
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||
}
|
||||
|
||||
// Update the variable if it's a variable expression
|
||||
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||
environment->assign(varExpr->name, Value(newValue));
|
||||
} else {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||
"Runtime Error", "Increment/decrement can only be applied to variables.", "");
|
||||
}
|
||||
throw std::runtime_error("Increment/decrement can only be applied to variables.");
|
||||
}
|
||||
|
||||
// Return the appropriate value based on prefix/postfix
|
||||
if (expression->isPrefix) {
|
||||
return Value(newValue); // Prefix: return new value
|
||||
} else {
|
||||
return currentValue; // Postfix: return old value
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::addStdLibFunctions() {
|
||||
// Add standard library functions to the environment
|
||||
StdLib::addToEnvironment(environment, *this, errorReporter);
|
||||
}
|
||||
|
||||
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||
builtinFunctions.push_back(func);
|
||||
}
|
||||
|
||||
Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
||||
Value value = evaluate(expression->value);
|
||||
|
||||
switch (expression->op.type) {
|
||||
case PLUS_EQUAL:
|
||||
case MINUS_EQUAL:
|
||||
case STAR_EQUAL:
|
||||
case SLASH_EQUAL:
|
||||
case PERCENT_EQUAL:
|
||||
case BIN_AND_EQUAL:
|
||||
case BIN_OR_EQUAL:
|
||||
case BIN_XOR_EQUAL:
|
||||
case BIN_SLEFT_EQUAL:
|
||||
case BIN_SRIGHT_EQUAL: {
|
||||
Value currentValue = environment->get(expression->name.lexeme);
|
||||
switch (expression->op.type) {
|
||||
case PLUS_EQUAL:
|
||||
value = currentValue + value;
|
||||
break;
|
||||
case MINUS_EQUAL:
|
||||
value = currentValue - value;
|
||||
break;
|
||||
case STAR_EQUAL:
|
||||
value = currentValue * value;
|
||||
break;
|
||||
case SLASH_EQUAL:
|
||||
value = currentValue / value;
|
||||
break;
|
||||
case PERCENT_EQUAL:
|
||||
value = currentValue % value;
|
||||
break;
|
||||
case BIN_AND_EQUAL:
|
||||
value = currentValue & value;
|
||||
break;
|
||||
case BIN_OR_EQUAL:
|
||||
value = currentValue | value;
|
||||
break;
|
||||
case BIN_XOR_EQUAL:
|
||||
value = currentValue ^ value;
|
||||
break;
|
||||
case BIN_SLEFT_EQUAL:
|
||||
value = currentValue << value;
|
||||
break;
|
||||
case BIN_SRIGHT_EQUAL:
|
||||
value = currentValue >> value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
environment->assign(expression->name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||
Value callee = evaluate(expression->callee);
|
||||
|
||||
std::vector<Value> arguments;
|
||||
for (const std::shared_ptr<Expr>& argument : expression->arguments) {
|
||||
arguments.push_back(evaluate(argument));
|
||||
}
|
||||
|
||||
if (callee.isBuiltinFunction()) {
|
||||
// Builtin functions now work directly with Value and receive line and column
|
||||
return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column);
|
||||
}
|
||||
|
||||
if (callee.isFunction()) {
|
||||
Function* function = callee.asFunction();
|
||||
if (arguments.size() != function->params.size()) {
|
||||
throw std::runtime_error("Expected " + std::to_string(function->params.size()) +
|
||||
" arguments but got " + std::to_string(arguments.size()) + ".");
|
||||
}
|
||||
|
||||
auto previousEnv = environment;
|
||||
environment = std::make_shared<Environment>(function->closure);
|
||||
environment->setErrorReporter(errorReporter);
|
||||
|
||||
for (size_t i = 0; i < function->params.size(); i++) {
|
||||
environment->define(function->params[i], arguments[i]);
|
||||
}
|
||||
|
||||
ExecutionContext context;
|
||||
context.isFunctionBody = true;
|
||||
|
||||
for (const auto& stmt : function->body) {
|
||||
execute(stmt, &context);
|
||||
if (context.hasReturn) {
|
||||
environment = previousEnv;
|
||||
return context.returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
environment = previousEnv;
|
||||
return context.returnValue;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Can only call functions and classes.");
|
||||
}
|
||||
|
||||
Value Interpreter::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
||||
// Convert Token parameters to string parameters
|
||||
std::vector<std::string> paramNames;
|
||||
for (const Token& param : expression->params) {
|
||||
paramNames.push_back(param.lexeme);
|
||||
}
|
||||
|
||||
auto function = msptr(Function)("anonymous", paramNames, expression->body, environment);
|
||||
functions.push_back(function); // Keep the shared_ptr alive
|
||||
return Value(function.get());
|
||||
}
|
||||
|
||||
void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
||||
auto newEnv = std::make_shared<Environment>(environment);
|
||||
newEnv->setErrorReporter(errorReporter);
|
||||
executeBlock(statement->statements, newEnv, context);
|
||||
}
|
||||
|
||||
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||
Value value = evaluate(statement->expression);
|
||||
|
||||
if(IsInteractive)
|
||||
std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context)
|
||||
{
|
||||
Value value = NONE_VALUE;
|
||||
if(statement->initializer != nullptr)
|
||||
{
|
||||
value = evaluate(statement->initializer);
|
||||
}
|
||||
|
||||
//std::cout << "Visit var stmt: " << statement->name.lexeme << " set to: " << stringify(value) << std::endl;
|
||||
|
||||
environment->define(statement->name.lexeme, value);
|
||||
}
|
||||
|
||||
void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context)
|
||||
{
|
||||
// Convert Token parameters to string parameters
|
||||
std::vector<std::string> paramNames;
|
||||
for (const Token& param : statement->params) {
|
||||
paramNames.push_back(param.lexeme);
|
||||
}
|
||||
|
||||
auto function = msptr(Function)(statement->name.lexeme,
|
||||
paramNames,
|
||||
statement->body,
|
||||
environment);
|
||||
functions.push_back(function); // Keep the shared_ptr alive
|
||||
environment->define(statement->name.lexeme, Value(function.get()));
|
||||
}
|
||||
|
||||
void Interpreter::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context)
|
||||
{
|
||||
Value value = NONE_VALUE;
|
||||
if (statement->value != nullptr) {
|
||||
value = evaluate(statement->value);
|
||||
}
|
||||
|
||||
if (context && context->isFunctionBody) {
|
||||
context->hasReturn = true;
|
||||
context->returnValue = value;
|
||||
}
|
||||
// If no context or not in function body, this is a top-level return (ignored)
|
||||
}
|
||||
|
||||
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context)
|
||||
{
|
||||
if (isTruthy(evaluate(statement->condition))) {
|
||||
execute(statement->thenBranch, context);
|
||||
} else if (statement->elseBranch != nullptr) {
|
||||
execute(statement->elseBranch, context);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt> > statements) {
|
||||
for(const std::shared_ptr<Stmt>& s : statements)
|
||||
{
|
||||
execute(s, nullptr); // No context needed for top-level execution
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context)
|
||||
{
|
||||
statement->accept(this, context);
|
||||
}
|
||||
|
||||
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context)
|
||||
{
|
||||
std::shared_ptr<Environment> previous = this->environment;
|
||||
this->environment = env;
|
||||
|
||||
for(const std::shared_ptr<Stmt>& s : statements)
|
||||
{
|
||||
execute(s, context);
|
||||
if (context && context->hasReturn) {
|
||||
this->environment = previous;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->environment = previous;
|
||||
}
|
||||
|
||||
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
||||
return expr->accept(this);
|
||||
}
|
||||
|
||||
bool Interpreter::isTruthy(Value object) {
|
||||
|
||||
if(object.isBoolean())
|
||||
{
|
||||
return object.asBoolean();
|
||||
}
|
||||
|
||||
if(object.isNone())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(object.isNumber())
|
||||
{
|
||||
return object.asNumber() != 0;
|
||||
}
|
||||
|
||||
if(object.isString())
|
||||
{
|
||||
return object.asString().length() > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Interpreter::isEqual(Value a, Value b) {
|
||||
if(a.isNumber())
|
||||
{
|
||||
if(b.isNumber())
|
||||
{
|
||||
return a.asNumber() == b.asNumber();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(a.isBoolean())
|
||||
{
|
||||
if(b.isBoolean())
|
||||
{
|
||||
return a.asBoolean() == b.asBoolean();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(a.isString())
|
||||
{
|
||||
if(b.isString())
|
||||
{
|
||||
return a.asString() == b.asString();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(a.isNone())
|
||||
{
|
||||
if(b.isNone())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Invalid isEqual compariosn");
|
||||
}
|
||||
|
||||
std::string Interpreter::stringify(Value object) {
|
||||
if(object.isNone())
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
else if(object.isNumber())
|
||||
{
|
||||
double integral = object.asNumber();
|
||||
double fractional = std::modf(object.asNumber(), &integral);
|
||||
|
||||
std::stringstream ss;
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
ss << std::fixed << std::setprecision(0) << integral;
|
||||
return ss.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << object.asNumber();
|
||||
std::string str = ss.str();
|
||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||
if (str.back() == '.') {
|
||||
str.pop_back();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
else if(object.isString())
|
||||
{
|
||||
return object.asString();
|
||||
}
|
||||
else if(object.isBoolean())
|
||||
{
|
||||
return object.asBoolean() == 1 ? "true" : "false";
|
||||
}
|
||||
else if(object.isFunction())
|
||||
{
|
||||
return "<function " + object.asFunction()->name + ">";
|
||||
}
|
||||
else if(object.isBuiltinFunction())
|
||||
{
|
||||
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
|
||||
}
|
||||
|
||||
throw std::runtime_error("Could not convert object to string");
|
||||
}
|
||||
|
||||
bool Interpreter::isWholeNumer(double num) {
|
||||
double integral = num;
|
||||
double fractional = std::modf(num, &integral);
|
||||
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,554 +0,0 @@
|
||||
//
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
//
|
||||
// Created by Bobby Lucero on 5/27/23.
|
||||
//
|
||||
#include "../headers/TypeWrapper.h"
|
||||
#include <iostream>
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
#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);
|
||||
@ -1,93 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
44
src/headers/Executor.h
Normal file
44
src/headers/Executor.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "Statement.h"
|
||||
|
||||
class Evaluator; // Forward declaration
|
||||
class Interpreter; // Forward declaration
|
||||
|
||||
/**
|
||||
* @class Executor
|
||||
* @brief Handles the execution of statements and control flow.
|
||||
*
|
||||
* Implements the StmtVisitor pattern. It is responsible for executing statements,
|
||||
* managing environments, and handling control flow constructs like loops and
|
||||
* conditionals. It uses the Evaluator to evaluate expressions when needed.
|
||||
*/
|
||||
class Executor : public StmtVisitor {
|
||||
private:
|
||||
Interpreter* interpreter; // Back-pointer to access interpreter services
|
||||
Evaluator* evaluator; // For evaluating expressions
|
||||
|
||||
public:
|
||||
Executor(Interpreter* interpreter, Evaluator* evaluator);
|
||||
virtual ~Executor();
|
||||
|
||||
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
|
||||
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
|
||||
|
||||
// Statement Visitors
|
||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||
|
||||
private:
|
||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
||||
};
|
||||
8
src/headers/builtinModules/base64_module.h
Normal file
8
src/headers/builtinModules/base64_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'base64' module
|
||||
void registerBase64Module(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/eval.h
Normal file
8
src/headers/builtinModules/eval.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'eval' module providing eval(code) and evalFile(path)
|
||||
void registerEvalModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/io.h
Normal file
8
src/headers/builtinModules/io.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'io' module (file I/O and input)
|
||||
void registerIoModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/json.h
Normal file
8
src/headers/builtinModules/json.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'json' module
|
||||
void registerJsonModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/math_module.h
Normal file
8
src/headers/builtinModules/math_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'math' module
|
||||
void registerMathModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/os.h
Normal file
8
src/headers/builtinModules/os.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'os' module
|
||||
void registerOsModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/path_module.h
Normal file
8
src/headers/builtinModules/path_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'path' module (path utilities)
|
||||
void registerPathModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/rand.h
Normal file
8
src/headers/builtinModules/rand.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'rand' module
|
||||
void registerRandModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/register.h
Normal file
8
src/headers/builtinModules/register.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Registers all builtin modules with the interpreter
|
||||
void registerAllBuiltinModules(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/sys.h
Normal file
8
src/headers/builtinModules/sys.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'sys' module
|
||||
void registerSysModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/time_module.h
Normal file
8
src/headers/builtinModules/time_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'time' module (time functions)
|
||||
void registerTimeModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
125
src/headers/cli/bob.h
Normal file
125
src/headers/cli/bob.h
Normal file
@ -0,0 +1,125 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
10
src/headers/common/helperFunctions/ErrorUtils.h
Normal file
10
src/headers/common/helperFunctions/ErrorUtils.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
// Common error message utilities
|
||||
namespace ErrorUtils {
|
||||
// Generate consistent operator error messages with single quotes
|
||||
inline std::string makeOperatorError(const std::string& op, const std::string& leftType, const std::string& rightType) {
|
||||
return "'" + op + "' is not supported between '" + leftType + "' and '" + rightType + "'";
|
||||
}
|
||||
}
|
||||
@ -3,8 +3,9 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <cctype>
|
||||
|
||||
inline std::vector<std::string> splitString(const std::string& input, std::string delimiter) {
|
||||
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
size_t start = 0;
|
||||
@ -56,9 +57,9 @@ inline bool isHexDigit(char c) {
|
||||
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
|
||||
}
|
||||
|
||||
inline u_long binaryStringToLong(const std::string& binaryString) {
|
||||
inline unsigned long long binaryStringToLong(const std::string& binaryString) {
|
||||
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
|
||||
u_long result = 0;
|
||||
unsigned long long result = 0;
|
||||
for (char ch : binaryDigits) {
|
||||
result <<= 1;
|
||||
result += (ch - '0');
|
||||
@ -34,6 +34,9 @@ private:
|
||||
std::string currentFileName;
|
||||
std::vector<std::string> callStack;
|
||||
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:
|
||||
ErrorReporter() = default;
|
||||
@ -48,6 +51,9 @@ public:
|
||||
// Check if an error has been reported
|
||||
bool hasReportedError() const { return hadError; }
|
||||
|
||||
// Reset error state (call this between REPL commands)
|
||||
void resetErrorState() { hadError = false; }
|
||||
|
||||
// Report errors with full context
|
||||
void reportErrorWithContext(const ErrorContext& context);
|
||||
|
||||
@ -55,6 +61,11 @@ public:
|
||||
void pushCallStack(const std::string& functionName);
|
||||
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:
|
||||
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);
|
||||
@ -1,6 +1,4 @@
|
||||
//
|
||||
// Created by Bobby Lucero on 5/21/23.
|
||||
//
|
||||
// Expression AST nodes for Bob language
|
||||
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
@ -13,6 +11,16 @@
|
||||
// Forward declarations
|
||||
struct FunctionExpr;
|
||||
struct IncrementExpr;
|
||||
struct TernaryExpr;
|
||||
struct ArrayLiteralExpr;
|
||||
struct ArrayIndexExpr;
|
||||
struct PropertyExpr;
|
||||
|
||||
struct ArrayAssignExpr;
|
||||
struct PropertyAssignExpr;
|
||||
struct DictLiteralExpr;
|
||||
struct DictIndexExpr;
|
||||
struct DictAssignExpr;
|
||||
struct ExprVisitor;
|
||||
|
||||
struct AssignExpr;
|
||||
@ -35,6 +43,15 @@ struct ExprVisitor
|
||||
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
||||
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& 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> {
|
||||
@ -152,3 +169,101 @@ struct IncrementExpr : Expr
|
||||
}
|
||||
};
|
||||
|
||||
struct TernaryExpr : Expr
|
||||
{
|
||||
std::shared_ptr<Expr> condition;
|
||||
std::shared_ptr<Expr> thenExpr;
|
||||
std::shared_ptr<Expr> elseExpr;
|
||||
|
||||
TernaryExpr(std::shared_ptr<Expr> condition, std::shared_ptr<Expr> thenExpr, std::shared_ptr<Expr> elseExpr)
|
||||
: condition(condition), thenExpr(thenExpr), elseExpr(elseExpr) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitTernaryExpr(std::static_pointer_cast<TernaryExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct ArrayLiteralExpr : Expr
|
||||
{
|
||||
std::vector<std::shared_ptr<Expr>> elements;
|
||||
|
||||
explicit ArrayLiteralExpr(const std::vector<std::shared_ptr<Expr>>& elements)
|
||||
: elements(elements) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitArrayLiteralExpr(std::static_pointer_cast<ArrayLiteralExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct ArrayIndexExpr : Expr
|
||||
{
|
||||
std::shared_ptr<Expr> array;
|
||||
std::shared_ptr<Expr> index;
|
||||
Token bracket; // The closing bracket token for error reporting
|
||||
|
||||
ArrayIndexExpr(std::shared_ptr<Expr> array, std::shared_ptr<Expr> index, Token bracket)
|
||||
: array(array), index(index), bracket(bracket) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitArrayIndexExpr(std::static_pointer_cast<ArrayIndexExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct PropertyExpr : Expr
|
||||
{
|
||||
std::shared_ptr<Expr> object;
|
||||
Token name;
|
||||
|
||||
PropertyExpr(std::shared_ptr<Expr> object, Token name)
|
||||
: object(object), name(name) {}
|
||||
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitPropertyExpr(std::static_pointer_cast<PropertyExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct ArrayAssignExpr : Expr
|
||||
{
|
||||
std::shared_ptr<Expr> array;
|
||||
std::shared_ptr<Expr> index;
|
||||
std::shared_ptr<Expr> value;
|
||||
Token bracket; // The closing bracket token for error reporting
|
||||
|
||||
ArrayAssignExpr(std::shared_ptr<Expr> array, std::shared_ptr<Expr> index, std::shared_ptr<Expr> value, Token bracket)
|
||||
: array(array), index(index), value(value), bracket(bracket) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitArrayAssignExpr(std::static_pointer_cast<ArrayAssignExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct PropertyAssignExpr : Expr
|
||||
{
|
||||
std::shared_ptr<Expr> object;
|
||||
Token name;
|
||||
std::shared_ptr<Expr> value;
|
||||
|
||||
PropertyAssignExpr(std::shared_ptr<Expr> object, Token name, std::shared_ptr<Expr> value)
|
||||
: object(object), name(name), value(value) {}
|
||||
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitPropertyAssignExpr(std::static_pointer_cast<PropertyAssignExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct DictLiteralExpr : Expr
|
||||
{
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Expr>>> pairs;
|
||||
|
||||
explicit DictLiteralExpr(const std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& pairs)
|
||||
: pairs(pairs) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitDictLiteralExpr(std::static_pointer_cast<DictLiteralExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
enum TokenType{
|
||||
OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE,
|
||||
OPEN_BRACKET, CLOSE_BRACKET, // Array brackets
|
||||
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT,
|
||||
|
||||
BIN_OR, BIN_AND, BIN_NOT, BIN_XOR, BIN_SLEFT, BIN_SRIGHT,
|
||||
@ -15,13 +16,18 @@ enum TokenType{
|
||||
GREATER, GREATER_EQUAL,
|
||||
LESS, LESS_EQUAL,
|
||||
|
||||
// Ternary operator
|
||||
QUESTION, COLON,
|
||||
|
||||
// Increment/decrement operators
|
||||
PLUS_PLUS, MINUS_MINUS,
|
||||
|
||||
IDENTIFIER, STRING, NUMBER, BOOL,
|
||||
IDENTIFIER, STRING, NUMBER, KW_BOOL,
|
||||
|
||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
|
||||
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
||||
IMPORT, FROM, AS,
|
||||
TRY, CATCH, FINALLY, THROW,
|
||||
|
||||
// Compound assignment operators
|
||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||
@ -33,6 +39,7 @@ enum TokenType{
|
||||
};
|
||||
|
||||
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",
|
||||
|
||||
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
||||
@ -42,12 +49,16 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
||||
"GREATER", "GREATER_EQUAL",
|
||||
"LESS", "LESS_EQUAL",
|
||||
|
||||
"QUESTION", "COLON",
|
||||
|
||||
"PLUS_PLUS", "MINUS_MINUS",
|
||||
|
||||
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
||||
"IDENTIFIER", "STRING", "NUMBER", "KW_BOOL",
|
||||
|
||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
|
||||
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
||||
"IMPORT", "FROM", "AS",
|
||||
"TRY", "CATCH", "FINALLY", "THROW",
|
||||
|
||||
// Compound assignment operators
|
||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||
@ -67,12 +78,24 @@ const std::map<std::string, TokenType> KEYWORDS {
|
||||
{"func", FUNCTION},
|
||||
{"for", FOR},
|
||||
{"while", WHILE},
|
||||
{"do", DO},
|
||||
{"var", VAR},
|
||||
{"class", CLASS},
|
||||
{"extends", EXTENDS},
|
||||
{"extension", EXTENSION},
|
||||
{"super", SUPER},
|
||||
{"this", THIS},
|
||||
{"none", NONE},
|
||||
{"return", RETURN},
|
||||
{"break", BREAK},
|
||||
{"continue", CONTINUE},
|
||||
{"import", IMPORT},
|
||||
{"from", FROM},
|
||||
{"as", AS},
|
||||
{"try", TRY},
|
||||
{"catch", CATCH},
|
||||
{"finally", FINALLY},
|
||||
{"throw", THROW},
|
||||
};
|
||||
|
||||
struct Token
|
||||
@ -24,6 +24,7 @@ public:
|
||||
private:
|
||||
sptr(Expr) expression();
|
||||
sptr(Expr) logical_or();
|
||||
sptr(Expr) ternary();
|
||||
sptr(Expr) logical_and();
|
||||
sptr(Expr) bitwise_or();
|
||||
sptr(Expr) bitwise_xor();
|
||||
@ -56,20 +57,45 @@ private:
|
||||
|
||||
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> 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> functionDeclaration();
|
||||
std::shared_ptr<Expr> functionExpression();
|
||||
|
||||
std::shared_ptr<Stmt> assignmentStatement();
|
||||
sptr(Expr) assignment();
|
||||
sptr(Expr) assignmentExpression(); // For for loop increment clauses
|
||||
sptr(Expr) increment(); // Parse increment/decrement expressions
|
||||
sptr(Expr) postfix(); // Parse postfix operators
|
||||
|
||||
std::vector<std::shared_ptr<Stmt>> block();
|
||||
|
||||
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
|
||||
void enterFunction() { functionDepth++; }
|
||||
305
src/headers/parsing/Statement.h
Normal file
305
src/headers/parsing/Statement.h
Normal file
@ -0,0 +1,305 @@
|
||||
#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);
|
||||
}
|
||||
};
|
||||
35
src/headers/runtime/AssignmentUtils.h
Normal file
35
src/headers/runtime/AssignmentUtils.h
Normal file
@ -0,0 +1,35 @@
|
||||
#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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,21 +3,34 @@
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include "Value.h"
|
||||
#include "Lexer.h"
|
||||
|
||||
// Forward declaration
|
||||
class ErrorReporter;
|
||||
|
||||
class Environment {
|
||||
struct Environment {
|
||||
public:
|
||||
Environment() : parent(nullptr), 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
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
}
|
||||
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
||||
|
||||
// Optimized define with inline
|
||||
inline void define(const std::string& name, const Value& value) {
|
||||
@ -30,19 +43,18 @@ public:
|
||||
// Enhanced get with error reporting
|
||||
Value get(const Token& name);
|
||||
|
||||
// Get by string name with error reporting
|
||||
Value get(const std::string& name);
|
||||
// Prune heavy containers in a snapshot to avoid capture cycles
|
||||
void pruneForClosureCapture();
|
||||
|
||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||
inline void clear() { variables.clear(); }
|
||||
// Export all variables (shallow copy) for module namespace
|
||||
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:
|
||||
std::unordered_map<std::string, Value> variables;
|
||||
std::shared_ptr<Environment> parent;
|
||||
ErrorReporter* errorReporter;
|
||||
};
|
||||
std::unordered_set<std::string> constBindings;
|
||||
};
|
||||
|
||||
45
src/headers/runtime/Evaluator.h
Normal file
45
src/headers/runtime/Evaluator.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "Expression.h"
|
||||
#include "Value.h"
|
||||
|
||||
class Interpreter; // Forward declaration for the back-pointer
|
||||
|
||||
/**
|
||||
* @class Evaluator
|
||||
* @brief Handles the logic for visiting and evaluating Expression AST nodes.
|
||||
*
|
||||
* Implements the Visitor pattern for all Expression types. This class
|
||||
* contains the core evaluation logic for expressions, returning a Value.
|
||||
*/
|
||||
class Evaluator : public ExprVisitor {
|
||||
private:
|
||||
Interpreter* interpreter; // Back-pointer to access interpreter services like isTruthy, etc.
|
||||
|
||||
public:
|
||||
explicit Evaluator(Interpreter* interpreter);
|
||||
virtual ~Evaluator() = default;
|
||||
|
||||
// Expression Visitors
|
||||
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
||||
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
||||
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
||||
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
||||
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
||||
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
||||
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
||||
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
||||
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
||||
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
|
||||
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
|
||||
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
|
||||
Value visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expression) override;
|
||||
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
|
||||
Value visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expression) override;
|
||||
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
|
||||
|
||||
private:
|
||||
// Helper methods for builtin properties
|
||||
Value getArrayProperty(const Value& array, const std::string& propertyName);
|
||||
Value getDictProperty(const Value& dict, const std::string& propertyName);
|
||||
};
|
||||
14
src/headers/runtime/ExecutionContext.h
Normal file
14
src/headers/runtime/ExecutionContext.h
Normal file
@ -0,0 +1,14 @@
|
||||
#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;
|
||||
};
|
||||
50
src/headers/runtime/Executor.h
Normal file
50
src/headers/runtime/Executor.h
Normal file
@ -0,0 +1,50 @@
|
||||
#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);
|
||||
};
|
||||
198
src/headers/runtime/Interpreter.h
Normal file
198
src/headers/runtime/Interpreter.h
Normal file
@ -0,0 +1,198 @@
|
||||
#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;
|
||||
};
|
||||
14
src/headers/runtime/ModuleDef.h
Normal file
14
src/headers/runtime/ModuleDef.h
Normal file
@ -0,0 +1,14 @@
|
||||
// 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;
|
||||
};
|
||||
|
||||
|
||||
70
src/headers/runtime/ModuleRegistry.h
Normal file
70
src/headers/runtime/ModuleRegistry.h
Normal file
@ -0,0 +1,70 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
|
||||
40
src/headers/runtime/RuntimeDiagnostics.h
Normal file
40
src/headers/runtime/RuntimeDiagnostics.h
Normal file
@ -0,0 +1,40 @@
|
||||
#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);
|
||||
};
|
||||
@ -10,51 +10,22 @@
|
||||
struct Stmt;
|
||||
struct Environment;
|
||||
|
||||
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
|
||||
struct Function
|
||||
{
|
||||
const std::string name;
|
||||
const std::vector<std::string> params;
|
||||
const std::vector<std::shared_ptr<Stmt>> body;
|
||||
const std::shared_ptr<Environment> closure;
|
||||
const std::string ownerClass; // empty for non-methods
|
||||
|
||||
Function(std::string name, std::vector<std::string> params,
|
||||
std::vector<std::shared_ptr<Stmt>> body,
|
||||
std::shared_ptr<Environment> closure)
|
||||
: name(name), params(params), body(body), closure(closure) {}
|
||||
std::shared_ptr<Environment> closure,
|
||||
std::string ownerClass = "")
|
||||
: name(name), params(params), body(body), closure(closure), ownerClass(ownerClass) {}
|
||||
};
|
||||
|
||||
struct BuiltinFunction : public Object
|
||||
struct BuiltinFunction
|
||||
{
|
||||
const std::string name;
|
||||
const std::function<Value(std::vector<Value>, int, int)> func;
|
||||
458
src/headers/runtime/Value.h
Normal file
458
src/headers/runtime/Value.h
Normal file
@ -0,0 +1,458 @@
|
||||
#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;
|
||||
@ -7,7 +7,7 @@
|
||||
class Interpreter;
|
||||
class ErrorReporter;
|
||||
|
||||
class StdLib {
|
||||
class BobStdLib {
|
||||
public:
|
||||
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
|
||||
};
|
||||
41
src/sources/builtinModules/base64.cpp
Normal file
41
src/sources/builtinModules/base64.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#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()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
64
src/sources/builtinModules/eval.cpp
Normal file
64
src/sources/builtinModules/eval.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
72
src/sources/builtinModules/io.cpp
Normal file
72
src/sources/builtinModules/io.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
83
src/sources/builtinModules/json.cpp
Normal file
83
src/sources/builtinModules/json.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#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]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
56
src/sources/builtinModules/math.cpp
Normal file
56
src/sources/builtinModules/math.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
132
src/sources/builtinModules/os.cpp
Normal file
132
src/sources/builtinModules/os.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
#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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
63
src/sources/builtinModules/path.cpp
Normal file
63
src/sources/builtinModules/path.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#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());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
35
src/sources/builtinModules/rand.cpp
Normal file
35
src/sources/builtinModules/rand.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#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)];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
25
src/sources/builtinModules/register.cpp
Normal file
25
src/sources/builtinModules/register.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#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
|
||||
}
|
||||
|
||||
|
||||
99
src/sources/builtinModules/sys.cpp
Normal file
99
src/sources/builtinModules/sys.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
#include "sys.h"
|
||||
#include "Interpreter.h"
|
||||
#include "Environment.h"
|
||||
#include "Lexer.h" // for Token and IDENTIFIER
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <limits.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
// Platform-specific includes for memoryUsage()
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach.h>
|
||||
#elif defined(__linux__)
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#elif defined(_WIN32)
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#endif
|
||||
|
||||
void registerSysModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("sys", [](Interpreter::ModuleBuilder& m) {
|
||||
Interpreter& I = m.interpreterRef;
|
||||
m.fn("platform", [](std::vector<Value>, int, int) -> Value {
|
||||
#if defined(_WIN32)
|
||||
return Value(std::string("win32"));
|
||||
#elif defined(__APPLE__)
|
||||
return Value(std::string("darwin"));
|
||||
#elif defined(__linux__)
|
||||
return Value(std::string("linux"));
|
||||
#else
|
||||
return Value(std::string("unknown"));
|
||||
#endif
|
||||
});
|
||||
m.fn("version", [](std::vector<Value>, int, int) -> Value { return Value(std::string("0.0.3")); });
|
||||
// argv(): array of strings
|
||||
m.fn("argv", [&I](std::vector<Value>, int, int) -> Value {
|
||||
std::vector<Value> out;
|
||||
for (const auto& s : I.getArgv()) out.push_back(Value(s));
|
||||
return Value(out);
|
||||
});
|
||||
// executable(): absolute path to the running binary (host-provided)
|
||||
m.fn("executable", [&I](std::vector<Value>, int, int) -> Value { return Value(I.getExecutablePath()); });
|
||||
// modules(): read-only snapshot of module cache
|
||||
m.fn("modules", [&I](std::vector<Value>, int, int) -> Value {
|
||||
Value dictVal = Value(std::unordered_map<std::string, Value>{});
|
||||
auto snapshot = I.getModuleCacheSnapshot();
|
||||
return Value(snapshot);
|
||||
});
|
||||
// memoryUsage(): process RSS in MB (best effort per-platform)
|
||||
m.fn("memoryUsage", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (!a.empty()) return NONE_VALUE;
|
||||
size_t memoryBytes = 0;
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
// macOS
|
||||
struct mach_task_basic_info info;
|
||||
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
|
||||
memoryBytes = info.resident_size;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
// Linux
|
||||
std::ifstream statusFile("/proc/self/status");
|
||||
std::string line;
|
||||
while (std::getline(statusFile, line)) {
|
||||
if (line.substr(0, 6) == "VmRSS:") {
|
||||
std::istringstream iss(line);
|
||||
std::string label, value, unit;
|
||||
iss >> label >> value >> unit;
|
||||
memoryBytes = std::stoull(value) * 1024; // KB -> bytes
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
// Windows
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
||||
memoryBytes = pmc.WorkingSetSize;
|
||||
}
|
||||
#endif
|
||||
double memoryMB = static_cast<double>(memoryBytes) / (1024.0 * 1024.0);
|
||||
return Value(memoryMB);
|
||||
});
|
||||
m.fn("exit", [](std::vector<Value> a, int, int) -> Value {
|
||||
int code = 0; if (!a.empty() && a[0].isNumber()) code = static_cast<int>(a[0].asNumber());
|
||||
std::exit(code);
|
||||
return NONE_VALUE;
|
||||
});
|
||||
// env/cwd/pid moved to os; keep sys minimal
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
31
src/sources/builtinModules/time.cpp
Normal file
31
src/sources/builtinModules/time.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "time_module.h"
|
||||
#include "Interpreter.h"
|
||||
#include "Environment.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
void registerTimeModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("time", [](Interpreter::ModuleBuilder& m) {
|
||||
m.fn("now", [](std::vector<Value>, int, int) -> Value {
|
||||
using namespace std::chrono;
|
||||
auto now = system_clock::now().time_since_epoch();
|
||||
auto us = duration_cast<microseconds>(now).count();
|
||||
return Value(static_cast<double>(us));
|
||||
});
|
||||
m.fn("monotonic", [](std::vector<Value>, int, int) -> Value {
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now().time_since_epoch();
|
||||
auto us = duration_cast<microseconds>(now).count();
|
||||
return Value(static_cast<double>(us));
|
||||
});
|
||||
m.fn("sleep", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
||||
double seconds = a[0].asNumber();
|
||||
if (seconds < 0) return NONE_VALUE;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(seconds * 1000)));
|
||||
return NONE_VALUE;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
73
src/sources/cli/bob.cpp
Normal file
73
src/sources/cli/bob.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include <utility>
|
||||
|
||||
#include "bob.h"
|
||||
#include "Parser.h"
|
||||
|
||||
void Bob::ensureInterpreter(bool interactive) {
|
||||
if (!interpreter) interpreter = msptr(Interpreter)(interactive);
|
||||
applyPendingConfigs();
|
||||
}
|
||||
|
||||
void Bob::runFile(const std::string& path)
|
||||
{
|
||||
ensureInterpreter(false);
|
||||
interpreter->addStdLibFunctions();
|
||||
if (!evalFile(path)) {
|
||||
std::cout << "Execution failed\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Bob::runPrompt()
|
||||
{
|
||||
ensureInterpreter(true);
|
||||
std::cout << "Bob v" << VERSION << ", 2025\n";
|
||||
while(true)
|
||||
{
|
||||
std::string line;
|
||||
std::cout << "\033[0;36m" << "-> " << "\033[0;37m";
|
||||
std::getline(std::cin, line);
|
||||
|
||||
if(std::cin.eof())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset error state before each REPL command
|
||||
errorReporter.resetErrorState();
|
||||
interpreter->addStdLibFunctions();
|
||||
(void)evalString(line, "REPL");
|
||||
}
|
||||
}
|
||||
|
||||
bool Bob::evalFile(const std::string& path) {
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) return false;
|
||||
std::string src((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
errorReporter.loadSource(src, path);
|
||||
interpreter->setErrorReporter(&errorReporter);
|
||||
try {
|
||||
lexer.setErrorReporter(&errorReporter);
|
||||
auto tokens = lexer.Tokenize(src);
|
||||
Parser p(tokens);
|
||||
p.setErrorReporter(&errorReporter);
|
||||
auto statements = p.parse();
|
||||
interpreter->interpret(statements);
|
||||
return true;
|
||||
} catch (...) { return false; }
|
||||
}
|
||||
|
||||
bool Bob::evalString(const std::string& code, const std::string& filename) {
|
||||
errorReporter.loadSource(code, filename);
|
||||
interpreter->setErrorReporter(&errorReporter);
|
||||
try {
|
||||
lexer.setErrorReporter(&errorReporter);
|
||||
auto tokens = lexer.Tokenize(code);
|
||||
Parser p(tokens);
|
||||
p.setErrorReporter(&errorReporter);
|
||||
auto statements = p.parse();
|
||||
interpreter->interpret(statements);
|
||||
return true;
|
||||
} catch (...) { return false; }
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user