Compare commits
No commits in common. "adb00d496f4b8b4bab16a8d27dcd7b2c139f1b31" and "b38f6bff2536e17cabd80ce33fb282481ba01d90" have entirely different histories.
adb00d496f
...
b38f6bff25
8
.idea/inspectionProfiles/Project_Default.xml
generated
8
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,8 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ClangTidy" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="clangTidyChecks" value="-*,bugprone-argument-comment,bugprone-assert-side-effect,bugprone-bad-signal-to-kill-thread,bugprone-branch-clone,bugprone-copy-constructor-init,bugprone-dangling-handle,bugprone-dynamic-static-initializers,bugprone-fold-init-type,bugprone-forward-declaration-namespace,bugprone-forwarding-reference-overload,bugprone-inaccurate-erase,bugprone-incorrect-roundings,bugprone-integer-division,bugprone-lambda-function-name,bugprone-macro-parentheses,bugprone-macro-repeated-side-effects,bugprone-misplaced-operator-in-strlen-in-alloc,bugprone-misplaced-pointer-arithmetic-in-alloc,bugprone-misplaced-widening-cast,bugprone-move-forwarding-reference,bugprone-multiple-statement-macro,bugprone-no-escape,bugprone-not-null-terminated-result,bugprone-parent-virtual-call,bugprone-posix-return,bugprone-reserved-identifier,bugprone-sizeof-container,bugprone-sizeof-expression,bugprone-spuriously-wake-up-functions,bugprone-string-constructor,bugprone-string-integer-assignment,bugprone-string-literal-with-embedded-nul,bugprone-suspicious-enum-usage,bugprone-suspicious-include,bugprone-suspicious-memory-comparison,bugprone-suspicious-memset-usage,bugprone-suspicious-missing-comma,bugprone-suspicious-semicolon,bugprone-suspicious-string-compare,bugprone-swapped-arguments,bugprone-terminating-continue,bugprone-throw-keyword-missing,bugprone-too-small-loop-variable,bugprone-undefined-memory-manipulation,bugprone-undelegated-constructor,bugprone-unhandled-self-assignment,bugprone-unused-raii,bugprone-unused-return-value,bugprone-use-after-move,bugprone-virtual-near-miss,cert-dcl21-cpp,cert-dcl58-cpp,cert-err34-c,cert-err52-cpp,cert-err60-cpp,cert-flp30-c,cert-msc50-cpp,cert-msc51-cpp,cert-str34-c,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,misc-misplaced-const,misc-new-delete-overloads,misc-non-copyable-objects,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,misc-uniqueptr-reset-release,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-headers,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,readability-avoid-const-params-in-decls,readability-const-return-type,readability-container-size-empty,readability-convert-member-functions-to-static,readability-delete-null-pointer,readability-deleted-default,readability-inconsistent-declaration-parameter-name,readability-make-member-function-const,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-redundant-control-flow,readability-redundant-declaration,readability-redundant-function-ptr-dereference,readability-redundant-smartptr-get,readability-redundant-string-cstr,readability-redundant-string-init,readability-simplify-subscript-expr,readability-static-accessed-through-instance,readability-static-definition-in-anonymous-namespace,readability-string-compare,readability-uniqueptr-delete-release" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
@ -1,361 +0,0 @@
|
||||
# Bob Language Reference
|
||||
|
||||
## Overview
|
||||
|
||||
Bob is a dynamically typed programming language with a focus on simplicity and expressiveness. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Data Types](#data-types)
|
||||
2. [Variables](#variables)
|
||||
3. [Operators](#operators)
|
||||
4. [Functions](#functions)
|
||||
5. [Control Flow](#control-flow)
|
||||
6. [Standard Library](#standard-library)
|
||||
7. [Error Handling](#error-handling)
|
||||
8. [Examples](#examples)
|
||||
9. [Language Nuances](#language-nuances)
|
||||
|
||||
## Data Types
|
||||
|
||||
### Numbers
|
||||
- **Integers**: `42`, `-10`, `0`
|
||||
- **Floats**: `3.14`, `2.718`, `-1.5`
|
||||
- **Automatic conversion**: Numbers are stored as doubles internally
|
||||
|
||||
### Strings
|
||||
- **Literal strings**: `"Hello, World!"`
|
||||
- **Empty strings**: `""`
|
||||
- **Escape sequences**: Not currently supported
|
||||
|
||||
### Booleans
|
||||
- **True**: `true`
|
||||
- **False**: `false`
|
||||
|
||||
### None
|
||||
- **Null value**: `none` (represents absence of value)
|
||||
|
||||
## Variables
|
||||
|
||||
### Declaration
|
||||
```bob
|
||||
var name = "Bob";
|
||||
var age = 25;
|
||||
var isActive = true;
|
||||
```
|
||||
|
||||
### Assignment
|
||||
```bob
|
||||
var x = 10;
|
||||
x = 20; // Reassignment
|
||||
```
|
||||
|
||||
### Scoping
|
||||
- **Global scope**: Variables declared at top level
|
||||
- **Local scope**: Variables declared inside functions
|
||||
- **Shadowing**: Local variables can shadow global variables
|
||||
- **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration
|
||||
|
||||
### Variable Behavior
|
||||
```bob
|
||||
var globalVar = 100;
|
||||
|
||||
func testScope() {
|
||||
var localVar = 50; // Local variable
|
||||
return localVar + globalVar; // Can access global
|
||||
}
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
### Arithmetic Operators
|
||||
- **Addition**: `+`
|
||||
- **Subtraction**: `-`
|
||||
- **Multiplication**: `*`
|
||||
- **Division**: `/`
|
||||
- **Modulo**: `%`
|
||||
|
||||
### Comparison Operators
|
||||
- **Equal**: `==`
|
||||
- **Not equal**: `!=`
|
||||
- **Greater than**: `>`
|
||||
- **Less than**: `<`
|
||||
- **Greater than or equal**: `>=`
|
||||
- **Less than or equal**: `<=`
|
||||
|
||||
### String Operators
|
||||
|
||||
#### Concatenation
|
||||
Bob supports bidirectional string + number concatenation with automatic type conversion:
|
||||
|
||||
```bob
|
||||
// String + Number
|
||||
"Hello " + 42; // → "Hello 42"
|
||||
"Pi: " + 3.14; // → "Pi: 3.14"
|
||||
|
||||
// Number + String
|
||||
42 + " items"; // → "42 items"
|
||||
3.14 + " is pi"; // → "3.14 is pi"
|
||||
```
|
||||
|
||||
#### String Multiplication
|
||||
```bob
|
||||
"hello" * 3; // → "hellohellohello"
|
||||
3 * "hello"; // → "hellohellohello"
|
||||
```
|
||||
|
||||
**Note**: String multiplication requires whole numbers.
|
||||
|
||||
### Number Formatting
|
||||
Bob automatically formats numbers to show only significant digits:
|
||||
|
||||
```bob
|
||||
"Count: " + 2.0; // → "Count: 2" (no trailing zeros)
|
||||
"Pi: " + 3.14; // → "Pi: 3.14" (exact precision)
|
||||
"Integer: " + 42; // → "Integer: 42" (no decimal)
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### Function Declaration
|
||||
```bob
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
### Function Call
|
||||
```bob
|
||||
var result = add(2, 3); // result = 5
|
||||
```
|
||||
|
||||
### Parameters
|
||||
- **Any number of parameters** supported
|
||||
- **No default parameters** (not implemented)
|
||||
- **No keyword arguments** (not implemented)
|
||||
|
||||
### Return Values
|
||||
- **Explicit return**: `return value;`
|
||||
- **Implicit return**: Functions return `none` if no return statement
|
||||
- **Early return**: Functions can return from anywhere
|
||||
|
||||
### Closures
|
||||
Bob supports lexical closures with variable capture:
|
||||
|
||||
```bob
|
||||
var outerVar = "Outer";
|
||||
|
||||
func makeGreeter(greeting) {
|
||||
return greeting + " " + outerVar;
|
||||
}
|
||||
|
||||
var greeter = makeGreeter("Hello");
|
||||
// greeter captures outerVar from its lexical scope
|
||||
```
|
||||
|
||||
### Nested Functions
|
||||
```bob
|
||||
func outer() {
|
||||
func inner() {
|
||||
return 42;
|
||||
}
|
||||
return inner();
|
||||
}
|
||||
```
|
||||
|
||||
## Control Flow
|
||||
|
||||
### Current Status
|
||||
**Control flow statements are NOT implemented yet:**
|
||||
- `if` statements
|
||||
- `while` loops
|
||||
- `for` loops
|
||||
- `else` clauses
|
||||
|
||||
### Planned Features
|
||||
- Conditional execution
|
||||
- Looping constructs
|
||||
- Logical operators (`and`, `or`, `not`)
|
||||
|
||||
## Standard Library
|
||||
|
||||
### Print Function
|
||||
```bob
|
||||
print("Hello, World!");
|
||||
print(42);
|
||||
print(true);
|
||||
```
|
||||
|
||||
**Usage**: `print(expression)`
|
||||
- Prints the result of any expression
|
||||
- Automatically converts values to strings
|
||||
- Adds newline after output
|
||||
|
||||
### Assert Function
|
||||
```bob
|
||||
assert(condition, "optional message");
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
- `assert(true)` - passes silently
|
||||
- `assert(false)` - throws error and stops execution
|
||||
- `assert(condition, "message")` - includes custom error message
|
||||
|
||||
**Behavior**:
|
||||
- Terminates program execution on failure
|
||||
- No exception handling mechanism (yet)
|
||||
- Useful for testing and validation
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Current Error Types
|
||||
- **Division by zero**: `DivisionByZeroError`
|
||||
- **Type errors**: `Operands must be of same type`
|
||||
- **String multiplication**: `String multiplier must be whole number`
|
||||
- **Assertion failures**: `Assertion failed: condition is false`
|
||||
|
||||
### Error Behavior
|
||||
- **No try-catch**: Exception handling not implemented
|
||||
- **Program termination**: Errors stop execution immediately
|
||||
- **Error messages**: Descriptive error messages printed to console
|
||||
|
||||
### Common Error Scenarios
|
||||
```bob
|
||||
// Division by zero
|
||||
10 / 0; // Error: DivisionByZeroError
|
||||
|
||||
// Type mismatch
|
||||
"hello" - "world"; // Error: Cannot use '-' on two strings
|
||||
|
||||
// Invalid string multiplication
|
||||
"hello" * 3.5; // Error: String multiplier must be whole number
|
||||
|
||||
// Undefined variable
|
||||
undefinedVar; // Error: Undefined variable
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Calculator
|
||||
```bob
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
func multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
var result = add(5, multiply(3, 4));
|
||||
print("Result: " + result); // Result: 17
|
||||
```
|
||||
|
||||
### String Processing
|
||||
```bob
|
||||
func greet(name) {
|
||||
return "Hello, " + name + "!";
|
||||
}
|
||||
|
||||
func repeat(str, count) {
|
||||
return str * count;
|
||||
}
|
||||
|
||||
var greeting = greet("Bob");
|
||||
var repeated = repeat("Ha", 3);
|
||||
print(greeting + " " + repeated); // Hello, Bob! HaHaHa
|
||||
```
|
||||
|
||||
### Variable Scoping Example
|
||||
```bob
|
||||
var globalCounter = 0;
|
||||
|
||||
func increment() {
|
||||
var localCounter = 1;
|
||||
globalCounter = globalCounter + localCounter;
|
||||
return globalCounter;
|
||||
}
|
||||
|
||||
print("Before: " + globalCounter); // Before: 0
|
||||
increment();
|
||||
print("After: " + globalCounter); // After: 1
|
||||
```
|
||||
|
||||
## Language Nuances
|
||||
|
||||
### Type System
|
||||
- **Dynamic typing**: Variables can hold any type
|
||||
- **Automatic conversion**: Numbers and strings convert automatically
|
||||
- **No type annotations**: Types are inferred at runtime
|
||||
|
||||
### Memory Management
|
||||
- **Reference counting**: Uses `std::shared_ptr` for automatic memory management
|
||||
- **No manual memory management**: No `delete` or `free` needed
|
||||
- **Garbage collection**: Automatic cleanup of unused objects
|
||||
|
||||
### Performance Characteristics
|
||||
- **Interpreted**: Code is executed by an interpreter
|
||||
- **AST-based**: Abstract Syntax Tree for execution
|
||||
- **No compilation**: Direct interpretation of source code
|
||||
|
||||
### Syntax Rules
|
||||
- **Semicolons**: Required at end of statements
|
||||
- **Parentheses**: Required for function calls
|
||||
- **Curly braces**: Required for function bodies
|
||||
- **Case sensitive**: `var` and `Var` are different
|
||||
|
||||
### Comparison with Other Languages
|
||||
|
||||
#### vs Python
|
||||
- **Similar**: Dynamic typing, functions, closures
|
||||
- **Different**: No `global` keyword, automatic string conversion, different error handling
|
||||
|
||||
#### vs JavaScript
|
||||
- **Similar**: Automatic type conversion, string concatenation
|
||||
- **Different**: No `undefined`, different syntax, no `null`
|
||||
|
||||
#### vs Lua
|
||||
- **Similar**: Dynamic typing, functions
|
||||
- **Different**: No `local` keyword, different scoping rules
|
||||
|
||||
### Limitations
|
||||
- **No control flow**: No if/while/for statements
|
||||
- **No logical operators**: No `and`/`or`/`not`
|
||||
- **No exception handling**: No try-catch blocks
|
||||
- **No modules**: No import/export system
|
||||
- **No classes**: No object-oriented features
|
||||
- **No arrays/lists**: No built-in collection types
|
||||
- **No dictionaries**: No key-value data structures
|
||||
|
||||
### Future Features
|
||||
- Control flow statements
|
||||
- Logical operators
|
||||
- Exception handling
|
||||
- Collection types (arrays, dictionaries)
|
||||
- Modules and imports
|
||||
- Object-oriented programming
|
||||
- Standard library expansion
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Running Bob Code
|
||||
```bash
|
||||
# Compile the interpreter
|
||||
make
|
||||
|
||||
# Run a Bob file
|
||||
./build/bob your_file.bob
|
||||
|
||||
# Run the comprehensive test suite
|
||||
./build/bob test_bob_language.bob
|
||||
```
|
||||
|
||||
### File Extension
|
||||
- **`.bob`**: Standard file extension for Bob source code
|
||||
|
||||
### Interactive Mode
|
||||
- **Not implemented**: No REPL (Read-Eval-Print Loop) yet
|
||||
- **File-based**: All code must be in `.bob` files
|
||||
|
||||
---
|
||||
|
||||
*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.*
|
||||
4
Makefile
4
Makefile
@ -4,7 +4,7 @@
|
||||
CC = g++
|
||||
|
||||
# Compiler flags
|
||||
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
|
||||
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter
|
||||
|
||||
# Source directory
|
||||
SRC_DIR = ./source
|
||||
@ -22,7 +22,7 @@ OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
all: build run
|
||||
|
||||
# Rule to create necessary directories
|
||||
$(DIRS):
|
||||
|
||||
203
ROADMAP.md
203
ROADMAP.md
@ -1,203 +0,0 @@
|
||||
# Bob Language Development Roadmap
|
||||
|
||||
## Current Status
|
||||
- Basic expressions (arithmetic, comparison, logical)
|
||||
- Variables and assignment
|
||||
- Print statements (converted to standard library function)
|
||||
- Block statements
|
||||
- Environment/scoping
|
||||
- Function implementation (COMPLETED)
|
||||
- Return statements (COMPLETED)
|
||||
- Closures (COMPLETED)
|
||||
- Assert function (COMPLETED)
|
||||
- Standard library infrastructure (COMPLETED)
|
||||
- First-class functions and higher-order functions (COMPLETED)
|
||||
- String + number concatenation with smart formatting (COMPLETED)
|
||||
- String multiplication (COMPLETED)
|
||||
- Alphanumeric identifiers (COMPLETED)
|
||||
- Comprehensive testing framework (COMPLETED)
|
||||
|
||||
## Phase 1: Core Language Features (High Priority)
|
||||
|
||||
### 1. Control Flow
|
||||
```bob
|
||||
// If statements
|
||||
if (x > 10) {
|
||||
print "big";
|
||||
} else {
|
||||
print "small";
|
||||
}
|
||||
|
||||
// While loops
|
||||
var i = 0;
|
||||
while (i < 5) {
|
||||
print i;
|
||||
i = i + 1;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Add `IfStmt` and `WhileStmt` to Statement.h
|
||||
- Update parser to handle `if` and `while` keywords
|
||||
- Implement control flow in interpreter
|
||||
|
||||
### 2. Logical Operators
|
||||
```bob
|
||||
// Currently missing: and, or, not operators
|
||||
if (x > 0 and y < 10) {
|
||||
print "valid range";
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Add `and`, `or`, `not` operator parsing
|
||||
- Implement logical operator evaluation in interpreter
|
||||
|
||||
### 3. Better Error Handling
|
||||
```bob
|
||||
// Current: Basic error messages
|
||||
// Goal: Line numbers, better context
|
||||
Error at line 5: Expected ';' after expression
|
||||
print 42
|
||||
^
|
||||
```
|
||||
|
||||
## Phase 2: Data Structures (Medium Priority)
|
||||
|
||||
### 4. Arrays/Lists
|
||||
```bob
|
||||
var numbers = [1, 2, 3, 4];
|
||||
print numbers[0]; // 1
|
||||
numbers[1] = 42;
|
||||
```
|
||||
|
||||
### 5. Maps/Dictionaries
|
||||
```bob
|
||||
var person = {"name": "Bob", "age": 25};
|
||||
print person["name"];
|
||||
person["city"] = "NYC";
|
||||
```
|
||||
|
||||
## Phase 3: Standard Library (Medium Priority)
|
||||
|
||||
### 6. Additional Built-in Functions
|
||||
```bob
|
||||
len("hello"); // String length
|
||||
input("Enter name: "); // User input
|
||||
random(1, 100); // Random numbers
|
||||
type(42); // Type checking
|
||||
```
|
||||
|
||||
### 7. File I/O
|
||||
```bob
|
||||
var content = readFile("data.txt");
|
||||
writeFile("output.txt", "Hello World");
|
||||
```
|
||||
|
||||
## Phase 4: Advanced Features (Lower Priority)
|
||||
|
||||
### 8. Classes & Objects
|
||||
```bob
|
||||
class Person {
|
||||
init(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
greet() {
|
||||
print "Hello, I'm " + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
var bob = Person("Bob", 25);
|
||||
bob.greet();
|
||||
```
|
||||
|
||||
### 9. Modules/Imports
|
||||
```bob
|
||||
import "math.bob";
|
||||
import "utils.bob";
|
||||
```
|
||||
|
||||
### 10. Type System
|
||||
```bob
|
||||
// Optional type annotations
|
||||
fun add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Tips
|
||||
|
||||
### For Each Feature:
|
||||
1. Lexer: Add new tokens if needed
|
||||
2. Parser: Add new expression/statement types
|
||||
3. AST: Define new node types
|
||||
4. Interpreter: Implement evaluation logic
|
||||
5. Test: Create test cases using assert function
|
||||
|
||||
### Testing Strategy:
|
||||
```bob
|
||||
// Use the new assert function for comprehensive testing
|
||||
assert(add(2, 3) == 5, "add(2, 3) should equal 5");
|
||||
assert(x > 0, "x should be positive");
|
||||
```
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
1. Add if statements (fundamental control flow)
|
||||
2. Add while loops (enables iteration)
|
||||
3. Implement logical operators (and, or, not)
|
||||
4. Improve error messages (better developer experience)
|
||||
5. Add arrays (most useful data structure)
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- [x] Can write simple functions
|
||||
- [x] Can use return statements
|
||||
- [x] Can use closures
|
||||
- [x] Has assert function for testing
|
||||
- [x] Has standard library infrastructure
|
||||
- [x] Supports first-class functions
|
||||
- [x] Has comprehensive testing framework
|
||||
- [ ] Can use if/else statements
|
||||
- [ ] Can use while loops
|
||||
- [ ] Can use logical operators
|
||||
- [ ] Can work with arrays
|
||||
- [ ] Can read/write files
|
||||
- [ ] Has good error messages
|
||||
|
||||
## Resources
|
||||
|
||||
- [Crafting Interpreters](https://craftinginterpreters.com/) - Excellent resource for language implementation
|
||||
- [Bob's current source code](./source/) - Your implementation
|
||||
- [Test files](./*.bob) - Examples of current functionality
|
||||
|
||||
## Recent Achievements
|
||||
|
||||
### Function Implementation (COMPLETED)
|
||||
- Function declarations with parameters
|
||||
- Function calls with arguments
|
||||
- Return statements
|
||||
- Proper scoping and closures
|
||||
- Nested function calls
|
||||
|
||||
### Standard Library (COMPLETED)
|
||||
- `print()` function (converted from statement)
|
||||
- `assert()` function with custom messages
|
||||
- Extensible architecture for adding more functions
|
||||
|
||||
### Testing Framework (COMPLETED)
|
||||
- Comprehensive test suite using assert
|
||||
- Tests for all language features
|
||||
- Proper error handling and execution stopping
|
||||
|
||||
### Advanced Language Features (COMPLETED)
|
||||
- First-class functions and higher-order functions
|
||||
- Function passing as arguments
|
||||
- Function composition patterns
|
||||
- Callback patterns and function storage
|
||||
- String + number concatenation with smart formatting
|
||||
- String multiplication (string * number, number * string)
|
||||
- Alphanumeric identifiers support
|
||||
- Stress testing with 100-parameter functions
|
||||
30
benchmark.py
30
benchmark.py
@ -1,30 +0,0 @@
|
||||
import time
|
||||
|
||||
# Test the time function
|
||||
print("Testing time function:")
|
||||
|
||||
time1 = time.time()
|
||||
print(f"Time 1: {time1}")
|
||||
|
||||
time2 = time.time()
|
||||
print(f"Time 2: {time2}")
|
||||
|
||||
time3 = time.time()
|
||||
print(f"Time 3: {time3}")
|
||||
|
||||
diff1 = time2 - time1
|
||||
diff2 = time3 - time2
|
||||
|
||||
print(f"Difference 1-2: {diff1} seconds")
|
||||
print(f"Difference 2-3: {diff2} seconds")
|
||||
|
||||
# Test with some work in between
|
||||
start = time.time()
|
||||
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
|
||||
end = time.time()
|
||||
duration = end - start
|
||||
|
||||
print(f"Work duration: {duration} seconds")
|
||||
print(f"Sum: {sum_val}")
|
||||
|
||||
print("Time function analysis complete!")
|
||||
20
headers/ASTPrinter.h
Normal file
20
headers/ASTPrinter.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "Expression.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
|
||||
|
||||
class ASTPrinter : Visitor
|
||||
{
|
||||
sptr(Object) visitBinaryExpr(sptr(BinaryExpr) expr) override;
|
||||
sptr(Object) visitGroupingExpr(sptr(GroupingExpr) expr) override;
|
||||
sptr(Object) visitLiteralExpr(sptr(LiteralExpr) expr) override;
|
||||
sptr(Object) visitUnaryExpr(sptr(UnaryExpr) expr) override;
|
||||
public:
|
||||
sptr(Object) print(sptr(Expr) expr);
|
||||
private:
|
||||
sptr(Object) parenthesize(std::string name, std::vector<sptr(Expr)> exprs);
|
||||
|
||||
};
|
||||
@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "Value.h"
|
||||
#include "Lexer.h"
|
||||
|
||||
// Forward declaration
|
||||
class ErrorReporter;
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
Environment() : parent(nullptr), errorReporter(nullptr) {}
|
||||
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
|
||||
|
||||
// Set error reporter for enhanced error reporting
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
}
|
||||
|
||||
// Optimized define with inline
|
||||
inline void define(const std::string& name, const Value& value) {
|
||||
variables[name] = value;
|
||||
}
|
||||
|
||||
// Enhanced assign with error reporting
|
||||
void assign(const Token& name, const Value& value);
|
||||
|
||||
// Enhanced get with error reporting
|
||||
Value get(const Token& name);
|
||||
|
||||
// Get by string name with error reporting
|
||||
Value get(const std::string& name);
|
||||
|
||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||
inline void clear() { variables.clear(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, Value> variables;
|
||||
std::shared_ptr<Environment> parent;
|
||||
ErrorReporter* errorReporter;
|
||||
};
|
||||
@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
// ANSI color codes for terminal output
|
||||
namespace Colors {
|
||||
const std::string RED = "\033[31m";
|
||||
const std::string GREEN = "\033[32m";
|
||||
const std::string YELLOW = "\033[33m";
|
||||
const std::string BLUE = "\033[34m";
|
||||
const std::string MAGENTA = "\033[35m";
|
||||
const std::string CYAN = "\033[36m";
|
||||
const std::string WHITE = "\033[37m";
|
||||
const std::string BOLD = "\033[1m";
|
||||
const std::string RESET = "\033[0m";
|
||||
}
|
||||
|
||||
struct ErrorContext {
|
||||
std::string errorType;
|
||||
std::string message;
|
||||
std::string fileName;
|
||||
int line;
|
||||
int column;
|
||||
std::vector<std::string> callStack;
|
||||
};
|
||||
|
||||
class ErrorReporter {
|
||||
private:
|
||||
std::vector<std::string> sourceLines;
|
||||
std::string currentFileName;
|
||||
std::vector<std::string> callStack;
|
||||
bool hadError = false;
|
||||
|
||||
public:
|
||||
ErrorReporter() = default;
|
||||
~ErrorReporter() = default;
|
||||
|
||||
// Load source code for context
|
||||
void loadSource(const std::string& source, const std::string& fileName);
|
||||
|
||||
// Report errors with line and column information
|
||||
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
||||
|
||||
// Check if an error has been reported
|
||||
bool hasReportedError() const { return hadError; }
|
||||
|
||||
// Report errors with full context
|
||||
void reportErrorWithContext(const ErrorContext& context);
|
||||
|
||||
// Call stack management
|
||||
void pushCallStack(const std::string& functionName);
|
||||
void popCallStack();
|
||||
|
||||
private:
|
||||
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
||||
void displayCallStack(const std::vector<std::string>& callStack);
|
||||
std::string getLineWithArrow(int line, int column);
|
||||
std::string colorize(const std::string& text, const std::string& color);
|
||||
};
|
||||
@ -4,133 +4,81 @@
|
||||
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include "Lexer.h"
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "Value.h"
|
||||
|
||||
// Forward declarations
|
||||
struct FunctionExpr;
|
||||
struct ExprVisitor;
|
||||
|
||||
struct AssignExpr;
|
||||
struct BinaryExpr;
|
||||
struct GroupingExpr;
|
||||
struct LiteralExpr;
|
||||
struct UnaryExpr;
|
||||
struct VarExpr;
|
||||
struct CallExpr;
|
||||
|
||||
// AST nodes use shared_ptr for proper memory management
|
||||
struct ExprVisitor
|
||||
struct Visitor
|
||||
{
|
||||
virtual Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expr) = 0;
|
||||
virtual Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expr) = 0;
|
||||
virtual Value visitCallExpr(const std::shared_ptr<CallExpr>& expr) = 0;
|
||||
virtual Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expr) = 0;
|
||||
virtual Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expr) = 0;
|
||||
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
||||
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
|
||||
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
||||
virtual sptr(Object) visitBinaryExpr(sptr(BinaryExpr) expr) = 0;
|
||||
virtual sptr(Object) visitGroupingExpr(sptr(GroupingExpr) expr) = 0;
|
||||
virtual sptr(Object) visitLiteralExpr(sptr(LiteralExpr) expr) = 0;
|
||||
virtual sptr(Object) visitUnaryExpr(sptr(UnaryExpr) expr) = 0;
|
||||
};
|
||||
|
||||
struct Expr : public std::enable_shared_from_this<Expr> {
|
||||
virtual Value accept(ExprVisitor* visitor) = 0;
|
||||
virtual ~Expr() = default;
|
||||
|
||||
struct Expr{
|
||||
virtual sptr(Object) accept(Visitor* visitor) = 0;
|
||||
virtual ~Expr(){}
|
||||
};
|
||||
|
||||
struct AssignExpr : Expr
|
||||
{
|
||||
const Token name;
|
||||
const Token op;
|
||||
std::shared_ptr<Expr> value;
|
||||
AssignExpr(Token name, Token op, std::shared_ptr<Expr> value)
|
||||
: name(name), op(op), value(value) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitAssignExpr(std::static_pointer_cast<AssignExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct BinaryExpr : Expr
|
||||
struct BinaryExpr : Expr, public std::enable_shared_from_this<BinaryExpr>
|
||||
{
|
||||
std::shared_ptr<Expr> left;
|
||||
const std::shared_ptr<Expr> left;
|
||||
const Token oper;
|
||||
std::shared_ptr<Expr> right;
|
||||
const std::shared_ptr<Expr> right;
|
||||
|
||||
BinaryExpr(std::shared_ptr<Expr> left, Token oper, std::shared_ptr<Expr> right)
|
||||
: left(left), oper(oper), right(right) {}
|
||||
Value accept(ExprVisitor* visitor) override{
|
||||
return visitor->visitBinaryExpr(std::static_pointer_cast<BinaryExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct GroupingExpr : Expr
|
||||
{
|
||||
std::shared_ptr<Expr> expression;
|
||||
|
||||
explicit GroupingExpr(std::shared_ptr<Expr> expression) : expression(expression) {}
|
||||
Value accept(ExprVisitor* visitor) override{
|
||||
return visitor->visitGroupingExpr(std::static_pointer_cast<GroupingExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct LiteralExpr : Expr
|
||||
{
|
||||
std::string value;
|
||||
bool isNumber;
|
||||
bool isNull;
|
||||
bool isBoolean;
|
||||
|
||||
LiteralExpr(const std::string& value, bool isNumber, bool isNull, bool isBoolean)
|
||||
: value(value), isNumber(isNumber), isNull(isNull), isBoolean(isBoolean) {}
|
||||
Value accept(ExprVisitor* visitor) override{
|
||||
return visitor->visitLiteralExpr(std::static_pointer_cast<LiteralExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct UnaryExpr : Expr
|
||||
{
|
||||
Token oper;
|
||||
std::shared_ptr<Expr> right;
|
||||
UnaryExpr(Token oper, std::shared_ptr<Expr> right) : oper(oper), right(right) {}
|
||||
Value accept(ExprVisitor* visitor) override{
|
||||
return visitor->visitUnaryExpr(std::static_pointer_cast<UnaryExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct VarExpr : Expr
|
||||
{
|
||||
Token name;
|
||||
explicit VarExpr(Token name) : name(name){};
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
BinaryExpr(sptr(Expr) left, Token oper, sptr(Expr) right) : left(left), oper(oper), right(right)
|
||||
{
|
||||
return visitor->visitVarExpr(std::static_pointer_cast<VarExpr>(shared_from_this()));
|
||||
}
|
||||
sptr(Object) accept(Visitor* visitor) override{
|
||||
return visitor->visitBinaryExpr(shared_from_this() );
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionExpr : Expr {
|
||||
std::vector<Token> params;
|
||||
std::vector<std::shared_ptr<Stmt>> body;
|
||||
FunctionExpr(const std::vector<Token>& params, const std::vector<std::shared_ptr<Stmt>>& body)
|
||||
: params(params), body(body) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitFunctionExpr(std::static_pointer_cast<FunctionExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct CallExpr : Expr
|
||||
struct GroupingExpr : Expr, public std::enable_shared_from_this<GroupingExpr>
|
||||
{
|
||||
std::shared_ptr<Expr> callee;
|
||||
Token paren;
|
||||
std::vector<std::shared_ptr<Expr>> arguments;
|
||||
CallExpr(std::shared_ptr<Expr> callee, Token paren, std::vector<std::shared_ptr<Expr>> arguments)
|
||||
: callee(callee), paren(paren), arguments(arguments) {}
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
const std::shared_ptr<Expr> expression;
|
||||
|
||||
GroupingExpr(sptr(Expr) expression) : expression(expression)
|
||||
{
|
||||
return visitor->visitCallExpr(std::static_pointer_cast<CallExpr>(shared_from_this()));
|
||||
}
|
||||
sptr(Object) accept(Visitor* visitor) override{
|
||||
return visitor->visitGroupingExpr(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
struct LiteralExpr : Expr, public std::enable_shared_from_this<LiteralExpr>
|
||||
{
|
||||
const std::string value;
|
||||
const bool isNumber;
|
||||
const bool isNull;
|
||||
LiteralExpr(std::string value, bool isNumber, bool isNull) : value(value), isNumber(isNumber), isNull(isNull)
|
||||
{
|
||||
}
|
||||
sptr(Object) accept(Visitor* visitor) override{
|
||||
return visitor->visitLiteralExpr(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
struct UnaryExpr : Expr, public std::enable_shared_from_this<UnaryExpr>
|
||||
{
|
||||
const Token oper;
|
||||
const std::shared_ptr<Expr> right;
|
||||
|
||||
UnaryExpr(Token oper, sptr(Expr) right) : oper(oper), right(right)
|
||||
{
|
||||
}
|
||||
sptr(Object) accept(Visitor* visitor) override{
|
||||
return visitor->visitUnaryExpr(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
|
||||
@ -1,71 +1,25 @@
|
||||
#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 {
|
||||
class Interpreter : Visitor
|
||||
{
|
||||
|
||||
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 visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
||||
sptr(Object) visitBinaryExpr(sptr(BinaryExpr) expression) override;
|
||||
sptr(Object) visitGroupingExpr(sptr(GroupingExpr) expression) override;
|
||||
sptr(Object) visitLiteralExpr(sptr(LiteralExpr) expression) override;
|
||||
sptr(Object) visitUnaryExpr(sptr(UnaryExpr) expression) override;
|
||||
|
||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) override;
|
||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) override;
|
||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement) override;
|
||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement) override;
|
||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement) override;
|
||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement) 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;
|
||||
void interpret(sptr(Expr) expr);
|
||||
|
||||
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);
|
||||
void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env);
|
||||
void addStdLibFunctions();
|
||||
|
||||
public:
|
||||
bool isTruthy(Value object);
|
||||
std::string stringify(Value object);
|
||||
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
||||
sptr(Object) evaluate(sptr(Expr) expr);
|
||||
bool isTruthy(sptr(Object) object);
|
||||
bool isEqual(sptr(Object) a, sptr(Object) b);
|
||||
|
||||
// Error reporting
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
if (environment) {
|
||||
environment->setErrorReporter(reporter);
|
||||
}
|
||||
|
||||
// Add standard library functions after error reporter is set
|
||||
addStdLibFunctions();
|
||||
}
|
||||
std::string stringify(sptr(Object) object);
|
||||
|
||||
bool isWholeNumer(double num);
|
||||
};
|
||||
|
||||
@ -6,52 +6,23 @@
|
||||
|
||||
enum TokenType{
|
||||
OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE,
|
||||
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT,
|
||||
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR,
|
||||
|
||||
BIN_OR, BIN_AND, BIN_NOT, BIN_XOR, BIN_SLEFT, BIN_SRIGHT,
|
||||
BINARY_OP,
|
||||
|
||||
BANG, BANG_EQUAL,
|
||||
EQUAL, DOUBLE_EQUAL,
|
||||
GREATER, GREATER_EQUAL,
|
||||
LESS, LESS_EQUAL,
|
||||
|
||||
IDENTIFIER, STRING, NUMBER, BOOL,
|
||||
IDENTIFIER, STRING, NUMBER,
|
||||
|
||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
|
||||
|
||||
// Compound assignment operators
|
||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||
|
||||
// Compound bitwise assignment operators
|
||||
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL,
|
||||
|
||||
END_OF_FILE
|
||||
};
|
||||
|
||||
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
|
||||
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
|
||||
|
||||
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
||||
|
||||
"BANG", "BANG_EQUAL",
|
||||
"EQUAL", "DOUBLE_EQUAL",
|
||||
"GREATER", "GREATER_EQUAL",
|
||||
"LESS", "LESS_EQUAL",
|
||||
|
||||
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
||||
|
||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
|
||||
|
||||
// Compound assignment operators
|
||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||
|
||||
// Compound bitwise assignment operators
|
||||
"BIN_AND_EQUAL", "BIN_OR_EQUAL", "BIN_XOR_EQUAL", "BIN_SLEFT_EQUAL", "BIN_SRIGHT_EQUAL",
|
||||
|
||||
"END_OF_FILE"};
|
||||
|
||||
const std::map<std::string, TokenType> KEYWORDS {
|
||||
{"and", AND},
|
||||
{"or", OR},
|
||||
@ -67,44 +38,28 @@ const std::map<std::string, TokenType> KEYWORDS {
|
||||
{"super", SUPER},
|
||||
{"this", THIS},
|
||||
{"none", NONE},
|
||||
{"return", RETURN},
|
||||
{"return", RETURN}
|
||||
};
|
||||
|
||||
struct Token
|
||||
{
|
||||
TokenType type;
|
||||
std::string lexeme;
|
||||
//TODO Object literal;
|
||||
int line;
|
||||
int column;
|
||||
};
|
||||
|
||||
|
||||
// Forward declaration
|
||||
class ErrorReporter;
|
||||
|
||||
class Lexer{
|
||||
public:
|
||||
Lexer() : errorReporter(nullptr) {}
|
||||
|
||||
std::vector<Token> Tokenize(std::string source);
|
||||
|
||||
// Set error reporter for enhanced error reporting
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
}
|
||||
|
||||
private:
|
||||
int line;
|
||||
int column;
|
||||
std::vector<char> src;
|
||||
ErrorReporter* errorReporter;
|
||||
|
||||
private:
|
||||
bool matchOn(char expected);
|
||||
|
||||
char peekNext();
|
||||
|
||||
void advance(int by = 1);
|
||||
|
||||
std::string parseEscapeCharacters(const std::string &input);
|
||||
};
|
||||
|
||||
@ -3,32 +3,21 @@
|
||||
#include <vector>
|
||||
#include "Lexer.h"
|
||||
#include "Expression.h"
|
||||
#include "Statement.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
class Parser
|
||||
{
|
||||
private:
|
||||
const std::vector<Token> tokens;
|
||||
int current = 0;
|
||||
int functionDepth = 0; // Track nesting level of functions
|
||||
ErrorReporter* errorReporter = nullptr;
|
||||
|
||||
public:
|
||||
explicit Parser(std::vector<Token> tokens) : tokens(std::move(tokens)){};
|
||||
std::vector<sptr(Stmt)> parse();
|
||||
void setErrorReporter(ErrorReporter* reporter) { errorReporter = reporter; }
|
||||
sptr(Expr) parse();
|
||||
|
||||
private:
|
||||
sptr(Expr) expression();
|
||||
sptr(Expr) logical_or();
|
||||
sptr(Expr) logical_and();
|
||||
sptr(Expr) bitwise_or();
|
||||
sptr(Expr) bitwise_xor();
|
||||
sptr(Expr) bitwise_and();
|
||||
sptr(Expr) shift();
|
||||
sptr(Expr) equality();
|
||||
sptr(Expr) comparison();
|
||||
sptr(Expr) term();
|
||||
@ -36,41 +25,15 @@ private:
|
||||
sptr(Expr) unary();
|
||||
sptr(Expr) primary();
|
||||
|
||||
bool match(const std::vector<TokenType>& types);
|
||||
bool match(std::vector<TokenType> types);
|
||||
|
||||
bool check(TokenType type);
|
||||
bool isAtEnd();
|
||||
Token advance();
|
||||
Token peek();
|
||||
Token previous();
|
||||
Token consume(TokenType type, const std::string& message);
|
||||
sptr(Stmt) statement();
|
||||
Token consume(TokenType type, std::string message);
|
||||
|
||||
void sync();
|
||||
|
||||
|
||||
|
||||
std::shared_ptr<Stmt> expressionStatement();
|
||||
|
||||
std::shared_ptr<Stmt> returnStatement();
|
||||
|
||||
std::shared_ptr<Stmt> ifStatement();
|
||||
|
||||
std::shared_ptr<Stmt> declaration();
|
||||
|
||||
std::shared_ptr<Stmt> varDeclaration();
|
||||
|
||||
std::shared_ptr<Stmt> functionDeclaration();
|
||||
std::shared_ptr<Expr> functionExpression();
|
||||
|
||||
sptr(Expr) assignment();
|
||||
|
||||
std::vector<std::shared_ptr<Stmt>> block();
|
||||
|
||||
sptr(Expr) finishCall(sptr(Expr) callee);
|
||||
|
||||
// Helper methods for function scope tracking
|
||||
void enterFunction() { functionDepth++; }
|
||||
void exitFunction() { functionDepth--; }
|
||||
bool isInFunction() const { return functionDepth > 0; }
|
||||
};
|
||||
@ -1,114 +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 StmtVisitor
|
||||
{
|
||||
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt) = 0;
|
||||
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt) = 0;
|
||||
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt) = 0;
|
||||
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt) = 0;
|
||||
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt) = 0;
|
||||
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt) = 0;
|
||||
};
|
||||
|
||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||
{
|
||||
std::shared_ptr<Expr> expression;
|
||||
virtual void accept(StmtVisitor* visitor) = 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) override
|
||||
{
|
||||
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
struct ExpressionStmt : Stmt
|
||||
{
|
||||
std::shared_ptr<Expr> expression;
|
||||
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
|
||||
{
|
||||
}
|
||||
|
||||
void accept(StmtVisitor* visitor) override
|
||||
{
|
||||
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
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) override
|
||||
{
|
||||
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
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) override
|
||||
{
|
||||
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
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) override
|
||||
{
|
||||
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
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) override
|
||||
{
|
||||
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Value.h"
|
||||
#include "Environment.h"
|
||||
#include <memory>
|
||||
|
||||
class Interpreter;
|
||||
class ErrorReporter;
|
||||
|
||||
class StdLib {
|
||||
public:
|
||||
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
|
||||
};
|
||||
@ -1,15 +1,5 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include "Value.h"
|
||||
|
||||
// Forward declarations
|
||||
struct Stmt;
|
||||
struct Environment;
|
||||
|
||||
struct Object
|
||||
{
|
||||
virtual ~Object(){};
|
||||
@ -25,9 +15,6 @@ struct String : Object
|
||||
{
|
||||
std::string value;
|
||||
explicit String(std::string str) : value(str) {}
|
||||
~String(){
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct Boolean : Object
|
||||
@ -39,27 +26,4 @@ struct Boolean : Object
|
||||
struct None : public Object
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
struct Function : public Object
|
||||
{
|
||||
const std::string name;
|
||||
const std::vector<std::string> params;
|
||||
const std::vector<std::shared_ptr<Stmt>> body;
|
||||
const std::shared_ptr<Environment> closure;
|
||||
|
||||
Function(std::string name, std::vector<std::string> params,
|
||||
std::vector<std::shared_ptr<Stmt>> body,
|
||||
std::shared_ptr<Environment> closure)
|
||||
: name(name), params(params), body(body), closure(closure) {}
|
||||
};
|
||||
|
||||
struct BuiltinFunction : public Object
|
||||
{
|
||||
const std::string name;
|
||||
const std::function<Value(std::vector<Value>, int, int)> func;
|
||||
|
||||
BuiltinFunction(std::string name, std::function<Value(std::vector<Value>, int, int)> func)
|
||||
: name(name), func(func) {}
|
||||
};
|
||||
|
||||
};
|
||||
261
headers/Value.h
261
headers/Value.h
@ -1,261 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
// Forward declarations
|
||||
class Environment;
|
||||
class Function;
|
||||
class BuiltinFunction;
|
||||
|
||||
// Type tags for the Value union
|
||||
enum ValueType : uint8_t {
|
||||
VAL_NONE,
|
||||
VAL_NUMBER,
|
||||
VAL_BOOLEAN,
|
||||
VAL_STRING,
|
||||
VAL_FUNCTION,
|
||||
VAL_BUILTIN_FUNCTION
|
||||
};
|
||||
|
||||
// Tagged value system (like Lua) - no heap allocation for simple values
|
||||
struct Value {
|
||||
union {
|
||||
double number;
|
||||
bool boolean;
|
||||
Function* function;
|
||||
BuiltinFunction* builtin_function;
|
||||
};
|
||||
ValueType type;
|
||||
std::string string_value; // Store strings outside the union for safety
|
||||
|
||||
// Constructors
|
||||
Value() : number(0.0), type(ValueType::VAL_NONE) {}
|
||||
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
|
||||
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
|
||||
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
|
||||
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
|
||||
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
|
||||
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
|
||||
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
|
||||
|
||||
// Move constructor
|
||||
Value(Value&& other) noexcept
|
||||
: type(other.type), string_value(std::move(other.string_value)) {
|
||||
if (type != ValueType::VAL_STRING) {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
other.type = ValueType::VAL_NONE;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
Value& operator=(Value&& other) noexcept {
|
||||
if (this != &other) {
|
||||
type = other.type;
|
||||
if (type == ValueType::VAL_STRING) {
|
||||
string_value = std::move(other.string_value);
|
||||
} else {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
other.type = ValueType::VAL_NONE;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Copy constructor (only when needed)
|
||||
Value(const Value& other) : type(other.type) {
|
||||
if (type == ValueType::VAL_STRING) {
|
||||
string_value = other.string_value;
|
||||
} else {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
}
|
||||
|
||||
// Copy assignment (only when needed)
|
||||
Value& operator=(const Value& other) {
|
||||
if (this != &other) {
|
||||
type = other.type;
|
||||
if (type == ValueType::VAL_STRING) {
|
||||
string_value = other.string_value;
|
||||
} else {
|
||||
number = other.number; // Copy the union
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Type checking (fast, no dynamic casting) - inline for performance
|
||||
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
|
||||
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
|
||||
inline bool isString() const { return type == ValueType::VAL_STRING; }
|
||||
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
|
||||
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
||||
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
||||
|
||||
// Value extraction (safe, with type checking) - inline for performance
|
||||
inline double asNumber() const { return isNumber() ? number : 0.0; }
|
||||
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
|
||||
inline const std::string& asString() const { return string_value; }
|
||||
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
|
||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
|
||||
|
||||
// Truthiness check - inline for performance
|
||||
inline bool isTruthy() const {
|
||||
switch (type) {
|
||||
case ValueType::VAL_NONE: return false;
|
||||
case ValueType::VAL_BOOLEAN: return boolean;
|
||||
case ValueType::VAL_NUMBER: return number != 0.0;
|
||||
case ValueType::VAL_STRING: return !string_value.empty();
|
||||
case ValueType::VAL_FUNCTION: return function != nullptr;
|
||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Equality comparison - inline for performance
|
||||
inline bool equals(const Value& other) const {
|
||||
if (type != other.type) return false;
|
||||
|
||||
switch (type) {
|
||||
case ValueType::VAL_NONE: return true;
|
||||
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
|
||||
case ValueType::VAL_NUMBER: return number == other.number;
|
||||
case ValueType::VAL_STRING: return string_value == other.string_value;
|
||||
case ValueType::VAL_FUNCTION: return function == other.function;
|
||||
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// String representation
|
||||
std::string toString() const {
|
||||
switch (type) {
|
||||
case ValueType::VAL_NONE: return "none";
|
||||
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
|
||||
case ValueType::VAL_NUMBER: {
|
||||
// Format numbers like the original stringify function
|
||||
if (number == std::floor(number)) {
|
||||
return std::to_string(static_cast<long long>(number));
|
||||
} else {
|
||||
std::string str = std::to_string(number);
|
||||
// Remove trailing zeros
|
||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||
if (str.back() == '.') str.pop_back();
|
||||
return str;
|
||||
}
|
||||
}
|
||||
case ValueType::VAL_STRING: return string_value;
|
||||
case ValueType::VAL_FUNCTION: return "<function>";
|
||||
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Arithmetic operators
|
||||
Value operator+(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number + other.number);
|
||||
}
|
||||
if (isString() && other.isString()) {
|
||||
return Value(string_value + other.string_value);
|
||||
}
|
||||
if (isString() && other.isNumber()) {
|
||||
return Value(string_value + other.toString());
|
||||
}
|
||||
if (isNumber() && other.isString()) {
|
||||
return Value(toString() + other.string_value);
|
||||
}
|
||||
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;
|
||||
@ -5,8 +5,6 @@
|
||||
#include <string>
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/helperFunctions/ShortHands.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
|
||||
#define VERSION "0.0.1"
|
||||
|
||||
@ -14,16 +12,22 @@ class Bob
|
||||
{
|
||||
public:
|
||||
Lexer lexer;
|
||||
sptr(Interpreter) interpreter;
|
||||
ErrorReporter errorReporter;
|
||||
|
||||
~Bob() = default;
|
||||
Interpreter interpreter;
|
||||
|
||||
public:
|
||||
void runFile(const std::string& path);
|
||||
void runFile(std::string path);
|
||||
|
||||
void runPrompt();
|
||||
|
||||
void error(int line, std::string message);
|
||||
|
||||
|
||||
private:
|
||||
bool hadError = false;
|
||||
|
||||
private:
|
||||
void run(std::string source);
|
||||
|
||||
void report(int line, std::string where, std::string message);
|
||||
};
|
||||
|
||||
|
||||
3
source.bob
Normal file
3
source.bob
Normal file
@ -0,0 +1,3 @@
|
||||
//10 + 10
|
||||
//0xFF + 0xFF
|
||||
0xFF + 0xFF00 == 0xFFFF
|
||||
41
source/ASTPrinter.cpp
Normal file
41
source/ASTPrinter.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by Bobby Lucero on 5/23/23.
|
||||
//
|
||||
#include "../headers/ASTPrinter.h"
|
||||
|
||||
|
||||
sptr(Object) ASTPrinter::visitBinaryExpr(sptr(BinaryExpr) expression){
|
||||
std::cout << expression->left << std::endl;
|
||||
return parenthesize(expression->oper.lexeme, std::vector<sptr(Expr)>{expression->left, expression->right});
|
||||
}
|
||||
|
||||
sptr(Object) ASTPrinter::visitGroupingExpr(sptr(GroupingExpr) expression){
|
||||
return parenthesize("group", std::vector<sptr(Expr)>{expression->expression});
|
||||
}
|
||||
sptr(Object) ASTPrinter::visitLiteralExpr(sptr(LiteralExpr) expression){
|
||||
return msptr(String)(expression->value);
|
||||
}
|
||||
sptr(Object) ASTPrinter::visitUnaryExpr(sptr(UnaryExpr) expression){
|
||||
return parenthesize(expression->oper.lexeme, std::vector<sptr(Expr)>{expression->right});
|
||||
}
|
||||
|
||||
sptr(Object) ASTPrinter::print(sptr(Expr) expr) {
|
||||
return expr->accept(this);
|
||||
}
|
||||
|
||||
sptr(Object) ASTPrinter::parenthesize(std::string name, std::vector<sptr(Expr)> exprs) {
|
||||
std::string builder;
|
||||
|
||||
builder += "(" + name;
|
||||
|
||||
for(const sptr(Expr)& expr : exprs)
|
||||
{
|
||||
builder += " ";
|
||||
builder += std::dynamic_pointer_cast<String>(expr->accept(this))->value;
|
||||
}
|
||||
|
||||
builder += ")";
|
||||
|
||||
return msptr(String)(builder);
|
||||
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
#include "../headers/Environment.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
|
||||
void Environment::assign(const Token& name, const Value& value) {
|
||||
auto it = variables.find(name.lexeme);
|
||||
if (it != variables.end()) {
|
||||
it->second = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent != nullptr) {
|
||||
parent->assign(name, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
||||
"Undefined variable '" + name.lexeme + "'", "");
|
||||
}
|
||||
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
|
||||
}
|
||||
|
||||
Value Environment::get(const Token& name) {
|
||||
auto it = variables.find(name.lexeme);
|
||||
if (it != variables.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
if (parent != nullptr) {
|
||||
return parent->get(name);
|
||||
}
|
||||
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(name.line, name.column, "Runtime Error",
|
||||
"Undefined variable '" + name.lexeme + "'", "");
|
||||
}
|
||||
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
|
||||
}
|
||||
|
||||
Value Environment::get(const std::string& name) {
|
||||
auto it = variables.find(name);
|
||||
if (it != variables.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
if (parent != nullptr) {
|
||||
return parent->get(name);
|
||||
}
|
||||
|
||||
throw std::runtime_error("Undefined variable '" + name + "'");
|
||||
}
|
||||
@ -1,207 +0,0 @@
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
// Helper function to find operator in source line
|
||||
int findOperatorInLine(const std::string& sourceLine, const std::string& operator_) {
|
||||
size_t pos = 0;
|
||||
while ((pos = sourceLine.find(operator_, pos)) != std::string::npos) {
|
||||
// Check if this operator is not inside a string
|
||||
bool inString = false;
|
||||
for (size_t i = 0; i < pos; i++) {
|
||||
if (sourceLine[i] == '"' && (i == 0 || sourceLine[i-1] != '\\')) {
|
||||
inString = !inString;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inString) {
|
||||
// Check if this is a standalone operator, not part of a larger operator
|
||||
bool isStandalone = true;
|
||||
|
||||
// Check character before the operator
|
||||
if (pos > 0) {
|
||||
char before = sourceLine[pos - 1];
|
||||
if (before == '&' || before == '|' || before == '=' || before == '<' || before == '>') {
|
||||
isStandalone = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check character after the operator
|
||||
if (pos + operator_.length() < sourceLine.length()) {
|
||||
char after = sourceLine[pos + operator_.length()];
|
||||
if (after == '&' || after == '|' || after == '=' || after == '<' || after == '>') {
|
||||
isStandalone = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isStandalone) {
|
||||
return static_cast<int>(pos + 1); // Convert to 1-based column
|
||||
}
|
||||
}
|
||||
pos += 1; // Move to next position to continue searching
|
||||
}
|
||||
|
||||
return 1; // Default to column 1 if not found
|
||||
}
|
||||
|
||||
void ErrorReporter::loadSource(const std::string& source, const std::string& fileName) {
|
||||
currentFileName = fileName;
|
||||
sourceLines.clear();
|
||||
|
||||
std::istringstream iss(source);
|
||||
std::string line;
|
||||
while (std::getline(iss, line)) {
|
||||
sourceLines.push_back(line);
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
||||
hadError = true;
|
||||
displaySourceContext(line, column, errorType, message, operator_, showArrow);
|
||||
}
|
||||
|
||||
void ErrorReporter::reportErrorWithContext(const ErrorContext& context) {
|
||||
hadError = true;
|
||||
|
||||
std::cout << "\n";
|
||||
std::cout << colorize("╔══════════════════════════════════════════════════════════════╗", Colors::RED) << "\n";
|
||||
std::cout << colorize("║ ERROR REPORT ║", Colors::RED) << "\n";
|
||||
std::cout << colorize("╚══════════════════════════════════════════════════════════════╝", Colors::RED) << "\n\n";
|
||||
|
||||
std::cout << colorize("Error Type: ", Colors::BOLD) << colorize(context.errorType, Colors::RED) << "\n";
|
||||
std::cout << colorize("Message: ", Colors::BOLD) << colorize(context.message, Colors::WHITE) << "\n";
|
||||
|
||||
if (!context.fileName.empty()) {
|
||||
std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n";
|
||||
}
|
||||
|
||||
std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) +
|
||||
", Column " + std::to_string(context.column), Colors::YELLOW) << "\n\n";
|
||||
|
||||
displaySourceContext(context.line, context.column, context.errorType, context.message);
|
||||
|
||||
if (!context.callStack.empty()) {
|
||||
displayCallStack(context.callStack);
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void ErrorReporter::pushCallStack(const std::string& functionName) {
|
||||
// This would be called when entering a function
|
||||
// Implementation depends on integration with the interpreter
|
||||
}
|
||||
|
||||
void ErrorReporter::popCallStack() {
|
||||
// This would be called when exiting a function
|
||||
// Implementation depends on integration with the interpreter
|
||||
}
|
||||
|
||||
void ErrorReporter::displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) {
|
||||
if (sourceLines.empty()) {
|
||||
std::cout << colorize("Error: ", Colors::RED) << colorize(errorType, Colors::BOLD) << "\n";
|
||||
std::cout << colorize("Message: ", Colors::BOLD) << message << "\n";
|
||||
std::cout << colorize("Location: ", Colors::BOLD) << "Line " << line << ", Column " << column << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
int maxWidth = 65;
|
||||
int startLine = std::max(1, line - 4);
|
||||
int endLine = std::min(static_cast<int>(sourceLines.size()), line + 2);
|
||||
|
||||
for (int i = startLine; i <= endLine; i++) {
|
||||
if (i > 0 && i <= static_cast<int>(sourceLines.size())) {
|
||||
int lineWidth = static_cast<int>(sourceLines[i-1].length()) + 8;
|
||||
maxWidth = std::max(maxWidth, lineWidth);
|
||||
}
|
||||
}
|
||||
|
||||
int errorLineWidth = 8 + column + 1 + static_cast<int>(message.length());
|
||||
maxWidth = std::max(maxWidth, errorLineWidth);
|
||||
maxWidth = std::max(maxWidth, 65);
|
||||
|
||||
std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n";
|
||||
std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n";
|
||||
|
||||
for (int i = startLine; i <= endLine; i++) {
|
||||
std::string lineNum = std::to_string(i);
|
||||
std::string linePrefix = " " + std::string(4 - lineNum.length(), ' ') + lineNum + " | ";
|
||||
|
||||
if (i > 0 && i <= static_cast<int>(sourceLines.size())) {
|
||||
if (i == line) {
|
||||
std::string sourceLine = sourceLines[i-1];
|
||||
std::string fullLine = colorize(linePrefix, Colors::RED) + colorize(sourceLine, Colors::YELLOW);
|
||||
|
||||
std::cout << fullLine << "\n";
|
||||
|
||||
// Draw arrow only if showArrow is true
|
||||
if (showArrow) {
|
||||
std::string arrowLine = colorize(" | ", Colors::RED);
|
||||
int safeColumn = std::max(1, std::min(column, static_cast<int>(sourceLine.length() + 1)));
|
||||
arrowLine += std::string(safeColumn - 1, ' ') + colorize("^", Colors::RED) + colorize(" " + message, Colors::RED);
|
||||
|
||||
std::cout << arrowLine << "\n";
|
||||
}
|
||||
} else {
|
||||
std::string sourceLine = sourceLines[i - 1];
|
||||
std::string fullLine = colorize(linePrefix, Colors::BLUE) + sourceLine;
|
||||
std::cout << fullLine << "\n";
|
||||
}
|
||||
} else {
|
||||
std::string fullLine = linePrefix;
|
||||
std::cout << colorize(fullLine, Colors::BLUE) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << colorize("└" + std::string(maxWidth, '-') + "┘", Colors::BLUE) << "\n";
|
||||
std::cout << colorize("Error: ", Colors::RED) << colorize(errorType, Colors::BOLD) << "\n";
|
||||
std::cout << colorize("Message: ", Colors::BOLD) << message << "\n\n";
|
||||
}
|
||||
|
||||
void ErrorReporter::displayCallStack(const std::vector<std::string>& callStack) {
|
||||
if (callStack.empty()) return;
|
||||
|
||||
int maxWidth = 65;
|
||||
for (const auto& func : callStack) {
|
||||
int funcWidth = static_cast<int>(func.length()) + 6;
|
||||
maxWidth = std::max(maxWidth, funcWidth);
|
||||
}
|
||||
|
||||
std::cout << colorize("Call Stack:", Colors::BOLD) << "\n";
|
||||
std::cout << colorize("┌" + std::string(maxWidth - 2, '-') + "┐", Colors::MAGENTA) << "\n";
|
||||
|
||||
for (size_t i = 0; i < callStack.size(); i++) {
|
||||
std::string prefix = "│ " + std::to_string(i + 1) + ". ";
|
||||
std::string line = prefix + callStack[i];
|
||||
|
||||
int currentWidth = static_cast<int>(line.length());
|
||||
if (currentWidth < maxWidth - 1) {
|
||||
line += std::string(maxWidth - 1 - currentWidth, ' ') + "│";
|
||||
} else {
|
||||
line += " │";
|
||||
}
|
||||
|
||||
std::cout << colorize(line, Colors::MAGENTA) << "\n";
|
||||
}
|
||||
|
||||
std::cout << colorize("└" + std::string(maxWidth - 2, '-') + "┘", Colors::MAGENTA) << "\n\n";
|
||||
}
|
||||
|
||||
std::string ErrorReporter::getLineWithArrow(int line, int column) {
|
||||
if (line < 1 || line > static_cast<int>(sourceLines.size())) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string sourceLine = sourceLines[line - 1];
|
||||
std::string arrow = std::string(column - 1, ' ') + "^";
|
||||
return sourceLine + "\n" + arrow;
|
||||
}
|
||||
|
||||
std::string ErrorReporter::colorize(const std::string& text, const std::string& color) {
|
||||
try {
|
||||
return color + text + Colors::RESET;
|
||||
} catch (...) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@ -6,30 +6,12 @@
|
||||
#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) {}
|
||||
};
|
||||
|
||||
static ReturnContext g_returnContext;
|
||||
|
||||
|
||||
|
||||
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||
if(expr->isNull) return NONE_VALUE;
|
||||
sptr(Object) Interpreter::visitLiteralExpr(sptr(LiteralExpr) expr) {
|
||||
if(expr->isNull) return msptr(None)();
|
||||
if(expr->isNumber){
|
||||
double num;
|
||||
if(expr->value[1] == 'b')
|
||||
@ -40,30 +22,26 @@ Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||
{
|
||||
num = std::stod(expr->value);
|
||||
}
|
||||
return Value(num);
|
||||
return msptr(Number)(num);
|
||||
}
|
||||
if(expr->isBoolean) {
|
||||
if(expr->value == "true") return TRUE_VALUE;
|
||||
if(expr->value == "false") return FALSE_VALUE;
|
||||
}
|
||||
return Value(expr->value);
|
||||
return msptr(String)(expr->value);
|
||||
}
|
||||
|
||||
Value Interpreter::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
||||
sptr(Object) Interpreter::visitGroupingExpr(sptr(GroupingExpr) expression) {
|
||||
|
||||
return evaluate(expression->expression);
|
||||
}
|
||||
|
||||
Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
||||
sptr(Object) Interpreter::visitUnaryExpr(sptr(UnaryExpr) expression)
|
||||
{
|
||||
Value right = evaluate(expression->right);
|
||||
sptr(Object) right = evaluate(expression->right);
|
||||
|
||||
if(expression->oper.type == MINUS)
|
||||
{
|
||||
if(right.isNumber())
|
||||
if(std::dynamic_pointer_cast<Number>(right))
|
||||
{
|
||||
double value = right.asNumber();
|
||||
return Value(-value);
|
||||
double value = std::dynamic_pointer_cast<Number>(right)->value;
|
||||
return msptr(Number)(-value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -74,690 +52,176 @@ Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
||||
|
||||
if(expression->oper.type == BANG)
|
||||
{
|
||||
return Value(!isTruthy(right));
|
||||
return msptr(Boolean)(!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);
|
||||
sptr(Object) Interpreter::visitBinaryExpr(sptr(BinaryExpr) expression)
|
||||
{
|
||||
sptr(Object) left = evaluate(expression->left);
|
||||
sptr(Object) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (expression->oper.type) {
|
||||
case BANG_EQUAL:
|
||||
return msptr(Boolean)(!isEqual(left, right));
|
||||
case DOUBLE_EQUAL:
|
||||
return msptr(Boolean)(isEqual(left, right));
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
if (left.isString() && right.isString()) {
|
||||
std::string left_string = left.asString();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
if(std::dynamic_pointer_cast<Number>(left) && std::dynamic_pointer_cast<Number>(right))
|
||||
{
|
||||
double left_double = std::dynamic_pointer_cast<Number>(left)->value;
|
||||
double right_double = std::dynamic_pointer_cast<Number>(right)->value;
|
||||
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
|
||||
}
|
||||
}
|
||||
case GREATER:
|
||||
return msptr(Boolean)(left_double > right_double);
|
||||
case GREATER_EQUAL:
|
||||
return msptr(Boolean)(left_double >= right_double);
|
||||
case LESS:
|
||||
return msptr(Boolean)(left_double < right_double);
|
||||
case LESS_EQUAL:
|
||||
return msptr(Boolean)(left_double <= right_double);
|
||||
case MINUS:
|
||||
return msptr(Number)(left_double - right_double);
|
||||
case PLUS:
|
||||
return msptr(Number)(left_double + right_double);
|
||||
case SLASH:
|
||||
return msptr(Number)(left_double / right_double);
|
||||
case STAR:
|
||||
return msptr(Number)(left_double * right_double);
|
||||
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");
|
||||
return msptr(None)(); //unreachable
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isString() && right.isNumber()) {
|
||||
std::string left_string = left.asString();
|
||||
double right_num = right.asNumber();
|
||||
|
||||
else if(std::dynamic_pointer_cast<String>(left) && std::dynamic_pointer_cast<String>(right))
|
||||
{
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return Value(left_string + stringify(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);
|
||||
case PLUS:
|
||||
std::string left_string = std::dynamic_pointer_cast<String>(left)->value;
|
||||
std::string right_string = std::dynamic_pointer_cast<String>(right)->value;
|
||||
return msptr(String)(left_string + right_string);
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings");
|
||||
|
||||
}
|
||||
else if(std::dynamic_pointer_cast<String>(left) && std::dynamic_pointer_cast<Number>(right))
|
||||
{
|
||||
switch (expression->oper.type) {
|
||||
case STAR:
|
||||
std::string left_string = std::dynamic_pointer_cast<String>(left)->value;
|
||||
double right_number = std::dynamic_pointer_cast<Number>(right)->value;
|
||||
if(isWholeNumer(right_number))
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < (int)right_number; ++i) {
|
||||
ss << left_string;
|
||||
}
|
||||
return msptr(String)(ss.str());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a number");
|
||||
|
||||
if (left.isNumber() && right.isString()) {
|
||||
double left_num = left.asNumber();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return Value(stringify(left) + right_string);
|
||||
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 Value(stringify(left) + right_string);
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isString() && right.isBoolean()) {
|
||||
std::string left_string = left.asString();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return Value(left_string + stringify(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 Value(left.asString() + stringify(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 Value(stringify(left) + right.asString());
|
||||
}
|
||||
}
|
||||
|
||||
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 Value(left.asString() + stringify(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 Value(stringify(left) + right.asString());
|
||||
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 Value("none" + right_string);
|
||||
}
|
||||
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");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
Value returnValue = NONE_VALUE;
|
||||
|
||||
for (const auto& stmt : function->body) {
|
||||
// Reset return context for each statement
|
||||
g_returnContext.hasReturn = false;
|
||||
g_returnContext.returnValue = NONE_VALUE;
|
||||
|
||||
execute(stmt);
|
||||
if (g_returnContext.hasReturn) {
|
||||
returnValue = g_returnContext.returnValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
environment = previousEnv;
|
||||
return 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) {
|
||||
auto newEnv = std::make_shared<Environment>(environment);
|
||||
newEnv->setErrorReporter(errorReporter);
|
||||
executeBlock(statement->statements, newEnv);
|
||||
}
|
||||
|
||||
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) {
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
Value value = NONE_VALUE;
|
||||
if (statement->value != nullptr) {
|
||||
value = evaluate(statement->value);
|
||||
}
|
||||
|
||||
g_returnContext.hasReturn = true;
|
||||
g_returnContext.returnValue = value;
|
||||
}
|
||||
|
||||
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement)
|
||||
{
|
||||
if (isTruthy(evaluate(statement->condition))) {
|
||||
execute(statement->thenBranch);
|
||||
} else if (statement->elseBranch != nullptr) {
|
||||
execute(statement->elseBranch);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
|
||||
for(const std::shared_ptr<Stmt>& s : statements)
|
||||
{
|
||||
execute(s);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::execute(const std::shared_ptr<Stmt>& statement)
|
||||
{
|
||||
statement->accept(this);
|
||||
}
|
||||
|
||||
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env)
|
||||
{
|
||||
std::shared_ptr<Environment> previous = this->environment;
|
||||
this->environment = env;
|
||||
|
||||
for(const std::shared_ptr<Stmt>& s : statements)
|
||||
{
|
||||
execute(s);
|
||||
}
|
||||
|
||||
this->environment = previous;
|
||||
}
|
||||
|
||||
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
||||
sptr(Object) Interpreter::evaluate(sptr(Expr) expr) {
|
||||
return expr->accept(this);
|
||||
}
|
||||
|
||||
bool Interpreter::isTruthy(Value object) {
|
||||
bool Interpreter::isTruthy(sptr(Object) object) {
|
||||
|
||||
if(object.isBoolean())
|
||||
if(auto boolean = std::dynamic_pointer_cast<Boolean>(object))
|
||||
{
|
||||
return object.asBoolean();
|
||||
boolean->value;
|
||||
}
|
||||
|
||||
if(object.isNone())
|
||||
if(auto obj = std::dynamic_pointer_cast<None>(object))
|
||||
{
|
||||
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())
|
||||
bool Interpreter::isEqual(sptr(Object) a, sptr(Object) b) {
|
||||
if(auto left = std::dynamic_pointer_cast<Number>(a))
|
||||
{
|
||||
if(b.isNumber())
|
||||
if(auto right = std::dynamic_pointer_cast<Number>(b))
|
||||
{
|
||||
return a.asNumber() == b.asNumber();
|
||||
return left->value == right->value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(a.isBoolean())
|
||||
else if(auto left = std::dynamic_pointer_cast<Boolean>(a))
|
||||
{
|
||||
if(b.isBoolean())
|
||||
if(auto right = std::dynamic_pointer_cast<Boolean>(b))
|
||||
{
|
||||
return a.asBoolean() == b.asBoolean();
|
||||
return left->value == right->value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(a.isString())
|
||||
else if(auto left = std::dynamic_pointer_cast<String>(a))
|
||||
{
|
||||
if(b.isString())
|
||||
if(auto right = std::dynamic_pointer_cast<String>(b))
|
||||
{
|
||||
return a.asString() == b.asString();
|
||||
return left->value == right->value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(a.isNone())
|
||||
else if(auto left = std::dynamic_pointer_cast<None>(a))
|
||||
{
|
||||
if(b.isNone())
|
||||
if(auto right = std::dynamic_pointer_cast<None>(b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Invalid isEqual compariosn");
|
||||
}
|
||||
|
||||
std::string Interpreter::stringify(Value object) {
|
||||
if(object.isNone())
|
||||
void Interpreter::interpret(std::shared_ptr<Expr> expr) {
|
||||
|
||||
sptr(Object) value = evaluate(std::move(expr));
|
||||
std::cout << "\033[0;32m" << stringify(value) << std::endl;
|
||||
|
||||
}
|
||||
|
||||
std::string Interpreter::stringify(std::shared_ptr<Object> object) {
|
||||
if(std::dynamic_pointer_cast<None>(object))
|
||||
{
|
||||
return "none";
|
||||
return "None";
|
||||
}
|
||||
else if(object.isNumber())
|
||||
else if(auto num = std::dynamic_pointer_cast<Number>(object))
|
||||
{
|
||||
double integral = object.asNumber();
|
||||
double fractional = std::modf(object.asNumber(), &integral);
|
||||
double integral = num->value;
|
||||
double fractional = std::modf(num->value, &integral);
|
||||
|
||||
std::stringstream ss;
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
ss << std::fixed << std::setprecision(0) << integral;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << object.asNumber();
|
||||
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << num->value;
|
||||
std::string str = ss.str();
|
||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||
if (str.back() == '.') {
|
||||
@ -767,24 +231,14 @@ std::string Interpreter::stringify(Value object) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
else if(object.isString())
|
||||
else if(auto string = std::dynamic_pointer_cast<String>(object))
|
||||
{
|
||||
return object.asString();
|
||||
return string->value;
|
||||
}
|
||||
else if(object.isBoolean())
|
||||
else if(auto Bool = std::dynamic_pointer_cast<Boolean>(object))
|
||||
{
|
||||
return object.asBoolean() == 1 ? "true" : "false";
|
||||
return Bool->value == 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) {
|
||||
@ -804,12 +258,3 @@ bool Interpreter::isWholeNumer(double num) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
243
source/Lexer.cpp
243
source/Lexer.cpp
@ -1,5 +1,4 @@
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||
#include <cctype>
|
||||
#include <stdexcept>
|
||||
@ -9,94 +8,59 @@ using namespace std;
|
||||
std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
std::vector<Token> tokens;
|
||||
src = std::vector<char>{source.begin(), source.end()};
|
||||
line = 1;
|
||||
column = 1;
|
||||
line = 0;
|
||||
|
||||
while(!src.empty())
|
||||
{
|
||||
char t = src[0];
|
||||
if(t == '(')
|
||||
{
|
||||
tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line, column}); //brace initialization in case you forget
|
||||
tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line}); //brace initialization in case you forget
|
||||
advance();
|
||||
}
|
||||
else if(t == ')')
|
||||
{
|
||||
tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line, column});
|
||||
tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == '{')
|
||||
{
|
||||
tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line, column});
|
||||
tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == '}')
|
||||
{
|
||||
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column});
|
||||
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == ',')
|
||||
{
|
||||
tokens.push_back(Token{COMMA, std::string(1, t), line, column});
|
||||
tokens.push_back(Token{COMMA, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == '.')
|
||||
{
|
||||
tokens.push_back(Token{DOT, std::string(1, t), line, column});
|
||||
tokens.push_back(Token{DOT, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == ';')
|
||||
{
|
||||
tokens.push_back(Token{SEMICOLON, std::string(1, t), line, column});
|
||||
tokens.push_back(Token{SEMICOLON, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == '+')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
tokens.push_back(Token{PLUS, std::string(1, t), line});
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{PLUS_EQUAL, "+=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{PLUS, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '-')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
tokens.push_back(Token{MINUS, std::string(1, t), line});
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{MINUS_EQUAL, "-=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{MINUS, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '*')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{STAR_EQUAL, "*=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{STAR, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '%')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{PERCENT_EQUAL, "%=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{PERCENT, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '~')
|
||||
{
|
||||
tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1});
|
||||
tokens.push_back(Token{STAR, std::string(1, t), line});
|
||||
advance();
|
||||
}
|
||||
else if(t == '=')
|
||||
@ -105,7 +69,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line, column - static_cast<int>(token.length())});
|
||||
tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line});
|
||||
}
|
||||
else if(t == '!')
|
||||
{
|
||||
@ -113,95 +77,31 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line, column - static_cast<int>(token.length())});
|
||||
tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line});
|
||||
}
|
||||
else if(t == '<')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
if(matchOn('='))
|
||||
{
|
||||
tokens.push_back(Token{LESS_EQUAL, "<=", line, column - 1});
|
||||
}
|
||||
else if(matchOn('<'))
|
||||
{
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_SLEFT_EQUAL, "<<=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_SLEFT, "<<", line, column - 1});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{LESS, token, line, column - static_cast<int>(token.length())});
|
||||
}
|
||||
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? LESS_EQUAL : LESS, token, line});
|
||||
}
|
||||
else if(t == '>')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
if(matchOn('='))
|
||||
{
|
||||
tokens.push_back(Token{GREATER_EQUAL, ">=", line, column - 1});
|
||||
}
|
||||
else if(matchOn('>'))
|
||||
{
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_SRIGHT_EQUAL, ">>=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_SRIGHT, ">>", line, column - 1});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{GREATER, token, line, column - static_cast<int>(token.length())});
|
||||
}
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token, line});
|
||||
}
|
||||
else if(t == '&')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('&');
|
||||
if(match) {
|
||||
tokens.push_back(Token{AND, "&&", line, column - 1});
|
||||
} else {
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_AND_EQUAL, "&=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_AND, "&", line, column - 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(t == '|')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('|');
|
||||
if(match) {
|
||||
tokens.push_back(Token{OR, "||", line, column - 1});
|
||||
} else {
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_OR_EQUAL, "|=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_OR, "|", line, column - 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(t == '^')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{BIN_XOR_EQUAL, "^=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_XOR, "^", line, column - 1});
|
||||
}
|
||||
token += match ? "&" : "";
|
||||
if(match) tokens.push_back(Token{AND, token, line});
|
||||
}
|
||||
else if(t == '/')
|
||||
{
|
||||
@ -217,35 +117,20 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
}
|
||||
else
|
||||
{
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{SLASH_EQUAL, "/=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{SLASH, "/", line, column - 1});
|
||||
}
|
||||
tokens.push_back(Token{SLASH, std::string(1, t), line});
|
||||
}
|
||||
}
|
||||
else if(t == '"')
|
||||
{
|
||||
bool last_was_escape = false;
|
||||
std::string str;
|
||||
int startColumn = column;
|
||||
advance();
|
||||
|
||||
while(!src.empty())
|
||||
while(!src.empty() && src[0] != '"')
|
||||
{
|
||||
std::string next_c = std::string(1, src[0]);
|
||||
if(next_c == "\"") break;
|
||||
if(next_c == "\\")
|
||||
{
|
||||
advance();
|
||||
next_c = "\\" + std::string(1, src[0]);
|
||||
}
|
||||
|
||||
str += next_c;
|
||||
if(src[0] == '\n') line++;
|
||||
str += src[0];
|
||||
advance();
|
||||
}
|
||||
|
||||
if(src.empty())
|
||||
{
|
||||
throw std::runtime_error("LEXER: Unterminated string at line: " + std::to_string(this->line));
|
||||
@ -254,16 +139,14 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
{
|
||||
|
||||
advance();
|
||||
|
||||
|
||||
std::string escaped_str = parseEscapeCharacters(str);
|
||||
tokens.push_back(Token{STRING, escaped_str, line, startColumn});
|
||||
tokens.push_back(Token{STRING, str, line});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if(t == '\n')
|
||||
{
|
||||
line++;
|
||||
advance();
|
||||
}
|
||||
else
|
||||
@ -275,7 +158,6 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
if(std::isdigit(t))
|
||||
{
|
||||
std::string num;
|
||||
int startColumn = column;
|
||||
|
||||
if(src[0] != '0') notationInvalidated = true;
|
||||
|
||||
@ -345,13 +227,12 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push_back(Token{NUMBER, num, line, startColumn});
|
||||
tokens.push_back(Token{NUMBER, num, line});
|
||||
}
|
||||
else if(std::isalpha(t))
|
||||
{
|
||||
std::string ident;
|
||||
int startColumn = column;
|
||||
while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_'))
|
||||
while(!src.empty() && std::isalpha(src[0]))
|
||||
{
|
||||
ident += src[0];
|
||||
advance();
|
||||
@ -359,11 +240,11 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
|
||||
if(KEYWORDS.find(ident) != KEYWORDS.end()) //identifier is a keyword
|
||||
{
|
||||
tokens.push_back(Token{KEYWORDS.at(ident), ident, line, startColumn});
|
||||
tokens.push_back(Token{KEYWORDS.at(ident), ident, line});
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{IDENTIFIER, ident, line, startColumn});
|
||||
tokens.push_back(Token{IDENTIFIER, ident, line});
|
||||
}
|
||||
|
||||
}
|
||||
@ -373,10 +254,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "Lexer Error",
|
||||
"Unknown token '" + std::string(1, t) + "'", "");
|
||||
}
|
||||
|
||||
throw std::runtime_error("LEXER: Unknown Token: '" + std::string(1, t) + "'");
|
||||
}
|
||||
|
||||
@ -399,27 +277,7 @@ bool Lexer::matchOn(char expected)
|
||||
void Lexer::advance(int by)
|
||||
{
|
||||
for (int i = 0; i < by; ++i) {
|
||||
if (!src.empty()) {
|
||||
char c = src[0];
|
||||
src.erase(src.begin());
|
||||
|
||||
// Update column and line counters
|
||||
if (c == '\n') {
|
||||
line++;
|
||||
column = 1;
|
||||
} else if (c == '\r') {
|
||||
// Handle \r\n sequence
|
||||
if (!src.empty() && src[0] == '\n') {
|
||||
src.erase(src.begin());
|
||||
line++;
|
||||
column = 1;
|
||||
} else {
|
||||
column++;
|
||||
}
|
||||
} else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
src.erase(src.begin());
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,41 +287,6 @@ char Lexer::peekNext()
|
||||
{
|
||||
return src[1];
|
||||
}
|
||||
|
||||
return '\0';
|
||||
}
|
||||
|
||||
std::string Lexer::parseEscapeCharacters(const std::string& input) {
|
||||
std::string output;
|
||||
bool escapeMode = false;
|
||||
|
||||
for (char c : input) {
|
||||
if (escapeMode) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
output += '\n';
|
||||
break;
|
||||
case 't':
|
||||
output += '\t';
|
||||
break;
|
||||
case '"':
|
||||
output += '\"';
|
||||
break;
|
||||
case '\\':
|
||||
output += '\\';
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("Invalid escape character: " + std::string(1, c));
|
||||
}
|
||||
escapeMode = false;
|
||||
} else if (c == '\\') {
|
||||
escapeMode = true;
|
||||
} else {
|
||||
output += c;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
// Created by Bobby Lucero on 5/26/23.
|
||||
//
|
||||
#include "../headers/Parser.h"
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
// Precedence
|
||||
@ -11,117 +10,7 @@
|
||||
|
||||
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 = logical_or();
|
||||
|
||||
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;
|
||||
return equality();
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::equality()
|
||||
@ -140,12 +29,12 @@ sptr(Expr) Parser::equality()
|
||||
|
||||
sptr(Expr) Parser::comparison()
|
||||
{
|
||||
sptr(Expr) expr = bitwise_or();
|
||||
sptr(Expr) expr = term();
|
||||
|
||||
while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = bitwise_or();
|
||||
sptr(Expr) right = term();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
@ -170,7 +59,7 @@ sptr(Expr) Parser::factor()
|
||||
{
|
||||
sptr(Expr) expr = unary();
|
||||
|
||||
while(match({SLASH, STAR, PERCENT}))
|
||||
while(match({SLASH, STAR}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = unary();
|
||||
@ -182,7 +71,7 @@ sptr(Expr) Parser::factor()
|
||||
|
||||
sptr(Expr) Parser::unary()
|
||||
{
|
||||
if(match({BANG, MINUS, BIN_NOT}))
|
||||
if(match({BANG, MINUS}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = unary();
|
||||
@ -194,225 +83,35 @@ sptr(Expr) Parser::unary()
|
||||
|
||||
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({FALSE})) return msptr(LiteralExpr)("true", false, false);
|
||||
if(match({TRUE})) return msptr(LiteralExpr)("true", false, false);
|
||||
if(match({NONE})) return msptr(LiteralExpr)("none", false, 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({NUMBER})) return msptr(LiteralExpr)(previous().lexeme, true, false);
|
||||
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false);
|
||||
|
||||
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() {
|
||||
sptr(Expr) Parser::parse() {
|
||||
|
||||
std::vector<sptr(Stmt)> statements;
|
||||
while(!isAtEnd())
|
||||
{
|
||||
statements.push_back(declaration());
|
||||
}
|
||||
|
||||
return statements;
|
||||
return expression();
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
bool Parser::match(std::vector<TokenType> types) {
|
||||
for(TokenType t : types)
|
||||
{
|
||||
if(check(t))
|
||||
@ -447,28 +146,10 @@ Token Parser::previous() {
|
||||
return tokens[current - 1];
|
||||
}
|
||||
|
||||
Token Parser::consume(TokenType type, const std::string& message) {
|
||||
Token Parser::consume(TokenType type, 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);
|
||||
throw std::runtime_error(peek().lexeme +": "+ message);
|
||||
}
|
||||
|
||||
void Parser::sync()
|
||||
|
||||
@ -1,241 +0,0 @@
|
||||
#include "../headers/StdLib.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include <chrono>
|
||||
|
||||
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
||||
// Create a built-in toString function
|
||||
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
return Value(interpreter.stringify(args[0]));
|
||||
});
|
||||
env->define("toString", Value(toStringFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(toStringFunc);
|
||||
|
||||
// Create a built-in print function
|
||||
auto printFunc = std::make_shared<BuiltinFunction>("print",
|
||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
// Use the interpreter's stringify function
|
||||
std::cout << interpreter.stringify(args[0]) << std::endl;
|
||||
return NONE_VALUE;
|
||||
});
|
||||
env->define("print", Value(printFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(printFunc);
|
||||
|
||||
// Create a built-in assert function
|
||||
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1 && args.size() != 2) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
// Simple truthy check without calling interpreter.isTruthy
|
||||
bool isTruthy = false;
|
||||
if (args[0].isBoolean()) {
|
||||
isTruthy = args[0].asBoolean();
|
||||
} else if (args[0].isNone()) {
|
||||
isTruthy = false;
|
||||
} else {
|
||||
isTruthy = true; // Numbers, strings, functions are truthy
|
||||
}
|
||||
|
||||
if (!isTruthy) {
|
||||
std::string message = "Assertion failed: condition is false";
|
||||
if (args.size() == 2) {
|
||||
if (args[1].isString()) {
|
||||
message += " - " + std::string(args[1].asString());
|
||||
}
|
||||
}
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
return NONE_VALUE;
|
||||
});
|
||||
env->define("assert", Value(assertFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(assertFunc);
|
||||
|
||||
// Create a built-in time function (returns microseconds since Unix epoch)
|
||||
auto timeFunc = std::make_shared<BuiltinFunction>("time",
|
||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 0) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
||||
|
||||
return Value(static_cast<double>(microseconds));
|
||||
});
|
||||
env->define("time", Value(timeFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(timeFunc);
|
||||
|
||||
// Create a built-in input function
|
||||
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() > 1) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
// Optional prompt
|
||||
if (args.size() == 1) {
|
||||
std::cout << interpreter.stringify(args[0]);
|
||||
}
|
||||
|
||||
// Get user input
|
||||
std::string userInput;
|
||||
std::getline(std::cin, userInput);
|
||||
|
||||
return Value(userInput);
|
||||
});
|
||||
env->define("input", Value(inputFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(inputFunc);
|
||||
|
||||
// Create a built-in type function
|
||||
auto typeFunc = std::make_shared<BuiltinFunction>("type",
|
||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
std::string typeName;
|
||||
if (args[0].isNumber()) {
|
||||
typeName = "number";
|
||||
} else if (args[0].isString()) {
|
||||
typeName = "string";
|
||||
} else if (args[0].isBoolean()) {
|
||||
typeName = "boolean";
|
||||
} else if (args[0].isNone()) {
|
||||
typeName = "none";
|
||||
} else if (args[0].isFunction()) {
|
||||
typeName = "function";
|
||||
} else if (args[0].isBuiltinFunction()) {
|
||||
typeName = "builtin_function";
|
||||
} else {
|
||||
typeName = "unknown";
|
||||
}
|
||||
|
||||
return Value(typeName);
|
||||
});
|
||||
env->define("type", Value(typeFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(typeFunc);
|
||||
|
||||
// Create a built-in toNumber function for string-to-number conversion
|
||||
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
|
||||
[](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1) {
|
||||
return NONE_VALUE; // Return none for wrong argument count
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
return NONE_VALUE; // Return none for wrong type
|
||||
}
|
||||
|
||||
std::string str = args[0].asString();
|
||||
|
||||
// Remove leading/trailing whitespace
|
||||
str.erase(0, str.find_first_not_of(" \t\n\r"));
|
||||
str.erase(str.find_last_not_of(" \t\n\r") + 1);
|
||||
|
||||
if (str.empty()) {
|
||||
return NONE_VALUE; // Return none for empty string
|
||||
}
|
||||
|
||||
try {
|
||||
double value = std::stod(str);
|
||||
return Value(value);
|
||||
} catch (const std::invalid_argument&) {
|
||||
return NONE_VALUE; // Return none for invalid conversion
|
||||
} catch (const std::out_of_range&) {
|
||||
return NONE_VALUE; // Return none for out of range
|
||||
}
|
||||
});
|
||||
env->define("toNumber", Value(toNumberFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(toNumberFunc);
|
||||
|
||||
// Create a built-in toBoolean function for explicit boolean conversion
|
||||
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
|
||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
// Use the same logic as isTruthy() for consistency
|
||||
Value value = args[0];
|
||||
|
||||
if (value.isNone()) {
|
||||
return Value(false);
|
||||
}
|
||||
|
||||
if (value.isBoolean()) {
|
||||
return value; // Already a boolean
|
||||
}
|
||||
|
||||
if (value.isNumber()) {
|
||||
return Value(value.asNumber() != 0.0);
|
||||
}
|
||||
|
||||
if (value.isString()) {
|
||||
return Value(!value.asString().empty());
|
||||
}
|
||||
|
||||
// For any other type (functions, etc.), consider them truthy
|
||||
return Value(true);
|
||||
});
|
||||
env->define("toBoolean", Value(toBooleanFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(toBooleanFunc);
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
#include "../headers/Value.h"
|
||||
|
||||
// Global constants for common values (no heap allocation)
|
||||
const Value NONE_VALUE = Value();
|
||||
const Value TRUE_VALUE = Value(true);
|
||||
const Value FALSE_VALUE = Value(false);
|
||||
const Value ZERO_VALUE = Value(0.0);
|
||||
const Value ONE_VALUE = Value(1.0);
|
||||
@ -1,15 +1,13 @@
|
||||
#include <utility>
|
||||
|
||||
#include "../headers/bob.h"
|
||||
#include "../headers/Parser.h"
|
||||
#include "../headers/ASTPrinter.h"
|
||||
using namespace std;
|
||||
|
||||
void Bob::runFile(const string& path)
|
||||
void Bob::runFile(string path)
|
||||
{
|
||||
this->interpreter = msptr(Interpreter)(false);
|
||||
ifstream file = ifstream(path);
|
||||
|
||||
string source;
|
||||
string source = "";
|
||||
|
||||
if(file.is_open()){
|
||||
source = string(istreambuf_iterator<char>(file), istreambuf_iterator<char>());
|
||||
@ -20,19 +18,11 @@ void Bob::runFile(const string& path)
|
||||
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(;;)
|
||||
{
|
||||
@ -45,49 +35,47 @@ void Bob::runPrompt()
|
||||
break;
|
||||
}
|
||||
|
||||
// Load source code into error reporter for context
|
||||
errorReporter.loadSource(line, "REPL");
|
||||
|
||||
// Connect error reporter to interpreter
|
||||
interpreter->setErrorReporter(&errorReporter);
|
||||
|
||||
this->run(line);
|
||||
hadError = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Bob::error(int line, string message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Bob::run(string source)
|
||||
{
|
||||
try {
|
||||
// Connect error reporter to lexer
|
||||
lexer.setErrorReporter(&errorReporter);
|
||||
|
||||
vector<Token> tokens = lexer.Tokenize(std::move(source));
|
||||
vector<Token> tokens = lexer.Tokenize(source);
|
||||
Parser p(tokens);
|
||||
|
||||
// Connect error reporter to parser
|
||||
p.setErrorReporter(&errorReporter);
|
||||
|
||||
vector<sptr(Stmt)> statements = p.parse();
|
||||
interpreter->interpret(statements);
|
||||
shared_ptr<Expr> expr = p.parse();
|
||||
interpreter.interpret(expr);
|
||||
//cout << "=========================" << endl;
|
||||
ASTPrinter printer;
|
||||
|
||||
//cout << dynamic_pointer_cast<String>(printer.print(expr))->value << endl;
|
||||
|
||||
for(Token t : tokens){
|
||||
//cout << "{type: " << t.type << ", value: " << t.lexeme << "}" << endl;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
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");
|
||||
cout << "ERROR OCCURRED: " << e.what() << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Bob::report(int line, string where, string message)
|
||||
{
|
||||
hadError = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
//
|
||||
// Created by Bobby Lucero on 5/21/23.
|
||||
//
|
||||
|
||||
|
||||
#include "../headers/bob.h"
|
||||
|
||||
int main(int argc, char* argv[]){
|
||||
int main(){
|
||||
Bob bobLang;
|
||||
|
||||
if(argc > 1) {
|
||||
bobLang.runFile(argv[1]);
|
||||
} else {
|
||||
bobLang.runPrompt();
|
||||
}
|
||||
//bobLang.runFile("source.bob");
|
||||
bobLang.runPrompt();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
- binary number interpretation broken (0b will throw error, but 0ba will not)
|
||||
File diff suppressed because it is too large
Load Diff
15
test_fib.bob
15
test_fib.bob
@ -1,15 +0,0 @@
|
||||
func fib(n) {
|
||||
if (n <= 1) {
|
||||
return n;
|
||||
}
|
||||
//print("Current operation: " + (n - 1) + ":" + (n-2));
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
print("Fibonacci test:");
|
||||
var fib_result = fib(30);
|
||||
|
||||
print("Result: " + fib_result);
|
||||
|
||||
|
||||
print(10 / 0);
|
||||
51
testthing
Normal file
51
testthing
Normal file
@ -0,0 +1,51 @@
|
||||
template <typename T>
|
||||
struct BinaryExpr : Expr<T>, Visitor<T>
|
||||
{
|
||||
const Expr<T> left;
|
||||
const Token oper;
|
||||
const Expr<T> right;
|
||||
|
||||
BinaryExpr(Expr<T> left, Token oper, Expr<T> right) : left(left), oper(oper), right(right)
|
||||
{
|
||||
}
|
||||
T accept(Visitor<T> visitor){
|
||||
return visitor.visitBinaryExpr(this);
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct GroupingExpr : Expr<T>, Visitor<T>
|
||||
{
|
||||
const Expr<T> expression;
|
||||
|
||||
GroupingExpr(Expr<T> expression) : expression(expression)
|
||||
{
|
||||
}
|
||||
T accept(Visitor<T> visitor){
|
||||
return visitor.visitGroupingExpr(this);
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct LiteralExpr : Expr<T>, Visitor<T>
|
||||
{
|
||||
const std::string value;
|
||||
|
||||
LiteralExpr(std::string value) : value(value)
|
||||
{
|
||||
}
|
||||
T accept(Visitor<T> visitor){
|
||||
return visitor.visitLiteralExpr(this);
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct UnaryExpr : Expr<T>, Visitor<T>
|
||||
{
|
||||
const Token oper;
|
||||
const Expr<T> right;
|
||||
|
||||
UnaryExpr(Token oper, Expr<T> right) : oper(oper), right(right)
|
||||
{
|
||||
}
|
||||
T accept(Visitor<T> visitor){
|
||||
return visitor.visitUnaryExpr(this);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user