Compare commits
No commits in common. "development" and "master" have entirely different histories.
developmen
...
master
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,3 @@
|
|||||||
/.vscode
|
/.vscode
|
||||||
build/
|
build/
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
build-ninja
|
|
||||||
build-release
|
|
||||||
|
|||||||
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.*
|
||||||
163
CMakeLists.txt
163
CMakeLists.txt
@ -1,163 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
|
|
||||||
# Project definition
|
|
||||||
project(bob
|
|
||||||
VERSION 0.0.3
|
|
||||||
DESCRIPTION "Bob Language Interpreter"
|
|
||||||
LANGUAGES CXX
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set C++ standard
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Build type defaults
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
|
||||||
set(CMAKE_BUILD_TYPE Release)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Output directories
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|
||||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|
||||||
|
|
||||||
# Compiler-specific options
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
set(BOB_COMPILE_OPTIONS
|
|
||||||
-Wall -Wextra
|
|
||||||
-Wno-unused-variable
|
|
||||||
-Wno-unused-parameter
|
|
||||||
-Wno-switch
|
|
||||||
)
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
list(APPEND BOB_COMPILE_OPTIONS -O3 -march=native)
|
|
||||||
endif()
|
|
||||||
elseif(MSVC)
|
|
||||||
set(BOB_COMPILE_OPTIONS
|
|
||||||
/W4
|
|
||||||
/wd4100 # unreferenced formal parameter
|
|
||||||
/wd4101 # unreferenced local variable
|
|
||||||
)
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
list(APPEND BOB_COMPILE_OPTIONS /O2)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Collect source files
|
|
||||||
file(GLOB_RECURSE BOB_RUNTIME_SOURCES CONFIGURE_DEPENDS "src/sources/runtime/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_PARSING_SOURCES CONFIGURE_DEPENDS "src/sources/parsing/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_STDLIB_SOURCES CONFIGURE_DEPENDS "src/sources/stdlib/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_BUILTIN_SOURCES CONFIGURE_DEPENDS "src/sources/builtinModules/*.cpp")
|
|
||||||
file(GLOB_RECURSE BOB_CLI_SOURCES CONFIGURE_DEPENDS "src/sources/cli/*.cpp")
|
|
||||||
|
|
||||||
# All source files
|
|
||||||
set(BOB_ALL_SOURCES
|
|
||||||
${BOB_RUNTIME_SOURCES}
|
|
||||||
${BOB_PARSING_SOURCES}
|
|
||||||
${BOB_STDLIB_SOURCES}
|
|
||||||
${BOB_BUILTIN_SOURCES}
|
|
||||||
${BOB_CLI_SOURCES}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the executable
|
|
||||||
add_executable(bob ${BOB_ALL_SOURCES})
|
|
||||||
|
|
||||||
# Include directories
|
|
||||||
target_include_directories(bob PRIVATE
|
|
||||||
src/headers/runtime
|
|
||||||
src/headers/parsing
|
|
||||||
src/headers/stdlib
|
|
||||||
src/headers/builtinModules
|
|
||||||
src/headers/cli
|
|
||||||
src/headers/common
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply compiler options
|
|
||||||
target_compile_options(bob PRIVATE ${BOB_COMPILE_OPTIONS})
|
|
||||||
|
|
||||||
# Enable Link Time Optimization (LTO) for Release builds
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
include(CheckIPOSupported)
|
|
||||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
|
||||||
if(ipo_supported)
|
|
||||||
message(STATUS "IPO/LTO enabled")
|
|
||||||
set_property(TARGET bob PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
|
||||||
else()
|
|
||||||
message(WARNING "IPO/LTO not supported: ${ipo_error}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Platform-specific settings
|
|
||||||
if(WIN32)
|
|
||||||
# Windows-specific settings
|
|
||||||
target_compile_definitions(bob PRIVATE _CRT_SECURE_NO_WARNINGS)
|
|
||||||
elseif(UNIX AND NOT APPLE)
|
|
||||||
# Linux-specific settings
|
|
||||||
target_link_libraries(bob PRIVATE pthread)
|
|
||||||
elseif(APPLE)
|
|
||||||
# macOS-specific settings
|
|
||||||
set_target_properties(bob PROPERTIES
|
|
||||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Generate compile_commands.json for language servers
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
# Testing support
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
# Add test for the main test suite
|
|
||||||
add_test(
|
|
||||||
NAME bob_test_suite
|
|
||||||
COMMAND bob test_bob_language.bob
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Custom target for running tests with verbose output
|
|
||||||
add_custom_target(test_verbose
|
|
||||||
COMMAND ${CMAKE_CTEST_COMMAND} --verbose
|
|
||||||
DEPENDS bob
|
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Install rules
|
|
||||||
install(TARGETS bob
|
|
||||||
RUNTIME DESTINATION bin
|
|
||||||
COMPONENT Runtime
|
|
||||||
)
|
|
||||||
|
|
||||||
# Install test files (optional)
|
|
||||||
install(FILES test_bob_language.bob
|
|
||||||
DESTINATION share/bob/tests
|
|
||||||
COMPONENT Tests
|
|
||||||
)
|
|
||||||
|
|
||||||
# CPack configuration for packaging
|
|
||||||
set(CPACK_PACKAGE_NAME "Bob Language")
|
|
||||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Bob Language Interpreter")
|
|
||||||
set(CPACK_PACKAGE_VENDOR "Bob Language Team")
|
|
||||||
set(CPACK_PACKAGE_CONTACT "your-email@example.com")
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(CPACK_GENERATOR "ZIP;NSIS")
|
|
||||||
elseif(APPLE)
|
|
||||||
set(CPACK_GENERATOR "TGZ;DragNDrop")
|
|
||||||
else()
|
|
||||||
set(CPACK_GENERATOR "TGZ;DEB;RPM")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(CPack)
|
|
||||||
|
|
||||||
# Print configuration summary
|
|
||||||
message(STATUS "Bob Language Build Configuration:")
|
|
||||||
message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
|
|
||||||
message(STATUS " Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
|
||||||
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
|
||||||
message(STATUS " Install Prefix: ${CMAKE_INSTALL_PREFIX}")
|
|
||||||
message(STATUS " Runtime Sources: ${BOB_RUNTIME_SOURCES}")
|
|
||||||
message(STATUS " Parsing Sources: ${BOB_PARSING_SOURCES}")
|
|
||||||
message(STATUS " Stdlib Sources: ${BOB_STDLIB_SOURCES}")
|
|
||||||
message(STATUS " CLI Sources: ${BOB_CLI_SOURCES}")
|
|
||||||
48
Makefile
Normal file
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,474 +0,0 @@
|
|||||||
# Bob Language Reference
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build Bob
|
|
||||||
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
ninja -C build
|
|
||||||
|
|
||||||
# Run a file
|
|
||||||
./build/bin/bob script.bob
|
|
||||||
|
|
||||||
# Interactive mode
|
|
||||||
./build/bin/bob
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Types
|
|
||||||
|
|
||||||
### Numbers
|
|
||||||
```go
|
|
||||||
var integer = 42;
|
|
||||||
var float = 3.14;
|
|
||||||
var negative = -10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strings
|
|
||||||
```go
|
|
||||||
var text = "Hello, World!";
|
|
||||||
var empty = "";
|
|
||||||
var escaped = "Line 1\nLine 2\t\"quoted\"";
|
|
||||||
var concat = "Hello" + " " + "World";
|
|
||||||
var repeat = "hi" * 3; // "hihihi"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Booleans
|
|
||||||
```go
|
|
||||||
var yes = true;
|
|
||||||
var no = false;
|
|
||||||
```
|
|
||||||
|
|
||||||
### None
|
|
||||||
```go
|
|
||||||
var nothing = none;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrays
|
|
||||||
```go
|
|
||||||
var numbers = [1, 2, 3];
|
|
||||||
var mixed = [42, "hello", true];
|
|
||||||
var nested = [[1, 2], [3, 4]];
|
|
||||||
|
|
||||||
// Access and modify
|
|
||||||
print(numbers[0]); // 1
|
|
||||||
numbers[1] = 99;
|
|
||||||
|
|
||||||
// Array properties (read-only)
|
|
||||||
print(numbers.length); // 3
|
|
||||||
print(numbers.first); // 1
|
|
||||||
print(numbers.last); // 3
|
|
||||||
print(numbers.empty); // false
|
|
||||||
|
|
||||||
var empty = [];
|
|
||||||
print(empty.length); // 0
|
|
||||||
print(empty.first); // none
|
|
||||||
print(empty.empty); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dictionaries
|
|
||||||
```go
|
|
||||||
var person = {"name": "Alice", "age": 30};
|
|
||||||
|
|
||||||
// Access and modify (bracket notation)
|
|
||||||
print(person["name"]); // Alice
|
|
||||||
person["city"] = "NYC";
|
|
||||||
|
|
||||||
// Access and modify (dot notation - cleaner syntax)
|
|
||||||
print(person.name); // Alice
|
|
||||||
person.city = "NYC";
|
|
||||||
person.age = 31;
|
|
||||||
|
|
||||||
// Both notations are equivalent
|
|
||||||
assert(person.name == person["name"]);
|
|
||||||
|
|
||||||
// Dictionary properties (built-in)
|
|
||||||
print(person.length); // 3 (number of key-value pairs)
|
|
||||||
print(person.empty); // false
|
|
||||||
print(person.keys); // ["name", "age", "city"]
|
|
||||||
print(person.values); // ["Alice", 31, "NYC"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variables
|
|
||||||
|
|
||||||
```go
|
|
||||||
var x = 10; // Declaration
|
|
||||||
x = 20; // Reassignment
|
|
||||||
y += 5; // Compound assignment
|
|
||||||
z++; // Increment (for array elements)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operators
|
|
||||||
|
|
||||||
### Arithmetic
|
|
||||||
```go
|
|
||||||
+ - * / % // Basic math
|
|
||||||
-x // Unary minus
|
|
||||||
```
|
|
||||||
|
|
||||||
### Comparison
|
|
||||||
```go
|
|
||||||
== != < > <= >= // Comparisons
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logical
|
|
||||||
```go
|
|
||||||
&& || ! // AND, OR, NOT (short-circuit)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bitwise
|
|
||||||
```go
|
|
||||||
& | ^ << >> ~ // Bitwise operations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compound Assignment
|
|
||||||
```go
|
|
||||||
+= -= *= /= %= // Arithmetic compound
|
|
||||||
&= |= ^= <<= >>= // Bitwise compound
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ternary
|
|
||||||
```go
|
|
||||||
var result = condition ? "yes" : "no";
|
|
||||||
```
|
|
||||||
|
|
||||||
## Control Flow
|
|
||||||
|
|
||||||
### If Statements
|
|
||||||
```go
|
|
||||||
if (x > 0) {
|
|
||||||
print("positive");
|
|
||||||
} else if (x < 0) {
|
|
||||||
print("negative");
|
|
||||||
} else {
|
|
||||||
print("zero");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### While Loops
|
|
||||||
```go
|
|
||||||
while (i < 10) {
|
|
||||||
print(i);
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Do-While Loops
|
|
||||||
```go
|
|
||||||
do {
|
|
||||||
print(i);
|
|
||||||
i = i + 1;
|
|
||||||
} while (i < 10);
|
|
||||||
```
|
|
||||||
|
|
||||||
### For Loops
|
|
||||||
```go
|
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
|
||||||
print(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break and continue work in all loops
|
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
|
||||||
if (i == 5) break;
|
|
||||||
if (i % 2 == 0) continue;
|
|
||||||
print(i);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
|
|
||||||
### Basic Functions
|
|
||||||
```go
|
|
||||||
func greet(name) {
|
|
||||||
return "Hello, " + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = greet("Alice");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Anonymous Functions
|
|
||||||
```go
|
|
||||||
var square = func(x) { return x * x; };
|
|
||||||
var result = square(5);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Closures
|
|
||||||
```go
|
|
||||||
func makeCounter() {
|
|
||||||
var count = 0;
|
|
||||||
return func() {
|
|
||||||
count = count + 1;
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var counter = makeCounter();
|
|
||||||
print(counter()); // 1
|
|
||||||
print(counter()); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
### First-Class Functions
|
|
||||||
```go
|
|
||||||
func apply(fn, x) {
|
|
||||||
return fn(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = apply(square, 10); // 100
|
|
||||||
```
|
|
||||||
|
|
||||||
## Built-in Functions
|
|
||||||
|
|
||||||
### I/O
|
|
||||||
```go
|
|
||||||
print("Hello"); // Output with newline
|
|
||||||
printRaw("No newline"); // Output without newline
|
|
||||||
var input = input("Enter something: ");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type Conversion
|
|
||||||
```go
|
|
||||||
toString(42); // "42"
|
|
||||||
toNumber("3.14"); // 3.14
|
|
||||||
toInt(3.9); // 3
|
|
||||||
toBoolean(1); // true
|
|
||||||
type(42); // "number"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrays, Strings, and Dictionaries: Method style (preferred)
|
|
||||||
```go
|
|
||||||
[1, 2, 3].len(); // 3
|
|
||||||
"hello".len(); // 5
|
|
||||||
var a = [1, 2]; a.push(3); // a is now [1, 2, 3]
|
|
||||||
var v = a.pop(); // v == 3
|
|
||||||
|
|
||||||
var d = {"a": 1, "b": 2};
|
|
||||||
d.len(); // 2
|
|
||||||
d.keys(); // ["a", "b"]
|
|
||||||
d.values(); // [1, 2]
|
|
||||||
d.has("a"); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Global forms like `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)` have been removed. Use method style.
|
|
||||||
|
|
||||||
### Numbers
|
|
||||||
```go
|
|
||||||
toInt(3.9); // 3 (global)
|
|
||||||
(3.9).toInt(); // 3 (method on number)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Utility
|
|
||||||
```go
|
|
||||||
assert(condition, "message"); // Testing
|
|
||||||
time(); // Current time in microseconds
|
|
||||||
sleep(1.5); // Sleep for 1.5 seconds
|
|
||||||
rand.random(); // Random number 0-1
|
|
||||||
eval("print('Hello');"); // Execute string as code
|
|
||||||
exit(0); // Exit program
|
|
||||||
```
|
|
||||||
|
|
||||||
### File I/O
|
|
||||||
```go
|
|
||||||
var content = readFile("data.txt");
|
|
||||||
writeFile("output.txt", "Hello");
|
|
||||||
var lines = readLines("config.txt");
|
|
||||||
var exists = fileExists("test.txt");
|
|
||||||
```
|
|
||||||
|
|
||||||
## Standard Library Reference
|
|
||||||
|
|
||||||
The following built-ins are available by default. Unless specified, functions throw on invalid argument counts/types.
|
|
||||||
|
|
||||||
- print(x): prints x with newline
|
|
||||||
- printRaw(x): prints x without newline
|
|
||||||
- input(prompt?): reads a line from stdin (optional prompt)
|
|
||||||
- toString(x): returns string representation
|
|
||||||
- toNumber(s): parses string to number or returns none
|
|
||||||
- toInt(n): truncates number to integer
|
|
||||||
- toBoolean(x): converts to boolean using truthiness rules
|
|
||||||
- type(x): returns the type name as string
|
|
||||||
- len(x) / x.len(): length of array/string/dict
|
|
||||||
- push(arr, ...values) / arr.push(...values): appends values to array in place, returns arr
|
|
||||||
- pop(arr) / arr.pop(): removes and returns last element
|
|
||||||
- keys(dict) / dict.keys(): returns array of keys
|
|
||||||
- values(dict) / dict.values(): returns array of values
|
|
||||||
- has(dict, key) / dict.has(key): returns true if key exists
|
|
||||||
- readFile(path): returns entire file contents as string
|
|
||||||
- writeFile(path, content): writes content to file
|
|
||||||
- readLines(path): returns array of lines
|
|
||||||
- fileExists(path): boolean
|
|
||||||
- time(): microseconds since Unix epoch
|
|
||||||
- sleep(seconds): pauses execution
|
|
||||||
- rand.random(): float in [0,1)
|
|
||||||
- eval(code): executes code string in current environment
|
|
||||||
- exit(code?): terminates the program
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Arrays support properties: length, first, last, empty
|
|
||||||
- Dicts support properties: length, empty, keys, values
|
|
||||||
- Method-style builtins on arrays/strings/dicts are preferred; global forms remain for compatibility.
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Classes (Phase 1)
|
|
||||||
```go
|
|
||||||
// Declare a class with fields and methods
|
|
||||||
class Person {
|
|
||||||
var name;
|
|
||||||
var age;
|
|
||||||
|
|
||||||
// Methods can use implicit `this`
|
|
||||||
func setName(n) { this.name = n; }
|
|
||||||
func greet() { print("Hi, I'm " + this.name); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct via the class name
|
|
||||||
var p = Person();
|
|
||||||
p.setName("Bob");
|
|
||||||
p.greet();
|
|
||||||
|
|
||||||
// Fields are stored on the instance (a dictionary under the hood)
|
|
||||||
p.age = 30;
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Instances are plain dictionaries; methods are shared functions placed on the instance.
|
|
||||||
- On a property call like `obj.method(...)`, the interpreter injects `this = obj` into the call frame (no argument injection).
|
|
||||||
- Taking a method reference and calling it later does not auto‑bind `this`; call via `obj.method(...)` when needed.
|
|
||||||
|
|
||||||
### Extensions (Built‑ins and Classes)
|
|
||||||
Extend existing types (including built‑ins) with new methods:
|
|
||||||
|
|
||||||
```go
|
|
||||||
extension array {
|
|
||||||
func sum() {
|
|
||||||
var i = 0; var s = 0;
|
|
||||||
while (i < len(this)) { s = s + this[i]; i = i + 1; }
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension dict { func size() { return len(this); } }
|
|
||||||
extension string { func shout() { return toString(this) + "!"; } }
|
|
||||||
extension any { func tag() { return "<" + type(this) + ">"; } }
|
|
||||||
|
|
||||||
assert([1,2,3].sum() == 6);
|
|
||||||
assert({"a":1,"b":2}.size() == 2);
|
|
||||||
assert("hi".shout() == "hi!");
|
|
||||||
assert(42.tag() == "<number>");
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Lookup order for `obj.method(...)`: instance dictionary → class extensions (for user classes) → built‑in extensions (string/array/dict) → `any`.
|
|
||||||
- `this` is injected for property calls.
|
|
||||||
|
|
||||||
### String Interpolation
|
|
||||||
```go
|
|
||||||
var name = "Alice";
|
|
||||||
var age = 30;
|
|
||||||
var message = "Name: " + name + ", Age: " + age;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tail Call Optimization
|
|
||||||
```go
|
|
||||||
func factorial(n, acc) {
|
|
||||||
if (n <= 1) return acc;
|
|
||||||
return factorial(n - 1, n * acc); // Tail call optimized
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Assignment System
|
|
||||||
Bob has a unique assignment system that prevents common bugs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Assignment statements (everywhere)
|
|
||||||
var x = 5;
|
|
||||||
x = 10;
|
|
||||||
y += 5;
|
|
||||||
|
|
||||||
// Assignment expressions (only in for loops)
|
|
||||||
for (var i = 0; i < 5; i = i + 1) { } // OK
|
|
||||||
for (j = 0; j < 5; j += 1) { } // OK
|
|
||||||
|
|
||||||
// This prevents bugs like:
|
|
||||||
if (x = 10) { } // PARSE ERROR - prevents accidental assignment
|
|
||||||
```
|
|
||||||
|
|
||||||
## Memory Management
|
|
||||||
|
|
||||||
Bob automatically manages memory - no manual allocation or deallocation needed. Objects are cleaned up when no longer referenced.
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
Bob provides helpful error messages with context:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Runtime errors show line numbers and context
|
|
||||||
var x = undefined_variable; // Error: Undefined variable 'undefined_variable' at line 2
|
|
||||||
|
|
||||||
// Type errors are caught
|
|
||||||
var result = "hello" / 5; // Error: Cannot divide string by number
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interactive Mode (REPL)
|
|
||||||
|
|
||||||
Bob includes an interactive mode for experimenting:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ./build/bin/bob
|
|
||||||
Bob Interactive Mode
|
|
||||||
> var x = 42;
|
|
||||||
> print(x * 2);
|
|
||||||
84
|
|
||||||
> exit();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Fibonacci with Tail Call Optimization
|
|
||||||
```go
|
|
||||||
func fib(n, a, b) {
|
|
||||||
if (n == 0) return a;
|
|
||||||
if (n == 1) return b;
|
|
||||||
return fib(n - 1, b, a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
print(fib(40, 0, 1)); // Fast even for large numbers
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with Data Structures
|
|
||||||
```go
|
|
||||||
var people = [
|
|
||||||
{"name": "Alice", "age": 30},
|
|
||||||
{"name": "Bob", "age": 25}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var i = 0; i < people.len(); i = i + 1) {
|
|
||||||
var person = people[i];
|
|
||||||
print(person["name"] + " is " + person["age"] + " years old");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### File Processing
|
|
||||||
```go
|
|
||||||
var lines = readLines("data.txt");
|
|
||||||
var processed = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < lines.len(); i = i + 1) {
|
|
||||||
var line = lines[i];
|
|
||||||
if (line.len() > 0) {
|
|
||||||
processed.push("Processed: " + line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = "";
|
|
||||||
for (var i = 0; i < processed.len(); i = i + 1) {
|
|
||||||
output = output + processed[i];
|
|
||||||
if (i < processed.len() - 1) {
|
|
||||||
output = output + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeFile("output.txt", output);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*For more examples, see the comprehensive test suite in `test_bob_language.bob`*
|
|
||||||
@ -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,113 +0,0 @@
|
|||||||
Embedding Bob: Public API Guide
|
|
||||||
================================
|
|
||||||
|
|
||||||
This document explains how to embed the Bob interpreter in your C++ application, register custom modules, and control sandbox policies.
|
|
||||||
|
|
||||||
Quick Start
|
|
||||||
-----------
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include "cli/bob.h"
|
|
||||||
#include "ModuleRegistry.h"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
Bob bob;
|
|
||||||
|
|
||||||
// Optional: configure policies or modules before first use
|
|
||||||
bob.setBuiltinModulePolicy(true); // allow builtin modules (default)
|
|
||||||
bob.setBuiltinModuleDenyList({/* e.g., "sys" */});
|
|
||||||
|
|
||||||
// Register a custom builtin module called "demo"
|
|
||||||
bob.registerModule("demo", [](ModuleRegistry::ModuleBuilder& m) {
|
|
||||||
m.fn("hello", [](std::vector<Value> args, int, int) -> Value {
|
|
||||||
std::string who = (args.size() >= 1 && args[0].isString()) ? args[0].asString() : "world";
|
|
||||||
return Value(std::string("hello ") + who);
|
|
||||||
});
|
|
||||||
m.val("meaning", Value(42.0));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Evaluate code from a string
|
|
||||||
bob.evalString("import demo; print(demo.hello(\"Bob\"));", "<host>");
|
|
||||||
|
|
||||||
// Evaluate a file (imports inside resolve relative to the file's directory)
|
|
||||||
bob.evalFile("script.bob");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
API Overview
|
|
||||||
------------
|
|
||||||
|
|
||||||
Bob exposes a single high-level object with a minimal, consistent API. It self-manages an internal interpreter and applies configuration on first use.
|
|
||||||
|
|
||||||
- Program execution
|
|
||||||
- `bool evalString(const std::string& code, const std::string& filename = "<eval>")`
|
|
||||||
- `bool evalFile(const std::string& path)`
|
|
||||||
- `void runFile(const std::string& path)` (CLI convenience – delegates to `evalFile`)
|
|
||||||
- `void runPrompt()` (interactive CLI – delegates each line to `evalString`)
|
|
||||||
|
|
||||||
- Module registration and sandboxing
|
|
||||||
- `void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init)`
|
|
||||||
- `void setBuiltinModulePolicy(bool allow)`
|
|
||||||
- `void setBuiltinModuleAllowList(const std::vector<std::string>& allowed)`
|
|
||||||
- `void setBuiltinModuleDenyList(const std::vector<std::string>& denied)`
|
|
||||||
|
|
||||||
- Global environment helpers
|
|
||||||
- `bool defineGlobal(const std::string& name, const Value& value)`
|
|
||||||
- `bool tryGetGlobal(const std::string& name, Value& out) const`
|
|
||||||
|
|
||||||
All configuration calls are safe to use before any evaluation – they are queued and applied automatically when the interpreter is first created.
|
|
||||||
|
|
||||||
Registering Custom Builtin Modules
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Use the builder convenience to create a module:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bob.registerModule("raylib", [](ModuleRegistry::ModuleBuilder& m) {
|
|
||||||
m.fn("init", [](std::vector<Value> args, int line, int col) -> Value {
|
|
||||||
// call into your library here; validate args, return Value
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
m.val("VERSION", Value(std::string("5.0")));
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
At runtime:
|
|
||||||
|
|
||||||
```bob
|
|
||||||
import raylib;
|
|
||||||
raylib.init();
|
|
||||||
print(raylib.VERSION);
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Modules are immutable, first-class objects. `type(module)` is "module" and `toString(module)` prints `<module 'name'>`.
|
|
||||||
- Reassigning a module binding or setting module properties throws an error.
|
|
||||||
|
|
||||||
Builtin Modules and Sandboxing
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
- Builtin modules (e.g., `sys`) are registered during interpreter construction.
|
|
||||||
- File imports are always resolved relative to the importing file's directory.
|
|
||||||
- Policies:
|
|
||||||
- `setBuiltinModulePolicy(bool allow)` – enable/disable all builtin modules.
|
|
||||||
- `setBuiltinModuleAllowList(vector<string>)` – allow only listed modules (deny others).
|
|
||||||
- `setBuiltinModuleDenyList(vector<string>)` – explicitly deny listed modules.
|
|
||||||
- Denied/disabled modules are cloaked: `import name` reports "Module not found".
|
|
||||||
|
|
||||||
Error Reporting
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- `evalString`/`evalFile` set file context for error reporting so line/column references point to the real source.
|
|
||||||
- Both return `true` on success and `false` if execution failed (errors are reported via the internal error reporter).
|
|
||||||
|
|
||||||
CLI vs Embedding
|
|
||||||
----------------
|
|
||||||
|
|
||||||
- CLI builds include `main.cpp` (entry point), which uses `Bob::runFile` or `Bob::runPrompt`.
|
|
||||||
- Embedded hosts do not use `main.cpp`; instead they instantiate `Bob` and call `evalString`/`evalFile` directly.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
# Bob Language Development Roadmap
|
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
Bob is a mature, working programming language with a modern architecture and comprehensive feature set.
|
|
||||||
|
|
||||||
### ✅ **Core Language Features (Complete)**
|
|
||||||
|
|
||||||
#### **Data Types & Variables**
|
|
||||||
- **Numbers**: Integers, floats, automatic conversion
|
|
||||||
- **Strings**: Literals, concatenation, multiplication, escape sequences
|
|
||||||
- **Booleans**: `true`, `false`
|
|
||||||
- **None**: Null value representation
|
|
||||||
- **Arrays**: Dynamic arrays with indexing, assignment, and built-in functions
|
|
||||||
- **Dictionaries**: Hash maps with string keys and mixed-type values
|
|
||||||
- **Functions**: First-class functions as values
|
|
||||||
- **Variables**: Declaration, assignment, scoping
|
|
||||||
- **Assignment System**: Dual system (statements + expressions for loops only)
|
|
||||||
|
|
||||||
#### **Operators (Complete)**
|
|
||||||
- **Arithmetic**: `+`, `-`, `*`, `/`, `%`, unary `-`
|
|
||||||
- **Comparison**: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
|
||||||
- **Logical**: `&&`, `||`, `!` (with short-circuit evaluation)
|
|
||||||
- **Bitwise**: `&`, `|`, `^`, `<<`, `>>`, `~`
|
|
||||||
- **Compound Assignment**: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
|
||||||
- **Ternary**: `condition ? valueIfTrue : valueIfFalse`
|
|
||||||
- **Increment/Decrement**: `++`, `--` (for array elements)
|
|
||||||
|
|
||||||
#### **Control Flow (Complete)**
|
|
||||||
- **If Statements**: `if`, `else`, `else if` chains
|
|
||||||
- **While Loops**: Basic, nested, complex conditions
|
|
||||||
- **Do-While Loops**: Basic, nested, break/continue support
|
|
||||||
- **For Loops**: All clause variations, nested loops
|
|
||||||
- **Break/Continue**: Full support in all loop types
|
|
||||||
|
|
||||||
#### **Functions (Complete)**
|
|
||||||
- **Function Declaration**: `func name(params) { body }`
|
|
||||||
- **Parameters**: Any number of parameters (tested up to 100)
|
|
||||||
- **Return Values**: Explicit and implicit returns
|
|
||||||
- **Closures**: Lexical scoping with variable capture
|
|
||||||
- **First-Class Functions**: Functions as values, parameters, return values
|
|
||||||
- **Anonymous Functions**: `func(params) { body }`
|
|
||||||
- **Nested Functions**: Functions defined inside other functions
|
|
||||||
- **Recursion**: Full support including deep recursion
|
|
||||||
- **Tail Call Optimization**: Trampoline-based optimization preventing stack overflow
|
|
||||||
|
|
||||||
#### **Data Structures (Complete)**
|
|
||||||
- **Arrays**: Full support with indexing, assignment, nested arrays
|
|
||||||
- **Dictionaries**: Full support with key-value pairs, nested dictionaries
|
|
||||||
- **Array Operations**: `len()`, `push()`, `pop()`, indexing, assignment, properties (`length`, `first`, `last`, `empty`)
|
|
||||||
- **Dictionary Operations**: `keys()`, `values()`, `has()`, indexing, assignment, dot notation (`obj.prop`)
|
|
||||||
- **Mixed Types**: Arrays and dictionaries can hold any value types
|
|
||||||
|
|
||||||
#### **Standard Library (Complete)**
|
|
||||||
- **I/O Functions**: `print()`, `printRaw()`, `input()`
|
|
||||||
- **Type System**: `type()`, `toString()`, `toNumber()`, `toInt()`, `toBoolean()`
|
|
||||||
- **Testing**: `assert()` with custom error messages
|
|
||||||
- **Timing**: `time()` (microsecond precision), `sleep()`
|
|
||||||
- **Utility**: `rand.random()` (properly seeded), `eval.eval()`, `sys.exit()`
|
|
||||||
- **Data Structure**: `len()`, `push()`, `pop()`, `keys()`, `values()`, `has()`
|
|
||||||
- **File I/O**: `readFile()`, `writeFile()`, `readLines()`, `fileExists()`
|
|
||||||
|
|
||||||
#### **Advanced Features (Complete)**
|
|
||||||
- **String Operations**: Bidirectional string + number concatenation
|
|
||||||
- **Number Formatting**: Smart significant digits handling
|
|
||||||
- **Memory Management**: Automatic cleanup with reference counting
|
|
||||||
- **Error Handling**: Comprehensive error reporting with context
|
|
||||||
- **Testing Framework**: Built-in assert function with 70+ comprehensive tests
|
|
||||||
- **Operator Precedence**: Full precedence hierarchy implementation
|
|
||||||
- **Variable Shadowing**: Proper lexical scoping rules
|
|
||||||
- **Interactive Mode**: Full REPL with error handling
|
|
||||||
- **Cross-Type Comparisons**: Smart equality for all types
|
|
||||||
- **Copy Semantics**: Value vs reference copying for different types
|
|
||||||
|
|
||||||
### ✅ **Architecture & Infrastructure (Complete)**
|
|
||||||
|
|
||||||
#### **Modern Build System**
|
|
||||||
- **CMake**: Cross-platform build configuration
|
|
||||||
- **Ninja**: High-speed build system (3.1x faster than Make)
|
|
||||||
- **CTest**: Integrated testing framework
|
|
||||||
- **Cross-Platform**: Windows, macOS, Linux support
|
|
||||||
- **Performance**: Optimized build times and incremental compilation
|
|
||||||
|
|
||||||
#### **Clean Architecture**
|
|
||||||
- **Modular Design**: Separated parsing, runtime, stdlib, and CLI
|
|
||||||
- **Tier Separation**: Clear boundaries between language components
|
|
||||||
- **Header Organization**: Organized by functional area
|
|
||||||
- **Source Structure**: `src/headers/` and `src/sources/` organization
|
|
||||||
|
|
||||||
#### **Refactored Interpreter**
|
|
||||||
- **Evaluator**: Expression evaluation (visitor pattern)
|
|
||||||
- **Executor**: Statement execution and control flow
|
|
||||||
- **RuntimeDiagnostics**: Utility functions and type checking
|
|
||||||
- **Memory Management**: Smart pointer usage throughout
|
|
||||||
- **Error System**: Centralized error reporting
|
|
||||||
|
|
||||||
### **Current Architecture Status**
|
|
||||||
|
|
||||||
```
|
|
||||||
Bob Language
|
|
||||||
├── Parsing Layer
|
|
||||||
│ ├── Lexer (tokenization)
|
|
||||||
│ ├── Parser (AST generation)
|
|
||||||
│ └── ErrorReporter (syntax errors)
|
|
||||||
├── Runtime Layer
|
|
||||||
│ ├── Evaluator (expression visitor)
|
|
||||||
│ ├── Executor (statement visitor)
|
|
||||||
│ ├── Interpreter (orchestration)
|
|
||||||
│ ├── Environment (variable scoping)
|
|
||||||
│ ├── Value (type system)
|
|
||||||
│ └── RuntimeDiagnostics (utilities)
|
|
||||||
├── Standard Library
|
|
||||||
│ └── BobStdLib (built-in functions)
|
|
||||||
└── CLI Interface
|
|
||||||
└── Bob (command-line interface)
|
|
||||||
```
|
|
||||||
|
|
||||||
## **Future Development Phases**
|
|
||||||
|
|
||||||
### **Phase 1: Advanced Language Features (Medium Priority)**
|
|
||||||
|
|
||||||
#### **Exception Handling System**
|
|
||||||
```bob
|
|
||||||
try {
|
|
||||||
var result = 10 / 0;
|
|
||||||
} catch (error) {
|
|
||||||
print("Error: " + error.message);
|
|
||||||
} finally {
|
|
||||||
print("Cleanup");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `try`/`catch`/`finally` syntax to parser
|
|
||||||
- Implement exception objects with stack traces
|
|
||||||
- Add `throw` statement for custom exceptions
|
|
||||||
- Integrate with existing error system
|
|
||||||
|
|
||||||
#### **Pattern Matching**
|
|
||||||
```bob
|
|
||||||
match value {
|
|
||||||
case 0: "zero"
|
|
||||||
case 1 | 2: "small"
|
|
||||||
case x if x > 10: "large"
|
|
||||||
default: "other"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `match`/`case` syntax
|
|
||||||
- Implement pattern matching logic
|
|
||||||
- Support guards with `if` conditions
|
|
||||||
- Add destructuring for arrays/dictionaries
|
|
||||||
|
|
||||||
### **Phase 2: Object System (Lower Priority)**
|
|
||||||
|
|
||||||
#### **Simple Objects**
|
|
||||||
```bob
|
|
||||||
var person = {
|
|
||||||
name: "Alice",
|
|
||||||
age: 30,
|
|
||||||
greet: func() {
|
|
||||||
return "Hello, I'm " + this.name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add object literal syntax
|
|
||||||
- Implement `this` binding
|
|
||||||
- Support method calls
|
|
||||||
- ✅ Add property access/assignment (completed - dot notation for dictionaries and arrays)
|
|
||||||
|
|
||||||
#### **Classes (Optional)**
|
|
||||||
```bob
|
|
||||||
class Person {
|
|
||||||
init(name, age) {
|
|
||||||
this.name = name;
|
|
||||||
this.age = age;
|
|
||||||
}
|
|
||||||
|
|
||||||
greet() {
|
|
||||||
return "Hello, I'm " + this.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `class` keyword and syntax
|
|
||||||
- Implement constructors with `init()`
|
|
||||||
- Support inheritance with `extends`
|
|
||||||
- Add method definitions
|
|
||||||
|
|
||||||
### **Phase 3: Module System (Lower Priority)**
|
|
||||||
|
|
||||||
#### **Simple Modules**
|
|
||||||
```bob
|
|
||||||
// math.bob
|
|
||||||
func sqrt(x) { return x ** 0.5; }
|
|
||||||
func max(a, b) { return a > b ? a : b; }
|
|
||||||
|
|
||||||
// main.bob
|
|
||||||
import "math.bob" as math;
|
|
||||||
var result = math.sqrt(16);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
- Add `import` statement syntax
|
|
||||||
- Implement module loading from files
|
|
||||||
- Support namespace aliases
|
|
||||||
- Create standard library modules
|
|
||||||
|
|
||||||
### **Phase 4: Language Enhancements (Optional)**
|
|
||||||
|
|
||||||
#### **Enhanced Standard Library**
|
|
||||||
```bob
|
|
||||||
// Additional string functions
|
|
||||||
var parts = "a,b,c".split(",");
|
|
||||||
var joined = ["a", "b", "c"].join("-");
|
|
||||||
var upper = "hello".toUpper();
|
|
||||||
|
|
||||||
// Math library
|
|
||||||
var result = Math.sqrt(16);
|
|
||||||
var max = Math.max(5, 10, 3);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Async/Await (Advanced)**
|
|
||||||
```bob
|
|
||||||
async func fetchData() {
|
|
||||||
var response = await http.get("api.com/data");
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## **Implementation Guidelines**
|
|
||||||
|
|
||||||
### **For Each New Feature:**
|
|
||||||
1. **Design**: Plan syntax and semantics carefully
|
|
||||||
2. **Lexer**: Add new tokens if needed
|
|
||||||
3. **Parser**: Add new expression/statement types
|
|
||||||
4. **AST**: Define new node types in Expression.h/Statement.h
|
|
||||||
5. **Evaluator/Executor**: Implement evaluation logic
|
|
||||||
6. **Testing**: Write tests for the new feature
|
|
||||||
7. **Documentation**: Update language reference
|
|
||||||
|
|
||||||
### **Development Approach:**
|
|
||||||
- **Testing**: Write tests for new features
|
|
||||||
- **Error Messages**: Make errors helpful and clear
|
|
||||||
- **Memory**: Use smart pointers to avoid leaks
|
|
||||||
- **Performance**: Don't make things unnecessarily slow
|
|
||||||
- **Code Style**: Keep it readable and maintainable
|
|
||||||
- **Portability**: Make sure it works on different platforms
|
|
||||||
|
|
||||||
## **Success Metrics**
|
|
||||||
|
|
||||||
### **What's Done ✅**
|
|
||||||
- [x] Core language syntax and semantics
|
|
||||||
- [x] All operators and expressions
|
|
||||||
- [x] Control flow (if, while, for, do-while)
|
|
||||||
- [x] Functions, closures, and tail call optimization
|
|
||||||
- [x] Arrays and dictionaries
|
|
||||||
- [x] Standard library (25+ built-in functions)
|
|
||||||
- [x] File I/O operations
|
|
||||||
- [x] Interactive REPL
|
|
||||||
- [x] Test suite with 70+ tests
|
|
||||||
- [x] Error handling and reporting
|
|
||||||
- [x] Memory management
|
|
||||||
- [x] CMake + Ninja build system
|
|
||||||
- [x] Modular architecture
|
|
||||||
- [x] Cross-platform support
|
|
||||||
- [x] Various optimizations
|
|
||||||
|
|
||||||
### **Might Add Later 📋**
|
|
||||||
- [ ] Exception handling (try/catch)
|
|
||||||
- [ ] Pattern matching
|
|
||||||
- [ ] Simple objects
|
|
||||||
- [ ] Module/import system
|
|
||||||
- [ ] More built-in functions
|
|
||||||
- [ ] Debugging tools
|
|
||||||
|
|
||||||
## **Resources**
|
|
||||||
|
|
||||||
- **[Language Reference](BOB_LANGUAGE_REFERENCE.md)** - Language documentation
|
|
||||||
- **[Build Guide](BUILD.md)** - How to build Bob
|
|
||||||
- **[Test Suite](../test_bob_language.bob)** - 70+ tests
|
|
||||||
- **[Crafting Interpreters](https://craftinginterpreters.com/)** - Helpful book for language implementation
|
|
||||||
|
|
||||||
## **Recent Work**
|
|
||||||
|
|
||||||
### **Architecture Cleanup (2025)**
|
|
||||||
- Split the interpreter into separate components (Evaluator/Executor/RuntimeDiagnostics)
|
|
||||||
- Switched to CMake + Ninja build system (3x faster builds)
|
|
||||||
- Reorganized code into cleaner modules
|
|
||||||
- Added Windows/macOS/Linux build support
|
|
||||||
|
|
||||||
### **Feature Completion**
|
|
||||||
- Added file I/O and type conversion functions
|
|
||||||
- Implemented all the operators I wanted (bitwise, compound assignment, etc.)
|
|
||||||
- Got arrays and dictionaries working properly
|
|
||||||
- Added tail call optimization and closures
|
|
||||||
|
|
||||||
### **Testing & Polish**
|
|
||||||
- Wrote 70+ tests covering pretty much everything
|
|
||||||
- Improved error messages to be more helpful
|
|
||||||
- Fixed memory leaks using smart pointers
|
|
||||||
- Various performance improvements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Bob works well for what I wanted - a programming language with the features and syntax I prefer.
|
|
||||||
|
|
||||||
*Last updated: January 2025*
|
|
||||||
30
benchmark.py
Normal file
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,132 +0,0 @@
|
|||||||
# Bob Language Extension for VS Code
|
|
||||||
|
|
||||||
This extension provides syntax highlighting and language support for the Bob programming language in Visual Studio Code and Cursor.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Syntax Highlighting**: Full syntax highlighting for Bob language constructs
|
|
||||||
- **Code Snippets**: Useful code snippets for common Bob patterns
|
|
||||||
- **Auto-closing Brackets**: Automatic bracket and quote pairing
|
|
||||||
- **Indentation**: Smart indentation for Bob code blocks
|
|
||||||
- **Comments**: Support for line and block comments
|
|
||||||
- **Folding**: Code folding support with region markers
|
|
||||||
|
|
||||||
## Supported Syntax
|
|
||||||
|
|
||||||
### Keywords
|
|
||||||
- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return`
|
|
||||||
- Variable declaration: `var`
|
|
||||||
- Function declaration: `func`
|
|
||||||
- Classes and OOP: `class`, `extends`, `extension`, `this`, `super`
|
|
||||||
- Logical operators: `and`, `or`, `not`
|
|
||||||
|
|
||||||
### Built-in Functions
|
|
||||||
- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `toInt()`, `time()`, `sleep()`, `printRaw()`
|
|
||||||
- Arrays/Dictionaries (preferred method style): `arr.len()`, `arr.push(...)`, `arr.pop()`, `dict.len()`, `dict.keys()`, `dict.values()`, `dict.has()`
|
|
||||||
- Global forms still available: `len(x)`, `push(arr, ...)`, `pop(arr)`, `keys(dict)`, `values(dict)`, `has(dict, key)`
|
|
||||||
- Misc: `rand.random()`, `eval.eval()`
|
|
||||||
|
|
||||||
### Data Types
|
|
||||||
- Numbers (integers, floats, binary `0b1010`, hex `0xFF`)
|
|
||||||
- Strings (single and double quoted)
|
|
||||||
- Booleans (`true`, `false`)
|
|
||||||
- None value (`none`)
|
|
||||||
- Arrays (`[1, 2, 3]`)
|
|
||||||
|
|
||||||
### Operators
|
|
||||||
- Arithmetic: `+`, `-`, `*`, `/`, `%`
|
|
||||||
- Comparison: `==`, `!=`, `<`, `>`, `<=`, `>=`
|
|
||||||
- Logical: `&&`, `||`, `!`
|
|
||||||
- Bitwise: `&`, `|`, `^`, `<<`, `>>`, `~`
|
|
||||||
- Compound assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
|
||||||
- Ternary: `condition ? valueIfTrue : valueIfFalse`
|
|
||||||
- String multiplication: `"hello" * 3`
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### From Source
|
|
||||||
1. Clone this repository
|
|
||||||
2. Run `npm install` to install dependencies
|
|
||||||
3. Run `npm run compile` to build the extension
|
|
||||||
4. Press `F5` in VS Code to launch the extension in a new window
|
|
||||||
|
|
||||||
### Manual Installation
|
|
||||||
1. Copy the extension files to your VS Code extensions directory
|
|
||||||
2. Restart VS Code
|
|
||||||
3. Open a `.bob` file to see syntax highlighting
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Code Snippets
|
|
||||||
Type the following prefixes and press `Tab` to insert code snippets:
|
|
||||||
|
|
||||||
- `func` - Function definition
|
|
||||||
- `if` - If statement
|
|
||||||
- `ifelse` - If-else statement
|
|
||||||
- `while` - While loop
|
|
||||||
- `for` - For loop
|
|
||||||
- `var` - Variable declaration
|
|
||||||
- `print` - Print statement
|
|
||||||
- `assert` - Assert statement
|
|
||||||
- `anon` - Anonymous function
|
|
||||||
- `return` - Return statement
|
|
||||||
- `break` - Break statement
|
|
||||||
- `continue` - Continue statement
|
|
||||||
- `comment` - Comment block
|
|
||||||
- `test` - Test function
|
|
||||||
- `array` - Array declaration
|
|
||||||
- `arrayaccess` - Array access
|
|
||||||
- `arrayassign` - Array assignment
|
|
||||||
- `len` - Array length
|
|
||||||
- `push` - Array push
|
|
||||||
- `pop` - Array pop
|
|
||||||
- `random` - Random number
|
|
||||||
- `sleep` - Sleep function
|
|
||||||
- `printraw` - Print raw
|
|
||||||
- `eval` - Eval function
|
|
||||||
|
|
||||||
### File Association
|
|
||||||
Files with the `.bob` extension will automatically be recognized as Bob language files.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
// This is a comment
|
|
||||||
var message = "Hello, Bob!";
|
|
||||||
print(message);
|
|
||||||
|
|
||||||
// Array operations (method style)
|
|
||||||
var numbers = [1, 2, 3, 4, 5];
|
|
||||||
print("Array length: " + numbers.len());
|
|
||||||
numbers.push(6);
|
|
||||||
print("Popped: " + numbers.pop());
|
|
||||||
print("First element: " + numbers[0]);
|
|
||||||
|
|
||||||
// Function with ternary operator
|
|
||||||
func factorial(n) {
|
|
||||||
if (n <= 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return n * factorial(n - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = factorial(5);
|
|
||||||
var status = result > 100 ? "large" : "small";
|
|
||||||
assert(result == 120, "Factorial calculation failed");
|
|
||||||
|
|
||||||
// String multiplication
|
|
||||||
var repeated = "hello" * 3; // "hellohellohello"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This extension is licensed under the MIT License.
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- [Bob Language Repository](https://github.com/bob-lang/bob)
|
|
||||||
- [VS Code Extension API](https://code.visualstudio.com/api)
|
|
||||||
Binary file not shown.
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: " + numbers.len());
|
|
||||||
print("First element: " + numbers[0]);
|
|
||||||
|
|
||||||
numbers[2] = 99; // Array assignment
|
|
||||||
numbers.push(6); // Add element
|
|
||||||
var lastElement = numbers.pop(); // Remove and get last element
|
|
||||||
|
|
||||||
// Function definition
|
|
||||||
func factorial(n) {
|
|
||||||
if (n <= 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return n * factorial(n - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// While loop with break
|
|
||||||
var counter = 0;
|
|
||||||
while (counter < 10) {
|
|
||||||
if (counter == 5) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
counter = counter + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For loop with continue
|
|
||||||
var sum = 0;
|
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
|
||||||
if (i % 2 == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sum = sum + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do-while loop
|
|
||||||
var doCounter = 0;
|
|
||||||
do {
|
|
||||||
doCounter = doCounter + 1;
|
|
||||||
} while (doCounter < 5);
|
|
||||||
|
|
||||||
// Anonymous function
|
|
||||||
var double = func(x) {
|
|
||||||
return x * 2;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ternary operator
|
|
||||||
var max = 10 > 5 ? 10 : 5;
|
|
||||||
var status = age >= 18 ? "adult" : "minor";
|
|
||||||
|
|
||||||
// String multiplication
|
|
||||||
var repeated = "hello" * 3; // "hellohellohello"
|
|
||||||
var numRepeated = 3 * "hi"; // "hihihi"
|
|
||||||
|
|
||||||
// Logical operators
|
|
||||||
var a = true && false;
|
|
||||||
var b = true || false;
|
|
||||||
var c = !false;
|
|
||||||
|
|
||||||
// Bitwise operators
|
|
||||||
var d = 5 & 3;
|
|
||||||
var e = 5 | 3;
|
|
||||||
var f = 5 ^ 3;
|
|
||||||
var g = 5 << 1;
|
|
||||||
var h = 5 >> 1;
|
|
||||||
|
|
||||||
// Compound assignment
|
|
||||||
var value = 10;
|
|
||||||
value += 5;
|
|
||||||
value *= 2;
|
|
||||||
value -= 3;
|
|
||||||
|
|
||||||
// New built-in functions
|
|
||||||
import rand; var randomValue = rand.random();
|
|
||||||
sleep(100); // Sleep for 100ms
|
|
||||||
printRaw("No newline here");
|
|
||||||
eval("print('Dynamic code execution!');");
|
|
||||||
|
|
||||||
// Assertions
|
|
||||||
assert(factorial(5) == 120, "Factorial calculation failed");
|
|
||||||
assert(sum == 25, "Sum calculation failed");
|
|
||||||
|
|
||||||
// Type checking
|
|
||||||
var typeOfMessage = type(message);
|
|
||||||
var typeOfNumber = type(number);
|
|
||||||
var typeOfArray = type(numbers);
|
|
||||||
|
|
||||||
// Time function
|
|
||||||
var currentTime = time();
|
|
||||||
|
|
||||||
print("All tests passed!");
|
|
||||||
@ -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|class|extension)\\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.5.0",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.60.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Programming Languages"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"bob",
|
|
||||||
"programming",
|
|
||||||
"language",
|
|
||||||
"syntax",
|
|
||||||
"highlighting"
|
|
||||||
],
|
|
||||||
"publisher": "bob-lang",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/bob-lang/bob-vscode-extension"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./out/extension.js",
|
|
||||||
"activationEvents": [
|
|
||||||
"onLanguage:bob"
|
|
||||||
],
|
|
||||||
"contributes": {
|
|
||||||
"languages": [
|
|
||||||
{
|
|
||||||
"id": "bob",
|
|
||||||
"aliases": [
|
|
||||||
"Bob",
|
|
||||||
"bob"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
".bob"
|
|
||||||
],
|
|
||||||
"configuration": "./language-configuration.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
|
||||||
{
|
|
||||||
"language": "bob",
|
|
||||||
"scopeName": "source.bob",
|
|
||||||
"path": "./syntaxes/bob.tmLanguage.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"snippets": [
|
|
||||||
{
|
|
||||||
"language": "bob",
|
|
||||||
"path": "./snippets/bob.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"themes": [
|
|
||||||
{
|
|
||||||
"label": "Bob Dark",
|
|
||||||
"uiTheme": "vs-dark",
|
|
||||||
"path": "./themes/bob-dark.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"vscode:prepublish": "npm run compile",
|
|
||||||
"compile": "tsc -p ./",
|
|
||||||
"watch": "tsc -watch -p ./"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/vscode": "^1.60.0",
|
|
||||||
"@types/node": "^16.0.0",
|
|
||||||
"typescript": "^4.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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,488 +0,0 @@
|
|||||||
{
|
|
||||||
"Function Definition": {
|
|
||||||
"prefix": "func",
|
|
||||||
"body": [
|
|
||||||
"func ${1:functionName}(${2:parameters}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a new function"
|
|
||||||
},
|
|
||||||
"If Statement": {
|
|
||||||
"prefix": "if",
|
|
||||||
"body": [
|
|
||||||
"if (${1:condition}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an if statement"
|
|
||||||
},
|
|
||||||
"If-Else Statement": {
|
|
||||||
"prefix": "ifelse",
|
|
||||||
"body": [
|
|
||||||
"if (${1:condition}) {",
|
|
||||||
"\t$2",
|
|
||||||
"} else {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an if-else statement"
|
|
||||||
},
|
|
||||||
"While Loop": {
|
|
||||||
"prefix": "while",
|
|
||||||
"body": [
|
|
||||||
"while (${1:condition}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a while loop"
|
|
||||||
},
|
|
||||||
"For Loop": {
|
|
||||||
"prefix": "for",
|
|
||||||
"body": [
|
|
||||||
"for (${1:initialization}; ${2:condition}; ${3:increment}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a for loop"
|
|
||||||
},
|
|
||||||
"Variable Declaration": {
|
|
||||||
"prefix": "var",
|
|
||||||
"body": [
|
|
||||||
"var ${1:variableName} = ${2:value};"
|
|
||||||
],
|
|
||||||
"description": "Declare a variable"
|
|
||||||
},
|
|
||||||
"Class Declaration": {
|
|
||||||
"prefix": "class",
|
|
||||||
"body": [
|
|
||||||
"class ${1:ClassName} ${2:extends ${3:Parent}} {",
|
|
||||||
" var ${4:field} = ${5:none};",
|
|
||||||
" func init(${6:args}) {",
|
|
||||||
" this.${4:field} = ${7:value};",
|
|
||||||
" }",
|
|
||||||
" func ${8:method}(${9:params}) {",
|
|
||||||
" $0",
|
|
||||||
" }",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a class with optional extends, fields, init, and method"
|
|
||||||
},
|
|
||||||
"Extension Block": {
|
|
||||||
"prefix": "extension",
|
|
||||||
"body": [
|
|
||||||
"extension ${1:Target} {",
|
|
||||||
" func ${2:method}(${3:params}) {",
|
|
||||||
" $0",
|
|
||||||
" }",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an extension for a class or builtin (string, array, dict, number, any)"
|
|
||||||
}
|
|
||||||
"Print Statement": {
|
|
||||||
"prefix": "print",
|
|
||||||
"body": [
|
|
||||||
"print(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Print a value"
|
|
||||||
},
|
|
||||||
"Assert Statement": {
|
|
||||||
"prefix": "assert",
|
|
||||||
"body": [
|
|
||||||
"assert(${1:condition}, \"${2:message}\");"
|
|
||||||
],
|
|
||||||
"description": "Create an assertion"
|
|
||||||
},
|
|
||||||
"Anonymous Function": {
|
|
||||||
"prefix": "anon",
|
|
||||||
"body": [
|
|
||||||
"func(${1:parameters}) {",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create an anonymous function"
|
|
||||||
},
|
|
||||||
"Return Statement": {
|
|
||||||
"prefix": "return",
|
|
||||||
"body": [
|
|
||||||
"return ${1:value};"
|
|
||||||
],
|
|
||||||
"description": "Return a value from function"
|
|
||||||
},
|
|
||||||
"Break Statement": {
|
|
||||||
"prefix": "break",
|
|
||||||
"body": [
|
|
||||||
"break;"
|
|
||||||
],
|
|
||||||
"description": "Break out of loop"
|
|
||||||
},
|
|
||||||
"Continue Statement": {
|
|
||||||
"prefix": "continue",
|
|
||||||
"body": [
|
|
||||||
"continue;"
|
|
||||||
],
|
|
||||||
"description": "Continue to next iteration"
|
|
||||||
},
|
|
||||||
"Comment Block": {
|
|
||||||
"prefix": "comment",
|
|
||||||
"body": [
|
|
||||||
"/*",
|
|
||||||
" * ${1:comment}",
|
|
||||||
" */"
|
|
||||||
],
|
|
||||||
"description": "Create a comment block"
|
|
||||||
},
|
|
||||||
"Test Function": {
|
|
||||||
"prefix": "test",
|
|
||||||
"body": [
|
|
||||||
"func test${1:TestName}() {",
|
|
||||||
"\tvar result = ${2:testExpression};",
|
|
||||||
"\tassert(result == ${3:expectedValue}, \"${4:test message}\");",
|
|
||||||
"\tprint(\"${1:TestName}: PASS\");",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a test function"
|
|
||||||
},
|
|
||||||
"Higher-Order Function": {
|
|
||||||
"prefix": "hof",
|
|
||||||
"body": [
|
|
||||||
"func ${1:functionName}(${2:callback}) {",
|
|
||||||
"\treturn func(${3:params}) {",
|
|
||||||
"\t\t$0",
|
|
||||||
"\t};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a higher-order function"
|
|
||||||
},
|
|
||||||
"Closure": {
|
|
||||||
"prefix": "closure",
|
|
||||||
"body": [
|
|
||||||
"func ${1:outerFunction}(${2:param}) {",
|
|
||||||
"\tvar ${3:capturedVar} = ${4:value};",
|
|
||||||
"\treturn func(${5:innerParam}) {",
|
|
||||||
"\t\treturn ${3:capturedVar} + ${5:innerParam};",
|
|
||||||
"\t};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a closure"
|
|
||||||
},
|
|
||||||
"Counter Pattern": {
|
|
||||||
"prefix": "counter",
|
|
||||||
"body": [
|
|
||||||
"func createCounter() {",
|
|
||||||
"\tvar count = 0;",
|
|
||||||
"\treturn func() {",
|
|
||||||
"\t\tcount = count + 1;",
|
|
||||||
"\t\treturn count;",
|
|
||||||
"\t};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a counter function"
|
|
||||||
},
|
|
||||||
"Loop with Break": {
|
|
||||||
"prefix": "loopbreak",
|
|
||||||
"body": [
|
|
||||||
"while (${1:condition}) {",
|
|
||||||
"\tif (${2:breakCondition}) {",
|
|
||||||
"\t\tbreak;",
|
|
||||||
"\t}",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a loop with break condition"
|
|
||||||
},
|
|
||||||
"Loop with Continue": {
|
|
||||||
"prefix": "loopcontinue",
|
|
||||||
"body": [
|
|
||||||
"for (${1:initialization}; ${2:condition}; ${3:increment}) {",
|
|
||||||
"\tif (${4:continueCondition}) {",
|
|
||||||
"\t\tcontinue;",
|
|
||||||
"\t}",
|
|
||||||
"\t$0",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a loop with continue condition"
|
|
||||||
},
|
|
||||||
"Nested Loop": {
|
|
||||||
"prefix": "nestedloop",
|
|
||||||
"body": [
|
|
||||||
"for (var i = 0; i < ${1:outerLimit}; i = i + 1) {",
|
|
||||||
"\tfor (var j = 0; j < ${2:innerLimit}; j = j + 1) {",
|
|
||||||
"\t\t$0",
|
|
||||||
"\t}",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create nested loops"
|
|
||||||
},
|
|
||||||
"Function with Early Return": {
|
|
||||||
"prefix": "earlyreturn",
|
|
||||||
"body": [
|
|
||||||
"func ${1:functionName}(${2:param}) {",
|
|
||||||
"\tif (${3:condition}) {",
|
|
||||||
"\t\treturn ${4:earlyValue};",
|
|
||||||
"\t}",
|
|
||||||
"\t$0",
|
|
||||||
"\treturn ${5:defaultValue};",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
"description": "Create a function with early return"
|
|
||||||
},
|
|
||||||
"String Concatenation": {
|
|
||||||
"prefix": "concat",
|
|
||||||
"body": [
|
|
||||||
"var result = \"${1:first}\" + \"${2:second}\";"
|
|
||||||
],
|
|
||||||
"description": "Concatenate strings"
|
|
||||||
},
|
|
||||||
"Number Operations": {
|
|
||||||
"prefix": "math",
|
|
||||||
"body": [
|
|
||||||
"var result = ${1:expression};"
|
|
||||||
],
|
|
||||||
"description": "Mathematical expression"
|
|
||||||
},
|
|
||||||
"Boolean Logic": {
|
|
||||||
"prefix": "bool",
|
|
||||||
"body": [
|
|
||||||
"var result = ${1:condition} == ${2:value};"
|
|
||||||
],
|
|
||||||
"description": "Boolean comparison"
|
|
||||||
},
|
|
||||||
"Type Check": {
|
|
||||||
"prefix": "type",
|
|
||||||
"body": [
|
|
||||||
"var typeResult = type(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Check type of expression"
|
|
||||||
},
|
|
||||||
"Array Declaration": {
|
|
||||||
"prefix": "array",
|
|
||||||
"body": [
|
|
||||||
"var ${1:arrayName} = [${2:element1}, ${3:element2}];"
|
|
||||||
],
|
|
||||||
"description": "Declare an array"
|
|
||||||
},
|
|
||||||
"Array Access": {
|
|
||||||
"prefix": "arrayaccess",
|
|
||||||
"body": [
|
|
||||||
"var element = ${1:arrayName}[${2:index}];"
|
|
||||||
],
|
|
||||||
"description": "Access array element"
|
|
||||||
},
|
|
||||||
"Array Assignment": {
|
|
||||||
"prefix": "arrayassign",
|
|
||||||
"body": [
|
|
||||||
"${1:arrayName}[${2:index}] = ${3:value};"
|
|
||||||
],
|
|
||||||
"description": "Assign value to array element"
|
|
||||||
},
|
|
||||||
"Array Length": {
|
|
||||||
"prefix": "len",
|
|
||||||
"body": [
|
|
||||||
"var length = len(${1:arrayName});"
|
|
||||||
],
|
|
||||||
"description": "Get array length"
|
|
||||||
},
|
|
||||||
"Array Push": {
|
|
||||||
"prefix": "push",
|
|
||||||
"body": [
|
|
||||||
"push(${1:arrayName}, ${2:value});"
|
|
||||||
],
|
|
||||||
"description": "Add element to array"
|
|
||||||
},
|
|
||||||
"Array Pop": {
|
|
||||||
"prefix": "pop",
|
|
||||||
"body": [
|
|
||||||
"var element = pop(${1:arrayName});"
|
|
||||||
],
|
|
||||||
"description": "Remove and return last element"
|
|
||||||
},
|
|
||||||
"Random Number": {
|
|
||||||
"prefix": "random",
|
|
||||||
"body": [
|
|
||||||
"import rand; var randomValue = rand.random();"
|
|
||||||
],
|
|
||||||
"description": "Generate random number"
|
|
||||||
},
|
|
||||||
"Sleep": {
|
|
||||||
"prefix": "sleep",
|
|
||||||
"body": [
|
|
||||||
"sleep(${1:milliseconds});"
|
|
||||||
],
|
|
||||||
"description": "Sleep for specified milliseconds"
|
|
||||||
},
|
|
||||||
"Print Raw": {
|
|
||||||
"prefix": "printraw",
|
|
||||||
"body": [
|
|
||||||
"printRaw(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Print without newline"
|
|
||||||
},
|
|
||||||
"Eval": {
|
|
||||||
"prefix": "eval",
|
|
||||||
"body": [
|
|
||||||
"eval(\"${1:code}\");"
|
|
||||||
],
|
|
||||||
"description": "Evaluate dynamic code"
|
|
||||||
},
|
|
||||||
"ToString": {
|
|
||||||
"prefix": "tostring",
|
|
||||||
"body": [
|
|
||||||
"var stringResult = toString(${1:expression});"
|
|
||||||
],
|
|
||||||
"description": "Convert to string"
|
|
||||||
},
|
|
||||||
"Test Suite Header": {
|
|
||||||
"prefix": "testsuite",
|
|
||||||
"body": [
|
|
||||||
"// ========================================",
|
|
||||||
"// ${1:TEST SUITE NAME}",
|
|
||||||
"// ========================================",
|
|
||||||
"// ${2:Description}",
|
|
||||||
"",
|
|
||||||
"print(\"${1:TEST SUITE NAME}\");",
|
|
||||||
"print(\"Running tests...\");",
|
|
||||||
"",
|
|
||||||
"$0",
|
|
||||||
"",
|
|
||||||
"print(\"All tests passed!\");"
|
|
||||||
],
|
|
||||||
"description": "Create a test suite header"
|
|
||||||
},
|
|
||||||
"Test Section": {
|
|
||||||
"prefix": "testsection",
|
|
||||||
"body": [
|
|
||||||
"// ========================================",
|
|
||||||
"// TEST ${1:NUMBER}: ${2:TEST NAME}",
|
|
||||||
"// ========================================",
|
|
||||||
"print(\"\\n--- Test ${1:NUMBER}: ${2:TEST NAME} ---\");",
|
|
||||||
"",
|
|
||||||
"$0",
|
|
||||||
"",
|
|
||||||
"print(\"${2:TEST NAME}: PASS\");"
|
|
||||||
],
|
|
||||||
"description": "Create a test section"
|
|
||||||
},
|
|
||||||
"Debug Print": {
|
|
||||||
"prefix": "debug",
|
|
||||||
"body": [
|
|
||||||
"print(\"DEBUG: ${1:variable} = \" + toString(${1:variable}));"
|
|
||||||
],
|
|
||||||
"description": "Debug print statement"
|
|
||||||
},
|
|
||||||
"Error Message": {
|
|
||||||
"prefix": "error",
|
|
||||||
"body": [
|
|
||||||
"print(\"ERROR: ${1:error message}\");"
|
|
||||||
],
|
|
||||||
"description": "Error message print"
|
|
||||||
},
|
|
||||||
"Success Message": {
|
|
||||||
"prefix": "success",
|
|
||||||
"body": [
|
|
||||||
"print(\"SUCCESS: ${1:success message}\");"
|
|
||||||
],
|
|
||||||
"description": "Success message print"
|
|
||||||
},
|
|
||||||
"ToInt": {
|
|
||||||
"prefix": "toint",
|
|
||||||
"body": [
|
|
||||||
"var intValue = toInt(${1:floatValue});"
|
|
||||||
],
|
|
||||||
"description": "Convert float to integer"
|
|
||||||
},
|
|
||||||
"Compound Assignment": {
|
|
||||||
"prefix": "compound",
|
|
||||||
"body": [
|
|
||||||
"${1:variable} += ${2:value};"
|
|
||||||
],
|
|
||||||
"description": "Compound assignment operator"
|
|
||||||
},
|
|
||||||
"Increment": {
|
|
||||||
"prefix": "inc",
|
|
||||||
"body": [
|
|
||||||
"${1:variable}++;"
|
|
||||||
],
|
|
||||||
"description": "Increment variable"
|
|
||||||
},
|
|
||||||
"Decrement": {
|
|
||||||
"prefix": "dec",
|
|
||||||
"body": [
|
|
||||||
"${1:variable}--;"
|
|
||||||
],
|
|
||||||
"description": "Decrement variable"
|
|
||||||
},
|
|
||||||
"Array Increment": {
|
|
||||||
"prefix": "arrayinc",
|
|
||||||
"body": [
|
|
||||||
"${1:arrayName}[${2:index}]++;"
|
|
||||||
],
|
|
||||||
"description": "Increment array element"
|
|
||||||
},
|
|
||||||
"Array Decrement": {
|
|
||||||
"prefix": "arraydec",
|
|
||||||
"body": [
|
|
||||||
"${1:arrayName}[${2:index}]--;"
|
|
||||||
],
|
|
||||||
"description": "Decrement array element"
|
|
||||||
},
|
|
||||||
"Cross-Type Comparison": {
|
|
||||||
"prefix": "crosscomp",
|
|
||||||
"body": [
|
|
||||||
"var result = ${1:value1} == ${2:value2}; // Works with any types"
|
|
||||||
],
|
|
||||||
"description": "Cross-type comparison"
|
|
||||||
},
|
|
||||||
"Float Array Index": {
|
|
||||||
"prefix": "floatindex",
|
|
||||||
"body": [
|
|
||||||
"var element = ${1:arrayName}[${2:floatIndex}]; // Auto-truncates to int"
|
|
||||||
],
|
|
||||||
"description": "Array access with float index (auto-truncates)"
|
|
||||||
},
|
|
||||||
"Dictionary Literal": {
|
|
||||||
"prefix": "dict",
|
|
||||||
"body": [
|
|
||||||
"var ${1:dictName} = {",
|
|
||||||
"\t\"${2:key1}\": ${3:value1},",
|
|
||||||
"\t\"${4:key2}\": ${5:value2}",
|
|
||||||
"};"
|
|
||||||
],
|
|
||||||
"description": "Create a dictionary literal"
|
|
||||||
},
|
|
||||||
"Dictionary Access": {
|
|
||||||
"prefix": "dictaccess",
|
|
||||||
"body": [
|
|
||||||
"var value = ${1:dictName}[\"${2:key}\"];"
|
|
||||||
],
|
|
||||||
"description": "Access dictionary value"
|
|
||||||
},
|
|
||||||
"Dictionary Assignment": {
|
|
||||||
"prefix": "dictassign",
|
|
||||||
"body": [
|
|
||||||
"${1:dictName}[\"${2:key}\"] = ${3:value};"
|
|
||||||
],
|
|
||||||
"description": "Assign value to dictionary key"
|
|
||||||
},
|
|
||||||
"Dictionary Keys": {
|
|
||||||
"prefix": "keys",
|
|
||||||
"body": [
|
|
||||||
"var keys = keys(${1:dictionary});"
|
|
||||||
],
|
|
||||||
"description": "Get all keys from dictionary"
|
|
||||||
},
|
|
||||||
"Dictionary Values": {
|
|
||||||
"prefix": "values",
|
|
||||||
"body": [
|
|
||||||
"var values = values(${1:dictionary});"
|
|
||||||
],
|
|
||||||
"description": "Get all values from dictionary"
|
|
||||||
},
|
|
||||||
"Dictionary Has": {
|
|
||||||
"prefix": "has",
|
|
||||||
"body": [
|
|
||||||
"var exists = has(${1:dictionary}, \"${2:key}\");"
|
|
||||||
],
|
|
||||||
"description": "Check if dictionary has key"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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|class|extends|extension|this|super)\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.bob",
|
|
||||||
"match": "\\b(and|or|not)\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.language.bob",
|
|
||||||
"match": "\\b(true|false|none)\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"functions": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "entity.name.function.bob",
|
|
||||||
"match": "\\b(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
|
|
||||||
"captures": {
|
|
||||||
"1": { "name": "keyword.control.bob" },
|
|
||||||
"2": { "name": "entity.name.function.bob" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "support.function.builtin.bob",
|
|
||||||
"match": "\\b(print|assert|input|type|toString|toNumber|toInt|time|sleep|printRaw|len|push|pop|random|eval|keys|values|has)\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"variables": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "variable.other.bob",
|
|
||||||
"match": "\\bvar\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
|
|
||||||
"captures": {
|
|
||||||
"1": { "name": "keyword.control.bob" },
|
|
||||||
"2": { "name": "variable.other.bob" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "variable.other.bob",
|
|
||||||
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)(?=\\s*=)",
|
|
||||||
"captures": {
|
|
||||||
"1": { "name": "variable.other.bob" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"operators": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.increment.bob",
|
|
||||||
"match": "\\+\\+|--"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.compound.bob",
|
|
||||||
"match": "\\+=|-=|\\*=|/=|%=|&=|\\|=|\\^=|<<=|>>="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.comparison.bob",
|
|
||||||
"match": "==|!=|<=|>="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.logical.bob",
|
|
||||||
"match": "&&|\\|\\||!"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.bitwise.bob",
|
|
||||||
"match": "<<|>>|&|\\||\\^|~"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.arithmetic.bob",
|
|
||||||
"match": "\\+|-|\\*|/|%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.comparison.bob",
|
|
||||||
"match": "<|>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.assignment.bob",
|
|
||||||
"match": "="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.punctuation.bob",
|
|
||||||
"match": "\\(|\\)|\\{|\\}|\\[|\\]|,|;|\\."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.conditional.bob",
|
|
||||||
"match": "\\?|:"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"expressions": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#comments"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#strings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#numbers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#keywords"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#functions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#variables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#operators"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
13
bobby.bob
13
bobby.bob
@ -1,13 +0,0 @@
|
|||||||
class A {
|
|
||||||
var inner = 10;
|
|
||||||
|
|
||||||
func test(){
|
|
||||||
print(this.inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func hello(){
|
|
||||||
print("hello");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -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
@ -3,34 +3,21 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_set>
|
|
||||||
#include "Value.h"
|
#include "Value.h"
|
||||||
#include "Lexer.h"
|
#include "Lexer.h"
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ErrorReporter;
|
class ErrorReporter;
|
||||||
|
|
||||||
struct Environment {
|
class Environment {
|
||||||
public:
|
public:
|
||||||
Environment() : parent(nullptr), errorReporter(nullptr) {}
|
Environment() : parent(nullptr), errorReporter(nullptr) {}
|
||||||
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
|
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
|
||||||
|
|
||||||
// Copy constructor for closure snapshots - creates a deep copy of the environment chain
|
|
||||||
Environment(const Environment& other) : parent(nullptr), errorReporter(other.errorReporter) {
|
|
||||||
// Copy all variables normally - arrays will be handled by forceCleanup
|
|
||||||
variables = other.variables;
|
|
||||||
|
|
||||||
// Create a deep copy of the parent environment chain
|
|
||||||
if (other.parent) {
|
|
||||||
parent = std::make_shared<Environment>(*other.parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set error reporter for enhanced error reporting
|
// Set error reporter for enhanced error reporting
|
||||||
void setErrorReporter(ErrorReporter* reporter) {
|
void setErrorReporter(ErrorReporter* reporter) {
|
||||||
errorReporter = reporter;
|
errorReporter = reporter;
|
||||||
}
|
}
|
||||||
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
|
||||||
|
|
||||||
// Optimized define with inline
|
// Optimized define with inline
|
||||||
inline void define(const std::string& name, const Value& value) {
|
inline void define(const std::string& name, const Value& value) {
|
||||||
@ -43,18 +30,19 @@ public:
|
|||||||
// Enhanced get with error reporting
|
// Enhanced get with error reporting
|
||||||
Value get(const Token& name);
|
Value get(const Token& name);
|
||||||
|
|
||||||
// Prune heavy containers in a snapshot to avoid capture cycles
|
// Get by string name with error reporting
|
||||||
void pruneForClosureCapture();
|
Value get(const std::string& name);
|
||||||
|
|
||||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||||
// Export all variables (shallow copy) for module namespace
|
inline void clear() { variables.clear(); }
|
||||||
std::unordered_map<std::string, Value> getAll() const { return variables; }
|
|
||||||
|
|
||||||
|
// Set parent environment for TCO environment reuse
|
||||||
|
inline void setParent(std::shared_ptr<Environment> newParent) {
|
||||||
|
parent = newParent;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, Value> variables;
|
std::unordered_map<std::string, Value> variables;
|
||||||
std::shared_ptr<Environment> parent;
|
std::shared_ptr<Environment> parent;
|
||||||
ErrorReporter* errorReporter;
|
ErrorReporter* errorReporter;
|
||||||
std::unordered_set<std::string> constBindings;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -34,9 +34,6 @@ private:
|
|||||||
std::string currentFileName;
|
std::string currentFileName;
|
||||||
std::vector<std::string> callStack;
|
std::vector<std::string> callStack;
|
||||||
bool hadError = false;
|
bool hadError = false;
|
||||||
// Support nested sources (e.g., eval of external files)
|
|
||||||
std::vector<std::vector<std::string>> sourceStack;
|
|
||||||
std::vector<std::string> fileNameStack;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ErrorReporter() = default;
|
ErrorReporter() = default;
|
||||||
@ -51,9 +48,6 @@ public:
|
|||||||
// Check if an error has been reported
|
// Check if an error has been reported
|
||||||
bool hasReportedError() const { return hadError; }
|
bool hasReportedError() const { return hadError; }
|
||||||
|
|
||||||
// Reset error state (call this between REPL commands)
|
|
||||||
void resetErrorState() { hadError = false; }
|
|
||||||
|
|
||||||
// Report errors with full context
|
// Report errors with full context
|
||||||
void reportErrorWithContext(const ErrorContext& context);
|
void reportErrorWithContext(const ErrorContext& context);
|
||||||
|
|
||||||
@ -61,11 +55,6 @@ public:
|
|||||||
void pushCallStack(const std::string& functionName);
|
void pushCallStack(const std::string& functionName);
|
||||||
void popCallStack();
|
void popCallStack();
|
||||||
|
|
||||||
// Source push/pop for eval
|
|
||||||
void pushSource(const std::string& source, const std::string& fileName);
|
|
||||||
void popSource();
|
|
||||||
const std::string& getCurrentFileName() const { return currentFileName; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
||||||
void displayCallStack(const std::vector<std::string>& callStack);
|
void displayCallStack(const std::vector<std::string>& callStack);
|
||||||
@ -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,18 +15,13 @@ enum TokenType{
|
|||||||
GREATER, GREATER_EQUAL,
|
GREATER, GREATER_EQUAL,
|
||||||
LESS, LESS_EQUAL,
|
LESS, LESS_EQUAL,
|
||||||
|
|
||||||
// Ternary operator
|
|
||||||
QUESTION, COLON,
|
|
||||||
|
|
||||||
// Increment/decrement operators
|
// Increment/decrement operators
|
||||||
PLUS_PLUS, MINUS_MINUS,
|
PLUS_PLUS, MINUS_MINUS,
|
||||||
|
|
||||||
IDENTIFIER, STRING, NUMBER, KW_BOOL,
|
IDENTIFIER, STRING, NUMBER, BOOL,
|
||||||
|
|
||||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||||
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
|
||||||
IMPORT, FROM, AS,
|
|
||||||
TRY, CATCH, FINALLY, THROW,
|
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||||
@ -39,7 +33,6 @@ enum TokenType{
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
|
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
|
||||||
"OPEN_BRACKET", "CLOSE_BRACKET", // Array brackets
|
|
||||||
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
|
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
|
||||||
|
|
||||||
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
||||||
@ -49,16 +42,12 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
|||||||
"GREATER", "GREATER_EQUAL",
|
"GREATER", "GREATER_EQUAL",
|
||||||
"LESS", "LESS_EQUAL",
|
"LESS", "LESS_EQUAL",
|
||||||
|
|
||||||
"QUESTION", "COLON",
|
|
||||||
|
|
||||||
"PLUS_PLUS", "MINUS_MINUS",
|
"PLUS_PLUS", "MINUS_MINUS",
|
||||||
|
|
||||||
"IDENTIFIER", "STRING", "NUMBER", "KW_BOOL",
|
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
||||||
|
|
||||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||||
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
|
||||||
"IMPORT", "FROM", "AS",
|
|
||||||
"TRY", "CATCH", "FINALLY", "THROW",
|
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||||
@ -78,24 +67,12 @@ const std::map<std::string, TokenType> KEYWORDS {
|
|||||||
{"func", FUNCTION},
|
{"func", FUNCTION},
|
||||||
{"for", FOR},
|
{"for", FOR},
|
||||||
{"while", WHILE},
|
{"while", WHILE},
|
||||||
{"do", DO},
|
|
||||||
{"var", VAR},
|
{"var", VAR},
|
||||||
{"class", CLASS},
|
{"class", CLASS},
|
||||||
{"extends", EXTENDS},
|
|
||||||
{"extension", EXTENSION},
|
|
||||||
{"super", SUPER},
|
{"super", SUPER},
|
||||||
{"this", THIS},
|
{"this", THIS},
|
||||||
{"none", NONE},
|
{"none", NONE},
|
||||||
{"return", RETURN},
|
{"return", RETURN},
|
||||||
{"break", BREAK},
|
|
||||||
{"continue", CONTINUE},
|
|
||||||
{"import", IMPORT},
|
|
||||||
{"from", FROM},
|
|
||||||
{"as", AS},
|
|
||||||
{"try", TRY},
|
|
||||||
{"catch", CATCH},
|
|
||||||
{"finally", FINALLY},
|
|
||||||
{"throw", THROW},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Token
|
struct Token
|
||||||
@ -24,7 +24,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
sptr(Expr) expression();
|
sptr(Expr) expression();
|
||||||
sptr(Expr) logical_or();
|
sptr(Expr) logical_or();
|
||||||
sptr(Expr) ternary();
|
|
||||||
sptr(Expr) logical_and();
|
sptr(Expr) logical_and();
|
||||||
sptr(Expr) bitwise_or();
|
sptr(Expr) bitwise_or();
|
||||||
sptr(Expr) bitwise_xor();
|
sptr(Expr) bitwise_xor();
|
||||||
@ -57,45 +56,20 @@ private:
|
|||||||
|
|
||||||
std::shared_ptr<Stmt> ifStatement();
|
std::shared_ptr<Stmt> ifStatement();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> whileStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> doWhileStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> forStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> breakStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> continueStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> declaration();
|
std::shared_ptr<Stmt> declaration();
|
||||||
std::shared_ptr<Stmt> classDeclaration();
|
|
||||||
std::shared_ptr<Stmt> extensionDeclaration();
|
|
||||||
std::shared_ptr<Stmt> tryStatement();
|
|
||||||
std::shared_ptr<Stmt> throwStatement();
|
|
||||||
std::shared_ptr<Stmt> importStatement();
|
|
||||||
std::shared_ptr<Stmt> fromImportStatement();
|
|
||||||
|
|
||||||
std::shared_ptr<Stmt> varDeclaration();
|
std::shared_ptr<Stmt> varDeclaration();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> functionDeclaration();
|
std::shared_ptr<Stmt> functionDeclaration();
|
||||||
std::shared_ptr<Expr> functionExpression();
|
std::shared_ptr<Expr> functionExpression();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> assignmentStatement();
|
|
||||||
sptr(Expr) assignment();
|
sptr(Expr) assignment();
|
||||||
sptr(Expr) assignmentExpression(); // For for loop increment clauses
|
|
||||||
sptr(Expr) increment(); // Parse increment/decrement expressions
|
sptr(Expr) increment(); // Parse increment/decrement expressions
|
||||||
sptr(Expr) postfix(); // Parse postfix operators
|
sptr(Expr) postfix(); // Parse postfix operators
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Stmt>> block();
|
std::vector<std::shared_ptr<Stmt>> block();
|
||||||
|
|
||||||
sptr(Expr) finishCall(sptr(Expr) callee);
|
sptr(Expr) finishCall(sptr(Expr) callee);
|
||||||
sptr(Expr) finishArrayIndex(sptr(Expr) array);
|
|
||||||
sptr(Expr) finishArrayAssign(sptr(Expr) array, sptr(Expr) index, sptr(Expr) value);
|
|
||||||
sptr(Expr) finishDictIndex(sptr(Expr) dict);
|
|
||||||
sptr(Expr) finishDictAssign(sptr(Expr) dict, sptr(Expr) key, sptr(Expr) value);
|
|
||||||
sptr(Expr) arrayLiteral();
|
|
||||||
sptr(Expr) dictLiteral();
|
|
||||||
sptr(Expr) call(); // Handle call chains (function calls, array indexing, and dict indexing)
|
|
||||||
|
|
||||||
// Helper methods for function scope tracking
|
// Helper methods for function scope tracking
|
||||||
void enterFunction() { functionDepth++; }
|
void enterFunction() { functionDepth++; }
|
||||||
120
headers/Statement.h
Normal file
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);
|
||||||
};
|
};
|
||||||
@ -10,22 +10,51 @@
|
|||||||
struct Stmt;
|
struct Stmt;
|
||||||
struct Environment;
|
struct Environment;
|
||||||
|
|
||||||
struct Function
|
struct Object
|
||||||
|
{
|
||||||
|
virtual ~Object(){};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Number : Object
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
explicit Number(double value) : value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct String : Object
|
||||||
|
{
|
||||||
|
std::string value;
|
||||||
|
explicit String(std::string str) : value(str) {}
|
||||||
|
~String(){
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Boolean : Object
|
||||||
|
{
|
||||||
|
bool value;
|
||||||
|
explicit Boolean(bool value) : value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct None : public Object
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Function : public Object
|
||||||
{
|
{
|
||||||
const std::string name;
|
const std::string name;
|
||||||
const std::vector<std::string> params;
|
const std::vector<std::string> params;
|
||||||
const std::vector<std::shared_ptr<Stmt>> body;
|
const std::vector<std::shared_ptr<Stmt>> body;
|
||||||
const std::shared_ptr<Environment> closure;
|
const std::shared_ptr<Environment> closure;
|
||||||
const std::string ownerClass; // empty for non-methods
|
|
||||||
|
|
||||||
Function(std::string name, std::vector<std::string> params,
|
Function(std::string name, std::vector<std::string> params,
|
||||||
std::vector<std::shared_ptr<Stmt>> body,
|
std::vector<std::shared_ptr<Stmt>> body,
|
||||||
std::shared_ptr<Environment> closure,
|
std::shared_ptr<Environment> closure)
|
||||||
std::string ownerClass = "")
|
: name(name), params(params), body(body), closure(closure) {}
|
||||||
: name(name), params(params), body(body), closure(closure), ownerClass(ownerClass) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BuiltinFunction
|
struct BuiltinFunction : public Object
|
||||||
{
|
{
|
||||||
const std::string name;
|
const std::string name;
|
||||||
const std::function<Value(std::vector<Value>, int, int)> func;
|
const std::function<Value(std::vector<Value>, int, int)> func;
|
||||||
274
headers/Value.h
Normal file
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;
|
||||||
29
headers/bob.h
Normal file
29
headers/bob.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include "../headers/Lexer.h"
|
||||||
|
#include "../headers/Interpreter.h"
|
||||||
|
#include "../headers/helperFunctions/ShortHands.h"
|
||||||
|
#include "../headers/ErrorReporter.h"
|
||||||
|
|
||||||
|
#define VERSION "0.0.1"
|
||||||
|
|
||||||
|
class Bob
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Lexer lexer;
|
||||||
|
sptr(Interpreter) interpreter;
|
||||||
|
ErrorReporter errorReporter;
|
||||||
|
|
||||||
|
~Bob() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void runFile(const std::string& path);
|
||||||
|
void runPrompt();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run(std::string source);
|
||||||
|
};
|
||||||
|
|
||||||
@ -3,9 +3,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
inline std::vector<std::string> splitString(const std::string& input, const std::string& delimiter) {
|
inline std::vector<std::string> splitString(const std::string& input, std::string delimiter) {
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
std::string token;
|
std::string token;
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
@ -57,9 +56,9 @@ inline bool isHexDigit(char c) {
|
|||||||
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
|
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline unsigned long long binaryStringToLong(const std::string& binaryString) {
|
inline u_long binaryStringToLong(const std::string& binaryString) {
|
||||||
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
|
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
|
||||||
unsigned long long result = 0;
|
u_long result = 0;
|
||||||
for (char ch : binaryDigits) {
|
for (char ch : binaryDigits) {
|
||||||
result <<= 1;
|
result <<= 1;
|
||||||
result += (ch - '0');
|
result += (ch - '0');
|
||||||
@ -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,173 +0,0 @@
|
|||||||
// Memory leak test: Builtin function and stdlib scenarios
|
|
||||||
// Test memory behavior with builtin functions and standard library
|
|
||||||
|
|
||||||
print("=== Builtin Function Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Heavy string operations
|
|
||||||
print("Test 1: Heavy string operations");
|
|
||||||
var stringData = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var str = toString(i) + "_" + toString(i * 2) + "_" + toString(i * 3);
|
|
||||||
stringData.push({
|
|
||||||
"original": str,
|
|
||||||
"upper": str, // Bob doesn't have toUpper, but test string storage
|
|
||||||
"length": str.len(),
|
|
||||||
"type": type(str)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + stringData.len() + " string operation results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear string data...");
|
|
||||||
stringData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Type conversion stress test
|
|
||||||
print("Test 2: Type conversion stress");
|
|
||||||
var conversions = [];
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
var num = i * 1.5;
|
|
||||||
var str = toString(num);
|
|
||||||
var backToNum = toNumber(str);
|
|
||||||
var intVal = toInt(num);
|
|
||||||
var boolVal = toBoolean(i % 2);
|
|
||||||
|
|
||||||
conversions.push([
|
|
||||||
num, str, backToNum, intVal, boolVal,
|
|
||||||
type(num), type(str), type(backToNum), type(intVal), type(boolVal)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + conversions.len() + " type conversion results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear conversions...");
|
|
||||||
conversions = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Array/Dict builtin operations
|
|
||||||
print("Test 3: Collection builtin operations");
|
|
||||||
var collections = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
var arr = [i, i+1, i+2];
|
|
||||||
var dict = {"a": i, "b": i+1, "c": i+2};
|
|
||||||
|
|
||||||
// Use builtin functions heavily
|
|
||||||
var arrLen = arr.len();
|
|
||||||
arr.push(i+3);
|
|
||||||
var popped = arr.pop();
|
|
||||||
|
|
||||||
var dictKeys = dict.keys();
|
|
||||||
var dictValues = dict.values();
|
|
||||||
var hasA = dict.has("a");
|
|
||||||
|
|
||||||
collections.push({
|
|
||||||
"array": arr,
|
|
||||||
"dict": dict,
|
|
||||||
"arrLen": arrLen,
|
|
||||||
"popped": popped,
|
|
||||||
"keys": dictKeys,
|
|
||||||
"values": dictValues,
|
|
||||||
"hasA": hasA
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + collections.len() + " collection operation results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear collections...");
|
|
||||||
collections = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Eval function stress test
|
|
||||||
print("Test 4: Eval function stress");
|
|
||||||
var evalResults = [];
|
|
||||||
for (var i = 0; i < 10000; i++) {
|
|
||||||
var expression = toString(i) + " * 2 + 1;";
|
|
||||||
var result = eval(expression);
|
|
||||||
|
|
||||||
var funcExpr = "func() { return " + toString(i) + "; };";
|
|
||||||
var evalFunc = eval(funcExpr);
|
|
||||||
|
|
||||||
evalResults.push({
|
|
||||||
"expr": expression,
|
|
||||||
"result": result,
|
|
||||||
"func": evalFunc,
|
|
||||||
"funcResult": evalFunc()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + evalResults.len() + " eval results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear eval results...");
|
|
||||||
evalResults = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: File I/O operations (if supported)
|
|
||||||
print("Test 5: File I/O operations");
|
|
||||||
var fileData = [];
|
|
||||||
for (var i = 0; i < 1000; i++) {
|
|
||||||
var filename = "temp_" + toString(i) + ".txt";
|
|
||||||
var content = "This is test data for file " + toString(i) + "\n";
|
|
||||||
content = content + "Line 2: " + toString(i * 2) + "\n";
|
|
||||||
content = content + "Line 3: " + toString(i * 3) + "\n";
|
|
||||||
|
|
||||||
// Write file
|
|
||||||
writeFile(filename, content);
|
|
||||||
|
|
||||||
// Check if exists
|
|
||||||
var exists = fileExists(filename);
|
|
||||||
|
|
||||||
// Read back
|
|
||||||
var readContent = readFile(filename);
|
|
||||||
var lines = readLines(filename);
|
|
||||||
|
|
||||||
fileData.push({
|
|
||||||
"filename": filename,
|
|
||||||
"exists": exists,
|
|
||||||
"content": readContent,
|
|
||||||
"lines": lines,
|
|
||||||
"lineCount": lines.len()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + fileData.len() + " file operation results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear file data...");
|
|
||||||
fileData = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Clean up test files
|
|
||||||
print("Cleaning up test files...");
|
|
||||||
for (var i = 0; i < 1000; i++) {
|
|
||||||
var filename = "temp_" + toString(i) + ".txt";
|
|
||||||
if (fileExists(filename)) {
|
|
||||||
// Bob doesn't have deleteFile, but we created them
|
|
||||||
print("Note: Test file " + filename + " still exists");
|
|
||||||
if (i > 10) break; // Don't spam too many messages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input("File data cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 6: Random number generation
|
|
||||||
print("Test 6: Random number stress");
|
|
||||||
var randomData = [];
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
import rand as RLeak;
|
|
||||||
var rand1 = RLeak.random();
|
|
||||||
var rand2 = RLeak.random();
|
|
||||||
var sum = rand1 + rand2;
|
|
||||||
|
|
||||||
randomData.push({
|
|
||||||
"rand1": rand1,
|
|
||||||
"rand2": rand2,
|
|
||||||
"sum": sum,
|
|
||||||
"product": rand1 * rand2
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + randomData.len() + " random number results");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear random data...");
|
|
||||||
randomData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Builtin Function Tests Complete ===");
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
// Memory leak test: Collection-related scenarios
|
|
||||||
// Test arrays, dictionaries, and nested structures
|
|
||||||
|
|
||||||
print("=== Collection Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Large nested arrays
|
|
||||||
print("Test 1: Large nested arrays");
|
|
||||||
var nestedArrays = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
nestedArrays.push([
|
|
||||||
[i, i+1, i+2],
|
|
||||||
[i*2, i*3, i*4],
|
|
||||||
[[i, [i+1, [i+2]]], i*5]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + nestedArrays.len() + " nested array structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear nested arrays...");
|
|
||||||
nestedArrays = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Large nested dictionaries
|
|
||||||
print("Test 2: Large nested dictionaries");
|
|
||||||
var nestedDicts = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
nestedDicts.push({
|
|
||||||
"id": i,
|
|
||||||
"data": {
|
|
||||||
"value": i * 2,
|
|
||||||
"nested": {
|
|
||||||
"deep": {
|
|
||||||
"deeper": i * 3,
|
|
||||||
"info": "test" + i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"created": i,
|
|
||||||
"tags": ["tag" + i, "tag" + (i+1)]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + nestedDicts.len() + " nested dictionary structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear nested dicts...");
|
|
||||||
nestedDicts = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Mixed array/dict structures
|
|
||||||
print("Test 3: Mixed array/dict structures");
|
|
||||||
var mixedStructures = [];
|
|
||||||
for (var i = 0; i < 30000; i++) {
|
|
||||||
mixedStructures.push([
|
|
||||||
{"arrays": [[i, i+1], [i+2, i+3]]},
|
|
||||||
[{"dicts": {"a": i, "b": i+1}}, {"more": [i, i+1]}],
|
|
||||||
{
|
|
||||||
"complex": [
|
|
||||||
{"nested": [i, {"deep": i*2}]},
|
|
||||||
[{"very": {"deep": [i, i+1, {"final": i*3}]}}]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + mixedStructures.len() + " mixed structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear mixed structures...");
|
|
||||||
mixedStructures = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Self-referencing structures (potential cycles)
|
|
||||||
print("Test 4: Self-referencing structures");
|
|
||||||
var selfRef = [];
|
|
||||||
for (var i = 0; i < 1000000; i++) {
|
|
||||||
var item = {"id": i, "value": i * 2};
|
|
||||||
// Create a structure that references itself
|
|
||||||
item["self"] = [item, {"parent": item}];
|
|
||||||
selfRef.push(item);
|
|
||||||
}
|
|
||||||
print("Created " + selfRef.len() + " self-referencing structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear self-ref structures...");
|
|
||||||
// Break cycles explicitly so reference counting can reclaim memory deterministically
|
|
||||||
for (var i = 0; i < selfRef.len(); i++) {
|
|
||||||
selfRef[i]["self"] = none;
|
|
||||||
}
|
|
||||||
selfRef = 123;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: Large string collections
|
|
||||||
print("Test 5: Large string collections");
|
|
||||||
var stringCollections = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var longString = "";
|
|
||||||
for (var j = 0; j < 100; j++) {
|
|
||||||
longString = longString + "data" + i + "_" + j + " ";
|
|
||||||
}
|
|
||||||
stringCollections.push({
|
|
||||||
"content": longString,
|
|
||||||
"words": [longString, longString + "_copy", longString + "_backup"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + stringCollections.len() + " string collections");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear string collections...");
|
|
||||||
stringCollections = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Collection Tests Complete ===");
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
// Memory leak test: Function-related scenarios
|
|
||||||
// Test various function patterns that could cause memory leaks
|
|
||||||
|
|
||||||
print("=== Function Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Recursive function closures
|
|
||||||
print("Test 1: Recursive function closures");
|
|
||||||
var recursiveFuncs = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
recursiveFuncs.push(func() {
|
|
||||||
var capturedI = i;
|
|
||||||
return func() {
|
|
||||||
if (capturedI > 0) {
|
|
||||||
return capturedI * capturedI;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + recursiveFuncs.len() + " recursive closure functions");
|
|
||||||
print("Memory after creation: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear recursive functions...");
|
|
||||||
recursiveFuncs = none;
|
|
||||||
print("Memory after cleanup: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Functions returning functions (factory pattern)
|
|
||||||
print("Test 2: Function factories");
|
|
||||||
var factories = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
factories.push(func() {
|
|
||||||
var multiplier = i;
|
|
||||||
return func(x) {
|
|
||||||
return x * multiplier;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + factories.len() + " function factories");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear factories...");
|
|
||||||
factories = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Deep function nesting
|
|
||||||
print("Test 3: Deep function nesting");
|
|
||||||
var deepNested = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
deepNested.push(func() {
|
|
||||||
return func() {
|
|
||||||
return func() {
|
|
||||||
return func() {
|
|
||||||
return func() {
|
|
||||||
return i * 42;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + deepNested.len() + " deeply nested functions");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear deep nested...");
|
|
||||||
deepNested = "test";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Circular function references
|
|
||||||
print("Test 4: Circular function references");
|
|
||||||
var circularFuncs = [];
|
|
||||||
for (var i = 0; i < 1000000; i++) {
|
|
||||||
var funcA = func() {
|
|
||||||
return "A" + i;
|
|
||||||
};
|
|
||||||
var funcB = func() {
|
|
||||||
return "B" + i;
|
|
||||||
};
|
|
||||||
// Store both in same array element to create potential circular refs
|
|
||||||
circularFuncs.push([funcA, funcB, func() { return funcA; }]);
|
|
||||||
}
|
|
||||||
print("Created " + circularFuncs.len() + " circular function structures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear circular functions...");
|
|
||||||
circularFuncs = 42;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Function Tests Complete ===");
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
// Memory leak test: Loop and repetitive operation scenarios
|
|
||||||
// Test memory behavior in various loop patterns
|
|
||||||
|
|
||||||
print("=== Loop Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Nested loop memory allocation
|
|
||||||
print("Test 1: Nested loop allocation");
|
|
||||||
var nestedData = [];
|
|
||||||
for (var i = 0; i < 1000; i++) {
|
|
||||||
var row = [];
|
|
||||||
for (var j = 0; j < 1000; j++) {
|
|
||||||
row.push({
|
|
||||||
"i": i,
|
|
||||||
"j": j,
|
|
||||||
"func": func() { return i * j; },
|
|
||||||
"data": [i, j, i+j]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
nestedData.push(row);
|
|
||||||
}
|
|
||||||
print("Created " + nestedData.len() + "x" + nestedData[0].len() + " nested structure");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear nested data...");
|
|
||||||
nestedData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: While loop with accumulation
|
|
||||||
print("Test 2: While loop accumulation");
|
|
||||||
var accumulator = [];
|
|
||||||
var counter = 0;
|
|
||||||
while (counter < 500000) {
|
|
||||||
accumulator.push({
|
|
||||||
"count": counter,
|
|
||||||
"func": func() { return counter * 2; },
|
|
||||||
"meta": ["item" + counter, counter % 100]
|
|
||||||
});
|
|
||||||
counter = counter + 1;
|
|
||||||
}
|
|
||||||
print("Accumulated " + accumulator.len() + " items in while loop");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear accumulator...");
|
|
||||||
accumulator = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: For loop with variable reassignment
|
|
||||||
print("Test 3: Variable reassignment in loops");
|
|
||||||
var reassignVar = none;
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
// Constantly reassign to different types
|
|
||||||
if (i % 4 == 0) {
|
|
||||||
reassignVar = [i, func() { return i; }];
|
|
||||||
} else if (i % 4 == 1) {
|
|
||||||
reassignVar = {"id": i, "func": func() { return i*2; }};
|
|
||||||
} else if (i % 4 == 2) {
|
|
||||||
reassignVar = func() { return i*3; };
|
|
||||||
} else {
|
|
||||||
reassignVar = "string" + i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print("Completed " + 200000 + " reassignments, final type: " + type(reassignVar));
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear reassignment var...");
|
|
||||||
reassignVar = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Do-while with function creation
|
|
||||||
print("Test 4: Do-while function creation");
|
|
||||||
var doWhileFuncs = [];
|
|
||||||
var dwCounter = 0;
|
|
||||||
do {
|
|
||||||
doWhileFuncs.push(func() {
|
|
||||||
var captured = dwCounter;
|
|
||||||
return func() {
|
|
||||||
return captured * captured;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
dwCounter = dwCounter + 1;
|
|
||||||
} while (dwCounter < 100000);
|
|
||||||
print("Created " + doWhileFuncs.len() + " functions in do-while");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear do-while functions...");
|
|
||||||
doWhileFuncs = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: Loop with early breaks and continues
|
|
||||||
print("Test 5: Complex loop control flow");
|
|
||||||
var complexData = [];
|
|
||||||
for (var i = 0; i < 500000; i++) {
|
|
||||||
if (i % 7 == 0) {
|
|
||||||
continue; // Skip some iterations
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > 400000 && i % 100 == 0) {
|
|
||||||
// Create larger objects near the end
|
|
||||||
complexData.push({
|
|
||||||
"large": [
|
|
||||||
func() { return i; },
|
|
||||||
[i, i+1, i+2, i+3],
|
|
||||||
{"nested": {"deep": func() { return i*2; }}}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
complexData.push(func() { return i; });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > 450000 && complexData.len() > 350000) {
|
|
||||||
break; // Early exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print("Complex loop created " + complexData.len() + " items");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear complex data...");
|
|
||||||
complexData = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 6: Memory churn test (create and destroy in same loop)
|
|
||||||
print("Test 6: Memory churn test");
|
|
||||||
for (var cycle = 0; cycle < 100; cycle++) {
|
|
||||||
var churnData = [];
|
|
||||||
|
|
||||||
// Create lots of data
|
|
||||||
for (var i = 0; i < 10000; i++) {
|
|
||||||
churnData.push([
|
|
||||||
func() { return i + cycle; },
|
|
||||||
{"cycle": cycle, "item": i}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear it immediately
|
|
||||||
churnData = none;
|
|
||||||
|
|
||||||
if (cycle % 10 == 0) {
|
|
||||||
print("Completed churn cycle " + cycle + ", Memory: " + memoryUsage() + " MB");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print("Final memory after churn test: " + memoryUsage() + " MB");
|
|
||||||
input("Completed memory churn test. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Loop Tests Complete ===");
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
// Memory leak test: Mixed type scenarios and edge cases
|
|
||||||
// Test combinations and edge cases that might cause leaks
|
|
||||||
|
|
||||||
print("=== Mixed Type Memory Leak Tests ===");
|
|
||||||
print("Initial memory: " + memoryUsage() + " MB");
|
|
||||||
|
|
||||||
// Test 1: Functions with collection captures
|
|
||||||
print("Test 1: Functions capturing collections");
|
|
||||||
var funcWithCollections = [];
|
|
||||||
for (var i = 0; i < 50000; i++) {
|
|
||||||
var capturedArray = [i, i+1, i+2, "data" + i];
|
|
||||||
var capturedDict = {"id": i, "values": [i*2, i*3]};
|
|
||||||
|
|
||||||
funcWithCollections.push(func() {
|
|
||||||
// Capture both collections
|
|
||||||
var localArray = capturedArray;
|
|
||||||
var localDict = capturedDict;
|
|
||||||
return func() {
|
|
||||||
return localArray.len() + localDict.len();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
print("Created " + funcWithCollections.len() + " functions with collection captures");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear function collections...");
|
|
||||||
funcWithCollections = [];
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 2: Collections containing functions and other collections
|
|
||||||
print("Test 2: Collections with mixed content");
|
|
||||||
var mixedContent = [];
|
|
||||||
for (var i = 0; i < 30000; i++) {
|
|
||||||
mixedContent.push([
|
|
||||||
func() { return i; }, // Function
|
|
||||||
[i, i+1, func() { return i*2; }], // Array with function
|
|
||||||
{"value": i, "func": func() { return i*3; }}, // Dict with function
|
|
||||||
"string" + i, // String
|
|
||||||
i * 1.5, // Number
|
|
||||||
i % 2 == 0 // Boolean
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Created " + mixedContent.len() + " mixed content collections");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear mixed content...");
|
|
||||||
mixedContent = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 3: Property assignment patterns
|
|
||||||
print("Test 3: Property assignment patterns");
|
|
||||||
var propObjects = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var obj = {"base": i};
|
|
||||||
// Dynamic property assignment
|
|
||||||
obj["prop" + i] = func() { return i; };
|
|
||||||
obj["nested"] = {"deep": {"value": i}};
|
|
||||||
obj["array"] = [1, 2, 3];
|
|
||||||
obj["array"][0] = func() { return i * 2; };
|
|
||||||
|
|
||||||
propObjects.push(obj);
|
|
||||||
}
|
|
||||||
print("Created " + propObjects.len() + " objects with dynamic properties");
|
|
||||||
print("Memory: " + memoryUsage() + " MB");
|
|
||||||
input("Press Enter to clear property objects...");
|
|
||||||
propObjects = "cleared";
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 4: Type reassignment chains
|
|
||||||
print("Test 4: Type reassignment chains");
|
|
||||||
var typeChains = [];
|
|
||||||
for (var i = 0; i < 200000; i++) {
|
|
||||||
typeChains.push(i);
|
|
||||||
}
|
|
||||||
print("Memory after number array: " + memoryUsage() + " MB");
|
|
||||||
input("Created number array. Press Enter to convert to functions...");
|
|
||||||
|
|
||||||
// Reassign all elements to functions
|
|
||||||
for (var i = 0; i < typeChains.len(); i++) {
|
|
||||||
typeChains[i] = func() { return i; };
|
|
||||||
}
|
|
||||||
print("Memory after function conversion: " + memoryUsage() + " MB");
|
|
||||||
input("Converted to functions. Press Enter to convert to dicts...");
|
|
||||||
|
|
||||||
// Reassign all elements to dicts
|
|
||||||
for (var i = 0; i < typeChains.len(); i++) {
|
|
||||||
typeChains[i] = {"id": i, "func": func() { return i; }};
|
|
||||||
}
|
|
||||||
print("Memory after dict conversion: " + memoryUsage() + " MB");
|
|
||||||
input("Converted to dicts. Press Enter to clear...");
|
|
||||||
typeChains = none;
|
|
||||||
print("Memory after clear: " + memoryUsage() + " MB");
|
|
||||||
input("Cleared. Check memory usage...");
|
|
||||||
|
|
||||||
// Test 5: Rapid allocation/deallocation
|
|
||||||
print("Test 5: Rapid allocation/deallocation");
|
|
||||||
for (var round = 0; round < 10; round++) {
|
|
||||||
var temp = [];
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
temp.push([
|
|
||||||
func() { return i; },
|
|
||||||
{"data": [i, i+1, func() { return i*2; }]}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
print("Round " + round + ": Created " + temp.len() + " items, Memory: " + memoryUsage() + " MB");
|
|
||||||
temp = none; // Clear immediately
|
|
||||||
if (round % 2 == 1) print("After clear round " + round + ": " + memoryUsage() + " MB");
|
|
||||||
}
|
|
||||||
print("Final memory after all cycles: " + memoryUsage() + " MB");
|
|
||||||
input("Completed rapid allocation cycles. Check memory usage...");
|
|
||||||
|
|
||||||
print("=== Mixed Type Tests Complete ===");
|
|
||||||
@ -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.
51
source/Environment.cpp
Normal file
51
source/Environment.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "../headers/Environment.h"
|
||||||
|
#include "../headers/ErrorReporter.h"
|
||||||
|
|
||||||
|
void Environment::assign(const Token& name, const Value& value) {
|
||||||
|
auto it = variables.find(name.lexeme);
|
||||||
|
if (it != variables.end()) {
|
||||||
|
it->second = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent != nullptr) {
|
||||||
|
parent->assign(name, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
||||||
|
"Undefined variable '" + name.lexeme + "'", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Environment::get(const Token& name) {
|
||||||
|
auto it = variables.find(name.lexeme);
|
||||||
|
if (it != variables.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent != nullptr) {
|
||||||
|
return parent->get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
||||||
|
"Undefined variable '" + name.lexeme + "'", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Environment::get(const std::string& name) {
|
||||||
|
auto it = variables.find(name);
|
||||||
|
if (it != variables.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent != nullptr) {
|
||||||
|
return parent->get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Undefined variable '" + name + "'");
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "ErrorReporter.h"
|
#include "../headers/ErrorReporter.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -56,33 +56,9 @@ void ErrorReporter::loadSource(const std::string& source, const std::string& fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ErrorReporter::pushSource(const std::string& source, const std::string& fileName) {
|
|
||||||
// Save current
|
|
||||||
sourceStack.push_back(sourceLines);
|
|
||||||
fileNameStack.push_back(currentFileName);
|
|
||||||
// Load new
|
|
||||||
loadSource(source, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorReporter::popSource() {
|
|
||||||
if (!sourceStack.empty()) {
|
|
||||||
sourceLines = sourceStack.back();
|
|
||||||
sourceStack.pop_back();
|
|
||||||
} else {
|
|
||||||
sourceLines.clear();
|
|
||||||
}
|
|
||||||
if (!fileNameStack.empty()) {
|
|
||||||
currentFileName = fileNameStack.back();
|
|
||||||
fileNameStack.pop_back();
|
|
||||||
} else {
|
|
||||||
currentFileName.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
||||||
hadError = true;
|
hadError = true;
|
||||||
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
||||||
std::cout.flush(); // Ensure output is flushed before any exception is thrown
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
||||||
@ -98,8 +74,6 @@ void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
|||||||
|
|
||||||
if (!context.fileName.empty()) {
|
if (!context.fileName.empty()) {
|
||||||
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
|
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
|
||||||
} else if (!currentFileName.empty()) {
|
|
||||||
std::cout << colorize("File: ", Colors::BOLD) << colorize(currentFileName, Colors::CYAN) << "\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +
|
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +
|
||||||
@ -132,8 +106,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int ERROR_DISPLAY_MAX_WIDTH = 65;
|
int maxWidth = 65;
|
||||||
int maxWidth = ERROR_DISPLAY_MAX_WIDTH;
|
|
||||||
int startLine = std::max(1, line - 4);
|
int startLine = std::max(1, line - 4);
|
||||||
int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2);
|
int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2);
|
||||||
|
|
||||||
@ -146,7 +119,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
|
|||||||
|
|
||||||
int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length());
|
int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length());
|
||||||
maxWidth = std::max(maxWidth, errorLineWidth);
|
maxWidth = std::max(maxWidth, errorLineWidth);
|
||||||
maxWidth = std::max(maxWidth, ERROR_DISPLAY_MAX_WIDTH);
|
maxWidth = std::max(maxWidth, 65);
|
||||||
|
|
||||||
std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n";
|
std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n";
|
||||||
std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n";
|
std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n";
|
||||||
@ -189,8 +162,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string
|
|||||||
void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) {
|
void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) {
|
||||||
if (callStack.empty()) return;
|
if (callStack.empty()) return;
|
||||||
|
|
||||||
static const int CALL_STACK_MAX_WIDTH = 65;
|
int maxWidth = 65;
|
||||||
int maxWidth = CALL_STACK_MAX_WIDTH;
|
|
||||||
for (const auto& func : callStack) {
|
for (const auto& func : callStack) {
|
||||||
int funcWidth = static_cast<int>(func.length()) + 6;
|
int funcWidth = static_cast<int>(func.length()) + 6;
|
||||||
maxWidth = std::max(maxWidth, funcWidth);
|
maxWidth = std::max(maxWidth, funcWidth);
|
||||||
5
source/Expression.cpp
Normal file
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);
|
||||||
@ -363,14 +343,18 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
|
|
||||||
}
|
}
|
||||||
if(!isNotation) {
|
if(!isNotation) {
|
||||||
// Only treat '.' as part of the number if followed by a digit
|
if (!src.empty() && src[0] == '.') {
|
||||||
if (src.size() > 1 && src[0] == '.' && std::isdigit(src[1])) {
|
advance();
|
||||||
advance(); // consume '.'
|
if (!src.empty() && std::isdigit(src[0])) {
|
||||||
num += '.';
|
num += '.';
|
||||||
while (!src.empty() && std::isdigit(src[0])) {
|
while (!src.empty() && std::isdigit(src[0])) {
|
||||||
num += src[0];
|
num += src[0];
|
||||||
advance();
|
advance();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("LEXER: malformed number at: " + std::to_string(this->line));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -437,7 +421,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
tokens.push_back({END_OF_FILE, "eof", line, column});
|
tokens.push_back({END_OF_FILE, "eof", line});
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,11 +473,8 @@ char Lexer::peekNext()
|
|||||||
std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
||||||
std::string output;
|
std::string output;
|
||||||
bool escapeMode = false;
|
bool escapeMode = false;
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
while (i < input.length()) {
|
|
||||||
char c = input[i];
|
|
||||||
|
|
||||||
|
for (char c : input) {
|
||||||
if (escapeMode) {
|
if (escapeMode) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'n':
|
case 'n':
|
||||||
@ -508,30 +489,8 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
|||||||
case '\\':
|
case '\\':
|
||||||
output += '\\';
|
output += '\\';
|
||||||
break;
|
break;
|
||||||
case '0':
|
|
||||||
output += '\0';
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
output += '\r';
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
output += '\a';
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
output += '\b';
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
output += '\f';
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
output += '\v';
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
// ANSI escape sequence
|
|
||||||
output += '\033';
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid escape character: " + std::string(1, c));
|
throw runtime_error("Invalid escape character: " + std::string(1, c));
|
||||||
}
|
}
|
||||||
escapeMode = false;
|
escapeMode = false;
|
||||||
} else if (c == '\\') {
|
} else if (c == '\\') {
|
||||||
@ -539,7 +498,6 @@ std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
|||||||
} else {
|
} else {
|
||||||
output += c;
|
output += c;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
554
source/Parser.cpp
Normal file
554
source/Parser.cpp
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
//
|
||||||
|
// Created by Bobby Lucero on 5/26/23.
|
||||||
|
//
|
||||||
|
#include "../headers/Parser.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
|
// Precedence
|
||||||
|
// to all the morons on facebook who don't know what pemdas is, fuck you
|
||||||
|
///////////////////////////////////////////
|
||||||
|
|
||||||
|
sptr(Expr) Parser::expression()
|
||||||
|
{
|
||||||
|
return assignment();
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::logical_or()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = logical_and();
|
||||||
|
|
||||||
|
while(match({OR}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = logical_and();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::logical_and()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = equality();
|
||||||
|
|
||||||
|
while(match({AND}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = equality();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitwise_or now calls comparison (not bitwise_xor)
|
||||||
|
sptr(Expr) Parser::bitwise_or()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = bitwise_xor();
|
||||||
|
|
||||||
|
while(match({BIN_OR}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = bitwise_xor();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::bitwise_xor()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = bitwise_and();
|
||||||
|
|
||||||
|
while(match({BIN_XOR}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = bitwise_and();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::bitwise_and()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = shift();
|
||||||
|
|
||||||
|
while(match({BIN_AND}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = shift();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::shift()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = term();
|
||||||
|
|
||||||
|
while(match({BIN_SLEFT, BIN_SRIGHT}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = term();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::assignment()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = increment();
|
||||||
|
|
||||||
|
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||||
|
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) value = assignment();
|
||||||
|
if(std::dynamic_pointer_cast<VarExpr>(expr))
|
||||||
|
{
|
||||||
|
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
|
||||||
|
return msptr(AssignExpr)(name, op, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(op.line, op.column, "Parse Error",
|
||||||
|
"Invalid assignment target", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid assignment target.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::increment()
|
||||||
|
{
|
||||||
|
return logical_or();
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::equality()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = comparison();
|
||||||
|
|
||||||
|
while(match({BANG_EQUAL, DOUBLE_EQUAL}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = comparison();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::comparison()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = bitwise_or();
|
||||||
|
|
||||||
|
while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = bitwise_or();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::term()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = factor();
|
||||||
|
|
||||||
|
while(match({MINUS, PLUS}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = factor();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::factor()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = unary();
|
||||||
|
|
||||||
|
while(match({SLASH, STAR, PERCENT}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = unary();
|
||||||
|
expr = msptr(BinaryExpr)(expr, op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::unary()
|
||||||
|
{
|
||||||
|
if(match({BANG, MINUS, BIN_NOT, PLUS_PLUS, MINUS_MINUS}))
|
||||||
|
{
|
||||||
|
Token op = previous();
|
||||||
|
sptr(Expr) right = unary();
|
||||||
|
|
||||||
|
// Handle prefix increment/decrement
|
||||||
|
if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) {
|
||||||
|
// Ensure the operand is a variable
|
||||||
|
if (!std::dynamic_pointer_cast<VarExpr>(right)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(op.line, op.column, "Parse Error",
|
||||||
|
"Prefix increment/decrement can only be applied to variables", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Prefix increment/decrement can only be applied to variables.");
|
||||||
|
}
|
||||||
|
return msptr(IncrementExpr)(right, op, true); // true = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
return msptr(UnaryExpr)(op, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return postfix();
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::postfix()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = primary();
|
||||||
|
|
||||||
|
// Check for postfix increment/decrement
|
||||||
|
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
||||||
|
Token oper = previous();
|
||||||
|
|
||||||
|
// Ensure the expression is a variable
|
||||||
|
if (!std::dynamic_pointer_cast<VarExpr>(expr)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(oper.line, oper.column, "Parse Error",
|
||||||
|
"Postfix increment/decrement can only be applied to variables", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Postfix increment/decrement can only be applied to variables.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::primary()
|
||||||
|
{
|
||||||
|
if(match({FALSE})) return msptr(LiteralExpr)("false", false, false, true);
|
||||||
|
if(match({TRUE})) return msptr(LiteralExpr)("true", false, false, true);
|
||||||
|
if(match({NONE})) return msptr(LiteralExpr)("none", false, true, false);
|
||||||
|
|
||||||
|
if(match({NUMBER})) return msptr(LiteralExpr)(previous().lexeme, true, false, false);
|
||||||
|
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false, false);
|
||||||
|
|
||||||
|
if(match( {IDENTIFIER})) {
|
||||||
|
if (check(OPEN_PAREN)) {
|
||||||
|
return finishCall(msptr(VarExpr)(previous()));
|
||||||
|
}
|
||||||
|
return msptr(VarExpr)(previous());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(match({OPEN_PAREN}))
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = expression();
|
||||||
|
consume(CLOSE_PAREN, "Expected ')' after expression on line " + std::to_string(peek().line));
|
||||||
|
if (check(OPEN_PAREN)) {
|
||||||
|
return finishCall(msptr(GroupingExpr)(expr));
|
||||||
|
}
|
||||||
|
return msptr(GroupingExpr)(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(match({FUNCTION})) {
|
||||||
|
return functionExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(peek().line, peek().column, "Parse Error",
|
||||||
|
"Expression expected", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expression expected at: " + std::to_string(peek().line));
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<sptr(Stmt)> Parser::parse() {
|
||||||
|
|
||||||
|
std::vector<sptr(Stmt)> statements;
|
||||||
|
while(!isAtEnd())
|
||||||
|
{
|
||||||
|
statements.push_back(declaration());
|
||||||
|
}
|
||||||
|
|
||||||
|
return statements;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::declaration()
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
if(match({VAR})) return varDeclaration();
|
||||||
|
if(match({FUNCTION})) return functionDeclaration();
|
||||||
|
return statement();
|
||||||
|
}
|
||||||
|
catch(std::runtime_error& e)
|
||||||
|
{
|
||||||
|
sync();
|
||||||
|
throw std::runtime_error(e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::varDeclaration()
|
||||||
|
{
|
||||||
|
Token name = consume(IDENTIFIER, "Expected variable name.");
|
||||||
|
|
||||||
|
sptr(Expr) initializer = msptr(LiteralExpr)("none", false, true, false);
|
||||||
|
if(match({EQUAL}))
|
||||||
|
{
|
||||||
|
initializer = expression();
|
||||||
|
}
|
||||||
|
consume(SEMICOLON, "Expected ';' after variable declaration.");
|
||||||
|
return msptr(VarStmt)(name, initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::functionDeclaration()
|
||||||
|
{
|
||||||
|
Token name = consume(IDENTIFIER, "Expected function name.");
|
||||||
|
consume(OPEN_PAREN, "Expected '(' after function name.");
|
||||||
|
|
||||||
|
std::vector<Token> parameters;
|
||||||
|
if (!check(CLOSE_PAREN)) {
|
||||||
|
do {
|
||||||
|
parameters.push_back(consume(IDENTIFIER, "Expected parameter name."));
|
||||||
|
} while (match({COMMA}));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(CLOSE_PAREN, "Expected ')' after parameters.");
|
||||||
|
consume(OPEN_BRACE, "Expected '{' before function body.");
|
||||||
|
|
||||||
|
// Enter function scope
|
||||||
|
enterFunction();
|
||||||
|
|
||||||
|
std::vector<sptr(Stmt)> body = block();
|
||||||
|
|
||||||
|
// Exit function scope
|
||||||
|
exitFunction();
|
||||||
|
|
||||||
|
return msptr(FunctionStmt)(name, parameters, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Expr> Parser::functionExpression() {
|
||||||
|
consume(OPEN_PAREN, "Expect '(' after 'func'.");
|
||||||
|
std::vector<Token> parameters;
|
||||||
|
if (!check(CLOSE_PAREN)) {
|
||||||
|
do {
|
||||||
|
if (parameters.size() >= 255) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(peek().line, 0, "Parse Error",
|
||||||
|
"Cannot have more than 255 parameters", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot have more than 255 parameters.");
|
||||||
|
}
|
||||||
|
parameters.push_back(consume(IDENTIFIER, "Expect parameter name."));
|
||||||
|
} while (match({COMMA}));
|
||||||
|
}
|
||||||
|
consume(CLOSE_PAREN, "Expect ')' after parameters.");
|
||||||
|
consume(OPEN_BRACE, "Expect '{' before function body.");
|
||||||
|
|
||||||
|
// Enter function scope
|
||||||
|
enterFunction();
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Stmt>> body = block();
|
||||||
|
|
||||||
|
// Exit function scope
|
||||||
|
exitFunction();
|
||||||
|
|
||||||
|
return msptr(FunctionExpr)(parameters, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::statement()
|
||||||
|
{
|
||||||
|
if(match({RETURN})) return returnStatement();
|
||||||
|
if(match({IF})) return ifStatement();
|
||||||
|
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
||||||
|
return expressionStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::ifStatement()
|
||||||
|
{
|
||||||
|
consume(OPEN_PAREN, "Expected '(' after 'if'.");
|
||||||
|
sptr(Expr) condition = expression();
|
||||||
|
consume(CLOSE_PAREN, "Expected ')' after if condition.");
|
||||||
|
|
||||||
|
sptr(Stmt) thenBranch = statement();
|
||||||
|
sptr(Stmt) elseBranch = nullptr;
|
||||||
|
|
||||||
|
if (match({ELSE})) {
|
||||||
|
elseBranch = statement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return msptr(IfStmt)(condition, thenBranch, elseBranch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to detect if an expression is a tail call
|
||||||
|
bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) {
|
||||||
|
// Check if this is a direct function call (no operations on the result)
|
||||||
|
if (auto callExpr = std::dynamic_pointer_cast<CallExpr>(expr)) {
|
||||||
|
return true; // Direct function call in return statement
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::returnStatement()
|
||||||
|
{
|
||||||
|
Token keyword = previous();
|
||||||
|
|
||||||
|
// Check if we're inside a function
|
||||||
|
if (!isInFunction()) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(keyword.line, 0, "Parse Error",
|
||||||
|
"Cannot return from outside a function", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot return from outside a function");
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) value = msptr(LiteralExpr)("none", false, true, false);
|
||||||
|
|
||||||
|
if (!check(SEMICOLON)) {
|
||||||
|
value = expression();
|
||||||
|
|
||||||
|
// Check if this is a tail call and mark it
|
||||||
|
if (isTailCall(value)) {
|
||||||
|
if (auto callExpr = std::dynamic_pointer_cast<CallExpr>(value)) {
|
||||||
|
callExpr->isTailCall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(SEMICOLON, "Expected ';' after return value.");
|
||||||
|
return msptr(ReturnStmt)(keyword, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Stmt) Parser::expressionStatement()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = expression();
|
||||||
|
consume(SEMICOLON, "Expected ';' after expression.");
|
||||||
|
return msptr(ExpressionStmt)(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sptr(Stmt)> Parser::block()
|
||||||
|
{
|
||||||
|
std::vector<sptr(Stmt)> statements;
|
||||||
|
|
||||||
|
while(!check(CLOSE_BRACE) && !isAtEnd())
|
||||||
|
{
|
||||||
|
statements.push_back(declaration());
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(CLOSE_BRACE, "Expected '}' after block.");
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
||||||
|
std::vector<sptr(Expr)> arguments;
|
||||||
|
|
||||||
|
// Consume the opening parenthesis
|
||||||
|
consume(OPEN_PAREN, "Expected '(' after function name.");
|
||||||
|
|
||||||
|
// Parse arguments if there are any
|
||||||
|
if (!check(CLOSE_PAREN)) {
|
||||||
|
do {
|
||||||
|
arguments.push_back(expression());
|
||||||
|
} while (match({COMMA}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Token paren = consume(CLOSE_PAREN, "Expected ')' after arguments.");
|
||||||
|
return msptr(CallExpr)(callee, paren, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::match(const std::vector<TokenType>& types) {
|
||||||
|
for(TokenType t : types)
|
||||||
|
{
|
||||||
|
if(check(t))
|
||||||
|
{
|
||||||
|
advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::check(TokenType type) {
|
||||||
|
if(isAtEnd()) return false;
|
||||||
|
return peek().type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::isAtEnd() {
|
||||||
|
return peek().type == END_OF_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::advance() {
|
||||||
|
if(!isAtEnd()) current++;
|
||||||
|
return previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::peek() {
|
||||||
|
return tokens[current];
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::previous() {
|
||||||
|
return tokens[current - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::consume(TokenType type, const std::string& message) {
|
||||||
|
if(check(type)) return advance();
|
||||||
|
|
||||||
|
if (errorReporter) {
|
||||||
|
// Use the precise column information from the token
|
||||||
|
int errorColumn = peek().column;
|
||||||
|
|
||||||
|
// For missing closing parenthesis, point to where it should be
|
||||||
|
if (type == CLOSE_PAREN) {
|
||||||
|
// The closing parenthesis should be right after the previous token
|
||||||
|
errorColumn = previous().column + previous().lexeme.length();
|
||||||
|
|
||||||
|
// For string tokens, add 2 to account for the opening and closing quotes
|
||||||
|
if (previous().type == STRING) {
|
||||||
|
errorColumn += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorReporter->reportError(peek().line, errorColumn, "Parse Error",
|
||||||
|
"Unexpected symbol '" + peek().lexeme + "': " + message, "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Unexpected symbol '" + peek().lexeme +"': "+ message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::sync()
|
||||||
|
{
|
||||||
|
advance();
|
||||||
|
while(!isAtEnd())
|
||||||
|
{
|
||||||
|
if(previous().type == SEMICOLON) return;
|
||||||
|
|
||||||
|
switch (peek().type) {
|
||||||
|
case CLASS:
|
||||||
|
case FUNCTION:
|
||||||
|
case VAR:
|
||||||
|
case FOR:
|
||||||
|
case IF:
|
||||||
|
case WHILE:
|
||||||
|
case RETURN:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1,25 +1,9 @@
|
|||||||
#include "BobStdLib.h"
|
#include "../headers/StdLib.h"
|
||||||
#include "Interpreter.h"
|
#include "../headers/Interpreter.h"
|
||||||
#include "ErrorReporter.h"
|
#include "../headers/ErrorReporter.h"
|
||||||
#include "Lexer.h"
|
|
||||||
#include "Parser.h"
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
|
||||||
#include <ctime>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
// Platform-specific includes for memory usage
|
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
|
||||||
#include <mach/mach.h>
|
|
||||||
#elif defined(__linux__)
|
|
||||||
// Uses /proc/self/status, no extra includes needed
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
#include <windows.h>
|
|
||||||
#include <psapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
|
||||||
// Create a built-in toString function
|
// Create a built-in toString function
|
||||||
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
@ -33,7 +17,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(interpreter.stringify(args[0]));
|
return Value(interpreter.stringify(args[0]));
|
||||||
});
|
});
|
||||||
env->define("toString", Value(toStringFunc));
|
env->define("toString", Value(toStringFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toStringFunc);
|
interpreter.addBuiltinFunction(toStringFunc);
|
||||||
@ -49,35 +33,14 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||||
}
|
}
|
||||||
// Use the interpreter's stringify function
|
// Use the interpreter's stringify function
|
||||||
std::cout << interpreter.stringify(args[0]) << '\n';
|
std::cout << interpreter.stringify(args[0]) << std::endl;
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("print", Value(printFunc));
|
env->define("print", Value(printFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(printFunc);
|
interpreter.addBuiltinFunction(printFunc);
|
||||||
|
|
||||||
// Create a built-in printRaw function (no newline, for ANSI escape sequences)
|
|
||||||
auto printRawFunc = std::make_shared<BuiltinFunction>("printRaw",
|
|
||||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
// Print without newline and flush immediately for ANSI escape sequences
|
|
||||||
std::cout << interpreter.stringify(args[0]) << std::flush;
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
env->define("printRaw", Value(printRawFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(printRawFunc);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create a built-in assert function
|
// Create a built-in assert function
|
||||||
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
@ -114,12 +77,32 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return NONE_VALUE;
|
return NONE_VALUE;
|
||||||
});
|
});
|
||||||
env->define("assert", Value(assertFunc));
|
env->define("assert", Value(assertFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(assertFunc);
|
interpreter.addBuiltinFunction(assertFunc);
|
||||||
|
|
||||||
// time-related globals moved into builtin time module
|
// Create a built-in time function (returns microseconds since Unix epoch)
|
||||||
|
auto timeFunc = std::make_shared<BuiltinFunction>("time",
|
||||||
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
if (args.size() != 0) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, "StdLib Error",
|
||||||
|
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = now.time_since_epoch();
|
||||||
|
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
||||||
|
|
||||||
|
return Value(static_cast<double>(microseconds));
|
||||||
|
});
|
||||||
|
env->define("time", Value(timeFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(timeFunc);
|
||||||
|
|
||||||
// Create a built-in input function
|
// Create a built-in input function
|
||||||
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
||||||
@ -143,7 +126,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
|
|
||||||
return Value(userInput);
|
return Value(userInput);
|
||||||
});
|
});
|
||||||
env->define("input", Value(inputFunc));
|
env->define("input", Value(inputFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(inputFunc);
|
interpreter.addBuiltinFunction(inputFunc);
|
||||||
@ -172,19 +155,13 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
typeName = "function";
|
typeName = "function";
|
||||||
} else if (args[0].isBuiltinFunction()) {
|
} else if (args[0].isBuiltinFunction()) {
|
||||||
typeName = "builtin_function";
|
typeName = "builtin_function";
|
||||||
} else if (args[0].isArray()) {
|
|
||||||
typeName = "array";
|
|
||||||
} else if (args[0].isDict()) {
|
|
||||||
typeName = "dict";
|
|
||||||
} else if (args[0].isModule()) {
|
|
||||||
typeName = "module";
|
|
||||||
} else {
|
} else {
|
||||||
typeName = "unknown";
|
typeName = "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Value(typeName);
|
return Value(typeName);
|
||||||
});
|
});
|
||||||
env->define("type", Value(typeFunc));
|
env->define("type", Value(typeFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(typeFunc);
|
interpreter.addBuiltinFunction(typeFunc);
|
||||||
@ -219,39 +196,11 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
return NONE_VALUE; // Return none for out of range
|
return NONE_VALUE; // Return none for out of range
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
env->define("toNumber", Value(toNumberFunc));
|
env->define("toNumber", Value(toNumberFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toNumberFunc);
|
interpreter.addBuiltinFunction(toNumberFunc);
|
||||||
|
|
||||||
// Create a built-in toInt function for float-to-integer conversion
|
|
||||||
auto toIntFunc = std::make_shared<BuiltinFunction>("toInt",
|
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0].isNumber()) {
|
|
||||||
if (errorReporter) {
|
|
||||||
errorReporter->reportError(line, column, "StdLib Error",
|
|
||||||
"toInt() can only be used on numbers", "", true);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("toInt() can only be used on numbers");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to integer by truncating (same as | 0)
|
|
||||||
double value = args[0].asNumber();
|
|
||||||
return Value(static_cast<double>(static_cast<long long>(value)));
|
|
||||||
});
|
|
||||||
env->define("toInt", Value(toIntFunc));
|
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
|
||||||
interpreter.addBuiltinFunction(toIntFunc);
|
|
||||||
|
|
||||||
// Create a built-in toBoolean function for explicit boolean conversion
|
// Create a built-in toBoolean function for explicit boolean conversion
|
||||||
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
|
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
|
||||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||||
@ -285,65 +234,28 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
// For any other type (functions, etc.), consider them truthy
|
// For any other type (functions, etc.), consider them truthy
|
||||||
return Value(true);
|
return Value(true);
|
||||||
});
|
});
|
||||||
env->define("toBoolean", Value(toBooleanFunc));
|
env->define("toBoolean", Value(toBooleanFunc.get()));
|
||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toBooleanFunc);
|
interpreter.addBuiltinFunction(toBooleanFunc);
|
||||||
|
|
||||||
// exit moved to sys module
|
// Create a built-in exit function to terminate the program
|
||||||
|
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
|
||||||
|
[](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
int exitCode = 0; // Default exit code
|
||||||
|
|
||||||
// sleep moved into builtin time module
|
if (args.size() > 0) {
|
||||||
|
if (args[0].isNumber()) {
|
||||||
// Introspection: dir(obj) and functions(obj)
|
exitCode = static_cast<int>(args[0].asNumber());
|
||||||
auto dirFunc = std::make_shared<BuiltinFunction>("dir",
|
|
||||||
[](std::vector<Value> args, int, int) -> Value {
|
|
||||||
if (args.size() != 1) return Value(std::vector<Value>{});
|
|
||||||
Value obj = args[0];
|
|
||||||
std::vector<Value> out;
|
|
||||||
if (obj.isModule()) {
|
|
||||||
auto* mod = obj.asModule();
|
|
||||||
if (mod && mod->exports) {
|
|
||||||
for (const auto& kv : *mod->exports) out.push_back(Value(kv.first));
|
|
||||||
}
|
}
|
||||||
} else if (obj.isDict()) {
|
// If not a number, just use default exit code 0
|
||||||
const auto& d = obj.asDict();
|
|
||||||
for (const auto& kv : d) out.push_back(Value(kv.first));
|
|
||||||
}
|
}
|
||||||
return Value(out);
|
|
||||||
|
std::exit(exitCode);
|
||||||
|
return NONE_VALUE; // This line should never be reached
|
||||||
});
|
});
|
||||||
env->define("dir", Value(dirFunc));
|
env->define("exit", Value(exitFunc.get()));
|
||||||
interpreter.addBuiltinFunction(dirFunc);
|
|
||||||
|
|
||||||
auto functionsFunc = std::make_shared<BuiltinFunction>("functions",
|
|
||||||
[](std::vector<Value> args, int, int) -> Value {
|
|
||||||
if (args.size() != 1) return Value(std::vector<Value>{});
|
|
||||||
Value obj = args[0];
|
|
||||||
std::vector<Value> out;
|
|
||||||
auto pushIfFn = [&out](const std::pair<const std::string, Value>& kv){
|
|
||||||
if (kv.second.isFunction() || kv.second.isBuiltinFunction()) out.push_back(Value(kv.first));
|
|
||||||
};
|
|
||||||
if (obj.isModule()) {
|
|
||||||
auto* mod = obj.asModule();
|
|
||||||
if (mod && mod->exports) {
|
|
||||||
for (const auto& kv : *mod->exports) pushIfFn(kv);
|
|
||||||
}
|
|
||||||
} else if (obj.isDict()) {
|
|
||||||
const auto& d = obj.asDict();
|
|
||||||
for (const auto& kv : d) pushIfFn(kv);
|
|
||||||
}
|
|
||||||
return Value(out);
|
|
||||||
});
|
|
||||||
env->define("functions", Value(functionsFunc));
|
|
||||||
interpreter.addBuiltinFunction(functionsFunc);
|
|
||||||
|
|
||||||
// random moved to rand module
|
|
||||||
|
|
||||||
// (eval and evalFile moved to eval module)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// (file I/O moved to io module)
|
|
||||||
|
|
||||||
// memoryUsage moved to sys module
|
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(exitFunc);
|
||||||
}
|
}
|
||||||
6
source/TypeWrapper.cpp
Normal file
6
source/TypeWrapper.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
//
|
||||||
|
// Created by Bobby Lucero on 5/27/23.
|
||||||
|
//
|
||||||
|
#include "../headers/TypeWrapper.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
8
source/Value.cpp
Normal file
8
source/Value.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include "../headers/Value.h"
|
||||||
|
|
||||||
|
// Global constants for common values (no heap allocation)
|
||||||
|
const Value NONE_VALUE = Value();
|
||||||
|
const Value TRUE_VALUE = Value(true);
|
||||||
|
const Value FALSE_VALUE = Value(false);
|
||||||
|
const Value ZERO_VALUE = Value(0.0);
|
||||||
|
const Value ONE_VALUE = Value(1.0);
|
||||||
93
source/bob.cpp
Normal file
93
source/bob.cpp
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "../headers/bob.h"
|
||||||
|
#include "../headers/Parser.h"
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
void Bob::runFile(const string& path)
|
||||||
|
{
|
||||||
|
this->interpreter = msptr(Interpreter)(false);
|
||||||
|
ifstream file = ifstream(path);
|
||||||
|
|
||||||
|
string source;
|
||||||
|
|
||||||
|
if(file.is_open()){
|
||||||
|
source = string(istreambuf_iterator<char>(file), istreambuf_iterator<char>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "File not found" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load source code into error reporter for context
|
||||||
|
errorReporter.loadSource(source, path);
|
||||||
|
|
||||||
|
// Connect error reporter to interpreter
|
||||||
|
interpreter->setErrorReporter(&errorReporter);
|
||||||
|
|
||||||
|
this->run(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bob::runPrompt()
|
||||||
|
{
|
||||||
|
this->interpreter = msptr(Interpreter)(true);
|
||||||
|
|
||||||
|
cout << "Bob v" << VERSION << ", 2023" << endl;
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
cout << "\033[0;36m" << "-> " << "\033[0;37m";
|
||||||
|
std::getline(std::cin, line);
|
||||||
|
|
||||||
|
if(std::cin.eof())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load source code into error reporter for context
|
||||||
|
errorReporter.loadSource(line, "REPL");
|
||||||
|
|
||||||
|
// Connect error reporter to interpreter
|
||||||
|
interpreter->setErrorReporter(&errorReporter);
|
||||||
|
|
||||||
|
this->run(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bob::run(string source)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Connect error reporter to lexer
|
||||||
|
lexer.setErrorReporter(&errorReporter);
|
||||||
|
|
||||||
|
vector<Token> tokens = lexer.Tokenize(std::move(source));
|
||||||
|
Parser p(tokens);
|
||||||
|
|
||||||
|
// Connect error reporter to parser
|
||||||
|
p.setErrorReporter(&errorReporter);
|
||||||
|
|
||||||
|
vector<sptr(Stmt)> statements = p.parse();
|
||||||
|
interpreter->interpret(statements);
|
||||||
|
}
|
||||||
|
catch(std::exception &e)
|
||||||
|
{
|
||||||
|
// Only suppress errors that have already been reported by the error reporter
|
||||||
|
if (errorReporter.hasReportedError()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors that weren't reported (like parser errors, undefined variables, etc.)
|
||||||
|
// print them normally
|
||||||
|
std::cout << "Error: " << e.what() << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch(...)
|
||||||
|
{
|
||||||
|
// Unknown error - report it since it wasn't handled by the interpreter
|
||||||
|
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
16
source/main.cpp
Normal file
16
source/main.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// Created by Bobby Lucero on 5/21/23.
|
||||||
|
//
|
||||||
|
#include "../headers/bob.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]){
|
||||||
|
Bob bobLang;
|
||||||
|
|
||||||
|
if(argc > 1) {
|
||||||
|
bobLang.runFile(argv[1]);
|
||||||
|
} else {
|
||||||
|
bobLang.runPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -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,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'base64' module
|
|
||||||
void registerBase64Module(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'eval' module providing eval(code) and evalFile(path)
|
|
||||||
void registerEvalModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'io' module (file I/O and input)
|
|
||||||
void registerIoModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'json' module
|
|
||||||
void registerJsonModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'math' module
|
|
||||||
void registerMathModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'os' module
|
|
||||||
void registerOsModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'path' module (path utilities)
|
|
||||||
void registerPathModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'rand' module
|
|
||||||
void registerRandModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Registers all builtin modules with the interpreter
|
|
||||||
void registerAllBuiltinModules(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'sys' module
|
|
||||||
void registerSysModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// Register the builtin 'time' module (time functions)
|
|
||||||
void registerTimeModule(Interpreter& interpreter);
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include "Lexer.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "ModuleRegistry.h"
|
|
||||||
#include "helperFunctions/ShortHands.h"
|
|
||||||
#include "ErrorReporter.h"
|
|
||||||
|
|
||||||
#define VERSION "0.0.3"
|
|
||||||
|
|
||||||
class Bob
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Lexer lexer;
|
|
||||||
sptr(Interpreter) interpreter;
|
|
||||||
ErrorReporter errorReporter;
|
|
||||||
|
|
||||||
~Bob() = default;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Embedding helpers (bridge to internal interpreter)
|
|
||||||
void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init) {
|
|
||||||
if (interpreter) interpreter->registerModule(name, init);
|
|
||||||
else pendingConfigurators.push_back([name, init](Interpreter& I){ I.registerModule(name, init); });
|
|
||||||
}
|
|
||||||
void setBuiltinModulePolicy(bool allow) {
|
|
||||||
if (interpreter) interpreter->setBuiltinModulePolicy(allow);
|
|
||||||
else pendingConfigurators.push_back([allow](Interpreter& I){ I.setBuiltinModulePolicy(allow); });
|
|
||||||
}
|
|
||||||
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) {
|
|
||||||
if (interpreter) interpreter->setBuiltinModuleAllowList(allowed);
|
|
||||||
else pendingConfigurators.push_back([allowed](Interpreter& I){ I.setBuiltinModuleAllowList(allowed); });
|
|
||||||
}
|
|
||||||
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) {
|
|
||||||
if (interpreter) interpreter->setBuiltinModuleDenyList(denied);
|
|
||||||
else pendingConfigurators.push_back([denied](Interpreter& I){ I.setBuiltinModuleDenyList(denied); });
|
|
||||||
}
|
|
||||||
bool defineGlobal(const std::string& name, const Value& v) {
|
|
||||||
if (interpreter) return interpreter->defineGlobalVar(name, v);
|
|
||||||
pendingConfigurators.push_back([name, v](Interpreter& I){ I.defineGlobalVar(name, v); });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool tryGetGlobal(const std::string& name, Value& out) const { return interpreter ? interpreter->tryGetGlobalVar(name, out) : false; }
|
|
||||||
void runFile(const std::string& path);
|
|
||||||
void runPrompt();
|
|
||||||
bool evalFile(const std::string& path);
|
|
||||||
bool evalString(const std::string& code, const std::string& filename = "<eval>");
|
|
||||||
|
|
||||||
// Safety policy helpers (public API)
|
|
||||||
// Set all safety-related policies at once
|
|
||||||
void setSafetyPolicy(
|
|
||||||
bool allowBuiltins,
|
|
||||||
const std::vector<std::string>& allowList,
|
|
||||||
const std::vector<std::string>& denyList,
|
|
||||||
bool allowFileImports,
|
|
||||||
bool preferFileOverBuiltin,
|
|
||||||
const std::vector<std::string>& searchPaths
|
|
||||||
) {
|
|
||||||
if (interpreter) {
|
|
||||||
interpreter->setBuiltinModulePolicy(allowBuiltins);
|
|
||||||
interpreter->setBuiltinModuleAllowList(allowList);
|
|
||||||
interpreter->setBuiltinModuleDenyList(denyList);
|
|
||||||
interpreter->setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
|
|
||||||
} else {
|
|
||||||
pendingConfigurators.push_back([=](Interpreter& I){
|
|
||||||
I.setBuiltinModulePolicy(allowBuiltins);
|
|
||||||
I.setBuiltinModuleAllowList(allowList);
|
|
||||||
I.setBuiltinModuleDenyList(denyList);
|
|
||||||
I.setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple presets: "open", "safe", "locked"
|
|
||||||
void setSafetyPreset(const std::string& preset) {
|
|
||||||
if (preset == "open") {
|
|
||||||
setSafetyPolicy(
|
|
||||||
true, /* allowBuiltins */
|
|
||||||
{}, /* allowList -> empty means allow all */
|
|
||||||
{},
|
|
||||||
true, /* allowFileImports */
|
|
||||||
true, /* preferFileOverBuiltin */
|
|
||||||
{} /* searchPaths */
|
|
||||||
);
|
|
||||||
} else if (preset == "safe") {
|
|
||||||
// Allow only pure/harmless modules by default
|
|
||||||
setSafetyPolicy(
|
|
||||||
true,
|
|
||||||
std::vector<std::string>{
|
|
||||||
"sys", "time", "rand", "math", "path", "base64"
|
|
||||||
},
|
|
||||||
std::vector<std::string>{ /* denyList empty when allowList is used */ },
|
|
||||||
false, /* disallow file-based imports */
|
|
||||||
true,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
} else if (preset == "locked") {
|
|
||||||
// No builtins visible; no file imports
|
|
||||||
setSafetyPolicy(
|
|
||||||
false,
|
|
||||||
{},
|
|
||||||
{},
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Default to safe
|
|
||||||
setSafetyPreset("safe");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void ensureInterpreter(bool interactive);
|
|
||||||
void applyPendingConfigs() {
|
|
||||||
if (!interpreter) return;
|
|
||||||
for (auto& f : pendingConfigurators) { f(*interpreter); }
|
|
||||||
pendingConfigurators.clear();
|
|
||||||
}
|
|
||||||
std::vector<std::function<void(Interpreter&)>> pendingConfigurators;
|
|
||||||
};
|
|
||||||
|
|
||||||
@ -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,305 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
#include "helperFunctions/ShortHands.h"
|
|
||||||
#include "TypeWrapper.h"
|
|
||||||
#include "Expression.h"
|
|
||||||
|
|
||||||
struct ExpressionStmt;
|
|
||||||
struct VarStmt;
|
|
||||||
struct BlockStmt;
|
|
||||||
struct FunctionStmt;
|
|
||||||
struct ReturnStmt;
|
|
||||||
struct IfStmt;
|
|
||||||
struct WhileStmt;
|
|
||||||
struct DoWhileStmt;
|
|
||||||
struct ForStmt;
|
|
||||||
struct BreakStmt;
|
|
||||||
struct ContinueStmt;
|
|
||||||
struct AssignStmt;
|
|
||||||
struct ClassStmt;
|
|
||||||
struct ExtensionStmt;
|
|
||||||
|
|
||||||
#include "ExecutionContext.h"
|
|
||||||
|
|
||||||
struct StmtVisitor
|
|
||||||
{
|
|
||||||
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitWhileStmt(const std::shared_ptr<WhileStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitForStmt(const std::shared_ptr<ForStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitBreakStmt(const std::shared_ptr<BreakStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitContinueStmt(const std::shared_ptr<ContinueStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitAssignStmt(const std::shared_ptr<AssignStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitClassStmt(const std::shared_ptr<ClassStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitTryStmt(const std::shared_ptr<struct TryStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitThrowStmt(const std::shared_ptr<struct ThrowStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitImportStmt(const std::shared_ptr<struct ImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual void visitFromImportStmt(const std::shared_ptr<struct FromImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> expression;
|
|
||||||
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
|
|
||||||
virtual ~Stmt(){};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClassField {
|
|
||||||
Token name;
|
|
||||||
std::shared_ptr<Expr> initializer; // may be null
|
|
||||||
ClassField(Token name, std::shared_ptr<Expr> init) : name(name), initializer(init) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClassStmt : Stmt {
|
|
||||||
const Token name;
|
|
||||||
bool hasParent;
|
|
||||||
Token parentName; // valid only if hasParent
|
|
||||||
std::vector<ClassField> fields;
|
|
||||||
std::vector<std::shared_ptr<FunctionStmt>> methods;
|
|
||||||
|
|
||||||
ClassStmt(Token name, bool hasParent, Token parentName, std::vector<ClassField> fields, std::vector<std::shared_ptr<FunctionStmt>> methods)
|
|
||||||
: name(name), hasParent(hasParent), parentName(parentName), fields(std::move(fields)), methods(std::move(methods)) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
|
||||||
visitor->visitClassStmt(std::static_pointer_cast<ClassStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ExtensionStmt : Stmt {
|
|
||||||
const Token target;
|
|
||||||
std::vector<std::shared_ptr<FunctionStmt>> methods;
|
|
||||||
|
|
||||||
ExtensionStmt(Token target, std::vector<std::shared_ptr<FunctionStmt>> methods)
|
|
||||||
: target(target), methods(std::move(methods)) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
|
||||||
visitor->visitExtensionStmt(std::static_pointer_cast<ExtensionStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BlockStmt : Stmt
|
|
||||||
{
|
|
||||||
std::vector<std::shared_ptr<Stmt>> statements;
|
|
||||||
explicit BlockStmt(std::vector<std::shared_ptr<Stmt>> statements) : statements(statements)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ExpressionStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> expression;
|
|
||||||
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct VarStmt : Stmt
|
|
||||||
{
|
|
||||||
Token name;
|
|
||||||
std::shared_ptr<Expr> initializer;
|
|
||||||
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FunctionStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token name;
|
|
||||||
const std::vector<Token> params;
|
|
||||||
std::vector<std::shared_ptr<Stmt>> body;
|
|
||||||
|
|
||||||
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt>> body)
|
|
||||||
: name(name), params(params), body(body) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReturnStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token keyword;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
|
|
||||||
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IfStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Stmt> thenBranch;
|
|
||||||
std::shared_ptr<Stmt> elseBranch;
|
|
||||||
|
|
||||||
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
|
|
||||||
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WhileStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Stmt> body;
|
|
||||||
|
|
||||||
WhileStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> body)
|
|
||||||
: condition(condition), body(body) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitWhileStmt(std::static_pointer_cast<WhileStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DoWhileStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Stmt> body;
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
|
|
||||||
DoWhileStmt(std::shared_ptr<Stmt> body, std::shared_ptr<Expr> condition)
|
|
||||||
: body(body), condition(condition) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitDoWhileStmt(std::static_pointer_cast<DoWhileStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ForStmt : Stmt
|
|
||||||
{
|
|
||||||
std::shared_ptr<Stmt> initializer;
|
|
||||||
std::shared_ptr<Expr> condition;
|
|
||||||
std::shared_ptr<Expr> increment;
|
|
||||||
std::shared_ptr<Stmt> body;
|
|
||||||
|
|
||||||
ForStmt(std::shared_ptr<Stmt> initializer, std::shared_ptr<Expr> condition,
|
|
||||||
std::shared_ptr<Expr> increment, std::shared_ptr<Stmt> body)
|
|
||||||
: initializer(initializer), condition(condition), increment(increment), body(body) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitForStmt(std::static_pointer_cast<ForStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BreakStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token keyword;
|
|
||||||
|
|
||||||
BreakStmt(Token keyword) : keyword(keyword) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitBreakStmt(std::static_pointer_cast<BreakStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ContinueStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token keyword;
|
|
||||||
|
|
||||||
ContinueStmt(Token keyword) : keyword(keyword) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitContinueStmt(std::static_pointer_cast<ContinueStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AssignStmt : Stmt
|
|
||||||
{
|
|
||||||
const Token name;
|
|
||||||
const Token op;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
|
|
||||||
AssignStmt(Token name, Token op, std::shared_ptr<Expr> value)
|
|
||||||
: name(name), op(op), value(value) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
|
||||||
{
|
|
||||||
visitor->visitAssignStmt(std::static_pointer_cast<AssignStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TryStmt : Stmt {
|
|
||||||
std::shared_ptr<Stmt> tryBlock;
|
|
||||||
Token catchVar; // IDENTIFIER or empty token if no catch
|
|
||||||
std::shared_ptr<Stmt> catchBlock; // may be null
|
|
||||||
std::shared_ptr<Stmt> finallyBlock; // may be null
|
|
||||||
|
|
||||||
TryStmt(std::shared_ptr<Stmt> t, Token cvar, std::shared_ptr<Stmt> cblk, std::shared_ptr<Stmt> fblk)
|
|
||||||
: tryBlock(t), catchVar(cvar), catchBlock(cblk), finallyBlock(fblk) {}
|
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
|
||||||
visitor->visitTryStmt(std::static_pointer_cast<TryStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ThrowStmt : Stmt {
|
|
||||||
const Token keyword;
|
|
||||||
std::shared_ptr<Expr> value;
|
|
||||||
ThrowStmt(Token kw, std::shared_ptr<Expr> v) : keyword(kw), value(v) {}
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
|
||||||
visitor->visitThrowStmt(std::static_pointer_cast<ThrowStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// import module [as alias]
|
|
||||||
struct ImportStmt : Stmt {
|
|
||||||
Token importToken; // IMPORT
|
|
||||||
Token moduleName; // IDENTIFIER
|
|
||||||
bool hasAlias = false;
|
|
||||||
Token alias; // IDENTIFIER if hasAlias
|
|
||||||
ImportStmt(Token kw, Token mod, bool ha, Token al)
|
|
||||||
: importToken(kw), moduleName(mod), hasAlias(ha), alias(al) {}
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
|
||||||
visitor->visitImportStmt(std::static_pointer_cast<ImportStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// from module import name [as alias], name2 ...
|
|
||||||
struct FromImportStmt : Stmt {
|
|
||||||
Token fromToken; // FROM
|
|
||||||
Token moduleName; // IDENTIFIER or STRING
|
|
||||||
struct ImportItem { Token name; bool hasAlias; Token alias; };
|
|
||||||
std::vector<ImportItem> items;
|
|
||||||
bool importAll = false; // true for: from module import *;
|
|
||||||
FromImportStmt(Token kw, Token mod, std::vector<ImportItem> it)
|
|
||||||
: fromToken(kw), moduleName(mod), items(std::move(it)), importAll(false) {}
|
|
||||||
FromImportStmt(Token kw, Token mod, bool all)
|
|
||||||
: fromToken(kw), moduleName(mod), importAll(all) {}
|
|
||||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
|
||||||
visitor->visitFromImportStmt(std::static_pointer_cast<FromImportStmt>(shared_from_this()), context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Value.h"
|
|
||||||
#include "Lexer.h"
|
|
||||||
|
|
||||||
// Utility to compute the result of a compound assignment (e.g., +=, -=, etc.)
|
|
||||||
// Leaves error reporting to callers; throws std::runtime_error on unknown operator
|
|
||||||
inline Value computeCompoundAssignment(const Value& currentValue, TokenType opType, const Value& rhs) {
|
|
||||||
switch (opType) {
|
|
||||||
case PLUS_EQUAL:
|
|
||||||
return currentValue + rhs;
|
|
||||||
case MINUS_EQUAL:
|
|
||||||
return currentValue - rhs;
|
|
||||||
case STAR_EQUAL:
|
|
||||||
return currentValue * rhs;
|
|
||||||
case SLASH_EQUAL:
|
|
||||||
return currentValue / rhs;
|
|
||||||
case PERCENT_EQUAL:
|
|
||||||
return currentValue % rhs;
|
|
||||||
case BIN_AND_EQUAL:
|
|
||||||
return currentValue & rhs;
|
|
||||||
case BIN_OR_EQUAL:
|
|
||||||
return currentValue | rhs;
|
|
||||||
case BIN_XOR_EQUAL:
|
|
||||||
return currentValue ^ rhs;
|
|
||||||
case BIN_SLEFT_EQUAL:
|
|
||||||
return currentValue << rhs;
|
|
||||||
case BIN_SRIGHT_EQUAL:
|
|
||||||
return currentValue >> rhs;
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Unknown compound assignment operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -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,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Value.h"
|
|
||||||
|
|
||||||
struct ExecutionContext {
|
|
||||||
bool isFunctionBody = false;
|
|
||||||
bool hasReturn = false;
|
|
||||||
Value returnValue = NONE_VALUE;
|
|
||||||
bool shouldBreak = false;
|
|
||||||
bool shouldContinue = false;
|
|
||||||
bool hasThrow = false;
|
|
||||||
Value thrownValue = NONE_VALUE;
|
|
||||||
int throwLine = 0;
|
|
||||||
int throwColumn = 0;
|
|
||||||
};
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Statement.h"
|
|
||||||
|
|
||||||
class Evaluator; // Forward declaration
|
|
||||||
class Interpreter; // Forward declaration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Executor
|
|
||||||
* @brief Handles the execution of statements and control flow.
|
|
||||||
*
|
|
||||||
* Implements the StmtVisitor pattern. It is responsible for executing statements,
|
|
||||||
* managing environments, and handling control flow constructs like loops and
|
|
||||||
* conditionals. It uses the Evaluator to evaluate expressions when needed.
|
|
||||||
*/
|
|
||||||
class Executor : public StmtVisitor {
|
|
||||||
private:
|
|
||||||
Interpreter* interpreter; // Back-pointer to access interpreter services
|
|
||||||
Evaluator* evaluator; // For evaluating expressions
|
|
||||||
|
|
||||||
public:
|
|
||||||
Executor(Interpreter* interpreter, Evaluator* evaluator);
|
|
||||||
virtual ~Executor();
|
|
||||||
|
|
||||||
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
|
|
||||||
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
|
|
||||||
|
|
||||||
// Statement Visitors
|
|
||||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitClassStmt(const std::shared_ptr<ClassStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitImportStmt(const std::shared_ptr<ImportStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitFromImportStmt(const std::shared_ptr<FromImportStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
|
||||||
};
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <stack>
|
|
||||||
#include <optional>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "Value.h"
|
|
||||||
#include "TypeWrapper.h"
|
|
||||||
#include "RuntimeDiagnostics.h"
|
|
||||||
#include "ModuleRegistry.h"
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
struct Expr;
|
|
||||||
struct Stmt;
|
|
||||||
struct Environment;
|
|
||||||
struct BuiltinFunction;
|
|
||||||
struct Function;
|
|
||||||
struct Thunk;
|
|
||||||
class ErrorReporter;
|
|
||||||
struct ExecutionContext;
|
|
||||||
struct CallExpr;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
class Evaluator;
|
|
||||||
|
|
||||||
// RAII helper for thunk execution flag
|
|
||||||
struct ScopedThunkFlag {
|
|
||||||
bool& flag;
|
|
||||||
bool prev;
|
|
||||||
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
|
|
||||||
~ScopedThunkFlag() { flag = prev; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// RAII helper for environment management
|
|
||||||
struct ScopedEnv {
|
|
||||||
std::shared_ptr<Environment>& target;
|
|
||||||
std::shared_ptr<Environment> prev;
|
|
||||||
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
|
|
||||||
~ScopedEnv() { target = prev; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Thunk class for trampoline-based tail call optimization
|
|
||||||
struct Thunk {
|
|
||||||
public:
|
|
||||||
using ThunkFunction = std::function<Value()>;
|
|
||||||
|
|
||||||
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
|
|
||||||
|
|
||||||
Value execute() const {
|
|
||||||
return func();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isThunk() const { return true; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ThunkFunction func;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Executor;
|
|
||||||
|
|
||||||
class Interpreter {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Environment> environment;
|
|
||||||
bool isInteractive;
|
|
||||||
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
|
|
||||||
std::vector<std::shared_ptr<Function>> functions;
|
|
||||||
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
|
|
||||||
// Global extension registries
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<Function>>> classExtensions;
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<Function>>> builtinExtensions; // keys: "string","array","dict","any"
|
|
||||||
std::unordered_map<std::string, std::string> classParents; // child -> parent
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, Value>> classTemplates; // className -> template dict
|
|
||||||
// Field initializers per class in source order (to evaluate across inheritance chain)
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, std::shared_ptr<Expr>>>> classFieldInitializers; // className -> [(field, expr)]
|
|
||||||
ErrorReporter* errorReporter;
|
|
||||||
bool inThunkExecution = false;
|
|
||||||
|
|
||||||
// Automatic cleanup tracking
|
|
||||||
int thunkCreationCount = 0;
|
|
||||||
static const int CLEANUP_THRESHOLD = 10000;
|
|
||||||
|
|
||||||
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
|
||||||
std::unique_ptr<Evaluator> evaluator;
|
|
||||||
std::unique_ptr<Executor> executor;
|
|
||||||
// Module cache: module key -> module dict value
|
|
||||||
std::unordered_map<std::string, Value> moduleCache;
|
|
||||||
// Builtin module registry
|
|
||||||
ModuleRegistry builtinModules;
|
|
||||||
// Import policy flags
|
|
||||||
bool allowFileImports = true;
|
|
||||||
bool preferFileOverBuiltin = true;
|
|
||||||
bool allowBuiltinImports = true;
|
|
||||||
std::vector<std::string> moduleSearchPaths; // e.g., BOBPATH
|
|
||||||
// Pending throw propagation from expression evaluation
|
|
||||||
bool hasPendingThrow = false;
|
|
||||||
Value pendingThrow = NONE_VALUE;
|
|
||||||
int pendingThrowLine = 0;
|
|
||||||
int pendingThrowColumn = 0;
|
|
||||||
int lastErrorLine = 0;
|
|
||||||
int lastErrorColumn = 0;
|
|
||||||
int tryDepth = 0;
|
|
||||||
bool inlineErrorReported = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Interpreter(bool isInteractive);
|
|
||||||
virtual ~Interpreter();
|
|
||||||
|
|
||||||
// Public interface for main
|
|
||||||
void interpret(std::vector<std::shared_ptr<Stmt>> statements);
|
|
||||||
void setErrorReporter(ErrorReporter* reporter);
|
|
||||||
|
|
||||||
// Methods needed by Evaluator
|
|
||||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
|
||||||
Value evaluateCallExprInline(const std::shared_ptr<CallExpr>& expression); // Inline TCO for performance
|
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
|
||||||
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
|
||||||
bool isTruthy(Value object);
|
|
||||||
bool isEqual(Value a, Value b);
|
|
||||||
std::string stringify(Value object);
|
|
||||||
bool isInteractiveMode() const;
|
|
||||||
std::shared_ptr<Environment> getEnvironment();
|
|
||||||
void setEnvironment(std::shared_ptr<Environment> env);
|
|
||||||
ErrorReporter* getErrorReporter() const { return errorReporter; }
|
|
||||||
|
|
||||||
void addFunction(std::shared_ptr<Function> function);
|
|
||||||
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
|
|
||||||
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
|
||||||
void cleanupUnusedFunctions();
|
|
||||||
void cleanupUnusedThunks();
|
|
||||||
void forceCleanup();
|
|
||||||
// Extension APIs
|
|
||||||
void registerExtension(const std::string& targetName, const std::string& methodName, std::shared_ptr<Function> fn);
|
|
||||||
std::shared_ptr<Function> lookupExtension(const std::string& targetName, const std::string& methodName);
|
|
||||||
void registerClass(const std::string& className, const std::string& parentName);
|
|
||||||
std::string getParentClass(const std::string& className) const;
|
|
||||||
void setClassTemplate(const std::string& className, const std::unordered_map<std::string, Value>& tmpl);
|
|
||||||
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
|
|
||||||
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
|
|
||||||
// Field initializer APIs
|
|
||||||
void setClassFieldInitializers(const std::string& className, const std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& inits) { classFieldInitializers[className] = inits; }
|
|
||||||
bool getClassFieldInitializers(const std::string& className, std::vector<std::pair<std::string, std::shared_ptr<Expr>>>& out) const {
|
|
||||||
auto it = classFieldInitializers.find(className);
|
|
||||||
if (it == classFieldInitializers.end()) return false; out = it->second; return true;
|
|
||||||
}
|
|
||||||
void addStdLibFunctions();
|
|
||||||
// Module APIs
|
|
||||||
Value importModule(const std::string& spec, int line, int column); // returns module dict
|
|
||||||
bool fromImport(const std::string& spec, const std::vector<std::pair<std::string, std::string>>& items, int line, int column); // name->alias
|
|
||||||
void setModulePolicy(bool allowFiles, bool preferFiles, const std::vector<std::string>& searchPaths);
|
|
||||||
void setBuiltinModulePolicy(bool allowBuiltins) { allowBuiltinImports = allowBuiltins; builtinModules.setPolicy(allowBuiltins); }
|
|
||||||
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) { builtinModules.setAllowList(allowed); }
|
|
||||||
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) { builtinModules.setDenyList(denied); }
|
|
||||||
void registerBuiltinModule(const std::string& name, std::function<Value(Interpreter&)> factory) { builtinModules.registerFactory(name, std::move(factory)); }
|
|
||||||
|
|
||||||
// Simple module registration API
|
|
||||||
using ModuleBuilder = ModuleRegistry::ModuleBuilder;
|
|
||||||
|
|
||||||
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
|
|
||||||
builtinModules.registerModule(name, std::move(init));
|
|
||||||
}
|
|
||||||
// Global environment helpers
|
|
||||||
bool defineGlobalVar(const std::string& name, const Value& value);
|
|
||||||
bool tryGetGlobalVar(const std::string& name, Value& out) const;
|
|
||||||
// Throw propagation helpers
|
|
||||||
void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
|
|
||||||
bool consumePendingThrow(Value& out, int* lineOut = nullptr, int* colOut = nullptr) { if (!hasPendingThrow) return false; out = pendingThrow; if (lineOut) *lineOut = pendingThrowLine; if (colOut) *colOut = pendingThrowColumn; hasPendingThrow = false; pendingThrow = NONE_VALUE; pendingThrowLine = 0; pendingThrowColumn = 0; return true; }
|
|
||||||
// Try tracking
|
|
||||||
void enterTry() { tryDepth++; }
|
|
||||||
void exitTry() { if (tryDepth > 0) tryDepth--; }
|
|
||||||
bool isInTry() const { return tryDepth > 0; }
|
|
||||||
void markInlineErrorReported() { inlineErrorReported = true; }
|
|
||||||
bool hasInlineErrorReported() const { return inlineErrorReported; }
|
|
||||||
void clearInlineErrorReported() { inlineErrorReported = false; }
|
|
||||||
bool hasReportedError() const;
|
|
||||||
// Last error site tracking
|
|
||||||
void setLastErrorSite(int line, int column) { lastErrorLine = line; lastErrorColumn = column; }
|
|
||||||
int getLastErrorLine() const { return lastErrorLine; }
|
|
||||||
int getLastErrorColumn() const { return lastErrorColumn; }
|
|
||||||
|
|
||||||
// Process/host metadata (for sys module)
|
|
||||||
void setArgv(const std::vector<std::string>& args, const std::string& executablePath) { argvData = args; executableFile = executablePath; }
|
|
||||||
std::vector<std::string> getArgv() const { return argvData; }
|
|
||||||
std::string getExecutablePath() const { return executableFile; }
|
|
||||||
std::unordered_map<std::string, Value> getModuleCacheSnapshot() const { return moduleCache; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
Value runTrampoline(Value initialResult);
|
|
||||||
// Stored argv/executable for sys module
|
|
||||||
std::vector<std::string> argvData;
|
|
||||||
std::string executableFile;
|
|
||||||
};
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
// ModuleDef.h
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "Value.h"
|
|
||||||
|
|
||||||
struct Module {
|
|
||||||
std::string name;
|
|
||||||
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include "TypeWrapper.h" // BuiltinFunction, Value
|
|
||||||
|
|
||||||
class Interpreter; // fwd
|
|
||||||
|
|
||||||
class ModuleRegistry {
|
|
||||||
public:
|
|
||||||
struct ModuleBuilder {
|
|
||||||
std::string moduleName;
|
|
||||||
Interpreter& interpreterRef;
|
|
||||||
std::unordered_map<std::string, Value> exports;
|
|
||||||
ModuleBuilder(const std::string& n, Interpreter& i) : moduleName(n), interpreterRef(i) {}
|
|
||||||
void fn(const std::string& name, std::function<Value(std::vector<Value>, int, int)> func) {
|
|
||||||
exports[name] = Value(std::make_shared<BuiltinFunction>(name, func));
|
|
||||||
}
|
|
||||||
void val(const std::string& name, const Value& v) { exports[name] = v; }
|
|
||||||
};
|
|
||||||
|
|
||||||
using Factory = std::function<Value(Interpreter&)>;
|
|
||||||
|
|
||||||
void registerFactory(const std::string& name, Factory factory) {
|
|
||||||
factories[name] = std::move(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
|
|
||||||
registerFactory(name, [name, init](Interpreter& I) -> Value {
|
|
||||||
ModuleBuilder b(name, I);
|
|
||||||
init(b);
|
|
||||||
auto m = std::make_shared<Module>(name, b.exports);
|
|
||||||
return Value(m);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has(const std::string& name) const {
|
|
||||||
auto it = factories.find(name);
|
|
||||||
if (it == factories.end()) return false;
|
|
||||||
// Respect policy for presence checks to optionally cloak denied modules
|
|
||||||
if (!allowBuiltins) return false;
|
|
||||||
if (!allowList.empty() && allowList.find(name) == allowList.end()) return false;
|
|
||||||
if (denyList.find(name) != denyList.end()) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value create(const std::string& name, Interpreter& I) const {
|
|
||||||
auto it = factories.find(name);
|
|
||||||
if (it == factories.end()) return NONE_VALUE;
|
|
||||||
if (!allowBuiltins) return NONE_VALUE;
|
|
||||||
if (!allowList.empty() && allowList.find(name) == allowList.end()) return NONE_VALUE;
|
|
||||||
if (denyList.find(name) != denyList.end()) return NONE_VALUE;
|
|
||||||
return it->second(I);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPolicy(bool allow) { allowBuiltins = allow; }
|
|
||||||
void setAllowList(const std::vector<std::string>& allowed) { allowList = std::unordered_set<std::string>(allowed.begin(), allowed.end()); }
|
|
||||||
void setDenyList(const std::vector<std::string>& denied) { denyList = std::unordered_set<std::string>(denied.begin(), denied.end()); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unordered_map<std::string, Factory> factories;
|
|
||||||
std::unordered_set<std::string> allowList;
|
|
||||||
std::unordered_set<std::string> denyList;
|
|
||||||
bool allowBuiltins = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Value.h"
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// Forward declarations from Value.h
|
|
||||||
struct Function;
|
|
||||||
struct BuiltinFunction;
|
|
||||||
struct Thunk;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RuntimeDiagnostics - Utility functions for runtime operations
|
|
||||||
*
|
|
||||||
* This class handles value conversion, equality checking, string representation,
|
|
||||||
* and other diagnostic utilities that don't belong in core evaluation logic.
|
|
||||||
*/
|
|
||||||
class RuntimeDiagnostics {
|
|
||||||
public:
|
|
||||||
RuntimeDiagnostics() = default;
|
|
||||||
|
|
||||||
// Value utility functions
|
|
||||||
bool isTruthy(Value object);
|
|
||||||
bool isEqual(Value a, Value b);
|
|
||||||
std::string stringify(Value object);
|
|
||||||
|
|
||||||
// Memory management utilities
|
|
||||||
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
|
|
||||||
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& functions);
|
|
||||||
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
|
|
||||||
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
|
|
||||||
std::vector<std::shared_ptr<Function>>& functions,
|
|
||||||
std::vector<std::shared_ptr<Thunk>>& thunks);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Helper methods for stringify
|
|
||||||
std::string formatNumber(double value);
|
|
||||||
std::string formatArray(const std::vector<Value>& arr);
|
|
||||||
std::string formatDict(const std::unordered_map<std::string, Value>& dict);
|
|
||||||
};
|
|
||||||
@ -1,458 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "helperFunctions/ErrorUtils.h"
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
#include <cmath>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
struct Environment;
|
|
||||||
struct Function;
|
|
||||||
struct BuiltinFunction;
|
|
||||||
struct Thunk;
|
|
||||||
struct Module;
|
|
||||||
|
|
||||||
// Type tags for the Value union
|
|
||||||
enum ValueType {
|
|
||||||
VAL_NONE,
|
|
||||||
VAL_NUMBER,
|
|
||||||
VAL_BOOLEAN,
|
|
||||||
VAL_STRING,
|
|
||||||
VAL_FUNCTION,
|
|
||||||
VAL_BUILTIN_FUNCTION,
|
|
||||||
VAL_THUNK,
|
|
||||||
VAL_ARRAY,
|
|
||||||
VAL_DICT,
|
|
||||||
VAL_MODULE
|
|
||||||
};
|
|
||||||
|
|
||||||
// (moved below Value)
|
|
||||||
|
|
||||||
// Tagged value system (like Lua) - no heap allocation for simple values
|
|
||||||
struct Value {
|
|
||||||
union {
|
|
||||||
double number;
|
|
||||||
bool boolean;
|
|
||||||
};
|
|
||||||
ValueType type;
|
|
||||||
std::string string_value; // Store strings outside the union for safety
|
|
||||||
std::shared_ptr<std::vector<Value> > array_value; // Store arrays as shared_ptr for mutability
|
|
||||||
std::shared_ptr<std::unordered_map<std::string, Value> > dict_value; // Store dictionaries as shared_ptr for mutability
|
|
||||||
std::shared_ptr<Module> module_value; // Module object
|
|
||||||
|
|
||||||
// Store functions as shared_ptr for proper reference counting
|
|
||||||
std::shared_ptr<Function> function;
|
|
||||||
std::shared_ptr<BuiltinFunction> builtin_function;
|
|
||||||
std::shared_ptr<Thunk> thunk;
|
|
||||||
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
|
||||||
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
|
||||||
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
|
|
||||||
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
|
||||||
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
|
||||||
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
|
||||||
Value(std::shared_ptr<Function> f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
|
||||||
Value(std::shared_ptr<BuiltinFunction> bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
|
||||||
Value(std::shared_ptr<Thunk> t) : thunk(t), type(ValueType::VAL_THUNK) {}
|
|
||||||
Value(const std::vector<Value>& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(arr)) {}
|
|
||||||
Value(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(arr))) {}
|
|
||||||
Value(const std::unordered_map<std::string, Value>& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(dict)) {}
|
|
||||||
Value(std::unordered_map<std::string, Value>&& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(std::move(dict))) {}
|
|
||||||
Value(std::shared_ptr<Module> m) : type(ValueType::VAL_MODULE), module_value(std::move(m)) {}
|
|
||||||
|
|
||||||
// Destructor to clean up functions and thunks
|
|
||||||
~Value() {
|
|
||||||
// Functions and thunks are managed by the Interpreter, so we don't delete them
|
|
||||||
// Arrays and dictionaries are managed by shared_ptr, so they clean up automatically
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Move constructor
|
|
||||||
Value(Value&& other) noexcept
|
|
||||||
: type(other.type), string_value(std::move(other.string_value)), array_value(std::move(other.array_value)), dict_value(std::move(other.dict_value)),
|
|
||||||
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)), module_value(std::move(other.module_value)) {
|
|
||||||
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
|
|
||||||
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
|
|
||||||
number = other.number; // Copy the union
|
|
||||||
}
|
|
||||||
other.type = ValueType::VAL_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move assignment
|
|
||||||
Value& operator=(Value&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
type = other.type;
|
|
||||||
if (type == ValueType::VAL_STRING) {
|
|
||||||
string_value = std::move(other.string_value);
|
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
|
||||||
array_value = std::move(other.array_value);
|
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
|
||||||
dict_value = std::move(other.dict_value);
|
|
||||||
} else if (type == ValueType::VAL_FUNCTION) {
|
|
||||||
function = std::move(other.function);
|
|
||||||
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
|
||||||
builtin_function = std::move(other.builtin_function);
|
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
|
||||||
thunk = std::move(other.thunk);
|
|
||||||
} else if (type == ValueType::VAL_MODULE) {
|
|
||||||
module_value = std::move(other.module_value);
|
|
||||||
} else {
|
|
||||||
number = other.number;
|
|
||||||
}
|
|
||||||
|
|
||||||
other.type = ValueType::VAL_NONE;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy constructor (only when needed)
|
|
||||||
Value(const Value& other) : type(other.type) {
|
|
||||||
if (type == ValueType::VAL_STRING) {
|
|
||||||
string_value = other.string_value;
|
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
|
||||||
array_value = other.array_value; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
|
||||||
dict_value = other.dict_value; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_FUNCTION) {
|
|
||||||
function = other.function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
|
||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_MODULE) {
|
|
||||||
module_value = other.module_value; // shared module
|
|
||||||
} else {
|
|
||||||
number = other.number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy assignment (only when needed)
|
|
||||||
Value& operator=(const Value& other) {
|
|
||||||
if (this != &other) {
|
|
||||||
// First, clear all old shared_ptr members to release references
|
|
||||||
array_value.reset();
|
|
||||||
dict_value.reset();
|
|
||||||
function.reset();
|
|
||||||
builtin_function.reset();
|
|
||||||
thunk.reset();
|
|
||||||
|
|
||||||
// Then set the new type and value
|
|
||||||
type = other.type;
|
|
||||||
if (type == ValueType::VAL_STRING) {
|
|
||||||
string_value = other.string_value;
|
|
||||||
} else if (type == ValueType::VAL_ARRAY) {
|
|
||||||
array_value = other.array_value; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_DICT) {
|
|
||||||
dict_value = other.dict_value;
|
|
||||||
} else if (type == ValueType::VAL_FUNCTION) {
|
|
||||||
function = other.function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_BUILTIN_FUNCTION) {
|
|
||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
|
||||||
} else if (type == ValueType::VAL_MODULE) {
|
|
||||||
module_value = other.module_value;
|
|
||||||
} else {
|
|
||||||
number = other.number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type checking (fast, no dynamic casting) - inline for performance
|
|
||||||
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
|
|
||||||
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
|
|
||||||
inline bool isString() const { return type == ValueType::VAL_STRING; }
|
|
||||||
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
|
|
||||||
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
|
||||||
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
|
|
||||||
inline bool isDict() const { return type == ValueType::VAL_DICT; }
|
|
||||||
inline bool isModule() const { return type == ValueType::VAL_MODULE; }
|
|
||||||
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
|
|
||||||
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
|
||||||
|
|
||||||
// Get type name as string for error messages
|
|
||||||
inline std::string getType() const {
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return "none";
|
|
||||||
case ValueType::VAL_NUMBER: return "number";
|
|
||||||
case ValueType::VAL_BOOLEAN: return "boolean";
|
|
||||||
case ValueType::VAL_STRING: return "string";
|
|
||||||
case ValueType::VAL_FUNCTION: return "function";
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return "builtin_function";
|
|
||||||
case ValueType::VAL_THUNK: return "thunk";
|
|
||||||
case ValueType::VAL_ARRAY: return "array";
|
|
||||||
case ValueType::VAL_DICT: return "dict";
|
|
||||||
case ValueType::VAL_MODULE: return "module";
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Value extraction (safe, with type checking) - inline for performance
|
|
||||||
inline double asNumber() const { return isNumber() ? number : 0.0; }
|
|
||||||
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
|
|
||||||
inline const std::string& asString() const { return string_value; }
|
|
||||||
inline const std::vector<Value>& asArray() const {
|
|
||||||
return *array_value;
|
|
||||||
}
|
|
||||||
inline std::vector<Value>& asArray() {
|
|
||||||
return *array_value;
|
|
||||||
}
|
|
||||||
inline const std::unordered_map<std::string, Value>& asDict() const {
|
|
||||||
return *dict_value;
|
|
||||||
}
|
|
||||||
inline std::unordered_map<std::string, Value>& asDict() {
|
|
||||||
return *dict_value;
|
|
||||||
}
|
|
||||||
inline Module* asModule() const { return isModule() ? module_value.get() : nullptr; }
|
|
||||||
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
|
||||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
|
||||||
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
|
||||||
|
|
||||||
// Truthiness check - inline for performance
|
|
||||||
inline bool isTruthy() const {
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return false;
|
|
||||||
case ValueType::VAL_BOOLEAN: return boolean;
|
|
||||||
case ValueType::VAL_NUMBER: return number != 0.0;
|
|
||||||
case ValueType::VAL_STRING: return !string_value.empty();
|
|
||||||
case ValueType::VAL_FUNCTION: return function != nullptr;
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
|
|
||||||
case ValueType::VAL_THUNK: return thunk != nullptr;
|
|
||||||
case ValueType::VAL_ARRAY: return !array_value->empty();
|
|
||||||
case ValueType::VAL_DICT: return !dict_value->empty();
|
|
||||||
case ValueType::VAL_MODULE: return module_value != nullptr;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equality comparison - inline for performance
|
|
||||||
inline bool equals(const Value& other) const {
|
|
||||||
if (type != other.type) return false;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return true;
|
|
||||||
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
|
|
||||||
case ValueType::VAL_NUMBER: return number == other.number;
|
|
||||||
case ValueType::VAL_STRING: return string_value == other.string_value;
|
|
||||||
case ValueType::VAL_FUNCTION: return function == other.function;
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
|
|
||||||
case ValueType::VAL_THUNK: return thunk == other.thunk;
|
|
||||||
case ValueType::VAL_ARRAY: {
|
|
||||||
if (array_value->size() != other.array_value->size()) return false;
|
|
||||||
for (size_t i = 0; i < array_value->size(); i++) {
|
|
||||||
if (!(*array_value)[i].equals((*other.array_value)[i])) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case ValueType::VAL_DICT: {
|
|
||||||
if (dict_value->size() != other.dict_value->size()) return false;
|
|
||||||
for (const auto& pair : *dict_value) {
|
|
||||||
auto it = other.dict_value->find(pair.first);
|
|
||||||
if (it == other.dict_value->end() || !pair.second.equals(it->second)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String representation
|
|
||||||
std::string toString() const {
|
|
||||||
switch (type) {
|
|
||||||
case ValueType::VAL_NONE: return "none";
|
|
||||||
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
|
|
||||||
case ValueType::VAL_NUMBER: {
|
|
||||||
// Format numbers like the original stringify function
|
|
||||||
if (number == std::floor(number)) {
|
|
||||||
return std::to_string(static_cast<long long>(number));
|
|
||||||
} else {
|
|
||||||
std::string str = std::to_string(number);
|
|
||||||
// Remove trailing zeros
|
|
||||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
|
||||||
if (str.back() == '.') str.pop_back();
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ValueType::VAL_STRING: return string_value;
|
|
||||||
case ValueType::VAL_FUNCTION: return "<function>";
|
|
||||||
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
|
|
||||||
case ValueType::VAL_THUNK: return "<thunk>";
|
|
||||||
case ValueType::VAL_ARRAY: {
|
|
||||||
const std::vector<Value>& arr = *array_value;
|
|
||||||
std::string result = "[";
|
|
||||||
|
|
||||||
for (size_t i = 0; i < arr.size(); i++) {
|
|
||||||
if (i > 0) result += ", ";
|
|
||||||
result += arr[i].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "]";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
case ValueType::VAL_DICT: {
|
|
||||||
const std::unordered_map<std::string, Value>& dict = *dict_value;
|
|
||||||
std::string result = "{";
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
for (const auto& pair : dict) {
|
|
||||||
if (!first) result += ", ";
|
|
||||||
result += "\"" + pair.first + "\": " + pair.second.toString();
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "}";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
case ValueType::VAL_MODULE: {
|
|
||||||
// Avoid accessing Module fields when it's still an incomplete type in some TUs.
|
|
||||||
// Delegate formatting to a small helper defined out-of-line in Value.cpp.
|
|
||||||
extern std::string formatModuleForToString(const std::shared_ptr<Module>&);
|
|
||||||
return formatModuleForToString(module_value);
|
|
||||||
}
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equality operator
|
|
||||||
bool operator==(const Value& other) const {
|
|
||||||
return equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const Value& other) const {
|
|
||||||
return !equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arithmetic operators
|
|
||||||
Value operator+(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(number + other.number);
|
|
||||||
}
|
|
||||||
if (isString() && other.isString()) {
|
|
||||||
return Value(string_value + other.string_value);
|
|
||||||
}
|
|
||||||
if (isString() && other.isNumber()) {
|
|
||||||
return Value(string_value + other.toString());
|
|
||||||
}
|
|
||||||
if (isNumber() && other.isString()) {
|
|
||||||
return Value(toString() + other.string_value);
|
|
||||||
}
|
|
||||||
// Handle none values by converting to string
|
|
||||||
if (isString() && other.isNone()) {
|
|
||||||
return Value(string_value + "none");
|
|
||||||
}
|
|
||||||
if (isNone() && other.isString()) {
|
|
||||||
return Value("none" + other.string_value);
|
|
||||||
}
|
|
||||||
if (isString() && !other.isString() && !other.isNumber()) {
|
|
||||||
return Value(string_value + other.toString());
|
|
||||||
}
|
|
||||||
if (!isString() && !isNumber() && other.isString()) {
|
|
||||||
return Value(toString() + other.string_value);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("+", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator-(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(number - other.number);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("-", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator*(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(number * other.number);
|
|
||||||
}
|
|
||||||
if (isString() && other.isNumber()) {
|
|
||||||
std::string result;
|
|
||||||
for (int i = 0; i < static_cast<int>(other.number); ++i) {
|
|
||||||
result += string_value;
|
|
||||||
}
|
|
||||||
return Value(result);
|
|
||||||
}
|
|
||||||
if (isNumber() && other.isString()) {
|
|
||||||
std::string result;
|
|
||||||
for (int i = 0; i < static_cast<int>(number); ++i) {
|
|
||||||
result += other.string_value;
|
|
||||||
}
|
|
||||||
return Value(result);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("*", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator/(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
if (other.number == 0) {
|
|
||||||
throw std::runtime_error("Division by zero");
|
|
||||||
}
|
|
||||||
return Value(number / other.number);
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("/", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator%(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(fmod(number, other.number));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("%", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator&(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("&", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator|(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("|", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator^(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("^", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator<<(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError("<<", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operator>>(const Value& other) const {
|
|
||||||
if (isNumber() && other.isNumber()) {
|
|
||||||
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
|
|
||||||
}
|
|
||||||
throw std::runtime_error(ErrorUtils::makeOperatorError(">>", getType(), other.getType()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define Module after Value so it can hold Value in exports without incomplete type issues
|
|
||||||
struct Module {
|
|
||||||
std::string name;
|
|
||||||
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
|
|
||||||
Module() = default;
|
|
||||||
Module(const std::string& n, const std::unordered_map<std::string, Value>& dict)
|
|
||||||
: name(n), exports(std::make_shared<std::unordered_map<std::string, Value>>(dict)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Global constants for common values
|
|
||||||
extern const Value NONE_VALUE;
|
|
||||||
extern const Value TRUE_VALUE;
|
|
||||||
extern const Value FALSE_VALUE;
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
#include "base64_module.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
static const char* B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
|
|
||||||
static std::string b64encode(const std::string& in){
|
|
||||||
std::string out; out.reserve(((in.size()+2)/3)*4);
|
|
||||||
int val=0, valb=-6;
|
|
||||||
for (unsigned char c : in){
|
|
||||||
val = (val<<8) + c;
|
|
||||||
valb += 8;
|
|
||||||
while (valb >= 0){ out.push_back(B64[(val>>valb)&0x3F]); valb -= 6; }
|
|
||||||
}
|
|
||||||
if (valb>-6) out.push_back(B64[((val<<8)>>(valb+8))&0x3F]);
|
|
||||||
while (out.size()%4) out.push_back('=');
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string b64decode(const std::string& in){
|
|
||||||
std::vector<int> T(256,-1); for (int i=0;i<64;i++) T[(unsigned char)B64[i]]=i;
|
|
||||||
std::string out; out.reserve((in.size()*3)/4);
|
|
||||||
int val=0, valb=-8;
|
|
||||||
for (unsigned char c : in){ if (T[c]==-1) break; val=(val<<6)+T[c]; valb+=6; if (valb>=0){ out.push_back(char((val>>valb)&0xFF)); valb-=8; } }
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerBase64Module(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("base64", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
m.fn("encode", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
return Value(b64encode(a[0].asString()));
|
|
||||||
});
|
|
||||||
m.fn("decode", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
return Value(b64decode(a[0].asString()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
#include "eval.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "ErrorReporter.h"
|
|
||||||
#include "Lexer.h"
|
|
||||||
#include "Parser.h"
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
void registerEvalModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("eval", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
ErrorReporter* er = m.interpreterRef.getErrorReporter();
|
|
||||||
m.fn("eval", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1 || !args[0].isString()) {
|
|
||||||
if (er) er->reportError(line, column, "Invalid Arguments", "eval expects exactly 1 string argument", "eval");
|
|
||||||
throw std::runtime_error("eval expects exactly 1 string argument");
|
|
||||||
}
|
|
||||||
std::string code = args[0].asString();
|
|
||||||
std::string evalName = "<eval>";
|
|
||||||
try {
|
|
||||||
if (er) er->pushSource(code, evalName);
|
|
||||||
Lexer lx; if (er) lx.setErrorReporter(er);
|
|
||||||
auto toks = lx.Tokenize(code);
|
|
||||||
Parser p(toks); if (er) p.setErrorReporter(er);
|
|
||||||
auto stmts = p.parse();
|
|
||||||
I.interpret(stmts);
|
|
||||||
return NONE_VALUE;
|
|
||||||
} catch (...) {
|
|
||||||
if (er) er->popSource();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
if (er) er->popSource();
|
|
||||||
});
|
|
||||||
|
|
||||||
m.fn("evalFile", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
|
|
||||||
if (args.size() != 1 || !args[0].isString()) {
|
|
||||||
if (er) er->reportError(line, column, "Invalid Arguments", "evalFile expects exactly 1 string argument (path)", "evalFile");
|
|
||||||
throw std::runtime_error("evalFile expects exactly 1 string argument (path)");
|
|
||||||
}
|
|
||||||
std::string filename = args[0].asString();
|
|
||||||
std::ifstream f(filename);
|
|
||||||
if (!f.is_open()) {
|
|
||||||
if (er) er->reportError(line, column, "StdLib Error", "Could not open file: " + filename, "");
|
|
||||||
throw std::runtime_error("Could not open file: " + filename);
|
|
||||||
}
|
|
||||||
std::stringstream buf; buf << f.rdbuf(); f.close();
|
|
||||||
std::string code = buf.str();
|
|
||||||
try {
|
|
||||||
if (er) er->pushSource(code, filename);
|
|
||||||
Lexer lx; if (er) lx.setErrorReporter(er);
|
|
||||||
auto toks = lx.Tokenize(code);
|
|
||||||
Parser p(toks); if (er) p.setErrorReporter(er);
|
|
||||||
auto stmts = p.parse();
|
|
||||||
I.interpret(stmts);
|
|
||||||
return NONE_VALUE;
|
|
||||||
} catch (...) {
|
|
||||||
if (er) er->popSource();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
if (er) er->popSource();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include "io.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "ErrorReporter.h"
|
|
||||||
|
|
||||||
void registerIoModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("io", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
ErrorReporter* er = m.interpreterRef.getErrorReporter();
|
|
||||||
|
|
||||||
m.fn("readFile", [er](std::vector<Value> a, int line, int col) -> Value {
|
|
||||||
if (a.empty() || !a[0].isString() || a.size() > 2 || (a.size() == 2 && !a[1].isString())) {
|
|
||||||
if (er) er->reportError(line, col, "Invalid Arguments", "readFile(path[, mode]) expects 1-2 args (strings)", "readFile");
|
|
||||||
throw std::runtime_error("readFile(path[, mode]) expects 1-2 string args");
|
|
||||||
}
|
|
||||||
std::string mode = (a.size() == 2) ? a[1].asString() : std::string("r");
|
|
||||||
std::ios_base::openmode om = std::ios::in;
|
|
||||||
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
|
|
||||||
std::ifstream f(a[0].asString(), om);
|
|
||||||
if (!f.is_open()) {
|
|
||||||
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
|
|
||||||
throw std::runtime_error("Could not open file");
|
|
||||||
}
|
|
||||||
std::stringstream buf; buf << f.rdbuf(); f.close();
|
|
||||||
return Value(buf.str());
|
|
||||||
});
|
|
||||||
|
|
||||||
m.fn("writeFile", [er](std::vector<Value> a, int line, int col) -> Value {
|
|
||||||
if (a.size() < 2 || a.size() > 3 || !a[0].isString() || !a[1].isString() || (a.size() == 3 && !a[2].isString())) {
|
|
||||||
if (er) er->reportError(line, col, "Invalid Arguments", "writeFile(path, data[, mode]) expects 2-3 args (strings)", "writeFile");
|
|
||||||
throw std::runtime_error("writeFile(path, data[, mode]) expects 2-3 string args");
|
|
||||||
}
|
|
||||||
std::string mode = (a.size() == 3) ? a[2].asString() : std::string("w");
|
|
||||||
std::ios_base::openmode om = std::ios::out;
|
|
||||||
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
|
|
||||||
if (mode.find('a') != std::string::npos) om |= std::ios::app; else om |= std::ios::trunc;
|
|
||||||
std::ofstream f(a[0].asString(), om);
|
|
||||||
if (!f.is_open()) {
|
|
||||||
if (er) er->reportError(line, col, "StdLib Error", "Could not create file", a[0].asString());
|
|
||||||
throw std::runtime_error("Could not create file");
|
|
||||||
}
|
|
||||||
f << a[1].asString(); f.close();
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
|
|
||||||
m.fn("readLines", [er](std::vector<Value> a, int line, int col) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) {
|
|
||||||
if (er) er->reportError(line, col, "Invalid Arguments", "readLines(path) expects 1 string arg", "readLines");
|
|
||||||
throw std::runtime_error("readLines(path) expects 1 string arg");
|
|
||||||
}
|
|
||||||
std::ifstream f(a[0].asString());
|
|
||||||
if (!f.is_open()) {
|
|
||||||
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
|
|
||||||
throw std::runtime_error("Could not open file");
|
|
||||||
}
|
|
||||||
std::vector<Value> lines; std::string s;
|
|
||||||
while (std::getline(f, s)) lines.emplace_back(s);
|
|
||||||
f.close();
|
|
||||||
return Value(lines);
|
|
||||||
});
|
|
||||||
|
|
||||||
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
std::ifstream f(a[0].asString()); bool ok = f.good(); f.close();
|
|
||||||
return Value(ok);
|
|
||||||
});
|
|
||||||
|
|
||||||
// input remains a global in stdlib; not provided here
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
#include "json.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <string>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
// Minimal JSON parser/stringifier (numbers, strings, booleans, null, arrays, objects)
|
|
||||||
namespace {
|
|
||||||
struct Cursor { const std::string* s; size_t i = 0; };
|
|
||||||
void skipWs(Cursor& c){ while (c.i < c.s->size() && std::isspace(static_cast<unsigned char>((*c.s)[c.i]))) ++c.i; }
|
|
||||||
bool match(Cursor& c, char ch){ skipWs(c); if (c.i < c.s->size() && (*c.s)[c.i]==ch){ ++c.i; return true;} return false; }
|
|
||||||
std::string parseString(Cursor& c){
|
|
||||||
if (!match(c,'"')) return {};
|
|
||||||
std::string out; while (c.i < c.s->size()){
|
|
||||||
char ch = (*c.s)[c.i++];
|
|
||||||
if (ch=='"') break;
|
|
||||||
if (ch=='\\' && c.i < c.s->size()){
|
|
||||||
char e = (*c.s)[c.i++];
|
|
||||||
switch(e){ case '"': out+='"'; break; case '\\': out+='\\'; break; case '/': out+='/'; break; case 'b': out+='\b'; break; case 'f': out+='\f'; break; case 'n': out+='\n'; break; case 'r': out+='\r'; break; case 't': out+='\t'; break; default: out+=e; }
|
|
||||||
} else out+=ch;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
double parseNumber(Cursor& c){ skipWs(c); size_t start=c.i; while (c.i<c.s->size() && (std::isdigit((*c.s)[c.i])||(*c.s)[c.i]=='-'||(*c.s)[c.i]=='+'||(*c.s)[c.i]=='.'||(*c.s)[c.i]=='e'||(*c.s)[c.i]=='E')) ++c.i; return std::stod(c.s->substr(start,c.i-start)); }
|
|
||||||
Value parseValue(Cursor& c);
|
|
||||||
Value parseArray(Cursor& c){
|
|
||||||
match(c,'['); std::vector<Value> arr; skipWs(c); if (match(c,']')) return Value(arr);
|
|
||||||
while (true){ arr.push_back(parseValue(c)); skipWs(c); if (match(c,']')) break; match(c,','); }
|
|
||||||
return Value(arr);
|
|
||||||
}
|
|
||||||
Value parseObject(Cursor& c){
|
|
||||||
match(c,'{'); std::unordered_map<std::string,Value> obj; skipWs(c); if (match(c,'}')) return Value(obj);
|
|
||||||
while (true){ std::string k = parseString(c); match(c,':'); Value v = parseValue(c); obj.emplace(k, v); skipWs(c); if (match(c,'}')) break; match(c,','); }
|
|
||||||
return Value(obj);
|
|
||||||
}
|
|
||||||
Value parseValue(Cursor& c){ skipWs(c); if (c.i>=c.s->size()) return NONE_VALUE; char ch=(*c.s)[c.i];
|
|
||||||
if (ch=='"') return Value(parseString(c));
|
|
||||||
if (ch=='[') return parseArray(c);
|
|
||||||
if (ch=='{') return parseObject(c);
|
|
||||||
if (!c.s->compare(c.i,4,"true")) { c.i+=4; return Value(true);}
|
|
||||||
if (!c.s->compare(c.i,5,"false")) { c.i+=5; return Value(false);}
|
|
||||||
if (!c.s->compare(c.i,4,"null")) { c.i+=4; return NONE_VALUE;}
|
|
||||||
return Value(parseNumber(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string escapeString(const std::string& s){
|
|
||||||
std::string out; out.reserve(s.size()+2); out.push_back('"');
|
|
||||||
for(char ch: s){
|
|
||||||
switch(ch){ case '"': out+="\\\""; break; case '\\': out+="\\\\"; break; case '\n': out+="\\n"; break; case '\r': out+="\\r"; break; case '\t': out+="\\t"; break; default: out+=ch; }
|
|
||||||
}
|
|
||||||
out.push_back('"'); return out;
|
|
||||||
}
|
|
||||||
std::string stringifyValue(const Value& v){
|
|
||||||
switch(v.type){
|
|
||||||
case VAL_NONE: return "null";
|
|
||||||
case VAL_BOOLEAN: return v.asBoolean()?"true":"false";
|
|
||||||
case VAL_NUMBER: return v.toString();
|
|
||||||
case VAL_STRING: return escapeString(v.asString());
|
|
||||||
case VAL_ARRAY: {
|
|
||||||
const auto& a=v.asArray(); std::string out="["; for(size_t i=0;i<a.size();++i){ if(i) out+=","; out+=stringifyValue(a[i]); } out+="]"; return out;
|
|
||||||
}
|
|
||||||
case VAL_DICT: {
|
|
||||||
const auto& d=v.asDict(); std::string out="{"; bool first=true; for(const auto& kv:d){ if(!first) out+=","; first=false; out+=escapeString(kv.first); out+=":"; out+=stringifyValue(kv.second);} out+="}"; return out;
|
|
||||||
}
|
|
||||||
default: return "null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerJsonModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("json", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
m.fn("parse", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
Cursor c{&a[0].asString(), 0};
|
|
||||||
return parseValue(c);
|
|
||||||
});
|
|
||||||
m.fn("stringify", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1) return Value(std::string("null"));
|
|
||||||
return Value(stringifyValue(a[0]));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
#include "math_module.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
static Value unary_math(std::vector<Value> a, double(*fn)(double)){
|
|
||||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
|
||||||
return Value(fn(a[0].asNumber()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerMathModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("math", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
m.fn("sin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sin); });
|
|
||||||
m.fn("cos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cos); });
|
|
||||||
m.fn("tan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tan); });
|
|
||||||
m.fn("asin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::asin); });
|
|
||||||
m.fn("acos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::acos); });
|
|
||||||
m.fn("atan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::atan); });
|
|
||||||
m.fn("sinh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sinh); });
|
|
||||||
m.fn("cosh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cosh); });
|
|
||||||
m.fn("tanh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tanh); });
|
|
||||||
m.fn("exp", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::exp); });
|
|
||||||
m.fn("log", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log); });
|
|
||||||
m.fn("log10", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log10); });
|
|
||||||
m.fn("sqrt", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sqrt); });
|
|
||||||
m.fn("ceil", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::ceil); });
|
|
||||||
m.fn("floor", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::floor); });
|
|
||||||
m.fn("round", [](std::vector<Value> a, int, int)->Value{
|
|
||||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
|
||||||
return Value(std::round(a[0].asNumber()));
|
|
||||||
});
|
|
||||||
m.fn("abs", [](std::vector<Value> a, int, int)->Value{
|
|
||||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
|
||||||
return Value(std::fabs(a[0].asNumber()));
|
|
||||||
});
|
|
||||||
m.fn("pow", [](std::vector<Value> a, int, int)->Value{
|
|
||||||
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
|
|
||||||
return Value(std::pow(a[0].asNumber(), a[1].asNumber()));
|
|
||||||
});
|
|
||||||
m.fn("min", [](std::vector<Value> a, int, int)->Value{
|
|
||||||
if (a.empty()) return NONE_VALUE;
|
|
||||||
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
|
|
||||||
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::min(mval, a[i].asNumber()); }
|
|
||||||
return Value(mval);
|
|
||||||
});
|
|
||||||
m.fn("max", [](std::vector<Value> a, int, int)->Value{
|
|
||||||
if (a.empty()) return NONE_VALUE;
|
|
||||||
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
|
|
||||||
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::max(mval, a[i].asNumber()); }
|
|
||||||
return Value(mval);
|
|
||||||
});
|
|
||||||
m.val("pi", Value(3.14159265358979323846));
|
|
||||||
m.val("e", Value(2.71828182845904523536));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
#include "os.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include "Lexer.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <filesystem>
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#include <windows.h>
|
|
||||||
#include <direct.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <io.h>
|
|
||||||
#ifndef PATH_MAX
|
|
||||||
#define PATH_MAX MAX_PATH
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
void registerOsModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("os", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
// Process
|
|
||||||
m.fn("getcwd", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
char buf[PATH_MAX];
|
|
||||||
#if defined(_WIN32)
|
|
||||||
if (_getcwd(buf, sizeof(buf))) return Value(std::string(buf));
|
|
||||||
#else
|
|
||||||
if (getcwd(buf, sizeof(buf))) return Value(std::string(buf));
|
|
||||||
#endif
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
m.fn("chdir", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
#if defined(_WIN32)
|
|
||||||
int rc = ::_chdir(a[0].asString().c_str());
|
|
||||||
#else
|
|
||||||
int rc = ::chdir(a[0].asString().c_str());
|
|
||||||
#endif
|
|
||||||
return Value(rc == 0);
|
|
||||||
});
|
|
||||||
m.fn("getpid", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return Value(static_cast<double>(GetCurrentProcessId()));
|
|
||||||
#else
|
|
||||||
return Value(static_cast<double>(getpid()));
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
m.fn("getppid", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return NONE_VALUE; // not directly available; could use Toolhelp32Snapshot if needed
|
|
||||||
#else
|
|
||||||
return Value(static_cast<double>(getppid()));
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
m.fn("name", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return Value(std::string("nt"));
|
|
||||||
#else
|
|
||||||
return Value(std::string("posix"));
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filesystem
|
|
||||||
m.fn("listdir", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
std::string path = ".";
|
|
||||||
if (!a.empty() && a[0].isString()) path = a[0].asString();
|
|
||||||
std::vector<Value> out;
|
|
||||||
try {
|
|
||||||
for (const auto& entry : fs::directory_iterator(path)) {
|
|
||||||
out.push_back(Value(entry.path().filename().string()));
|
|
||||||
}
|
|
||||||
} catch (...) {}
|
|
||||||
return Value(out);
|
|
||||||
});
|
|
||||||
m.fn("mkdir", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
try { return Value(fs::create_directory(a[0].asString())); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
m.fn("rmdir", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
m.fn("remove", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
try { return Value(fs::exists(a[0].asString())); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
m.fn("isfile", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
try { return Value(fs::is_regular_file(a[0].asString())); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
m.fn("isdir", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
|
||||||
try { return Value(fs::is_directory(a[0].asString())); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
m.fn("rename", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 2 || !a[0].isString() || !a[1].isString()) return Value(false);
|
|
||||||
try { fs::rename(a[0].asString(), a[1].asString()); return Value(true); } catch (...) { return Value(false); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Separators
|
|
||||||
m.fn("sep", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return Value(std::string("\\"));
|
|
||||||
#else
|
|
||||||
return Value(std::string("/"));
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
m.fn("pathsep", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return Value(std::string(";"));
|
|
||||||
#else
|
|
||||||
return Value(std::string(":"));
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
m.fn("linesep", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return Value(std::string("\r\n"));
|
|
||||||
#else
|
|
||||||
return Value(std::string("\n"));
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
#include "path_module.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <filesystem>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
static std::string join_impl(const std::vector<Value>& parts){
|
|
||||||
if (parts.empty()) return std::string();
|
|
||||||
fs::path p;
|
|
||||||
for (const auto& v : parts) if (v.isString()) p /= v.asString();
|
|
||||||
return p.generic_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isabs_impl(const std::string& s) {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
if (s.size() >= 2 && (s[0] == '/' || s[0] == '\\')) return true; // root-relative on current drive
|
|
||||||
if (s.rfind("\\\\", 0) == 0) return true; // UNC path
|
|
||||||
if (s.size() >= 3 && std::isalpha(static_cast<unsigned char>(s[0])) && s[1] == ':' && (s[2] == '/' || s[2] == '\\')) return true; // C:\ or C:/
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return !s.empty() && s[0] == '/';
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerPathModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("path", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
m.fn("join", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
return Value(join_impl(a));
|
|
||||||
});
|
|
||||||
m.fn("dirname", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
return Value(fs::path(a[0].asString()).parent_path().generic_string());
|
|
||||||
});
|
|
||||||
m.fn("basename", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
return Value(fs::path(a[0].asString()).filename().generic_string());
|
|
||||||
});
|
|
||||||
m.fn("splitext", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
fs::path p(a[0].asString());
|
|
||||||
std::string ext = p.has_extension() ? p.extension().generic_string() : std::string("");
|
|
||||||
fs::path basePath = p.has_extension() ? (p.parent_path() / p.stem()) : p;
|
|
||||||
return Value(std::vector<Value>{ Value(basePath.generic_string()), Value(ext) });
|
|
||||||
});
|
|
||||||
m.fn("normalize", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
|
||||||
return Value(fs::path(a[0].asString()).lexically_normal().generic_string());
|
|
||||||
});
|
|
||||||
m.fn("isabs", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()!=1 || !a[0].isString()) return Value(false);
|
|
||||||
return Value(isabs_impl(a[0].asString()));
|
|
||||||
});
|
|
||||||
m.fn("relpath", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size()<1 || a.size()>2 || !a[0].isString() || (a.size()==2 && !a[1].isString())) return NONE_VALUE;
|
|
||||||
fs::path target(a[0].asString());
|
|
||||||
fs::path base = (a.size()==2)? fs::path(a[1].asString()) : fs::current_path();
|
|
||||||
return Value(fs::relative(target, base).generic_string());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
#include "rand.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
void registerRandModule(Interpreter& interpreter) {
|
|
||||||
interpreter.registerModule("rand", [](Interpreter::ModuleBuilder& m) {
|
|
||||||
static std::mt19937_64 rng{std::random_device{}()};
|
|
||||||
m.fn("seed", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() == 1 && a[0].isNumber()) {
|
|
||||||
rng.seed(static_cast<uint64_t>(a[0].asNumber()));
|
|
||||||
}
|
|
||||||
return NONE_VALUE;
|
|
||||||
});
|
|
||||||
m.fn("random", [](std::vector<Value>, int, int) -> Value {
|
|
||||||
std::uniform_real_distribution<double> dist(0.0, 1.0);
|
|
||||||
return Value(dist(rng));
|
|
||||||
});
|
|
||||||
m.fn("randint", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
|
|
||||||
long long lo = static_cast<long long>(a[0].asNumber());
|
|
||||||
long long hi = static_cast<long long>(a[1].asNumber());
|
|
||||||
if (hi < lo) std::swap(lo, hi);
|
|
||||||
std::uniform_int_distribution<long long> dist(lo, hi);
|
|
||||||
return Value(static_cast<double>(dist(rng)));
|
|
||||||
});
|
|
||||||
m.fn("choice", [](std::vector<Value> a, int, int) -> Value {
|
|
||||||
if (a.size() != 1 || !a[0].isArray() || a[0].asArray().empty()) return NONE_VALUE;
|
|
||||||
const auto& arr = a[0].asArray();
|
|
||||||
std::uniform_int_distribution<size_t> dist(0, arr.size() - 1);
|
|
||||||
return arr[dist(rng)];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
#include "register.h"
|
|
||||||
#include "sys.h"
|
|
||||||
#include "os.h"
|
|
||||||
#include "eval.h"
|
|
||||||
#include "io.h"
|
|
||||||
#include "time_module.h"
|
|
||||||
#include "rand.h"
|
|
||||||
#include "math_module.h"
|
|
||||||
#include "path_module.h"
|
|
||||||
#include "base64_module.h"
|
|
||||||
|
|
||||||
void registerAllBuiltinModules(Interpreter& interpreter) {
|
|
||||||
registerSysModule(interpreter);
|
|
||||||
registerOsModule(interpreter);
|
|
||||||
registerEvalModule(interpreter);
|
|
||||||
registerIoModule(interpreter);
|
|
||||||
registerTimeModule(interpreter);
|
|
||||||
registerRandModule(interpreter);
|
|
||||||
registerMathModule(interpreter);
|
|
||||||
registerPathModule(interpreter);
|
|
||||||
registerBase64Module(interpreter);
|
|
||||||
// registerJsonModule(interpreter); // deferred pending extensive testing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user