Cleaned up project structure

This commit is contained in:
Bobby Lucero 2025-08-07 17:10:20 -04:00
parent 2104fbe1f5
commit 6c17ce96f0
47 changed files with 2097 additions and 1869 deletions

View File

@ -1,4 +1,4 @@
# Makefile # Makefile - Bob language interpreter
# Compiler # Compiler
CC = g++ CC = g++
@ -6,43 +6,50 @@ CC = g++
# Compiler flags # Compiler flags
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
# Source directory # Source layout:
SRC_DIR = ./source # src/
# ├── headers/ - All header files (public interface)
# │ ├── runtime/ - Runtime execution headers
# │ ├── parsing/ - Front-end processing headers
# │ ├── stdlib/ - Standard library headers
# │ ├── cli/ - CLI headers
# │ └── common/ - Shared utilities (helperFunctions)
# └── sources/ - All source files (implementation)
# ├── runtime/ - Interpreter, Evaluator, Executor, Environment, Value, TypeWrapper
# ├── parsing/ - Lexer, Parser, ErrorReporter, Expression AST
# ├── stdlib/ - Built-in functions (BobStdLib)
# └── cli/ - Command-line interface (main, bob)
# Output directory SRC_ROOT = ./src
BUILD_DIR = ./build BUILD_DIR = ./build
# Find all C++ files recursively in the source directory # Find all .cpp files under src/sources (recursively)
CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp') CPP_FILES := $(shell find $(SRC_ROOT)/sources -type f -name '*.cpp')
# Generate object file names by replacing the source directory with the build directory # Map every source file to its corresponding object file inside build/
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES)) # Strip src/sources/ prefix and add build/ prefix
OBJ_FILES := $(patsubst $(SRC_ROOT)/sources/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
# Create directories for object files # Make sure every object directory exists ahead of time
$(shell mkdir -p $(dir $(OBJ_FILES))) $(shell mkdir -p $(dir $(OBJ_FILES)))
# Default target # Default goal
all: build all: build
# Rule to create necessary directories # Pattern rule compile each .cpp from sources/ into a mirrored .o inside build/
$(DIRS): $(BUILD_DIR)/%.o: $(SRC_ROOT)/sources/%.cpp
mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@) @mkdir -p $(dir $@)
$(CC) $(CFLAGS) -I$(SRC_ROOT)/headers/runtime -I$(SRC_ROOT)/headers/parsing -I$(SRC_ROOT)/headers/stdlib -I$(SRC_ROOT)/headers/cli -I$(SRC_ROOT)/headers/common -c $< -o $@
# Rule to compile object files # Link all objects into the final executable
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CC) $(CFLAGS) -c $< -o $@
# Rule to link object files into the final executable
$(BUILD_DIR)/bob: $(OBJ_FILES) $(BUILD_DIR)/bob: $(OBJ_FILES)
$(CC) $(CFLAGS) $^ -o $@ $(CC) $(CFLAGS) $^ -o $@
# Convenience targets
run: run:
./$(BUILD_DIR)/bob ./$(BUILD_DIR)/bob
build: clean $(BUILD_DIR)/bob build: clean $(BUILD_DIR)/bob
# Clean build directory
clean: clean:
rm -rf $(BUILD_DIR)/* rm -rf $(BUILD_DIR)/*

View File

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

View File

@ -1,144 +0,0 @@
#pragma once
#include "Expression.h"
#include "Statement.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Environment.h"
#include "Value.h"
#include "BobStdLib.h"
#include "ErrorReporter.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include <stack>
#include <optional>
#include <functional>
// Forward declaration
class Interpreter;
// RAII helper for thunk execution flag
struct ScopedThunkFlag {
bool& flag;
bool prev;
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
~ScopedThunkFlag() { flag = prev; }
};
// RAII helper for environment management
struct ScopedEnv {
std::shared_ptr<Environment>& target;
std::shared_ptr<Environment> prev;
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
~ScopedEnv() { target = prev; }
};
// Thunk class for trampoline-based tail call optimization
struct Thunk {
public:
using ThunkFunction = std::function<Value()>;
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
Value execute() const {
return func();
}
bool isThunk() const { return true; }
private:
ThunkFunction func;
};
class 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;
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& 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 visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
void 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;
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
ErrorReporter* errorReporter;
bool inThunkExecution = false;
// Automatic cleanup tracking
int functionCreationCount = 0;
int thunkCreationCount = 0;
static const int CLEANUP_THRESHOLD = 1000000; // Cleanup every 1M creations (effectively disabled for performance)
static const int MAX_FUNCTION_PARAMETERS = 255; // Maximum number of function parameters
Value evaluate(const std::shared_ptr<Expr>& expr);
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
bool isEqual(Value a, Value b);
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();
// Trampoline execution
Value runTrampoline(Value initialResult);
public:
bool isTruthy(Value object);
std::string stringify(Value object);
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
// Memory management
void cleanupUnusedFunctions();
void cleanupUnusedThunks();
void forceCleanup();
// 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

@ -1,339 +0,0 @@
// Pseudo-OOP Examples using Dictionaries
print("=== PSEUDO-OOP EXAMPLES ===");
// Example 1: Simple Person Object
print("\n--- Example 1: Person Object ---");
func createPerson(name, age) {
var person = {
"name": name,
"age": age
};
person["greet"] = func() {
return "Hello, I'm " + person["name"] + " and I'm " + person["age"] + " years old.";
};
person["haveBirthday"] = func() {
person["age"] = person["age"] + 1;
return person["name"] + " is now " + person["age"] + " years old!";
};
return person;
}
var alice = createPerson("Alice", 25);
var bob = createPerson("Bob", 30);
print(alice["greet"]());
print(bob["greet"]());
print(alice["haveBirthday"]());
print(alice["greet"]());
// Example 2: Bank Account with Encapsulation
print("\n--- Example 2: Bank Account ---");
func createBankAccount(accountNumber, initialBalance) {
var account = {
"accountNumber": accountNumber,
"balance": initialBalance,
"transactions": []
};
account["addTransaction"] = func(type, amount) {
push(account["transactions"], {
"type": type,
"amount": amount,
"balance": account["balance"],
"timestamp": time()
});
};
account["deposit"] = func(amount) {
if (amount > 0) {
account["balance"] = account["balance"] + amount;
account["addTransaction"]("deposit", amount);
return "Deposited " + amount + ". New balance: " + account["balance"];
} else {
return "Invalid deposit amount";
}
};
account["withdraw"] = func(amount) {
if (amount > 0 && amount <= account["balance"]) {
account["balance"] = account["balance"] - amount;
account["addTransaction"]("withdrawal", amount);
return "Withdrew " + amount + ". New balance: " + account["balance"];
} else {
return "Invalid withdrawal amount or insufficient funds";
}
};
account["getStatement"] = func() {
var statement = "Account: " + account["accountNumber"] + "\n";
statement = statement + "Balance: " + account["balance"] + "\n";
statement = statement + "Transactions:\n";
for (var i = 0; i < len(account["transactions"]); i++) {
var tx = account["transactions"][i];
statement = statement + " " + tx["type"] + ": " + tx["amount"] + " (balance: " + tx["balance"] + ")\n";
}
return statement;
};
return account;
}
var account1 = createBankAccount("12345", 1000);
print(account1["deposit"](500));
print(account1["withdraw"](200));
print(account1["withdraw"](50));
print(account1["getStatement"]());
// Example 3: Game Character with Inheritance-like Pattern
print("\n--- Example 3: Game Character System ---");
func createCharacter(name, health, attack) {
var character = {
"name": name,
"health": health,
"maxHealth": health,
"attack": attack,
"level": 1,
"experience": 0,
"inventory": []
};
character["takeDamage"] = func(damage) {
character["health"] = character["health"] - damage;
if (character["health"] < 0) {
character["health"] = 0;
}
return character["name"] + " took " + damage + " damage. Health: " + character["health"] + "/" + character["maxHealth"];
};
character["heal"] = func(amount) {
var oldHealth = character["health"];
character["health"] = character["health"] + amount;
if (character["health"] > character["maxHealth"]) {
character["health"] = character["maxHealth"];
}
var healed = character["health"] - oldHealth;
return character["name"] + " healed " + healed + " health. Health: " + character["health"] + "/" + character["maxHealth"];
};
character["gainExperience"] = func(exp) {
character["experience"] = character["experience"] + exp;
var levelUp = false;
while (character["experience"] >= character["level"] * 100) {
character["experience"] = character["experience"] - (character["level"] * 100);
character["level"] = character["level"] + 1;
character["maxHealth"] = character["maxHealth"] + 10;
character["health"] = character["maxHealth"]; // Full heal on level up
character["attack"] = character["attack"] + 2;
levelUp = true;
}
if (levelUp) {
return character["name"] + " leveled up to " + character["level"] + "!";
} else {
return character["name"] + " gained " + exp + " experience.";
}
};
character["addItem"] = func(item) {
push(character["inventory"], item);
return character["name"] + " picked up " + item["name"];
};
character["getStatus"] = func() {
return character["name"] + " (Lv." + character["level"] + ") - HP: " + character["health"] + "/" + character["maxHealth"] +
" ATK: " + character["attack"] + " EXP: " + character["experience"] + "/" + (character["level"] * 100);
};
return character;
}
func createWarrior(name) {
var warrior = createCharacter(name, 120, 15);
warrior["class"] = "Warrior";
warrior["specialAbility"] = func() {
return warrior["name"] + " uses Battle Cry! Attack increased!";
};
return warrior;
}
func createMage(name) {
var mage = createCharacter(name, 80, 8);
mage["class"] = "Mage";
mage["mana"] = 100;
mage["maxMana"] = 100;
mage["castSpell"] = func(spellName, manaCost) {
if (mage["mana"] >= manaCost) {
mage["mana"] = mage["mana"] - manaCost;
return mage["name"] + " casts " + spellName + "! Mana: " + mage["mana"] + "/" + mage["maxMana"];
} else {
return mage["name"] + " doesn't have enough mana!";
}
};
return mage;
}
var hero = createWarrior("Hero");
var wizard = createMage("Wizard");
print(hero["getStatus"]());
print(wizard["getStatus"]());
print(hero["takeDamage"](20));
print(hero["gainExperience"](150));
print(hero["getStatus"]());
print(wizard["castSpell"]("Fireball", 30));
print(wizard["castSpell"]("Lightning", 50));
print(wizard["getStatus"]());
// Example 4: Simple Game Engine
print("\n--- Example 4: Simple Game Engine ---");
func createGameEngine() {
var engine = {
"entities": [],
"time": 0
};
engine["addEntity"] = func(entity) {
push(engine["entities"], entity);
return "Added " + entity["name"] + " to the game";
};
engine["removeEntity"] = func(entityName) {
for (var i = 0; i < len(engine["entities"]); i++) {
if (engine["entities"][i]["name"] == entityName) {
// Remove by shifting elements (simple implementation)
for (var j = i; j < len(engine["entities"]) - 1; j++) {
engine["entities"][j] = engine["entities"][j + 1];
}
engine["entities"][len(engine["entities"]) - 1] = none;
return "Removed " + entityName + " from the game";
}
}
return "Entity " + entityName + " not found";
};
engine["update"] = func() {
engine["time"] = engine["time"] + 1;
var updates = [];
for (var i = 0; i < len(engine["entities"]); i++) {
var entity = engine["entities"][i];
if (has(entity, "update")) {
push(updates, entity["update"]());
}
}
return "Game updated. Time: " + engine["time"] + ". Updates: " + len(updates);
};
engine["getEntityCount"] = func() {
return len(engine["entities"]);
};
return engine;
}
func createNPC(name, behavior) {
var npc = {
"name": name,
"behavior": behavior,
"position": {"x": 0, "y": 0}
};
npc["update"] = func() {
if (npc["behavior"] == "wander") {
npc["position"]["x"] = npc["position"]["x"] + (random() * 2 - 1);
npc["position"]["y"] = npc["position"]["y"] + (random() * 2 - 1);
return npc["name"] + " wandered to (" + npc["position"]["x"] + ", " + npc["position"]["y"] + ")";
} else if (npc["behavior"] == "patrol") {
npc["position"]["x"] = npc["position"]["x"] + 1;
if (npc["position"]["x"] > 10) {
npc["position"]["x"] = 0;
}
return npc["name"] + " patrolled to (" + npc["position"]["x"] + ", " + npc["position"]["y"] + ")";
}
return npc["name"] + " did nothing";
};
return npc;
}
var game = createGameEngine();
var guard = createNPC("Guard", "patrol");
var merchant = createNPC("Merchant", "wander");
print(game["addEntity"](guard));
print(game["addEntity"](merchant));
print("Entity count: " + game["getEntityCount"]());
for (var i = 0; i < 3; i++) {
print(game["update"]());
}
// Example 5: Event System
print("\n--- Example 5: Event System ---");
func createEventSystem() {
var eventSystem = {
"listeners": {}
};
eventSystem["on"] = func(event, callback) {
if (!has(eventSystem["listeners"], event)) {
eventSystem["listeners"][event] = [];
}
push(eventSystem["listeners"][event], callback);
return "Added listener for " + event;
};
eventSystem["emit"] = func(event, data) {
if (has(eventSystem["listeners"], event)) {
var results = [];
for (var i = 0; i < len(eventSystem["listeners"][event]); i++) {
var result = eventSystem["listeners"][event][i](data);
push(results, result);
}
return "Emitted " + event + " to " + len(results) + " listeners";
} else {
return "No listeners for " + event;
}
};
eventSystem["removeListener"] = func(event, callback) {
if (has(eventSystem["listeners"], event)) {
for (var i = 0; i < len(eventSystem["listeners"][event]); i++) {
if (eventSystem["listeners"][event][i] == callback) {
// Remove by shifting elements
for (var j = i; j < len(eventSystem["listeners"][event]) - 1; j++) {
eventSystem["listeners"][event][j] = eventSystem["listeners"][event][j + 1];
}
eventSystem["listeners"][event][len(eventSystem["listeners"][event]) - 1] = none;
return "Removed listener for " + event;
}
}
}
return "Listener not found for " + event;
};
return eventSystem;
}
var events = createEventSystem();
func logEvent(data) {
return "Logger: " + data;
}
func alertEvent(data) {
return "Alert: " + data + " happened!";
}
events["on"]("user_login", logEvent);
events["on"]("user_login", alertEvent);
events["on"]("error", logEvent);
print(events["emit"]("user_login", "Alice logged in"));
print(events["emit"]("error", "Database connection failed"));
print("\n=== ALL PSEUDO-OOP EXAMPLES COMPLETED ===");

View File

@ -1,3 +0,0 @@
#include "../headers/Expression.h"

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
#include "../headers/TypeWrapper.h"
#include <iostream>

44
src/headers/Executor.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "Statement.h"
class Evaluator; // Forward declaration
class Interpreter; // Forward declaration
/**
* @class Executor
* @brief Handles the execution of statements and control flow.
*
* Implements the StmtVisitor pattern. It is responsible for executing statements,
* managing environments, and handling control flow constructs like loops and
* conditionals. It uses the Evaluator to evaluate expressions when needed.
*/
class Executor : public StmtVisitor {
private:
Interpreter* interpreter; // Back-pointer to access interpreter services
Evaluator* evaluator; // For evaluating expressions
public:
Executor(Interpreter* interpreter, Evaluator* evaluator);
virtual ~Executor();
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
// Statement Visitors
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
private:
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
};

View File

@ -3,10 +3,10 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include "../headers/Lexer.h" #include "Lexer.h"
#include "../headers/Interpreter.h" #include "Interpreter.h"
#include "../headers/helperFunctions/ShortHands.h" #include "helperFunctions/ShortHands.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#define VERSION "0.0.3" #define VERSION "0.0.3"

View File

@ -18,13 +18,7 @@ struct BreakStmt;
struct ContinueStmt; struct ContinueStmt;
struct AssignStmt; struct AssignStmt;
struct ExecutionContext { #include "ExecutionContext.h"
bool isFunctionBody = false;
bool hasReturn = false;
bool hasBreak = false;
bool hasContinue = false;
Value returnValue;
};
struct StmtVisitor struct StmtVisitor
{ {

View File

@ -0,0 +1,38 @@
#pragma once
#include "Expression.h"
#include "Value.h"
class Interpreter; // Forward declaration for the back-pointer
/**
* @class Evaluator
* @brief Handles the logic for visiting and evaluating Expression AST nodes.
*
* Implements the Visitor pattern for all Expression types. This class
* contains the core evaluation logic for expressions, returning a Value.
*/
class Evaluator : public ExprVisitor {
private:
Interpreter* interpreter; // Back-pointer to access interpreter services like isTruthy, etc.
public:
explicit Evaluator(Interpreter* interpreter);
virtual ~Evaluator() = default;
// Expression Visitors
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
};

View File

@ -0,0 +1,10 @@
#pragma once
#include "Value.h"
struct ExecutionContext {
bool isFunctionBody = false;
bool hasReturn = false;
Value returnValue = NONE_VALUE;
bool shouldBreak = false;
bool shouldContinue = false;
};

View File

@ -0,0 +1,44 @@
#pragma once
#include "Statement.h"
class Evaluator; // Forward declaration
class Interpreter; // Forward declaration
/**
* @class Executor
* @brief Handles the execution of statements and control flow.
*
* Implements the StmtVisitor pattern. It is responsible for executing statements,
* managing environments, and handling control flow constructs like loops and
* conditionals. It uses the Evaluator to evaluate expressions when needed.
*/
class Executor : public StmtVisitor {
private:
Interpreter* interpreter; // Back-pointer to access interpreter services
Evaluator* evaluator; // For evaluating expressions
public:
Executor(Interpreter* interpreter, Evaluator* evaluator);
virtual ~Executor();
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
// Statement Visitors
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
private:
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
};

View File

@ -0,0 +1,101 @@
#pragma once
#include "Expression.h"
#include "Statement.h"
#include "helperFunctions/ShortHands.h"
#include "TypeWrapper.h"
#include "Environment.h"
#include "Value.h"
#include "BobStdLib.h"
#include "ErrorReporter.h"
#include "ExecutionContext.h"
#include "RuntimeDiagnostics.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include <stack>
#include <optional>
#include <functional>
// Forward declaration
class Evaluator;
// RAII helper for thunk execution flag
struct ScopedThunkFlag {
bool& flag;
bool prev;
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
~ScopedThunkFlag() { flag = prev; }
};
// RAII helper for environment management
struct ScopedEnv {
std::shared_ptr<Environment>& target;
std::shared_ptr<Environment> prev;
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
~ScopedEnv() { target = prev; }
};
// Thunk class for trampoline-based tail call optimization
struct Thunk {
public:
using ThunkFunction = std::function<Value()>;
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
Value execute() const {
return func();
}
bool isThunk() const { return true; }
private:
ThunkFunction func;
};
class Executor;
class Interpreter {
private:
std::shared_ptr<Environment> environment;
bool isInteractive;
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
std::vector<std::shared_ptr<Function>> functions;
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
ErrorReporter* errorReporter;
bool inThunkExecution = false;
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
std::unique_ptr<Evaluator> evaluator;
std::unique_ptr<Executor> executor;
public:
explicit Interpreter(bool isInteractive);
virtual ~Interpreter();
// Public interface for main
void interpret(std::vector<std::shared_ptr<Stmt>> statements);
void setErrorReporter(ErrorReporter* reporter);
// Methods needed by Evaluator
Value evaluate(const std::shared_ptr<Expr>& expr);
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
bool isTruthy(Value object);
bool isEqual(Value a, Value b);
std::string stringify(Value object);
bool isInteractiveMode() const;
std::shared_ptr<Environment> getEnvironment();
void setEnvironment(std::shared_ptr<Environment> env);
void addThunk(std::shared_ptr<Thunk> thunk);
void addFunction(std::shared_ptr<Function> function);
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
void cleanupUnusedFunctions();
void cleanupUnusedThunks();
void forceCleanup();
private:
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
void addStdLibFunctions();
Value runTrampoline(Value initialResult);
};

View File

@ -0,0 +1,38 @@
#pragma once
#include "Value.h"
#include <string>
#include <memory>
// Forward declarations from Value.h
struct Function;
struct BuiltinFunction;
struct Thunk;
/**
* RuntimeDiagnostics - Utility functions for runtime operations
*
* This class handles value conversion, equality checking, string representation,
* and other diagnostic utilities that don't belong in core evaluation logic.
*/
class RuntimeDiagnostics {
public:
RuntimeDiagnostics() = default;
// Value utility functions
bool isTruthy(Value object);
bool isEqual(Value a, Value b);
std::string stringify(Value object);
// Memory management utilities
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks);
private:
// Helper methods for stringify
std::string formatNumber(double value);
std::string formatArray(const std::vector<Value>& arr);
std::string formatDict(const std::unordered_map<std::string, Value>& dict);
};

434
src/runtime/Evaluator.cpp Normal file
View File

@ -0,0 +1,434 @@
#include "Evaluator.h"
#include "Interpreter.h"
#include "helperFunctions/HelperFunctions.h"
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
if (expr->isNull) {
return NONE_VALUE;
}
if (expr->isNumber) {
double num;
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
num = binaryStringToLong(expr->value);
} else {
num = std::stod(expr->value);
}
return Value(num);
}
if (expr->isBoolean) {
if (expr->value == "true") return TRUE_VALUE;
if (expr->value == "false") return FALSE_VALUE;
}
return Value(expr->value);
}
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
return interpreter->evaluate(expression->expression);
}
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
{
Value right = interpreter->evaluate(expression->right);
switch (expression->oper.type) {
case MINUS:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(-right.asNumber());
case BANG:
return Value(!interpreter->isTruthy(right));
case BIN_NOT:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
default:
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
}
}
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
Value left = interpreter->evaluate(expression->left);
Value right = interpreter->evaluate(expression->right);
// Handle logical operators (AND, OR) - these work with any types
if (expression->oper.type == AND) {
return interpreter->isTruthy(left) ? right : left;
}
if (expression->oper.type == OR) {
return interpreter->isTruthy(left) ? left : right;
}
// Handle equality operators - these work with any types
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
bool equal = interpreter->isEqual(left, right);
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
}
// Handle comparison operators - only work with numbers
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
if (left.isNumber() && right.isNumber()) {
double leftNum = left.asNumber();
double rightNum = right.asNumber();
switch (expression->oper.type) {
case GREATER: return Value(leftNum > rightNum);
case GREATER_EQUAL: return Value(leftNum >= rightNum);
case LESS: return Value(leftNum < rightNum);
case LESS_EQUAL: return Value(leftNum <= rightNum);
default: break; // Unreachable
}
}
// Error for non-number comparisons
std::string opName;
switch (expression->oper.type) {
case GREATER: opName = ">"; break;
case GREATER_EQUAL: opName = ">="; break;
case LESS: opName = "<"; break;
case LESS_EQUAL: opName = "<="; break;
default: break; // Unreachable
}
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
}
// Handle all other operators using Value's operator overloads
try {
switch (expression->oper.type) {
case PLUS: return left + right;
case MINUS: return left - right;
case STAR: return left * right;
case SLASH: return left / right;
case PERCENT: return left % right;
case BIN_AND: return left & right;
case BIN_OR: return left | right;
case BIN_XOR: return left ^ right;
case BIN_SLEFT: return left << right;
case BIN_SRIGHT: return left >> right;
default:
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
}
} catch (const std::runtime_error& e) {
// The Value operators provide good error messages, just add context
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
e.what(), expression->oper.lexeme);
throw;
}
}
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
{
return interpreter->getEnvironment()->get(expression->name);
}
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
// Get the current value of the operand
Value currentValue = interpreter->evaluate(expression->operand);
if (!currentValue.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
}
double currentNum = currentValue.asNumber();
double newValue;
// Determine the operation based on the operator
if (expression->oper.type == PLUS_PLUS) {
newValue = currentNum + 1.0;
} else if (expression->oper.type == MINUS_MINUS) {
newValue = currentNum - 1.0;
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Invalid increment/decrement operator.", "");
throw std::runtime_error("Invalid increment/decrement operator.");
}
// Update the variable or array element
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
// Handle array indexing increment/decrement
Value array = interpreter->evaluate(arrayExpr->array);
Value index = interpreter->evaluate(arrayExpr->index);
if (!array.isArray()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Can only index arrays", "");
throw std::runtime_error("Can only index arrays");
}
if (!index.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
"Runtime Error", "Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
// Update the array element
arr[idx] = Value(newValue);
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
}
// Return the appropriate value based on prefix/postfix
if (expression->isPrefix) {
return Value(newValue); // Prefix: return new value
} else {
return currentValue; // Postfix: return old value
}
}
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = interpreter->evaluate(expression->value);
switch (expression->op.type) {
case PLUS_EQUAL:
case MINUS_EQUAL:
case STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case BIN_AND_EQUAL:
case BIN_OR_EQUAL:
case BIN_XOR_EQUAL:
case BIN_SLEFT_EQUAL:
case BIN_SRIGHT_EQUAL: {
Value currentValue = interpreter->getEnvironment()->get(expression->name);
// ... (rest of compound assignment logic) ...
break;
}
default:
break;
}
interpreter->getEnvironment()->assign(expression->name, value);
return value;
}
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
Value condition = interpreter->evaluate(expression->condition);
if (interpreter->isTruthy(condition)) {
return interpreter->evaluate(expression->thenExpr);
} else {
return interpreter->evaluate(expression->elseExpr);
}
}
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
Value callee = expression->callee->accept(this);
std::vector<Value> arguments;
for (const auto& argument : expression->arguments) {
arguments.push_back(argument->accept(this));
}
if (callee.isFunction()) {
Function* function = callee.asFunction();
// Check arity
if (arguments.size() != function->params.size()) {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
std::to_string(arguments.size()) + ".", "");
throw std::runtime_error("Wrong number of arguments.");
}
// Create new environment for function call
auto environment = std::make_shared<Environment>(function->closure);
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
}
// Execute function body
auto previous = interpreter->getEnvironment();
interpreter->setEnvironment(environment);
ExecutionContext context;
context.isFunctionBody = true;
try {
for (const auto& stmt : function->body) {
interpreter->execute(stmt, &context);
if (context.hasReturn) {
interpreter->setEnvironment(previous);
return context.returnValue;
}
}
} catch (...) {
interpreter->setEnvironment(previous);
throw;
}
interpreter->setEnvironment(previous);
return NONE_VALUE;
} else if (callee.isBuiltinFunction()) {
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
} else {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Can only call functions and classes.", "");
throw std::runtime_error("Can only call functions and classes.");
}
}
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
std::vector<Value> elements;
for (const auto& element : expr->elements) {
elements.push_back(interpreter->evaluate(element));
}
return Value(elements);
}
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
Value array = expr->array->accept(this);
Value index = expr->index->accept(this);
if (array.isArray()) {
// Handle array indexing
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
const std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
return arr[idx];
} else if (array.isDict()) {
// Handle dictionary indexing
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
throw std::runtime_error("Dictionary key must be a string");
}
std::string key = index.asString();
const std::unordered_map<std::string, Value>& dict = array.asDict();
auto it = dict.find(key);
if (it != dict.end()) {
return it->second;
} else {
return NONE_VALUE; // Return none for missing keys
}
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only index arrays and dictionaries", "");
throw std::runtime_error("Can only index arrays and dictionaries");
}
}
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
Value array = expr->array->accept(this);
Value index = expr->index->accept(this);
Value value = expr->value->accept(this);
if (array.isArray()) {
// Handle array assignment
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
arr[idx] = value;
return value;
} else if (array.isDict()) {
// Handle dictionary assignment
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
throw std::runtime_error("Dictionary key must be a string");
}
std::string key = index.asString();
std::unordered_map<std::string, Value>& dict = array.asDict();
dict[key] = value;
return value;
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only assign to array or dictionary elements", "");
throw std::runtime_error("Can only assign to array or dictionary elements");
}
}
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
std::unordered_map<std::string, Value> dict;
for (const auto& pair : expr->pairs) {
Value value = interpreter->evaluate(pair.second);
dict[pair.first] = value;
}
return Value(dict);
}
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
std::vector<std::string> paramNames;
for (const Token& param : expression->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
interpreter->addFunction(function);
return Value(function.get());
}

245
src/runtime/Executor.cpp Normal file
View File

@ -0,0 +1,245 @@
#include "Executor.h"
#include "Evaluator.h"
#include "Interpreter.h"
#include <iostream>
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
: interpreter(interpreter), evaluator(evaluator) {}
Executor::~Executor() {}
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
for (const auto& statement : statements) {
execute(statement, nullptr);
}
}
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
statement->accept(this, context);
}
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
interpreter->setEnvironment(env);
for (const auto& statement : statements) {
execute(statement, context);
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
interpreter->setEnvironment(previous);
return;
}
}
interpreter->setEnvironment(previous);
}
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
executeBlock(statement->statements, newEnv, context);
}
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
Value value = statement->expression->accept(evaluator);
if (interpreter->isInteractiveMode())
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
}
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
Value value = NONE_VALUE;
if (statement->initializer != nullptr) {
value = statement->initializer->accept(evaluator);
}
interpreter->getEnvironment()->define(statement->name.lexeme, value);
}
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
std::vector<std::string> paramNames;
for (const Token& param : statement->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>(statement->name.lexeme,
paramNames,
statement->body,
interpreter->getEnvironment());
interpreter->addFunction(function);
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function.get()));
}
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
Value value = NONE_VALUE;
if (statement->value != nullptr) {
value = statement->value->accept(evaluator);
}
if (context && context->isFunctionBody) {
context->hasReturn = true;
context->returnValue = value;
}
}
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch, context);
}
}
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
}
}
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
do {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
}
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
if (statement->initializer != nullptr) {
execute(statement->initializer, context);
}
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
}
continue;
}
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
}
}
}
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
if (context) {
context->shouldBreak = true;
}
}
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
if (context) {
context->shouldContinue = true;
}
}
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
interpreter->getEnvironment()->assign(statement->name, value);
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(statement->name);
Value newValue;
switch (statement->op.type) {
case PLUS_EQUAL:
newValue = currentValue + value;
break;
case MINUS_EQUAL:
newValue = currentValue - value;
break;
case STAR_EQUAL:
newValue = currentValue * value;
break;
case SLASH_EQUAL:
newValue = currentValue / value;
break;
case PERCENT_EQUAL:
newValue = currentValue % value;
break;
case BIN_AND_EQUAL:
newValue = currentValue & value;
break;
case BIN_OR_EQUAL:
newValue = currentValue | value;
break;
case BIN_XOR_EQUAL:
newValue = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
newValue = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
newValue = currentValue >> value;
break;
default:
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
"Unknown assignment operator: " + statement->op.lexeme, "");
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
}
interpreter->getEnvironment()->assign(statement->name, newValue);
}
}

View File

@ -0,0 +1,96 @@
#include "Interpreter.h"
#include "Evaluator.h"
#include "Executor.h"
#include "BobStdLib.h"
#include <iostream>
Interpreter::Interpreter(bool isInteractive)
: isInteractive(isInteractive), errorReporter(nullptr) {
evaluator = std::make_unique<Evaluator>(this);
executor = std::make_unique<Executor>(this, evaluator.get());
environment = std::make_shared<Environment>();
}
Interpreter::~Interpreter() = default;
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
executor->interpret(statements);
}
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
statement->accept(executor.get(), context);
}
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
executor->executeBlock(statements, env, context);
}
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
Value result = expr->accept(evaluator.get());
if (inThunkExecution) {
return result;
}
return runTrampoline(result);
}
Value Interpreter::runTrampoline(Value initialResult) {
Value current = initialResult;
while (current.isThunk()) {
current = current.asThunk()->execute();
}
return current;
}
bool Interpreter::isTruthy(Value object) {
return diagnostics.isTruthy(object);
}
bool Interpreter::isEqual(Value a, Value b) {
return diagnostics.isEqual(a, b);
}
std::string Interpreter::stringify(Value object) {
return diagnostics.stringify(object);
}
void Interpreter::addStdLibFunctions() {
BobStdLib::addToEnvironment(environment, *this, errorReporter);
}
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
builtinFunctions.push_back(func);
}
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
thunks.push_back(thunk);
}
void Interpreter::addFunction(std::shared_ptr<Function> function) {
functions.push_back(function);
}
void Interpreter::setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
if (environment) {
environment->setErrorReporter(reporter);
}
addStdLibFunctions();
}
bool Interpreter::isInteractiveMode() const {
return isInteractive;
}
std::shared_ptr<Environment> Interpreter::getEnvironment() {
return environment;
}
void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
environment = env;
}
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
if (errorReporter) {
errorReporter->reportError(line, column, errorType, message, lexeme);
}
}

View File

@ -1,7 +1,7 @@
#include <utility> #include <utility>
#include "../headers/bob.h" #include "bob.h"
#include "../headers/Parser.h" #include "Parser.h"
void Bob::runFile(const std::string& path) void Bob::runFile(const std::string& path)
{ {

View File

@ -1,7 +1,7 @@
// //
// //
#include "../headers/bob.h" #include "bob.h"
int main(int argc, char* argv[]){ int main(int argc, char* argv[]){
Bob bobLang; Bob bobLang;

View File

@ -1,4 +1,4 @@
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
#include <string> #include <string>

View File

@ -0,0 +1,3 @@
#include "Expression.h"

View File

@ -1,6 +1,6 @@
#include "../headers/Lexer.h" #include "Lexer.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#include "../headers/helperFunctions/HelperFunctions.h" #include "helperFunctions/HelperFunctions.h"
#include <cctype> #include <cctype>
#include <stdexcept> #include <stdexcept>

View File

@ -1,5 +1,5 @@
#include "../headers/Parser.h" #include "Parser.h"
#include <stdexcept> #include <stdexcept>

View File

@ -1,5 +1,5 @@
#include "../headers/Environment.h" #include "Environment.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
void Environment::assign(const Token& name, const Value& value) { void Environment::assign(const Token& name, const Value& value) {
auto it = variables.find(name.lexeme); auto it = variables.find(name.lexeme);

View File

@ -0,0 +1,434 @@
#include "Evaluator.h"
#include "Interpreter.h"
#include "helperFunctions/HelperFunctions.h"
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
if (expr->isNull) {
return NONE_VALUE;
}
if (expr->isNumber) {
double num;
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
num = binaryStringToLong(expr->value);
} else {
num = std::stod(expr->value);
}
return Value(num);
}
if (expr->isBoolean) {
if (expr->value == "true") return TRUE_VALUE;
if (expr->value == "false") return FALSE_VALUE;
}
return Value(expr->value);
}
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
return interpreter->evaluate(expression->expression);
}
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
{
Value right = interpreter->evaluate(expression->right);
switch (expression->oper.type) {
case MINUS:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(-right.asNumber());
case BANG:
return Value(!interpreter->isTruthy(right));
case BIN_NOT:
if (!right.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
}
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
default:
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
}
}
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
Value left = interpreter->evaluate(expression->left);
Value right = interpreter->evaluate(expression->right);
// Handle logical operators (AND, OR) - these work with any types
if (expression->oper.type == AND) {
return interpreter->isTruthy(left) ? right : left;
}
if (expression->oper.type == OR) {
return interpreter->isTruthy(left) ? left : right;
}
// Handle equality operators - these work with any types
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
bool equal = interpreter->isEqual(left, right);
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
}
// Handle comparison operators - only work with numbers
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
if (left.isNumber() && right.isNumber()) {
double leftNum = left.asNumber();
double rightNum = right.asNumber();
switch (expression->oper.type) {
case GREATER: return Value(leftNum > rightNum);
case GREATER_EQUAL: return Value(leftNum >= rightNum);
case LESS: return Value(leftNum < rightNum);
case LESS_EQUAL: return Value(leftNum <= rightNum);
default: break; // Unreachable
}
}
// Error for non-number comparisons
std::string opName;
switch (expression->oper.type) {
case GREATER: opName = ">"; break;
case GREATER_EQUAL: opName = ">="; break;
case LESS: opName = "<"; break;
case LESS_EQUAL: opName = "<="; break;
default: break; // Unreachable
}
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
}
// Handle all other operators using Value's operator overloads
try {
switch (expression->oper.type) {
case PLUS: return left + right;
case MINUS: return left - right;
case STAR: return left * right;
case SLASH: return left / right;
case PERCENT: return left % right;
case BIN_AND: return left & right;
case BIN_OR: return left | right;
case BIN_XOR: return left ^ right;
case BIN_SLEFT: return left << right;
case BIN_SRIGHT: return left >> right;
default:
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
}
} catch (const std::runtime_error& e) {
// The Value operators provide good error messages, just add context
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
e.what(), expression->oper.lexeme);
throw;
}
}
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
{
return interpreter->getEnvironment()->get(expression->name);
}
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
// Get the current value of the operand
Value currentValue = interpreter->evaluate(expression->operand);
if (!currentValue.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
}
double currentNum = currentValue.asNumber();
double newValue;
// Determine the operation based on the operator
if (expression->oper.type == PLUS_PLUS) {
newValue = currentNum + 1.0;
} else if (expression->oper.type == MINUS_MINUS) {
newValue = currentNum - 1.0;
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Invalid increment/decrement operator.", "");
throw std::runtime_error("Invalid increment/decrement operator.");
}
// Update the variable or array element
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
// Handle array indexing increment/decrement
Value array = interpreter->evaluate(arrayExpr->array);
Value index = interpreter->evaluate(arrayExpr->index);
if (!array.isArray()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Can only index arrays", "");
throw std::runtime_error("Can only index arrays");
}
if (!index.isNumber()) {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
"Runtime Error", "Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
// Update the array element
arr[idx] = Value(newValue);
} else {
interpreter->reportError(expression->oper.line, expression->oper.column,
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
}
// Return the appropriate value based on prefix/postfix
if (expression->isPrefix) {
return Value(newValue); // Prefix: return new value
} else {
return currentValue; // Postfix: return old value
}
}
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = interpreter->evaluate(expression->value);
switch (expression->op.type) {
case PLUS_EQUAL:
case MINUS_EQUAL:
case STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case BIN_AND_EQUAL:
case BIN_OR_EQUAL:
case BIN_XOR_EQUAL:
case BIN_SLEFT_EQUAL:
case BIN_SRIGHT_EQUAL: {
Value currentValue = interpreter->getEnvironment()->get(expression->name);
// ... (rest of compound assignment logic) ...
break;
}
default:
break;
}
interpreter->getEnvironment()->assign(expression->name, value);
return value;
}
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
Value condition = interpreter->evaluate(expression->condition);
if (interpreter->isTruthy(condition)) {
return interpreter->evaluate(expression->thenExpr);
} else {
return interpreter->evaluate(expression->elseExpr);
}
}
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
Value callee = expression->callee->accept(this);
std::vector<Value> arguments;
for (const auto& argument : expression->arguments) {
arguments.push_back(argument->accept(this));
}
if (callee.isFunction()) {
Function* function = callee.asFunction();
// Check arity
if (arguments.size() != function->params.size()) {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
std::to_string(arguments.size()) + ".", "");
throw std::runtime_error("Wrong number of arguments.");
}
// Create new environment for function call
auto environment = std::make_shared<Environment>(function->closure);
for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]);
}
// Execute function body
auto previous = interpreter->getEnvironment();
interpreter->setEnvironment(environment);
ExecutionContext context;
context.isFunctionBody = true;
try {
for (const auto& stmt : function->body) {
interpreter->execute(stmt, &context);
if (context.hasReturn) {
interpreter->setEnvironment(previous);
return context.returnValue;
}
}
} catch (...) {
interpreter->setEnvironment(previous);
throw;
}
interpreter->setEnvironment(previous);
return NONE_VALUE;
} else if (callee.isBuiltinFunction()) {
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
} else {
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
"Can only call functions and classes.", "");
throw std::runtime_error("Can only call functions and classes.");
}
}
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
std::vector<Value> elements;
for (const auto& element : expr->elements) {
elements.push_back(interpreter->evaluate(element));
}
return Value(elements);
}
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
Value array = expr->array->accept(this);
Value index = expr->index->accept(this);
if (array.isArray()) {
// Handle array indexing
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
const std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
return arr[idx];
} else if (array.isDict()) {
// Handle dictionary indexing
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
throw std::runtime_error("Dictionary key must be a string");
}
std::string key = index.asString();
const std::unordered_map<std::string, Value>& dict = array.asDict();
auto it = dict.find(key);
if (it != dict.end()) {
return it->second;
} else {
return NONE_VALUE; // Return none for missing keys
}
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only index arrays and dictionaries", "");
throw std::runtime_error("Can only index arrays and dictionaries");
}
}
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
Value array = expr->array->accept(this);
Value index = expr->index->accept(this);
Value value = expr->value->accept(this);
if (array.isArray()) {
// Handle array assignment
if (!index.isNumber()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index must be a number", "");
throw std::runtime_error("Array index must be a number");
}
int idx = static_cast<int>(index.asNumber());
std::vector<Value>& arr = array.asArray();
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Array index out of bounds", "");
throw std::runtime_error("Array index out of bounds");
}
arr[idx] = value;
return value;
} else if (array.isDict()) {
// Handle dictionary assignment
if (!index.isString()) {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Dictionary key must be a string", "");
throw std::runtime_error("Dictionary key must be a string");
}
std::string key = index.asString();
std::unordered_map<std::string, Value>& dict = array.asDict();
dict[key] = value;
return value;
} else {
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
"Can only assign to array or dictionary elements", "");
throw std::runtime_error("Can only assign to array or dictionary elements");
}
}
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
std::unordered_map<std::string, Value> dict;
for (const auto& pair : expr->pairs) {
Value value = interpreter->evaluate(pair.second);
dict[pair.first] = value;
}
return Value(dict);
}
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
std::vector<std::string> paramNames;
for (const Token& param : expression->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
interpreter->addFunction(function);
return Value(function.get());
}

View File

@ -0,0 +1,245 @@
#include "Executor.h"
#include "Evaluator.h"
#include "Interpreter.h"
#include <iostream>
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
: interpreter(interpreter), evaluator(evaluator) {}
Executor::~Executor() {}
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
for (const auto& statement : statements) {
execute(statement, nullptr);
}
}
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
statement->accept(this, context);
}
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
interpreter->setEnvironment(env);
for (const auto& statement : statements) {
execute(statement, context);
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
interpreter->setEnvironment(previous);
return;
}
}
interpreter->setEnvironment(previous);
}
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
executeBlock(statement->statements, newEnv, context);
}
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
Value value = statement->expression->accept(evaluator);
if (interpreter->isInteractiveMode())
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
}
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
Value value = NONE_VALUE;
if (statement->initializer != nullptr) {
value = statement->initializer->accept(evaluator);
}
interpreter->getEnvironment()->define(statement->name.lexeme, value);
}
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
std::vector<std::string> paramNames;
for (const Token& param : statement->params) {
paramNames.push_back(param.lexeme);
}
auto function = std::make_shared<Function>(statement->name.lexeme,
paramNames,
statement->body,
interpreter->getEnvironment());
interpreter->addFunction(function);
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function.get()));
}
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
Value value = NONE_VALUE;
if (statement->value != nullptr) {
value = statement->value->accept(evaluator);
}
if (context && context->isFunctionBody) {
context->hasReturn = true;
context->returnValue = value;
}
}
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->thenBranch, context);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch, context);
}
}
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
}
}
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
do {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
continue;
}
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
}
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
if (statement->initializer != nullptr) {
execute(statement->initializer, context);
}
ExecutionContext loopContext;
if (context) {
loopContext.isFunctionBody = context->isFunctionBody;
}
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
execute(statement->body, &loopContext);
if (loopContext.hasReturn) {
if (context) {
context->hasReturn = true;
context->returnValue = loopContext.returnValue;
}
break;
}
if (loopContext.shouldBreak) {
break;
}
if (loopContext.shouldContinue) {
loopContext.shouldContinue = false;
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
}
continue;
}
if (statement->increment != nullptr) {
statement->increment->accept(evaluator);
}
}
}
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
if (context) {
context->shouldBreak = true;
}
}
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
if (context) {
context->shouldContinue = true;
}
}
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
interpreter->getEnvironment()->assign(statement->name, value);
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(statement->name);
Value newValue;
switch (statement->op.type) {
case PLUS_EQUAL:
newValue = currentValue + value;
break;
case MINUS_EQUAL:
newValue = currentValue - value;
break;
case STAR_EQUAL:
newValue = currentValue * value;
break;
case SLASH_EQUAL:
newValue = currentValue / value;
break;
case PERCENT_EQUAL:
newValue = currentValue % value;
break;
case BIN_AND_EQUAL:
newValue = currentValue & value;
break;
case BIN_OR_EQUAL:
newValue = currentValue | value;
break;
case BIN_XOR_EQUAL:
newValue = currentValue ^ value;
break;
case BIN_SLEFT_EQUAL:
newValue = currentValue << value;
break;
case BIN_SRIGHT_EQUAL:
newValue = currentValue >> value;
break;
default:
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
"Unknown assignment operator: " + statement->op.lexeme, "");
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
}
interpreter->getEnvironment()->assign(statement->name, newValue);
}
}

View File

@ -0,0 +1,96 @@
#include "Interpreter.h"
#include "Evaluator.h"
#include "Executor.h"
#include "BobStdLib.h"
#include <iostream>
Interpreter::Interpreter(bool isInteractive)
: isInteractive(isInteractive), errorReporter(nullptr) {
evaluator = std::make_unique<Evaluator>(this);
executor = std::make_unique<Executor>(this, evaluator.get());
environment = std::make_shared<Environment>();
}
Interpreter::~Interpreter() = default;
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
executor->interpret(statements);
}
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
statement->accept(executor.get(), context);
}
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
executor->executeBlock(statements, env, context);
}
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
Value result = expr->accept(evaluator.get());
if (inThunkExecution) {
return result;
}
return runTrampoline(result);
}
Value Interpreter::runTrampoline(Value initialResult) {
Value current = initialResult;
while (current.isThunk()) {
current = current.asThunk()->execute();
}
return current;
}
bool Interpreter::isTruthy(Value object) {
return diagnostics.isTruthy(object);
}
bool Interpreter::isEqual(Value a, Value b) {
return diagnostics.isEqual(a, b);
}
std::string Interpreter::stringify(Value object) {
return diagnostics.stringify(object);
}
void Interpreter::addStdLibFunctions() {
BobStdLib::addToEnvironment(environment, *this, errorReporter);
}
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
builtinFunctions.push_back(func);
}
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
thunks.push_back(thunk);
}
void Interpreter::addFunction(std::shared_ptr<Function> function) {
functions.push_back(function);
}
void Interpreter::setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
if (environment) {
environment->setErrorReporter(reporter);
}
addStdLibFunctions();
}
bool Interpreter::isInteractiveMode() const {
return isInteractive;
}
std::shared_ptr<Environment> Interpreter::getEnvironment() {
return environment;
}
void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
environment = env;
}
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
if (errorReporter) {
errorReporter->reportError(line, column, errorType, message, lexeme);
}
}

View File

@ -0,0 +1,215 @@
#include "RuntimeDiagnostics.h"
#include "Value.h"
#include "TypeWrapper.h" // For Function and BuiltinFunction definitions
#include <sstream>
#include <iomanip>
#include <limits>
#include <cmath>
#include <algorithm>
bool RuntimeDiagnostics::isTruthy(Value object) {
if(object.isBoolean()) {
return object.asBoolean();
}
if(object.isNone()) {
return false;
}
if(object.isNumber()) {
return object.asNumber() != 0;
}
if(object.isString()) {
return object.asString().length() > 0;
}
return true;
}
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
// Handle none comparisons first
if (a.isNone() || b.isNone()) {
return a.isNone() && b.isNone();
}
// Handle same type comparisons
if (a.isNumber() && b.isNumber()) {
return a.asNumber() == b.asNumber();
}
if (a.isBoolean() && b.isBoolean()) {
return a.asBoolean() == b.asBoolean();
}
if (a.isString() && b.isString()) {
return a.asString() == b.asString();
}
if (a.isArray() && b.isArray()) {
const std::vector<Value>& arrA = a.asArray();
const std::vector<Value>& arrB = b.asArray();
if (arrA.size() != arrB.size()) {
return false;
}
for (size_t i = 0; i < arrA.size(); i++) {
if (!isEqual(arrA[i], arrB[i])) {
return false;
}
}
return true;
}
if (a.isFunction() && b.isFunction()) {
// Functions are equal only if they are the same object
return a.asFunction() == b.asFunction();
}
if (a.isBuiltinFunction() && b.isBuiltinFunction()) {
// Builtin functions are equal only if they are the same object
return a.asBuiltinFunction() == b.asBuiltinFunction();
}
// Cross-type comparisons that make sense
if (a.isNumber() && b.isBoolean()) {
// Numbers and booleans: 0 and false are equal, non-zero and true are equal
if (b.asBoolean()) {
return a.asNumber() != 0.0;
} else {
return a.asNumber() == 0.0;
}
}
if (a.isBoolean() && b.isNumber()) {
// Same as above, but reversed
if (a.asBoolean()) {
return b.asNumber() != 0.0;
} else {
return b.asNumber() == 0.0;
}
}
// For all other type combinations, return false
return false;
}
std::string RuntimeDiagnostics::stringify(Value object) {
if(object.isNone()) {
return "none";
}
else if(object.isNumber()) {
return formatNumber(object.asNumber());
}
else if(object.isString()) {
return object.asString();
}
else if(object.isBoolean()) {
return object.asBoolean() == 1 ? "true" : "false";
}
else if(object.isFunction()) {
return "<function " + object.asFunction()->name + ">";
}
else if(object.isBuiltinFunction()) {
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
}
else if(object.isArray()) {
return formatArray(object.asArray());
}
else if(object.isDict()) {
return formatDict(object.asDict());
}
throw std::runtime_error("Could not convert object to string");
}
std::string RuntimeDiagnostics::formatNumber(double value) {
double integral = value;
double fractional = std::modf(value, &integral);
std::stringstream ss;
if(std::abs(fractional) < std::numeric_limits<double>::epsilon()) {
ss << std::fixed << std::setprecision(0) << integral;
return ss.str();
}
else {
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << value;
std::string str = ss.str();
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') {
str.pop_back();
}
return str;
}
}
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
std::string result = "[";
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0) result += ", ";
result += stringify(arr[i]);
}
result += "]";
return result;
}
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) {
std::string result = "{";
bool first = true;
for (const auto& pair : dict) {
if (!first) result += ", ";
result += "\"" + pair.first + "\": " + stringify(pair.second);
first = false;
}
result += "}";
return result;
}
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
// This is more conservative to prevent dangling pointer issues
functions.erase(
std::remove_if(functions.begin(), functions.end(),
[](const std::shared_ptr<BuiltinFunction>& func) {
return func.use_count() == 1; // Only referenced by this vector, nowhere else
}),
functions.end()
);
}
void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks) {
// Only remove thunks that are definitely not referenced anywhere (use_count == 1)
// This is more conservative to prevent dangling pointer issues
thunks.erase(
std::remove_if(thunks.begin(), thunks.end(),
[](const std::shared_ptr<Thunk>& thunk) {
return thunk.use_count() == 1; // Only referenced by this vector, nowhere else
}),
thunks.end()
);
}
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks) {
// More aggressive cleanup when breaking array references
functions.erase(
std::remove_if(functions.begin(), functions.end(),
[](const std::shared_ptr<BuiltinFunction>& func) {
return func.use_count() <= 2; // More aggressive than == 1
}),
functions.end()
);
thunks.erase(
std::remove_if(thunks.begin(), thunks.end(),
[](const std::shared_ptr<Thunk>& thunk) {
return thunk.use_count() <= 2; // More aggressive than == 1
}),
thunks.end()
);
}

View File

@ -0,0 +1,4 @@
#include "TypeWrapper.h"
#include <iostream>

View File

@ -1,4 +1,4 @@
#include "../headers/Value.h" #include "Value.h"
// Global constants for common values (no heap allocation) // Global constants for common values (no heap allocation)
const Value NONE_VALUE = Value(); const Value NONE_VALUE = Value();

View File

@ -1,8 +1,8 @@
#include "../headers/BobStdLib.h" #include "BobStdLib.h"
#include "../headers/Interpreter.h" #include "Interpreter.h"
#include "../headers/ErrorReporter.h" #include "ErrorReporter.h"
#include "../headers/Lexer.h" #include "Lexer.h"
#include "../headers/Parser.h" #include "Parser.h"
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <ctime> #include <ctime>

13
tco.bob
View File

@ -1,13 +0,0 @@
func countdown(n)
{
if(n == 0)
return 0;
print(n);
return countdown(n - 1);
}
countdown(1000000);

View File

@ -1,23 +0,0 @@
// Test to demonstrate bracket conflict between arrays and dictionaries
// Current working syntax
var arr = [10, 20, 30];
var dict = {"0": "zero", "1": "one", "2": "two"};
print("Array access:");
print(arr[0]); // 10
print(arr[1]); // 20
print("Dictionary access (current syntax):");
print(dict{"0"}); // zero
print(dict{"1"}); // one
print("If we used dict[\"0\"] instead of dict{\"0\"}:");
print("This would conflict because:");
print(" - arr[0] means array index 0");
print(" - dict[0] would mean dictionary key \"0\"");
print(" - The parser couldn't distinguish between them!");
print("Current syntax is clear:");
print(" - arr[0] = array indexing");
print(" - dict{\"0\"} = dictionary key access");