Compare commits
No commits in common. "f70c6abd7743e4b83cb76b9f0a75a5d7baaeae3b" and "72a31b28afe69c9ca4b94bf1f340f73bef2f9463" have entirely different histories.
f70c6abd77
...
72a31b28af
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
/.vscode
|
/.vscode
|
||||||
build/
|
build/
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
build-ninja
|
|
||||||
|
|||||||
361
BOB_LANGUAGE_REFERENCE.md
Normal file
361
BOB_LANGUAGE_REFERENCE.md
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
# Bob Language Reference
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Bob is a dynamically typed programming language with a focus on simplicity and expressiveness. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Data Types](#data-types)
|
||||||
|
2. [Variables](#variables)
|
||||||
|
3. [Operators](#operators)
|
||||||
|
4. [Functions](#functions)
|
||||||
|
5. [Control Flow](#control-flow)
|
||||||
|
6. [Standard Library](#standard-library)
|
||||||
|
7. [Error Handling](#error-handling)
|
||||||
|
8. [Examples](#examples)
|
||||||
|
9. [Language Nuances](#language-nuances)
|
||||||
|
|
||||||
|
## Data Types
|
||||||
|
|
||||||
|
### Numbers
|
||||||
|
- **Integers**: `42`, `-10`, `0`
|
||||||
|
- **Floats**: `3.14`, `2.718`, `-1.5`
|
||||||
|
- **Automatic conversion**: Numbers are stored as doubles internally
|
||||||
|
|
||||||
|
### Strings
|
||||||
|
- **Literal strings**: `"Hello, World!"`
|
||||||
|
- **Empty strings**: `""`
|
||||||
|
- **Escape sequences**: Not currently supported
|
||||||
|
|
||||||
|
### Booleans
|
||||||
|
- **True**: `true`
|
||||||
|
- **False**: `false`
|
||||||
|
|
||||||
|
### None
|
||||||
|
- **Null value**: `none` (represents absence of value)
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
### Declaration
|
||||||
|
```bob
|
||||||
|
var name = "Bob";
|
||||||
|
var age = 25;
|
||||||
|
var isActive = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assignment
|
||||||
|
```bob
|
||||||
|
var x = 10;
|
||||||
|
x = 20; // Reassignment
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scoping
|
||||||
|
- **Global scope**: Variables declared at top level
|
||||||
|
- **Local scope**: Variables declared inside functions
|
||||||
|
- **Shadowing**: Local variables can shadow global variables
|
||||||
|
- **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration
|
||||||
|
|
||||||
|
### Variable Behavior
|
||||||
|
```bob
|
||||||
|
var globalVar = 100;
|
||||||
|
|
||||||
|
func testScope() {
|
||||||
|
var localVar = 50; // Local variable
|
||||||
|
return localVar + globalVar; // Can access global
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operators
|
||||||
|
|
||||||
|
### Arithmetic Operators
|
||||||
|
- **Addition**: `+`
|
||||||
|
- **Subtraction**: `-`
|
||||||
|
- **Multiplication**: `*`
|
||||||
|
- **Division**: `/`
|
||||||
|
- **Modulo**: `%`
|
||||||
|
|
||||||
|
### Comparison Operators
|
||||||
|
- **Equal**: `==`
|
||||||
|
- **Not equal**: `!=`
|
||||||
|
- **Greater than**: `>`
|
||||||
|
- **Less than**: `<`
|
||||||
|
- **Greater than or equal**: `>=`
|
||||||
|
- **Less than or equal**: `<=`
|
||||||
|
|
||||||
|
### String Operators
|
||||||
|
|
||||||
|
#### Concatenation
|
||||||
|
Bob supports bidirectional string + number concatenation with automatic type conversion:
|
||||||
|
|
||||||
|
```bob
|
||||||
|
// String + Number
|
||||||
|
"Hello " + 42; // → "Hello 42"
|
||||||
|
"Pi: " + 3.14; // → "Pi: 3.14"
|
||||||
|
|
||||||
|
// Number + String
|
||||||
|
42 + " items"; // → "42 items"
|
||||||
|
3.14 + " is pi"; // → "3.14 is pi"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### String Multiplication
|
||||||
|
```bob
|
||||||
|
"hello" * 3; // → "hellohellohello"
|
||||||
|
3 * "hello"; // → "hellohellohello"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: String multiplication requires whole numbers.
|
||||||
|
|
||||||
|
### Number Formatting
|
||||||
|
Bob automatically formats numbers to show only significant digits:
|
||||||
|
|
||||||
|
```bob
|
||||||
|
"Count: " + 2.0; // → "Count: 2" (no trailing zeros)
|
||||||
|
"Pi: " + 3.14; // → "Pi: 3.14" (exact precision)
|
||||||
|
"Integer: " + 42; // → "Integer: 42" (no decimal)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### Function Declaration
|
||||||
|
```bob
|
||||||
|
func add(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Call
|
||||||
|
```bob
|
||||||
|
var result = add(2, 3); // result = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
- **Any number of parameters** supported
|
||||||
|
- **No default parameters** (not implemented)
|
||||||
|
- **No keyword arguments** (not implemented)
|
||||||
|
|
||||||
|
### Return Values
|
||||||
|
- **Explicit return**: `return value;`
|
||||||
|
- **Implicit return**: Functions return `none` if no return statement
|
||||||
|
- **Early return**: Functions can return from anywhere
|
||||||
|
|
||||||
|
### Closures
|
||||||
|
Bob supports lexical closures with variable capture:
|
||||||
|
|
||||||
|
```bob
|
||||||
|
var outerVar = "Outer";
|
||||||
|
|
||||||
|
func makeGreeter(greeting) {
|
||||||
|
return greeting + " " + outerVar;
|
||||||
|
}
|
||||||
|
|
||||||
|
var greeter = makeGreeter("Hello");
|
||||||
|
// greeter captures outerVar from its lexical scope
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested Functions
|
||||||
|
```bob
|
||||||
|
func outer() {
|
||||||
|
func inner() {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
return inner();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
|
||||||
|
### Current Status
|
||||||
|
**Control flow statements are NOT implemented yet:**
|
||||||
|
- `if` statements
|
||||||
|
- `while` loops
|
||||||
|
- `for` loops
|
||||||
|
- `else` clauses
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
- Conditional execution
|
||||||
|
- Looping constructs
|
||||||
|
- Logical operators (`and`, `or`, `not`)
|
||||||
|
|
||||||
|
## Standard Library
|
||||||
|
|
||||||
|
### Print Function
|
||||||
|
```bob
|
||||||
|
print("Hello, World!");
|
||||||
|
print(42);
|
||||||
|
print(true);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage**: `print(expression)`
|
||||||
|
- Prints the result of any expression
|
||||||
|
- Automatically converts values to strings
|
||||||
|
- Adds newline after output
|
||||||
|
|
||||||
|
### Assert Function
|
||||||
|
```bob
|
||||||
|
assert(condition, "optional message");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
- `assert(true)` - passes silently
|
||||||
|
- `assert(false)` - throws error and stops execution
|
||||||
|
- `assert(condition, "message")` - includes custom error message
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- Terminates program execution on failure
|
||||||
|
- No exception handling mechanism (yet)
|
||||||
|
- Useful for testing and validation
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Current Error Types
|
||||||
|
- **Division by zero**: `DivisionByZeroError`
|
||||||
|
- **Type errors**: `Operands must be of same type`
|
||||||
|
- **String multiplication**: `String multiplier must be whole number`
|
||||||
|
- **Assertion failures**: `Assertion failed: condition is false`
|
||||||
|
|
||||||
|
### Error Behavior
|
||||||
|
- **No try-catch**: Exception handling not implemented
|
||||||
|
- **Program termination**: Errors stop execution immediately
|
||||||
|
- **Error messages**: Descriptive error messages printed to console
|
||||||
|
|
||||||
|
### Common Error Scenarios
|
||||||
|
```bob
|
||||||
|
// Division by zero
|
||||||
|
10 / 0; // Error: DivisionByZeroError
|
||||||
|
|
||||||
|
// Type mismatch
|
||||||
|
"hello" - "world"; // Error: Cannot use '-' on two strings
|
||||||
|
|
||||||
|
// Invalid string multiplication
|
||||||
|
"hello" * 3.5; // Error: String multiplier must be whole number
|
||||||
|
|
||||||
|
// Undefined variable
|
||||||
|
undefinedVar; // Error: Undefined variable
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic Calculator
|
||||||
|
```bob
|
||||||
|
func add(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiply(a, b) {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = add(5, multiply(3, 4));
|
||||||
|
print("Result: " + result); // Result: 17
|
||||||
|
```
|
||||||
|
|
||||||
|
### String Processing
|
||||||
|
```bob
|
||||||
|
func greet(name) {
|
||||||
|
return "Hello, " + name + "!";
|
||||||
|
}
|
||||||
|
|
||||||
|
func repeat(str, count) {
|
||||||
|
return str * count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var greeting = greet("Bob");
|
||||||
|
var repeated = repeat("Ha", 3);
|
||||||
|
print(greeting + " " + repeated); // Hello, Bob! HaHaHa
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variable Scoping Example
|
||||||
|
```bob
|
||||||
|
var globalCounter = 0;
|
||||||
|
|
||||||
|
func increment() {
|
||||||
|
var localCounter = 1;
|
||||||
|
globalCounter = globalCounter + localCounter;
|
||||||
|
return globalCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Before: " + globalCounter); // Before: 0
|
||||||
|
increment();
|
||||||
|
print("After: " + globalCounter); // After: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Language Nuances
|
||||||
|
|
||||||
|
### Type System
|
||||||
|
- **Dynamic typing**: Variables can hold any type
|
||||||
|
- **Automatic conversion**: Numbers and strings convert automatically
|
||||||
|
- **No type annotations**: Types are inferred at runtime
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
- **Reference counting**: Uses `std::shared_ptr` for automatic memory management
|
||||||
|
- **No manual memory management**: No `delete` or `free` needed
|
||||||
|
- **Garbage collection**: Automatic cleanup of unused objects
|
||||||
|
|
||||||
|
### Performance Characteristics
|
||||||
|
- **Interpreted**: Code is executed by an interpreter
|
||||||
|
- **AST-based**: Abstract Syntax Tree for execution
|
||||||
|
- **No compilation**: Direct interpretation of source code
|
||||||
|
|
||||||
|
### Syntax Rules
|
||||||
|
- **Semicolons**: Required at end of statements
|
||||||
|
- **Parentheses**: Required for function calls
|
||||||
|
- **Curly braces**: Required for function bodies
|
||||||
|
- **Case sensitive**: `var` and `Var` are different
|
||||||
|
|
||||||
|
### Comparison with Other Languages
|
||||||
|
|
||||||
|
#### vs Python
|
||||||
|
- **Similar**: Dynamic typing, functions, closures
|
||||||
|
- **Different**: No `global` keyword, automatic string conversion, different error handling
|
||||||
|
|
||||||
|
#### vs JavaScript
|
||||||
|
- **Similar**: Automatic type conversion, string concatenation
|
||||||
|
- **Different**: No `undefined`, different syntax, no `null`
|
||||||
|
|
||||||
|
#### vs Lua
|
||||||
|
- **Similar**: Dynamic typing, functions
|
||||||
|
- **Different**: No `local` keyword, different scoping rules
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
- **No control flow**: No if/while/for statements
|
||||||
|
- **No logical operators**: No `and`/`or`/`not`
|
||||||
|
- **No exception handling**: No try-catch blocks
|
||||||
|
- **No modules**: No import/export system
|
||||||
|
- **No classes**: No object-oriented features
|
||||||
|
- **No arrays/lists**: No built-in collection types
|
||||||
|
- **No dictionaries**: No key-value data structures
|
||||||
|
|
||||||
|
### Future Features
|
||||||
|
- Control flow statements
|
||||||
|
- Logical operators
|
||||||
|
- Exception handling
|
||||||
|
- Collection types (arrays, dictionaries)
|
||||||
|
- Modules and imports
|
||||||
|
- Object-oriented programming
|
||||||
|
- Standard library expansion
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Running Bob Code
|
||||||
|
```bash
|
||||||
|
# Compile the interpreter
|
||||||
|
make
|
||||||
|
|
||||||
|
# Run a Bob file
|
||||||
|
./build/bob your_file.bob
|
||||||
|
|
||||||
|
# Run the comprehensive test suite
|
||||||
|
./build/bob test_bob_language.bob
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Extension
|
||||||
|
- **`.bob`**: Standard file extension for Bob source code
|
||||||
|
|
||||||
|
### Interactive Mode
|
||||||
|
- **Not implemented**: No REPL (Read-Eval-Print Loop) yet
|
||||||
|
- **File-based**: All code must be in `.bob` files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.*
|
||||||
160
CMakeLists.txt
160
CMakeLists.txt
@ -1,160 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
|
|
||||||
# Project definition
|
|
||||||
project(bob
|
|
||||||
VERSION 0.0.3
|
|
||||||
DESCRIPTION "Bob Language Interpreter"
|
|
||||||
LANGUAGES CXX
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set C++ standard
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Build type defaults
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
|
||||||
set(CMAKE_BUILD_TYPE Release)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Output directories
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|
||||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|
||||||
|
|
||||||
# Compiler-specific options
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
set(BOB_COMPILE_OPTIONS
|
|
||||||
-Wall -Wextra
|
|
||||||
-Wno-unused-variable
|
|
||||||
-Wno-unused-parameter
|
|
||||||
-Wno-switch
|
|
||||||
)
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
list(APPEND BOB_COMPILE_OPTIONS -O3 -march=native)
|
|
||||||
endif()
|
|
||||||
elseif(MSVC)
|
|
||||||
set(BOB_COMPILE_OPTIONS
|
|
||||||
/W4
|
|
||||||
/wd4100 # unreferenced formal parameter
|
|
||||||
/wd4101 # unreferenced local variable
|
|
||||||
)
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
list(APPEND BOB_COMPILE_OPTIONS /O2)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Collect source files
|
|
||||||
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_PARSING_SOURCES "src/sources/parsing/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_STDLIB_SOURCES "src/sources/stdlib/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.cpp")
|
|
||||||
|
|
||||||
# All source files
|
|
||||||
set(BOB_ALL_SOURCES
|
|
||||||
${BOB_RUNTIME_SOURCES}
|
|
||||||
${BOB_PARSING_SOURCES}
|
|
||||||
${BOB_STDLIB_SOURCES}
|
|
||||||
${BOB_CLI_SOURCES}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the executable
|
|
||||||
add_executable(bob ${BOB_ALL_SOURCES})
|
|
||||||
|
|
||||||
# Include directories
|
|
||||||
target_include_directories(bob PRIVATE
|
|
||||||
src/headers/runtime
|
|
||||||
src/headers/parsing
|
|
||||||
src/headers/stdlib
|
|
||||||
src/headers/cli
|
|
||||||
src/headers/common
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply compiler options
|
|
||||||
target_compile_options(bob PRIVATE ${BOB_COMPILE_OPTIONS})
|
|
||||||
|
|
||||||
# Enable Link Time Optimization (LTO) for Release builds
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
include(CheckIPOSupported)
|
|
||||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
|
||||||
if(ipo_supported)
|
|
||||||
message(STATUS "IPO/LTO enabled")
|
|
||||||
set_property(TARGET bob PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
|
||||||
else()
|
|
||||||
message(WARNING "IPO/LTO not supported: ${ipo_error}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Platform-specific settings
|
|
||||||
if(WIN32)
|
|
||||||
# Windows-specific settings
|
|
||||||
target_compile_definitions(bob PRIVATE _CRT_SECURE_NO_WARNINGS)
|
|
||||||
elseif(UNIX AND NOT APPLE)
|
|
||||||
# Linux-specific settings
|
|
||||||
target_link_libraries(bob PRIVATE pthread)
|
|
||||||
elseif(APPLE)
|
|
||||||
# macOS-specific settings
|
|
||||||
set_target_properties(bob PROPERTIES
|
|
||||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Generate compile_commands.json for language servers
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
# Testing support
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
# Add test for the main test suite
|
|
||||||
add_test(
|
|
||||||
NAME bob_test_suite
|
|
||||||
COMMAND bob test_bob_language.bob
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Custom target for running tests with verbose output
|
|
||||||
add_custom_target(test_verbose
|
|
||||||
COMMAND ${CMAKE_CTEST_COMMAND} --verbose
|
|
||||||
DEPENDS bob
|
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Install rules
|
|
||||||
install(TARGETS bob
|
|
||||||
RUNTIME DESTINATION bin
|
|
||||||
COMPONENT Runtime
|
|
||||||
)
|
|
||||||
|
|
||||||
# Install test files (optional)
|
|
||||||
install(FILES test_bob_language.bob
|
|
||||||
DESTINATION share/bob/tests
|
|
||||||
COMPONENT Tests
|
|
||||||
)
|
|
||||||
|
|
||||||
# CPack configuration for packaging
|
|
||||||
set(CPACK_PACKAGE_NAME "Bob Language")
|
|
||||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Bob Language Interpreter")
|
|
||||||
set(CPACK_PACKAGE_VENDOR "Bob Language Team")
|
|
||||||
set(CPACK_PACKAGE_CONTACT "your-email@example.com")
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(CPACK_GENERATOR "ZIP;NSIS")
|
|
||||||
elseif(APPLE)
|
|
||||||
set(CPACK_GENERATOR "TGZ;DragNDrop")
|
|
||||||
else()
|
|
||||||
set(CPACK_GENERATOR "TGZ;DEB;RPM")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(CPack)
|
|
||||||
|
|
||||||
# Print configuration summary
|
|
||||||
message(STATUS "Bob Language Build Configuration:")
|
|
||||||
message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
|
|
||||||
message(STATUS " Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
|
||||||
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
|
||||||
message(STATUS " Install Prefix: ${CMAKE_INSTALL_PREFIX}")
|
|
||||||
message(STATUS " Runtime Sources: ${BOB_RUNTIME_SOURCES}")
|
|
||||||
message(STATUS " Parsing Sources: ${BOB_PARSING_SOURCES}")
|
|
||||||
message(STATUS " Stdlib Sources: ${BOB_STDLIB_SOURCES}")
|
|
||||||
message(STATUS " CLI Sources: ${BOB_CLI_SOURCES}")
|
|
||||||
48
Makefile
Normal file
48
Makefile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Makefile
|
||||||
|
|
||||||
|
# Compiler
|
||||||
|
CC = g++
|
||||||
|
|
||||||
|
# Compiler flags
|
||||||
|
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
|
||||||
|
|
||||||
|
# Source directory
|
||||||
|
SRC_DIR = ./source
|
||||||
|
|
||||||
|
# Output directory
|
||||||
|
BUILD_DIR = ./build
|
||||||
|
|
||||||
|
# Find all C++ files recursively in the source directory
|
||||||
|
CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp')
|
||||||
|
|
||||||
|
# Generate object file names by replacing the source directory with the build directory
|
||||||
|
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||||
|
|
||||||
|
# Create directories for object files
|
||||||
|
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
all: build
|
||||||
|
|
||||||
|
# Rule to create necessary directories
|
||||||
|
$(DIRS):
|
||||||
|
mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@)
|
||||||
|
|
||||||
|
# Rule to compile object files
|
||||||
|
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
# Rule to link object files into the final executable
|
||||||
|
$(BUILD_DIR)/bob: $(OBJ_FILES)
|
||||||
|
$(CC) $(CFLAGS) $^ -o $@
|
||||||
|
|
||||||
|
|
||||||
|
run:
|
||||||
|
./$(BUILD_DIR)/bob
|
||||||
|
|
||||||
|
build: clean $(BUILD_DIR)/bob
|
||||||
|
|
||||||
|
|
||||||
|
# Clean build directory
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)/*
|
||||||
38
README.md
38
README.md
@ -1,37 +1 @@
|
|||||||
```
|
# Bob
|
||||||
██████╗ ██████╗ ██████╗
|
|
||||||
██╔══██╗██╔═══██╗██╔══██╗
|
|
||||||
██████╔╝██║ ██║██████╔╝
|
|
||||||
██╔══██╗██║ ██║██╔══██╗
|
|
||||||
██████╔╝╚██████╔╝██████╔╝
|
|
||||||
╚═════╝ ╚═════╝ ╚═════╝
|
|
||||||
```
|
|
||||||
|
|
||||||
A modern programming language with all the features/sytax I prefer
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- **[Language Reference](Reference/BOB_LANGUAGE_REFERENCE.md)** - Language syntax and features
|
|
||||||
- **[Build Guide](Reference/BUILD.md)** - How to build Bob
|
|
||||||
- **[Roadmap](Reference/ROADMAP.md)** - What's done and what might come next
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Core Types**: Numbers, strings, booleans, arrays, dictionaries, functions
|
|
||||||
- **Advanced Functions**: First-class functions, closures, anonymous functions, tail call optimization
|
|
||||||
- **Control Flow**: If/else statements, while/do-while/for loops, break/continue
|
|
||||||
- **Operators**: Arithmetic, logical, bitwise, comparison, compound assignment (+=, -=, etc.)
|
|
||||||
- **Built-in Functions**: print, input, assert, len, push, pop, keys, values, type conversion (toString, toNumber, toInt)
|
|
||||||
- **Other Stuff**: String interpolation, escape sequences, file I/O, eval, time/sleep, random
|
|
||||||
- **Memory**: Automatic cleanup, no manual memory management
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
|
|
||||||
# Run
|
|
||||||
./build/bin/bob your_file.bob
|
|
||||||
```
|
|
||||||
|
|||||||
203
ROADMAP.md
Normal file
203
ROADMAP.md
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
# Bob Language Development Roadmap
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
- Basic expressions (arithmetic, comparison, logical)
|
||||||
|
- Variables and assignment
|
||||||
|
- Print statements (converted to standard library function)
|
||||||
|
- Block statements
|
||||||
|
- Environment/scoping
|
||||||
|
- Function implementation (COMPLETED)
|
||||||
|
- Return statements (COMPLETED)
|
||||||
|
- Closures (COMPLETED)
|
||||||
|
- Assert function (COMPLETED)
|
||||||
|
- Standard library infrastructure (COMPLETED)
|
||||||
|
- First-class functions and higher-order functions (COMPLETED)
|
||||||
|
- String + number concatenation with smart formatting (COMPLETED)
|
||||||
|
- String multiplication (COMPLETED)
|
||||||
|
- Alphanumeric identifiers (COMPLETED)
|
||||||
|
- Comprehensive testing framework (COMPLETED)
|
||||||
|
|
||||||
|
## Phase 1: Core Language Features (High Priority)
|
||||||
|
|
||||||
|
### 1. Control Flow
|
||||||
|
```bob
|
||||||
|
// If statements
|
||||||
|
if (x > 10) {
|
||||||
|
print "big";
|
||||||
|
} else {
|
||||||
|
print "small";
|
||||||
|
}
|
||||||
|
|
||||||
|
// While loops
|
||||||
|
var i = 0;
|
||||||
|
while (i < 5) {
|
||||||
|
print i;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Add `IfStmt` and `WhileStmt` to Statement.h
|
||||||
|
- Update parser to handle `if` and `while` keywords
|
||||||
|
- Implement control flow in interpreter
|
||||||
|
|
||||||
|
### 2. Logical Operators
|
||||||
|
```bob
|
||||||
|
// Currently missing: and, or, not operators
|
||||||
|
if (x > 0 and y < 10) {
|
||||||
|
print "valid range";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Add `and`, `or`, `not` operator parsing
|
||||||
|
- Implement logical operator evaluation in interpreter
|
||||||
|
|
||||||
|
### 3. Better Error Handling
|
||||||
|
```bob
|
||||||
|
// Current: Basic error messages
|
||||||
|
// Goal: Line numbers, better context
|
||||||
|
Error at line 5: Expected ';' after expression
|
||||||
|
print 42
|
||||||
|
^
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 2: Data Structures (Medium Priority)
|
||||||
|
|
||||||
|
### 4. Arrays/Lists
|
||||||
|
```bob
|
||||||
|
var numbers = [1, 2, 3, 4];
|
||||||
|
print numbers[0]; // 1
|
||||||
|
numbers[1] = 42;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Maps/Dictionaries
|
||||||
|
```bob
|
||||||
|
var person = {"name": "Bob", "age": 25};
|
||||||
|
print person["name"];
|
||||||
|
person["city"] = "NYC";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 3: Standard Library (Medium Priority)
|
||||||
|
|
||||||
|
### 6. Additional Built-in Functions
|
||||||
|
```bob
|
||||||
|
len("hello"); // String length
|
||||||
|
input("Enter name: "); // User input
|
||||||
|
random(1, 100); // Random numbers
|
||||||
|
type(42); // Type checking
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. File I/O
|
||||||
|
```bob
|
||||||
|
var content = readFile("data.txt");
|
||||||
|
writeFile("output.txt", "Hello World");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4: Advanced Features (Lower Priority)
|
||||||
|
|
||||||
|
### 8. Classes & Objects
|
||||||
|
```bob
|
||||||
|
class Person {
|
||||||
|
init(name, age) {
|
||||||
|
this.name = name;
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
greet() {
|
||||||
|
print "Hello, I'm " + this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bob = Person("Bob", 25);
|
||||||
|
bob.greet();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Modules/Imports
|
||||||
|
```bob
|
||||||
|
import "math.bob";
|
||||||
|
import "utils.bob";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Type System
|
||||||
|
```bob
|
||||||
|
// Optional type annotations
|
||||||
|
fun add(a: number, b: number): number {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Tips
|
||||||
|
|
||||||
|
### For Each Feature:
|
||||||
|
1. Lexer: Add new tokens if needed
|
||||||
|
2. Parser: Add new expression/statement types
|
||||||
|
3. AST: Define new node types
|
||||||
|
4. Interpreter: Implement evaluation logic
|
||||||
|
5. Test: Create test cases using assert function
|
||||||
|
|
||||||
|
### Testing Strategy:
|
||||||
|
```bob
|
||||||
|
// Use the new assert function for comprehensive testing
|
||||||
|
assert(add(2, 3) == 5, "add(2, 3) should equal 5");
|
||||||
|
assert(x > 0, "x should be positive");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommended Next Steps
|
||||||
|
|
||||||
|
1. Add if statements (fundamental control flow)
|
||||||
|
2. Add while loops (enables iteration)
|
||||||
|
3. Implement logical operators (and, or, not)
|
||||||
|
4. Improve error messages (better developer experience)
|
||||||
|
5. Add arrays (most useful data structure)
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
- [x] Can write simple functions
|
||||||
|
- [x] Can use return statements
|
||||||
|
- [x] Can use closures
|
||||||
|
- [x] Has assert function for testing
|
||||||
|
- [x] Has standard library infrastructure
|
||||||
|
- [x] Supports first-class functions
|
||||||
|
- [x] Has comprehensive testing framework
|
||||||
|
- [ ] Can use if/else statements
|
||||||
|
- [ ] Can use while loops
|
||||||
|
- [ ] Can use logical operators
|
||||||
|
- [ ] Can work with arrays
|
||||||
|
- [ ] Can read/write files
|
||||||
|
- [ ] Has good error messages
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Crafting Interpreters](https://craftinginterpreters.com/) - Excellent resource for language implementation
|
||||||
|
- [Bob's current source code](./source/) - Your implementation
|
||||||
|
- [Test files](./*.bob) - Examples of current functionality
|
||||||
|
|
||||||
|
## Recent Achievements
|
||||||
|
|
||||||
|
### Function Implementation (COMPLETED)
|
||||||
|
- Function declarations with parameters
|
||||||
|
- Function calls with arguments
|
||||||
|
- Return statements
|
||||||
|
- Proper scoping and closures
|
||||||
|
- Nested function calls
|
||||||
|
|
||||||
|
### Standard Library (COMPLETED)
|
||||||
|
- `print()` function (converted from statement)
|
||||||
|
- `assert()` function with custom messages
|
||||||
|
- Extensible architecture for adding more functions
|
||||||
|
|
||||||
|
### Testing Framework (COMPLETED)
|
||||||
|
- Comprehensive test suite using assert
|
||||||
|
- Tests for all language features
|
||||||
|
- Proper error handling and execution stopping
|
||||||
|
|
||||||
|
### Advanced Language Features (COMPLETED)
|
||||||
|
- First-class functions and higher-order functions
|
||||||
|
- Function passing as arguments
|
||||||
|
- Function composition patterns
|
||||||
|
- Callback patterns and function storage
|
||||||
|
- String + number concatenation with smart formatting
|
||||||
|
- String multiplication (string * number, number * string)
|
||||||
|
- Alphanumeric identifiers support
|
||||||
|
- Stress testing with 100-parameter functions
|
||||||
@ -1,382 +0,0 @@
|
|||||||
# Bob Language Reference
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build Bob
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
|
|
||||||
# Run a file
|
|
||||||
./build/bin/bob script.bob
|
|
||||||
|
|
||||||
# Interactive mode
|
|
||||||
./build/bin/bob
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Types
|
|
||||||
|
|
||||||
### Numbers
|
|
||||||
```go
|
|
||||||
var integer = 42;
|
|
||||||
var float = 3.14;
|
|
||||||
var negative = -10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strings
|
|
||||||
```go
|
|
||||||
var text = "Hello, World!";
|
|
||||||
var empty = "";
|
|
||||||
var escaped = "Line 1\nLine 2\t\"quoted\"";
|
|
||||||
var concat = "Hello" + " " + "World";
|
|
||||||
var repeat = "hi" * 3; // "hihihi"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Booleans
|
|
||||||
```go
|
|
||||||
var yes = true;
|
|
||||||
var no = false;
|
|
||||||
```
|
|
||||||
|
|
||||||
### None
|
|
||||||
```go
|
|
||||||
var nothing = none;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrays
|
|
||||||
```go
|
|
||||||
var numbers = [1, 2, 3];
|
|
||||||
var mixed = [42, "hello", true];
|
|
||||||
var nested = [[1, 2], [3, 4]];
|
|
||||||
|
|
||||||
// Access and modify
|
|
||||||
print(numbers[0]); // 1
|
|
||||||
numbers[1] = 99;
|
|
||||||
|
|
||||||
// Array properties (read-only)
|
|
||||||
print(numbers.length); // 3
|
|
||||||
print(numbers.first); // 1
|
|
||||||
print(numbers.last); // 3
|
|
||||||
print(numbers.empty); // false
|
|
||||||
|
|
||||||
var empty = [];
|
|
||||||
print(empty.length); // 0
|
|
||||||
print(empty.first); // none
|
|
||||||
print(empty.empty); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dictionaries
|
|
||||||
```go
|
|
||||||
var person = {"name": "Alice", "age": 30};
|
|
||||||
|
|
||||||
// Access and modify (bracket notation)
|
|
||||||
print(person["name"]); // Alice
|
|
||||||
person["city"] = "NYC";
|
|
||||||
|
|
||||||
// Access and modify (dot notation - cleaner syntax)
|
|
||||||
print(person.name); // Alice
|
|
||||||
person.city = "NYC";
|
|
||||||
person.age = 31;
|
|
||||||
|
|
||||||
// Both notations are equivalent
|
|
||||||
assert(person.name == person["name"]);
|
|
||||||
|
|
||||||
// Dictionary properties (built-in)
|
|
||||||
print(person.length); // 3 (number of key-value pairs)
|
|
||||||
print(person.empty); // false
|
|
||||||
print(person.keys); // ["name", "age", "city"]
|
|
||||||
print(person.values); // ["Alice", 31, "NYC"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variables
|
|
||||||
|
|
||||||
```go
|
|
||||||
var x = 10; // Declaration
|
|
||||||
x = 20; // Reassignment
|
|
||||||
y += 5; // Compound assignment
|
|
||||||
z++; // Increment (for array elements)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operators
|
|
||||||
|
|
||||||
### Arithmetic
|
|
||||||
```go
|
|
||||||
+ - * / % // Basic math
|
|
||||||
-x // Unary minus
|
|
||||||
```
|
|
||||||
|
|
||||||
### Comparison
|
|
||||||
```go
|
|
||||||
== != < > <= >= // Comparisons
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logical
|
|
||||||
```go
|
|
||||||
&& || ! // AND, OR, NOT (short-circuit)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bitwise
|
|
||||||
```go
|
|
||||||
& | ^ << >> ~ // Bitwise operations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compound Assignment
|
|
||||||
```go
|
|
||||||
+= -= *= /= %= // Arithmetic compound
|
|
||||||
&= |= ^= <<= >>= // Bitwise compound
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ternary
|
|
||||||
```go
|
|
||||||
var result = condition ? "yes" : "no";
|
|
||||||
```
|
|
||||||
|
|
||||||
## Control Flow
|
|
||||||
|
|
||||||
### If Statements
|
|
||||||
```go
|
|
||||||
if (x > 0) {
|
|
||||||
print("positive");
|
|
||||||
} else if (x < 0) {
|
|
||||||
print("negative");
|
|
||||||
} else {
|
|
||||||
print("zero");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### While Loops
|
|
||||||
```go
|
|
||||||
while (i < 10) {
|
|
||||||
print(i);
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Do-While Loops
|
|
||||||
```go
|
|
||||||
do {
|
|
||||||
print(i);
|
|
||||||
i = i + 1;
|
|
||||||
} while (i < 10);
|
|
||||||
```
|
|
||||||
|
|
||||||
### For Loops
|
|
||||||
```go
|
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
|
||||||
print(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break and continue work in all loops
|
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
|
||||||
if (i == 5) break;
|
|
||||||
if (i % 2 == 0) continue;
|
|
||||||
print(i);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
|
|
||||||
### Basic Functions
|
|
||||||
```go
|
|
||||||
func greet(name) {
|
|
||||||
return "Hello, " + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = greet("Alice");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Anonymous Functions
|
|
||||||
```go
|
|
||||||
var square = func(x) { return x * x; };
|
|
||||||
var result = square(5);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Closures
|
|
||||||
```go
|
|
||||||
func makeCounter() {
|
|
||||||
var count = 0;
|
|
||||||
return func() {
|
|
||||||
count = count + 1;
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var counter = makeCounter();
|
|
||||||
print(counter()); // 1
|
|
||||||
print(counter()); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
### First-Class Functions
|
|
||||||
```go
|
|
||||||
func apply(fn, x) {
|
|
||||||
return fn(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = apply(square, 10); // 100
|
|
||||||
```
|
|
||||||
|
|
||||||
## Built-in Functions
|
|
||||||
|
|
||||||
### I/O
|
|
||||||
```go
|
|
||||||
print("Hello"); // Output with newline
|
|
||||||
printRaw("No newline"); // Output without newline
|
|
||||||
var input = input("Enter something: ");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type Conversion
|
|
||||||
```go
|
|
||||||
toString(42); // "42"
|
|
||||||
toNumber("3.14"); // 3.14
|
|
||||||
toInt(3.9); // 3
|
|
||||||
toBoolean(1); // true
|
|
||||||
type(42); // "number"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrays & Strings
|
|
||||||
```go
|
|
||||||
len([1, 2, 3]); // 3
|
|
||||||
len("hello"); // 5
|
|
||||||
push(array, value); // Add to end
|
|
||||||
pop(array); // Remove from end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dictionaries
|
|
||||||
```go
|
|
||||||
keys(dict); // Array of keys
|
|
||||||
values(dict); // Array of values
|
|
||||||
has(dict, "key"); // Check if key exists
|
|
||||||
```
|
|
||||||
|
|
||||||
### Utility
|
|
||||||
```go
|
|
||||||
assert(condition, "message"); // Testing
|
|
||||||
time(); // Current time in microseconds
|
|
||||||
sleep(1.5); // Sleep for 1.5 seconds
|
|
||||||
random(); // Random number 0-1
|
|
||||||
eval("print('Hello');"); // Execute string as code
|
|
||||||
exit(0); // Exit program
|
|
||||||
```
|
|
||||||
|
|
||||||
### File I/O
|
|
||||||
```go
|
|
||||||
var content = readFile("data.txt");
|
|
||||||
writeFile("output.txt", "Hello");
|
|
||||||
var lines = readLines("config.txt");
|
|
||||||
var exists = fileExists("test.txt");
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### String Interpolation
|
|
||||||
```go
|
|
||||||
var name = "Alice";
|
|
||||||
var age = 30;
|
|
||||||
var message = "Name: " + name + ", Age: " + age;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tail Call Optimization
|
|
||||||
```go
|
|
||||||
func factorial(n, acc) {
|
|
||||||
if (n <= 1) return acc;
|
|
||||||
return factorial(n - 1, n * acc); // Tail call optimized
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Assignment System
|
|
||||||
Bob has a unique assignment system that prevents common bugs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Assignment statements (everywhere)
|
|
||||||
var x = 5;
|
|
||||||
x = 10;
|
|
||||||
y += 5;
|
|
||||||
|
|
||||||
// Assignment expressions (only in for loops)
|
|
||||||
for (var i = 0; i < 5; i = i + 1) { } // OK
|
|
||||||
for (j = 0; j < 5; j += 1) { } // OK
|
|
||||||
|
|
||||||
// This prevents bugs like:
|
|
||||||
if (x = 10) { } // PARSE ERROR - prevents accidental assignment
|
|
||||||
```
|
|
||||||
|
|
||||||
## Memory Management
|
|
||||||
|
|
||||||
Bob automatically manages memory - no manual allocation or deallocation needed. Objects are cleaned up when no longer referenced.
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
Bob provides helpful error messages with context:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Runtime errors show line numbers and context
|
|
||||||
var x = undefined_variable; // Error: Undefined variable 'undefined_variable' at line 2
|
|
||||||
|
|
||||||
// Type errors are caught
|
|
||||||
var result = "hello" / 5; // Error: Cannot divide string by number
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interactive Mode (REPL)
|
|
||||||
|
|
||||||
Bob includes an interactive mode for experimenting:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ./build/bin/bob
|
|
||||||
Bob Interactive Mode
|
|
||||||
> var x = 42;
|
|
||||||
> print(x * 2);
|
|
||||||
84
|
|
||||||
> exit();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Fibonacci with Tail Call Optimization
|
|
||||||
```go
|
|
||||||
func fib(n, a, b) {
|
|
||||||
if (n == 0) return a;
|
|
||||||
if (n == 1) return b;
|
|
||||||
return fib(n - 1, b, a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
print(fib(40, 0, 1)); // Fast even for large numbers
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with Data Structures
|
|
||||||
```go
|
|
||||||
var people = [
|
|
||||||
{"name": "Alice", "age": 30},
|
|
||||||
{"name": "Bob", "age": 25}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var i = 0; i < len(people); i = i + 1) {
|
|
||||||
var person = people[i];
|
|
||||||
print(person["name"] + " is " + person["age"] + " years old");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### File Processing
|
|
||||||
```go
|
|
||||||
var lines = readLines("data.txt");
|
|
||||||
var processed = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < len(lines); i = i + 1) {
|
|
||||||
var line = lines[i];
|
|
||||||
if (len(line) > 0) {
|
|
||||||
push(processed, "Processed: " + line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = "";
|
|
||||||
for (var i = 0; i < len(processed); i = i + 1) {
|
|
||||||
output = output + processed[i];
|
|
||||||
if (i < len(processed) - 1) {
|
|
||||||
output = output + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeFile("output.txt", output);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*For more examples, see the comprehensive test suite in `test_bob_language.bob`*
|
|
||||||
@ -1,248 +0,0 @@
|
|||||||
# Bob Language - Build Guide
|
|
||||||
|
|
||||||
This guide describes how to build the Bob language interpreter using CMake + Ninja across different platforms.
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
- **CMake** 3.20 or later
|
|
||||||
- **Ninja** build system
|
|
||||||
- **C++17** compatible compiler
|
|
||||||
|
|
||||||
### Platform-Specific Setup
|
|
||||||
|
|
||||||
#### 🍎 macOS
|
|
||||||
```bash
|
|
||||||
# Install via Homebrew
|
|
||||||
brew install cmake ninja
|
|
||||||
|
|
||||||
# Or via MacPorts
|
|
||||||
sudo port install cmake ninja
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 🐧 Linux
|
|
||||||
|
|
||||||
**Ubuntu/Debian:**
|
|
||||||
```bash
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install cmake ninja-build build-essential
|
|
||||||
```
|
|
||||||
|
|
||||||
**RHEL/CentOS/Fedora:**
|
|
||||||
```bash
|
|
||||||
# RHEL/CentOS
|
|
||||||
sudo yum install cmake ninja-build gcc-c++
|
|
||||||
|
|
||||||
# Fedora
|
|
||||||
sudo dnf install cmake ninja-build gcc-c++
|
|
||||||
```
|
|
||||||
|
|
||||||
**Arch Linux:**
|
|
||||||
```bash
|
|
||||||
sudo pacman -S cmake ninja gcc
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 🪟 Windows
|
|
||||||
|
|
||||||
**Option 1: Visual Studio (Recommended)**
|
|
||||||
- Install Visual Studio 2019+ with C++ workload
|
|
||||||
- Install CMake via Visual Studio Installer
|
|
||||||
- Install Ninja: `winget install Ninja-build.Ninja`
|
|
||||||
|
|
||||||
**Option 2: MSYS2/MinGW**
|
|
||||||
```bash
|
|
||||||
# In MSYS2 terminal
|
|
||||||
pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 3: Chocolatey**
|
|
||||||
```powershell
|
|
||||||
# In Administrator PowerShell
|
|
||||||
choco install cmake ninja visualstudio2022buildtools
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔨 Build Commands
|
|
||||||
|
|
||||||
### Standard Build
|
|
||||||
|
|
||||||
**Release Build (Optimized):**
|
|
||||||
```bash
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Debug Build (Development):**
|
|
||||||
```bash
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Debug
|
|
||||||
ninja -C build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Platform-Specific Examples
|
|
||||||
|
|
||||||
#### macOS/Linux
|
|
||||||
```bash
|
|
||||||
# Configure and build
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
|
|
||||||
# Run interpreter
|
|
||||||
./build/bin/bob
|
|
||||||
|
|
||||||
# Run test suite
|
|
||||||
./build/bin/bob test_bob_language.bob
|
|
||||||
|
|
||||||
# Run with custom script
|
|
||||||
./build/bin/bob your_script.bob
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Windows (PowerShell/CMD)
|
|
||||||
```powershell
|
|
||||||
# Configure and build
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
|
|
||||||
# Run interpreter
|
|
||||||
.\build\bin\bob.exe
|
|
||||||
|
|
||||||
# Run test suite
|
|
||||||
.\build\bin\bob.exe test_bob_language.bob
|
|
||||||
|
|
||||||
# Run with custom script
|
|
||||||
.\build\bin\bob.exe your_script.bob
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Windows (MSYS2/Git Bash)
|
|
||||||
```bash
|
|
||||||
# Same as macOS/Linux
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
./build/bin/bob.exe test_bob_language.bob
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 Testing
|
|
||||||
|
|
||||||
### Automated Testing
|
|
||||||
|
|
||||||
**All Platforms:**
|
|
||||||
```bash
|
|
||||||
# Build first
|
|
||||||
ninja -C build
|
|
||||||
|
|
||||||
# Run tests via CTest
|
|
||||||
cd build
|
|
||||||
ctest --output-on-failure
|
|
||||||
|
|
||||||
# Or run tests verbosely
|
|
||||||
ctest --verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows PowerShell:**
|
|
||||||
```powershell
|
|
||||||
ninja -C build
|
|
||||||
cd build
|
|
||||||
ctest --output-on-failure
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Testing
|
|
||||||
|
|
||||||
**Interactive Mode:**
|
|
||||||
```bash
|
|
||||||
# Unix-like systems
|
|
||||||
./build/bin/bob
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
.\build\bin\bob.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
**Script Execution:**
|
|
||||||
```bash
|
|
||||||
# Unix-like systems
|
|
||||||
./build/bin/bob examples/hello.bob
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
.\build\bin\bob.exe examples\hello.bob
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚡ Performance
|
|
||||||
|
|
||||||
**CMake + Ninja** provides fast, cross-platform builds with excellent incremental compilation.
|
|
||||||
|
|
||||||
## 🔧 Advanced Configuration
|
|
||||||
|
|
||||||
### Custom Install Location
|
|
||||||
|
|
||||||
**Unix-like:**
|
|
||||||
```bash
|
|
||||||
cmake -G Ninja -B build \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DCMAKE_INSTALL_PREFIX=/opt/bob
|
|
||||||
ninja -C build install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows:**
|
|
||||||
```powershell
|
|
||||||
cmake -G Ninja -B build `
|
|
||||||
-DCMAKE_BUILD_TYPE=Release `
|
|
||||||
-DCMAKE_INSTALL_PREFIX="C:\Program Files\Bob"
|
|
||||||
ninja -C build install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiler Selection
|
|
||||||
|
|
||||||
**GCC:**
|
|
||||||
```bash
|
|
||||||
cmake -G Ninja -B build \
|
|
||||||
-DCMAKE_CXX_COMPILER=g++ \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
|
||||||
```
|
|
||||||
|
|
||||||
**Clang:**
|
|
||||||
```bash
|
|
||||||
cmake -G Ninja -B build \
|
|
||||||
-DCMAKE_CXX_COMPILER=clang++ \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
|
||||||
```
|
|
||||||
|
|
||||||
**MSVC (Windows):**
|
|
||||||
```powershell
|
|
||||||
cmake -G Ninja -B build `
|
|
||||||
-DCMAKE_CXX_COMPILER=cl `
|
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**CMake not found:**
|
|
||||||
- **macOS**: `brew install cmake`
|
|
||||||
- **Ubuntu**: `sudo apt install cmake`
|
|
||||||
- **Windows**: Install via Visual Studio or winget
|
|
||||||
|
|
||||||
**Ninja not found:**
|
|
||||||
- **macOS**: `brew install ninja`
|
|
||||||
- **Ubuntu**: `sudo apt install ninja-build`
|
|
||||||
- **Windows**: `winget install Ninja-build.Ninja`
|
|
||||||
|
|
||||||
**Compiler errors:**
|
|
||||||
- Ensure C++17 compiler is installed
|
|
||||||
- **Linux**: `sudo apt install build-essential`
|
|
||||||
- **Windows**: Install Visual Studio Build Tools
|
|
||||||
|
|
||||||
**Permission denied (Windows):**
|
|
||||||
- Run PowerShell as Administrator for system-wide installs
|
|
||||||
|
|
||||||
### Build Cache Issues
|
|
||||||
|
|
||||||
**Clean build:**
|
|
||||||
```bash
|
|
||||||
# Remove build directory
|
|
||||||
rm -rf build # Unix-like
|
|
||||||
rmdir /s build # Windows CMD
|
|
||||||
Remove-Item -Recurse build # PowerShell
|
|
||||||
|
|
||||||
# Reconfigure
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
```
|
|
||||||
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
# Bob Language Development Roadmap
|
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
Bob is a mature, working programming language with a modern architecture and comprehensive feature set.
|
|
||||||
|
|
||||||
### ✅ **Core Language Features (Complete)**
|
|
||||||
|
|
||||||
#### **Data Types & Variables**
|
|
||||||
- **Numbers**: Integers, floats, automatic conversion
|
|
||||||
- **Strings**: Literals, concatenation, multiplication, escape sequences
|
|
||||||
- **Booleans**: `true`, `false`
|
|
||||||
- **None**: Null value representation
|
|
||||||
- **Arrays**: Dynamic arrays with indexing, assignment, and built-in functions
|
|
||||||
- **Dictionaries**: Hash maps with string keys and mixed-type values
|
|
||||||
- **Functions**: First-class functions as values
|
|
||||||
- **Variables**: Declaration, assignment, scoping
|
|
||||||
- **Assignment System**: Dual system (statements + expressions for loops only)
|
|
||||||
|
|
||||||
#### **Operators (Complete)**
|
|
||||||
- **Arithmetic**: `+`, `-`, `*`, `/`, `%`, unary `-`
|
|
||||||
- **Comparison**: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
|
||||||
- **Logical**: `&&`, `||`, `!` (with short-circuit evaluation)
|
|
||||||
- **Bitwise**: `&`, `|`, `^`, `<<`, `>>`, `~`
|
|
||||||
- **Compound Assignment**: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
|
||||||
- **Ternary**: `condition ? valueIfTrue : valueIfFalse`
|
|
||||||
- **Increment/Decrement**: `++`, `--` (for array elements)
|
|
||||||
|
|
||||||
#### **Control Flow (Complete)**
|
|
||||||
- **If Statements**: `if`, `else`, `else if` chains
|
|
||||||
- **While Loops**: Basic, nested, complex conditions
|
|
||||||
- **Do-While Loops**: Basic, nested, break/continue support
|
|
||||||
- **For Loops**: All clause variations, nested loops
|
|
||||||
- **Break/Continue**: Full support in all loop types
|
|
||||||
|
|
||||||
#### **Functions (Complete)**
|
|
||||||
- **Function Declaration**: `func name(params) { body }`
|
|
||||||
- **Parameters**: Any number of parameters (tested up to 100)
|
|
||||||
- **Return Values**: Explicit and implicit returns
|
|
||||||
- **Closures**: Lexical scoping with variable capture
|
|
||||||
- **First-Class Functions**: Functions as values, parameters, return values
|
|
||||||
- **Anonymous Functions**: `func(params) { body }`
|
|
||||||
- **Nested Functions**: Functions defined inside other functions
|
|
||||||
- **Recursion**: Full support including deep recursion
|
|
||||||
- **Tail Call Optimization**: Trampoline-based optimization preventing stack overflow
|
|
||||||
|
|
||||||
#### **Data Structures (Complete)**
|
|
||||||
- **Arrays**: Full support with indexing, assignment, nested arrays
|
|
||||||
- **Dictionaries**: Full support with key-value pairs, nested dictionaries
|
|
||||||
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment, properties (`length`, `first`, `last`, `empty`)
|
|
||||||
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment, dot notation (`obj.prop`)
|
|
||||||
- **Mixed Types**: Arrays and dictionaries can hold any value types
|
|
||||||
|
|
||||||
#### **Standard Library (Complete)**
|
|
||||||
- **I/O Functions**: `print()`, `printRaw()`, `input()`
|
|
||||||
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
|
|
||||||
- **Testing**: `assert()` with custom error messages
|
|
||||||
- **Timing**: `time()` (microsecond precision), `sleep()`
|
|
||||||
- **Utility**: `random()` (properly seeded), `eval()`, `exit()`
|
|
||||||
- **Data Structure**: `len()`, `push()`, `pop()`, `keys()`, `values()`, `has()`
|
|
||||||
- **File I/O**: `readFile()`, `writeFile()`, `readLines()`, `fileExists()`
|
|
||||||
|
|
||||||
#### **Advanced Features (Complete)**
|
|
||||||
- **String Operations**: Bidirectional string + number concatenation
|
|
||||||
- **Number Formatting**: Smart significant digits handling
|
|
||||||
- **Memory Management**: Automatic cleanup with reference counting
|
|
||||||
- **Error Handling**: Comprehensive error reporting with context
|
|
||||||
- **Testing Framework**: Built-in assert function with 70+ comprehensive tests
|
|
||||||
- **Operator Precedence**: Full precedence hierarchy implementation
|
|
||||||
- **Variable Shadowing**: Proper lexical scoping rules
|
|
||||||
- **Interactive Mode**: Full REPL with error handling
|
|
||||||
- **Cross-Type Comparisons**: Smart equality for all types
|
|
||||||
- **Copy Semantics**: Value vs reference copying for different types
|
|
||||||
|
|
||||||
### ✅ **Architecture & Infrastructure (Complete)**
|
|
||||||
|
|
||||||
#### **Modern Build System**
|
|
||||||
- **CMake**: Cross-platform build configuration
|
|
||||||
- **Ninja**: High-speed build system (3.1x faster than Make)
|
|
||||||
- **CTest**: Integrated testing framework
|
|
||||||
- **Cross-Platform**: Windows, macOS, Linux support
|
|
||||||
- **Performance**: Optimized build times and incremental compilation
|
|
||||||
|
|
||||||
#### **Clean Architecture**
|
|
||||||
- **Modular Design**: Separated parsing, runtime, stdlib, and CLI
|
|
||||||
- **Tier Separation**: Clear boundaries between language components
|
|
||||||
- **Header Organization**: Organized by functional area
|
|
||||||
- **Source Structure**: `src/headers/` and `src/sources/` organization
|
|
||||||
|
|
||||||
#### **Refactored Interpreter**
|
|
||||||
- **Evaluator**: Expression evaluation (visitor pattern)
|
|
||||||
- **Executor**: Statement execution and control flow
|
|
||||||
- **RuntimeDiagnostics**: Utility functions and type checking
|
|
||||||
- **Memory Management**: Smart pointer usage throughout
|
|
||||||
- **Error System**: Centralized error reporting
|
|
||||||
|
|
||||||
### **Current Architecture Status**
|
|
||||||
|
|
||||||
```
|
|
||||||
Bob Language
|
|
||||||
├── Parsing Layer
|
|
||||||
│ ├── Lexer (tokenization)
|
|
||||||
│ ├── Parser (AST generation)
|
|
||||||
│ └── ErrorReporter (syntax errors)
|
|
||||||
├── Runtime Layer
|
|
||||||
│ ├── Evaluator (expression visitor)
|
|
||||||
│ ├── Executor (statement visitor)
|
|
||||||
│ ├── Interpreter (orchestration)
|
|
||||||
│ ├── Environment (variable scoping)
|
|
||||||
│ ├── Value (type system)
|
|
||||||
│ └── RuntimeDiagnostics (utilities)
|
|
||||||
├── Standard Library
|
|
||||||
│ └── BobStdLib (built-in functions)
|
|
||||||
└── CLI Interface
|
|
||||||
└── Bob (command-line interface)
|
|
||||||
```
|
|
||||||
|
|
||||||
## **Future Development Phases**
|
|
||||||
|
|
||||||
### **Phase 1: Advanced Language Features (Medium Priority)**
|
|
||||||
|
|
||||||
#### **Exception Handling System**
|
|
||||||
```bob
|
|
||||||
try {
|
|
||||||
var result = 10 / 0;
|
|
||||||
} catch (error) {
|
|
||||||
print("Error: " + error.message);
|
|
||||||
} finally {
|
|
||||||
print("Cleanup");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `try`/`catch`/`finally` syntax to parser
|
|
||||||
- Implement exception objects with stack traces
|
|
||||||
- Add `throw` statement for custom exceptions
|
|
||||||
- Integrate with existing error system
|
|
||||||
|
|
||||||
#### **Pattern Matching**
|
|
||||||
```bob
|
|
||||||
match value {
|
|
||||||
case 0: "zero"
|
|
||||||
case 1 | 2: "small"
|
|
||||||
case x if x > 10: "large"
|
|
||||||
default: "other"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `match`/`case` syntax
|
|
||||||
- Implement pattern matching logic
|
|
||||||
- Support guards with `if` conditions
|
|
||||||
- Add destructuring for arrays/dictionaries
|
|
||||||
|
|
||||||
### **Phase 2: Object System (Lower Priority)**
|
|
||||||
|
|
||||||
#### **Simple Objects**
|
|
||||||
```bob
|
|
||||||
var person = {
|
|
||||||
name: "Alice",
|
|
||||||
age: 30,
|
|
||||||
greet: func() {
|
|
||||||
return "Hello, I'm " + this.name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add object literal syntax
|
|
||||||
- Implement `this` binding
|
|
||||||
- Support method calls
|
|
||||||
- ✅ Add property access/assignment (completed - dot notation for dictionaries and arrays)
|
|
||||||
|
|
||||||
#### **Classes (Optional)**
|
|
||||||
```bob
|
|
||||||
class Person {
|
|
||||||
init(name, age) {
|
|
||||||
this.name = name;
|
|
||||||
this.age = age;
|
|
||||||
}
|
|
||||||
|
|
||||||
greet() {
|
|
||||||
return "Hello, I'm " + this.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `class` keyword and syntax
|
|
||||||
- Implement constructors with `init()`
|
|
||||||
- Support inheritance with `extends`
|
|
||||||
- Add method definitions
|
|
||||||
|
|
||||||
### **Phase 3: Module System (Lower Priority)**
|
|
||||||
|
|
||||||
#### **Simple Modules**
|
|
||||||
```bob
|
|
||||||
// math.bob
|
|
||||||
func sqrt(x) { return x ** 0.5; }
|
|
||||||
func max(a, b) { return a > b ? a : b; }
|
|
||||||
|
|
||||||
// main.bob
|
|
||||||
import "math.bob" as math;
|
|
||||||
var result = math.sqrt(16);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `import` statement syntax
|
|
||||||
- Implement module loading from files
|
|
||||||
- Support namespace aliases
|
|
||||||
- Create standard library modules
|
|
||||||
|
|
||||||
### **Phase 4: Language Enhancements (Optional)**
|
|
||||||
|
|
||||||
#### **Enhanced Standard Library**
|
|
||||||
```bob
|
|
||||||
// Additional string functions
|
|
||||||
var parts = "a,b,c".split(",");
|
|
||||||
var joined = ["a", "b", "c"].join("-");
|
|
||||||
var upper = "hello".toUpper();
|
|
||||||
|
|
||||||
// Math library
|
|
||||||
var result = Math.sqrt(16);
|
|
||||||
var max = Math.max(5, 10, 3);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Async/Await (Advanced)**
|
|
||||||
```bob
|
|
||||||
async func fetchData() {
|
|
||||||
var response = await http.get("api.com/data");
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## **Implementation Guidelines**
|
|
||||||
|
|
||||||
### **For Each New Feature:**
|
|
||||||
1. **Design**: Plan syntax and semantics carefully
|
|
||||||
2. **Lexer**: Add new tokens if needed
|
|
||||||
3. **Parser**: Add new expression/statement types
|
|
||||||
4. **AST**: Define new node types in Expression.h/Statement.h
|
|
||||||
5. **Evaluator/Executor**: Implement evaluation logic
|
|
||||||
6. **Testing**: Write tests for the new feature
|
|
||||||
7. **Documentation**: Update language reference
|
|
||||||
|
|
||||||
### **Development Approach:**
|
|
||||||
- **Testing**: Write tests for new features
|
|
||||||
- **Error Messages**: Make errors helpful and clear
|
|
||||||
- **Memory**: Use smart pointers to avoid leaks
|
|
||||||
- **Performance**: Don't make things unnecessarily slow
|
|
||||||
- **Code Style**: Keep it readable and maintainable
|
|
||||||
- **Portability**: Make sure it works on different platforms
|
|
||||||
|
|
||||||
## **Success Metrics**
|
|
||||||
|
|
||||||
### **What's Done ✅**
|
|
||||||
- [x] Core language syntax and semantics
|
|
||||||
- [x] All operators and expressions
|
|
||||||
- [x] Control flow (if, while, for, do-while)
|
|
||||||
- [x] Functions, closures, and tail call optimization
|
|
||||||
- [x] Arrays and dictionaries
|
|
||||||
- [x] Standard library (25+ built-in functions)
|
|
||||||
- [x] File I/O operations
|
|
||||||
- [x] Interactive REPL
|
|
||||||
- [x] Test suite with 70+ tests
|
|
||||||
- [x] Error handling and reporting
|
|
||||||
- [x] Memory management
|
|
||||||
- [x] CMake + Ninja build system
|
|
||||||
- [x] Modular architecture
|
|
||||||
- [x] Cross-platform support
|
|
||||||
- [x] Various optimizations
|
|
||||||
|
|
||||||
### **Might Add Later 📋**
|
|
||||||
- [ ] Exception handling (try/catch)
|
|
||||||
- [ ] Pattern matching
|
|
||||||
- [ ] Simple objects
|
|
||||||
- [ ] Module/import system
|
|
||||||
- [ ] More built-in functions
|
|
||||||
- [ ] Debugging tools
|
|
||||||
|
|
||||||
## **Resources**
|
|
||||||
|
|
||||||
- **[Language Reference](BOB_LANGUAGE_REFERENCE.md)** - Language documentation
|
|
||||||
- **[Build Guide](BUILD.md)** - How to build Bob
|
|
||||||
- **[Test Suite](../test_bob_language.bob)** - 70+ tests
|
|
||||||
- **[Crafting Interpreters](https://craftinginterpreters.com/)** - Helpful book for language implementation
|
|
||||||
|
|
||||||
## **Recent Work**
|
|
||||||
|
|
||||||
### **Architecture Cleanup (2025)**
|
|
||||||
- Split the interpreter into separate components (Evaluator/Executor/RuntimeDiagnostics)
|
|
||||||
- Switched to CMake + Ninja build system (3x faster builds)
|
|
||||||
- Reorganized code into cleaner modules
|
|
||||||
- Added Windows/macOS/Linux build support
|
|
||||||
|
|
||||||
### **Feature Completion**
|
|
||||||
- Added file I/O and type conversion functions
|
|
||||||
- Implemented all the operators I wanted (bitwise, compound assignment, etc.)
|
|
||||||
- Got arrays and dictionaries working properly
|
|
||||||
- Added tail call optimization and closures
|
|
||||||
|
|
||||||
### **Testing & Polish**
|
|
||||||
- Wrote 70+ tests covering pretty much everything
|
|
||||||
- Improved error messages to be more helpful
|
|
||||||
- Fixed memory leaks using smart pointers
|
|
||||||
- Various performance improvements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Bob works well for what I wanted - a programming language with the features and syntax I prefer.
|
|
||||||
|
|
||||||
*Last updated: January 2025*
|
|
||||||
30
benchmark.py
Normal file
30
benchmark.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
# Test the time function
|
||||||
|
print("Testing time function:")
|
||||||
|
|
||||||
|
time1 = time.time()
|
||||||
|
print(f"Time 1: {time1}")
|
||||||
|
|
||||||
|
time2 = time.time()
|
||||||
|
print(f"Time 2: {time2}")
|
||||||
|
|
||||||
|
time3 = time.time()
|
||||||
|
print(f"Time 3: {time3}")
|
||||||
|
|
||||||
|
diff1 = time2 - time1
|
||||||
|
diff2 = time3 - time2
|
||||||
|
|
||||||
|
print(f"Difference 1-2: {diff1} seconds")
|
||||||
|
print(f"Difference 2-3: {diff2} seconds")
|
||||||
|
|
||||||
|
# Test with some work in between
|
||||||
|
start = time.time()
|
||||||
|
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
|
||||||
|
end = time.time()
|
||||||
|
duration = end - start
|
||||||
|
|
||||||
|
print(f"Work duration: {duration} seconds")
|
||||||
|
print(f"Sum: {sum_val}")
|
||||||
|
|
||||||
|
print("Time function analysis complete!")
|
||||||
@ -1,10 +0,0 @@
|
|||||||
.vscode/**
|
|
||||||
.vscode-test/**
|
|
||||||
src/**
|
|
||||||
.gitignore
|
|
||||||
.yarnrc
|
|
||||||
vsc-extension-quickstart.md
|
|
||||||
**/tsconfig.json
|
|
||||||
**/.eslintrc.json
|
|
||||||
**/*.map
|
|
||||||
**/*.ts
|
|
||||||
@ -1 +0,0 @@
|
|||||||
bob-language-0.2.0.vsix
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
# Bob Language Extension Installation Guide
|
|
||||||
|
|
||||||
## Quick Installation
|
|
||||||
|
|
||||||
### Option 1: Automatic Installation (Recommended)
|
|
||||||
```bash
|
|
||||||
cd bob-language-extension
|
|
||||||
./install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Manual Installation
|
|
||||||
1. Copy the extension files to your VS Code extensions directory:
|
|
||||||
- **macOS**: `~/.vscode/extensions/bob-language-0.1.0/`
|
|
||||||
- **Linux**: `~/.vscode/extensions/bob-language-0.1.0/`
|
|
||||||
- **Windows**: `%APPDATA%\Code\User\extensions\bob-language-0.1.0\`
|
|
||||||
|
|
||||||
2. Restart VS Code/Cursor
|
|
||||||
|
|
||||||
### Option 3: VSIX Package
|
|
||||||
```bash
|
|
||||||
cd bob-language-extension
|
|
||||||
./package-vsix.sh
|
|
||||||
```
|
|
||||||
Then install the generated `.vsix` file in VS Code/Cursor.
|
|
||||||
|
|
||||||
## Features Included
|
|
||||||
|
|
||||||
### ✅ Syntax Highlighting
|
|
||||||
- Keywords: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, `var`, `func`
|
|
||||||
- Built-in functions: `print`, `assert`, `input`, `type`, `toString`, `toNumber`, `time`
|
|
||||||
- Data types: numbers, strings, booleans, `none`
|
|
||||||
- Operators: arithmetic, comparison, logical, bitwise, compound assignment
|
|
||||||
|
|
||||||
### ✅ Code Snippets
|
|
||||||
Type these prefixes and press `Tab`:
|
|
||||||
- `func` - Function definition
|
|
||||||
- `if` - If statement
|
|
||||||
- `while` - While loop
|
|
||||||
- `for` - For loop
|
|
||||||
- `var` - Variable declaration
|
|
||||||
- `print` - Print statement
|
|
||||||
- `assert` - Assert statement
|
|
||||||
- `anon` - Anonymous function
|
|
||||||
- `test` - Test function
|
|
||||||
|
|
||||||
### ✅ Language Features
|
|
||||||
- Auto-closing brackets and quotes
|
|
||||||
- Smart indentation
|
|
||||||
- Comment support (`//` and `/* */`)
|
|
||||||
- Code folding
|
|
||||||
- Hover information for built-in functions
|
|
||||||
- IntelliSense completion
|
|
||||||
|
|
||||||
### ✅ Color Theme
|
|
||||||
- "Bob Dark" theme included
|
|
||||||
- Optimized colors for Bob syntax
|
|
||||||
|
|
||||||
## Testing the Extension
|
|
||||||
|
|
||||||
1. Open VS Code/Cursor
|
|
||||||
2. Open the `example.bob` file in the extension directory
|
|
||||||
3. You should see syntax highlighting, code completion, and snippets working
|
|
||||||
|
|
||||||
## File Association
|
|
||||||
|
|
||||||
Files with `.bob` extension will automatically be recognized as Bob language files.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Extension not working?
|
|
||||||
1. Check that the extension is installed in the correct directory
|
|
||||||
2. Restart VS Code/Cursor completely
|
|
||||||
3. Check the Extensions panel (Ctrl+Shift+X) to see if the extension is listed
|
|
||||||
|
|
||||||
### Syntax highlighting not working?
|
|
||||||
1. Make sure your file has a `.bob` extension
|
|
||||||
2. Check the language mode in the bottom-right corner of VS Code
|
|
||||||
3. Manually select "Bob" as the language mode if needed
|
|
||||||
|
|
||||||
### Snippets not working?
|
|
||||||
1. Type the snippet prefix (e.g., `func`)
|
|
||||||
2. Press `Ctrl+Space` to trigger suggestions
|
|
||||||
3. Select the snippet and press `Tab`
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
To modify the extension:
|
|
||||||
1. Edit the files in the extension directory
|
|
||||||
2. Run `npm run compile` to rebuild TypeScript
|
|
||||||
3. Reload VS Code/Cursor to see changes
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
For issues or questions:
|
|
||||||
1. Check the README.md file
|
|
||||||
2. Look at the example.bob file for syntax examples
|
|
||||||
3. Review the TextMate grammar in `syntaxes/bob.tmLanguage.json`
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
# Bob Language Extension for VS Code
|
|
||||||
|
|
||||||
This extension provides syntax highlighting and language support for the Bob programming language in Visual Studio Code and Cursor.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Syntax Highlighting**: Full syntax highlighting for Bob language constructs
|
|
||||||
- **Code Snippets**: Useful code snippets for common Bob patterns
|
|
||||||
- **Auto-closing Brackets**: Automatic bracket and quote pairing
|
|
||||||
- **Indentation**: Smart indentation for Bob code blocks
|
|
||||||
- **Comments**: Support for line and block comments
|
|
||||||
- **Folding**: Code folding support with region markers
|
|
||||||
|
|
||||||
## Supported Syntax
|
|
||||||
|
|
||||||
### Keywords
|
|
||||||
- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return`
|
|
||||||
- Variable declaration: `var`
|
|
||||||
- Function declaration: `func`
|
|
||||||
- Logical operators: `and`, `or`, `not`
|
|
||||||
|
|
||||||
### Built-in Functions
|
|
||||||
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `time()`
|
|
||||||
- `sleep()`, `printRaw()`, `len()`, `push()`, `pop()`, `random()`, `eval()`
|
|
||||||
|
|
||||||
### Data Types
|
|
||||||
- Numbers (integers, floats, binary `0b1010`, hex `0xFF`)
|
|
||||||
- Strings (single and double quoted)
|
|
||||||
- Booleans (`true`, `false`)
|
|
||||||
- None value (`none`)
|
|
||||||
- Arrays (`[1, 2, 3]`)
|
|
||||||
|
|
||||||
### Operators
|
|
||||||
- Arithmetic: `+`, `-`, `*`, `/`, `%`
|
|
||||||
- Comparison: `==`, `!=`, `<`, `>`, `<=`, `>=`
|
|
||||||
- Logical: `&&`, `||`, `!`
|
|
||||||
- Bitwise: `&`, `|`, `^`, `<<`, `>>`, `~`
|
|
||||||
- Compound assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
|
||||||
- Ternary: `condition ? valueIfTrue : valueIfFalse`
|
|
||||||
- String multiplication: `"hello" * 3`
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### From Source
|
|
||||||
1. Clone this repository
|
|
||||||
2. Run `npm install` to install dependencies
|
|
||||||
3. Run `npm run compile` to build the extension
|
|
||||||
4. Press `F5` in VS Code to launch the extension in a new window
|
|
||||||
|
|
||||||
### Manual Installation
|
|
||||||
1. Copy the extension files to your VS Code extensions directory
|
|
||||||
2. Restart VS Code
|
|
||||||
3. Open a `.bob` file to see syntax highlighting
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Code Snippets
|
|
||||||
Type the following prefixes and press `Tab` to insert code snippets:
|
|
||||||
|
|
||||||
- `func` - Function definition
|
|
||||||
- `if` - If statement
|
|
||||||
- `ifelse` - If-else statement
|
|
||||||
- `while` - While loop
|
|
||||||
- `for` - For loop
|
|
||||||
- `var` - Variable declaration
|
|
||||||
- `print` - Print statement
|
|
||||||
- `assert` - Assert statement
|
|
||||||
- `anon` - Anonymous function
|
|
||||||
- `return` - Return statement
|
|
||||||
- `break` - Break statement
|
|
||||||
- `continue` - Continue statement
|
|
||||||
- `comment` - Comment block
|
|
||||||
- `test` - Test function
|
|
||||||
- `array` - Array declaration
|
|
||||||
- `arrayaccess` - Array access
|
|
||||||
- `arrayassign` - Array assignment
|
|
||||||
- `len` - Array length
|
|
||||||
- `push` - Array push
|
|
||||||
- `pop` - Array pop
|
|
||||||
- `random` - Random number
|
|
||||||
- `sleep` - Sleep function
|
|
||||||
- `printraw` - Print raw
|
|
||||||
- `eval` - Eval function
|
|
||||||
|
|
||||||
### File Association
|
|
||||||
Files with the `.bob` extension will automatically be recognized as Bob language files.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
// This is a comment
|
|
||||||
var message = "Hello, Bob!";
|
|
||||||
print(message);
|
|
||||||
|
|
||||||
// Array operations
|
|
||||||
var numbers = [1, 2, 3, 4, 5];
|
|
||||||
print("Array length: " + len(numbers));
|
|
||||||
print("First element: " + numbers[0]);
|
|
||||||
|
|
||||||
// Function with ternary operator
|
|
||||||
func factorial(n) {
|
|
||||||
if (n <= 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return n * factorial(n - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = factorial(5);
|
|
||||||
var status = result > 100 ? "large" : "small";
|
|
||||||
assert(result == 120, "Factorial calculation failed");
|
|
||||||
|
|
||||||
// String multiplication
|
|
||||||
var repeated = "hello" * 3; // "hellohellohello"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This extension is licensed under the MIT License.
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- [Bob Language Repository](https://github.com/bob-lang/bob)
|
|
||||||
- [VS Code Extension API](https://code.visualstudio.com/api)
|
|
||||||
Binary file not shown.
@ -1,44 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Create a simple VSIX package without npm
|
|
||||||
|
|
||||||
echo "Creating Bob Language Extension VSIX package..."
|
|
||||||
|
|
||||||
# Read version from package.json
|
|
||||||
VERSION=$(grep '"version"' package.json | sed 's/.*"version": "\([^"]*\)".*/\1/')
|
|
||||||
echo "Building version: $VERSION"
|
|
||||||
|
|
||||||
# Create a temporary directory for the package
|
|
||||||
TEMP_DIR=$(mktemp -d)
|
|
||||||
PACKAGE_DIR="$TEMP_DIR/bob-language-$VERSION"
|
|
||||||
|
|
||||||
# Create the extension directory structure
|
|
||||||
mkdir -p "$TEMP_DIR/extension"
|
|
||||||
|
|
||||||
# Copy all extension files to the extension directory
|
|
||||||
cp package.json "$TEMP_DIR/extension/"
|
|
||||||
cp language-configuration.json "$TEMP_DIR/extension/"
|
|
||||||
cp -r syntaxes "$TEMP_DIR/extension/"
|
|
||||||
cp -r snippets "$TEMP_DIR/extension/"
|
|
||||||
cp -r themes "$TEMP_DIR/extension/"
|
|
||||||
cp README.md "$TEMP_DIR/extension/"
|
|
||||||
|
|
||||||
# Create the VSIX file (simple zip with .vsix extension)
|
|
||||||
cd "$TEMP_DIR"
|
|
||||||
zip -r "bob-language-$VERSION.vsix" extension/
|
|
||||||
|
|
||||||
# Move to the extension directory
|
|
||||||
mv "bob-language-$VERSION.vsix" /Users/bobbylucero/Developer/Bob/bob-language-extension/
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm -rf "$TEMP_DIR"
|
|
||||||
|
|
||||||
echo "VSIX package created: bob-language-$VERSION.vsix"
|
|
||||||
echo ""
|
|
||||||
echo "To install in Cursor:"
|
|
||||||
echo "1. Open Cursor"
|
|
||||||
echo "2. Go to Extensions (Cmd+Shift+X)"
|
|
||||||
echo "3. Click the '...' menu and select 'Install from VSIX...'"
|
|
||||||
echo "4. Select the bob-language-$VERSION.vsix file"
|
|
||||||
echo ""
|
|
||||||
echo "Or simply restart Cursor - the extension should already be working!"
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
// Bob Language Example - Demonstrates syntax highlighting
|
|
||||||
|
|
||||||
// Variable declarations
|
|
||||||
var message = "Hello, Bob!";
|
|
||||||
var number = 42;
|
|
||||||
var pi = 3.14159;
|
|
||||||
var isActive = true;
|
|
||||||
var empty = none;
|
|
||||||
|
|
||||||
// Binary and hex numbers
|
|
||||||
var binaryNum = 0b1010; // 10 in decimal
|
|
||||||
var hexNum = 0xFF; // 255 in decimal
|
|
||||||
|
|
||||||
// Print statements
|
|
||||||
print(message);
|
|
||||||
print("Number: " + toString(number));
|
|
||||||
print("Pi: " + toString(pi));
|
|
||||||
|
|
||||||
// Array operations
|
|
||||||
var numbers = [1, 2, 3, 4, 5];
|
|
||||||
var fruits = ["apple", "banana", "cherry"];
|
|
||||||
|
|
||||||
print("Array length: " + len(numbers));
|
|
||||||
print("First element: " + numbers[0]);
|
|
||||||
|
|
||||||
numbers[2] = 99; // Array assignment
|
|
||||||
push(numbers, 6); // Add element
|
|
||||||
var lastElement = pop(numbers); // Remove and get last element
|
|
||||||
|
|
||||||
// Function definition
|
|
||||||
func factorial(n) {
|
|
||||||
if (n <= 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return n * factorial(n - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// While loop with break
|
|
||||||
var counter = 0;
|
|
||||||
while (counter < 10) {
|
|
||||||
if (counter == 5) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
counter = counter + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For loop with continue
|
|
||||||
var sum = 0;
|
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
|
||||||
if (i % 2 == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sum = sum + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do-while loop
|
|
||||||
var doCounter = 0;
|
|
||||||
do {
|
|
||||||
doCounter = doCounter + 1;
|
|
||||||
} while (doCounter < 5);
|
|
||||||
|
|
||||||
// Anonymous function
|
|
||||||
var double = func(x) {
|
|
||||||
return x * 2;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ternary operator
|
|
||||||
var max = 10 > 5 ? 10 : 5;
|
|
||||||
var status = age >= 18 ? "adult" : "minor";
|
|
||||||
|
|
||||||
// String multiplication
|
|
||||||
var repeated = "hello" * 3; // "hellohellohello"
|
|
||||||
var numRepeated = 3 * "hi"; // "hihihi"
|
|
||||||
|
|
||||||
// Logical operators
|
|
||||||
var a = true && false;
|
|
||||||
var b = true || false;
|
|
||||||
var c = !false;
|
|
||||||
|
|
||||||
// Bitwise operators
|
|
||||||
var d = 5 & 3;
|
|
||||||
var e = 5 | 3;
|
|
||||||
var f = 5 ^ 3;
|
|
||||||
var g = 5 << 1;
|
|
||||||
var h = 5 >> 1;
|
|
||||||
|
|
||||||
// Compound assignment
|
|
||||||
var value = 10;
|
|
||||||
value += 5;
|
|
||||||
value *= 2;
|
|
||||||
value -= 3;
|
|
||||||
|
|
||||||
// New built-in functions
|
|
||||||
var randomValue = random();
|
|
||||||
sleep(100); // Sleep for 100ms
|
|
||||||
printRaw("No newline here");
|
|
||||||
eval("print('Dynamic code execution!');");
|
|
||||||
|
|
||||||
// Assertions
|
|
||||||
assert(factorial(5) == 120, "Factorial calculation failed");
|
|
||||||
assert(sum == 25, "Sum calculation failed");
|
|
||||||
|
|
||||||
// Type checking
|
|
||||||
var typeOfMessage = type(message);
|
|
||||||
var typeOfNumber = type(number);
|
|
||||||
var typeOfArray = type(numbers);
|
|
||||||
|
|
||||||
// Time function
|
|
||||||
var currentTime = time();
|
|
||||||
|
|
||||||
print("All tests passed!");
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Bob Language Extension Installer for VS Code/Cursor
|
|
||||||
|
|
||||||
echo "Installing Bob Language Extension..."
|
|
||||||
|
|
||||||
# Get VS Code extensions directory
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
# macOS
|
|
||||||
VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions"
|
|
||||||
CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
# Linux
|
|
||||||
VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions"
|
|
||||||
CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions"
|
|
||||||
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
|
|
||||||
# Windows
|
|
||||||
VSCODE_EXTENSIONS_DIR="$APPDATA/Code/User/extensions"
|
|
||||||
CURSOR_EXTENSIONS_DIR="$APPDATA/Cursor/User/extensions"
|
|
||||||
else
|
|
||||||
echo "Unsupported operating system: $OSTYPE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create extension directory
|
|
||||||
EXTENSION_NAME="bob-language-0.1.0"
|
|
||||||
EXTENSION_DIR="$VSCODE_EXTENSIONS_DIR/$EXTENSION_NAME"
|
|
||||||
|
|
||||||
echo "Installing to: $EXTENSION_DIR"
|
|
||||||
|
|
||||||
# Create directories
|
|
||||||
mkdir -p "$EXTENSION_DIR"
|
|
||||||
|
|
||||||
# Copy extension files
|
|
||||||
cp -r package.json "$EXTENSION_DIR/"
|
|
||||||
cp -r language-configuration.json "$EXTENSION_DIR/"
|
|
||||||
cp -r syntaxes "$EXTENSION_DIR/"
|
|
||||||
cp -r snippets "$EXTENSION_DIR/"
|
|
||||||
cp -r README.md "$EXTENSION_DIR/"
|
|
||||||
|
|
||||||
# Compile TypeScript if available
|
|
||||||
if command -v npm &> /dev/null; then
|
|
||||||
echo "Compiling TypeScript..."
|
|
||||||
npm install
|
|
||||||
npm run compile
|
|
||||||
cp -r out "$EXTENSION_DIR/"
|
|
||||||
else
|
|
||||||
echo "npm not found, skipping TypeScript compilation"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Bob Language Extension installed successfully!"
|
|
||||||
echo ""
|
|
||||||
echo "To use the extension:"
|
|
||||||
echo "1. Restart VS Code/Cursor"
|
|
||||||
echo "2. Open a .bob file"
|
|
||||||
echo "3. Enjoy syntax highlighting and code snippets!"
|
|
||||||
echo ""
|
|
||||||
echo "Code snippets available:"
|
|
||||||
echo "- func: Function definition"
|
|
||||||
echo "- if: If statement"
|
|
||||||
echo "- while: While loop"
|
|
||||||
echo "- for: For loop"
|
|
||||||
echo "- var: Variable declaration"
|
|
||||||
echo "- print: Print statement"
|
|
||||||
echo "- assert: Assert statement"
|
|
||||||
echo "- And many more!"
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"comments": {
|
|
||||||
"lineComment": "//",
|
|
||||||
"blockComment": ["/*", "*/"]
|
|
||||||
},
|
|
||||||
"brackets": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"]
|
|
||||||
],
|
|
||||||
"autoClosingPairs": [
|
|
||||||
{ "open": "{", "close": "}" },
|
|
||||||
{ "open": "[", "close": "]" },
|
|
||||||
{ "open": "(", "close": ")" },
|
|
||||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
|
||||||
{ "open": "'", "close": "'", "notIn": ["string"] }
|
|
||||||
],
|
|
||||||
"surroundingPairs": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"],
|
|
||||||
["\"", "\""],
|
|
||||||
["'", "'"]
|
|
||||||
],
|
|
||||||
"indentationRules": {
|
|
||||||
"increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for)\\b.*$",
|
|
||||||
"decreaseIndentPattern": "^\\s*[})]"
|
|
||||||
},
|
|
||||||
"folding": {
|
|
||||||
"markers": {
|
|
||||||
"start": "^\\s*//\\s*#?region\\b",
|
|
||||||
"end": "^\\s*//\\s*#?endregion\\b"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Package Bob Language Extension as VSIX
|
|
||||||
|
|
||||||
echo "Packaging Bob Language Extension..."
|
|
||||||
|
|
||||||
# Check if vsce is installed
|
|
||||||
if ! command -v vsce &> /dev/null; then
|
|
||||||
echo "Installing vsce..."
|
|
||||||
npm install -g @vscode/vsce
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Compile TypeScript
|
|
||||||
npm run compile
|
|
||||||
|
|
||||||
# Package the extension
|
|
||||||
vsce package
|
|
||||||
|
|
||||||
echo "Extension packaged successfully!"
|
|
||||||
echo "You can now install the .vsix file in VS Code/Cursor:"
|
|
||||||
echo "1. Open VS Code/Cursor"
|
|
||||||
echo "2. Go to Extensions (Ctrl+Shift+X)"
|
|
||||||
echo "3. Click the '...' menu and select 'Install from VSIX...'"
|
|
||||||
echo "4. Select the generated .vsix file"
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bob-language",
|
|
||||||
"displayName": "Bob Language",
|
|
||||||
"description": "Syntax highlighting and language support for the Bob programming language - featuring arrays, dictionaries, auto-truncating float indices, increment/decrement operators, cross-type comparisons, and comprehensive built-in functions",
|
|
||||||
"version": "0.4.0",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.60.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Programming Languages"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"bob",
|
|
||||||
"programming",
|
|
||||||
"language",
|
|
||||||
"syntax",
|
|
||||||
"highlighting"
|
|
||||||
],
|
|
||||||
"publisher": "bob-lang",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/bob-lang/bob-vscode-extension"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./out/extension.js",
|
|
||||||
"activationEvents": [
|
|
||||||
"onLanguage:bob"
|
|
||||||
],
|
|
||||||
"contributes": {
|
|
||||||
"languages": [
|
|
||||||
{
|
|
||||||
"id": "bob",
|
|
||||||
"aliases": [
|
|
||||||
"Bob",
|
|
||||||
"bob"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
".bob"
|
|
||||||
],
|
|
||||||
"configuration": "./language-configuration.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
|
||||||
{
|
|
||||||
"language": "bob",
|
|
||||||
"scopeName": "source.bob",
|
|
||||||
"path": "./syntaxes/bob.tmLanguage.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"snippets": [
|
|
||||||
{
|
|
||||||
"language": "bob",
|
|
||||||
"path": "./snippets/bob.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"themes": [
|
|
||||||
{
|
|
||||||
"label": "Bob Dark",
|
|
||||||
"uiTheme": "vs-dark",
|
|
||||||
"path": "./themes/bob-dark.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"vscode:prepublish": "npm run compile",
|
|
||||||
"compile": "tsc -p ./",
|
|
||||||
"watch": "tsc -watch -p ./"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/vscode": "^1.60.0",
|
|
||||||
"@types/node": "^16.0.0",
|
|
||||||
"typescript": "^4.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Attempting to reload Bob language extension..."
|
|
||||||
|
|
||||||
# Try to reload the extension using VS Code CLI if available
|
|
||||||
if command -v code &> /dev/null; then
|
|
||||||
echo "Found VS Code CLI, attempting to reload window..."
|
|
||||||
code --command "workbench.action.reloadWindow"
|
|
||||||
echo "Reload command sent to VS Code/Cursor"
|
|
||||||
elif command -v cursor &> /dev/null; then
|
|
||||||
echo "Found Cursor CLI, attempting to reload window..."
|
|
||||||
cursor --command "workbench.action.reloadWindow"
|
|
||||||
echo "Reload command sent to Cursor"
|
|
||||||
else
|
|
||||||
echo "VS Code/Cursor CLI not found in PATH"
|
|
||||||
echo ""
|
|
||||||
echo "Manual reload required:"
|
|
||||||
echo "1. Open Cursor"
|
|
||||||
echo "2. Press Cmd+Shift+P (or Ctrl+Shift+P on Windows/Linux)"
|
|
||||||
echo "3. Type 'Developer: Reload Window' and press Enter"
|
|
||||||
echo ""
|
|
||||||
echo "Or alternatively:"
|
|
||||||
echo "1. Press Cmd+Shift+X to open Extensions"
|
|
||||||
echo "2. Find 'Bob Language' extension"
|
|
||||||
echo "3. Click the reload button (circular arrow icon)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Extension should now be reloaded with updated syntax highlighting!"
|
|
||||||
@ -1,462 +0,0 @@
|
|||||||
{
|
|
||||||
"Function Definition": {
|
|
||||||
"prefix": "func",
|
|
||||||
"body": [
|
|
||||||
"func ${1:functionName}(${2:parameters}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a new function"
|
|
||||||
},
|
|
||||||
"If Statement": {
|
|
||||||
"prefix": "if",
|
|
||||||
"body": [
|
|
||||||
"if (${1:condition}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an if statement"
|
|
||||||
},
|
|
||||||
"If-Else Statement": {
|
|
||||||
"prefix": "ifelse",
|
|
||||||
"body": [
|
|
||||||
"if (${1:condition}) {",
|
|
||||||
"\t$2",
|
|
||||||
"} else {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an if-else statement"
|
|
||||||
},
|
|
||||||
"While Loop": {
|
|
||||||
"prefix": "while",
|
|
||||||
"body": [
|
|
||||||
"while (${1:condition}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a while loop"
|
|
||||||
},
|
|
||||||
"For Loop": {
|
|
||||||
"prefix": "for",
|
|
||||||
"body": [
|
|
||||||
"for (${1:initialization}; ${2:condition}; ${3:increment}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a for loop"
|
|
||||||
},
|
|
||||||
"Variable Declaration": {
|
|
||||||
"prefix": "var",
|
|
||||||
"body": [
|
|
||||||
"var ${1:variableName} = ${2:value};"
|
|
||||||
],
|
|
||||||
"description": "Declare a variable"
|
|
||||||
},
|
|
||||||
"Print Statement": {
|
|
||||||
"prefix": "print",
|
|
||||||
"body": [
|
|
||||||
"print(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Print a value"
|
|
||||||
},
|
|
||||||
"Assert Statement": {
|
|
||||||
"prefix": "assert",
|
|
||||||
"body": [
|
|
||||||
"assert(${1:condition}, \"${2:message}\");"
|
|
||||||
],
|
|
||||||
"description": "Create an assertion"
|
|
||||||
},
|
|
||||||
"Anonymous Function": {
|
|
||||||
"prefix": "anon",
|
|
||||||
"body": [
|
|
||||||
"func(${1:parameters}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an anonymous function"
|
|
||||||
},
|
|
||||||
"Return Statement": {
|
|
||||||
"prefix": "return",
|
|
||||||
"body": [
|
|
||||||
"return ${1:value};"
|
|
||||||
],
|
|
||||||
"description": "Return a value from function"
|
|
||||||
},
|
|
||||||
"Break Statement": {
|
|
||||||
"prefix": "break",
|
|
||||||
"body": [
|
|
||||||
"break;"
|
|
||||||
],
|
|
||||||
"description": "Break out of loop"
|
|
||||||
},
|
|
||||||
"Continue Statement": {
|
|
||||||
"prefix": "continue",
|
|
||||||
"body": [
|
|
||||||
"continue;"
|
|
||||||
],
|
|
||||||
"description": "Continue to next iteration"
|
|
||||||
},
|
|
||||||
"Comment Block": {
|
|
||||||
"prefix": "comment",
|
|
||||||
"body": [
|
|
||||||
"/*",
|
|
||||||
" * ${1:comment}",
|
|
||||||
" */"
|
|
||||||
],
|
|
||||||
"description": "Create a comment block"
|
|
||||||
},
|
|
||||||
"Test Function": {
|
|
||||||
"prefix": "test",
|
|
||||||
"body": [
|
|
||||||
"func test${1:TestName}() {",
|
|
||||||
"\tvar result = ${2:testExpression};",
|
|
||||||
"\tassert(result == ${3:expectedValue}, \"${4:test message}\");",
|
|
||||||
"\tprint(\"${1:TestName}: PASS\");",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a test function"
|
|
||||||
},
|
|
||||||
"Higher-Order Function": {
|
|
||||||
"prefix": "hof",
|
|
||||||
"body": [
|
|
||||||
"func ${1:functionName}(${2:callback}) {",
|
|
||||||
"\treturn func(${3:params}) {",
|
|
||||||
"\t\t$0",
|
|
||||||
"\t};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a higher-order function"
|
|
||||||
},
|
|
||||||
"Closure": {
|
|
||||||
"prefix": "closure",
|
|
||||||
"body": [
|
|
||||||
"func ${1:outerFunction}(${2:param}) {",
|
|
||||||
"\tvar ${3:capturedVar} = ${4:value};",
|
|
||||||
"\treturn func(${5:innerParam}) {",
|
|
||||||
"\t\treturn ${3:capturedVar} + ${5:innerParam};",
|
|
||||||
"\t};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a closure"
|
|
||||||
},
|
|
||||||
"Counter Pattern": {
|
|
||||||
"prefix": "counter",
|
|
||||||
"body": [
|
|
||||||
"func createCounter() {",
|
|
||||||
"\tvar count = 0;",
|
|
||||||
"\treturn func() {",
|
|
||||||
"\t\tcount = count + 1;",
|
|
||||||
"\t\treturn count;",
|
|
||||||
"\t};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a counter function"
|
|
||||||
},
|
|
||||||
"Loop with Break": {
|
|
||||||
"prefix": "loopbreak",
|
|
||||||
"body": [
|
|
||||||
"while (${1:condition}) {",
|
|
||||||
"\tif (${2:breakCondition}) {",
|
|
||||||
"\t\tbreak;",
|
|
||||||
"\t}",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a loop with break condition"
|
|
||||||
},
|
|
||||||
"Loop with Continue": {
|
|
||||||
"prefix": "loopcontinue",
|
|
||||||
"body": [
|
|
||||||
"for (${1:initialization}; ${2:condition}; ${3:increment}) {",
|
|
||||||
"\tif (${4:continueCondition}) {",
|
|
||||||
"\t\tcontinue;",
|
|
||||||
"\t}",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a loop with continue condition"
|
|
||||||
},
|
|
||||||
"Nested Loop": {
|
|
||||||
"prefix": "nestedloop",
|
|
||||||
"body": [
|
|
||||||
"for (var i = 0; i < ${1:outerLimit}; i = i + 1) {",
|
|
||||||
"\tfor (var j = 0; j < ${2:innerLimit}; j = j + 1) {",
|
|
||||||
"\t\t$0",
|
|
||||||
"\t}",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create nested loops"
|
|
||||||
},
|
|
||||||
"Function with Early Return": {
|
|
||||||
"prefix": "earlyreturn",
|
|
||||||
"body": [
|
|
||||||
"func ${1:functionName}(${2:param}) {",
|
|
||||||
"\tif (${3:condition}) {",
|
|
||||||
"\t\treturn ${4:earlyValue};",
|
|
||||||
"\t}",
|
|
||||||
"\t$0",
|
|
||||||
"\treturn ${5:defaultValue};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a function with early return"
|
|
||||||
},
|
|
||||||
"String Concatenation": {
|
|
||||||
"prefix": "concat",
|
|
||||||
"body": [
|
|
||||||
"var result = \"${1:first}\" + \"${2:second}\";"
|
|
||||||
],
|
|
||||||
"description": "Concatenate strings"
|
|
||||||
},
|
|
||||||
"Number Operations": {
|
|
||||||
"prefix": "math",
|
|
||||||
"body": [
|
|
||||||
"var result = ${1:expression};"
|
|
||||||
],
|
|
||||||
"description": "Mathematical expression"
|
|
||||||
},
|
|
||||||
"Boolean Logic": {
|
|
||||||
"prefix": "bool",
|
|
||||||
"body": [
|
|
||||||
"var result = ${1:condition} == ${2:value};"
|
|
||||||
],
|
|
||||||
"description": "Boolean comparison"
|
|
||||||
},
|
|
||||||
"Type Check": {
|
|
||||||
"prefix": "type",
|
|
||||||
"body": [
|
|
||||||
"var typeResult = type(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Check type of expression"
|
|
||||||
},
|
|
||||||
"Array Declaration": {
|
|
||||||
"prefix": "array",
|
|
||||||
"body": [
|
|
||||||
"var ${1:arrayName} = [${2:element1}, ${3:element2}];"
|
|
||||||
],
|
|
||||||
"description": "Declare an array"
|
|
||||||
},
|
|
||||||
"Array Access": {
|
|
||||||
"prefix": "arrayaccess",
|
|
||||||
"body": [
|
|
||||||
"var element = ${1:arrayName}[${2:index}];"
|
|
||||||
],
|
|
||||||
"description": "Access array element"
|
|
||||||
},
|
|
||||||
"Array Assignment": {
|
|
||||||
"prefix": "arrayassign",
|
|
||||||
"body": [
|
|
||||||
"${1:arrayName}[${2:index}] = ${3:value};"
|
|
||||||
],
|
|
||||||
"description": "Assign value to array element"
|
|
||||||
},
|
|
||||||
"Array Length": {
|
|
||||||
"prefix": "len",
|
|
||||||
"body": [
|
|
||||||
"var length = len(${1:arrayName});"
|
|
||||||
],
|
|
||||||
"description": "Get array length"
|
|
||||||
},
|
|
||||||
"Array Push": {
|
|
||||||
"prefix": "push",
|
|
||||||
"body": [
|
|
||||||
"push(${1:arrayName}, ${2:value});"
|
|
||||||
],
|
|
||||||
"description": "Add element to array"
|
|
||||||
},
|
|
||||||
"Array Pop": {
|
|
||||||
"prefix": "pop",
|
|
||||||
"body": [
|
|
||||||
"var element = pop(${1:arrayName});"
|
|
||||||
],
|
|
||||||
"description": "Remove and return last element"
|
|
||||||
},
|
|
||||||
"Random Number": {
|
|
||||||
"prefix": "random",
|
|
||||||
"body": [
|
|
||||||
"var randomValue = random();"
|
|
||||||
],
|
|
||||||
"description": "Generate random number"
|
|
||||||
},
|
|
||||||
"Sleep": {
|
|
||||||
"prefix": "sleep",
|
|
||||||
"body": [
|
|
||||||
"sleep(${1:milliseconds});"
|
|
||||||
],
|
|
||||||
"description": "Sleep for specified milliseconds"
|
|
||||||
},
|
|
||||||
"Print Raw": {
|
|
||||||
"prefix": "printraw",
|
|
||||||
"body": [
|
|
||||||
"printRaw(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Print without newline"
|
|
||||||
},
|
|
||||||
"Eval": {
|
|
||||||
"prefix": "eval",
|
|
||||||
"body": [
|
|
||||||
"eval(\"${1:code}\");"
|
|
||||||
],
|
|
||||||
"description": "Evaluate dynamic code"
|
|
||||||
},
|
|
||||||
"ToString": {
|
|
||||||
"prefix": "tostring",
|
|
||||||
"body": [
|
|
||||||
"var stringResult = toString(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Convert to string"
|
|
||||||
},
|
|
||||||
"Test Suite Header": {
|
|
||||||
"prefix": "testsuite",
|
|
||||||
"body": [
|
|
||||||
"// ========================================",
|
|
||||||
"// ${1:TEST SUITE NAME}",
|
|
||||||
"// ========================================",
|
|
||||||
"// ${2:Description}",
|
|
||||||
"",
|
|
||||||
"print(\"${1:TEST SUITE NAME}\");",
|
|
||||||
"print(\"Running tests...\");",
|
|
||||||
"",
|
|
||||||
"$0",
|
|
||||||
"",
|
|
||||||
"print(\"All tests passed!\");"
|
|
||||||
],
|
|
||||||
"description": "Create a test suite header"
|
|
||||||
},
|
|
||||||
"Test Section": {
|
|
||||||
"prefix": "testsection",
|
|
||||||
"body": [
|
|
||||||
"// ========================================",
|
|
||||||
"// TEST ${1:NUMBER}: ${2:TEST NAME}",
|
|
||||||
"// ========================================",
|
|
||||||
"print(\"\\n--- Test ${1:NUMBER}: ${2:TEST NAME} ---\");",
|
|
||||||
"",
|
|
||||||
"$0",
|
|
||||||
"",
|
|
||||||
"print(\"${2:TEST NAME}: PASS\");"
|
|
||||||
],
|
|
||||||
"description": "Create a test section"
|
|
||||||
},
|
|
||||||
"Debug Print": {
|
|
||||||
"prefix": "debug",
|
|
||||||
"body": [
|
|
||||||
"print(\"DEBUG: ${1:variable} = \" + toString(${1:variable}));"
|
|
||||||
],
|
|
||||||
"description": "Debug print statement"
|
|
||||||
},
|
|
||||||
"Error Message": {
|
|
||||||
"prefix": "error",
|
|
||||||
"body": [
|
|
||||||
"print(\"ERROR: ${1:error message}\");"
|
|
||||||
],
|
|
||||||
"description": "Error message print"
|
|
||||||
},
|
|
||||||
"Success Message": {
|
|
||||||
"prefix": "success",
|
|
||||||
"body": [
|
|
||||||
"print(\"SUCCESS: ${1:success message}\");"
|
|
||||||
],
|
|
||||||
"description": "Success message print"
|
|
||||||
},
|
|
||||||
"ToInt": {
|
|
||||||
"prefix": "toint",
|
|
||||||
"body": [
|
|
||||||
"var intValue = toInt(${1:floatValue});"
|
|
||||||
],
|
|
||||||
"description": "Convert float to integer"
|
|
||||||
},
|
|
||||||
"Compound Assignment": {
|
|
||||||
"prefix": "compound",
|
|
||||||
"body": [
|
|
||||||
"${1:variable} += ${2:value};"
|
|
||||||
],
|
|
||||||
"description": "Compound assignment operator"
|
|
||||||
},
|
|
||||||
"Increment": {
|
|
||||||
"prefix": "inc",
|
|
||||||
"body": [
|
|
||||||
"${1:variable}++;"
|
|
||||||
],
|
|
||||||
"description": "Increment variable"
|
|
||||||
},
|
|
||||||
"Decrement": {
|
|
||||||
"prefix": "dec",
|
|
||||||
"body": [
|
|
||||||
"${1:variable}--;"
|
|
||||||
],
|
|
||||||
"description": "Decrement variable"
|
|
||||||
},
|
|
||||||
"Array Increment": {
|
|
||||||
"prefix": "arrayinc",
|
|
||||||
"body": [
|
|
||||||
"${1:arrayName}[${2:index}]++;"
|
|
||||||
],
|
|
||||||
"description": "Increment array element"
|
|
||||||
},
|
|
||||||
"Array Decrement": {
|
|
||||||
"prefix": "arraydec",
|
|
||||||
"body": [
|
|
||||||
"${1:arrayName}[${2:index}]--;"
|
|
||||||
],
|
|
||||||
"description": "Decrement array element"
|
|
||||||
},
|
|
||||||
"Cross-Type Comparison": {
|
|
||||||
"prefix": "crosscomp",
|
|
||||||
"body": [
|
|
||||||
"var result = ${1:value1} == ${2:value2}; // Works with any types"
|
|
||||||
],
|
|
||||||
"description": "Cross-type comparison"
|
|
||||||
},
|
|
||||||
"Float Array Index": {
|
|
||||||
"prefix": "floatindex",
|
|
||||||
"body": [
|
|
||||||
"var element = ${1:arrayName}[${2:floatIndex}]; // Auto-truncates to int"
|
|
||||||
],
|
|
||||||
"description": "Array access with float index (auto-truncates)"
|
|
||||||
},
|
|
||||||
"Dictionary Literal": {
|
|
||||||
"prefix": "dict",
|
|
||||||
"body": [
|
|
||||||
"var ${1:dictName} = {",
|
|
||||||
"\t\"${2:key1}\": ${3:value1},",
|
|
||||||
"\t\"${4:key2}\": ${5:value2}",
|
|
||||||
"};"
|
|
||||||
],
|
|
||||||
"description": "Create a dictionary literal"
|
|
||||||
},
|
|
||||||
"Dictionary Access": {
|
|
||||||
"prefix": "dictaccess",
|
|
||||||
"body": [
|
|
||||||
"var value = ${1:dictName}[\"${2:key}\"];"
|
|
||||||
],
|
|
||||||
"description": "Access dictionary value"
|
|
||||||
},
|
|
||||||
"Dictionary Assignment": {
|
|
||||||
"prefix": "dictassign",
|
|
||||||
"body": [
|
|
||||||
"${1:dictName}[\"${2:key}\"] = ${3:value};"
|
|
||||||
],
|
|
||||||
"description": "Assign value to dictionary key"
|
|
||||||
},
|
|
||||||
"Dictionary Keys": {
|
|
||||||
"prefix": "keys",
|
|
||||||
"body": [
|
|
||||||
"var keys = keys(${1:dictionary});"
|
|
||||||
],
|
|
||||||
"description": "Get all keys from dictionary"
|
|
||||||
},
|
|
||||||
"Dictionary Values": {
|
|
||||||
"prefix": "values",
|
|
||||||
"body": [
|
|
||||||
"var values = values(${1:dictionary});"
|
|
||||||
],
|
|
||||||
"description": "Get all values from dictionary"
|
|
||||||
},
|
|
||||||
"Dictionary Has": {
|
|
||||||
"prefix": "has",
|
|
||||||
"body": [
|
|
||||||
"var exists = has(${1:dictionary}, \"${2:key}\");"
|
|
||||||
],
|
|
||||||
"description": "Check if dictionary has key"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import * as vscode from 'vscode';
|
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
|
||||||
console.log('Bob language extension is now active!');
|
|
||||||
|
|
||||||
// Register language configuration
|
|
||||||
const bobLanguageConfig = vscode.languages.setLanguageConfiguration('bob', {
|
|
||||||
comments: {
|
|
||||||
lineComment: '//',
|
|
||||||
blockComment: ['/*', '*/']
|
|
||||||
},
|
|
||||||
brackets: [
|
|
||||||
['{', '}'],
|
|
||||||
['[', ']'],
|
|
||||||
['(', ')']
|
|
||||||
],
|
|
||||||
autoClosingPairs: [
|
|
||||||
{ open: '{', close: '}' },
|
|
||||||
{ open: '[', close: ']' },
|
|
||||||
{ open: '(', close: ')' },
|
|
||||||
{ open: '"', close: '"', notIn: ['string'] },
|
|
||||||
{ open: "'", close: "'", notIn: ['string'] }
|
|
||||||
],
|
|
||||||
surroundingPairs: [
|
|
||||||
['{', '}'],
|
|
||||||
['[', ']'],
|
|
||||||
['(', ')'],
|
|
||||||
['"', '"'],
|
|
||||||
["'", "'"]
|
|
||||||
],
|
|
||||||
indentationRules: {
|
|
||||||
increaseIndentPattern: /\{[^}]*$|\b(func|if|else|while|for)\b.*$/,
|
|
||||||
decreaseIndentPattern: /^\s*[})]/
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.subscriptions.push(bobLanguageConfig);
|
|
||||||
|
|
||||||
// Register hover provider for built-in functions
|
|
||||||
const hoverProvider = vscode.languages.registerHoverProvider('bob', {
|
|
||||||
provideHover(document, position, token) {
|
|
||||||
const range = document.getWordRangeAtPosition(position);
|
|
||||||
const word = document.getText(range);
|
|
||||||
|
|
||||||
const builtinFunctions = {
|
|
||||||
'print': 'Prints a value to the console',
|
|
||||||
'assert': 'Asserts a condition and throws an error if false',
|
|
||||||
'input': 'Gets user input from the console',
|
|
||||||
'type': 'Returns the type of a value',
|
|
||||||
'toString': 'Converts a value to a string',
|
|
||||||
'toNumber': 'Converts a value to a number',
|
|
||||||
'time': 'Returns the current time in microseconds'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (builtinFunctions[word]) {
|
|
||||||
return new vscode.Hover(`**${word}()** - ${builtinFunctions[word]}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.subscriptions.push(hoverProvider);
|
|
||||||
|
|
||||||
// Register completion provider
|
|
||||||
const completionProvider = vscode.languages.registerCompletionItemProvider('bob', {
|
|
||||||
provideCompletionItems(document, position, token, context) {
|
|
||||||
const completions = [];
|
|
||||||
|
|
||||||
// Keywords
|
|
||||||
const keywords = ['if', 'else', 'while', 'for', 'break', 'continue', 'return', 'var', 'func'];
|
|
||||||
keywords.forEach(keyword => {
|
|
||||||
const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword);
|
|
||||||
item.detail = 'Bob keyword';
|
|
||||||
completions.push(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Built-in functions
|
|
||||||
const builtins = ['print', 'assert', 'input', 'type', 'toString', 'toNumber', 'time'];
|
|
||||||
builtins.forEach(func => {
|
|
||||||
const item = new vscode.CompletionItem(func, vscode.CompletionItemKind.Function);
|
|
||||||
item.detail = 'Built-in function';
|
|
||||||
item.insertText = func + '()';
|
|
||||||
completions.push(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
const constants = ['true', 'false', 'none'];
|
|
||||||
constants.forEach(constant => {
|
|
||||||
const item = new vscode.CompletionItem(constant, vscode.CompletionItemKind.Constant);
|
|
||||||
item.detail = 'Bob constant';
|
|
||||||
completions.push(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
return completions;
|
|
||||||
}
|
|
||||||
}, '.');
|
|
||||||
|
|
||||||
context.subscriptions.push(completionProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deactivate() {
|
|
||||||
console.log('Bob language extension is now deactivated!');
|
|
||||||
}
|
|
||||||
@ -1,273 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Bob",
|
|
||||||
"scopeName": "source.bob",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#comments"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#strings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#numbers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#arrays"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#dictionaries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#keywords"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#functions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#variables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#operators"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"comments": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "comment.line.double-slash.bob",
|
|
||||||
"match": "//.*$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "comment.block.bob",
|
|
||||||
"begin": "/\\*",
|
|
||||||
"end": "\\*/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"strings": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "string.quoted.double.bob",
|
|
||||||
"begin": "\"",
|
|
||||||
"end": "\"",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.bob",
|
|
||||||
"match": "\\\\[nt\"\\\\e]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "string.quoted.single.bob",
|
|
||||||
"begin": "'",
|
|
||||||
"end": "'",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.bob",
|
|
||||||
"match": "\\\\[nt'\\\\e]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"numbers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.bob",
|
|
||||||
"match": "\\b\\d+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.float.bob",
|
|
||||||
"match": "\\b\\d+\\.\\d+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.binary.bob",
|
|
||||||
"match": "\\b0b[01]+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.hex.bob",
|
|
||||||
"match": "\\b0x[0-9a-fA-F]+\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"arrays": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "meta.array.bob",
|
|
||||||
"begin": "\\[",
|
|
||||||
"end": "\\]",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#expressions"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "variable.other.array-index.bob",
|
|
||||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\[([^\\]]+)\\]",
|
|
||||||
"captures": {
|
|
||||||
"1": { "name": "variable.other.bob" },
|
|
||||||
"2": { "name": "constant.numeric.integer.bob" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dictionaries": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "meta.dictionary.bob",
|
|
||||||
"begin": "\\{",
|
|
||||||
"end": "\\}",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "string.quoted.double.bob",
|
|
||||||
"begin": "\"",
|
|
||||||
"end": "\"",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.bob",
|
|
||||||
"match": "\\\\[nt\"\\\\e]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.bob",
|
|
||||||
"match": ":"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#expressions"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "variable.other.dictionary-index.bob",
|
|
||||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\{([^\\}]+)\\}",
|
|
||||||
"captures": {
|
|
||||||
"1": { "name": "variable.other.bob" },
|
|
||||||
"2": { "name": "string.quoted.double.bob" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"keywords": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "keyword.control.bob",
|
|
||||||
"match": "\\b(if|else|while|do|for|break|continue|return|var|func)\\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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
// Test file for operator syntax highlighting
|
|
||||||
var x = 5;
|
|
||||||
var y = 10;
|
|
||||||
var z = x + y;
|
|
||||||
var w = x - y;
|
|
||||||
var a = x * y;
|
|
||||||
var b = x / y;
|
|
||||||
var c = x % y;
|
|
||||||
|
|
||||||
// Comparison operators
|
|
||||||
var eq = x == y;
|
|
||||||
var ne = x != y;
|
|
||||||
var lt = x < y;
|
|
||||||
var gt = x > y;
|
|
||||||
var le = x <= y;
|
|
||||||
var ge = x >= y;
|
|
||||||
|
|
||||||
// Increment/decrement
|
|
||||||
x++;
|
|
||||||
y--;
|
|
||||||
|
|
||||||
// Compound assignment
|
|
||||||
x += 5;
|
|
||||||
y -= 3;
|
|
||||||
z *= 2;
|
|
||||||
w /= 4;
|
|
||||||
a %= 3;
|
|
||||||
|
|
||||||
// Logical operators
|
|
||||||
var and = true && false;
|
|
||||||
var or = true || false;
|
|
||||||
var not = !true;
|
|
||||||
|
|
||||||
// Bitwise operators
|
|
||||||
var bit_and = x & y;
|
|
||||||
var bit_or = x | y;
|
|
||||||
var bit_xor = x ^ y;
|
|
||||||
var left_shift = x << 2;
|
|
||||||
var right_shift = x >> 1;
|
|
||||||
|
|
||||||
// Compound bitwise assignment
|
|
||||||
x &= y;
|
|
||||||
y |= z;
|
|
||||||
z ^= w;
|
|
||||||
w <<= 2;
|
|
||||||
a >>= 1;
|
|
||||||
|
|
||||||
// Function with operators
|
|
||||||
func test_operators() {
|
|
||||||
var result = 0;
|
|
||||||
if (x >= 0 && y <= 100) {
|
|
||||||
result = x + y * 2;
|
|
||||||
if (result != 0) {
|
|
||||||
result++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Bob Dark",
|
|
||||||
"type": "dark",
|
|
||||||
"colors": {
|
|
||||||
"editor.background": "#1e1e1e",
|
|
||||||
"editor.foreground": "#d4d4d4",
|
|
||||||
"editor.lineHighlightBackground": "#2a2a2a",
|
|
||||||
"editor.selectionBackground": "#264f78",
|
|
||||||
"editor.inactiveSelectionBackground": "#3a3d41"
|
|
||||||
},
|
|
||||||
"tokenColors": [
|
|
||||||
{
|
|
||||||
"name": "Comments",
|
|
||||||
"scope": ["comment", "punctuation.definition.comment"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#6a9955"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Strings",
|
|
||||||
"scope": ["string", "string.quoted"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#ce9178"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Numbers",
|
|
||||||
"scope": ["constant.numeric"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#b5cea8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Keywords",
|
|
||||||
"scope": ["keyword.control", "keyword.operator"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#569cd6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Functions",
|
|
||||||
"scope": ["entity.name.function", "support.function.builtin"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#dcdcaa"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Variables",
|
|
||||||
"scope": ["variable.other"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#9cdcfe"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Constants",
|
|
||||||
"scope": ["constant.language"],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#4ec9b0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Operators",
|
|
||||||
"scope": [
|
|
||||||
"keyword.operator.arithmetic",
|
|
||||||
"keyword.operator.comparison",
|
|
||||||
"keyword.operator.logical",
|
|
||||||
"keyword.operator.bitwise",
|
|
||||||
"keyword.operator.assignment",
|
|
||||||
"keyword.operator.compound",
|
|
||||||
"keyword.operator.increment",
|
|
||||||
"keyword.operator.punctuation",
|
|
||||||
"keyword.operator.conditional"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"foreground": "#d4d4d4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "ES2020",
|
|
||||||
"outDir": "out",
|
|
||||||
"lib": [
|
|
||||||
"ES2020"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
|
||||||
"rootDir": "src",
|
|
||||||
"strict": true
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
".vscode-test"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
# Bob Language Extension v0.4.0
|
|
||||||
|
|
||||||
## What's New
|
|
||||||
|
|
||||||
### ✨ New Features Added
|
|
||||||
|
|
||||||
#### **Dictionary Support**
|
|
||||||
- **Dictionary literals**: `{"key": "value", "number": 42}`
|
|
||||||
- **Dictionary indexing**: `dict{"key"}` (returns `none` for missing keys)
|
|
||||||
- **Dictionary assignment**: `dict{"key"} = value`
|
|
||||||
- **Nested dictionaries**: `{"user": {"name": "Bob", "age": 30}}`
|
|
||||||
- **Mixed type values**: Any type can be stored as dictionary values
|
|
||||||
|
|
||||||
#### **Dictionary Built-in Functions**
|
|
||||||
- `keys(dict)` - Returns array of all keys
|
|
||||||
- `values(dict)` - Returns array of all values
|
|
||||||
- `has(dict, key)` - Returns boolean if key exists
|
|
||||||
|
|
||||||
#### **Dictionary Code Snippets**
|
|
||||||
- `dict` - Create dictionary literal
|
|
||||||
- `dictaccess` - Access dictionary value
|
|
||||||
- `dictassign` - Assign to dictionary key
|
|
||||||
- `keys`, `values`, `has` - Built-in function snippets
|
|
||||||
|
|
||||||
### 🎨 Syntax Highlighting Improvements
|
|
||||||
- Dictionary literal syntax highlighting
|
|
||||||
- Dictionary indexing syntax support
|
|
||||||
- Built-in function highlighting for `keys`, `values`, `has`
|
|
||||||
|
|
||||||
### 📝 Documentation Updates
|
|
||||||
- Complete dictionary documentation with examples
|
|
||||||
- Dictionary built-in functions documentation
|
|
||||||
- Updated language reference with dictionary section
|
|
||||||
- Array and dictionary built-in functions documentation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Previous Version (v0.3.0)
|
|
||||||
|
|
||||||
### ✨ New Features Added
|
|
||||||
|
|
||||||
#### **Enhanced Array Support**
|
|
||||||
- **Auto-truncating float indices**: `array[3.14]` → `array[3]` (like JavaScript/Lua)
|
|
||||||
- **Increment/decrement on array elements**: `array[0]++`, `++array[1]`
|
|
||||||
- **Improved array operations**: Better error handling and bounds checking
|
|
||||||
|
|
||||||
#### **New Built-in Functions**
|
|
||||||
- `toInt()` - convert floats to integers (truncates decimals)
|
|
||||||
- Enhanced error reporting for all built-in functions
|
|
||||||
|
|
||||||
#### **Increment/Decrement Operators**
|
|
||||||
- **Prefix increment**: `++x`
|
|
||||||
- **Postfix increment**: `x++`
|
|
||||||
- **Prefix decrement**: `--x`
|
|
||||||
- **Postfix decrement**: `x--`
|
|
||||||
- **Works on variables and array elements**
|
|
||||||
|
|
||||||
#### **Cross-Type Comparisons**
|
|
||||||
- **Equality operators** (`==`, `!=`) work with any types
|
|
||||||
- **Comparison operators** (`>`, `<`, `>=`, `<=`) only work with numbers
|
|
||||||
- **Clear error messages** for type mismatches
|
|
||||||
|
|
||||||
#### **Compound Assignment Operators**
|
|
||||||
- **Enhanced error reporting** with correct operator names
|
|
||||||
- **Consistent behavior** across all compound operators
|
|
||||||
- **Better type checking** before operations
|
|
||||||
|
|
||||||
#### **New Code Snippets**
|
|
||||||
- `toint` - Convert float to integer
|
|
||||||
- `compound` - Compound assignment operators
|
|
||||||
- `inc` - Increment variable
|
|
||||||
- `dec` - Decrement variable
|
|
||||||
- `arrayinc` - Increment array element
|
|
||||||
- `arraydec` - Decrement array element
|
|
||||||
- `crosscomp` - Cross-type comparison
|
|
||||||
- `floatindex` - Array access with float index
|
|
||||||
|
|
||||||
### 🎨 Syntax Highlighting Improvements
|
|
||||||
- Support for `toInt()` built-in function
|
|
||||||
- Enhanced operator recognition for increment/decrement
|
|
||||||
- Better array indexing syntax support
|
|
||||||
|
|
||||||
### 📝 Documentation Updates
|
|
||||||
- Comprehensive array documentation with auto-truncation examples
|
|
||||||
- New built-in function documentation (`toInt`, enhanced error reporting)
|
|
||||||
- Cross-type comparison behavior documentation
|
|
||||||
- Increment/decrement operator documentation
|
|
||||||
- Compound assignment operator documentation
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
|
||||||
- **Fixed array printing** - arrays no longer show as "unknown"
|
|
||||||
- **Enhanced error reporting** - all errors now use the error reporter system
|
|
||||||
- **Improved type checking** - better error messages for type mismatches
|
|
||||||
- **Memory management** - better cleanup of unused functions and arrays
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To create the VSIX package:
|
|
||||||
1. Install Node.js and npm
|
|
||||||
2. Run `npm install -g vsce`
|
|
||||||
3. Run `./package-vsix.sh`
|
|
||||||
|
|
||||||
The extension will be packaged as `bob-language-0.4.0.vsix`
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
- VS Code 1.60.0+
|
|
||||||
- Cursor (VS Code compatible)
|
|
||||||
- All platforms (Windows, macOS, Linux)
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// ===========================================================
|
|
||||||
// Bob feature-tour demo
|
|
||||||
// – dot-notation for dictionaries & arrays
|
|
||||||
// – property assignment (incl. nested)
|
|
||||||
// – built-in properties: dict.keys / dict.values
|
|
||||||
// arr.length / arr.first / arr.last / arr.empty
|
|
||||||
// – tail-call-optimised (TCO) recursion
|
|
||||||
// ===========================================================
|
|
||||||
|
|
||||||
// ---------- 1. Dictionary property access / assignment ----------
|
|
||||||
var person = { "name": "Alice", "age": 30 };
|
|
||||||
print(person.name);
|
|
||||||
print(person.age);
|
|
||||||
|
|
||||||
// add a brand-new user property
|
|
||||||
person.country = "Canada";
|
|
||||||
print(person.country);
|
|
||||||
|
|
||||||
// nested property assignment
|
|
||||||
var team = { "lead": { "name": "Bob", "age": 40 } };
|
|
||||||
team.lead.age = 41;
|
|
||||||
print(team.lead.age);
|
|
||||||
|
|
||||||
// built-in dictionary properties
|
|
||||||
print("dict length = " + team.length);
|
|
||||||
print(team.keys);
|
|
||||||
print(team.values[0].name);
|
|
||||||
|
|
||||||
// ---------- 2. Array dot-properties ----------
|
|
||||||
var nums = [ 10, 20, 30, 40 ];
|
|
||||||
print("len = " + nums.length);
|
|
||||||
print("first = " + nums.first);
|
|
||||||
print("last = " + nums.last);
|
|
||||||
print("empty? " + nums.empty);
|
|
||||||
|
|
||||||
// dot-properties are read-only; assignment still via index
|
|
||||||
nums[0] = 99;
|
|
||||||
print(nums.first);
|
|
||||||
|
|
||||||
// ---------- 3. Tail-call-optimised recursion ----------
|
|
||||||
func fibTail(n, a, b) {
|
|
||||||
return n <= 0 ? a : fibTail(n - 1, b, a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Fast TCO fib(90) = " + fibTail(90, 0, 1));
|
|
||||||
print("Fast TCO fib(5000) = " + fibTail(5000, 0, 1));
|
|
||||||
|
|
||||||
// ---------- 4. Compare non-TCO version (stack grows!) ----------
|
|
||||||
func fibPlain(n) {
|
|
||||||
return n <= 1 ? n : fibPlain(n - 1) + fibPlain(n - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Slow recursive fib(35) = " + fibPlain(35));
|
|
||||||
|
|
||||||
// ---------- 5. Summary ----------
|
|
||||||
print("All new features demonstrated!");
|
|
||||||
0
headers/AST.h
Normal file
0
headers/AST.h
Normal file
@ -9,22 +9,11 @@
|
|||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ErrorReporter;
|
class ErrorReporter;
|
||||||
|
|
||||||
struct Environment {
|
class Environment {
|
||||||
public:
|
public:
|
||||||
Environment() : parent(nullptr), errorReporter(nullptr) {}
|
Environment() : parent(nullptr), errorReporter(nullptr) {}
|
||||||
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
|
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
|
||||||
|
|
||||||
// Copy constructor for closure snapshots - creates a deep copy of the environment chain
|
|
||||||
Environment(const Environment& other) : parent(nullptr), errorReporter(other.errorReporter) {
|
|
||||||
// Copy all variables normally - arrays will be handled by forceCleanup
|
|
||||||
variables = other.variables;
|
|
||||||
|
|
||||||
// Create a deep copy of the parent environment chain
|
|
||||||
if (other.parent) {
|
|
||||||
parent = std::make_shared<Environment>(*other.parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set error reporter for enhanced error reporting
|
// Set error reporter for enhanced error reporting
|
||||||
void setErrorReporter(ErrorReporter* reporter) {
|
void setErrorReporter(ErrorReporter* reporter) {
|
||||||
errorReporter = reporter;
|
errorReporter = reporter;
|
||||||
@ -44,9 +33,6 @@ public:
|
|||||||
// Get by string name with error reporting
|
// Get by string name with error reporting
|
||||||
Value get(const std::string& name);
|
Value get(const std::string& name);
|
||||||
|
|
||||||
// Prune heavy containers in a snapshot to avoid capture cycles
|
|
||||||
void pruneForClosureCapture();
|
|
||||||
|
|
||||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||||
inline void clear() { variables.clear(); }
|
inline void clear() { variables.clear(); }
|
||||||
|
|
||||||
@ -59,5 +45,4 @@ private:
|
|||||||
std::unordered_map<std::string, Value> variables;
|
std::unordered_map<std::string, Value> variables;
|
||||||
std::shared_ptr<Environment> parent;
|
std::shared_ptr<Environment> parent;
|
||||||
ErrorReporter* errorReporter;
|
ErrorReporter* errorReporter;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,9 +48,6 @@ public:
|
|||||||
// Check if an error has been reported
|
// Check if an error has been reported
|
||||||
bool hasReportedError() const { return hadError; }
|
bool hasReportedError() const { return hadError; }
|
||||||
|
|
||||||
// Reset error state (call this between REPL commands)
|
|
||||||
void resetErrorState() { hadError = false; }
|
|
||||||
|
|
||||||
// Report errors with full context
|
// Report errors with full context
|
||||||
void reportErrorWithContext(const ErrorContext& context);
|
void reportErrorWithContext(const ErrorContext& context);
|
||||||
|
|
||||||
@ -1,4 +1,6 @@
|
|||||||
// Expression AST nodes for Bob language
|
//
|
||||||
|
// Created by Bobby Lucero on 5/21/23.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -11,16 +13,6 @@
|
|||||||
// Forward declarations
|
// Forward declarations
|
||||||
struct FunctionExpr;
|
struct FunctionExpr;
|
||||||
struct IncrementExpr;
|
struct IncrementExpr;
|
||||||
struct TernaryExpr;
|
|
||||||
struct ArrayLiteralExpr;
|
|
||||||
struct ArrayIndexExpr;
|
|
||||||
struct PropertyExpr;
|
|
||||||
|
|
||||||
struct ArrayAssignExpr;
|
|
||||||
struct PropertyAssignExpr;
|
|
||||||
struct DictLiteralExpr;
|
|
||||||
struct DictIndexExpr;
|
|
||||||
struct DictAssignExpr;
|
|
||||||
struct ExprVisitor;
|
struct ExprVisitor;
|
||||||
|
|
||||||
struct AssignExpr;
|
struct AssignExpr;
|
||||||
@ -43,15 +35,6 @@ struct ExprVisitor
|
|||||||
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
||||||
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
|
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
|
||||||
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
||||||
virtual Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expr) = 0;
|
|
||||||
virtual Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) = 0;
|
|
||||||
virtual Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) = 0;
|
|
||||||
virtual Value visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) = 0;
|
|
||||||
|
|
||||||
virtual Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) = 0;
|
|
||||||
virtual Value visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expr) = 0;
|
|
||||||
virtual Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) = 0;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Expr : public std::enable_shared_from_this<Expr> {
|
struct Expr : public std::enable_shared_from_this<Expr> {
|
||||||
@ -169,101 +152,3 @@ struct IncrementExpr : Expr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TernaryExpr : Expr
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Expr> thenExpr;
|
|
||||||
std::shared_ptr<Expr> elseExpr;
|
|
||||||
|
|
||||||
TernaryExpr(std::shared_ptr<Expr> condition, std::shared_ptr<Expr> thenExpr, std::shared_ptr<Expr> elseExpr)
|
|
||||||
: condition(condition), thenExpr(thenExpr), elseExpr(elseExpr) {}
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitTernaryExpr(std::static_pointer_cast<TernaryExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ArrayLiteralExpr : Expr
|
|
||||||
{
|
|
||||||
std::vector<std::shared_ptr<Expr>> elements;
|
|
||||||
|
|
||||||
explicit ArrayLiteralExpr(const std::vector<std::shared_ptr<Expr>>& elements)
|
|
||||||
: elements(elements) {}
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitArrayLiteralExpr(std::static_pointer_cast<ArrayLiteralExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ArrayIndexExpr : Expr
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> array;
|
|
||||||
std::shared_ptr<Expr> index;
|
|
||||||
Token bracket; // The closing bracket token for error reporting
|
|
||||||
|
|
||||||
ArrayIndexExpr(std::shared_ptr<Expr> array, std::shared_ptr<Expr> index, Token bracket)
|
|
||||||
: array(array), index(index), bracket(bracket) {}
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitArrayIndexExpr(std::static_pointer_cast<ArrayIndexExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PropertyExpr : Expr
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> object;
|
|
||||||
Token name;
|
|
||||||
|
|
||||||
PropertyExpr(std::shared_ptr<Expr> object, Token name)
|
|
||||||
: object(object), name(name) {}
|
|
||||||
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitPropertyExpr(std::static_pointer_cast<PropertyExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ArrayAssignExpr : Expr
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> array;
|
|
||||||
std::shared_ptr<Expr> index;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
Token bracket; // The closing bracket token for error reporting
|
|
||||||
|
|
||||||
ArrayAssignExpr(std::shared_ptr<Expr> array, std::shared_ptr<Expr> index, std::shared_ptr<Expr> value, Token bracket)
|
|
||||||
: array(array), index(index), value(value), bracket(bracket) {}
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitArrayAssignExpr(std::static_pointer_cast<ArrayAssignExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PropertyAssignExpr : Expr
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> object;
|
|
||||||
Token name;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
|
|
||||||
PropertyAssignExpr(std::shared_ptr<Expr> object, Token name, std::shared_ptr<Expr> value)
|
|
||||||
: object(object), name(name), value(value) {}
|
|
||||||
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitPropertyAssignExpr(std::static_pointer_cast<PropertyAssignExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DictLiteralExpr : Expr
|
|
||||||
{
|
|
||||||
std::vector<std::pair<std::string, std::shared_ptr<Expr>>> pairs;
|
|
||||||
|
|
||||||
explicit DictLiteralExpr(const std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& pairs)
|
|
||||||
: pairs(pairs) {}
|
|
||||||
Value accept(ExprVisitor* visitor) override
|
|
||||||
{
|
|
||||||
return visitor->visitDictLiteralExpr(std::static_pointer_cast<DictLiteralExpr>(shared_from_this()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
72
headers/Interpreter.h
Normal file
72
headers/Interpreter.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Expression.h"
|
||||||
|
#include "Statement.h"
|
||||||
|
#include "helperFunctions/ShortHands.h"
|
||||||
|
#include "TypeWrapper.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "Value.h"
|
||||||
|
#include "StdLib.h"
|
||||||
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
class Interpreter : public ExprVisitor, public StmtVisitor {
|
||||||
|
|
||||||
|
public:
|
||||||
|
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
||||||
|
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
||||||
|
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
||||||
|
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
||||||
|
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
||||||
|
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
||||||
|
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
||||||
|
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
||||||
|
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
||||||
|
|
||||||
|
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
|
||||||
|
void interpret(std::vector<std::shared_ptr<Stmt> > statements);
|
||||||
|
|
||||||
|
explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){
|
||||||
|
environment = std::make_shared<Environment>();
|
||||||
|
}
|
||||||
|
virtual ~Interpreter() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Environment> environment;
|
||||||
|
bool IsInteractive;
|
||||||
|
std::vector<std::shared_ptr<BuiltinFunction> > builtinFunctions;
|
||||||
|
std::vector<std::shared_ptr<Function> > functions;
|
||||||
|
ErrorReporter* errorReporter;
|
||||||
|
|
||||||
|
Value evaluate(const std::shared_ptr<Expr>& expr);
|
||||||
|
bool isEqual(Value a, Value b);
|
||||||
|
bool isWholeNumer(double num);
|
||||||
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
||||||
|
void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
||||||
|
void addStdLibFunctions();
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool isTruthy(Value object);
|
||||||
|
std::string stringify(Value object);
|
||||||
|
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
||||||
|
|
||||||
|
// Error reporting
|
||||||
|
void setErrorReporter(ErrorReporter* reporter) {
|
||||||
|
errorReporter = reporter;
|
||||||
|
if (environment) {
|
||||||
|
environment->setErrorReporter(reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add standard library functions after error reporter is set
|
||||||
|
addStdLibFunctions();
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
enum TokenType{
|
enum TokenType{
|
||||||
OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE,
|
OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE,
|
||||||
OPEN_BRACKET, CLOSE_BRACKET, // Array brackets
|
|
||||||
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT,
|
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT,
|
||||||
|
|
||||||
BIN_OR, BIN_AND, BIN_NOT, BIN_XOR, BIN_SLEFT, BIN_SRIGHT,
|
BIN_OR, BIN_AND, BIN_NOT, BIN_XOR, BIN_SLEFT, BIN_SRIGHT,
|
||||||
@ -16,16 +15,13 @@ enum TokenType{
|
|||||||
GREATER, GREATER_EQUAL,
|
GREATER, GREATER_EQUAL,
|
||||||
LESS, LESS_EQUAL,
|
LESS, LESS_EQUAL,
|
||||||
|
|
||||||
// Ternary operator
|
|
||||||
QUESTION, COLON,
|
|
||||||
|
|
||||||
// Increment/decrement operators
|
// Increment/decrement operators
|
||||||
PLUS_PLUS, MINUS_MINUS,
|
PLUS_PLUS, MINUS_MINUS,
|
||||||
|
|
||||||
IDENTIFIER, STRING, NUMBER, BOOL,
|
IDENTIFIER, STRING, NUMBER, BOOL,
|
||||||
|
|
||||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||||
WHILE, DO, VAR, CLASS, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||||
@ -37,7 +33,6 @@ enum TokenType{
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
|
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
|
||||||
"OPEN_BRACKET", "CLOSE_BRACKET", // Array brackets
|
|
||||||
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
|
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
|
||||||
|
|
||||||
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
||||||
@ -47,14 +42,12 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
|||||||
"GREATER", "GREATER_EQUAL",
|
"GREATER", "GREATER_EQUAL",
|
||||||
"LESS", "LESS_EQUAL",
|
"LESS", "LESS_EQUAL",
|
||||||
|
|
||||||
"QUESTION", "COLON",
|
|
||||||
|
|
||||||
"PLUS_PLUS", "MINUS_MINUS",
|
"PLUS_PLUS", "MINUS_MINUS",
|
||||||
|
|
||||||
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
||||||
|
|
||||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||||
"WHILE", "DO", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||||
@ -74,15 +67,12 @@ const std::map<std::string, TokenType> KEYWORDS {
|
|||||||
{"func", FUNCTION},
|
{"func", FUNCTION},
|
||||||
{"for", FOR},
|
{"for", FOR},
|
||||||
{"while", WHILE},
|
{"while", WHILE},
|
||||||
{"do", DO},
|
|
||||||
{"var", VAR},
|
{"var", VAR},
|
||||||
{"class", CLASS},
|
{"class", CLASS},
|
||||||
{"super", SUPER},
|
{"super", SUPER},
|
||||||
{"this", THIS},
|
{"this", THIS},
|
||||||
{"none", NONE},
|
{"none", NONE},
|
||||||
{"return", RETURN},
|
{"return", RETURN},
|
||||||
{"break", BREAK},
|
|
||||||
{"continue", CONTINUE},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Token
|
struct Token
|
||||||
@ -24,7 +24,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
sptr(Expr) expression();
|
sptr(Expr) expression();
|
||||||
sptr(Expr) logical_or();
|
sptr(Expr) logical_or();
|
||||||
sptr(Expr) ternary();
|
|
||||||
sptr(Expr) logical_and();
|
sptr(Expr) logical_and();
|
||||||
sptr(Expr) bitwise_or();
|
sptr(Expr) bitwise_or();
|
||||||
sptr(Expr) bitwise_xor();
|
sptr(Expr) bitwise_xor();
|
||||||
@ -57,16 +56,6 @@ private:
|
|||||||
|
|
||||||
std::shared_ptr<Stmt> ifStatement();
|
std::shared_ptr<Stmt> ifStatement();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> whileStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> doWhileStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> forStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> breakStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> continueStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> declaration();
|
std::shared_ptr<Stmt> declaration();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> varDeclaration();
|
std::shared_ptr<Stmt> varDeclaration();
|
||||||
@ -74,22 +63,13 @@ private:
|
|||||||
std::shared_ptr<Stmt> functionDeclaration();
|
std::shared_ptr<Stmt> functionDeclaration();
|
||||||
std::shared_ptr<Expr> functionExpression();
|
std::shared_ptr<Expr> functionExpression();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> assignmentStatement();
|
|
||||||
sptr(Expr) assignment();
|
sptr(Expr) assignment();
|
||||||
sptr(Expr) assignmentExpression(); // For for loop increment clauses
|
|
||||||
sptr(Expr) increment(); // Parse increment/decrement expressions
|
sptr(Expr) increment(); // Parse increment/decrement expressions
|
||||||
sptr(Expr) postfix(); // Parse postfix operators
|
sptr(Expr) postfix(); // Parse postfix operators
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Stmt>> block();
|
std::vector<std::shared_ptr<Stmt>> block();
|
||||||
|
|
||||||
sptr(Expr) finishCall(sptr(Expr) callee);
|
sptr(Expr) finishCall(sptr(Expr) callee);
|
||||||
sptr(Expr) finishArrayIndex(sptr(Expr) array);
|
|
||||||
sptr(Expr) finishArrayAssign(sptr(Expr) array, sptr(Expr) index, sptr(Expr) value);
|
|
||||||
sptr(Expr) finishDictIndex(sptr(Expr) dict);
|
|
||||||
sptr(Expr) finishDictAssign(sptr(Expr) dict, sptr(Expr) key, sptr(Expr) value);
|
|
||||||
sptr(Expr) arrayLiteral();
|
|
||||||
sptr(Expr) dictLiteral();
|
|
||||||
sptr(Expr) call(); // Handle call chains (function calls, array indexing, and dict indexing)
|
|
||||||
|
|
||||||
// Helper methods for function scope tracking
|
// Helper methods for function scope tracking
|
||||||
void enterFunction() { functionDepth++; }
|
void enterFunction() { functionDepth++; }
|
||||||
120
headers/Statement.h
Normal file
120
headers/Statement.h
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "helperFunctions/ShortHands.h"
|
||||||
|
#include "TypeWrapper.h"
|
||||||
|
#include "Expression.h"
|
||||||
|
|
||||||
|
struct ExpressionStmt;
|
||||||
|
struct VarStmt;
|
||||||
|
struct BlockStmt;
|
||||||
|
struct FunctionStmt;
|
||||||
|
struct ReturnStmt;
|
||||||
|
struct IfStmt;
|
||||||
|
|
||||||
|
struct ExecutionContext {
|
||||||
|
bool isFunctionBody = false;
|
||||||
|
bool hasReturn = false;
|
||||||
|
Value returnValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StmtVisitor
|
||||||
|
{
|
||||||
|
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||||
|
{
|
||||||
|
std::shared_ptr<Expr> expression;
|
||||||
|
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual ~Stmt(){};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BlockStmt : Stmt
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<Stmt> > statements;
|
||||||
|
explicit BlockStmt(std::vector<std::shared_ptr<Stmt> > statements) : statements(statements)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
|
{
|
||||||
|
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExpressionStmt : Stmt
|
||||||
|
{
|
||||||
|
std::shared_ptr<Expr> expression;
|
||||||
|
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
|
{
|
||||||
|
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct VarStmt : Stmt
|
||||||
|
{
|
||||||
|
Token name;
|
||||||
|
std::shared_ptr<Expr> initializer;
|
||||||
|
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
|
{
|
||||||
|
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FunctionStmt : Stmt
|
||||||
|
{
|
||||||
|
const Token name;
|
||||||
|
const std::vector<Token> params;
|
||||||
|
std::vector<std::shared_ptr<Stmt> > body;
|
||||||
|
|
||||||
|
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt> > body)
|
||||||
|
: name(name), params(params), body(body) {}
|
||||||
|
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
|
{
|
||||||
|
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReturnStmt : Stmt
|
||||||
|
{
|
||||||
|
const Token keyword;
|
||||||
|
std::shared_ptr<Expr> value;
|
||||||
|
|
||||||
|
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
|
||||||
|
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
|
{
|
||||||
|
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IfStmt : Stmt
|
||||||
|
{
|
||||||
|
std::shared_ptr<Expr> condition;
|
||||||
|
std::shared_ptr<Stmt> thenBranch;
|
||||||
|
std::shared_ptr<Stmt> elseBranch;
|
||||||
|
|
||||||
|
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
|
||||||
|
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
|
||||||
|
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
|
{
|
||||||
|
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -7,7 +7,7 @@
|
|||||||
class Interpreter;
|
class Interpreter;
|
||||||
class ErrorReporter;
|
class ErrorReporter;
|
||||||
|
|
||||||
class BobStdLib {
|
class StdLib {
|
||||||
public:
|
public:
|
||||||
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
|
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
|
||||||
};
|
};
|
||||||
274
headers/Value.h
Normal file
274
headers/Value.h
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class Environment;
|
||||||
|
class Function;
|
||||||
|
class BuiltinFunction;
|
||||||
|
|
||||||
|
// Type tags for the Value union
|
||||||
|
enum ValueType : uint8_t {
|
||||||
|
VAL_NONE,
|
||||||
|
VAL_NUMBER,
|
||||||
|
VAL_BOOLEAN,
|
||||||
|
VAL_STRING,
|
||||||
|
VAL_FUNCTION,
|
||||||
|
VAL_BUILTIN_FUNCTION
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tagged value system (like Lua) - no heap allocation for simple values
|
||||||
|
struct Value {
|
||||||
|
union {
|
||||||
|
double number;
|
||||||
|
bool boolean;
|
||||||
|
Function* function;
|
||||||
|
BuiltinFunction* builtin_function;
|
||||||
|
};
|
||||||
|
ValueType type;
|
||||||
|
std::string string_value; // Store strings outside the union for safety
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
||||||
|
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
||||||
|
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
|
||||||
|
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
||||||
|
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
||||||
|
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
||||||
|
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
||||||
|
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
||||||
|
|
||||||
|
// Move constructor
|
||||||
|
Value(Value&& other) noexcept
|
||||||
|
: type(other.type), string_value(std::move(other.string_value)) {
|
||||||
|
if (type != ValueType::VAL_STRING) {
|
||||||
|
number = other.number; // Copy the union
|
||||||
|
}
|
||||||
|
other.type = ValueType::VAL_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move assignment
|
||||||
|
Value& operator=(Value&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
type = other.type;
|
||||||
|
if (type == ValueType::VAL_STRING) {
|
||||||
|
string_value = std::move(other.string_value);
|
||||||
|
} else {
|
||||||
|
number = other.number; // Copy the union
|
||||||
|
}
|
||||||
|
other.type = ValueType::VAL_NONE;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy constructor (only when needed)
|
||||||
|
Value(const Value& other) : type(other.type) {
|
||||||
|
if (type == ValueType::VAL_STRING) {
|
||||||
|
string_value = other.string_value;
|
||||||
|
} else {
|
||||||
|
number = other.number; // Copy the union
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy assignment (only when needed)
|
||||||
|
Value& operator=(const Value& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
type = other.type;
|
||||||
|
if (type == ValueType::VAL_STRING) {
|
||||||
|
string_value = other.string_value;
|
||||||
|
} else {
|
||||||
|
number = other.number; // Copy the union
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking (fast, no dynamic casting) - inline for performance
|
||||||
|
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
|
||||||
|
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
|
||||||
|
inline bool isString() const { return type == ValueType::VAL_STRING; }
|
||||||
|
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
|
||||||
|
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
||||||
|
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
||||||
|
|
||||||
|
// Value extraction (safe, with type checking) - inline for performance
|
||||||
|
inline double asNumber() const { return isNumber() ? number : 0.0; }
|
||||||
|
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
|
||||||
|
inline const std::string& asString() const { return string_value; }
|
||||||
|
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
|
||||||
|
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
|
||||||
|
|
||||||
|
// Truthiness check - inline for performance
|
||||||
|
inline bool isTruthy() const {
|
||||||
|
switch (type) {
|
||||||
|
case ValueType::VAL_NONE: return false;
|
||||||
|
case ValueType::VAL_BOOLEAN: return boolean;
|
||||||
|
case ValueType::VAL_NUMBER: return number != 0.0;
|
||||||
|
case ValueType::VAL_STRING: return !string_value.empty();
|
||||||
|
case ValueType::VAL_FUNCTION: return function != nullptr;
|
||||||
|
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equality comparison - inline for performance
|
||||||
|
inline bool equals(const Value& other) const {
|
||||||
|
if (type != other.type) return false;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ValueType::VAL_NONE: return true;
|
||||||
|
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
|
||||||
|
case ValueType::VAL_NUMBER: return number == other.number;
|
||||||
|
case ValueType::VAL_STRING: return string_value == other.string_value;
|
||||||
|
case ValueType::VAL_FUNCTION: return function == other.function;
|
||||||
|
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String representation
|
||||||
|
std::string toString() const {
|
||||||
|
switch (type) {
|
||||||
|
case ValueType::VAL_NONE: return "none";
|
||||||
|
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
|
||||||
|
case ValueType::VAL_NUMBER: {
|
||||||
|
// Format numbers like the original stringify function
|
||||||
|
if (number == std::floor(number)) {
|
||||||
|
return std::to_string(static_cast<long long>(number));
|
||||||
|
} else {
|
||||||
|
std::string str = std::to_string(number);
|
||||||
|
// Remove trailing zeros
|
||||||
|
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||||
|
if (str.back() == '.') str.pop_back();
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ValueType::VAL_STRING: return string_value;
|
||||||
|
case ValueType::VAL_FUNCTION: return "<function>";
|
||||||
|
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
|
||||||
|
default: return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arithmetic operators
|
||||||
|
Value operator+(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(number + other.number);
|
||||||
|
}
|
||||||
|
if (isString() && other.isString()) {
|
||||||
|
return Value(string_value + other.string_value);
|
||||||
|
}
|
||||||
|
if (isString() && other.isNumber()) {
|
||||||
|
return Value(string_value + other.toString());
|
||||||
|
}
|
||||||
|
if (isNumber() && other.isString()) {
|
||||||
|
return Value(toString() + other.string_value);
|
||||||
|
}
|
||||||
|
// Handle none values by converting to string
|
||||||
|
if (isString() && other.isNone()) {
|
||||||
|
return Value(string_value + "none");
|
||||||
|
}
|
||||||
|
if (isNone() && other.isString()) {
|
||||||
|
return Value("none" + other.string_value);
|
||||||
|
}
|
||||||
|
if (isString() && !other.isString() && !other.isNumber()) {
|
||||||
|
return Value(string_value + other.toString());
|
||||||
|
}
|
||||||
|
if (!isString() && !isNumber() && other.isString()) {
|
||||||
|
return Value(toString() + other.string_value);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for + operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator-(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(number - other.number);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for - operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator*(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(number * other.number);
|
||||||
|
}
|
||||||
|
if (isString() && other.isNumber()) {
|
||||||
|
std::string result;
|
||||||
|
for (int i = 0; i < static_cast<int>(other.number); ++i) {
|
||||||
|
result += string_value;
|
||||||
|
}
|
||||||
|
return Value(result);
|
||||||
|
}
|
||||||
|
if (isNumber() && other.isString()) {
|
||||||
|
std::string result;
|
||||||
|
for (int i = 0; i < static_cast<int>(number); ++i) {
|
||||||
|
result += other.string_value;
|
||||||
|
}
|
||||||
|
return Value(result);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for * operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator/(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
if (other.number == 0) {
|
||||||
|
throw std::runtime_error("Division by zero");
|
||||||
|
}
|
||||||
|
return Value(number / other.number);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for / operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator%(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(fmod(number, other.number));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for % operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator&(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for & operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator|(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for | operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator^(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for ^ operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator<<(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for << operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value operator>>(const Value& other) const {
|
||||||
|
if (isNumber() && other.isNumber()) {
|
||||||
|
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid operands for >> operator");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global constants for common values
|
||||||
|
extern const Value NONE_VALUE;
|
||||||
|
extern const Value TRUE_VALUE;
|
||||||
|
extern const Value FALSE_VALUE;
|
||||||
|
extern const Value ZERO_VALUE;
|
||||||
|
extern const Value ONE_VALUE;
|
||||||
@ -3,12 +3,12 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "Lexer.h"
|
#include "../headers/Lexer.h"
|
||||||
#include "Interpreter.h"
|
#include "../headers/Interpreter.h"
|
||||||
#include "helperFunctions/ShortHands.h"
|
#include "../headers/helperFunctions/ShortHands.h"
|
||||||
#include "ErrorReporter.h"
|
#include "../headers/ErrorReporter.h"
|
||||||
|
|
||||||
#define VERSION "0.0.3"
|
#define VERSION "0.0.1"
|
||||||
|
|
||||||
class Bob
|
class Bob
|
||||||
{
|
{
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
|
||||||
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
|
inline std::vector<std::string> splitString(const std::string& input, std::string delimiter) {
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
std::string token;
|
std::string token;
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
@ -1,95 +0,0 @@
|
|||||||
# Bob Memory Leak Test Suite
|
|
||||||
|
|
||||||
This directory contains comprehensive memory leak tests for the Bob programming language. Each test file focuses on different scenarios that could potentially cause memory leaks.
|
|
||||||
|
|
||||||
## Test Files
|
|
||||||
|
|
||||||
### `leaktest_functions.bob`
|
|
||||||
Tests function-related memory scenarios:
|
|
||||||
- Recursive function closures
|
|
||||||
- Function factories (functions returning functions)
|
|
||||||
- Deep function nesting
|
|
||||||
- Circular function references
|
|
||||||
- Expected behavior: Memory should be freed when functions are cleared
|
|
||||||
|
|
||||||
### `leaktest_collections.bob`
|
|
||||||
Tests collection (arrays/dictionaries) memory scenarios:
|
|
||||||
- Large nested arrays
|
|
||||||
- Large nested dictionaries
|
|
||||||
- Mixed array/dict structures
|
|
||||||
- Self-referencing structures
|
|
||||||
- Large string collections
|
|
||||||
- Expected behavior: Collections should be properly freed when reassigned
|
|
||||||
|
|
||||||
### `leaktest_mixed.bob`
|
|
||||||
Tests mixed type scenarios and edge cases:
|
|
||||||
- Functions capturing collections
|
|
||||||
- Collections containing functions and mixed types
|
|
||||||
- Dynamic property assignment patterns
|
|
||||||
- Type reassignment chains
|
|
||||||
- Rapid allocation/deallocation cycles
|
|
||||||
- Expected behavior: Memory should be freed regardless of type mixing
|
|
||||||
|
|
||||||
### `leaktest_loops.bob`
|
|
||||||
Tests memory behavior in loops and repetitive operations:
|
|
||||||
- Nested loop allocation
|
|
||||||
- While loop accumulation
|
|
||||||
- Variable reassignment in loops
|
|
||||||
- Do-while function creation
|
|
||||||
- Complex loop control flow
|
|
||||||
- Memory churn tests
|
|
||||||
- Expected behavior: Loop-created objects should be freed when variables are reassigned
|
|
||||||
|
|
||||||
### `leaktest_builtin.bob`
|
|
||||||
Tests builtin function and stdlib memory behavior:
|
|
||||||
- Heavy string operations
|
|
||||||
- Type conversion stress tests
|
|
||||||
- Array/Dict builtin operations
|
|
||||||
- Eval function stress tests
|
|
||||||
- File I/O operations
|
|
||||||
- Random number generation
|
|
||||||
- Expected behavior: Builtin operations should not leak memory
|
|
||||||
|
|
||||||
## How to Run Tests
|
|
||||||
|
|
||||||
Run each test individually and monitor memory usage:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Monitor memory before, during, and after each test
|
|
||||||
./build-ninja/bin/bob leakTests/leaktest_functions.bob
|
|
||||||
./build-ninja/bin/bob leakTests/leaktest_collections.bob
|
|
||||||
./build-ninja/bin/bob leakTests/leaktest_mixed.bob
|
|
||||||
./build-ninja/bin/bob leakTests/leaktest_loops.bob
|
|
||||||
./build-ninja/bin/bob leakTests/leaktest_builtin.bob
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
|
|
||||||
After the memory leak fixes:
|
|
||||||
1. **Memory should increase** during object creation phases
|
|
||||||
2. **Memory should decrease** significantly when objects are cleared (set to `none`, `[]`, different types, etc.)
|
|
||||||
3. **Memory should return close to baseline** after each test section
|
|
||||||
4. **No gradual memory increase** across multiple test cycles
|
|
||||||
|
|
||||||
## Memory Monitoring
|
|
||||||
|
|
||||||
Use system tools to monitor memory:
|
|
||||||
- **macOS**: Activity Monitor or `top -pid $(pgrep bob)`
|
|
||||||
- **Linux**: `top`, `htop`, or `ps aux | grep bob`
|
|
||||||
- **Windows**: Task Manager or Process Monitor
|
|
||||||
|
|
||||||
Look for:
|
|
||||||
- Memory spikes during creation phases ✅ Expected
|
|
||||||
- Memory drops after "cleared" messages ✅ Expected
|
|
||||||
- Memory staying high after clearing ❌ Potential leak
|
|
||||||
- Gradual increase across test cycles ❌ Potential leak
|
|
||||||
|
|
||||||
## Test Scenarios Covered
|
|
||||||
|
|
||||||
- **Object Types**: Functions, Arrays, Dictionaries, Strings, Numbers, Booleans
|
|
||||||
- **Memory Patterns**: Allocation, Deallocation, Reassignment, Type Changes
|
|
||||||
- **Edge Cases**: Circular references, Deep nesting, Self-references, Mixed types
|
|
||||||
- **Operations**: Loops, Builtin functions, File I/O, Type conversions
|
|
||||||
- **Cleanup Triggers**: Setting to `none`, `[]`, `{}`, different types, string values
|
|
||||||
|
|
||||||
This comprehensive test suite should help identify any remaining memory leak scenarios in the Bob interpreter.
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
// Memory leak test: Builtin function and stdlib scenarios
|
|
||||||
// Test memory behavior with builtin functions and standard library
|
|
||||||
|
|
||||||
print("=== Builtin Function Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Heavy string operations
|
|
||||||
print("Test 1: Heavy string operations");
|
|
||||||
var stringData = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var str = toString(i) + "_" + toString(i * 2) + "_" + toString(i * 3);
|
|
||||||
push(stringData, {
|
|
||||||
"original": str,
|
|
||||||
"upper": str, // Bob doesn't have toUpper, but test string storage
|
|
||||||
"length": len(str),
|
|
||||||
"type": type(str)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(stringData) + " string operation results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear string data...");
|
|
||||||
stringData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Type conversion stress test
|
|
||||||
print("Test 2: Type conversion stress");
|
|
||||||
var conversions = [];
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
var num = i * 1.5;
|
|
||||||
var str = toString(num);
|
|
||||||
var backToNum = toNumber(str);
|
|
||||||
var intVal = toInt(num);
|
|
||||||
var boolVal = toBoolean(i % 2);
|
|
||||||
|
|
||||||
push(conversions, [
|
|
||||||
num, str, backToNum, intVal, boolVal,
|
|
||||||
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + len(conversions) + " type conversion results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear conversions...");
|
|
||||||
conversions = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Array/Dict builtin operations
|
|
||||||
print("Test 3: Collection builtin operations");
|
|
||||||
var collections = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
var arr = [i, i+1, i+2];
|
|
||||||
var dict = {"a": i, "b": i+1, "c": i+2};
|
|
||||||
|
|
||||||
// Use builtin functions heavily
|
|
||||||
var arrLen = len(arr);
|
|
||||||
push(arr, i+3);
|
|
||||||
var popped = pop(arr);
|
|
||||||
|
|
||||||
var dictKeys = keys(dict);
|
|
||||||
var dictValues = values(dict);
|
|
||||||
var hasA = has(dict, "a");
|
|
||||||
|
|
||||||
push(collections, {
|
|
||||||
"array": arr,
|
|
||||||
"dict": dict,
|
|
||||||
"arrLen": arrLen,
|
|
||||||
"popped": popped,
|
|
||||||
"keys": dictKeys,
|
|
||||||
"values": dictValues,
|
|
||||||
"hasA": hasA
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(collections) + " collection operation results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear collections...");
|
|
||||||
collections = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Eval function stress test
|
|
||||||
print("Test 4: Eval function stress");
|
|
||||||
var evalResults = [];
|
|
||||||
for (var i = 0; i < 10000; i++) {
|
|
||||||
var expression = toString(i) + " * 2 + 1;";
|
|
||||||
var result = eval(expression);
|
|
||||||
|
|
||||||
var funcExpr = "func() { return " + toString(i) + "; };";
|
|
||||||
var evalFunc = eval(funcExpr);
|
|
||||||
|
|
||||||
push(evalResults, {
|
|
||||||
"expr": expression,
|
|
||||||
"result": result,
|
|
||||||
"func": evalFunc,
|
|
||||||
"funcResult": evalFunc()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(evalResults) + " eval results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear eval results...");
|
|
||||||
evalResults = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: File I/O operations (if supported)
|
|
||||||
print("Test 5: File I/O operations");
|
|
||||||
var fileData = [];
|
|
||||||
for (var i = 0; i < 1000; i++) {
|
|
||||||
var filename = "temp_" + toString(i) + ".txt";
|
|
||||||
var content = "This is test data for file " + toString(i) + "\n";
|
|
||||||
content = content + "Line 2: " + toString(i * 2) + "\n";
|
|
||||||
content = content + "Line 3: " + toString(i * 3) + "\n";
|
|
||||||
|
|
||||||
// Write file
|
|
||||||
writeFile(filename, content);
|
|
||||||
|
|
||||||
// Check if exists
|
|
||||||
var exists = fileExists(filename);
|
|
||||||
|
|
||||||
// Read back
|
|
||||||
var readContent = readFile(filename);
|
|
||||||
var lines = readLines(filename);
|
|
||||||
|
|
||||||
push(fileData, {
|
|
||||||
"filename": filename,
|
|
||||||
"exists": exists,
|
|
||||||
"content": readContent,
|
|
||||||
"lines": lines,
|
|
||||||
"lineCount": len(lines)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(fileData) + " file operation results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear file data...");
|
|
||||||
fileData = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Clean up test files
|
|
||||||
print("Cleaning up test files...");
|
|
||||||
for (var i = 0; i < 1000; i++) {
|
|
||||||
var filename = "temp_" + toString(i) + ".txt";
|
|
||||||
if (fileExists(filename)) {
|
|
||||||
// Bob doesn't have deleteFile, but we created them
|
|
||||||
print("Note: Test file " + filename + " still exists");
|
|
||||||
if (i > 10) break; // Don't spam too many messages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input("File data cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 6: Random number generation
|
|
||||||
print("Test 6: Random number stress");
|
|
||||||
var randomData = [];
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
var rand1 = random();
|
|
||||||
var rand2 = random();
|
|
||||||
var sum = rand1 + rand2;
|
|
||||||
|
|
||||||
push(randomData, {
|
|
||||||
"rand1": rand1,
|
|
||||||
"rand2": rand2,
|
|
||||||
"sum": sum,
|
|
||||||
"product": rand1 * rand2
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(randomData) + " random number results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear random data...");
|
|
||||||
randomData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Builtin Function Tests Complete ===");
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
// Memory leak test: Collection-related scenarios
|
|
||||||
// Test arrays, dictionaries, and nested structures
|
|
||||||
|
|
||||||
print("=== Collection Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Large nested arrays
|
|
||||||
print("Test 1: Large nested arrays");
|
|
||||||
var nestedArrays = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
push(nestedArrays, [
|
|
||||||
[i, i+1, i+2],
|
|
||||||
[i*2, i*3, i*4],
|
|
||||||
[[i, [i+1, [i+2]]], i*5]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + len(nestedArrays) + " nested array structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear nested arrays...");
|
|
||||||
nestedArrays = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Large nested dictionaries
|
|
||||||
print("Test 2: Large nested dictionaries");
|
|
||||||
var nestedDicts = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
push(nestedDicts, {
|
|
||||||
"id": i,
|
|
||||||
"data": {
|
|
||||||
"value": i * 2,
|
|
||||||
"nested": {
|
|
||||||
"deep": {
|
|
||||||
"deeper": i * 3,
|
|
||||||
"info": "test" + i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"created": i,
|
|
||||||
"tags": ["tag" + i, "tag" + (i+1)]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(nestedDicts) + " nested dictionary structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear nested dicts...");
|
|
||||||
nestedDicts = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Mixed array/dict structures
|
|
||||||
print("Test 3: Mixed array/dict structures");
|
|
||||||
var mixedStructures = [];
|
|
||||||
for (var i = 0; i < 30000; i++) {
|
|
||||||
push(mixedStructures, [
|
|
||||||
{"arrays": [[i, i+1], [i+2, i+3]]},
|
|
||||||
[{"dicts": {"a": i, "b": i+1}}, {"more": [i, i+1]}],
|
|
||||||
{
|
|
||||||
"complex": [
|
|
||||||
{"nested": [i, {"deep": i*2}]},
|
|
||||||
[{"very": {"deep": [i, i+1, {"final": i*3}]}}]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + len(mixedStructures) + " mixed structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear mixed structures...");
|
|
||||||
mixedStructures = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Self-referencing structures (potential cycles)
|
|
||||||
print("Test 4: Self-referencing structures");
|
|
||||||
var selfRef = [];
|
|
||||||
for (var i = 0; i < 1000000; i++) {
|
|
||||||
var item = {"id": i, "value": i * 2};
|
|
||||||
// Create a structure that references itself
|
|
||||||
item["self"] = [item, {"parent": item}];
|
|
||||||
push(selfRef, item);
|
|
||||||
}
|
|
||||||
print("Created " + len(selfRef) + " self-referencing structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear self-ref structures...");
|
|
||||||
selfRef = 123;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: Large string collections
|
|
||||||
print("Test 5: Large string collections");
|
|
||||||
var stringCollections = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var longString = "";
|
|
||||||
for (var j = 0; j < 100; j++) {
|
|
||||||
longString = longString + "data" + i + "_" + j + " ";
|
|
||||||
}
|
|
||||||
push(stringCollections, {
|
|
||||||
"content": longString,
|
|
||||||
"words": [longString, longString + "_copy", longString + "_backup"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(stringCollections) + " string collections");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear string collections...");
|
|
||||||
stringCollections = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Collection Tests Complete ===");
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
// Memory leak test: Function-related scenarios
|
|
||||||
// Test various function patterns that could cause memory leaks
|
|
||||||
|
|
||||||
print("=== Function Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Recursive function closures
|
|
||||||
print("Test 1: Recursive function closures");
|
|
||||||
var recursiveFuncs = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
push(recursiveFuncs, func() {
|
|
||||||
var capturedI = i;
|
|
||||||
return func() {
|
|
||||||
if (capturedI > 0) {
|
|
||||||
return capturedI * capturedI;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(recursiveFuncs) + " recursive closure functions");
|
|
||||||
print("Memory after creation: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear recursive functions...");
|
|
||||||
recursiveFuncs = none;
|
|
||||||
print("Memory after cleanup: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Functions returning functions (factory pattern)
|
|
||||||
print("Test 2: Function factories");
|
|
||||||
var factories = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
push(factories, func() {
|
|
||||||
var multiplier = i;
|
|
||||||
return func(x) {
|
|
||||||
return x * multiplier;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(factories) + " function factories");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear factories...");
|
|
||||||
factories = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Deep function nesting
|
|
||||||
print("Test 3: Deep function nesting");
|
|
||||||
var deepNested = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
push(deepNested, func() {
|
|
||||||
return func() {
|
|
||||||
return func() {
|
|
||||||
return func() {
|
|
||||||
return func() {
|
|
||||||
return i * 42;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(deepNested) + " deeply nested functions");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear deep nested...");
|
|
||||||
deepNested = "test";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Circular function references
|
|
||||||
print("Test 4: Circular function references");
|
|
||||||
var circularFuncs = [];
|
|
||||||
for (var i = 0; i < 1000000; i++) {
|
|
||||||
var funcA = func() {
|
|
||||||
return "A" + i;
|
|
||||||
};
|
|
||||||
var funcB = func() {
|
|
||||||
return "B" + i;
|
|
||||||
};
|
|
||||||
// Store both in same array element to create potential circular refs
|
|
||||||
push(circularFuncs, [funcA, funcB, func() { return funcA; }]);
|
|
||||||
}
|
|
||||||
print("Created " + len(circularFuncs) + " circular function structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear circular functions...");
|
|
||||||
circularFuncs = 42;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Function Tests Complete ===");
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
// Memory leak test: Loop and repetitive operation scenarios
|
|
||||||
// Test memory behavior in various loop patterns
|
|
||||||
|
|
||||||
print("=== Loop Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Nested loop memory allocation
|
|
||||||
print("Test 1: Nested loop allocation");
|
|
||||||
var nestedData = [];
|
|
||||||
for (var i = 0; i < 1000; i++) {
|
|
||||||
var row = [];
|
|
||||||
for (var j = 0; j < 1000; j++) {
|
|
||||||
push(row, {
|
|
||||||
"i": i,
|
|
||||||
"j": j,
|
|
||||||
"func": func() { return i * j; },
|
|
||||||
"data": [i, j, i+j]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
push(nestedData, row);
|
|
||||||
}
|
|
||||||
print("Created " + len(nestedData) + "x" + len(nestedData[0]) + " nested structure");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear nested data...");
|
|
||||||
nestedData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: While loop with accumulation
|
|
||||||
print("Test 2: While loop accumulation");
|
|
||||||
var accumulator = [];
|
|
||||||
var counter = 0;
|
|
||||||
while (counter < 500000) {
|
|
||||||
push(accumulator, {
|
|
||||||
"count": counter,
|
|
||||||
"func": func() { return counter * 2; },
|
|
||||||
"meta": ["item" + counter, counter % 100]
|
|
||||||
});
|
|
||||||
counter = counter + 1;
|
|
||||||
}
|
|
||||||
print("Accumulated " + len(accumulator) + " items in while loop");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear accumulator...");
|
|
||||||
accumulator = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: For loop with variable reassignment
|
|
||||||
print("Test 3: Variable reassignment in loops");
|
|
||||||
var reassignVar = none;
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
// Constantly reassign to different types
|
|
||||||
if (i % 4 == 0) {
|
|
||||||
reassignVar = [i, func() { return i; }];
|
|
||||||
} else if (i % 4 == 1) {
|
|
||||||
reassignVar = {"id": i, "func": func() { return i*2; }};
|
|
||||||
} else if (i % 4 == 2) {
|
|
||||||
reassignVar = func() { return i*3; };
|
|
||||||
} else {
|
|
||||||
reassignVar = "string" + i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print("Completed " + 200000 + " reassignments, final type: " + type(reassignVar));
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear reassignment var...");
|
|
||||||
reassignVar = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Do-while with function creation
|
|
||||||
print("Test 4: Do-while function creation");
|
|
||||||
var doWhileFuncs = [];
|
|
||||||
var dwCounter = 0;
|
|
||||||
do {
|
|
||||||
push(doWhileFuncs, func() {
|
|
||||||
var captured = dwCounter;
|
|
||||||
return func() {
|
|
||||||
return captured * captured;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
dwCounter = dwCounter + 1;
|
|
||||||
} while (dwCounter < 100000);
|
|
||||||
print("Created " + len(doWhileFuncs) + " functions in do-while");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear do-while functions...");
|
|
||||||
doWhileFuncs = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: Loop with early breaks and continues
|
|
||||||
print("Test 5: Complex loop control flow");
|
|
||||||
var complexData = [];
|
|
||||||
for (var i = 0; i < 500000; i++) {
|
|
||||||
if (i % 7 == 0) {
|
|
||||||
continue; // Skip some iterations
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > 400000 && i % 100 == 0) {
|
|
||||||
// Create larger objects near the end
|
|
||||||
push(complexData, {
|
|
||||||
"large": [
|
|
||||||
func() { return i; },
|
|
||||||
[i, i+1, i+2, i+3],
|
|
||||||
{"nested": {"deep": func() { return i*2; }}}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
push(complexData, func() { return i; });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > 450000 && len(complexData) > 350000) {
|
|
||||||
break; // Early exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print("Complex loop created " + len(complexData) + " items");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear complex data...");
|
|
||||||
complexData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 6: Memory churn test (create and destroy in same loop)
|
|
||||||
print("Test 6: Memory churn test");
|
|
||||||
for (var cycle = 0; cycle < 100; cycle++) {
|
|
||||||
var churnData = [];
|
|
||||||
|
|
||||||
// Create lots of data
|
|
||||||
for (var i = 0; i < 10000; i++) {
|
|
||||||
push(churnData, [
|
|
||||||
func() { return i + cycle; },
|
|
||||||
{"cycle": cycle, "item": i}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear it immediately
|
|
||||||
churnData = none;
|
|
||||||
|
|
||||||
if (cycle % 10 == 0) {
|
|
||||||
print("Completed churn cycle " + cycle + ", Memory: " + memoryUsage() + " MB");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print("Final memory after churn test: " + memoryUsage() + " MB");
|
|
||||||
input("Completed memory churn test. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Loop Tests Complete ===");
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
// Memory leak test: Mixed type scenarios and edge cases
|
|
||||||
// Test combinations and edge cases that might cause leaks
|
|
||||||
|
|
||||||
print("=== Mixed Type Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Functions with collection captures
|
|
||||||
print("Test 1: Functions capturing collections");
|
|
||||||
var funcWithCollections = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
var capturedArray = [i, i+1, i+2, "data" + i];
|
|
||||||
var capturedDict = {"id": i, "values": [i*2, i*3]};
|
|
||||||
|
|
||||||
push(funcWithCollections, func() {
|
|
||||||
// Capture both collections
|
|
||||||
var localArray = capturedArray;
|
|
||||||
var localDict = capturedDict;
|
|
||||||
return func() {
|
|
||||||
return len(localArray) + len(localDict);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + len(funcWithCollections) + " functions with collection captures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear function collections...");
|
|
||||||
funcWithCollections = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Collections containing functions and other collections
|
|
||||||
print("Test 2: Collections with mixed content");
|
|
||||||
var mixedContent = [];
|
|
||||||
for (var i = 0; i < 30000; i++) {
|
|
||||||
push(mixedContent, [
|
|
||||||
func() { return i; }, // Function
|
|
||||||
[i, i+1, func() { return i*2; }], // Array with function
|
|
||||||
{"value": i, "func": func() { return i*3; }}, // Dict with function
|
|
||||||
"string" + i, // String
|
|
||||||
i * 1.5, // Number
|
|
||||||
i % 2 == 0 // Boolean
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + len(mixedContent) + " mixed content collections");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear mixed content...");
|
|
||||||
mixedContent = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Property assignment patterns
|
|
||||||
print("Test 3: Property assignment patterns");
|
|
||||||
var propObjects = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var obj = {"base": i};
|
|
||||||
// Dynamic property assignment
|
|
||||||
obj["prop" + i] = func() { return i; };
|
|
||||||
obj["nested"] = {"deep": {"value": i}};
|
|
||||||
obj["array"] = [1, 2, 3];
|
|
||||||
obj["array"][0] = func() { return i * 2; };
|
|
||||||
|
|
||||||
push(propObjects, obj);
|
|
||||||
}
|
|
||||||
print("Created " + len(propObjects) + " objects with dynamic properties");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear property objects...");
|
|
||||||
propObjects = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Type reassignment chains
|
|
||||||
print("Test 4: Type reassignment chains");
|
|
||||||
var typeChains = [];
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
push(typeChains, i);
|
|
||||||
}
|
|
||||||
print("Memory after number array: " + memoryUsage() + " MB");
|
|
||||||
input("Created number array. Press Enter to convert to functions...");
|
|
||||||
|
|
||||||
// Reassign all elements to functions
|
|
||||||
for (var i = 0; i < len(typeChains); i++) {
|
|
||||||
typeChains[i] = func() { return i; };
|
|
||||||
}
|
|
||||||
print("Memory after function conversion: " + memoryUsage() + " MB");
|
|
||||||
input("Converted to functions. Press Enter to convert to dicts...");
|
|
||||||
|
|
||||||
// Reassign all elements to dicts
|
|
||||||
for (var i = 0; i < len(typeChains); i++) {
|
|
||||||
typeChains[i] = {"id": i, "func": func() { return i; }};
|
|
||||||
}
|
|
||||||
print("Memory after dict conversion: " + memoryUsage() + " MB");
|
|
||||||
input("Converted to dicts. Press Enter to clear...");
|
|
||||||
typeChains = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: Rapid allocation/deallocation
|
|
||||||
print("Test 5: Rapid allocation/deallocation");
|
|
||||||
for (var round = 0; round < 10; round++) {
|
|
||||||
var temp = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
push(temp, [
|
|
||||||
func() { return i; },
|
|
||||||
{"data": [i, i+1, func() { return i*2; }]}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Round " + round + ": Created " + len(temp) + " items, Memory: " + memoryUsage() + " MB");
|
|
||||||
temp = none; // Clear immediately
|
|
||||||
if (round % 2 == 1) print("After clear round " + round + ": " + memoryUsage() + " MB");
|
|
||||||
}
|
|
||||||
print("Final memory after all cycles: " + memoryUsage() + " MB");
|
|
||||||
input("Completed rapid allocation cycles. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Mixed Type Tests Complete ===");
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Bob Memory Leak Test Runner
|
|
||||||
# Runs all leak tests in sequence with memory monitoring
|
|
||||||
|
|
||||||
echo "🧪 Bob Memory Leak Test Suite"
|
|
||||||
echo "============================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Build first
|
|
||||||
echo "📦 Building Bob..."
|
|
||||||
ninja -C build-ninja
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ Build failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ Build successful"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Function to run a test and show memory info
|
|
||||||
run_test() {
|
|
||||||
local test_file=$1
|
|
||||||
local test_name=$2
|
|
||||||
|
|
||||||
echo "🔬 Running: $test_name"
|
|
||||||
echo "File: $test_file"
|
|
||||||
echo "Memory monitoring: Use Activity Monitor (macOS) or top/htop (Linux)"
|
|
||||||
echo "Press Ctrl+C during test to abort, or follow prompts to continue"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
./build-ninja/bin/bob "$test_file"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✅ Completed: $test_name"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run all tests
|
|
||||||
run_test "leakTests/leaktest_functions.bob" "Function Memory Tests"
|
|
||||||
run_test "leakTests/leaktest_collections.bob" "Collection Memory Tests"
|
|
||||||
run_test "leakTests/leaktest_mixed.bob" "Mixed Type Memory Tests"
|
|
||||||
run_test "leakTests/leaktest_loops.bob" "Loop Memory Tests"
|
|
||||||
run_test "leakTests/leaktest_builtin.bob" "Builtin Function Memory Tests"
|
|
||||||
|
|
||||||
echo "🎉 All memory leak tests completed!"
|
|
||||||
echo ""
|
|
||||||
echo "💡 Memory Monitoring Tips:"
|
|
||||||
echo "- Memory should spike during object creation"
|
|
||||||
echo "- Memory should drop after 'cleared' messages"
|
|
||||||
echo "- Memory should return close to baseline between tests"
|
|
||||||
echo "- Watch for gradual increases across test cycles (indicates leaks)"
|
|
||||||
BIN
source/.DS_Store
vendored
Normal file
BIN
source/.DS_Store
vendored
Normal file
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
#include "Environment.h"
|
#include "../headers/Environment.h"
|
||||||
#include "ErrorReporter.h"
|
#include "../headers/ErrorReporter.h"
|
||||||
|
|
||||||
void Environment::assign(const Token& name, const Value& value) {
|
void Environment::assign(const Token& name, const Value& value) {
|
||||||
auto it = variables.find(name.lexeme);
|
auto it = variables.find(name.lexeme);
|
||||||
@ -48,20 +48,4 @@ Value Environment::get(const std::string& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw std::runtime_error("Undefined variable '" + name + "'");
|
throw std::runtime_error("Undefined variable '" + name + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Environment::pruneForClosureCapture() {
|
|
||||||
for (auto &entry : variables) {
|
|
||||||
Value &v = entry.second;
|
|
||||||
if (v.isArray()) {
|
|
||||||
// Replace with a new empty array to avoid mutating original shared storage
|
|
||||||
entry.second = Value(std::vector<Value>{});
|
|
||||||
} else if (v.isDict()) {
|
|
||||||
// Replace with a new empty dict to avoid mutating original shared storage
|
|
||||||
entry.second = Value(std::unordered_map<std::string, Value>{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parent) {
|
|
||||||
parent->pruneForClosureCapture();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "ErrorReporter.h"
|
#include "../headers/ErrorReporter.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -59,7 +59,6 @@ void ErrorReporter::loadSource(const std::string& source, const std::string& fil
|
|||||||
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
||||||
hadError = true;
|
hadError = true;
|
||||||
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
||||||
std::cout.flush(); // Ensure output is flushed before any exception is thrown
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
||||||
@ -107,8 +106,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int ERROR_DISPLAY_MAX_WIDTH = 65;
|
int maxWidth = 65;
|
||||||
int maxWidth = ERROR_DISPLAY_MAX_WIDTH;
|
|
||||||
int startLine = std::max(1, line - 4);
|
int startLine = std::max(1, line - 4);
|
||||||
int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2);
|
int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2);
|
||||||
|
|
||||||
@ -121,7 +119,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
|
|||||||
|
|
||||||
int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length());
|
int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length());
|
||||||
maxWidth = std::max(maxWidth, errorLineWidth);
|
maxWidth = std::max(maxWidth, errorLineWidth);
|
||||||
maxWidth = std::max(maxWidth, ERROR_DISPLAY_MAX_WIDTH);
|
maxWidth = std::max(maxWidth, 65);
|
||||||
|
|
||||||
std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n";
|
std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n";
|
||||||
std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n";
|
std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n";
|
||||||
@ -164,8 +162,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
|
|||||||
void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) {
|
void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) {
|
||||||
if (callStack.empty()) return;
|
if (callStack.empty()) return;
|
||||||
|
|
||||||
static const int CALL_STACK_MAX_WIDTH = 65;
|
int maxWidth = 65;
|
||||||
int maxWidth = CALL_STACK_MAX_WIDTH;
|
|
||||||
for (const auto& func : callStack) {
|
for (const auto& func : callStack) {
|
||||||
int funcWidth = static_cast<int>(func.length()) + 6;
|
int funcWidth = static_cast<int>(func.length()) + 6;
|
||||||
maxWidth = std::max(maxWidth, funcWidth);
|
maxWidth = std::max(maxWidth, funcWidth);
|
||||||
5
source/Expression.cpp
Normal file
5
source/Expression.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Created by Bobby Lucero on 5/21/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "../headers/Expression.h"
|
||||||
878
source/Interpreter.cpp
Normal file
878
source/Interpreter.cpp
Normal file
@ -0,0 +1,878 @@
|
|||||||
|
//
|
||||||
|
// Created by Bobby Lucero on 5/27/23.
|
||||||
|
//
|
||||||
|
#include <utility>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <limits>
|
||||||
|
#include <cmath>
|
||||||
|
#include "../headers/Interpreter.h"
|
||||||
|
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "../headers/Interpreter.h"
|
||||||
|
#include "../headers/StdLib.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
struct ReturnContext {
|
||||||
|
Value returnValue;
|
||||||
|
bool hasReturn;
|
||||||
|
ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||||
|
if(expr->isNull) return NONE_VALUE;
|
||||||
|
if(expr->isNumber){
|
||||||
|
double num;
|
||||||
|
if(expr->value[1] == 'b')
|
||||||
|
{
|
||||||
|
num = binaryStringToLong(expr->value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
num = std::stod(expr->value);
|
||||||
|
}
|
||||||
|
return Value(num);
|
||||||
|
}
|
||||||
|
if(expr->isBoolean) {
|
||||||
|
if(expr->value == "true") return TRUE_VALUE;
|
||||||
|
if(expr->value == "false") return FALSE_VALUE;
|
||||||
|
}
|
||||||
|
return Value(expr->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
||||||
|
|
||||||
|
return evaluate(expression->expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
||||||
|
{
|
||||||
|
Value right = evaluate(expression->right);
|
||||||
|
|
||||||
|
if(expression->oper.type == MINUS)
|
||||||
|
{
|
||||||
|
if(right.isNumber())
|
||||||
|
{
|
||||||
|
double value = right.asNumber();
|
||||||
|
return Value(-value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expression->oper.type == BANG)
|
||||||
|
{
|
||||||
|
return Value(!isTruthy(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expression->oper.type == BIN_NOT)
|
||||||
|
{
|
||||||
|
if(right.isNumber())
|
||||||
|
{
|
||||||
|
double value = right.asNumber();
|
||||||
|
return Value(static_cast<double>(~(static_cast<long>(value))));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Operand must be an int when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//unreachable
|
||||||
|
throw std::runtime_error("Invalid unary expression");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
|
||||||
|
Value left = evaluate(expression->left);
|
||||||
|
Value right = evaluate(expression->right);
|
||||||
|
|
||||||
|
if (left.isNumber() && right.isNumber()) {
|
||||||
|
double leftNum = left.asNumber();
|
||||||
|
double rightNum = right.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return Value(leftNum + rightNum);
|
||||||
|
case MINUS: return Value(leftNum - rightNum);
|
||||||
|
case SLASH: {
|
||||||
|
if (rightNum == 0) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero",
|
||||||
|
"Cannot divide by zero", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Division by zero");
|
||||||
|
}
|
||||||
|
return Value(leftNum / rightNum);
|
||||||
|
}
|
||||||
|
case STAR: return Value(leftNum * rightNum);
|
||||||
|
case PERCENT: {
|
||||||
|
if (rightNum == 0) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero",
|
||||||
|
"Cannot perform modulo operation with zero", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Modulo by zero");
|
||||||
|
}
|
||||||
|
return Value(std::fmod(leftNum, rightNum));
|
||||||
|
}
|
||||||
|
case GREATER: return Value(leftNum > rightNum);
|
||||||
|
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
||||||
|
case LESS: return Value(leftNum < rightNum);
|
||||||
|
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
||||||
|
case DOUBLE_EQUAL: return Value(leftNum == rightNum);
|
||||||
|
case BANG_EQUAL: return Value(leftNum != rightNum);
|
||||||
|
case BIN_AND: return Value(static_cast<double>(static_cast<int>(leftNum) & static_cast<int>(rightNum)));
|
||||||
|
case BIN_OR: return Value(static_cast<double>(static_cast<int>(leftNum) | static_cast<int>(rightNum)));
|
||||||
|
case BIN_XOR: return Value(static_cast<double>(static_cast<int>(leftNum) ^ static_cast<int>(rightNum)));
|
||||||
|
case BIN_SLEFT: return Value(static_cast<double>(static_cast<int>(leftNum) << static_cast<int>(rightNum)));
|
||||||
|
case BIN_SRIGHT: return Value(static_cast<double>(static_cast<int>(leftNum) >> static_cast<int>(rightNum)));
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isString() && right.isString()) {
|
||||||
|
std::string left_string = left.asString();
|
||||||
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return Value(left_string + right_string);
|
||||||
|
case DOUBLE_EQUAL: return Value(left_string == right_string);
|
||||||
|
case BANG_EQUAL: return Value(left_string != right_string);
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Cannot use '" + expression->oper.lexeme + "' on two strings", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isString() && right.isNumber()) {
|
||||||
|
std::string left_string = left.asString();
|
||||||
|
double right_num = right.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
case STAR: {
|
||||||
|
if (!isWholeNumer(right_num)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||||
|
"String multiplier must be a whole number", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("String multiplier must be whole number");
|
||||||
|
}
|
||||||
|
std::string result;
|
||||||
|
for (int i = 0; i < static_cast<int>(right_num); i++) {
|
||||||
|
result += left_string;
|
||||||
|
}
|
||||||
|
return Value(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isNumber() && right.isString()) {
|
||||||
|
double left_num = left.asNumber();
|
||||||
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
case STAR: {
|
||||||
|
if (!isWholeNumer(left_num)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||||
|
"String multiplier must be a whole number", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("String multiplier must be whole number");
|
||||||
|
}
|
||||||
|
std::string result;
|
||||||
|
for (int i = 0; i < static_cast<int>(left_num); i++) {
|
||||||
|
result += right_string;
|
||||||
|
}
|
||||||
|
return Value(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isBoolean() && right.isBoolean()) {
|
||||||
|
bool left_bool = left.asBoolean();
|
||||||
|
bool right_bool = right.asBoolean();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: return Value(left_bool && right_bool);
|
||||||
|
case OR: return Value(left_bool || right_bool);
|
||||||
|
case DOUBLE_EQUAL: return Value(left_bool == right_bool);
|
||||||
|
case BANG_EQUAL: return Value(left_bool != right_bool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (left.isBoolean() && right.isString()) {
|
||||||
|
bool left_bool = left.asBoolean();
|
||||||
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isString() && right.isBoolean()) {
|
||||||
|
std::string left_string = left.asString();
|
||||||
|
bool right_bool = right.asBoolean();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isNumber() && right.isBoolean()) {
|
||||||
|
double left_num = left.asNumber();
|
||||||
|
bool right_bool = right.asBoolean();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isBoolean() && right.isNumber()) {
|
||||||
|
bool left_bool = left.asBoolean();
|
||||||
|
double right_num = right.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixed-type logical operations (string && boolean, etc.)
|
||||||
|
if (left.isString() && right.isBoolean()) {
|
||||||
|
bool right_bool = right.asBoolean();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isBoolean() && right.isString()) {
|
||||||
|
bool left_bool = left.asBoolean();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isString() && right.isNumber()) {
|
||||||
|
double right_num = right.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case PLUS: return left + right;
|
||||||
|
case STAR: {
|
||||||
|
if (!isWholeNumer(right_num)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||||
|
"String multiplier must be a whole number");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("String multiplier must be whole number");
|
||||||
|
}
|
||||||
|
std::string result;
|
||||||
|
for (int i = 0; i < static_cast<int>(right_num); i++) {
|
||||||
|
result += left.asString();
|
||||||
|
}
|
||||||
|
return Value(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isNumber() && right.isString()) {
|
||||||
|
double left_num = left.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case AND: {
|
||||||
|
if (!isTruthy(left)) {
|
||||||
|
return left; // Return the falsy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OR: {
|
||||||
|
if (isTruthy(left)) {
|
||||||
|
return left; // Return the truthy value
|
||||||
|
} else {
|
||||||
|
return right; // Return the second value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case PLUS: return left + right;
|
||||||
|
case STAR: {
|
||||||
|
if (!isWholeNumer(left_num)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
|
||||||
|
"String multiplier must be a whole number");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("String multiplier must be whole number");
|
||||||
|
}
|
||||||
|
std::string result;
|
||||||
|
for (int i = 0; i < static_cast<int>(left_num); i++) {
|
||||||
|
result += right.asString();
|
||||||
|
}
|
||||||
|
return Value(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isNone() && right.isString()) {
|
||||||
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Cannot use '" + expression->oper.lexeme + "' on none and a string", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.isString() && right.isNone()) {
|
||||||
|
std::string left_string = left.asString();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Cannot use '" + expression->oper.lexeme + "' on a string and none", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Operands must be of same type when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
||||||
|
{
|
||||||
|
return environment->get(expression->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
||||||
|
// Get the current value of the operand
|
||||||
|
Value currentValue = evaluate(expression->operand);
|
||||||
|
|
||||||
|
if (!currentValue.isNumber()) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentNum = currentValue.asNumber();
|
||||||
|
double newValue;
|
||||||
|
|
||||||
|
// Determine the operation based on the operator
|
||||||
|
if (expression->oper.type == PLUS_PLUS) {
|
||||||
|
newValue = currentNum + 1.0;
|
||||||
|
} else if (expression->oper.type == MINUS_MINUS) {
|
||||||
|
newValue = currentNum - 1.0;
|
||||||
|
} else {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Invalid increment/decrement operator.", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the variable if it's a variable expression
|
||||||
|
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||||
|
environment->assign(varExpr->name, Value(newValue));
|
||||||
|
} else {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to variables.", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to variables.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the appropriate value based on prefix/postfix
|
||||||
|
if (expression->isPrefix) {
|
||||||
|
return Value(newValue); // Prefix: return new value
|
||||||
|
} else {
|
||||||
|
return currentValue; // Postfix: return old value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addStdLibFunctions() {
|
||||||
|
// Add standard library functions to the environment
|
||||||
|
StdLib::addToEnvironment(environment, *this, errorReporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||||
|
builtinFunctions.push_back(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
||||||
|
Value value = evaluate(expression->value);
|
||||||
|
|
||||||
|
switch (expression->op.type) {
|
||||||
|
case PLUS_EQUAL:
|
||||||
|
case MINUS_EQUAL:
|
||||||
|
case STAR_EQUAL:
|
||||||
|
case SLASH_EQUAL:
|
||||||
|
case PERCENT_EQUAL:
|
||||||
|
case BIN_AND_EQUAL:
|
||||||
|
case BIN_OR_EQUAL:
|
||||||
|
case BIN_XOR_EQUAL:
|
||||||
|
case BIN_SLEFT_EQUAL:
|
||||||
|
case BIN_SRIGHT_EQUAL: {
|
||||||
|
Value currentValue = environment->get(expression->name.lexeme);
|
||||||
|
switch (expression->op.type) {
|
||||||
|
case PLUS_EQUAL:
|
||||||
|
value = currentValue + value;
|
||||||
|
break;
|
||||||
|
case MINUS_EQUAL:
|
||||||
|
value = currentValue - value;
|
||||||
|
break;
|
||||||
|
case STAR_EQUAL:
|
||||||
|
value = currentValue * value;
|
||||||
|
break;
|
||||||
|
case SLASH_EQUAL:
|
||||||
|
value = currentValue / value;
|
||||||
|
break;
|
||||||
|
case PERCENT_EQUAL:
|
||||||
|
value = currentValue % value;
|
||||||
|
break;
|
||||||
|
case BIN_AND_EQUAL:
|
||||||
|
value = currentValue & value;
|
||||||
|
break;
|
||||||
|
case BIN_OR_EQUAL:
|
||||||
|
value = currentValue | value;
|
||||||
|
break;
|
||||||
|
case BIN_XOR_EQUAL:
|
||||||
|
value = currentValue ^ value;
|
||||||
|
break;
|
||||||
|
case BIN_SLEFT_EQUAL:
|
||||||
|
value = currentValue << value;
|
||||||
|
break;
|
||||||
|
case BIN_SRIGHT_EQUAL:
|
||||||
|
value = currentValue >> value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
environment->assign(expression->name, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||||
|
Value callee = evaluate(expression->callee);
|
||||||
|
|
||||||
|
std::vector<Value> arguments;
|
||||||
|
for (const std::shared_ptr<Expr>& argument : expression->arguments) {
|
||||||
|
arguments.push_back(evaluate(argument));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callee.isBuiltinFunction()) {
|
||||||
|
// Builtin functions now work directly with Value and receive line and column
|
||||||
|
return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callee.isFunction()) {
|
||||||
|
Function* function = callee.asFunction();
|
||||||
|
if (arguments.size() != function->params.size()) {
|
||||||
|
throw std::runtime_error("Expected " + std::to_string(function->params.size()) +
|
||||||
|
" arguments but got " + std::to_string(arguments.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto previousEnv = environment;
|
||||||
|
environment = std::make_shared<Environment>(function->closure);
|
||||||
|
environment->setErrorReporter(errorReporter);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
|
environment->define(function->params[i], arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext context;
|
||||||
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
|
for (const auto& stmt : function->body) {
|
||||||
|
execute(stmt, &context);
|
||||||
|
if (context.hasReturn) {
|
||||||
|
environment = previousEnv;
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
environment = previousEnv;
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Can only call functions and classes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
||||||
|
// Convert Token parameters to string parameters
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
for (const Token& param : expression->params) {
|
||||||
|
paramNames.push_back(param.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto function = msptr(Function)("anonymous", paramNames, expression->body, environment);
|
||||||
|
functions.push_back(function); // Keep the shared_ptr alive
|
||||||
|
return Value(function.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
||||||
|
auto newEnv = std::make_shared<Environment>(environment);
|
||||||
|
newEnv->setErrorReporter(errorReporter);
|
||||||
|
executeBlock(statement->statements, newEnv, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = evaluate(statement->expression);
|
||||||
|
|
||||||
|
if(IsInteractive)
|
||||||
|
std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context)
|
||||||
|
{
|
||||||
|
Value value = NONE_VALUE;
|
||||||
|
if(statement->initializer != nullptr)
|
||||||
|
{
|
||||||
|
value = evaluate(statement->initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//std::cout << "Visit var stmt: " << statement->name.lexeme << " set to: " << stringify(value) << std::endl;
|
||||||
|
|
||||||
|
environment->define(statement->name.lexeme, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context)
|
||||||
|
{
|
||||||
|
// Convert Token parameters to string parameters
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
for (const Token& param : statement->params) {
|
||||||
|
paramNames.push_back(param.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto function = msptr(Function)(statement->name.lexeme,
|
||||||
|
paramNames,
|
||||||
|
statement->body,
|
||||||
|
environment);
|
||||||
|
functions.push_back(function); // Keep the shared_ptr alive
|
||||||
|
environment->define(statement->name.lexeme, Value(function.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context)
|
||||||
|
{
|
||||||
|
Value value = NONE_VALUE;
|
||||||
|
if (statement->value != nullptr) {
|
||||||
|
value = evaluate(statement->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context->isFunctionBody) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = value;
|
||||||
|
}
|
||||||
|
// If no context or not in function body, this is a top-level return (ignored)
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context)
|
||||||
|
{
|
||||||
|
if (isTruthy(evaluate(statement->condition))) {
|
||||||
|
execute(statement->thenBranch, context);
|
||||||
|
} else if (statement->elseBranch != nullptr) {
|
||||||
|
execute(statement->elseBranch, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt> > statements) {
|
||||||
|
for(const std::shared_ptr<Stmt>& s : statements)
|
||||||
|
{
|
||||||
|
execute(s, nullptr); // No context needed for top-level execution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context)
|
||||||
|
{
|
||||||
|
statement->accept(this, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context)
|
||||||
|
{
|
||||||
|
std::shared_ptr<Environment> previous = this->environment;
|
||||||
|
this->environment = env;
|
||||||
|
|
||||||
|
for(const std::shared_ptr<Stmt>& s : statements)
|
||||||
|
{
|
||||||
|
execute(s, context);
|
||||||
|
if (context && context->hasReturn) {
|
||||||
|
this->environment = previous;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->environment = previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
||||||
|
return expr->accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isTruthy(Value object) {
|
||||||
|
|
||||||
|
if(object.isBoolean())
|
||||||
|
{
|
||||||
|
return object.asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object.isNone())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object.isNumber())
|
||||||
|
{
|
||||||
|
return object.asNumber() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object.isString())
|
||||||
|
{
|
||||||
|
return object.asString().length() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isEqual(Value a, Value b) {
|
||||||
|
if(a.isNumber())
|
||||||
|
{
|
||||||
|
if(b.isNumber())
|
||||||
|
{
|
||||||
|
return a.asNumber() == b.asNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(a.isBoolean())
|
||||||
|
{
|
||||||
|
if(b.isBoolean())
|
||||||
|
{
|
||||||
|
return a.asBoolean() == b.asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(a.isString())
|
||||||
|
{
|
||||||
|
if(b.isString())
|
||||||
|
{
|
||||||
|
return a.asString() == b.asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(a.isNone())
|
||||||
|
{
|
||||||
|
if(b.isNone())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Invalid isEqual compariosn");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Interpreter::stringify(Value object) {
|
||||||
|
if(object.isNone())
|
||||||
|
{
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
else if(object.isNumber())
|
||||||
|
{
|
||||||
|
double integral = object.asNumber();
|
||||||
|
double fractional = std::modf(object.asNumber(), &integral);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||||
|
{
|
||||||
|
ss << std::fixed << std::setprecision(0) << integral;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << object.asNumber();
|
||||||
|
std::string str = ss.str();
|
||||||
|
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||||
|
if (str.back() == '.') {
|
||||||
|
str.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(object.isString())
|
||||||
|
{
|
||||||
|
return object.asString();
|
||||||
|
}
|
||||||
|
else if(object.isBoolean())
|
||||||
|
{
|
||||||
|
return object.asBoolean() == 1 ? "true" : "false";
|
||||||
|
}
|
||||||
|
else if(object.isFunction())
|
||||||
|
{
|
||||||
|
return "<function " + object.asFunction()->name + ">";
|
||||||
|
}
|
||||||
|
else if(object.isBuiltinFunction())
|
||||||
|
{
|
||||||
|
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Could not convert object to string");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isWholeNumer(double num) {
|
||||||
|
double integral = num;
|
||||||
|
double fractional = std::modf(num, &integral);
|
||||||
|
|
||||||
|
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#include "Lexer.h"
|
#include "../headers/Lexer.h"
|
||||||
#include "ErrorReporter.h"
|
#include "../headers/ErrorReporter.h"
|
||||||
#include "helperFunctions/HelperFunctions.h"
|
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
std::vector<Token> Lexer::Tokenize(std::string source){
|
std::vector<Token> Lexer::Tokenize(std::string source){
|
||||||
std::vector<Token> tokens;
|
std::vector<Token> tokens;
|
||||||
@ -35,16 +35,6 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column});
|
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column});
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
else if(t == '[')
|
|
||||||
{
|
|
||||||
tokens.push_back(Token{OPEN_BRACKET, std::string(1, t), line, column});
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
else if(t == ']')
|
|
||||||
{
|
|
||||||
tokens.push_back(Token{CLOSE_BRACKET, std::string(1, t), line, column});
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
else if(t == ',')
|
else if(t == ',')
|
||||||
{
|
{
|
||||||
tokens.push_back(Token{COMMA, std::string(1, t), line, column});
|
tokens.push_back(Token{COMMA, std::string(1, t), line, column});
|
||||||
@ -125,16 +115,6 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1});
|
tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1});
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
else if(t == '?')
|
|
||||||
{
|
|
||||||
tokens.push_back(Token{QUESTION, std::string(1, t), line, column});
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
else if(t == ':')
|
|
||||||
{
|
|
||||||
tokens.push_back(Token{COLON, std::string(1, t), line, column});
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
else if(t == '=')
|
else if(t == '=')
|
||||||
{
|
{
|
||||||
std::string token = std::string(1, t);
|
std::string token = std::string(1, t);
|
||||||
@ -441,7 +421,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
tokens.push_back({END_OF_FILE, "eof", line, column});
|
tokens.push_back({END_OF_FILE, "eof", line});
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,11 +473,8 @@ char Lexer::peekNext()
|
|||||||
std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
||||||
std::string output;
|
std::string output;
|
||||||
bool escapeMode = false;
|
bool escapeMode = false;
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
while (i < input.length()) {
|
for (char c : input) {
|
||||||
char c = input[i];
|
|
||||||
|
|
||||||
if (escapeMode) {
|
if (escapeMode) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'n':
|
case 'n':
|
||||||
@ -512,30 +489,8 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
|||||||
case '\\':
|
case '\\':
|
||||||
output += '\\';
|
output += '\\';
|
||||||
break;
|
break;
|
||||||
case '0':
|
|
||||||
output += '\0';
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
output += '\r';
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
output += '\a';
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
output += '\b';
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
output += '\f';
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
output += '\v';
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
// ANSI escape sequence
|
|
||||||
output += '\033';
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid escape character: " + std::string(1, c));
|
throw runtime_error("Invalid escape character: " + std::string(1, c));
|
||||||
}
|
}
|
||||||
escapeMode = false;
|
escapeMode = false;
|
||||||
} else if (c == '\\') {
|
} else if (c == '\\') {
|
||||||
@ -543,7 +498,6 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
|||||||
} else {
|
} else {
|
||||||
output += c;
|
output += c;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
@ -1,10 +1,12 @@
|
|||||||
|
//
|
||||||
#include "Parser.h"
|
// Created by Bobby Lucero on 5/26/23.
|
||||||
|
//
|
||||||
|
#include "../headers/Parser.h"
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
// Operator Precedence Rules
|
// Precedence
|
||||||
// Following standard mathematical order of operations
|
// to all the morons on facebook who don't know what pemdas is, fuck you
|
||||||
///////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
|
|
||||||
sptr(Expr) Parser::expression()
|
sptr(Expr) Parser::expression()
|
||||||
@ -14,32 +16,18 @@ sptr(Expr) Parser::expression()
|
|||||||
|
|
||||||
sptr(Expr) Parser::logical_or()
|
sptr(Expr) Parser::logical_or()
|
||||||
{
|
{
|
||||||
sptr(Expr) expr = ternary();
|
sptr(Expr) expr = logical_and();
|
||||||
|
|
||||||
while(match({OR}))
|
while(match({OR}))
|
||||||
{
|
{
|
||||||
Token op = previous();
|
Token op = previous();
|
||||||
sptr(Expr) right = ternary();
|
sptr(Expr) right = logical_and();
|
||||||
expr = msptr(BinaryExpr)(expr, op, right);
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
sptr(Expr) Parser::ternary()
|
|
||||||
{
|
|
||||||
sptr(Expr) expr = logical_and();
|
|
||||||
|
|
||||||
if (match({QUESTION})) {
|
|
||||||
sptr(Expr) thenExpr = expression();
|
|
||||||
consume(COLON, "Expected ':' after ternary condition");
|
|
||||||
sptr(Expr) elseExpr = expression();
|
|
||||||
expr = msptr(TernaryExpr)(expr, thenExpr, elseExpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Expr) Parser::logical_and()
|
sptr(Expr) Parser::logical_and()
|
||||||
{
|
{
|
||||||
sptr(Expr) expr = equality();
|
sptr(Expr) expr = equality();
|
||||||
@ -113,42 +101,18 @@ sptr(Expr) Parser::shift()
|
|||||||
|
|
||||||
sptr(Expr) Parser::assignment()
|
sptr(Expr) Parser::assignment()
|
||||||
{
|
{
|
||||||
// Assignments are now statements, not expressions
|
|
||||||
// This function should only handle expressions that are not assignments
|
|
||||||
return increment();
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Expr) Parser::assignmentExpression()
|
|
||||||
{
|
|
||||||
// This allows assignments as expressions (for for loop increment clauses)
|
|
||||||
sptr(Expr) expr = increment();
|
sptr(Expr) expr = increment();
|
||||||
|
|
||||||
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||||
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL}))
|
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL}))
|
||||||
{
|
{
|
||||||
Token op = previous();
|
Token op = previous();
|
||||||
sptr(Expr) value = assignmentExpression();
|
sptr(Expr) value = assignment();
|
||||||
if(std::dynamic_pointer_cast<VarExpr>(expr))
|
if(std::dynamic_pointer_cast<VarExpr>(expr))
|
||||||
{
|
{
|
||||||
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
|
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
|
||||||
return msptr(AssignExpr)(name, op, value);
|
return msptr(AssignExpr)(name, op, value);
|
||||||
}
|
}
|
||||||
else if(std::dynamic_pointer_cast<ArrayIndexExpr>(expr))
|
|
||||||
{
|
|
||||||
auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expr);
|
|
||||||
return msptr(ArrayAssignExpr)(arrayExpr->array, arrayExpr->index, value, arrayExpr->bracket);
|
|
||||||
}
|
|
||||||
else if(std::dynamic_pointer_cast<PropertyExpr>(expr))
|
|
||||||
{
|
|
||||||
auto propertyExpr = std::dynamic_pointer_cast<PropertyExpr>(expr);
|
|
||||||
return msptr(PropertyAssignExpr)(propertyExpr->object, propertyExpr->name, value);
|
|
||||||
}
|
|
||||||
else if(std::dynamic_pointer_cast<ArrayIndexExpr>(expr))
|
|
||||||
{
|
|
||||||
auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expr);
|
|
||||||
return msptr(ArrayAssignExpr)(arrayExpr->array, arrayExpr->index, value, arrayExpr->bracket);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(op.line, op.column, "Parse Error",
|
errorReporter->reportError(op.line, op.column, "Parse Error",
|
||||||
@ -230,14 +194,13 @@ sptr(Expr) Parser::unary()
|
|||||||
|
|
||||||
// Handle prefix increment/decrement
|
// Handle prefix increment/decrement
|
||||||
if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) {
|
if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) {
|
||||||
// Ensure the operand is a variable or array indexing
|
// Ensure the operand is a variable
|
||||||
if (!std::dynamic_pointer_cast<VarExpr>(right) &&
|
if (!std::dynamic_pointer_cast<VarExpr>(right)) {
|
||||||
!std::dynamic_pointer_cast<ArrayIndexExpr>(right)) {
|
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(op.line, op.column, "Parse Error",
|
errorReporter->reportError(op.line, op.column, "Parse Error",
|
||||||
"Prefix increment/decrement can only be applied to variables or array elements", "");
|
"Prefix increment/decrement can only be applied to variables", "");
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Prefix increment/decrement can only be applied to variables or array elements.");
|
throw std::runtime_error("Prefix increment/decrement can only be applied to variables.");
|
||||||
}
|
}
|
||||||
return msptr(IncrementExpr)(right, op, true); // true = prefix
|
return msptr(IncrementExpr)(right, op, true); // true = prefix
|
||||||
}
|
}
|
||||||
@ -256,14 +219,13 @@ sptr(Expr) Parser::postfix()
|
|||||||
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
||||||
Token oper = previous();
|
Token oper = previous();
|
||||||
|
|
||||||
// Ensure the expression is a variable or array indexing
|
// Ensure the expression is a variable
|
||||||
if (!std::dynamic_pointer_cast<VarExpr>(expr) &&
|
if (!std::dynamic_pointer_cast<VarExpr>(expr)) {
|
||||||
!std::dynamic_pointer_cast<ArrayIndexExpr>(expr)) {
|
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(oper.line, oper.column, "Parse Error",
|
errorReporter->reportError(oper.line, oper.column, "Parse Error",
|
||||||
"Postfix increment/decrement can only be applied to variables or array elements", "");
|
"Postfix increment/decrement can only be applied to variables", "");
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Postfix increment/decrement can only be applied to variables or array elements.");
|
throw std::runtime_error("Postfix increment/decrement can only be applied to variables.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
|
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
|
||||||
@ -282,7 +244,10 @@ sptr(Expr) Parser::primary()
|
|||||||
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
|
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
|
||||||
|
|
||||||
if(match( {IDENTIFIER})) {
|
if(match( {IDENTIFIER})) {
|
||||||
return call();
|
if (check(OPEN_PAREN)) {
|
||||||
|
return finishCall(msptr(VarExpr)(previous()));
|
||||||
|
}
|
||||||
|
return msptr(VarExpr)(previous());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(match({OPEN_PAREN}))
|
if(match({OPEN_PAREN}))
|
||||||
@ -299,14 +264,6 @@ sptr(Expr) Parser::primary()
|
|||||||
return functionExpression();
|
return functionExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(match({OPEN_BRACKET})) {
|
|
||||||
return arrayLiteral();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(match({OPEN_BRACE})) {
|
|
||||||
return dictLiteral();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(peek().line, peek().column, "Parse Error",
|
errorReporter->reportError(peek().line, peek().column, "Parse Error",
|
||||||
"Expression expected", "");
|
"Expression expected", "");
|
||||||
@ -314,69 +271,7 @@ sptr(Expr) Parser::primary()
|
|||||||
throw std::runtime_error("Expression expected at: " + std::to_string(peek().line));
|
throw std::runtime_error("Expression expected at: " + std::to_string(peek().line));
|
||||||
}
|
}
|
||||||
|
|
||||||
sptr(Expr) Parser::arrayLiteral()
|
///////////////////////////////////////////
|
||||||
{
|
|
||||||
std::vector<sptr(Expr)> elements;
|
|
||||||
|
|
||||||
if (!check(CLOSE_BRACKET)) {
|
|
||||||
do {
|
|
||||||
elements.push_back(expression());
|
|
||||||
} while (match({COMMA}));
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(CLOSE_BRACKET, "Expected ']' after array elements.");
|
|
||||||
return msptr(ArrayLiteralExpr)(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Expr) Parser::dictLiteral()
|
|
||||||
{
|
|
||||||
std::vector<std::pair<std::string, sptr(Expr)>> pairs;
|
|
||||||
|
|
||||||
if (!check(CLOSE_BRACE)) {
|
|
||||||
do {
|
|
||||||
// Parse key (must be a string literal)
|
|
||||||
if (!match({STRING})) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(peek().line, peek().column, "Parse Error",
|
|
||||||
"Dictionary key must be a string literal", "");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Dictionary key must be a string literal");
|
|
||||||
}
|
|
||||||
std::string key = previous().lexeme;
|
|
||||||
|
|
||||||
// Parse colon
|
|
||||||
consume(COLON, "Expected ':' after dictionary key");
|
|
||||||
|
|
||||||
// Parse value
|
|
||||||
sptr(Expr) value = expression();
|
|
||||||
|
|
||||||
pairs.emplace_back(key, value);
|
|
||||||
} while (match({COMMA}));
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(CLOSE_BRACE, "Expected '}' after dictionary pairs.");
|
|
||||||
return msptr(DictLiteralExpr)(pairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Expr) Parser::call()
|
|
||||||
{
|
|
||||||
sptr(Expr) expr = msptr(VarExpr)(previous());
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (match({OPEN_PAREN})) {
|
|
||||||
expr = finishCall(expr);
|
|
||||||
} else if (match({OPEN_BRACKET})) {
|
|
||||||
expr = finishArrayIndex(expr);
|
|
||||||
} else if (match({DOT})) {
|
|
||||||
Token name = consume(IDENTIFIER, "Expected property name after '.'.");
|
|
||||||
expr = msptr(PropertyExpr)(expr, name);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::vector<sptr(Stmt)> Parser::parse() {
|
std::vector<sptr(Stmt)> Parser::parse() {
|
||||||
@ -449,13 +344,12 @@ std::shared_ptr<Expr> Parser::functionExpression() {
|
|||||||
std::vector<Token> parameters;
|
std::vector<Token> parameters;
|
||||||
if (!check(CLOSE_PAREN)) {
|
if (!check(CLOSE_PAREN)) {
|
||||||
do {
|
do {
|
||||||
static const size_t MAX_FUNCTION_PARAMETERS = 255;
|
if (parameters.size() >= 255) {
|
||||||
if (parameters.size() >= MAX_FUNCTION_PARAMETERS) {
|
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(peek().line, 0, "Parse Error",
|
errorReporter->reportError(peek().line, 0, "Parse Error",
|
||||||
"Cannot have more than " + std::to_string(MAX_FUNCTION_PARAMETERS) + " parameters", "");
|
"Cannot have more than 255 parameters", "");
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Cannot have more than " + std::to_string(MAX_FUNCTION_PARAMETERS) + " parameters.");
|
throw std::runtime_error("Cannot have more than 255 parameters.");
|
||||||
}
|
}
|
||||||
parameters.push_back(consume(IDENTIFIER, "Expect parameter name."));
|
parameters.push_back(consume(IDENTIFIER, "Expect parameter name."));
|
||||||
} while (match({COMMA}));
|
} while (match({COMMA}));
|
||||||
@ -478,56 +372,11 @@ sptr(Stmt) Parser::statement()
|
|||||||
{
|
{
|
||||||
if(match({RETURN})) return returnStatement();
|
if(match({RETURN})) return returnStatement();
|
||||||
if(match({IF})) return ifStatement();
|
if(match({IF})) return ifStatement();
|
||||||
if(match({DO})) return doWhileStatement();
|
|
||||||
if(match({WHILE})) return whileStatement();
|
|
||||||
if(match({FOR})) return forStatement();
|
|
||||||
if(match({BREAK})) return breakStatement();
|
|
||||||
if(match({CONTINUE})) return continueStatement();
|
|
||||||
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
||||||
|
|
||||||
// Check for assignment statement - simplified approach
|
|
||||||
if(check(IDENTIFIER)) {
|
|
||||||
// Try to parse as assignment expression first
|
|
||||||
int currentPos = current;
|
|
||||||
try {
|
|
||||||
sptr(Expr) expr = assignmentExpression();
|
|
||||||
|
|
||||||
// If we successfully parsed an assignment expression, it's an assignment statement
|
|
||||||
if(std::dynamic_pointer_cast<AssignExpr>(expr) ||
|
|
||||||
std::dynamic_pointer_cast<ArrayAssignExpr>(expr) ||
|
|
||||||
std::dynamic_pointer_cast<PropertyAssignExpr>(expr)) {
|
|
||||||
consume(SEMICOLON, "Expected ';' after assignment.");
|
|
||||||
return msptr(ExpressionStmt)(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not an assignment, reset and parse as expression statement
|
|
||||||
current = currentPos;
|
|
||||||
} catch (...) {
|
|
||||||
// If assignment parsing failed, reset and parse as expression statement
|
|
||||||
current = currentPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expressionStatement();
|
return expressionStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
sptr(Stmt) Parser::assignmentStatement()
|
|
||||||
{
|
|
||||||
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
|
|
||||||
|
|
||||||
// Consume any assignment operator
|
|
||||||
Token op;
|
|
||||||
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
|
||||||
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL})) {
|
|
||||||
op = previous();
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Expected assignment operator.");
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Expr) value = expression();
|
|
||||||
consume(SEMICOLON, "Expected ';' after assignment.");
|
|
||||||
return msptr(AssignStmt)(name, op, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Stmt) Parser::ifStatement()
|
sptr(Stmt) Parser::ifStatement()
|
||||||
{
|
{
|
||||||
@ -545,78 +394,6 @@ sptr(Stmt) Parser::ifStatement()
|
|||||||
return msptr(IfStmt)(condition, thenBranch, elseBranch);
|
return msptr(IfStmt)(condition, thenBranch, elseBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
sptr(Stmt) Parser::whileStatement()
|
|
||||||
{
|
|
||||||
consume(OPEN_PAREN, "Expected '(' after 'while'.");
|
|
||||||
sptr(Expr) condition = expression();
|
|
||||||
consume(CLOSE_PAREN, "Expected ')' after while condition.");
|
|
||||||
|
|
||||||
sptr(Stmt) body = statement();
|
|
||||||
|
|
||||||
return msptr(WhileStmt)(condition, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Stmt) Parser::doWhileStatement()
|
|
||||||
{
|
|
||||||
sptr(Stmt) body = statement();
|
|
||||||
|
|
||||||
consume(WHILE, "Expected 'while' after do-while body.");
|
|
||||||
consume(OPEN_PAREN, "Expected '(' after 'while'.");
|
|
||||||
sptr(Expr) condition = expression();
|
|
||||||
consume(CLOSE_PAREN, "Expected ')' after while condition.");
|
|
||||||
consume(SEMICOLON, "Expected ';' after do-while condition.");
|
|
||||||
|
|
||||||
return msptr(DoWhileStmt)(body, condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Stmt) Parser::forStatement()
|
|
||||||
{
|
|
||||||
consume(OPEN_PAREN, "Expected '(' after 'for'.");
|
|
||||||
|
|
||||||
sptr(Stmt) initializer;
|
|
||||||
if (match({SEMICOLON})) {
|
|
||||||
initializer = nullptr;
|
|
||||||
} else if (match({VAR})) {
|
|
||||||
initializer = varDeclaration();
|
|
||||||
} else {
|
|
||||||
// Allow assignment expressions in for loop initializer
|
|
||||||
sptr(Expr) expr = assignmentExpression();
|
|
||||||
consume(SEMICOLON, "Expected ';' after for loop initializer.");
|
|
||||||
initializer = msptr(ExpressionStmt)(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Expr) condition = nullptr;
|
|
||||||
if (!check(SEMICOLON)) {
|
|
||||||
condition = expression();
|
|
||||||
}
|
|
||||||
consume(SEMICOLON, "Expected ';' after for loop condition.");
|
|
||||||
|
|
||||||
sptr(Expr) increment = nullptr;
|
|
||||||
if (!check(CLOSE_PAREN)) {
|
|
||||||
increment = assignmentExpression();
|
|
||||||
}
|
|
||||||
consume(CLOSE_PAREN, "Expected ')' after for clauses.");
|
|
||||||
|
|
||||||
sptr(Stmt) body = statement();
|
|
||||||
|
|
||||||
// Return the for statement directly instead of desugaring
|
|
||||||
return msptr(ForStmt)(initializer, condition, increment, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Stmt) Parser::breakStatement()
|
|
||||||
{
|
|
||||||
Token keyword = previous();
|
|
||||||
consume(SEMICOLON, "Expected ';' after 'break'.");
|
|
||||||
return msptr(BreakStmt)(keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
sptr(Stmt) Parser::continueStatement()
|
|
||||||
{
|
|
||||||
Token keyword = previous();
|
|
||||||
consume(SEMICOLON, "Expected ';' after 'continue'.");
|
|
||||||
return msptr(ContinueStmt)(keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to detect if an expression is a tail call
|
// Helper function to detect if an expression is a tail call
|
||||||
bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) {
|
bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) {
|
||||||
// Check if this is a direct function call (no operations on the result)
|
// Check if this is a direct function call (no operations on the result)
|
||||||
@ -679,6 +456,9 @@ std::vector<sptr(Stmt)> Parser::block()
|
|||||||
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
||||||
std::vector<sptr(Expr)> arguments;
|
std::vector<sptr(Expr)> arguments;
|
||||||
|
|
||||||
|
// Consume the opening parenthesis
|
||||||
|
consume(OPEN_PAREN, "Expected '(' after function name.");
|
||||||
|
|
||||||
// Parse arguments if there are any
|
// Parse arguments if there are any
|
||||||
if (!check(CLOSE_PAREN)) {
|
if (!check(CLOSE_PAREN)) {
|
||||||
do {
|
do {
|
||||||
@ -690,14 +470,6 @@ sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
|||||||
return msptr(CallExpr)(callee, paren, arguments);
|
return msptr(CallExpr)(callee, paren, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
sptr(Expr) Parser::finishArrayIndex(sptr(Expr) array) {
|
|
||||||
sptr(Expr) index = expression();
|
|
||||||
Token bracket = consume(CLOSE_BRACKET, "Expected ']' after index.");
|
|
||||||
return msptr(ArrayIndexExpr)(array, index, bracket);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool Parser::match(const std::vector<TokenType>& types) {
|
bool Parser::match(const std::vector<TokenType>& types) {
|
||||||
for(TokenType t : types)
|
for(TokenType t : types)
|
||||||
{
|
{
|
||||||
261
source/StdLib.cpp
Normal file
261
source/StdLib.cpp
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
#include "../headers/StdLib.h"
|
||||||
|
#include "../headers/Interpreter.h"
|
||||||
|
#include "../headers/ErrorReporter.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
||||||
|
// Create a built-in toString function
|
||||||
|
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
||||||
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(interpreter.stringify(args[0]));
|
||||||
|
});
|
||||||
|
env->define("toString", Value(toStringFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(toStringFunc);
|
||||||
|
|
||||||
|
// Create a built-in print function
|
||||||
|
auto printFunc = std::make_shared<BuiltinFunction>("print",
|
||||||
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
// Use the interpreter's stringify function
|
||||||
|
std::cout << interpreter.stringify(args[0]) << std::endl;
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
env->define("print", Value(printFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(printFunc);
|
||||||
|
|
||||||
|
// Create a built-in assert function
|
||||||
|
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
||||||
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 1 && args.size() != 2) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple truthy check without calling interpreter.isTruthy
|
||||||
|
bool isTruthy = false;
|
||||||
|
if (args[0].isBoolean()) {
|
||||||
|
isTruthy = args[0].asBoolean();
|
||||||
|
} else if (args[0].isNone()) {
|
||||||
|
isTruthy = false;
|
||||||
|
} else {
|
||||||
|
isTruthy = true; // Numbers, strings, functions are truthy
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTruthy) {
|
||||||
|
std::string message = "Assertion failed: condition is false";
|
||||||
|
if (args.size() == 2) {
|
||||||
|
if (args[1].isString()) {
|
||||||
|
message += " - " + std::string(args[1].asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
env->define("assert", Value(assertFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(assertFunc);
|
||||||
|
|
||||||
|
// Create a built-in time function (returns microseconds since Unix epoch)
|
||||||
|
auto timeFunc = std::make_shared<BuiltinFunction>("time",
|
||||||
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 0) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = now.time_since_epoch();
|
||||||
|
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
||||||
|
|
||||||
|
return Value(static_cast<double>(microseconds));
|
||||||
|
});
|
||||||
|
env->define("time", Value(timeFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(timeFunc);
|
||||||
|
|
||||||
|
// Create a built-in input function
|
||||||
|
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
||||||
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() > 1) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional prompt
|
||||||
|
if (args.size() == 1) {
|
||||||
|
std::cout << interpreter.stringify(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user input
|
||||||
|
std::string userInput;
|
||||||
|
std::getline(std::cin, userInput);
|
||||||
|
|
||||||
|
return Value(userInput);
|
||||||
|
});
|
||||||
|
env->define("input", Value(inputFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(inputFunc);
|
||||||
|
|
||||||
|
// Create a built-in type function
|
||||||
|
auto typeFunc = std::make_shared<BuiltinFunction>("type",
|
||||||
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string typeName;
|
||||||
|
if (args[0].isNumber()) {
|
||||||
|
typeName = "number";
|
||||||
|
} else if (args[0].isString()) {
|
||||||
|
typeName = "string";
|
||||||
|
} else if (args[0].isBoolean()) {
|
||||||
|
typeName = "boolean";
|
||||||
|
} else if (args[0].isNone()) {
|
||||||
|
typeName = "none";
|
||||||
|
} else if (args[0].isFunction()) {
|
||||||
|
typeName = "function";
|
||||||
|
} else if (args[0].isBuiltinFunction()) {
|
||||||
|
typeName = "builtin_function";
|
||||||
|
} else {
|
||||||
|
typeName = "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(typeName);
|
||||||
|
});
|
||||||
|
env->define("type", Value(typeFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(typeFunc);
|
||||||
|
|
||||||
|
// Create a built-in toNumber function for string-to-number conversion
|
||||||
|
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
|
||||||
|
[](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
return NONE_VALUE; // Return none for wrong argument count
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args[0].isString()) {
|
||||||
|
return NONE_VALUE; // Return none for wrong type
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str = args[0].asString();
|
||||||
|
|
||||||
|
// Remove leading/trailing whitespace
|
||||||
|
str.erase(0, str.find_first_not_of(" \t\n\r"));
|
||||||
|
str.erase(str.find_last_not_of(" \t\n\r") + 1);
|
||||||
|
|
||||||
|
if (str.empty()) {
|
||||||
|
return NONE_VALUE; // Return none for empty string
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
double value = std::stod(str);
|
||||||
|
return Value(value);
|
||||||
|
} catch (const std::invalid_argument&) {
|
||||||
|
return NONE_VALUE; // Return none for invalid conversion
|
||||||
|
} catch (const std::out_of_range&) {
|
||||||
|
return NONE_VALUE; // Return none for out of range
|
||||||
|
}
|
||||||
|
});
|
||||||
|
env->define("toNumber", Value(toNumberFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(toNumberFunc);
|
||||||
|
|
||||||
|
// Create a built-in toBoolean function for explicit boolean conversion
|
||||||
|
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
|
||||||
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the same logic as isTruthy() for consistency
|
||||||
|
Value value = args[0];
|
||||||
|
|
||||||
|
if (value.isNone()) {
|
||||||
|
return Value(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isBoolean()) {
|
||||||
|
return value; // Already a boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isNumber()) {
|
||||||
|
return Value(value.asNumber() != 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isString()) {
|
||||||
|
return Value(!value.asString().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any other type (functions, etc.), consider them truthy
|
||||||
|
return Value(true);
|
||||||
|
});
|
||||||
|
env->define("toBoolean", Value(toBooleanFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(toBooleanFunc);
|
||||||
|
|
||||||
|
// Create a built-in exit function to terminate the program
|
||||||
|
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
|
||||||
|
[](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
int exitCode = 0; // Default exit code
|
||||||
|
|
||||||
|
if (args.size() > 0) {
|
||||||
|
if (args[0].isNumber()) {
|
||||||
|
exitCode = static_cast<int>(args[0].asNumber());
|
||||||
|
}
|
||||||
|
// If not a number, just use default exit code 0
|
||||||
|
}
|
||||||
|
|
||||||
|
std::exit(exitCode);
|
||||||
|
return NONE_VALUE; // This line should never be reached
|
||||||
|
});
|
||||||
|
env->define("exit", Value(exitFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(exitFunc);
|
||||||
|
}
|
||||||
6
source/TypeWrapper.cpp
Normal file
6
source/TypeWrapper.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
//
|
||||||
|
// Created by Bobby Lucero on 5/27/23.
|
||||||
|
//
|
||||||
|
#include "../headers/TypeWrapper.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "Value.h"
|
#include "../headers/Value.h"
|
||||||
|
|
||||||
// Global constants for common values (no heap allocation)
|
// Global constants for common values (no heap allocation)
|
||||||
const Value NONE_VALUE = Value();
|
const Value NONE_VALUE = Value();
|
||||||
@ -1,21 +1,22 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "bob.h"
|
#include "../headers/bob.h"
|
||||||
#include "Parser.h"
|
#include "../headers/Parser.h"
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
void Bob::runFile(const std::string& path)
|
void Bob::runFile(const string& path)
|
||||||
{
|
{
|
||||||
this->interpreter = msptr(Interpreter)(false);
|
this->interpreter = msptr(Interpreter)(false);
|
||||||
std::ifstream file = std::ifstream(path);
|
ifstream file = ifstream(path);
|
||||||
|
|
||||||
std::string source;
|
string source;
|
||||||
|
|
||||||
if(file.is_open()){
|
if(file.is_open()){
|
||||||
source = std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
source = string(istreambuf_iterator<char>(file), istreambuf_iterator<char>());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cout << "File not found\n";
|
cout << "File not found" << endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,11 +33,11 @@ void Bob::runPrompt()
|
|||||||
{
|
{
|
||||||
this->interpreter = msptr(Interpreter)(true);
|
this->interpreter = msptr(Interpreter)(true);
|
||||||
|
|
||||||
std::cout << "Bob v" << VERSION << ", 2025\n";
|
cout << "Bob v" << VERSION << ", 2023" << endl;
|
||||||
while(true)
|
for(;;)
|
||||||
{
|
{
|
||||||
std::string line;
|
string line;
|
||||||
std::cout << "\033[0;36m" << "-> " << "\033[0;37m";
|
cout << "\033[0;36m" << "-> " << "\033[0;37m";
|
||||||
std::getline(std::cin, line);
|
std::getline(std::cin, line);
|
||||||
|
|
||||||
if(std::cin.eof())
|
if(std::cin.eof())
|
||||||
@ -44,9 +45,6 @@ void Bob::runPrompt()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset error state before each REPL command
|
|
||||||
errorReporter.resetErrorState();
|
|
||||||
|
|
||||||
// Load source code into error reporter for context
|
// Load source code into error reporter for context
|
||||||
errorReporter.loadSource(line, "REPL");
|
errorReporter.loadSource(line, "REPL");
|
||||||
|
|
||||||
@ -57,19 +55,19 @@ void Bob::runPrompt()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bob::run(std::string source)
|
void Bob::run(string source)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Connect error reporter to lexer
|
// Connect error reporter to lexer
|
||||||
lexer.setErrorReporter(&errorReporter);
|
lexer.setErrorReporter(&errorReporter);
|
||||||
|
|
||||||
std::vector<Token> tokens = lexer.Tokenize(std::move(source));
|
vector<Token> tokens = lexer.Tokenize(std::move(source));
|
||||||
Parser p(tokens);
|
Parser p(tokens);
|
||||||
|
|
||||||
// Connect error reporter to parser
|
// Connect error reporter to parser
|
||||||
p.setErrorReporter(&errorReporter);
|
p.setErrorReporter(&errorReporter);
|
||||||
|
|
||||||
std::vector<sptr(Stmt)> statements = p.parse();
|
vector<sptr(Stmt)> statements = p.parse();
|
||||||
interpreter->interpret(statements);
|
interpreter->interpret(statements);
|
||||||
}
|
}
|
||||||
catch(std::exception &e)
|
catch(std::exception &e)
|
||||||
@ -81,13 +79,13 @@ void Bob::run(std::string source)
|
|||||||
|
|
||||||
// For errors that weren't reported (like parser errors, undefined variables, etc.)
|
// For errors that weren't reported (like parser errors, undefined variables, etc.)
|
||||||
// print them normally
|
// print them normally
|
||||||
std::cout << "Error: " << e.what() << '\n';
|
std::cout << "Error: " << e.what() << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch(...)
|
||||||
{
|
{
|
||||||
// Unknown error - report it since it wasn't handled by the interpreter
|
// Unknown error - report it since it wasn't handled by the interpreter
|
||||||
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred: " + std::string(e.what()));
|
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
//
|
//
|
||||||
|
// Created by Bobby Lucero on 5/21/23.
|
||||||
//
|
//
|
||||||
#include "bob.h"
|
#include "../headers/bob.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]){
|
int main(int argc, char* argv[]){
|
||||||
Bob bobLang;
|
Bob bobLang;
|
||||||
@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Statement.h"
|
|
||||||
|
|
||||||
class Evaluator; // Forward declaration
|
|
||||||
class Interpreter; // Forward declaration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Executor
|
|
||||||
* @brief Handles the execution of statements and control flow.
|
|
||||||
*
|
|
||||||
* Implements the StmtVisitor pattern. It is responsible for executing statements,
|
|
||||||
* managing environments, and handling control flow constructs like loops and
|
|
||||||
* conditionals. It uses the Evaluator to evaluate expressions when needed.
|
|
||||||
*/
|
|
||||||
class Executor : public StmtVisitor {
|
|
||||||
private:
|
|
||||||
Interpreter* interpreter; // Back-pointer to access interpreter services
|
|
||||||
Evaluator* evaluator; // For evaluating expressions
|
|
||||||
|
|
||||||
public:
|
|
||||||
Executor(Interpreter* interpreter, Evaluator* evaluator);
|
|
||||||
virtual ~Executor();
|
|
||||||
|
|
||||||
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
|
|
||||||
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
|
|
||||||
|
|
||||||
// Statement Visitors
|
|
||||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
|
||||||
};
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Common error message utilities
|
|
||||||
namespace ErrorUtils {
|
|
||||||
// Generate consistent operator error messages with single quotes
|
|
||||||
inline std::string makeOperatorError(const std::string& op, const std::string& leftType, const std::string& rightType) {
|
|
||||||
return "'" + op + "' is not supported between '" + leftType + "' and '" + rightType + "'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
#include "helperFunctions/ShortHands.h"
|
|
||||||
#include "TypeWrapper.h"
|
|
||||||
#include "Expression.h"
|
|
||||||
|
|
||||||
struct ExpressionStmt;
|
|
||||||
struct VarStmt;
|
|
||||||
struct BlockStmt;
|
|
||||||
struct FunctionStmt;
|
|
||||||
struct ReturnStmt;
|
|
||||||
struct IfStmt;
|
|
||||||
struct WhileStmt;
|
|
||||||
struct DoWhileStmt;
|
|
||||||
struct ForStmt;
|
|
||||||
struct BreakStmt;
|
|
||||||
struct ContinueStmt;
|
|
||||||
struct AssignStmt;
|
|
||||||
|
|
||||||
#include "ExecutionContext.h"
|
|
||||||
|
|
||||||
struct StmtVisitor
|
|
||||||
{
|
|
||||||
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitWhileStmt(const std::shared_ptr<WhileStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitForStmt(const std::shared_ptr<ForStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitBreakStmt(const std::shared_ptr<BreakStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitContinueStmt(const std::shared_ptr<ContinueStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitAssignStmt(const std::shared_ptr<AssignStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> expression;
|
|
||||||
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual ~Stmt(){};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BlockStmt : Stmt
|
|
||||||
{
|
|
||||||
std::vector<std::shared_ptr<Stmt>> statements;
|
|
||||||
explicit BlockStmt(std::vector<std::shared_ptr<Stmt>> statements) : statements(statements)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ExpressionStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> expression;
|
|
||||||
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct VarStmt : Stmt
|
|
||||||
{
|
|
||||||
Token name;
|
|
||||||
std::shared_ptr<Expr> initializer;
|
|
||||||
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FunctionStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token name;
|
|
||||||
const std::vector<Token> params;
|
|
||||||
std::vector<std::shared_ptr<Stmt>> body;
|
|
||||||
|
|
||||||
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt>> body)
|
|
||||||
: name(name), params(params), body(body) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReturnStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token keyword;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
|
|
||||||
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IfStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Stmt> thenBranch;
|
|
||||||
std::shared_ptr<Stmt> elseBranch;
|
|
||||||
|
|
||||||
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
|
|
||||||
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WhileStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Stmt> body;
|
|
||||||
|
|
||||||
WhileStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> body)
|
|
||||||
: condition(condition), body(body) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitWhileStmt(std::static_pointer_cast<WhileStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DoWhileStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Stmt> body;
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
|
|
||||||
DoWhileStmt(std::shared_ptr<Stmt> body, std::shared_ptr<Expr> condition)
|
|
||||||
: body(body), condition(condition) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitDoWhileStmt(std::static_pointer_cast<DoWhileStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ForStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Stmt> initializer;
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Expr> increment;
|
|
||||||
std::shared_ptr<Stmt> body;
|
|
||||||
|
|
||||||
ForStmt(std::shared_ptr<Stmt> initializer, std::shared_ptr<Expr> condition,
|
|
||||||
std::shared_ptr<Expr> increment, std::shared_ptr<Stmt> body)
|
|
||||||
: initializer(initializer), condition(condition), increment(increment), body(body) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitForStmt(std::static_pointer_cast<ForStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BreakStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token keyword;
|
|
||||||
|
|
||||||
BreakStmt(Token keyword) : keyword(keyword) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitBreakStmt(std::static_pointer_cast<BreakStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ContinueStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token keyword;
|
|
||||||
|
|
||||||
ContinueStmt(Token keyword) : keyword(keyword) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitContinueStmt(std::static_pointer_cast<ContinueStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AssignStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token name;
|
|
||||||
const Token op;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
|
|
||||||
AssignStmt(Token name, Token op, std::shared_ptr<Expr> value)
|
|
||||||
: name(name), op(op), value(value) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Expression.h"
|
|
||||||
#include "Value.h"
|
|
||||||
|
|
||||||
class Interpreter; // Forward declaration for the back-pointer
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Evaluator
|
|
||||||
* @brief Handles the logic for visiting and evaluating Expression AST nodes.
|
|
||||||
*
|
|
||||||
* Implements the Visitor pattern for all Expression types. This class
|
|
||||||
* contains the core evaluation logic for expressions, returning a Value.
|
|
||||||
*/
|
|
||||||
class Evaluator : public ExprVisitor {
|
|
||||||
private:
|
|
||||||
Interpreter* interpreter; // Back-pointer to access interpreter services like isTruthy, etc.
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Evaluator(Interpreter* interpreter);
|
|
||||||
virtual ~Evaluator() = default;
|
|
||||||
|
|
||||||
// Expression Visitors
|
|
||||||
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
|
||||||
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
|
||||||
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
|
||||||
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
|
||||||
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
|
||||||
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
|
||||||
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
|
||||||
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
|
||||||
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
|
||||||
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
|
|
||||||
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
|
|
||||||
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
|
|
||||||
Value visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expression) override;
|
|
||||||
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
|
|
||||||
Value visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expression) override;
|
|
||||||
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Helper methods for builtin properties
|
|
||||||
Value getArrayProperty(const Value& array, const std::string& propertyName);
|
|
||||||
Value getDictProperty(const Value& dict, const std::string& propertyName);
|
|
||||||
};
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Value.h"
|
|
||||||
|
|
||||||
struct ExecutionContext {
|
|
||||||
bool isFunctionBody = false;
|
|
||||||
bool hasReturn = false;
|
|
||||||
Value returnValue = NONE_VALUE;
|
|
||||||
bool shouldBreak = false;
|
|
||||||
bool shouldContinue = false;
|
|
||||||
};
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Statement.h"
|
|
||||||
|
|
||||||
class Evaluator; // Forward declaration
|
|
||||||
class Interpreter; // Forward declaration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Executor
|
|
||||||
* @brief Handles the execution of statements and control flow.
|
|
||||||
*
|
|
||||||
* Implements the StmtVisitor pattern. It is responsible for executing statements,
|
|
||||||
* managing environments, and handling control flow constructs like loops and
|
|
||||||
* conditionals. It uses the Evaluator to evaluate expressions when needed.
|
|
||||||
*/
|
|
||||||
class Executor : public StmtVisitor {
|
|
||||||
private:
|
|
||||||
Interpreter* interpreter; // Back-pointer to access interpreter services
|
|
||||||
Evaluator* evaluator; // For evaluating expressions
|
|
||||||
|
|
||||||
public:
|
|
||||||
Executor(Interpreter* interpreter, Evaluator* evaluator);
|
|
||||||
virtual ~Executor();
|
|
||||||
|
|
||||||
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
|
|
||||||
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
|
|
||||||
|
|
||||||
// Statement Visitors
|
|
||||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
|
||||||
};
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Expression.h"
|
|
||||||
#include "Statement.h"
|
|
||||||
#include "helperFunctions/ShortHands.h"
|
|
||||||
#include "TypeWrapper.h"
|
|
||||||
#include "Environment.h"
|
|
||||||
#include "Value.h"
|
|
||||||
#include "BobStdLib.h"
|
|
||||||
#include "ErrorReporter.h"
|
|
||||||
#include "ExecutionContext.h"
|
|
||||||
#include "RuntimeDiagnostics.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <stack>
|
|
||||||
#include <optional>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
class Evaluator;
|
|
||||||
|
|
||||||
// RAII helper for thunk execution flag
|
|
||||||
struct ScopedThunkFlag {
|
|
||||||
bool& flag;
|
|
||||||
bool prev;
|
|
||||||
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
|
|
||||||
~ScopedThunkFlag() { flag = prev; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// RAII helper for environment management
|
|
||||||
struct ScopedEnv {
|
|
||||||
std::shared_ptr<Environment>& target;
|
|
||||||
std::shared_ptr<Environment> prev;
|
|
||||||
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
|
|
||||||
~ScopedEnv() { target = prev; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Thunk class for trampoline-based tail call optimization
|
|
||||||
struct Thunk {
|
|
||||||
public:
|
|
||||||
using ThunkFunction = std::function<Value()>;
|
|
||||||
|
|
||||||
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
|
|
||||||
|
|
||||||
Value execute() const {
|
|
||||||
return func();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isThunk() const { return true; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ThunkFunction func;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Executor;
|
|
||||||
|
|
||||||
class Interpreter {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Environment> environment;
|
|
||||||
bool isInteractive;
|
|
||||||
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
|
|
||||||
std::vector<std::shared_ptr<Function>> functions;
|
|
||||||
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
|
|
||||||
ErrorReporter* errorReporter;
|
|
||||||
bool inThunkExecution = false;
|
|
||||||
|
|
||||||
// Automatic cleanup tracking
|
|
||||||
int functionCreationCount = 0;
|
|
||||||
int thunkCreationCount = 0;
|
|
||||||
static const int CLEANUP_THRESHOLD = 1000000;
|
|
||||||
|
|
||||||
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
|
||||||
std::unique_ptr<Evaluator> evaluator;
|
|
||||||
std::unique_ptr<Executor> executor;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Interpreter(bool isInteractive);
|
|
||||||
virtual ~Interpreter();
|
|
||||||
|
|
||||||
// Public interface for main
|
|
||||||
void interpret(std::vector<std::shared_ptr<Stmt>> statements);
|
|
||||||
void setErrorReporter(ErrorReporter* reporter);
|
|
||||||
|
|
||||||
// Methods needed by Evaluator
|
|
||||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
|
||||||
Value evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression); // Inline TCO for performance
|
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
|
||||||
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
|
||||||
bool isTruthy(Value object);
|
|
||||||
bool isEqual(Value a, Value b);
|
|
||||||
std::string stringify(Value object);
|
|
||||||
bool isInteractiveMode() const;
|
|
||||||
std::shared_ptr<Environment> getEnvironment();
|
|
||||||
void setEnvironment(std::shared_ptr<Environment> env);
|
|
||||||
void addThunk(std::shared_ptr<Thunk> thunk);
|
|
||||||
void addFunction(std::shared_ptr<Function> function);
|
|
||||||
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
|
|
||||||
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
|
||||||
void cleanupUnusedFunctions();
|
|
||||||
void cleanupUnusedThunks();
|
|
||||||
void forceCleanup();
|
|
||||||
|
|
||||||
// Function creation count management
|
|
||||||
void incrementFunctionCreationCount();
|
|
||||||
int getFunctionCreationCount() const;
|
|
||||||
void resetFunctionCreationCount();
|
|
||||||
int getCleanupThreshold() const;
|
|
||||||
|
|
||||||
// Public access for Evaluator
|
|
||||||
bool& getInThunkExecutionRef() { return inThunkExecution; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
|
|
||||||
void addStdLibFunctions();
|
|
||||||
Value runTrampoline(Value initialResult);
|
|
||||||
};
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Value.h"
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// Forward declarations from Value.h
|
|
||||||
struct Function;
|
|
||||||
struct BuiltinFunction;
|
|
||||||
struct Thunk;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RuntimeDiagnostics - Utility functions for runtime operations
|
|
||||||
*
|
|
||||||
* This class handles value conversion, equality checking, string representation,
|
|
||||||
* and other diagnostic utilities that don't belong in core evaluation logic.
|
|
||||||
*/
|
|
||||||
class RuntimeDiagnostics {
|
|
||||||
public:
|
|
||||||
RuntimeDiagnostics() = default;
|
|
||||||
|
|
||||||
// Value utility functions
|
|
||||||
bool isTruthy(Value object);
|
|
||||||
bool isEqual(Value a, Value b);
|
|
||||||
std::string stringify(Value object);
|
|
||||||
|
|
||||||
// Memory management utilities
|
|
||||||
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
|
|
||||||
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions);
|
|
||||||
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
|
|
||||||
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
|
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks);
|
|
||||||
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
|
||||||
std::vector<std::shared_ptr<Function>>& functions,
|
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Helper methods for stringify
|
|
||||||
std::string formatNumber(double value);
|
|
||||||
std::string formatArray(const std::vector<Value>& arr);
|
|
||||||
std::string formatDict(const std::unordered_map<std::string, Value>& dict);
|
|
||||||
};
|
|
||||||
@ -1,428 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "helperFunctions/ErrorUtils.h"
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include <cmath>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
struct Environment;
|
|
||||||
struct Function;
|
|
||||||
struct BuiltinFunction;
|
|
||||||
struct Thunk;
|
|
||||||
|
|
||||||
// Type tags for the Value union
|
|
||||||
enum ValueType {
|
|
||||||
VAL_NONE,
|
|
||||||
VAL_NUMBER,
|
|
||||||
VAL_BOOLEAN,
|
|
||||||
VAL_STRING,
|
|
||||||
VAL_FUNCTION,
|
|
||||||
VAL_BUILTIN_FUNCTION,
|
|
||||||
VAL_THUNK,
|
|
||||||
VAL_ARRAY,
|
|
||||||
VAL_DICT
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tagged value system (like Lua) - no heap allocation for simple values
|
|
||||||
struct Value {
|
|
||||||
union {
|
|
||||||
double number;
|
|
||||||
bool boolean;
|
|
||||||
};
|
|
||||||
ValueType type;
|
|
||||||
std::string string_value; // Store strings outside the union for safety
|
|
||||||
std::shared_ptr<std::vector<Value> > array_value; // Store arrays as shared_ptr for mutability
|
|
||||||
std::shared_ptr<std::unordered_map<std::string, Value> > dict_value; // Store dictionaries as shared_ptr for mutability
|
|
||||||
|
|
||||||
// Store functions as shared_ptr for proper reference counting
|
|
||||||
std::shared_ptr<Function> function;
|
|
||||||
std::shared_ptr<BuiltinFunction> builtin_function;
|
|
||||||
std::shared_ptr<Thunk> thunk;
|
|
||||||
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
|
||||||
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
|
||||||
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
|
|
||||||
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
|
||||||
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
|
||||||
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
|
||||||
Value(std::shared_ptr<Function> f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
|
||||||
Value(std::shared_ptr<BuiltinFunction> bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
|
||||||
Value(std::shared_ptr<Thunk> t) : thunk(t), type(ValueType::VAL_THUNK) {}
|
|
||||||
Value(const std::vector<Value>& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(arr)) {}
|
|
||||||
Value(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(arr))) {}
|
|
||||||
Value(const std::unordered_map<std::string, Value>& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(dict)) {}
|
|
||||||
Value(std::unordered_map<std::string, Value>&& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(std::move(dict))) {}
|
|
||||||
|
|
||||||
// Destructor to clean up functions and thunks
|
|
||||||
~Value() {
|
|
||||||
// Functions and thunks are managed by the Interpreter, so we don't delete them
|
|
||||||
// Arrays and dictionaries are managed by shared_ptr, so they clean up automatically
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Move constructor
|
|
||||||
Value(Value&& other) noexcept
|
|
||||||
: type(other.type), string_value(std::move(other.string_value)), array_value(std::move(other.array_value)), dict_value(std::move(other.dict_value)),
|
|
||||||
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)) {
|
|
||||||
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
|
|
||||||
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
|
|
||||||
number = other.number; // Copy the union
|
|
||||||
}
|
|
||||||
other.type = ValueType::VAL_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move assignment
|
|
||||||
Value& operator=(Value&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
type = other.type;
|
|
||||||
if (type == ValueType::VAL_STRING) {
|
|
||||||
string_value = std::move(other.string_value);
|
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
|
||||||
array_value = std::move(other.array_value);
|
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
|
||||||
dict_value = std::move(other.dict_value);
|
|
||||||
} else if (type == ValueType::VAL_FUNCTION) {
|
|
||||||
function = std::move(other.function);
|
|
||||||
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
|
||||||
builtin_function = std::move(other.builtin_function);
|
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
|
||||||
thunk = std::move(other.thunk);
|
|
||||||
} else {
|
|
||||||
number = other.number;
|
|
||||||
}
|
|
||||||
|
|
||||||
other.type = ValueType::VAL_NONE;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy constructor (only when needed)
|
|
||||||
Value(const Value& other) : type(other.type) {
|
|
||||||
if (type == ValueType::VAL_STRING) {
|
|
||||||
string_value = other.string_value;
|
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
|
||||||
array_value = other.array_value; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
|
||||||
dict_value = other.dict_value; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_FUNCTION) {
|
|
||||||
function = other.function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
|
||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
|
||||||
} else {
|
|
||||||
number = other.number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy assignment (only when needed)
|
|
||||||
Value& operator=(const Value& other) {
|
|
||||||
if (this != &other) {
|
|
||||||
// First, clear all old shared_ptr members to release references
|
|
||||||
array_value.reset();
|
|
||||||
dict_value.reset();
|
|
||||||
function.reset();
|
|
||||||
builtin_function.reset();
|
|
||||||
thunk.reset();
|
|
||||||
|
|
||||||
// Then set the new type and value
|
|
||||||
type = other.type;
|
|
||||||
if (type == ValueType::VAL_STRING) {
|
|
||||||
string_value = other.string_value;
|
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
|
||||||
array_value = other.array_value; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
|
||||||
dict_value = other.dict_value;
|
|
||||||
} else if (type == ValueType::VAL_FUNCTION) {
|
|
||||||
function = other.function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
|
||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
|
||||||
} else {
|
|
||||||
number = other.number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type checking (fast, no dynamic casting) - inline for performance
|
|
||||||
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
|
|
||||||
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
|
|
||||||
inline bool isString() const { return type == ValueType::VAL_STRING; }
|
|
||||||
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
|
|
||||||
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
|
||||||
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
|
|
||||||
inline bool isDict() const { return type == ValueType::VAL_DICT; }
|
|
||||||
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
|
|
||||||
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
|
||||||
|
|
||||||
// Get type name as string for error messages
|
|
||||||
inline std::string getType() const {
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return "none";
|
|
||||||
case ValueType::VAL_NUMBER: return "number";
|
|
||||||
case ValueType::VAL_BOOLEAN: return "boolean";
|
|
||||||
case ValueType::VAL_STRING: return "string";
|
|
||||||
case ValueType::VAL_FUNCTION: return "function";
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return "builtin_function";
|
|
||||||
case ValueType::VAL_THUNK: return "thunk";
|
|
||||||
case ValueType::VAL_ARRAY: return "array";
|
|
||||||
case ValueType::VAL_DICT: return "dict";
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Value extraction (safe, with type checking) - inline for performance
|
|
||||||
inline double asNumber() const { return isNumber() ? number : 0.0; }
|
|
||||||
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
|
|
||||||
inline const std::string& asString() const { return string_value; }
|
|
||||||
inline const std::vector<Value>& asArray() const {
|
|
||||||
return *array_value;
|
|
||||||
}
|
|
||||||
inline std::vector<Value>& asArray() {
|
|
||||||
return *array_value;
|
|
||||||
}
|
|
||||||
inline const std::unordered_map<std::string, Value>& asDict() const {
|
|
||||||
return *dict_value;
|
|
||||||
}
|
|
||||||
inline std::unordered_map<std::string, Value>& asDict() {
|
|
||||||
return *dict_value;
|
|
||||||
}
|
|
||||||
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
|
||||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
|
||||||
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
|
||||||
|
|
||||||
// Truthiness check - inline for performance
|
|
||||||
inline bool isTruthy() const {
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return false;
|
|
||||||
case ValueType::VAL_BOOLEAN: return boolean;
|
|
||||||
case ValueType::VAL_NUMBER: return number != 0.0;
|
|
||||||
case ValueType::VAL_STRING: return !string_value.empty();
|
|
||||||
case ValueType::VAL_FUNCTION: return function != nullptr;
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
|
|
||||||
case ValueType::VAL_THUNK: return thunk != nullptr;
|
|
||||||
case ValueType::VAL_ARRAY: return !array_value->empty();
|
|
||||||
case ValueType::VAL_DICT: return !dict_value->empty();
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equality comparison - inline for performance
|
|
||||||
inline bool equals(const Value& other) const {
|
|
||||||
if (type != other.type) return false;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return true;
|
|
||||||
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
|
|
||||||
case ValueType::VAL_NUMBER: return number == other.number;
|
|
||||||
case ValueType::VAL_STRING: return string_value == other.string_value;
|
|
||||||
case ValueType::VAL_FUNCTION: return function == other.function;
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
|
|
||||||
case ValueType::VAL_THUNK: return thunk == other.thunk;
|
|
||||||
case ValueType::VAL_ARRAY: {
|
|
||||||
if (array_value->size() != other.array_value->size()) return false;
|
|
||||||
for (size_t i = 0; i < array_value->size(); i++) {
|
|
||||||
if (!(*array_value)[i].equals((*other.array_value)[i])) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case ValueType::VAL_DICT: {
|
|
||||||
if (dict_value->size() != other.dict_value->size()) return false;
|
|
||||||
for (const auto& pair : *dict_value) {
|
|
||||||
auto it = other.dict_value->find(pair.first);
|
|
||||||
if (it == other.dict_value->end() || !pair.second.equals(it->second)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String representation
|
|
||||||
std::string toString() const {
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return "none";
|
|
||||||
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
|
|
||||||
case ValueType::VAL_NUMBER: {
|
|
||||||
// Format numbers like the original stringify function
|
|
||||||
if (number == std::floor(number)) {
|
|
||||||
return std::to_string(static_cast<long long>(number));
|
|
||||||
} else {
|
|
||||||
std::string str = std::to_string(number);
|
|
||||||
// Remove trailing zeros
|
|
||||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
|
||||||
if (str.back() == '.') str.pop_back();
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ValueType::VAL_STRING: return string_value;
|
|
||||||
case ValueType::VAL_FUNCTION: return "<function>";
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
|
|
||||||
case ValueType::VAL_THUNK: return "<thunk>";
|
|
||||||
case ValueType::VAL_ARRAY: {
|
|
||||||
const std::vector<Value>& arr = *array_value;
|
|
||||||
std::string result = "[";
|
|
||||||
|
|
||||||
for (size_t i = 0; i < arr.size(); i++) {
|
|
||||||
if (i > 0) result += ", ";
|
|
||||||
result += arr[i].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "]";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
case ValueType::VAL_DICT: {
|
|
||||||
const std::unordered_map<std::string, Value>& dict = *dict_value;
|
|
||||||
std::string result = "{";
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
if (!first) result += ", ";
|
|
||||||
result += "\"" + pair.first + "\": " + pair.second.toString();
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "}";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equality operator
|
|
||||||
bool operator==(const Value& other) const {
|
|
||||||
return equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const Value& other) const {
|
|
||||||
return !equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arithmetic operators
|
|
||||||
Value operator+(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(number + other.number);
|
|
||||||
}
|
|
||||||
if (isString() && other.isString()) {
|
|
||||||
return Value(string_value + other.string_value);
|
|
||||||
}
|
|
||||||
if (isString() && other.isNumber()) {
|
|
||||||
return Value(string_value + other.toString());
|
|
||||||
}
|
|
||||||
if (isNumber() && other.isString()) {
|
|
||||||
return Value(toString() + other.string_value);
|
|
||||||
}
|
|
||||||
// Handle none values by converting to string
|
|
||||||
if (isString() && other.isNone()) {
|
|
||||||
return Value(string_value + "none");
|
|
||||||
}
|
|
||||||
if (isNone() && other.isString()) {
|
|
||||||
return Value("none" + other.string_value);
|
|
||||||
}
|
|
||||||
if (isString() && !other.isString() && !other.isNumber()) {
|
|
||||||
return Value(string_value + other.toString());
|
|
||||||
}
|
|
||||||
if (!isString() && !isNumber() && other.isString()) {
|
|
||||||
return Value(toString() + other.string_value);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("+", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator-(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(number - other.number);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("-", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator*(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(number * other.number);
|
|
||||||
}
|
|
||||||
if (isString() && other.isNumber()) {
|
|
||||||
std::string result;
|
|
||||||
for (int i = 0; i < static_cast<int>(other.number); ++i) {
|
|
||||||
result += string_value;
|
|
||||||
}
|
|
||||||
return Value(result);
|
|
||||||
}
|
|
||||||
if (isNumber() && other.isString()) {
|
|
||||||
std::string result;
|
|
||||||
for (int i = 0; i < static_cast<int>(number); ++i) {
|
|
||||||
result += other.string_value;
|
|
||||||
}
|
|
||||||
return Value(result);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("*", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator/(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
if (other.number == 0) {
|
|
||||||
throw std::runtime_error("Division by zero");
|
|
||||||
}
|
|
||||||
return Value(number / other.number);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("/", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator%(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(fmod(number, other.number));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("%", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator&(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("&", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator|(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("|", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator^(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("^", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator<<(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("<<", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator>>(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError(">>", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Global constants for common values
|
|
||||||
extern const Value NONE_VALUE;
|
|
||||||
extern const Value TRUE_VALUE;
|
|
||||||
extern const Value FALSE_VALUE;
|
|
||||||
extern const Value ZERO_VALUE;
|
|
||||||
extern const Value ONE_VALUE;
|
|
||||||
@ -1,434 +0,0 @@
|
|||||||
#include "Evaluator.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "helperFunctions/HelperFunctions.h"
|
|
||||||
|
|
||||||
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
|
|
||||||
|
|
||||||
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
|
||||||
if (expr->isNull) {
|
|
||||||
return NONE_VALUE;
|
|
||||||
}
|
|
||||||
if (expr->isNumber) {
|
|
||||||
double num;
|
|
||||||
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
|
|
||||||
num = binaryStringToLong(expr->value);
|
|
||||||
} else {
|
|
||||||
num = std::stod(expr->value);
|
|
||||||
}
|
|
||||||
return Value(num);
|
|
||||||
}
|
|
||||||
if (expr->isBoolean) {
|
|
||||||
if (expr->value == "true") return TRUE_VALUE;
|
|
||||||
if (expr->value == "false") return FALSE_VALUE;
|
|
||||||
}
|
|
||||||
return Value(expr->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
|
||||||
return interpreter->evaluate(expression->expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
|
||||||
{
|
|
||||||
Value right = interpreter->evaluate(expression->right);
|
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case MINUS:
|
|
||||||
if (!right.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
return Value(-right.asNumber());
|
|
||||||
|
|
||||||
case BANG:
|
|
||||||
return Value(!interpreter->isTruthy(right));
|
|
||||||
|
|
||||||
case BIN_NOT:
|
|
||||||
if (!right.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
|
||||||
|
|
||||||
default:
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
|
|
||||||
Value left = interpreter->evaluate(expression->left);
|
|
||||||
Value right = interpreter->evaluate(expression->right);
|
|
||||||
|
|
||||||
// Handle logical operators (AND, OR) - these work with any types
|
|
||||||
if (expression->oper.type == AND) {
|
|
||||||
return interpreter->isTruthy(left) ? right : left;
|
|
||||||
}
|
|
||||||
if (expression->oper.type == OR) {
|
|
||||||
return interpreter->isTruthy(left) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle equality operators - these work with any types
|
|
||||||
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
|
|
||||||
bool equal = interpreter->isEqual(left, right);
|
|
||||||
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle comparison operators - only work with numbers
|
|
||||||
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
|
|
||||||
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
|
|
||||||
|
|
||||||
if (left.isNumber() && right.isNumber()) {
|
|
||||||
double leftNum = left.asNumber();
|
|
||||||
double rightNum = right.asNumber();
|
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case GREATER: return Value(leftNum > rightNum);
|
|
||||||
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
|
||||||
case LESS: return Value(leftNum < rightNum);
|
|
||||||
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
|
||||||
default: break; // Unreachable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error for non-number comparisons
|
|
||||||
std::string opName;
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case GREATER: opName = ">"; break;
|
|
||||||
case GREATER_EQUAL: opName = ">="; break;
|
|
||||||
case LESS: opName = "<"; break;
|
|
||||||
case LESS_EQUAL: opName = "<="; break;
|
|
||||||
default: break; // Unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle all other operators using Value's operator overloads
|
|
||||||
try {
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case PLUS: return left + right;
|
|
||||||
case MINUS: return left - right;
|
|
||||||
case STAR: return left * right;
|
|
||||||
case SLASH: return left / right;
|
|
||||||
case PERCENT: return left % right;
|
|
||||||
case BIN_AND: return left & right;
|
|
||||||
case BIN_OR: return left | right;
|
|
||||||
case BIN_XOR: return left ^ right;
|
|
||||||
case BIN_SLEFT: return left << right;
|
|
||||||
case BIN_SRIGHT: return left >> right;
|
|
||||||
default:
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
} catch (const std::runtime_error& e) {
|
|
||||||
// The Value operators provide good error messages, just add context
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
e.what(), expression->oper.lexeme);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
|
||||||
{
|
|
||||||
return interpreter->getEnvironment()->get(expression->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
|
||||||
// Get the current value of the operand
|
|
||||||
Value currentValue = interpreter->evaluate(expression->operand);
|
|
||||||
|
|
||||||
if (!currentValue.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
|
||||||
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
double currentNum = currentValue.asNumber();
|
|
||||||
double newValue;
|
|
||||||
|
|
||||||
// Determine the operation based on the operator
|
|
||||||
if (expression->oper.type == PLUS_PLUS) {
|
|
||||||
newValue = currentNum + 1.0;
|
|
||||||
} else if (expression->oper.type == MINUS_MINUS) {
|
|
||||||
newValue = currentNum - 1.0;
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Invalid increment/decrement operator.", "");
|
|
||||||
throw std::runtime_error("Invalid increment/decrement operator.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the variable or array element
|
|
||||||
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
|
||||||
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
|
||||||
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
|
||||||
// Handle array indexing increment/decrement
|
|
||||||
Value array = interpreter->evaluate(arrayExpr->array);
|
|
||||||
Value index = interpreter->evaluate(arrayExpr->index);
|
|
||||||
|
|
||||||
if (!array.isArray()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Can only index arrays", "");
|
|
||||||
throw std::runtime_error("Can only index arrays");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!index.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Array index must be a number", "");
|
|
||||||
throw std::runtime_error("Array index must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = static_cast<int>(index.asNumber());
|
|
||||||
std::vector<Value>& arr = array.asArray();
|
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
|
||||||
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
|
|
||||||
"Runtime Error", "Array index out of bounds", "");
|
|
||||||
throw std::runtime_error("Array index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the array element
|
|
||||||
arr[idx] = Value(newValue);
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
|
||||||
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the appropriate value based on prefix/postfix
|
|
||||||
if (expression->isPrefix) {
|
|
||||||
return Value(newValue); // Prefix: return new value
|
|
||||||
} else {
|
|
||||||
return currentValue; // Postfix: return old value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
|
||||||
Value value = interpreter->evaluate(expression->value);
|
|
||||||
|
|
||||||
switch (expression->op.type) {
|
|
||||||
case PLUS_EQUAL:
|
|
||||||
case MINUS_EQUAL:
|
|
||||||
case STAR_EQUAL:
|
|
||||||
case SLASH_EQUAL:
|
|
||||||
case PERCENT_EQUAL:
|
|
||||||
case BIN_AND_EQUAL:
|
|
||||||
case BIN_OR_EQUAL:
|
|
||||||
case BIN_XOR_EQUAL:
|
|
||||||
case BIN_SLEFT_EQUAL:
|
|
||||||
case BIN_SRIGHT_EQUAL: {
|
|
||||||
Value currentValue = interpreter->getEnvironment()->get(expression->name);
|
|
||||||
|
|
||||||
// ... (rest of compound assignment logic) ...
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
interpreter->getEnvironment()->assign(expression->name, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
|
|
||||||
Value condition = interpreter->evaluate(expression->condition);
|
|
||||||
|
|
||||||
if (interpreter->isTruthy(condition)) {
|
|
||||||
return interpreter->evaluate(expression->thenExpr);
|
|
||||||
} else {
|
|
||||||
return interpreter->evaluate(expression->elseExpr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
|
||||||
Value callee = expression->callee->accept(this);
|
|
||||||
|
|
||||||
std::vector<Value> arguments;
|
|
||||||
for (const auto& argument : expression->arguments) {
|
|
||||||
arguments.push_back(argument->accept(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callee.isFunction()) {
|
|
||||||
Function* function = callee.asFunction();
|
|
||||||
|
|
||||||
// Check arity
|
|
||||||
if (arguments.size() != function->params.size()) {
|
|
||||||
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
|
||||||
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
|
|
||||||
std::to_string(arguments.size()) + ".", "");
|
|
||||||
throw std::runtime_error("Wrong number of arguments.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new environment for function call
|
|
||||||
auto environment = std::make_shared<Environment>(function->closure);
|
|
||||||
for (size_t i = 0; i < function->params.size(); i++) {
|
|
||||||
environment->define(function->params[i], arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute function body
|
|
||||||
auto previous = interpreter->getEnvironment();
|
|
||||||
interpreter->setEnvironment(environment);
|
|
||||||
|
|
||||||
ExecutionContext context;
|
|
||||||
context.isFunctionBody = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const auto& stmt : function->body) {
|
|
||||||
interpreter->execute(stmt, &context);
|
|
||||||
if (context.hasReturn) {
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
return context.returnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
return NONE_VALUE;
|
|
||||||
|
|
||||||
} else if (callee.isBuiltinFunction()) {
|
|
||||||
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
|
||||||
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
|
||||||
"Can only call functions and classes.", "");
|
|
||||||
throw std::runtime_error("Can only call functions and classes.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
|
|
||||||
std::vector<Value> elements;
|
|
||||||
|
|
||||||
for (const auto& element : expr->elements) {
|
|
||||||
elements.push_back(interpreter->evaluate(element));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
|
|
||||||
Value array = expr->array->accept(this);
|
|
||||||
Value index = expr->index->accept(this);
|
|
||||||
|
|
||||||
if (array.isArray()) {
|
|
||||||
// Handle array indexing
|
|
||||||
if (!index.isNumber()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index must be a number", "");
|
|
||||||
throw std::runtime_error("Array index must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = static_cast<int>(index.asNumber());
|
|
||||||
const std::vector<Value>& arr = array.asArray();
|
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index out of bounds", "");
|
|
||||||
throw std::runtime_error("Array index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr[idx];
|
|
||||||
|
|
||||||
} else if (array.isDict()) {
|
|
||||||
// Handle dictionary indexing
|
|
||||||
if (!index.isString()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Dictionary key must be a string", "");
|
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string key = index.asString();
|
|
||||||
const std::unordered_map<std::string, Value>& dict = array.asDict();
|
|
||||||
|
|
||||||
auto it = dict.find(key);
|
|
||||||
if (it != dict.end()) {
|
|
||||||
return it->second;
|
|
||||||
} else {
|
|
||||||
return NONE_VALUE; // Return none for missing keys
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Can only index arrays and dictionaries", "");
|
|
||||||
throw std::runtime_error("Can only index arrays and dictionaries");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
|
|
||||||
Value array = expr->array->accept(this);
|
|
||||||
Value index = expr->index->accept(this);
|
|
||||||
Value value = expr->value->accept(this);
|
|
||||||
|
|
||||||
if (array.isArray()) {
|
|
||||||
// Handle array assignment
|
|
||||||
if (!index.isNumber()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index must be a number", "");
|
|
||||||
throw std::runtime_error("Array index must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = static_cast<int>(index.asNumber());
|
|
||||||
std::vector<Value>& arr = array.asArray();
|
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index out of bounds", "");
|
|
||||||
throw std::runtime_error("Array index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
arr[idx] = value;
|
|
||||||
return value;
|
|
||||||
|
|
||||||
} else if (array.isDict()) {
|
|
||||||
// Handle dictionary assignment
|
|
||||||
if (!index.isString()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Dictionary key must be a string", "");
|
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string key = index.asString();
|
|
||||||
std::unordered_map<std::string, Value>& dict = array.asDict();
|
|
||||||
|
|
||||||
dict[key] = value;
|
|
||||||
return value;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Can only assign to array or dictionary elements", "");
|
|
||||||
throw std::runtime_error("Can only assign to array or dictionary elements");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
|
|
||||||
std::unordered_map<std::string, Value> dict;
|
|
||||||
|
|
||||||
for (const auto& pair : expr->pairs) {
|
|
||||||
Value value = interpreter->evaluate(pair.second);
|
|
||||||
dict[pair.first] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
|
||||||
std::vector<std::string> paramNames;
|
|
||||||
for (const Token& param : expression->params) {
|
|
||||||
paramNames.push_back(param.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
|
|
||||||
interpreter->addFunction(function);
|
|
||||||
return Value(function);
|
|
||||||
}
|
|
||||||
@ -1,245 +0,0 @@
|
|||||||
#include "Executor.h"
|
|
||||||
#include "Evaluator.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
|
||||||
: interpreter(interpreter), evaluator(evaluator) {}
|
|
||||||
|
|
||||||
Executor::~Executor() {}
|
|
||||||
|
|
||||||
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
|
||||||
for (const auto& statement : statements) {
|
|
||||||
execute(statement, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
|
||||||
statement->accept(this, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
|
||||||
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
|
|
||||||
interpreter->setEnvironment(env);
|
|
||||||
|
|
||||||
for (const auto& statement : statements) {
|
|
||||||
execute(statement, context);
|
|
||||||
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
|
||||||
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
|
|
||||||
executeBlock(statement->statements, newEnv, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = statement->expression->accept(evaluator);
|
|
||||||
|
|
||||||
if (interpreter->isInteractiveMode())
|
|
||||||
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = NONE_VALUE;
|
|
||||||
if (statement->initializer != nullptr) {
|
|
||||||
value = statement->initializer->accept(evaluator);
|
|
||||||
}
|
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
|
|
||||||
std::vector<std::string> paramNames;
|
|
||||||
for (const Token& param : statement->params) {
|
|
||||||
paramNames.push_back(param.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto function = std::make_shared<Function>(statement->name.lexeme,
|
|
||||||
paramNames,
|
|
||||||
statement->body,
|
|
||||||
interpreter->getEnvironment());
|
|
||||||
interpreter->addFunction(function);
|
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = NONE_VALUE;
|
|
||||||
if (statement->value != nullptr) {
|
|
||||||
value = statement->value->accept(evaluator);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context->isFunctionBody) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
|
||||||
execute(statement->thenBranch, context);
|
|
||||||
} else if (statement->elseBranch != nullptr) {
|
|
||||||
execute(statement->elseBranch, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
|
|
||||||
ExecutionContext loopContext;
|
|
||||||
if (context) {
|
|
||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
|
||||||
execute(statement->body, &loopContext);
|
|
||||||
|
|
||||||
if (loopContext.hasReturn) {
|
|
||||||
if (context) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = loopContext.returnValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
|
|
||||||
ExecutionContext loopContext;
|
|
||||||
if (context) {
|
|
||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
execute(statement->body, &loopContext);
|
|
||||||
|
|
||||||
if (loopContext.hasReturn) {
|
|
||||||
if (context) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = loopContext.returnValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (statement->initializer != nullptr) {
|
|
||||||
execute(statement->initializer, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutionContext loopContext;
|
|
||||||
if (context) {
|
|
||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
|
||||||
execute(statement->body, &loopContext);
|
|
||||||
|
|
||||||
if (loopContext.hasReturn) {
|
|
||||||
if (context) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = loopContext.returnValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
if (statement->increment != nullptr) {
|
|
||||||
statement->increment->accept(evaluator);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statement->increment != nullptr) {
|
|
||||||
statement->increment->accept(evaluator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (context) {
|
|
||||||
context->shouldBreak = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (context) {
|
|
||||||
context->shouldContinue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = statement->value->accept(evaluator);
|
|
||||||
|
|
||||||
if (statement->op.type == EQUAL) {
|
|
||||||
interpreter->getEnvironment()->assign(statement->name, value);
|
|
||||||
} else {
|
|
||||||
// Handle compound assignment operators
|
|
||||||
Value currentValue = interpreter->getEnvironment()->get(statement->name);
|
|
||||||
Value newValue;
|
|
||||||
|
|
||||||
switch (statement->op.type) {
|
|
||||||
case PLUS_EQUAL:
|
|
||||||
newValue = currentValue + value;
|
|
||||||
break;
|
|
||||||
case MINUS_EQUAL:
|
|
||||||
newValue = currentValue - value;
|
|
||||||
break;
|
|
||||||
case STAR_EQUAL:
|
|
||||||
newValue = currentValue * value;
|
|
||||||
break;
|
|
||||||
case SLASH_EQUAL:
|
|
||||||
newValue = currentValue / value;
|
|
||||||
break;
|
|
||||||
case PERCENT_EQUAL:
|
|
||||||
newValue = currentValue % value;
|
|
||||||
break;
|
|
||||||
case BIN_AND_EQUAL:
|
|
||||||
newValue = currentValue & value;
|
|
||||||
break;
|
|
||||||
case BIN_OR_EQUAL:
|
|
||||||
newValue = currentValue | value;
|
|
||||||
break;
|
|
||||||
case BIN_XOR_EQUAL:
|
|
||||||
newValue = currentValue ^ value;
|
|
||||||
break;
|
|
||||||
case BIN_SLEFT_EQUAL:
|
|
||||||
newValue = currentValue << value;
|
|
||||||
break;
|
|
||||||
case BIN_SRIGHT_EQUAL:
|
|
||||||
newValue = currentValue >> value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
|
|
||||||
"Unknown assignment operator: " + statement->op.lexeme, "");
|
|
||||||
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->getEnvironment()->assign(statement->name, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "Expression.h"
|
|
||||||
@ -1,524 +0,0 @@
|
|||||||
#include "Evaluator.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "helperFunctions/HelperFunctions.h"
|
|
||||||
|
|
||||||
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
|
|
||||||
|
|
||||||
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
|
||||||
if (expr->isNull) {
|
|
||||||
return NONE_VALUE;
|
|
||||||
}
|
|
||||||
if (expr->isNumber) {
|
|
||||||
double num;
|
|
||||||
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
|
|
||||||
num = binaryStringToLong(expr->value);
|
|
||||||
} else {
|
|
||||||
num = std::stod(expr->value);
|
|
||||||
}
|
|
||||||
return Value(num);
|
|
||||||
}
|
|
||||||
if (expr->isBoolean) {
|
|
||||||
if (expr->value == "true") return TRUE_VALUE;
|
|
||||||
if (expr->value == "false") return FALSE_VALUE;
|
|
||||||
}
|
|
||||||
return Value(expr->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
|
||||||
return interpreter->evaluate(expression->expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
|
||||||
{
|
|
||||||
Value right = interpreter->evaluate(expression->right);
|
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case MINUS:
|
|
||||||
if (!right.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
return Value(-right.asNumber());
|
|
||||||
|
|
||||||
case BANG:
|
|
||||||
return Value(!interpreter->isTruthy(right));
|
|
||||||
|
|
||||||
case BIN_NOT:
|
|
||||||
if (!right.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
|
||||||
|
|
||||||
default:
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
|
|
||||||
Value left = interpreter->evaluate(expression->left);
|
|
||||||
Value right = interpreter->evaluate(expression->right);
|
|
||||||
|
|
||||||
// Handle logical operators (AND, OR) - these work with any types
|
|
||||||
if (expression->oper.type == AND) {
|
|
||||||
return interpreter->isTruthy(left) ? right : left;
|
|
||||||
}
|
|
||||||
if (expression->oper.type == OR) {
|
|
||||||
return interpreter->isTruthy(left) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle equality operators - these work with any types
|
|
||||||
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
|
|
||||||
bool equal = interpreter->isEqual(left, right);
|
|
||||||
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle comparison operators - only work with numbers
|
|
||||||
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
|
|
||||||
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
|
|
||||||
|
|
||||||
if (left.isNumber() && right.isNumber()) {
|
|
||||||
double leftNum = left.asNumber();
|
|
||||||
double rightNum = right.asNumber();
|
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case GREATER: return Value(leftNum > rightNum);
|
|
||||||
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
|
||||||
case LESS: return Value(leftNum < rightNum);
|
|
||||||
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
|
||||||
default: break; // Unreachable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error for non-number comparisons
|
|
||||||
std::string opName;
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case GREATER: opName = ">"; break;
|
|
||||||
case GREATER_EQUAL: opName = ">="; break;
|
|
||||||
case LESS: opName = "<"; break;
|
|
||||||
case LESS_EQUAL: opName = "<="; break;
|
|
||||||
default: break; // Unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle all other operators using Value's operator overloads
|
|
||||||
try {
|
|
||||||
switch (expression->oper.type) {
|
|
||||||
case PLUS: return left + right;
|
|
||||||
case MINUS: return left - right;
|
|
||||||
case STAR: return left * right;
|
|
||||||
case SLASH: return left / right;
|
|
||||||
case PERCENT: return left % right;
|
|
||||||
case BIN_AND: return left & right;
|
|
||||||
case BIN_OR: return left | right;
|
|
||||||
case BIN_XOR: return left ^ right;
|
|
||||||
case BIN_SLEFT: return left << right;
|
|
||||||
case BIN_SRIGHT: return left >> right;
|
|
||||||
default:
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
|
||||||
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
|
||||||
}
|
|
||||||
} catch (const std::runtime_error& e) {
|
|
||||||
// The Value operators provide good error messages, just add context
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
|
||||||
e.what(), expression->oper.lexeme);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
|
||||||
{
|
|
||||||
return interpreter->getEnvironment()->get(expression->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
|
||||||
// Get the current value of the operand
|
|
||||||
Value currentValue = interpreter->evaluate(expression->operand);
|
|
||||||
|
|
||||||
if (!currentValue.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
|
||||||
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
double currentNum = currentValue.asNumber();
|
|
||||||
double newValue;
|
|
||||||
|
|
||||||
// Determine the operation based on the operator
|
|
||||||
if (expression->oper.type == PLUS_PLUS) {
|
|
||||||
newValue = currentNum + 1.0;
|
|
||||||
} else if (expression->oper.type == MINUS_MINUS) {
|
|
||||||
newValue = currentNum - 1.0;
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Invalid increment/decrement operator.", "");
|
|
||||||
throw std::runtime_error("Invalid increment/decrement operator.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the variable or array element
|
|
||||||
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
|
||||||
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
|
||||||
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
|
||||||
// Handle array indexing increment/decrement
|
|
||||||
Value array = interpreter->evaluate(arrayExpr->array);
|
|
||||||
Value index = interpreter->evaluate(arrayExpr->index);
|
|
||||||
|
|
||||||
if (!array.isArray()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Can only index arrays", "");
|
|
||||||
throw std::runtime_error("Can only index arrays");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!index.isNumber()) {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Array index must be a number", "");
|
|
||||||
throw std::runtime_error("Array index must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = static_cast<int>(index.asNumber());
|
|
||||||
std::vector<Value>& arr = array.asArray();
|
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
|
||||||
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
|
|
||||||
"Runtime Error", "Array index out of bounds", "");
|
|
||||||
throw std::runtime_error("Array index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the array element
|
|
||||||
arr[idx] = Value(newValue);
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expression->oper.line, expression->oper.column,
|
|
||||||
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
|
||||||
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the appropriate value based on prefix/postfix
|
|
||||||
if (expression->isPrefix) {
|
|
||||||
return Value(newValue); // Prefix: return new value
|
|
||||||
} else {
|
|
||||||
return currentValue; // Postfix: return old value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
|
||||||
Value value = interpreter->evaluate(expression->value);
|
|
||||||
|
|
||||||
if (expression->op.type == EQUAL) {
|
|
||||||
try {
|
|
||||||
// Check if the variable existed and whether it held a collection
|
|
||||||
bool existed = false;
|
|
||||||
bool wasCollection = false;
|
|
||||||
try {
|
|
||||||
Value oldValue = interpreter->getEnvironment()->get(expression->name);
|
|
||||||
existed = true;
|
|
||||||
wasCollection = oldValue.isArray() || oldValue.isDict();
|
|
||||||
} catch (...) {
|
|
||||||
existed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign first to release references held by the old values
|
|
||||||
interpreter->getEnvironment()->assign(expression->name, value);
|
|
||||||
|
|
||||||
// Now that the old values are released, perform cleanup on any reassignment
|
|
||||||
interpreter->forceCleanup();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error during assignment: " << e.what() << std::endl;
|
|
||||||
throw; // Re-throw to see the full stack trace
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle compound assignment operators
|
|
||||||
Value currentValue = interpreter->getEnvironment()->get(expression->name);
|
|
||||||
Value newValue;
|
|
||||||
|
|
||||||
switch (expression->op.type) {
|
|
||||||
case PLUS_EQUAL:
|
|
||||||
newValue = currentValue + value;
|
|
||||||
break;
|
|
||||||
case MINUS_EQUAL:
|
|
||||||
newValue = currentValue - value;
|
|
||||||
break;
|
|
||||||
case STAR_EQUAL:
|
|
||||||
newValue = currentValue * value;
|
|
||||||
break;
|
|
||||||
case SLASH_EQUAL:
|
|
||||||
newValue = currentValue / value;
|
|
||||||
break;
|
|
||||||
case PERCENT_EQUAL:
|
|
||||||
newValue = currentValue % value;
|
|
||||||
break;
|
|
||||||
case BIN_AND_EQUAL:
|
|
||||||
newValue = currentValue & value;
|
|
||||||
break;
|
|
||||||
case BIN_OR_EQUAL:
|
|
||||||
newValue = currentValue | value;
|
|
||||||
break;
|
|
||||||
case BIN_XOR_EQUAL:
|
|
||||||
newValue = currentValue ^ value;
|
|
||||||
break;
|
|
||||||
case BIN_SLEFT_EQUAL:
|
|
||||||
newValue = currentValue << value;
|
|
||||||
break;
|
|
||||||
case BIN_SRIGHT_EQUAL:
|
|
||||||
newValue = currentValue >> value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
interpreter->reportError(expression->op.line, expression->op.column, "Runtime Error",
|
|
||||||
"Unknown assignment operator: " + expression->op.lexeme, "");
|
|
||||||
throw std::runtime_error("Unknown assignment operator: " + expression->op.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->getEnvironment()->assign(expression->name, newValue);
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
|
|
||||||
Value condition = interpreter->evaluate(expression->condition);
|
|
||||||
|
|
||||||
if (interpreter->isTruthy(condition)) {
|
|
||||||
return interpreter->evaluate(expression->thenExpr);
|
|
||||||
} else {
|
|
||||||
return interpreter->evaluate(expression->elseExpr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
|
||||||
// Delegate to inline implementation in Interpreter for performance
|
|
||||||
return interpreter->evaluateCallExprInline(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
|
|
||||||
std::vector<Value> elements;
|
|
||||||
|
|
||||||
for (const auto& element : expr->elements) {
|
|
||||||
elements.push_back(interpreter->evaluate(element));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
|
|
||||||
Value array = expr->array->accept(this);
|
|
||||||
Value index = expr->index->accept(this);
|
|
||||||
|
|
||||||
if (array.isArray()) {
|
|
||||||
// Handle array indexing
|
|
||||||
if (!index.isNumber()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index must be a number", "");
|
|
||||||
throw std::runtime_error("Array index must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = static_cast<int>(index.asNumber());
|
|
||||||
const std::vector<Value>& arr = array.asArray();
|
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index out of bounds", "");
|
|
||||||
throw std::runtime_error("Array index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr[idx];
|
|
||||||
|
|
||||||
} else if (array.isDict()) {
|
|
||||||
// Handle dictionary indexing
|
|
||||||
if (!index.isString()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Dictionary key must be a string", "");
|
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string key = index.asString();
|
|
||||||
const std::unordered_map<std::string, Value>& dict = array.asDict();
|
|
||||||
|
|
||||||
auto it = dict.find(key);
|
|
||||||
if (it != dict.end()) {
|
|
||||||
return it->second;
|
|
||||||
} else {
|
|
||||||
return NONE_VALUE; // Return none for missing keys
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Can only index arrays and dictionaries", "");
|
|
||||||
throw std::runtime_error("Can only index arrays and dictionaries");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
|
||||||
Value object = expr->object->accept(this);
|
|
||||||
std::string propertyName = expr->name.lexeme;
|
|
||||||
|
|
||||||
if (object.isDict()) {
|
|
||||||
return getDictProperty(object, propertyName);
|
|
||||||
} else if (object.isArray()) {
|
|
||||||
return getArrayProperty(object, propertyName);
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
|
||||||
"Cannot access property '" + propertyName + "' on this type", "");
|
|
||||||
throw std::runtime_error("Cannot access property '" + propertyName + "' on this type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
|
|
||||||
Value array = expr->array->accept(this);
|
|
||||||
Value index = expr->index->accept(this);
|
|
||||||
Value value = expr->value->accept(this);
|
|
||||||
|
|
||||||
if (array.isArray()) {
|
|
||||||
// Handle array assignment
|
|
||||||
if (!index.isNumber()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index must be a number", "");
|
|
||||||
throw std::runtime_error("Array index must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = static_cast<int>(index.asNumber());
|
|
||||||
std::vector<Value>& arr = array.asArray();
|
|
||||||
|
|
||||||
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Array index out of bounds", "");
|
|
||||||
throw std::runtime_error("Array index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
arr[idx] = value;
|
|
||||||
return value;
|
|
||||||
|
|
||||||
} else if (array.isDict()) {
|
|
||||||
// Handle dictionary assignment
|
|
||||||
if (!index.isString()) {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Dictionary key must be a string", "");
|
|
||||||
throw std::runtime_error("Dictionary key must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string key = index.asString();
|
|
||||||
std::unordered_map<std::string, Value>& dict = array.asDict();
|
|
||||||
|
|
||||||
dict[key] = value;
|
|
||||||
return value;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
|
||||||
"Can only assign to array or dictionary elements", "");
|
|
||||||
throw std::runtime_error("Can only assign to array or dictionary elements");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
|
|
||||||
std::unordered_map<std::string, Value> dict;
|
|
||||||
|
|
||||||
for (const auto& pair : expr->pairs) {
|
|
||||||
Value value = interpreter->evaluate(pair.second);
|
|
||||||
dict[pair.first] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExpr>& expr) {
|
|
||||||
Value object = expr->object->accept(this);
|
|
||||||
Value value = expr->value->accept(this);
|
|
||||||
std::string propertyName = expr->name.lexeme;
|
|
||||||
|
|
||||||
if (object.isDict()) {
|
|
||||||
// Modify the dictionary in place
|
|
||||||
std::unordered_map<std::string, Value>& dict = object.asDict();
|
|
||||||
dict[propertyName] = value;
|
|
||||||
return value; // Return the assigned value
|
|
||||||
} else {
|
|
||||||
interpreter->reportError(expr->name.line, expr->name.column, "Runtime Error",
|
|
||||||
"Cannot assign property '" + propertyName + "' on non-object", "");
|
|
||||||
throw std::runtime_error("Cannot assign property '" + propertyName + "' on non-object");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
|
||||||
std::vector<std::string> paramNames;
|
|
||||||
for (const Token& param : expression->params) {
|
|
||||||
paramNames.push_back(param.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture a snapshot of the current environment so loop vars like 'i' are frozen per iteration
|
|
||||||
auto closureEnv = std::make_shared<Environment>(*interpreter->getEnvironment());
|
|
||||||
closureEnv->pruneForClosureCapture();
|
|
||||||
|
|
||||||
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
|
|
||||||
return Value(function);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::getArrayProperty(const Value& arrayValue, const std::string& propertyName) {
|
|
||||||
const std::vector<Value>& arr = arrayValue.asArray();
|
|
||||||
|
|
||||||
// Create builtin array properties as an actual dictionary
|
|
||||||
std::unordered_map<std::string, Value> arrayProperties;
|
|
||||||
arrayProperties["length"] = Value(static_cast<double>(arr.size()));
|
|
||||||
arrayProperties["empty"] = Value(arr.empty());
|
|
||||||
|
|
||||||
if (!arr.empty()) {
|
|
||||||
arrayProperties["first"] = arr[0];
|
|
||||||
arrayProperties["last"] = arr[arr.size() - 1];
|
|
||||||
} else {
|
|
||||||
arrayProperties["first"] = NONE_VALUE;
|
|
||||||
arrayProperties["last"] = NONE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up the requested property
|
|
||||||
auto it = arrayProperties.find(propertyName);
|
|
||||||
if (it != arrayProperties.end()) {
|
|
||||||
return it->second;
|
|
||||||
} else {
|
|
||||||
return NONE_VALUE; // Unknown property
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Evaluator::getDictProperty(const Value& dictValue, const std::string& propertyName) {
|
|
||||||
const std::unordered_map<std::string, Value>& dict = dictValue.asDict();
|
|
||||||
|
|
||||||
// First check if it's a user-defined property
|
|
||||||
auto userProp = dict.find(propertyName);
|
|
||||||
if (userProp != dict.end()) {
|
|
||||||
return userProp->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not found, check for builtin dictionary properties
|
|
||||||
std::unordered_map<std::string, Value> dictProperties;
|
|
||||||
dictProperties["length"] = Value(static_cast<double>(dict.size()));
|
|
||||||
dictProperties["empty"] = Value(dict.empty());
|
|
||||||
|
|
||||||
// Create keys array
|
|
||||||
std::vector<Value> keysArray;
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
keysArray.push_back(Value(pair.first));
|
|
||||||
}
|
|
||||||
dictProperties["keys"] = Value(keysArray);
|
|
||||||
|
|
||||||
// Create values array
|
|
||||||
std::vector<Value> valuesArray;
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
valuesArray.push_back(pair.second);
|
|
||||||
}
|
|
||||||
dictProperties["values"] = Value(valuesArray);
|
|
||||||
|
|
||||||
auto builtinProp = dictProperties.find(propertyName);
|
|
||||||
if (builtinProp != dictProperties.end()) {
|
|
||||||
return builtinProp->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Property not found
|
|
||||||
return NONE_VALUE;
|
|
||||||
}
|
|
||||||
@ -1,259 +0,0 @@
|
|||||||
#include "Executor.h"
|
|
||||||
#include "Evaluator.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
|
||||||
: interpreter(interpreter), evaluator(evaluator) {}
|
|
||||||
|
|
||||||
Executor::~Executor() {}
|
|
||||||
|
|
||||||
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
|
||||||
for (const auto& statement : statements) {
|
|
||||||
execute(statement, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
|
||||||
statement->accept(this, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
|
||||||
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
|
|
||||||
interpreter->setEnvironment(env);
|
|
||||||
|
|
||||||
for (const auto& statement : statements) {
|
|
||||||
execute(statement, context);
|
|
||||||
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
interpreter->setEnvironment(previous);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
|
||||||
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
|
|
||||||
executeBlock(statement->statements, newEnv, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = statement->expression->accept(evaluator);
|
|
||||||
|
|
||||||
if (interpreter->isInteractiveMode())
|
|
||||||
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = NONE_VALUE;
|
|
||||||
if (statement->initializer != nullptr) {
|
|
||||||
value = statement->initializer->accept(evaluator);
|
|
||||||
}
|
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
|
|
||||||
std::vector<std::string> paramNames;
|
|
||||||
for (const Token& param : statement->params) {
|
|
||||||
paramNames.push_back(param.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto function = std::make_shared<Function>(statement->name.lexeme,
|
|
||||||
paramNames,
|
|
||||||
statement->body,
|
|
||||||
interpreter->getEnvironment());
|
|
||||||
interpreter->addFunction(function);
|
|
||||||
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
|
||||||
Value value = NONE_VALUE;
|
|
||||||
if (statement->value != nullptr) {
|
|
||||||
value = statement->value->accept(evaluator);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context->isFunctionBody) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
|
||||||
execute(statement->thenBranch, context);
|
|
||||||
} else if (statement->elseBranch != nullptr) {
|
|
||||||
execute(statement->elseBranch, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
|
|
||||||
ExecutionContext loopContext;
|
|
||||||
if (context) {
|
|
||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
|
||||||
execute(statement->body, &loopContext);
|
|
||||||
|
|
||||||
if (loopContext.hasReturn) {
|
|
||||||
if (context) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = loopContext.returnValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
|
|
||||||
ExecutionContext loopContext;
|
|
||||||
if (context) {
|
|
||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
execute(statement->body, &loopContext);
|
|
||||||
|
|
||||||
if (loopContext.hasReturn) {
|
|
||||||
if (context) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = loopContext.returnValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (statement->initializer != nullptr) {
|
|
||||||
execute(statement->initializer, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutionContext loopContext;
|
|
||||||
if (context) {
|
|
||||||
loopContext.isFunctionBody = context->isFunctionBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
|
||||||
execute(statement->body, &loopContext);
|
|
||||||
|
|
||||||
if (loopContext.hasReturn) {
|
|
||||||
if (context) {
|
|
||||||
context->hasReturn = true;
|
|
||||||
context->returnValue = loopContext.returnValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldBreak) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loopContext.shouldContinue) {
|
|
||||||
loopContext.shouldContinue = false;
|
|
||||||
if (statement->increment != nullptr) {
|
|
||||||
statement->increment->accept(evaluator);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statement->increment != nullptr) {
|
|
||||||
statement->increment->accept(evaluator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (context) {
|
|
||||||
context->shouldBreak = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
|
|
||||||
if (context) {
|
|
||||||
context->shouldContinue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
|
||||||
try {
|
|
||||||
Value value = statement->value->accept(evaluator);
|
|
||||||
|
|
||||||
if (statement->op.type == EQUAL) {
|
|
||||||
try {
|
|
||||||
// Assign first to release references held by the old value
|
|
||||||
interpreter->getEnvironment()->assign(statement->name, value);
|
|
||||||
|
|
||||||
// Clean up on any reassignment, regardless of old/new type
|
|
||||||
interpreter->forceCleanup();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error during assignment: " << e.what() << std::endl;
|
|
||||||
throw; // Re-throw to see the full stack trace
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle compound assignment operators
|
|
||||||
Value currentValue = interpreter->getEnvironment()->get(statement->name);
|
|
||||||
Value newValue;
|
|
||||||
|
|
||||||
switch (statement->op.type) {
|
|
||||||
case PLUS_EQUAL:
|
|
||||||
newValue = currentValue + value;
|
|
||||||
break;
|
|
||||||
case MINUS_EQUAL:
|
|
||||||
newValue = currentValue - value;
|
|
||||||
break;
|
|
||||||
case STAR_EQUAL:
|
|
||||||
newValue = currentValue * value;
|
|
||||||
break;
|
|
||||||
case SLASH_EQUAL:
|
|
||||||
newValue = currentValue / value;
|
|
||||||
break;
|
|
||||||
case PERCENT_EQUAL:
|
|
||||||
newValue = currentValue % value;
|
|
||||||
break;
|
|
||||||
case BIN_AND_EQUAL:
|
|
||||||
newValue = currentValue & value;
|
|
||||||
break;
|
|
||||||
case BIN_OR_EQUAL:
|
|
||||||
newValue = currentValue | value;
|
|
||||||
break;
|
|
||||||
case BIN_XOR_EQUAL:
|
|
||||||
newValue = currentValue ^ value;
|
|
||||||
break;
|
|
||||||
case BIN_SLEFT_EQUAL:
|
|
||||||
newValue = currentValue << value;
|
|
||||||
break;
|
|
||||||
case BIN_SRIGHT_EQUAL:
|
|
||||||
newValue = currentValue >> value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
|
|
||||||
"Unknown assignment operator: " + statement->op.lexeme, "");
|
|
||||||
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
interpreter->getEnvironment()->assign(statement->name, newValue);
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error in visitAssignStmt: " << e.what() << std::endl;
|
|
||||||
throw; // Re-throw to see the full stack trace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,232 +0,0 @@
|
|||||||
#include "Interpreter.h"
|
|
||||||
#include "Evaluator.h"
|
|
||||||
#include "Executor.h"
|
|
||||||
#include "BobStdLib.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
Interpreter::Interpreter(bool isInteractive)
|
|
||||||
: isInteractive(isInteractive), errorReporter(nullptr) {
|
|
||||||
evaluator = std::make_unique<Evaluator>(this);
|
|
||||||
executor = std::make_unique<Executor>(this, evaluator.get());
|
|
||||||
environment = std::make_shared<Environment>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Interpreter::~Interpreter() = default;
|
|
||||||
|
|
||||||
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
|
|
||||||
executor->interpret(statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
|
||||||
statement->accept(executor.get(), context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
|
||||||
executor->executeBlock(statements, env, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
|
||||||
Value result = expr->accept(evaluator.get());
|
|
||||||
if (inThunkExecution) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return runTrampoline(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Interpreter::runTrampoline(Value initialResult) {
|
|
||||||
Value current = initialResult;
|
|
||||||
while (current.isThunk()) {
|
|
||||||
current = current.asThunk()->execute();
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Interpreter::isTruthy(Value object) {
|
|
||||||
return diagnostics.isTruthy(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Interpreter::isEqual(Value a, Value b) {
|
|
||||||
return diagnostics.isEqual(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Interpreter::stringify(Value object) {
|
|
||||||
return diagnostics.stringify(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addStdLibFunctions() {
|
|
||||||
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
|
||||||
builtinFunctions.push_back(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
|
|
||||||
thunks.push_back(thunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::addFunction(std::shared_ptr<Function> function) {
|
|
||||||
functions.push_back(function);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::setErrorReporter(ErrorReporter* reporter) {
|
|
||||||
errorReporter = reporter;
|
|
||||||
if (environment) {
|
|
||||||
environment->setErrorReporter(reporter);
|
|
||||||
}
|
|
||||||
addStdLibFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Interpreter::isInteractiveMode() const {
|
|
||||||
return isInteractive;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Environment> Interpreter::getEnvironment() {
|
|
||||||
return environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
|
|
||||||
environment = env;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, errorType, message, lexeme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::cleanupUnusedFunctions() {
|
|
||||||
diagnostics.cleanupUnusedFunctions(functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::cleanupUnusedThunks() {
|
|
||||||
diagnostics.cleanupUnusedThunks(thunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::forceCleanup() {
|
|
||||||
diagnostics.forceCleanup(builtinFunctions, functions, thunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression) {
|
|
||||||
Value callee = evaluate(expression->callee); // Direct call instead of through evaluator
|
|
||||||
|
|
||||||
if (callee.isBuiltinFunction()) {
|
|
||||||
// Handle builtin functions with direct evaluation
|
|
||||||
std::vector<Value> arguments;
|
|
||||||
for (const auto& argument : expression->arguments) {
|
|
||||||
arguments.push_back(evaluate(argument)); // Direct call
|
|
||||||
}
|
|
||||||
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
|
||||||
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callee.isFunction()) {
|
|
||||||
// Provide better error message with type information (like original)
|
|
||||||
std::string errorMsg = "Can only call functions, got " + callee.getType();
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
|
||||||
errorMsg, "");
|
|
||||||
}
|
|
||||||
throw std::runtime_error(errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
Function* function = callee.asFunction();
|
|
||||||
|
|
||||||
std::vector<Value> arguments;
|
|
||||||
for (const auto& argument : expression->arguments) {
|
|
||||||
arguments.push_back(evaluate(argument)); // Direct call instead of through evaluator
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check arity (like original)
|
|
||||||
if (arguments.size() != function->params.size()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
|
||||||
"Expected " + std::to_string(function->params.size()) +
|
|
||||||
" arguments but got " + std::to_string(arguments.size()) + ".", "");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected " + std::to_string(function->params.size()) +
|
|
||||||
" arguments but got " + std::to_string(arguments.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a tail call for inline TCO
|
|
||||||
if (expression->isTailCall) {
|
|
||||||
// Create a thunk for tail call optimization - original inline version
|
|
||||||
auto thunk = std::make_shared<Thunk>([this, function, arguments]() -> Value {
|
|
||||||
// Use RAII to manage environment (exactly like original)
|
|
||||||
ScopedEnv _env(environment);
|
|
||||||
environment = std::make_shared<Environment>(function->closure);
|
|
||||||
environment->setErrorReporter(errorReporter);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < function->params.size(); i++) {
|
|
||||||
environment->define(function->params[i], arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutionContext context;
|
|
||||||
context.isFunctionBody = true;
|
|
||||||
|
|
||||||
// Use RAII to manage thunk execution flag
|
|
||||||
ScopedThunkFlag _inThunk(inThunkExecution);
|
|
||||||
|
|
||||||
// Execute function body (inline like original - direct accept for performance)
|
|
||||||
for (const auto& stmt : function->body) {
|
|
||||||
stmt->accept(executor.get(), &context); // Direct call like original
|
|
||||||
if (context.hasReturn) {
|
|
||||||
return context.returnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.returnValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store the thunk to keep it alive and return as Value (exactly like original)
|
|
||||||
thunks.push_back(thunk);
|
|
||||||
|
|
||||||
// Automatic cleanup check
|
|
||||||
thunkCreationCount++;
|
|
||||||
if (thunkCreationCount >= CLEANUP_THRESHOLD) {
|
|
||||||
cleanupUnusedThunks();
|
|
||||||
thunkCreationCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(thunk);
|
|
||||||
} else {
|
|
||||||
// Normal function call - create new environment (exactly like original)
|
|
||||||
ScopedEnv _env(environment);
|
|
||||||
environment = std::make_shared<Environment>(function->closure);
|
|
||||||
environment->setErrorReporter(errorReporter);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < function->params.size(); i++) {
|
|
||||||
environment->define(function->params[i], arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutionContext context;
|
|
||||||
context.isFunctionBody = true;
|
|
||||||
|
|
||||||
// Execute function body (exactly like original - direct accept for performance)
|
|
||||||
for (const auto& stmt : function->body) {
|
|
||||||
stmt->accept(executor.get(), &context); // Direct call like original
|
|
||||||
if (context.hasReturn) {
|
|
||||||
return context.returnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.returnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function creation count management
|
|
||||||
void Interpreter::incrementFunctionCreationCount() {
|
|
||||||
functionCreationCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Interpreter::getFunctionCreationCount() const {
|
|
||||||
return functionCreationCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::resetFunctionCreationCount() {
|
|
||||||
functionCreationCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Interpreter::getCleanupThreshold() const {
|
|
||||||
return 1000000; // Same as CLEANUP_THRESHOLD used for thunks
|
|
||||||
}
|
|
||||||
@ -1,268 +0,0 @@
|
|||||||
#include "RuntimeDiagnostics.h"
|
|
||||||
#include "Value.h"
|
|
||||||
#include "TypeWrapper.h" // For Function and BuiltinFunction definitions
|
|
||||||
#include <sstream>
|
|
||||||
#if defined(__linux__)
|
|
||||||
#include <malloc.h>
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
#include <malloc/malloc.h>
|
|
||||||
#endif
|
|
||||||
#include <iomanip>
|
|
||||||
#include <limits>
|
|
||||||
#include <cmath>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
|
|
||||||
bool RuntimeDiagnostics::isTruthy(Value object) {
|
|
||||||
if(object.isBoolean()) {
|
|
||||||
return object.asBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object.isNone()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object.isNumber()) {
|
|
||||||
return object.asNumber() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object.isString()) {
|
|
||||||
return object.asString().length() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
|
|
||||||
// Handle none comparisons first
|
|
||||||
if (a.isNone() || b.isNone()) {
|
|
||||||
return a.isNone() && b.isNone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle same type comparisons
|
|
||||||
if (a.isNumber() && b.isNumber()) {
|
|
||||||
return a.asNumber() == b.asNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isBoolean() && b.isBoolean()) {
|
|
||||||
return a.asBoolean() == b.asBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isString() && b.isString()) {
|
|
||||||
return a.asString() == b.asString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isArray() && b.isArray()) {
|
|
||||||
const std::vector<Value>& arrA = a.asArray();
|
|
||||||
const std::vector<Value>& arrB = b.asArray();
|
|
||||||
|
|
||||||
if (arrA.size() != arrB.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < arrA.size(); i++) {
|
|
||||||
if (!isEqual(arrA[i], arrB[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isFunction() && b.isFunction()) {
|
|
||||||
// Functions are equal only if they are the same object
|
|
||||||
return a.asFunction() == b.asFunction();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isBuiltinFunction() && b.isBuiltinFunction()) {
|
|
||||||
// Builtin functions are equal only if they are the same object
|
|
||||||
return a.asBuiltinFunction() == b.asBuiltinFunction();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cross-type comparisons that make sense
|
|
||||||
if (a.isNumber() && b.isBoolean()) {
|
|
||||||
// Numbers and booleans: 0 and false are equal, non-zero and true are equal
|
|
||||||
if (b.asBoolean()) {
|
|
||||||
return a.asNumber() != 0.0;
|
|
||||||
} else {
|
|
||||||
return a.asNumber() == 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isBoolean() && b.isNumber()) {
|
|
||||||
// Same as above, but reversed
|
|
||||||
if (a.asBoolean()) {
|
|
||||||
return b.asNumber() != 0.0;
|
|
||||||
} else {
|
|
||||||
return b.asNumber() == 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For all other type combinations, return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RuntimeDiagnostics::stringify(Value object) {
|
|
||||||
if(object.isNone()) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
else if(object.isNumber()) {
|
|
||||||
return formatNumber(object.asNumber());
|
|
||||||
}
|
|
||||||
else if(object.isString()) {
|
|
||||||
return object.asString();
|
|
||||||
}
|
|
||||||
else if(object.isBoolean()) {
|
|
||||||
return object.asBoolean() == 1 ? "true" : "false";
|
|
||||||
}
|
|
||||||
else if(object.isFunction()) {
|
|
||||||
return "<function " + object.asFunction()->name + ">";
|
|
||||||
}
|
|
||||||
else if(object.isBuiltinFunction()) {
|
|
||||||
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
|
|
||||||
}
|
|
||||||
else if(object.isArray()) {
|
|
||||||
return formatArray(object.asArray());
|
|
||||||
}
|
|
||||||
else if(object.isDict()) {
|
|
||||||
return formatDict(object.asDict());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw std::runtime_error("Could not convert object to string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RuntimeDiagnostics::formatNumber(double value) {
|
|
||||||
double integral = value;
|
|
||||||
double fractional = std::modf(value, &integral);
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon()) {
|
|
||||||
ss << std::fixed << std::setprecision(0) << integral;
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << value;
|
|
||||||
std::string str = ss.str();
|
|
||||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
|
||||||
if (str.back() == '.') {
|
|
||||||
str.pop_back();
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
|
|
||||||
std::string result = "[";
|
|
||||||
|
|
||||||
for (size_t i = 0; i < arr.size(); i++) {
|
|
||||||
if (i > 0) result += ", ";
|
|
||||||
result += stringify(arr[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "]";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) {
|
|
||||||
std::string result = "{";
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
if (!first) result += ", ";
|
|
||||||
result += "\"" + pair.first + "\": " + stringify(pair.second);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "}";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
|
|
||||||
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
|
|
||||||
// This is more conservative to prevent dangling pointer issues
|
|
||||||
functions.erase(
|
|
||||||
std::remove_if(functions.begin(), functions.end(),
|
|
||||||
[](const std::shared_ptr<BuiltinFunction>& func) {
|
|
||||||
return func.use_count() == 1; // Only referenced by this vector, nowhere else
|
|
||||||
}),
|
|
||||||
functions.end()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions) {
|
|
||||||
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
|
|
||||||
// This is more conservative to prevent dangling pointer issues
|
|
||||||
functions.erase(
|
|
||||||
std::remove_if(functions.begin(), functions.end(),
|
|
||||||
[](const std::shared_ptr<Function>& func) {
|
|
||||||
return func.use_count() == 1; // Only referenced by this vector, nowhere else
|
|
||||||
}),
|
|
||||||
functions.end()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks) {
|
|
||||||
// Only remove thunks that are definitely not referenced anywhere (use_count == 1)
|
|
||||||
// This is more conservative to prevent dangling pointer issues
|
|
||||||
thunks.erase(
|
|
||||||
std::remove_if(thunks.begin(), thunks.end(),
|
|
||||||
[](const std::shared_ptr<Thunk>& thunk) {
|
|
||||||
return thunk.use_count() == 1; // Only referenced by this vector, nowhere else
|
|
||||||
}),
|
|
||||||
thunks.end()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
|
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks) {
|
|
||||||
// More aggressive cleanup when breaking array references
|
|
||||||
functions.erase(
|
|
||||||
std::remove_if(functions.begin(), functions.end(),
|
|
||||||
[](const std::shared_ptr<BuiltinFunction>& func) {
|
|
||||||
return func.use_count() <= 2; // More aggressive than == 1
|
|
||||||
}),
|
|
||||||
functions.end()
|
|
||||||
);
|
|
||||||
|
|
||||||
thunks.erase(
|
|
||||||
std::remove_if(thunks.begin(), thunks.end(),
|
|
||||||
[](const std::shared_ptr<Thunk>& thunk) {
|
|
||||||
return thunk.use_count() <= 2; // More aggressive than == 1
|
|
||||||
}),
|
|
||||||
thunks.end()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
|
||||||
std::vector<std::shared_ptr<Function>>& functions,
|
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks) {
|
|
||||||
try {
|
|
||||||
// Remove functions only when they are exclusively held by the interpreter vector
|
|
||||||
functions.erase(
|
|
||||||
std::remove_if(functions.begin(), functions.end(),
|
|
||||||
[](const std::shared_ptr<Function>& func) {
|
|
||||||
return func.use_count() == 1;
|
|
||||||
}),
|
|
||||||
functions.end()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Also cleanup builtin functions and thunks
|
|
||||||
builtinFunctions.erase(
|
|
||||||
std::remove_if(builtinFunctions.begin(), builtinFunctions.end(),
|
|
||||||
[](const std::shared_ptr<BuiltinFunction>& func) {
|
|
||||||
return func.use_count() <= 1; // Only referenced by Interpreter
|
|
||||||
}),
|
|
||||||
builtinFunctions.end()
|
|
||||||
);
|
|
||||||
|
|
||||||
thunks.erase(
|
|
||||||
std::remove_if(thunks.begin(), thunks.end(),
|
|
||||||
[](const std::shared_ptr<Thunk>& thunk) {
|
|
||||||
return thunk.use_count() <= 1; // Only referenced by Interpreter
|
|
||||||
}),
|
|
||||||
thunks.end()
|
|
||||||
);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Exception in forceCleanup: " << e.what() << std::endl;
|
|
||||||
throw; // Re-throw to let the caller handle it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
#include "TypeWrapper.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
@ -1,848 +0,0 @@
|
|||||||
#include "BobStdLib.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "ErrorReporter.h"
|
|
||||||
#include "Lexer.h"
|
|
||||||
#include "Parser.h"
|
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
|
||||||
#include <ctime>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
// Platform-specific includes for memory usage
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
|
||||||
#include <mach/mach.h>
|
|
||||||
#elif defined(__linux__)
|
|
||||||
// Uses /proc/self/status, no extra includes needed
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
#include <windows.h>
|
|
||||||
#include <psapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
|
||||||
// Create a built-in toString function
|
|
||||||
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(interpreter.stringify(args[0]));
|
|
||||||
});
|
|
||||||
env->define("toString", Value(toStringFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(toStringFunc);
|
|
||||||
|
|
||||||
// Create a built-in print function
|
|
||||||
auto printFunc = std::make_shared<BuiltinFunction>("print",
|
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
// Use the interpreter's stringify function
|
|
||||||
std::cout << interpreter.stringify(args[0]) << '\n';
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
env->define("print", Value(printFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(printFunc);
|
|
||||||
|
|
||||||
// Create a built-in printRaw function (no newline, for ANSI escape sequences)
|
|
||||||
auto printRawFunc = std::make_shared<BuiltinFunction>("printRaw",
|
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
// Print without newline and flush immediately for ANSI escape sequences
|
|
||||||
std::cout << interpreter.stringify(args[0]) << std::flush;
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
env->define("printRaw", Value(printRawFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(printRawFunc);
|
|
||||||
|
|
||||||
// Create a built-in len function for arrays and strings
|
|
||||||
auto lenFunc = std::make_shared<BuiltinFunction>("len",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0].isArray()) {
|
|
||||||
return Value(static_cast<double>(args[0].asArray().size()));
|
|
||||||
} else if (args[0].isString()) {
|
|
||||||
return Value(static_cast<double>(args[0].asString().length()));
|
|
||||||
} else if (args[0].isDict()) {
|
|
||||||
return Value(static_cast<double>(args[0].asDict().size()));
|
|
||||||
} else {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"len() can only be used on arrays, strings, and dictionaries", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("len() can only be used on arrays, strings, and dictionaries");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
env->define("len", Value(lenFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(lenFunc);
|
|
||||||
|
|
||||||
// Create a built-in push function for arrays
|
|
||||||
auto pushFunc = std::make_shared<BuiltinFunction>("push",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() < 2) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected at least 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected at least 2 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isArray()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"First argument to push() must be an array", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("First argument to push() must be an array");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the array and modify it in place
|
|
||||||
std::vector<Value>& arr = args[0].asArray();
|
|
||||||
|
|
||||||
// Add all arguments except the first one (which is the array)
|
|
||||||
for (size_t i = 1; i < args.size(); i++) {
|
|
||||||
arr.push_back(args[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return args[0]; // Return the modified array
|
|
||||||
});
|
|
||||||
env->define("push", Value(pushFunc));
|
|
||||||
interpreter.addBuiltinFunction(pushFunc);
|
|
||||||
|
|
||||||
// Create a built-in pop function for arrays
|
|
||||||
auto popFunc = std::make_shared<BuiltinFunction>("pop",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isArray()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"pop() can only be used on arrays", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("pop() can only be used on arrays");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Value>& arr = args[0].asArray();
|
|
||||||
if (arr.empty()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Cannot pop from empty array", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Cannot pop from empty array");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the last element and remove it from the array
|
|
||||||
Value lastElement = arr.back();
|
|
||||||
arr.pop_back();
|
|
||||||
|
|
||||||
return lastElement; // Return the popped element
|
|
||||||
});
|
|
||||||
env->define("pop", Value(popFunc));
|
|
||||||
interpreter.addBuiltinFunction(popFunc);
|
|
||||||
|
|
||||||
// Create a built-in assert function
|
|
||||||
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1 && args.size() != 2) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple truthy check without calling interpreter.isTruthy
|
|
||||||
bool isTruthy = false;
|
|
||||||
if (args[0].isBoolean()) {
|
|
||||||
isTruthy = args[0].asBoolean();
|
|
||||||
} else if (args[0].isNone()) {
|
|
||||||
isTruthy = false;
|
|
||||||
} else {
|
|
||||||
isTruthy = true; // Numbers, strings, functions are truthy
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTruthy) {
|
|
||||||
std::string message = "Assertion failed: condition is false";
|
|
||||||
if (args.size() == 2) {
|
|
||||||
if (args[1].isString()) {
|
|
||||||
message += " - " + std::string(args[1].asString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
env->define("assert", Value(assertFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(assertFunc);
|
|
||||||
|
|
||||||
// Create a built-in time function (returns microseconds since Unix epoch)
|
|
||||||
auto timeFunc = std::make_shared<BuiltinFunction>("time",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 0) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
|
||||||
auto duration = now.time_since_epoch();
|
|
||||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
|
||||||
|
|
||||||
return Value(static_cast<double>(microseconds));
|
|
||||||
});
|
|
||||||
env->define("time", Value(timeFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(timeFunc);
|
|
||||||
|
|
||||||
// Create a built-in input function
|
|
||||||
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() > 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional prompt
|
|
||||||
if (args.size() == 1) {
|
|
||||||
std::cout << interpreter.stringify(args[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user input
|
|
||||||
std::string userInput;
|
|
||||||
std::getline(std::cin, userInput);
|
|
||||||
|
|
||||||
return Value(userInput);
|
|
||||||
});
|
|
||||||
env->define("input", Value(inputFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(inputFunc);
|
|
||||||
|
|
||||||
// Create a built-in type function
|
|
||||||
auto typeFunc = std::make_shared<BuiltinFunction>("type",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string typeName;
|
|
||||||
if (args[0].isNumber()) {
|
|
||||||
typeName = "number";
|
|
||||||
} else if (args[0].isString()) {
|
|
||||||
typeName = "string";
|
|
||||||
} else if (args[0].isBoolean()) {
|
|
||||||
typeName = "boolean";
|
|
||||||
} else if (args[0].isNone()) {
|
|
||||||
typeName = "none";
|
|
||||||
} else if (args[0].isFunction()) {
|
|
||||||
typeName = "function";
|
|
||||||
} else if (args[0].isBuiltinFunction()) {
|
|
||||||
typeName = "builtin_function";
|
|
||||||
} else if (args[0].isArray()) {
|
|
||||||
typeName = "array";
|
|
||||||
} else if (args[0].isDict()) {
|
|
||||||
typeName = "dict";
|
|
||||||
} else {
|
|
||||||
typeName = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(typeName);
|
|
||||||
});
|
|
||||||
env->define("type", Value(typeFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(typeFunc);
|
|
||||||
|
|
||||||
// Create a built-in toNumber function for string-to-number conversion
|
|
||||||
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
|
|
||||||
[](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
return NONE_VALUE; // Return none for wrong argument count
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isString()) {
|
|
||||||
return NONE_VALUE; // Return none for wrong type
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string str = args[0].asString();
|
|
||||||
|
|
||||||
// Remove leading/trailing whitespace
|
|
||||||
str.erase(0, str.find_first_not_of(" \t\n\r"));
|
|
||||||
str.erase(str.find_last_not_of(" \t\n\r") + 1);
|
|
||||||
|
|
||||||
if (str.empty()) {
|
|
||||||
return NONE_VALUE; // Return none for empty string
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
double value = std::stod(str);
|
|
||||||
return Value(value);
|
|
||||||
} catch (const std::invalid_argument&) {
|
|
||||||
return NONE_VALUE; // Return none for invalid conversion
|
|
||||||
} catch (const std::out_of_range&) {
|
|
||||||
return NONE_VALUE; // Return none for out of range
|
|
||||||
}
|
|
||||||
});
|
|
||||||
env->define("toNumber", Value(toNumberFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(toNumberFunc);
|
|
||||||
|
|
||||||
// Create a built-in toInt function for float-to-integer conversion
|
|
||||||
auto toIntFunc = std::make_shared<BuiltinFunction>("toInt",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isNumber()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"toInt() can only be used on numbers", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("toInt() can only be used on numbers");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to integer by truncating (same as | 0)
|
|
||||||
double value = args[0].asNumber();
|
|
||||||
return Value(static_cast<double>(static_cast<long long>(value)));
|
|
||||||
});
|
|
||||||
env->define("toInt", Value(toIntFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(toIntFunc);
|
|
||||||
|
|
||||||
// Create a built-in toBoolean function for explicit boolean conversion
|
|
||||||
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the same logic as isTruthy() for consistency
|
|
||||||
Value value = args[0];
|
|
||||||
|
|
||||||
if (value.isNone()) {
|
|
||||||
return Value(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isBoolean()) {
|
|
||||||
return value; // Already a boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isNumber()) {
|
|
||||||
return Value(value.asNumber() != 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isString()) {
|
|
||||||
return Value(!value.asString().empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// For any other type (functions, etc.), consider them truthy
|
|
||||||
return Value(true);
|
|
||||||
});
|
|
||||||
env->define("toBoolean", Value(toBooleanFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(toBooleanFunc);
|
|
||||||
|
|
||||||
// Create a built-in exit function to terminate the program
|
|
||||||
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
|
|
||||||
[](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
int exitCode = 0; // Default exit code
|
|
||||||
|
|
||||||
if (args.size() > 0) {
|
|
||||||
if (args[0].isNumber()) {
|
|
||||||
exitCode = static_cast<int>(args[0].asNumber());
|
|
||||||
}
|
|
||||||
// If not a number, just use default exit code 0
|
|
||||||
}
|
|
||||||
|
|
||||||
std::exit(exitCode);
|
|
||||||
});
|
|
||||||
env->define("exit", Value(exitFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(exitFunc);
|
|
||||||
|
|
||||||
// Create a built-in sleep function for animations and timing
|
|
||||||
auto sleepFunc = std::make_shared<BuiltinFunction>("sleep",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isNumber()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"sleep() argument must be a number", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("sleep() argument must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
double seconds = args[0].asNumber();
|
|
||||||
if (seconds < 0) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"sleep() argument cannot be negative", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("sleep() argument cannot be negative");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to milliseconds and sleep
|
|
||||||
int milliseconds = static_cast<int>(seconds * 1000);
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
|
||||||
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
env->define("sleep", Value(sleepFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(sleepFunc);
|
|
||||||
|
|
||||||
// Create a built-in random function
|
|
||||||
auto randomFunc = std::make_shared<BuiltinFunction>("random",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 0) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seed the random number generator if not already done
|
|
||||||
static bool seeded = false;
|
|
||||||
if (!seeded) {
|
|
||||||
srand(static_cast<unsigned int>(time(nullptr)));
|
|
||||||
seeded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(static_cast<double>(rand()) / RAND_MAX);
|
|
||||||
});
|
|
||||||
env->define("random", Value(randomFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(randomFunc);
|
|
||||||
|
|
||||||
// Create a built-in eval function (like Python's eval)
|
|
||||||
auto evalFunc = std::make_shared<BuiltinFunction>("eval",
|
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "Invalid Arguments",
|
|
||||||
"eval expects exactly 1 argument (string)", "eval");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("eval expects exactly 1 argument");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "Invalid Type",
|
|
||||||
"eval argument must be a string", "eval");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("eval argument must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string code = args[0].asString();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create a new lexer for the code string
|
|
||||||
Lexer lexer;
|
|
||||||
lexer.setErrorReporter(errorReporter);
|
|
||||||
std::vector<Token> tokens = lexer.Tokenize(code);
|
|
||||||
|
|
||||||
// Create a new parser
|
|
||||||
Parser parser(tokens);
|
|
||||||
parser.setErrorReporter(errorReporter);
|
|
||||||
std::vector<std::shared_ptr<Stmt>> statements = parser.parse();
|
|
||||||
|
|
||||||
// Execute the statements in the current environment
|
|
||||||
// Note: This runs in the current scope, so variables are shared
|
|
||||||
interpreter.interpret(statements);
|
|
||||||
|
|
||||||
// For now, return NONE_VALUE since we don't have a way to get the last expression value
|
|
||||||
// In a more sophisticated implementation, we'd track the last expression result
|
|
||||||
return NONE_VALUE;
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "Eval Error",
|
|
||||||
"Failed to evaluate code: " + std::string(e.what()), code);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("eval failed: " + std::string(e.what()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
env->define("eval", Value(evalFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(evalFunc);
|
|
||||||
|
|
||||||
// Create a built-in keys function for dictionaries
|
|
||||||
auto keysFunc = std::make_shared<BuiltinFunction>("keys",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isDict()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"keys() can only be used on dictionaries", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("keys() can only be used on dictionaries");
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
|
|
||||||
std::vector<Value> keys;
|
|
||||||
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
keys.push_back(Value(pair.first));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(keys);
|
|
||||||
});
|
|
||||||
env->define("keys", Value(keysFunc));
|
|
||||||
interpreter.addBuiltinFunction(keysFunc);
|
|
||||||
|
|
||||||
// Create a built-in values function for dictionaries
|
|
||||||
auto valuesFunc = std::make_shared<BuiltinFunction>("values",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isDict()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"values() can only be used on dictionaries", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("values() can only be used on dictionaries");
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
|
|
||||||
std::vector<Value> values;
|
|
||||||
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
values.push_back(pair.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value(values);
|
|
||||||
});
|
|
||||||
env->define("values", Value(valuesFunc));
|
|
||||||
interpreter.addBuiltinFunction(valuesFunc);
|
|
||||||
|
|
||||||
// Create a built-in has function for dictionaries
|
|
||||||
auto hasFunc = std::make_shared<BuiltinFunction>("has",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 2) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isDict()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"First argument to has() must be a dictionary", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("First argument to has() must be a dictionary");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[1].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Second argument to has() must be a string", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Second argument to has() must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_map<std::string, Value>& dict = args[0].asDict();
|
|
||||||
std::string key = args[1].asString();
|
|
||||||
|
|
||||||
return Value(dict.find(key) != dict.end());
|
|
||||||
});
|
|
||||||
env->define("has", Value(hasFunc));
|
|
||||||
interpreter.addBuiltinFunction(hasFunc);
|
|
||||||
|
|
||||||
// Create a built-in readFile function
|
|
||||||
auto readFileFunc = std::make_shared<BuiltinFunction>("readFile",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"readFile() argument must be a string", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("readFile() argument must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = args[0].asString();
|
|
||||||
std::ifstream file(filename);
|
|
||||||
|
|
||||||
if (!file.is_open()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Could not open file: " + filename, "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Could not open file: " + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << file.rdbuf();
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return Value(buffer.str());
|
|
||||||
});
|
|
||||||
env->define("readFile", Value(readFileFunc));
|
|
||||||
interpreter.addBuiltinFunction(readFileFunc);
|
|
||||||
|
|
||||||
// Create a built-in writeFile function
|
|
||||||
auto writeFileFunc = std::make_shared<BuiltinFunction>("writeFile",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 2) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"First argument to writeFile() must be a string", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("First argument to writeFile() must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[1].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Second argument to writeFile() must be a string", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Second argument to writeFile() must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = args[0].asString();
|
|
||||||
std::string content = args[1].asString();
|
|
||||||
|
|
||||||
std::ofstream file(filename);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Could not create file: " + filename, "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Could not create file: " + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
file << content;
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
env->define("writeFile", Value(writeFileFunc));
|
|
||||||
interpreter.addBuiltinFunction(writeFileFunc);
|
|
||||||
|
|
||||||
// Create a built-in readLines function
|
|
||||||
auto readLinesFunc = std::make_shared<BuiltinFunction>("readLines",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"readLines() argument must be a string", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("readLines() argument must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = args[0].asString();
|
|
||||||
std::ifstream file(filename);
|
|
||||||
|
|
||||||
if (!file.is_open()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Could not open file: " + filename, "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Could not open file: " + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Value> lines;
|
|
||||||
std::string line_content;
|
|
||||||
|
|
||||||
while (std::getline(file, line_content)) {
|
|
||||||
lines.push_back(Value(line_content));
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
return Value(lines);
|
|
||||||
});
|
|
||||||
env->define("readLines", Value(readLinesFunc));
|
|
||||||
interpreter.addBuiltinFunction(readLinesFunc);
|
|
||||||
|
|
||||||
// Create a built-in fileExists function
|
|
||||||
auto fileExistsFunc = std::make_shared<BuiltinFunction>("fileExists",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isString()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"fileExists() argument must be a string", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("fileExists() argument must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = args[0].asString();
|
|
||||||
std::ifstream file(filename);
|
|
||||||
bool exists = file.good();
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return Value(exists);
|
|
||||||
});
|
|
||||||
env->define("fileExists", Value(fileExistsFunc));
|
|
||||||
interpreter.addBuiltinFunction(fileExistsFunc);
|
|
||||||
|
|
||||||
// Create a built-in memoryUsage function (platform-specific, best effort)
|
|
||||||
auto memoryUsageFunc = std::make_shared<BuiltinFunction>("memoryUsage",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 0) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platform-specific memory usage detection
|
|
||||||
size_t memoryBytes = 0;
|
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
|
||||||
// macOS
|
|
||||||
struct mach_task_basic_info info;
|
|
||||||
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
|
|
||||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
|
|
||||||
memoryBytes = info.resident_size;
|
|
||||||
}
|
|
||||||
#elif defined(__linux__)
|
|
||||||
// Linux - read from /proc/self/status
|
|
||||||
std::ifstream statusFile("/proc/self/status");
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(statusFile, line)) {
|
|
||||||
if (line.substr(0, 6) == "VmRSS:") {
|
|
||||||
std::istringstream iss(line);
|
|
||||||
std::string label, value, unit;
|
|
||||||
iss >> label >> value >> unit;
|
|
||||||
memoryBytes = std::stoull(value) * 1024; // Convert KB to bytes
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
// Windows
|
|
||||||
PROCESS_MEMORY_COUNTERS pmc;
|
|
||||||
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
|
||||||
memoryBytes = pmc.WorkingSetSize;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Return memory usage in MB for readability
|
|
||||||
double memoryMB = static_cast<double>(memoryBytes) / (1024.0 * 1024.0);
|
|
||||||
return Value(memoryMB);
|
|
||||||
});
|
|
||||||
env->define("memoryUsage", Value(memoryUsageFunc));
|
|
||||||
interpreter.addBuiltinFunction(memoryUsageFunc);
|
|
||||||
|
|
||||||
}
|
|
||||||
0
tech_debt.txt
Normal file
0
tech_debt.txt
Normal file
File diff suppressed because it is too large
Load Diff
16
test_fib.bob
16
test_fib.bob
@ -1,25 +1,17 @@
|
|||||||
var counter = 0;
|
var counter = 0;
|
||||||
|
|
||||||
func fib(n) {
|
func fib(n) {
|
||||||
return fibTail(n, 0, 1);
|
if (n <= 1) {
|
||||||
}
|
return n;
|
||||||
|
|
||||||
func fibTail(n, a, b) {
|
|
||||||
if (n <= 0) {
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
counter++; // Only increment for recursive calls
|
counter++; // Only increment for recursive calls
|
||||||
return fibTail(n - 1, b, a + b);
|
return fib(n - 1) + fib(n - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Fibonacci test:");
|
print("Fibonacci test:");
|
||||||
var fib_result = fib(50000000);
|
var fib_result = fib(30);
|
||||||
|
|
||||||
print("Result: " + fib_result);
|
print("Result: " + fib_result);
|
||||||
|
|
||||||
print("Counter: " + counter);
|
print("Counter: " + counter);
|
||||||
|
|
||||||
|
|
||||||
func test_fib() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
53
tests.bob
53
tests.bob
@ -1,53 +0,0 @@
|
|||||||
var a = [];
|
|
||||||
|
|
||||||
for(var i = 0; i < 1000000; i++){
|
|
||||||
print(i);
|
|
||||||
|
|
||||||
// Create nested structures with functions at different levels
|
|
||||||
if (i % 4 == 0) {
|
|
||||||
// Nested array with function
|
|
||||||
push(a, [
|
|
||||||
func(){print("Array nested func i=" + i); return i;},
|
|
||||||
[func(){return "Deep array func " + i;}],
|
|
||||||
i
|
|
||||||
]);
|
|
||||||
} else if (i % 4 == 1) {
|
|
||||||
// Nested dict with function
|
|
||||||
push(a, {
|
|
||||||
"func": func(){print("Dict func i=" + i); return i;},
|
|
||||||
"nested": {"deepFunc": func(){return "Deep dict func " + i;}},
|
|
||||||
"value": i
|
|
||||||
});
|
|
||||||
} else if (i % 4 == 2) {
|
|
||||||
// Mixed nested array/dict with functions
|
|
||||||
push(a, [
|
|
||||||
{"arrayInDict": func(){return "Mixed " + i;}},
|
|
||||||
[func(){return "Array in array " + i;}, {"more": func(){return i;}}],
|
|
||||||
func(){print("Top level in mixed i=" + i); return i;}
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
// Simple function (original test case)
|
|
||||||
push(a, func(){print("Simple func i=" + i); return toString(i);});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Before: " + len(a));
|
|
||||||
print("Memory usage: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test different types of nested function calls
|
|
||||||
a[3691](); // Simple function
|
|
||||||
if (len(a[3692]) > 0) {
|
|
||||||
a[3692][0](); // Nested array function
|
|
||||||
}
|
|
||||||
if (a[3693]["func"]) {
|
|
||||||
a[3693]["func"](); // Nested dict function
|
|
||||||
}
|
|
||||||
print(a);
|
|
||||||
//writeFile("array_contents.txt", toString(a));
|
|
||||||
print("Array contents written to array_contents.txt");
|
|
||||||
print("Memory before cleanup: " + memoryUsage() + " MB");
|
|
||||||
input("Press any key to free memory");
|
|
||||||
|
|
||||||
a = none;
|
|
||||||
print("Memory after cleanup: " + memoryUsage() + " MB");
|
|
||||||
input("waiting...");
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
|
// Created by Bobby Lucero on 5/21/23.
|
||||||
//
|
//
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user