Compare commits

...

17 Commits

Author SHA1 Message Date
72a31b28af Refactor to ExecutionContext pattern and fix string + none concatenation
- Add none value handling to Value::operator+ for string concatenation
- Replace direct string concatenations with Value::operator+ calls in Interpreter
- Add missing string+none and none+string type combinations
- Standardize token literal generation in Lexer
- Add ExecutionContext support across visitor pattern
- Enhance error reporting integration
- Add new standard library functions
2025-08-05 00:49:59 -04:00
adb00d496f Fixed performance, added enhanced error reporting, anon funcs, toBoolean, various other things 2025-08-01 13:43:35 -04:00
1e65b344ae Major speed optimization
- Replace Object* with Value tagged union for better performance
- Fix bug where "true"/"false" strings were treated as booleans
- Add isBoolean field to LiteralExpr to distinguish string vs boolean literals
- Implement fast function calls with g_returnContext instead of exceptions
- Add functions vector to prevent dangling pointers
- Remove try-catch blocks from execute() for 50x performance improvement
- Clean up test files, keep only main test suite and fib benchmark
- All 38 tests passing, fib(30) still ~848ms
2025-07-31 00:16:54 -04:00
cb14221429 Added if statements, more stdlib functions 2025-07-30 19:31:29 -04:00
7c57a9a111 Implement functions, closures, standard library, and comprehensive number system
- Add function declarations, calls, and return statements
- Implement lexical scoping with Environment class and closures
- Convert print from statement to standard library function
- Add assert() function to standard library for testing
- Add time() function for microsecond precision benchmarking
- Create StdLib class and BuiltinFunction wrapper for standard library
- Implement first-class functions and higher-order functions
- Add function parameter support (tested up to 100 parameters)
- Support alphanumeric identifiers in variable and function names
- Add underscore support in variable names and identifiers
- Implement string + number and number + string concatenation
- Add boolean + string and string + boolean concatenation
- Support string multiplication (string * number)
- Fix decimal truncation issue by using std::stod for all number parsing
- Add comprehensive number formatting with proper precision handling
- Support huge numbers (epoch timestamps) without integer overflow
- Clean number display (no trailing zeros on integers)
- Add basic error handling with program termination on errors
- Add comprehensive test suite covering all features
- Add escape sequence support (\n, \t, \", \\)
- Add comprehensive documentation and language reference
- Update development roadmap with completed features
2025-07-30 17:51:48 -04:00
Bobby Lucero
8258df216e Added simple escape character support 2023-06-02 14:43:33 -04:00
Bobby Lucero
942c1e323b Tidy code, interactive interpreter prints out result of expression without explicit print statement 2023-06-01 17:19:19 -04:00
Bobby Lucero
fe26a7c2e0 Added scopes 2023-05-31 02:44:39 -04:00
Bobby Lucero
7e0cead697 Added variable assignments 2023-05-31 01:48:50 -04:00
Bobby Lucero
03bfae9eb4 techdebt tracking for future me to handle 2023-05-30 16:31:28 -04:00
31b334ec65 Bug fixes 2023-05-28 15:13:59 -04:00
b38f6bff25 Fixed header duplicate symbols 2023-05-27 21:18:53 -04:00
92cd4e542a Binary and hex notation 2023-05-27 20:12:30 -04:00
3e5ba29283 It's alive! (implemented base interpreter) 2023-05-27 16:04:39 -04:00
9500cf9773 Removed templating as it turns out, wasn't needed. Added shorthands to make the code less ugly 2023-05-27 13:11:08 -04:00
a0ec4af169 ASTPrinter now prints generated AST from parser 2023-05-27 02:05:06 -04:00
fc0d9137a5 Initial Parser 2023-05-27 01:20:38 -04:00
38 changed files with 5811 additions and 349 deletions

View File

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

6
.idea/misc.xml generated
View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CidrRootsConfiguration">
<sourceRoots>
<file path="$PROJECT_DIR$/headers" />
<file path="$PROJECT_DIR$/source" />
</sourceRoots>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MakefileSettings">
<option name="linkedExternalProjectsSettings">

361
BOB_LANGUAGE_REFERENCE.md Normal file
View File

@ -0,0 +1,361 @@
# Bob Language Reference
## Overview
Bob is a dynamically typed programming language with a focus on simplicity and expressiveness. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages.
## Table of Contents
1. [Data Types](#data-types)
2. [Variables](#variables)
3. [Operators](#operators)
4. [Functions](#functions)
5. [Control Flow](#control-flow)
6. [Standard Library](#standard-library)
7. [Error Handling](#error-handling)
8. [Examples](#examples)
9. [Language Nuances](#language-nuances)
## Data Types
### Numbers
- **Integers**: `42`, `-10`, `0`
- **Floats**: `3.14`, `2.718`, `-1.5`
- **Automatic conversion**: Numbers are stored as doubles internally
### Strings
- **Literal strings**: `"Hello, World!"`
- **Empty strings**: `""`
- **Escape sequences**: Not currently supported
### Booleans
- **True**: `true`
- **False**: `false`
### None
- **Null value**: `none` (represents absence of value)
## Variables
### Declaration
```bob
var name = "Bob";
var age = 25;
var isActive = true;
```
### Assignment
```bob
var x = 10;
x = 20; // Reassignment
```
### Scoping
- **Global scope**: Variables declared at top level
- **Local scope**: Variables declared inside functions
- **Shadowing**: Local variables can shadow global variables
- **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration
### Variable Behavior
```bob
var globalVar = 100;
func testScope() {
var localVar = 50; // Local variable
return localVar + globalVar; // Can access global
}
```
## Operators
### Arithmetic Operators
- **Addition**: `+`
- **Subtraction**: `-`
- **Multiplication**: `*`
- **Division**: `/`
- **Modulo**: `%`
### Comparison Operators
- **Equal**: `==`
- **Not equal**: `!=`
- **Greater than**: `>`
- **Less than**: `<`
- **Greater than or equal**: `>=`
- **Less than or equal**: `<=`
### String Operators
#### Concatenation
Bob supports bidirectional string + number concatenation with automatic type conversion:
```bob
// String + Number
"Hello " + 42; // → "Hello 42"
"Pi: " + 3.14; // → "Pi: 3.14"
// Number + String
42 + " items"; // → "42 items"
3.14 + " is pi"; // → "3.14 is pi"
```
#### String Multiplication
```bob
"hello" * 3; // → "hellohellohello"
3 * "hello"; // → "hellohellohello"
```
**Note**: String multiplication requires whole numbers.
### Number Formatting
Bob automatically formats numbers to show only significant digits:
```bob
"Count: " + 2.0; // → "Count: 2" (no trailing zeros)
"Pi: " + 3.14; // → "Pi: 3.14" (exact precision)
"Integer: " + 42; // → "Integer: 42" (no decimal)
```
## Functions
### Function Declaration
```bob
func add(a, b) {
return a + b;
}
```
### Function Call
```bob
var result = add(2, 3); // result = 5
```
### Parameters
- **Any number of parameters** supported
- **No default parameters** (not implemented)
- **No keyword arguments** (not implemented)
### Return Values
- **Explicit return**: `return value;`
- **Implicit return**: Functions return `none` if no return statement
- **Early return**: Functions can return from anywhere
### Closures
Bob supports lexical closures with variable capture:
```bob
var outerVar = "Outer";
func makeGreeter(greeting) {
return greeting + " " + outerVar;
}
var greeter = makeGreeter("Hello");
// greeter captures outerVar from its lexical scope
```
### Nested Functions
```bob
func outer() {
func inner() {
return 42;
}
return inner();
}
```
## Control Flow
### Current Status
**Control flow statements are NOT implemented yet:**
- `if` statements
- `while` loops
- `for` loops
- `else` clauses
### Planned Features
- Conditional execution
- Looping constructs
- Logical operators (`and`, `or`, `not`)
## Standard Library
### Print Function
```bob
print("Hello, World!");
print(42);
print(true);
```
**Usage**: `print(expression)`
- Prints the result of any expression
- Automatically converts values to strings
- Adds newline after output
### Assert Function
```bob
assert(condition, "optional message");
```
**Usage**:
- `assert(true)` - passes silently
- `assert(false)` - throws error and stops execution
- `assert(condition, "message")` - includes custom error message
**Behavior**:
- Terminates program execution on failure
- No exception handling mechanism (yet)
- Useful for testing and validation
## Error Handling
### Current Error Types
- **Division by zero**: `DivisionByZeroError`
- **Type errors**: `Operands must be of same type`
- **String multiplication**: `String multiplier must be whole number`
- **Assertion failures**: `Assertion failed: condition is false`
### Error Behavior
- **No try-catch**: Exception handling not implemented
- **Program termination**: Errors stop execution immediately
- **Error messages**: Descriptive error messages printed to console
### Common Error Scenarios
```bob
// Division by zero
10 / 0; // Error: DivisionByZeroError
// Type mismatch
"hello" - "world"; // Error: Cannot use '-' on two strings
// Invalid string multiplication
"hello" * 3.5; // Error: String multiplier must be whole number
// Undefined variable
undefinedVar; // Error: Undefined variable
```
## Examples
### Basic Calculator
```bob
func add(a, b) {
return a + b;
}
func multiply(a, b) {
return a * b;
}
var result = add(5, multiply(3, 4));
print("Result: " + result); // Result: 17
```
### String Processing
```bob
func greet(name) {
return "Hello, " + name + "!";
}
func repeat(str, count) {
return str * count;
}
var greeting = greet("Bob");
var repeated = repeat("Ha", 3);
print(greeting + " " + repeated); // Hello, Bob! HaHaHa
```
### Variable Scoping Example
```bob
var globalCounter = 0;
func increment() {
var localCounter = 1;
globalCounter = globalCounter + localCounter;
return globalCounter;
}
print("Before: " + globalCounter); // Before: 0
increment();
print("After: " + globalCounter); // After: 1
```
## Language Nuances
### Type System
- **Dynamic typing**: Variables can hold any type
- **Automatic conversion**: Numbers and strings convert automatically
- **No type annotations**: Types are inferred at runtime
### Memory Management
- **Reference counting**: Uses `std::shared_ptr` for automatic memory management
- **No manual memory management**: No `delete` or `free` needed
- **Garbage collection**: Automatic cleanup of unused objects
### Performance Characteristics
- **Interpreted**: Code is executed by an interpreter
- **AST-based**: Abstract Syntax Tree for execution
- **No compilation**: Direct interpretation of source code
### Syntax Rules
- **Semicolons**: Required at end of statements
- **Parentheses**: Required for function calls
- **Curly braces**: Required for function bodies
- **Case sensitive**: `var` and `Var` are different
### Comparison with Other Languages
#### vs Python
- **Similar**: Dynamic typing, functions, closures
- **Different**: No `global` keyword, automatic string conversion, different error handling
#### vs JavaScript
- **Similar**: Automatic type conversion, string concatenation
- **Different**: No `undefined`, different syntax, no `null`
#### vs Lua
- **Similar**: Dynamic typing, functions
- **Different**: No `local` keyword, different scoping rules
### Limitations
- **No control flow**: No if/while/for statements
- **No logical operators**: No `and`/`or`/`not`
- **No exception handling**: No try-catch blocks
- **No modules**: No import/export system
- **No classes**: No object-oriented features
- **No arrays/lists**: No built-in collection types
- **No dictionaries**: No key-value data structures
### Future Features
- Control flow statements
- Logical operators
- Exception handling
- Collection types (arrays, dictionaries)
- Modules and imports
- Object-oriented programming
- Standard library expansion
## Getting Started
### Running Bob Code
```bash
# Compile the interpreter
make
# Run a Bob file
./build/bob your_file.bob
# Run the comprehensive test suite
./build/bob test_bob_language.bob
```
### File Extension
- **`.bob`**: Standard file extension for Bob source code
### Interactive Mode
- **Not implemented**: No REPL (Read-Eval-Print Loop) yet
- **File-based**: All code must be in `.bob` files
---
*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.*

View File

@ -4,7 +4,7 @@
CC = g++
# Compiler flags
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
# 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: clean $(BUILD_DIR)/bob
all: build
# Rule to create necessary directories
$(DIRS):
@ -37,8 +37,12 @@ $(BUILD_DIR)/bob: $(OBJ_FILES)
$(CC) $(CFLAGS) $^ -o $@
run:
./$(BUILD_DIR)/bob
build: clean $(BUILD_DIR)/bob
# Clean build directory
clean:
rm -rf $(BUILD_DIR)/*

203
ROADMAP.md Normal file
View File

@ -0,0 +1,203 @@
# Bob Language Development Roadmap
## Current Status
- Basic expressions (arithmetic, comparison, logical)
- Variables and assignment
- Print statements (converted to standard library function)
- Block statements
- Environment/scoping
- Function implementation (COMPLETED)
- Return statements (COMPLETED)
- Closures (COMPLETED)
- Assert function (COMPLETED)
- Standard library infrastructure (COMPLETED)
- First-class functions and higher-order functions (COMPLETED)
- String + number concatenation with smart formatting (COMPLETED)
- String multiplication (COMPLETED)
- Alphanumeric identifiers (COMPLETED)
- Comprehensive testing framework (COMPLETED)
## Phase 1: Core Language Features (High Priority)
### 1. Control Flow
```bob
// If statements
if (x > 10) {
print "big";
} else {
print "small";
}
// While loops
var i = 0;
while (i < 5) {
print i;
i = i + 1;
}
```
**Implementation:**
- Add `IfStmt` and `WhileStmt` to Statement.h
- Update parser to handle `if` and `while` keywords
- Implement control flow in interpreter
### 2. Logical Operators
```bob
// Currently missing: and, or, not operators
if (x > 0 and y < 10) {
print "valid range";
}
```
**Implementation:**
- Add `and`, `or`, `not` operator parsing
- Implement logical operator evaluation in interpreter
### 3. Better Error Handling
```bob
// Current: Basic error messages
// Goal: Line numbers, better context
Error at line 5: Expected ';' after expression
print 42
^
```
## Phase 2: Data Structures (Medium Priority)
### 4. Arrays/Lists
```bob
var numbers = [1, 2, 3, 4];
print numbers[0]; // 1
numbers[1] = 42;
```
### 5. Maps/Dictionaries
```bob
var person = {"name": "Bob", "age": 25};
print person["name"];
person["city"] = "NYC";
```
## Phase 3: Standard Library (Medium Priority)
### 6. Additional Built-in Functions
```bob
len("hello"); // String length
input("Enter name: "); // User input
random(1, 100); // Random numbers
type(42); // Type checking
```
### 7. File I/O
```bob
var content = readFile("data.txt");
writeFile("output.txt", "Hello World");
```
## Phase 4: Advanced Features (Lower Priority)
### 8. Classes & Objects
```bob
class Person {
init(name, age) {
this.name = name;
this.age = age;
}
greet() {
print "Hello, I'm " + this.name;
}
}
var bob = Person("Bob", 25);
bob.greet();
```
### 9. Modules/Imports
```bob
import "math.bob";
import "utils.bob";
```
### 10. Type System
```bob
// Optional type annotations
fun add(a: number, b: number): number {
return a + b;
}
```
## Implementation Tips
### For Each Feature:
1. Lexer: Add new tokens if needed
2. Parser: Add new expression/statement types
3. AST: Define new node types
4. Interpreter: Implement evaluation logic
5. Test: Create test cases using assert function
### Testing Strategy:
```bob
// Use the new assert function for comprehensive testing
assert(add(2, 3) == 5, "add(2, 3) should equal 5");
assert(x > 0, "x should be positive");
```
## Recommended Next Steps
1. Add if statements (fundamental control flow)
2. Add while loops (enables iteration)
3. Implement logical operators (and, or, not)
4. Improve error messages (better developer experience)
5. Add arrays (most useful data structure)
## Success Metrics
- [x] Can write simple functions
- [x] Can use return statements
- [x] Can use closures
- [x] Has assert function for testing
- [x] Has standard library infrastructure
- [x] Supports first-class functions
- [x] Has comprehensive testing framework
- [ ] Can use if/else statements
- [ ] Can use while loops
- [ ] Can use logical operators
- [ ] Can work with arrays
- [ ] Can read/write files
- [ ] Has good error messages
## Resources
- [Crafting Interpreters](https://craftinginterpreters.com/) - Excellent resource for language implementation
- [Bob's current source code](./source/) - Your implementation
- [Test files](./*.bob) - Examples of current functionality
## Recent Achievements
### Function Implementation (COMPLETED)
- Function declarations with parameters
- Function calls with arguments
- Return statements
- Proper scoping and closures
- Nested function calls
### Standard Library (COMPLETED)
- `print()` function (converted from statement)
- `assert()` function with custom messages
- Extensible architecture for adding more functions
### Testing Framework (COMPLETED)
- Comprehensive test suite using assert
- Tests for all language features
- Proper error handling and execution stopping
### Advanced Language Features (COMPLETED)
- First-class functions and higher-order functions
- Function passing as arguments
- Function composition patterns
- Callback patterns and function storage
- String + number concatenation with smart formatting
- String multiplication (string * number, number * string)
- Alphanumeric identifiers support
- Stress testing with 100-parameter functions

30
benchmark.py Normal file
View File

@ -0,0 +1,30 @@
import time
# Test the time function
print("Testing time function:")
time1 = time.time()
print(f"Time 1: {time1}")
time2 = time.time()
print(f"Time 2: {time2}")
time3 = time.time()
print(f"Time 3: {time3}")
diff1 = time2 - time1
diff2 = time3 - time2
print(f"Difference 1-2: {diff1} seconds")
print(f"Difference 2-3: {diff2} seconds")
# Test with some work in between
start = time.time()
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
end = time.time()
duration = end - start
print(f"Work duration: {duration} seconds")
print(f"Sum: {sum_val}")
print("Time function analysis complete!")

View File

@ -1,18 +0,0 @@
#pragma once
#include "Expression.h"
#include <string>
#include <initializer_list>
class ASTPrinter : public Visitor<std::string>
{
std::string visitBinaryExpr(BinaryExpr<std::string>* expression) override;
std::string visitGroupingExpr(GroupingExpr<std::string>* expression) override;
std::string visitLiteralExpr(LiteralExpr<std::string>* expression) override;
std::string visitUnaryExpr(UnaryExpr<std::string>* expression) override;
public:
int test = 10;
std::string print(Expr<std::string>* expr);
private:
std::string parenthesize(std::string name, std::vector<std::shared_ptr<Expr<std::string>>> exprs);
};

48
headers/Environment.h Normal file
View File

@ -0,0 +1,48 @@
#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(); }
// Set parent environment for TCO environment reuse
inline void setParent(std::shared_ptr<Environment> newParent) {
parent = newParent;
}
private:
std::unordered_map<std::string, Value> variables;
std::shared_ptr<Environment> parent;
ErrorReporter* errorReporter;
};

63
headers/ErrorReporter.h Normal file
View File

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

View File

@ -3,76 +3,152 @@
//
#pragma once
#include "Lexer.h"
#include <iostream>
#include <memory>
#include "Lexer.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Value.h"
template <typename T>
struct Visitor;
// Forward declarations
struct FunctionExpr;
struct IncrementExpr;
struct ExprVisitor;
template <typename T>
struct Expr{
virtual T accept(Visitor<T>* visitor) = 0;
virtual ~Expr(){}
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
{
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 visitIncrementExpr(const std::shared_ptr<IncrementExpr>& 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;
};
template <typename T>
struct BinaryExpr : Expr<T>
struct Expr : public std::enable_shared_from_this<Expr> {
virtual Value accept(ExprVisitor* visitor) = 0;
virtual ~Expr() = default;
};
struct AssignExpr : Expr
{
const std::shared_ptr<Expr<T>> left;
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
{
std::shared_ptr<Expr> left;
const Token oper;
const std::shared_ptr<Expr<T>> right;
std::shared_ptr<Expr> right;
BinaryExpr(std::shared_ptr<Expr<T>> left, Token oper, std::shared_ptr<Expr<T>> right) : left(left), oper(oper), right(right)
{
}
T accept(Visitor<T>* visitor) override{
return visitor->visitBinaryExpr(this);
}
};
template <typename T>
struct GroupingExpr : Expr<T>
{
const std::shared_ptr<Expr<T>> expression;
GroupingExpr(std::shared_ptr<Expr<T>> expression) : expression(expression)
{
}
T accept(Visitor<T>* visitor) override{
return visitor->visitGroupingExpr(this);
}
};
template <typename T>
struct LiteralExpr : Expr<T>
{
const std::string value;
const bool isNumber;
LiteralExpr(std::string value, bool isNumber) : value(value), isNumber(isNumber)
{
}
T accept(Visitor<T>* visitor) override{
return visitor->visitLiteralExpr(this);
}
};
template <typename T>
struct UnaryExpr : Expr<T>
{
const Token oper;
const std::shared_ptr<Expr<T>> right;
UnaryExpr(Token oper, std::shared_ptr<Expr<T>> right) : oper(oper), right(right)
{
}
T accept(Visitor<T>* visitor) override{
return visitor->visitUnaryExpr(this);
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()));
}
};
////
template <typename T>
struct Visitor
struct GroupingExpr : Expr
{
virtual T visitBinaryExpr(BinaryExpr<T>* expression) = 0;
virtual T visitGroupingExpr(GroupingExpr<T>* expression) = 0;
virtual T visitLiteralExpr(LiteralExpr<T>* expression) = 0;
virtual T visitUnaryExpr(UnaryExpr<T>* expression) = 0;
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
{
return visitor->visitVarExpr(std::static_pointer_cast<VarExpr>(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
{
std::shared_ptr<Expr> callee;
Token paren;
std::vector<std::shared_ptr<Expr>> arguments;
bool isTailCall = false; // Flag for tail call optimization
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
{
return visitor->visitCallExpr(std::static_pointer_cast<CallExpr>(shared_from_this()));
}
};
struct IncrementExpr : Expr
{
std::shared_ptr<Expr> operand;
Token oper;
bool isPrefix; // true for ++x, false for x++
IncrementExpr(std::shared_ptr<Expr> operand, Token oper, bool isPrefix)
: operand(operand), oper(oper), isPrefix(isPrefix) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitIncrementExpr(std::static_pointer_cast<IncrementExpr>(shared_from_this()));
}
};

72
headers/Interpreter.h Normal file
View File

@ -0,0 +1,72 @@
#pragma once
#include "Expression.h"
#include "Statement.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Environment.h"
#include "Value.h"
#include "StdLib.h"
#include "ErrorReporter.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include <stack>
class Interpreter : public ExprVisitor, public StmtVisitor {
public:
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
void interpret(std::vector<std::shared_ptr<Stmt> > statements);
explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){
environment = std::make_shared<Environment>();
}
virtual ~Interpreter() = default;
private:
std::shared_ptr<Environment> environment;
bool IsInteractive;
std::vector<std::shared_ptr<BuiltinFunction> > builtinFunctions;
std::vector<std::shared_ptr<Function> > functions;
ErrorReporter* errorReporter;
Value evaluate(const std::shared_ptr<Expr>& expr);
bool isEqual(Value a, Value b);
bool isWholeNumer(double num);
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
void addStdLibFunctions();
public:
bool isTruthy(Value object);
std::string stringify(Value object);
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
// Error reporting
void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
if (environment) {
environment->setErrorReporter(reporter);
}
// Add standard library functions after error reporter is set
addStdLibFunctions();
}
};

View File

@ -6,23 +6,57 @@
enum TokenType{
OPEN_PAREN, CLOSE_PAREN, OPEN_BRACE, CLOSE_BRACE,
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR,
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, PERCENT,
BINARY_OP,
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,
// Increment/decrement operators
PLUS_PLUS, MINUS_MINUS,
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
};
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",
"PLUS_PLUS", "MINUS_MINUS",
"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},
@ -38,25 +72,44 @@ 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);
void advance();
char peekNext();
void advance(int by = 1);
std::string parseEscapeCharacters(const std::string &input);
};

81
headers/Parser.h Normal file
View File

@ -0,0 +1,81 @@
#pragma once
#include <utility>
#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; }
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();
sptr(Expr) factor();
sptr(Expr) unary();
sptr(Expr) primary();
bool match(const 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();
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();
sptr(Expr) increment(); // Parse increment/decrement expressions
sptr(Expr) postfix(); // Parse postfix operators
std::vector<std::shared_ptr<Stmt>> block();
sptr(Expr) finishCall(sptr(Expr) callee);
// Helper methods for function scope tracking
void enterFunction() { functionDepth++; }
void exitFunction() { functionDepth--; }
bool isInFunction() const { return functionDepth > 0; }
// Helper method for tail call detection
bool isTailCall(const std::shared_ptr<Expr>& expr);
};

120
headers/Statement.h Normal file
View File

@ -0,0 +1,120 @@
#pragma once
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Expression.h"
struct ExpressionStmt;
struct VarStmt;
struct BlockStmt;
struct FunctionStmt;
struct ReturnStmt;
struct IfStmt;
struct ExecutionContext {
bool isFunctionBody = false;
bool hasReturn = false;
Value returnValue;
};
struct StmtVisitor
{
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
};
struct Stmt : public std::enable_shared_from_this<Stmt>
{
std::shared_ptr<Expr> expression;
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
virtual ~Stmt(){};
};
struct BlockStmt : Stmt
{
std::vector<std::shared_ptr<Stmt> > statements;
explicit BlockStmt(std::vector<std::shared_ptr<Stmt> > statements) : statements(statements)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
}
};
struct ExpressionStmt : Stmt
{
std::shared_ptr<Expr> expression;
explicit ExpressionStmt(std::shared_ptr<Expr> expression) : expression(expression)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
}
};
struct VarStmt : Stmt
{
Token name;
std::shared_ptr<Expr> initializer;
VarStmt(Token name, std::shared_ptr<Expr> initializer) : name(name), initializer(initializer)
{
}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
}
};
struct FunctionStmt : Stmt
{
const Token name;
const std::vector<Token> params;
std::vector<std::shared_ptr<Stmt> > body;
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt> > body)
: name(name), params(params), body(body) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
}
};
struct ReturnStmt : Stmt
{
const Token keyword;
std::shared_ptr<Expr> value;
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
}
};
struct IfStmt : Stmt
{
std::shared_ptr<Expr> condition;
std::shared_ptr<Stmt> thenBranch;
std::shared_ptr<Stmt> elseBranch;
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
{
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
}
};

13
headers/StdLib.h Normal file
View File

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

View File

@ -1,18 +1,65 @@
#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(){};
};
struct Number : public Object
struct Number : Object
{
double value;
explicit Number(double value) : value(value) {}
};
struct String : public Object
struct String : Object
{
std::string value;
explicit String(std::string str) : value(str) {}
~String(){
}
};
struct Boolean : Object
{
bool value;
explicit Boolean(bool value) : value(value) {}
};
struct None : public Object
{
};
struct Function : public Object
{
const std::string name;
const std::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) {}
};

274
headers/Value.h Normal file
View File

@ -0,0 +1,274 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <cmath>
#include <stdexcept>
#include <algorithm>
// Forward declarations
class Environment;
class Function;
class BuiltinFunction;
// Type tags for the Value union
enum ValueType : uint8_t {
VAL_NONE,
VAL_NUMBER,
VAL_BOOLEAN,
VAL_STRING,
VAL_FUNCTION,
VAL_BUILTIN_FUNCTION
};
// Tagged value system (like Lua) - no heap allocation for simple values
struct Value {
union {
double number;
bool boolean;
Function* function;
BuiltinFunction* builtin_function;
};
ValueType type;
std::string string_value; // Store strings outside the union for safety
// Constructors
Value() : number(0.0), type(ValueType::VAL_NONE) {}
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
// Move constructor
Value(Value&& other) noexcept
: type(other.type), string_value(std::move(other.string_value)) {
if (type != ValueType::VAL_STRING) {
number = other.number; // Copy the union
}
other.type = ValueType::VAL_NONE;
}
// Move assignment
Value& operator=(Value&& other) noexcept {
if (this != &other) {
type = other.type;
if (type == ValueType::VAL_STRING) {
string_value = std::move(other.string_value);
} else {
number = other.number; // Copy the union
}
other.type = ValueType::VAL_NONE;
}
return *this;
}
// Copy constructor (only when needed)
Value(const Value& other) : type(other.type) {
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else {
number = other.number; // Copy the union
}
}
// Copy assignment (only when needed)
Value& operator=(const Value& other) {
if (this != &other) {
type = other.type;
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else {
number = other.number; // Copy the union
}
}
return *this;
}
// Type checking (fast, no dynamic casting) - inline for performance
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
inline bool isString() const { return type == ValueType::VAL_STRING; }
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
inline bool isNone() const { return type == ValueType::VAL_NONE; }
// Value extraction (safe, with type checking) - inline for performance
inline double asNumber() const { return isNumber() ? number : 0.0; }
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
inline const std::string& asString() const { return string_value; }
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
// Truthiness check - inline for performance
inline bool isTruthy() const {
switch (type) {
case ValueType::VAL_NONE: return false;
case ValueType::VAL_BOOLEAN: return boolean;
case ValueType::VAL_NUMBER: return number != 0.0;
case ValueType::VAL_STRING: return !string_value.empty();
case ValueType::VAL_FUNCTION: return function != nullptr;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
default: return false;
}
}
// Equality comparison - inline for performance
inline bool equals(const Value& other) const {
if (type != other.type) return false;
switch (type) {
case ValueType::VAL_NONE: return true;
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
case ValueType::VAL_NUMBER: return number == other.number;
case ValueType::VAL_STRING: return string_value == other.string_value;
case ValueType::VAL_FUNCTION: return function == other.function;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
default: return false;
}
}
// String representation
std::string toString() const {
switch (type) {
case ValueType::VAL_NONE: return "none";
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
case ValueType::VAL_NUMBER: {
// Format numbers like the original stringify function
if (number == std::floor(number)) {
return std::to_string(static_cast<long long>(number));
} else {
std::string str = std::to_string(number);
// Remove trailing zeros
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') str.pop_back();
return str;
}
}
case ValueType::VAL_STRING: return string_value;
case ValueType::VAL_FUNCTION: return "<function>";
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
default: return "unknown";
}
}
// Arithmetic operators
Value operator+(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number + other.number);
}
if (isString() && other.isString()) {
return Value(string_value + other.string_value);
}
if (isString() && other.isNumber()) {
return Value(string_value + other.toString());
}
if (isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
// Handle none values by converting to string
if (isString() && other.isNone()) {
return Value(string_value + "none");
}
if (isNone() && other.isString()) {
return Value("none" + other.string_value);
}
if (isString() && !other.isString() && !other.isNumber()) {
return Value(string_value + other.toString());
}
if (!isString() && !isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
throw std::runtime_error("Invalid operands for + operator");
}
Value operator-(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number - other.number);
}
throw std::runtime_error("Invalid operands for - operator");
}
Value operator*(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number * other.number);
}
if (isString() && other.isNumber()) {
std::string result;
for (int i = 0; i < static_cast<int>(other.number); ++i) {
result += string_value;
}
return Value(result);
}
if (isNumber() && other.isString()) {
std::string result;
for (int i = 0; i < static_cast<int>(number); ++i) {
result += other.string_value;
}
return Value(result);
}
throw std::runtime_error("Invalid operands for * operator");
}
Value operator/(const Value& other) const {
if (isNumber() && other.isNumber()) {
if (other.number == 0) {
throw std::runtime_error("Division by zero");
}
return Value(number / other.number);
}
throw std::runtime_error("Invalid operands for / operator");
}
Value operator%(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(fmod(number, other.number));
}
throw std::runtime_error("Invalid operands for % operator");
}
Value operator&(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for & operator");
}
Value operator|(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for | operator");
}
Value operator^(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for ^ operator");
}
Value operator<<(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for << operator");
}
Value operator>>(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for >> operator");
}
};
// Global constants for common values
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;
extern const Value ZERO_VALUE;
extern const Value ONE_VALUE;

View File

@ -4,6 +4,9 @@
#include <fstream>
#include <string>
#include "../headers/Lexer.h"
#include "../headers/Interpreter.h"
#include "../headers/helperFunctions/ShortHands.h"
#include "../headers/ErrorReporter.h"
#define VERSION "0.0.1"
@ -11,21 +14,16 @@ class Bob
{
public:
Lexer lexer;
sptr(Interpreter) interpreter;
ErrorReporter errorReporter;
~Bob() = default;
public:
void runFile(std::string path);
void runFile(const 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);
};

View File

@ -2,8 +2,9 @@
#include <iostream>
#include <vector>
#include <bitset>
std::vector<std::string> splitString(const std::string& input, std::string delimiter) {
inline std::vector<std::string> splitString(const std::string& input, std::string delimiter) {
std::vector<std::string> tokens;
std::string token;
size_t start = 0;
@ -23,7 +24,7 @@ std::vector<std::string> splitString(const std::string& input, std::string delim
return tokens;
}
std::string trim(const std::string& str) {
inline std::string trim(const std::string& str) {
// Find the first non-whitespace character
size_t start = str.find_first_not_of(" \t\n\r");
@ -39,7 +40,7 @@ std::string trim(const std::string& str) {
return str.substr(start, end - start + 1);
}
std::string replaceSubstring(const std::string& str, const std::string& findSubstring, const std::string& replacement) {
inline std::string replaceSubstring(const std::string& str, const std::string& findSubstring, const std::string& replacement) {
std::string result = str;
size_t startPos = result.find(findSubstring);
@ -50,3 +51,17 @@ std::string replaceSubstring(const std::string& str, const std::string& findSubs
return result;
}
inline bool isHexDigit(char c) {
return (std::isdigit(c) || (std::isxdigit(c) && std::islower(c)));
}
inline u_long binaryStringToLong(const std::string& binaryString) {
std::string binaryDigits = binaryString.substr(2); // Remove the '0b' prefix
u_long result = 0;
for (char ch : binaryDigits) {
result <<= 1;
result += (ch - '0');
}
return result;
}

View File

@ -0,0 +1,5 @@
#pragma once
#include <memory>
#define sptr(T) std::shared_ptr<T>
#define msptr(T) std::make_shared<T>

View File

@ -1,14 +0,0 @@
#include <string>
#include <iostream>
class Test
{
public:
std::string message;
Test(std::string msg);
public:
void Hello();
};

View File

@ -1,16 +0,0 @@
bob.test
10
11.1
test = (11 + 2 "xs
hello
end")
//
//11.
12//11
11.
11.69 + 66.735293857293875 + 235982735987235.0 + 1
123a

View File

@ -1,43 +0,0 @@
//
// Created by Bobby Lucero on 5/23/23.
//
#include "../headers/ASTPrinter.h"
std::string ASTPrinter::visitBinaryExpr(BinaryExpr<std::string>* expression){
std::cout << expression->left << std::endl;
return parenthesize(expression->oper.lexeme, std::vector<std::shared_ptr<Expr<std::string>>>{expression->left, expression->right});
}
std::string ASTPrinter::visitGroupingExpr(GroupingExpr<std::string>* expression){
return parenthesize("group", std::vector<std::shared_ptr<Expr<std::string>>>{expression->expression});
}
std::string ASTPrinter::visitLiteralExpr(LiteralExpr<std::string>* expression){
return expression->value;
}
std::string ASTPrinter::visitUnaryExpr(UnaryExpr<std::string>* expression){
return parenthesize(expression->oper.lexeme, std::vector<std::shared_ptr<Expr<std::string>>>{expression->right});
}
std::string ASTPrinter::print(Expr<std::string> *expr) {
return expr->accept(this);
}
std::string ASTPrinter::parenthesize(std::string name, std::vector<std::shared_ptr<Expr<std::string>>> exprs) {
std::string builder;
builder += "(" + name;
for(std::shared_ptr<Expr<std::string>> expr : exprs)
{
std::cout << expr << std::endl;
builder += " ";
builder += expr->accept(this);
}
builder += ")";
return builder;
}

51
source/Environment.cpp Normal file
View File

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

207
source/ErrorReporter.cpp Normal file
View File

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

878
source/Interpreter.cpp Normal file
View File

@ -0,0 +1,878 @@
//
// Created by Bobby Lucero on 5/27/23.
//
#include <utility>
#include <sstream>
#include <cmath>
#include <iomanip>
#include <limits>
#include <cmath>
#include "../headers/Interpreter.h"
#include "../headers/helperFunctions/HelperFunctions.h"
#include <unordered_map>
#include "../headers/Interpreter.h"
#include "../headers/StdLib.h"
#include <iostream>
#include <chrono>
#include <cmath>
#include <stdexcept>
#include <algorithm>
struct ReturnContext {
Value returnValue;
bool hasReturn;
ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {}
};
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
if(expr->isNull) return NONE_VALUE;
if(expr->isNumber){
double num;
if(expr->value[1] == 'b')
{
num = binaryStringToLong(expr->value);
}
else
{
num = std::stod(expr->value);
}
return Value(num);
}
if(expr->isBoolean) {
if(expr->value == "true") return TRUE_VALUE;
if(expr->value == "false") return FALSE_VALUE;
}
return Value(expr->value);
}
Value Interpreter::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
return evaluate(expression->expression);
}
Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
{
Value right = evaluate(expression->right);
if(expression->oper.type == MINUS)
{
if(right.isNumber())
{
double value = right.asNumber();
return Value(-value);
}
else
{
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
}
if(expression->oper.type == BANG)
{
return Value(!isTruthy(right));
}
if(expression->oper.type == BIN_NOT)
{
if(right.isNumber())
{
double value = right.asNumber();
return Value(static_cast<double>(~(static_cast<long>(value))));
}
else
{
throw std::runtime_error("Operand must be an int when using: " + expression->oper.lexeme);
}
}
//unreachable
throw std::runtime_error("Invalid unary expression");
}
Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
Value left = evaluate(expression->left);
Value right = evaluate(expression->right);
if (left.isNumber() && right.isNumber()) {
double leftNum = left.asNumber();
double rightNum = right.asNumber();
switch (expression->oper.type) {
case PLUS: return Value(leftNum + rightNum);
case MINUS: return Value(leftNum - rightNum);
case SLASH: {
if (rightNum == 0) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero",
"Cannot divide by zero", expression->oper.lexeme);
}
throw std::runtime_error("Division by zero");
}
return Value(leftNum / rightNum);
}
case STAR: return Value(leftNum * rightNum);
case PERCENT: {
if (rightNum == 0) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero",
"Cannot perform modulo operation with zero", expression->oper.lexeme);
}
throw std::runtime_error("Modulo by zero");
}
return Value(std::fmod(leftNum, rightNum));
}
case GREATER: return Value(leftNum > rightNum);
case GREATER_EQUAL: return Value(leftNum >= rightNum);
case LESS: return Value(leftNum < rightNum);
case LESS_EQUAL: return Value(leftNum <= rightNum);
case DOUBLE_EQUAL: return Value(leftNum == rightNum);
case BANG_EQUAL: return Value(leftNum != rightNum);
case BIN_AND: return Value(static_cast<double>(static_cast<int>(leftNum) & static_cast<int>(rightNum)));
case BIN_OR: return Value(static_cast<double>(static_cast<int>(leftNum) | static_cast<int>(rightNum)));
case BIN_XOR: return Value(static_cast<double>(static_cast<int>(leftNum) ^ static_cast<int>(rightNum)));
case BIN_SLEFT: return Value(static_cast<double>(static_cast<int>(leftNum) << static_cast<int>(rightNum)));
case BIN_SRIGHT: return Value(static_cast<double>(static_cast<int>(leftNum) >> static_cast<int>(rightNum)));
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
}
}
if (left.isString() && right.isString()) {
std::string left_string = left.asString();
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return Value(left_string + right_string);
case DOUBLE_EQUAL: return Value(left_string == right_string);
case BANG_EQUAL: return Value(left_string != right_string);
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
default:
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Cannot use '" + expression->oper.lexeme + "' on two strings", expression->oper.lexeme);
}
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings");
}
}
if (left.isString() && right.isNumber()) {
std::string left_string = left.asString();
double right_num = right.asNumber();
switch (expression->oper.type) {
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(right_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number", expression->oper.lexeme);
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(right_num); i++) {
result += left_string;
}
return Value(result);
}
}
}
if (left.isNumber() && right.isString()) {
double left_num = left.asNumber();
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(left_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number", expression->oper.lexeme);
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(left_num); i++) {
result += right_string;
}
return Value(result);
}
}
}
if (left.isBoolean() && right.isBoolean()) {
bool left_bool = left.asBoolean();
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case AND: return Value(left_bool && right_bool);
case OR: return Value(left_bool || right_bool);
case DOUBLE_EQUAL: return Value(left_bool == right_bool);
case BANG_EQUAL: return Value(left_bool != right_bool);
}
}
if (left.isBoolean() && right.isString()) {
bool left_bool = left.asBoolean();
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
}
}
if (left.isString() && right.isBoolean()) {
std::string left_string = left.asString();
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case PLUS: return left + right;
}
}
if (left.isNumber() && right.isBoolean()) {
double left_num = left.asNumber();
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
}
}
if (left.isBoolean() && right.isNumber()) {
bool left_bool = left.asBoolean();
double right_num = right.asNumber();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
}
}
// Mixed-type logical operations (string && boolean, etc.)
if (left.isString() && right.isBoolean()) {
bool right_bool = right.asBoolean();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
}
}
if (left.isBoolean() && right.isString()) {
bool left_bool = left.asBoolean();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
}
}
if (left.isString() && right.isNumber()) {
double right_num = right.asNumber();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(right_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number");
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(right_num); i++) {
result += left.asString();
}
return Value(result);
}
}
}
if (left.isNumber() && right.isString()) {
double left_num = left.asNumber();
switch (expression->oper.type) {
case AND: {
if (!isTruthy(left)) {
return left; // Return the falsy value
} else {
return right; // Return the second value
}
}
case OR: {
if (isTruthy(left)) {
return left; // Return the truthy value
} else {
return right; // Return the second value
}
}
case PLUS: return left + right;
case STAR: {
if (!isWholeNumer(left_num)) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication",
"String multiplier must be a whole number");
}
throw std::runtime_error("String multiplier must be whole number");
}
std::string result;
for (int i = 0; i < static_cast<int>(left_num); i++) {
result += right.asString();
}
return Value(result);
}
}
}
if (left.isNone() && right.isString()) {
std::string right_string = right.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
}
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Cannot use '" + expression->oper.lexeme + "' on none and a string", expression->oper.lexeme);
}
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
}
if (left.isString() && right.isNone()) {
std::string left_string = left.asString();
switch (expression->oper.type) {
case PLUS: return left + right;
}
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Cannot use '" + expression->oper.lexeme + "' on a string and none", expression->oper.lexeme);
}
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none");
}
else
{
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operands must be of same type when using: " + expression->oper.lexeme, expression->oper.lexeme);
}
throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme);
}
}
Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
{
return environment->get(expression->name);
}
Value Interpreter::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
// Get the current value of the operand
Value currentValue = evaluate(expression->operand);
if (!currentValue.isNumber()) {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
}
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
}
double currentNum = currentValue.asNumber();
double newValue;
// Determine the operation based on the operator
if (expression->oper.type == PLUS_PLUS) {
newValue = currentNum + 1.0;
} else if (expression->oper.type == MINUS_MINUS) {
newValue = currentNum - 1.0;
} else {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Invalid increment/decrement operator.", "");
}
throw std::runtime_error("Invalid increment/decrement operator.");
}
// Update the variable if it's a variable expression
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
environment->assign(varExpr->name, Value(newValue));
} else {
if (errorReporter) {
errorReporter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to variables.", "");
}
throw std::runtime_error("Increment/decrement can only be applied to variables.");
}
// Return the appropriate value based on prefix/postfix
if (expression->isPrefix) {
return Value(newValue); // Prefix: return new value
} else {
return currentValue; // Postfix: return old value
}
}
void Interpreter::addStdLibFunctions() {
// Add standard library functions to the environment
StdLib::addToEnvironment(environment, *this, errorReporter);
}
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
builtinFunctions.push_back(func);
}
Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = evaluate(expression->value);
switch (expression->op.type) {
case PLUS_EQUAL:
case MINUS_EQUAL:
case STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case BIN_AND_EQUAL:
case BIN_OR_EQUAL:
case BIN_XOR_EQUAL:
case BIN_SLEFT_EQUAL:
case BIN_SRIGHT_EQUAL: {
Value currentValue = environment->get(expression->name.lexeme);
switch (expression->op.type) {
case PLUS_EQUAL:
value = currentValue + value;
break;
case MINUS_EQUAL:
value = currentValue - value;
break;
case STAR_EQUAL:
value = currentValue * value;
break;
case SLASH_EQUAL:
value = currentValue / value;
break;
case PERCENT_EQUAL:
value = currentValue % value;
break;
case BIN_AND_EQUAL:
value = currentValue & value;
break;
case BIN_OR_EQUAL:
value = currentValue | value;
break;
case BIN_XOR_EQUAL:
value = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
value = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
value = currentValue >> value;
break;
default:
break;
}
break;
}
default:
break;
}
environment->assign(expression->name, value);
return value;
}
Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
Value callee = evaluate(expression->callee);
std::vector<Value> arguments;
for (const std::shared_ptr<Expr>& argument : expression->arguments) {
arguments.push_back(evaluate(argument));
}
if (callee.isBuiltinFunction()) {
// Builtin functions now work directly with Value and receive line and column
return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column);
}
if (callee.isFunction()) {
Function* function = callee.asFunction();
if (arguments.size() != function->params.size()) {
throw std::runtime_error("Expected " + std::to_string(function->params.size()) +
" arguments but got " + std::to_string(arguments.size()) + ".");
}
auto previousEnv = environment;
environment = std::make_shared<Environment>(function->closure);
environment->setErrorReporter(errorReporter);
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
}
ExecutionContext context;
context.isFunctionBody = true;
for (const auto& stmt : function->body) {
execute(stmt, &context);
if (context.hasReturn) {
environment = previousEnv;
return context.returnValue;
}
}
environment = previousEnv;
return context.returnValue;
}
throw std::runtime_error("Can only call functions and classes.");
}
Value Interpreter::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
// Convert Token parameters to string parameters
std::vector<std::string> paramNames;
for (const Token& param : expression->params) {
paramNames.push_back(param.lexeme);
}
auto function = msptr(Function)("anonymous", paramNames, expression->body, environment);
functions.push_back(function); // Keep the shared_ptr alive
return Value(function.get());
}
void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
auto newEnv = std::make_shared<Environment>(environment);
newEnv->setErrorReporter(errorReporter);
executeBlock(statement->statements, newEnv, context);
}
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
Value value = evaluate(statement->expression);
if(IsInteractive)
std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m" << std::endl;
}
void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context)
{
Value value = NONE_VALUE;
if(statement->initializer != nullptr)
{
value = evaluate(statement->initializer);
}
//std::cout << "Visit var stmt: " << statement->name.lexeme << " set to: " << stringify(value) << std::endl;
environment->define(statement->name.lexeme, value);
}
void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context)
{
// Convert Token parameters to string parameters
std::vector<std::string> paramNames;
for (const Token& param : statement->params) {
paramNames.push_back(param.lexeme);
}
auto function = msptr(Function)(statement->name.lexeme,
paramNames,
statement->body,
environment);
functions.push_back(function); // Keep the shared_ptr alive
environment->define(statement->name.lexeme, Value(function.get()));
}
void Interpreter::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context)
{
Value value = NONE_VALUE;
if (statement->value != nullptr) {
value = evaluate(statement->value);
}
if (context && context->isFunctionBody) {
context->hasReturn = true;
context->returnValue = value;
}
// If no context or not in function body, this is a top-level return (ignored)
}
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context)
{
if (isTruthy(evaluate(statement->condition))) {
execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch, context);
}
}
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt> > statements) {
for(const std::shared_ptr<Stmt>& s : statements)
{
execute(s, nullptr); // No context needed for top-level execution
}
}
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context)
{
statement->accept(this, context);
}
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context)
{
std::shared_ptr<Environment> previous = this->environment;
this->environment = env;
for(const std::shared_ptr<Stmt>& s : statements)
{
execute(s, context);
if (context && context->hasReturn) {
this->environment = previous;
return;
}
}
this->environment = previous;
}
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
return expr->accept(this);
}
bool Interpreter::isTruthy(Value object) {
if(object.isBoolean())
{
return object.asBoolean();
}
if(object.isNone())
{
return false;
}
if(object.isNumber())
{
return object.asNumber() != 0;
}
if(object.isString())
{
return object.asString().length() > 0;
}
return true;
}
bool Interpreter::isEqual(Value a, Value b) {
if(a.isNumber())
{
if(b.isNumber())
{
return a.asNumber() == b.asNumber();
}
return false;
}
else if(a.isBoolean())
{
if(b.isBoolean())
{
return a.asBoolean() == b.asBoolean();
}
return false;
}
else if(a.isString())
{
if(b.isString())
{
return a.asString() == b.asString();
}
return false;
}
else if(a.isNone())
{
if(b.isNone())
{
return true;
}
return false;
}
throw std::runtime_error("Invalid isEqual compariosn");
}
std::string Interpreter::stringify(Value object) {
if(object.isNone())
{
return "none";
}
else if(object.isNumber())
{
double integral = object.asNumber();
double fractional = std::modf(object.asNumber(), &integral);
std::stringstream ss;
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
{
ss << std::fixed << std::setprecision(0) << integral;
return ss.str();
}
else
{
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << object.asNumber();
std::string str = ss.str();
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') {
str.pop_back();
}
return str;
}
}
else if(object.isString())
{
return object.asString();
}
else if(object.isBoolean())
{
return object.asBoolean() == 1 ? "true" : "false";
}
else if(object.isFunction())
{
return "<function " + object.asFunction()->name + ">";
}
else if(object.isBuiltinFunction())
{
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
}
throw std::runtime_error("Could not convert object to string");
}
bool Interpreter::isWholeNumer(double num) {
double integral = num;
double fractional = std::modf(num, &integral);
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
{
return true;
}
else
{
return false;
}
}

View File

@ -1,64 +1,118 @@
#include "../headers/Lexer.h"
#include "../headers/ErrorReporter.h"
#include "../headers/helperFunctions/HelperFunctions.h"
#include <cctype>
#include <stdexcept>
using namespace std;
std::vector<Token> Lexer::Tokenize(std::string source){
std::vector<Token> tokens;
src = std::vector<char>{source.begin(), source.end()};
line = 0;
line = 1;
column = 1;
while(!src.empty())
{
char t = src[0];
if(t == '(')
{
tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line}); //brace initialization in case you forget
tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line, column}); //brace initialization in case you forget
advance();
}
else if(t == ')')
{
tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line});
tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line, column});
advance();
}
else if(t == '{')
{
tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line});
tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line, column});
advance();
}
else if(t == '}')
{
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line});
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column});
advance();
}
else if(t == ',')
{
tokens.push_back(Token{COMMA, std::string(1, t), line});
tokens.push_back(Token{COMMA, std::string(1, t), line, column});
advance();
}
else if(t == '.')
{
tokens.push_back(Token{DOT, std::string(1, t), line});
tokens.push_back(Token{DOT, std::string(1, t), line, column});
advance();
}
else if(t == ';')
{
tokens.push_back(Token{SEMICOLON, std::string(1, t), line});
tokens.push_back(Token{SEMICOLON, std::string(1, t), line, column});
advance();
}
else if(t == '+')
{
tokens.push_back(Token{PLUS, std::string(1, t), line});
std::string token = std::string(1, t);
advance();
bool match = matchOn('+');
if(match) {
token += '+';
tokens.push_back(Token{PLUS_PLUS, token, line, column - 1});
} else {
match = matchOn('=');
if(match) {
token += '=';
tokens.push_back(Token{PLUS_EQUAL, token, line, column - 1});
} else {
tokens.push_back(Token{PLUS, token, line, column - 1});
}
}
}
else if(t == '-')
{
tokens.push_back(Token{MINUS, std::string(1, t), line});
std::string token = std::string(1, t);
advance();
bool match = matchOn('-');
if(match) {
token += '-';
tokens.push_back(Token{MINUS_MINUS, token, line, column - 1});
} else {
match = matchOn('=');
if(match) {
token += '=';
tokens.push_back(Token{MINUS_EQUAL, token, line, column - 1});
} else {
tokens.push_back(Token{MINUS, token, line, column - 1});
}
}
}
else if(t == '*')
{
tokens.push_back(Token{STAR, std::string(1, t), line});
std::string token = std::string(1, t);
advance();
bool match = matchOn('=');
if(match) {
token += '=';
tokens.push_back(Token{STAR_EQUAL, token, 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) {
token += '=';
tokens.push_back(Token{PERCENT_EQUAL, token, 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});
advance();
}
else if(t == '=')
@ -67,7 +121,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});
tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line, column - static_cast<int>(token.length())});
}
else if(t == '!')
{
@ -75,31 +129,99 @@ std::vector<Token> Lexer::Tokenize(std::string source){
advance();
bool match = matchOn('=');
token += match ? "=" : "";
tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line});
tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line, column - static_cast<int>(token.length())});
}
else if(t == '<')
{
std::string token = std::string(1, t);
advance();
bool match = matchOn('=');
token += match ? "=" : "";
tokens.push_back(Token{match ? LESS_EQUAL : LESS, token, line});
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())});
}
}
else if(t == '>')
{
std::string token = std::string(1, t);
advance();
bool match = matchOn('=');
token += match ? "=" : "";
tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token, line});
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())});
}
}
else if(t == '&')
{
std::string token = std::string(1, t);
advance();
bool match = matchOn('&');
token += match ? "&" : "";
if(match) tokens.push_back(Token{AND, token, line});
if(match) {
token += '&';
tokens.push_back(Token{AND, token, line, column - 1});
} else {
bool equalMatch = matchOn('=');
if(equalMatch) {
token += '=';
tokens.push_back(Token{BIN_AND_EQUAL, token, line, column - 1});
} else {
tokens.push_back(Token{BIN_AND, token, line, column - 1});
}
}
}
else if(t == '|')
{
std::string token = std::string(1, t);
advance();
bool match = matchOn('|');
if(match) {
token += '|';
tokens.push_back(Token{OR, token, line, column - 1});
} else {
bool equalMatch = matchOn('=');
if(equalMatch) {
token += '=';
tokens.push_back(Token{BIN_OR_EQUAL, token, line, column - 1});
} else {
tokens.push_back(Token{BIN_OR, token, 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});
}
}
else if(t == '/')
{
@ -115,74 +237,158 @@ std::vector<Token> Lexer::Tokenize(std::string source){
}
else
{
tokens.push_back(Token{SLASH, std::string(1, t), line});
bool starMatch = matchOn('*');
if(starMatch)
{
// Multi-line comment /* ... */
while(!src.empty())
{
if(src[0] == '*' && !src.empty() && src.size() > 1 && src[1] == '/')
{
advance(2); // Skip */
break;
}
advance();
}
}
else
{
bool equalMatch = matchOn('=');
if(equalMatch) {
tokens.push_back(Token{SLASH_EQUAL, "/=", line, column - 1});
} else {
tokens.push_back(Token{SLASH, "/", line, column - 1});
}
}
}
}
else if(t == '"')
{
std::string str = std::string(1, src[0]);
bool last_was_escape = false;
std::string str;
int startColumn = column;
advance();
while(!src.empty() && src[0] != '"')
while(!src.empty())
{
if(src[0] == '\n') line++;
str += 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;
advance();
}
if(src.empty())
{
throw std::runtime_error("Unterminated string at line: " + std::to_string(this->line));
throw std::runtime_error("LEXER: Unterminated string at line: " + std::to_string(this->line));
}
else if(src[0] == '"')
{
str += '"';
advance();
tokens.push_back(Token{STRING, str, line});
std::string escaped_str = parseEscapeCharacters(str);
tokens.push_back(Token{STRING, escaped_str, line, startColumn});
}
}
else if(t == '\n')
{
line++;
advance();
}
else
{
bool isNotation = false;
bool notationInvalidated = false;
char notationChar;
//Multi char tokens
if(std::isdigit(t))
{
std::string num;
while(!src.empty() && std::isdigit(src[0]))
{
num += src[0];
advance();
}
int startColumn = column;
if(!src.empty() && src[0] == '.')
if(src[0] != '0') notationInvalidated = true;
while(!src.empty())
{
advance();
if(!src.empty() && std::isdigit(src[0]))
if(std::isdigit(src[0]))
{
num += '.';
while(!src.empty() && std::isdigit(src[0]))
if(src[0] == '0' && !notationInvalidated)
{
num += src[0];
advance();
if(peekNext() == 'b' || peekNext() == 'x') {
num += "0";
num += peekNext();
notationChar = peekNext();
advance(2);
isNotation = true;
break;
}
}
num += src[0];
advance();
}
else
{
break;
}
}
if(!isNotation) {
if (!src.empty() && src[0] == '.') {
advance();
if (!src.empty() && std::isdigit(src[0])) {
num += '.';
while (!src.empty() && std::isdigit(src[0])) {
num += src[0];
advance();
}
} else {
throw std::runtime_error("LEXER: malformed number at: " + std::to_string(this->line));
}
}
}
else
{
if(!src.empty() && (src[0]))
{
if(notationChar == 'b') {
while (!src.empty() && (src[0] == '0' || src[0] == '1')) {
num += src[0];
advance();
}
}
else if(notationChar == 'x')
{
while (!src.empty() && std::isxdigit(src[0])) {
num += src[0];
advance();
}
}
}
else
{
throw std::runtime_error("malformed number at: " + std::to_string(this->line));
throw std::runtime_error("LEXER: malformed notation at: " + std::to_string(this->line));
}
}
tokens.push_back(Token{NUMBER, num, line});
tokens.push_back(Token{NUMBER, num, line, startColumn});
}
else if(std::isalpha(t))
{
std::string ident;
while(!src.empty() && std::isalpha(src[0]))
int startColumn = column;
while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_'))
{
ident += src[0];
advance();
@ -190,11 +396,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});
tokens.push_back(Token{KEYWORDS.at(ident), ident, line, startColumn});
}
else
{
tokens.push_back(Token{IDENTIFIER, ident, line});
tokens.push_back(Token{IDENTIFIER, ident, line, startColumn});
}
}
@ -204,15 +410,18 @@ std::vector<Token> Lexer::Tokenize(std::string source){
}
else
{
throw std::runtime_error("Unknown Token: '" + std::string(1, t) + "'");
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) + "'");
}
}
}
tokens.push_back({END_OF_FILE, "eof", line});
return tokens;
}
@ -224,7 +433,74 @@ bool Lexer::matchOn(char expected)
return true;
}
void Lexer::advance()
void Lexer::advance(int by)
{
src.erase(src.begin());
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++;
}
}
}
}
char Lexer::peekNext()
{
if(src.size() > 1)
{
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;
}

554
source/Parser.cpp Normal file
View File

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

261
source/StdLib.cpp Normal file
View File

@ -0,0 +1,261 @@
#include "../headers/StdLib.h"
#include "../headers/Interpreter.h"
#include "../headers/ErrorReporter.h"
#include <chrono>
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
// Create a built-in toString function
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
return Value(interpreter.stringify(args[0]));
});
env->define("toString", Value(toStringFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toStringFunc);
// Create a built-in print function
auto printFunc = std::make_shared<BuiltinFunction>("print",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Use the interpreter's stringify function
std::cout << interpreter.stringify(args[0]) << std::endl;
return NONE_VALUE;
});
env->define("print", Value(printFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(printFunc);
// Create a built-in assert function
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 && args.size() != 2) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
}
// Simple truthy check without calling interpreter.isTruthy
bool isTruthy = false;
if (args[0].isBoolean()) {
isTruthy = args[0].asBoolean();
} else if (args[0].isNone()) {
isTruthy = false;
} else {
isTruthy = true; // Numbers, strings, functions are truthy
}
if (!isTruthy) {
std::string message = "Assertion failed: condition is false";
if (args.size() == 2) {
if (args[1].isString()) {
message += " - " + std::string(args[1].asString());
}
}
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
}
throw std::runtime_error(message);
}
return NONE_VALUE;
});
env->define("assert", Value(assertFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(assertFunc);
// Create a built-in time function (returns microseconds since Unix epoch)
auto timeFunc = std::make_shared<BuiltinFunction>("time",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
}
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
return Value(static_cast<double>(microseconds));
});
env->define("time", Value(timeFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(timeFunc);
// Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input",
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() > 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
}
// Optional prompt
if (args.size() == 1) {
std::cout << interpreter.stringify(args[0]);
}
// Get user input
std::string userInput;
std::getline(std::cin, userInput);
return Value(userInput);
});
env->define("input", Value(inputFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(inputFunc);
// Create a built-in type function
auto typeFunc = std::make_shared<BuiltinFunction>("type",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
std::string typeName;
if (args[0].isNumber()) {
typeName = "number";
} else if (args[0].isString()) {
typeName = "string";
} else if (args[0].isBoolean()) {
typeName = "boolean";
} else if (args[0].isNone()) {
typeName = "none";
} else if (args[0].isFunction()) {
typeName = "function";
} else if (args[0].isBuiltinFunction()) {
typeName = "builtin_function";
} else {
typeName = "unknown";
}
return Value(typeName);
});
env->define("type", Value(typeFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(typeFunc);
// Create a built-in toNumber function for string-to-number conversion
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
[](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
return NONE_VALUE; // Return none for wrong argument count
}
if (!args[0].isString()) {
return NONE_VALUE; // Return none for wrong type
}
std::string str = args[0].asString();
// Remove leading/trailing whitespace
str.erase(0, str.find_first_not_of(" \t\n\r"));
str.erase(str.find_last_not_of(" \t\n\r") + 1);
if (str.empty()) {
return NONE_VALUE; // Return none for empty string
}
try {
double value = std::stod(str);
return Value(value);
} catch (const std::invalid_argument&) {
return NONE_VALUE; // Return none for invalid conversion
} catch (const std::out_of_range&) {
return NONE_VALUE; // Return none for out of range
}
});
env->define("toNumber", Value(toNumberFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toNumberFunc);
// Create a built-in toBoolean function for explicit boolean conversion
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) {
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error",
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
}
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
// Use the same logic as isTruthy() for consistency
Value value = args[0];
if (value.isNone()) {
return Value(false);
}
if (value.isBoolean()) {
return value; // Already a boolean
}
if (value.isNumber()) {
return Value(value.asNumber() != 0.0);
}
if (value.isString()) {
return Value(!value.asString().empty());
}
// For any other type (functions, etc.), consider them truthy
return Value(true);
});
env->define("toBoolean", Value(toBooleanFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toBooleanFunc);
// Create a built-in exit function to terminate the program
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
[](std::vector<Value> args, int line, int column) -> Value {
int exitCode = 0; // Default exit code
if (args.size() > 0) {
if (args[0].isNumber()) {
exitCode = static_cast<int>(args[0].asNumber());
}
// If not a number, just use default exit code 0
}
std::exit(exitCode);
return NONE_VALUE; // This line should never be reached
});
env->define("exit", Value(exitFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(exitFunc);
}

6
source/TypeWrapper.cpp Normal file
View File

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

8
source/Value.cpp Normal file
View File

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

View File

@ -1,11 +1,15 @@
#include <utility>
#include "../headers/bob.h"
#include "../headers/Parser.h"
using namespace std;
void Bob::runFile(string path)
void Bob::runFile(const 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>());
@ -16,16 +20,24 @@ void Bob::runFile(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(;;)
{
string line;
cout << "-> ";
cout << "\033[0;36m" << "-> " << "\033[0;37m";
std::getline(std::cin, line);
if(std::cin.eof())
@ -33,28 +45,49 @@ 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)
{
vector<Token> tokens = lexer.Tokenize(source);
try {
// Connect error reporter to lexer
lexer.setErrorReporter(&errorReporter);
for(Token t : tokens){
cout << "{type: " << t.type << ", value: " << t.lexeme << "}" << endl;
vector<Token> tokens = lexer.Tokenize(std::move(source));
Parser p(tokens);
// Connect error reporter to parser
p.setErrorReporter(&errorReporter);
vector<sptr(Stmt)> statements = p.parse();
interpreter->interpret(statements);
}
catch(std::exception &e)
{
// Only suppress errors that have already been reported by the error reporter
if (errorReporter.hasReportedError()) {
return;
}
// For errors that weren't reported (like parser errors, undefined variables, etc.)
// print them normally
std::cout << "Error: " << e.what() << std::endl;
return;
}
catch(...)
{
// Unknown error - report it since it wasn't handled by the interpreter
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred");
return;
}
}
void Bob::report(int line, string where, string message)
{
hadError = true;
}

View File

@ -1,53 +1,15 @@
//
// Created by Bobby Lucero on 5/21/23.
//
#include "../headers/bob.h"
#include "../headers/Expression.h"
#include "../headers/Lexer.h"
#include "../headers/ASTPrinter.h"
#include "../headers/TypeWrapper.h"
int main(){
int main(int argc, char* argv[]){
Bob bobLang;
//bobLang.runFile("source.bob");
ASTPrinter printer;
std::shared_ptr<Expr<std::string>> expression = std::make_shared<BinaryExpr<std::string> >(
std::make_shared<UnaryExpr<std::string>>(
Token{MINUS, "-", 1},
std::make_shared<LiteralExpr<std::string>>("123", true)
),
Token{MINUS, "-", 1},
std::make_shared<GroupingExpr<std::string>>(
std::make_shared<LiteralExpr<std::string>>("45.67", true)
)
);
// Expr<std::string>* e = new BinaryExpr<std::string>(
// new UnaryExpr<std::string>(Token{MINUS, "-", 0}, new LiteralExpr<std::string>("123")),
// Token{STAR, "*", 0},
// new UnaryExpr<std::string>(Token{PLUS, "+", 0}, new LiteralExpr<std::string>("535"))
// );
LiteralExpr<std::string>* le = new LiteralExpr<std::string>("123", true);
std::cout << printer.print(expression.get());
std::cout << std::endl;
//bobLang.runPrompt();
std::shared_ptr<Object> object = std::make_shared<String>(String{"Hi"});
if(auto num = std::dynamic_pointer_cast<Number>(object))
{
std::cout << num->value << std::endl;
}else if(auto str = std::dynamic_pointer_cast<String>(object))
{
std::cout << str->value << std::endl;
if(argc > 1) {
bobLang.runFile(argv[1]);
} else {
bobLang.runPrompt();
}
return 0;

View File

@ -1,9 +0,0 @@
#include "../headers/test.h"
Test::Test(std::string msg){
this->message = msg;
}
void Test::Hello(){
std::cout << this->message << std::endl;
}

0
tech_debt.txt Normal file
View File

1883
test_bob_language.bob Normal file

File diff suppressed because it is too large Load Diff

17
test_fib.bob Normal file
View File

@ -0,0 +1,17 @@
var counter = 0;
func fib(n) {
if (n <= 1) {
return n;
}
counter++; // Only increment for recursive calls
return fib(n - 1) + fib(n - 2);
}
print("Fibonacci test:");
var fib_result = fib(30);
print("Result: " + fib_result);
print("Counter: " + counter);

View File

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