From 6c17ce96f046d31ac4e9cbd942bb26abe58ab4dc Mon Sep 17 00:00:00 2001 From: Bobby Lucero Date: Thu, 7 Aug 2025 17:10:20 -0400 Subject: [PATCH] Cleaned up project structure --- Makefile | 51 +- .../BOB_LANGUAGE_REFERENCE.md | 0 ROADMAP.md => Reference/ROADMAP.md | 0 benchmark.py | 30 - headers/Interpreter.h | 144 -- pseudo_oop_examples.bob | 339 ----- source/Expression.cpp | 3 - source/Interpreter.cpp | 1264 ----------------- source/TypeWrapper.cpp | 4 - src/headers/Executor.h | 44 + {headers => src/headers/cli}/bob.h | 8 +- .../common}/helperFunctions/ErrorUtils.h | 0 .../common}/helperFunctions/HelperFunctions.h | 0 .../common}/helperFunctions/ShortHands.h | 0 .../headers/parsing}/ErrorReporter.h | 0 {headers => src/headers/parsing}/Expression.h | 0 {headers => src/headers/parsing}/Lexer.h | 0 {headers => src/headers/parsing}/Parser.h | 0 {headers => src/headers/parsing}/Statement.h | 8 +- .../headers/runtime}/Environment.h | 0 src/headers/runtime/Evaluator.h | 38 + src/headers/runtime/ExecutionContext.h | 10 + src/headers/runtime/Executor.h | 44 + src/headers/runtime/Interpreter.h | 101 ++ src/headers/runtime/RuntimeDiagnostics.h | 38 + .../headers/runtime}/TypeWrapper.h | 0 {headers => src/headers/runtime}/Value.h | 0 {headers => src/headers/stdlib}/BobStdLib.h | 0 src/runtime/Evaluator.cpp | 434 ++++++ src/runtime/Executor.cpp | 245 ++++ src/runtime/Interpreter.cpp | 96 ++ {source => src/sources/cli}/bob.cpp | 4 +- {source => src/sources/cli}/main.cpp | 2 +- .../sources/parsing}/ErrorReporter.cpp | 2 +- src/sources/parsing/Expression.cpp | 3 + {source => src/sources/parsing}/Lexer.cpp | 6 +- {source => src/sources/parsing}/Parser.cpp | 2 +- .../sources/runtime}/Environment.cpp | 4 +- src/sources/runtime/Evaluator.cpp | 434 ++++++ src/sources/runtime/Executor.cpp | 245 ++++ src/sources/runtime/Interpreter.cpp | 96 ++ src/sources/runtime/RuntimeDiagnostics.cpp | 215 +++ src/sources/runtime/TypeWrapper.cpp | 4 + {source => src/sources/runtime}/Value.cpp | 2 +- {source => src/sources/stdlib}/BobStdLib.cpp | 10 +- tco.bob | 13 - test_bracket_conflict.bob | 23 - 47 files changed, 2097 insertions(+), 1869 deletions(-) rename BOB_LANGUAGE_REFERENCE.md => Reference/BOB_LANGUAGE_REFERENCE.md (100%) rename ROADMAP.md => Reference/ROADMAP.md (100%) delete mode 100644 benchmark.py delete mode 100644 headers/Interpreter.h delete mode 100644 pseudo_oop_examples.bob delete mode 100644 source/Expression.cpp delete mode 100644 source/Interpreter.cpp delete mode 100644 source/TypeWrapper.cpp create mode 100644 src/headers/Executor.h rename {headers => src/headers/cli}/bob.h (69%) rename {headers => src/headers/common}/helperFunctions/ErrorUtils.h (100%) rename {headers => src/headers/common}/helperFunctions/HelperFunctions.h (100%) rename {headers => src/headers/common}/helperFunctions/ShortHands.h (100%) rename {headers => src/headers/parsing}/ErrorReporter.h (100%) rename {headers => src/headers/parsing}/Expression.h (100%) rename {headers => src/headers/parsing}/Lexer.h (100%) rename {headers => src/headers/parsing}/Parser.h (100%) rename {headers => src/headers/parsing}/Statement.h (97%) rename {headers => src/headers/runtime}/Environment.h (100%) create mode 100644 src/headers/runtime/Evaluator.h create mode 100644 src/headers/runtime/ExecutionContext.h create mode 100644 src/headers/runtime/Executor.h create mode 100644 src/headers/runtime/Interpreter.h create mode 100644 src/headers/runtime/RuntimeDiagnostics.h rename {headers => src/headers/runtime}/TypeWrapper.h (100%) rename {headers => src/headers/runtime}/Value.h (100%) rename {headers => src/headers/stdlib}/BobStdLib.h (100%) create mode 100644 src/runtime/Evaluator.cpp create mode 100644 src/runtime/Executor.cpp create mode 100644 src/runtime/Interpreter.cpp rename {source => src/sources/cli}/bob.cpp (97%) rename {source => src/sources/cli}/main.cpp (86%) rename {source => src/sources/parsing}/ErrorReporter.cpp (99%) create mode 100644 src/sources/parsing/Expression.cpp rename {source => src/sources/parsing}/Lexer.cpp (99%) rename {source => src/sources/parsing}/Parser.cpp (99%) rename {source => src/sources/runtime}/Environment.cpp (94%) create mode 100644 src/sources/runtime/Evaluator.cpp create mode 100644 src/sources/runtime/Executor.cpp create mode 100644 src/sources/runtime/Interpreter.cpp create mode 100644 src/sources/runtime/RuntimeDiagnostics.cpp create mode 100644 src/sources/runtime/TypeWrapper.cpp rename {source => src/sources/runtime}/Value.cpp (89%) rename {source => src/sources/stdlib}/BobStdLib.cpp (99%) delete mode 100644 tco.bob delete mode 100644 test_bracket_conflict.bob diff --git a/Makefile b/Makefile index 291f369..7fad88e 100644 --- a/Makefile +++ b/Makefile @@ -1,48 +1,55 @@ -# Makefile +# Makefile - Bob language interpreter # Compiler CC = g++ -# Compiler flags +# Compiler flags CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native -# Source directory -SRC_DIR = ./source +# Source layout: +# 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 -# Find all C++ files recursively in the source directory -CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp') +# Find all .cpp files under src/sources (recursively) +CPP_FILES := $(shell find $(SRC_ROOT)/sources -type f -name '*.cpp') -# Generate object file names by replacing the source directory with the build directory -OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES)) +# Map every source file to its corresponding object file inside build/ +# 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))) -# Default target +# Default goal all: build -# Rule to create necessary directories -$(DIRS): - mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@) +# Pattern rule – compile each .cpp from sources/ into a mirrored .o inside build/ +$(BUILD_DIR)/%.o: $(SRC_ROOT)/sources/%.cpp + @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 -$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp - $(CC) $(CFLAGS) -c $< -o $@ - -# Rule to link object files into the final executable +# Link all objects into the final executable $(BUILD_DIR)/bob: $(OBJ_FILES) $(CC) $(CFLAGS) $^ -o $@ - +# Convenience targets run: ./$(BUILD_DIR)/bob build: clean $(BUILD_DIR)/bob - -# Clean build directory clean: rm -rf $(BUILD_DIR)/* diff --git a/BOB_LANGUAGE_REFERENCE.md b/Reference/BOB_LANGUAGE_REFERENCE.md similarity index 100% rename from BOB_LANGUAGE_REFERENCE.md rename to Reference/BOB_LANGUAGE_REFERENCE.md diff --git a/ROADMAP.md b/Reference/ROADMAP.md similarity index 100% rename from ROADMAP.md rename to Reference/ROADMAP.md diff --git a/benchmark.py b/benchmark.py deleted file mode 100644 index 40a2348..0000000 --- a/benchmark.py +++ /dev/null @@ -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!") \ No newline at end of file diff --git a/headers/Interpreter.h b/headers/Interpreter.h deleted file mode 100644 index 4acca7b..0000000 --- a/headers/Interpreter.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -// 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& target; - std::shared_ptr prev; - ScopedEnv(std::shared_ptr& e) : target(e), prev(e) {} - ~ScopedEnv() { target = prev; } -}; - -// Thunk class for trampoline-based tail call optimization -struct Thunk { -public: - using ThunkFunction = std::function; - - 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& expression) override; - Value visitCallExpr(const std::shared_ptr& expression) override; - Value visitFunctionExpr(const std::shared_ptr& expression) override; - Value visitGroupingExpr(const std::shared_ptr& expression) override; - Value visitLiteralExpr(const std::shared_ptr& expression) override; - Value visitUnaryExpr(const std::shared_ptr& expression) override; - Value visitVarExpr(const std::shared_ptr& expression) override; - Value visitIncrementExpr(const std::shared_ptr& expression) override; - Value visitAssignExpr(const std::shared_ptr& expression) override; - Value visitTernaryExpr(const std::shared_ptr& expression) override; - Value visitArrayLiteralExpr(const std::shared_ptr& expression) override; - Value visitArrayIndexExpr(const std::shared_ptr& expression) override; - Value visitArrayAssignExpr(const std::shared_ptr& expression) override; - Value visitDictLiteralExpr(const std::shared_ptr& expression) override; - - void visitBlockStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitExpressionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitVarStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitFunctionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitReturnStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitIfStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitDoWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitForStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitBreakStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - void visitAssignStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; - - void interpret(std::vector> statements); - - explicit Interpreter(bool isInteractive) : isInteractive(isInteractive), errorReporter(nullptr){ - environment = std::make_shared(); - } - virtual ~Interpreter() = default; - -private: - std::shared_ptr environment; - bool isInteractive; - std::vector> builtinFunctions; - std::vector> functions; - std::vector> 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); - Value evaluateWithoutTrampoline(const std::shared_ptr& expr); - bool isEqual(Value a, Value b); - - void execute(const std::shared_ptr& statement, ExecutionContext* context = nullptr); - void executeBlock(std::vector> statements, std::shared_ptr 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 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(); - } - - -}; diff --git a/pseudo_oop_examples.bob b/pseudo_oop_examples.bob deleted file mode 100644 index 6ad11cc..0000000 --- a/pseudo_oop_examples.bob +++ /dev/null @@ -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 ==="); \ No newline at end of file diff --git a/source/Expression.cpp b/source/Expression.cpp deleted file mode 100644 index 216c862..0000000 --- a/source/Expression.cpp +++ /dev/null @@ -1,3 +0,0 @@ - - -#include "../headers/Expression.h" diff --git a/source/Interpreter.cpp b/source/Interpreter.cpp deleted file mode 100644 index b69b6c2..0000000 --- a/source/Interpreter.cpp +++ /dev/null @@ -1,1264 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../headers/Interpreter.h" -#include "../headers/helperFunctions/HelperFunctions.h" -#include "../headers/BobStdLib.h" - -// Return context for managing function return values -struct ReturnContext { - Value returnValue; - bool hasReturn; - ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {} -}; - -// Trampoline-based tail call optimization to prevent stack overflow - - - - -Value Interpreter::visitLiteralExpr(const std::shared_ptr& 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& expression) { - return evaluate(expression->expression); -} - -Value Interpreter::visitUnaryExpr(const std::shared_ptr& expression) -{ - Value right = evaluate(expression->right); - - switch (expression->oper.type) { - case MINUS: - if (!right.isNumber()) { - if (errorReporter) { - errorReporter->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(!isTruthy(right)); - - case BIN_NOT: - if (!right.isNumber()) { - if (errorReporter) { - errorReporter->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(~(static_cast(right.asNumber())))); - - default: - if (errorReporter) { - errorReporter->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 Interpreter::visitBinaryExpr(const std::shared_ptr& expression) { - Value left = evaluate(expression->left); - Value right = evaluate(expression->right); - - // Handle logical operators (AND, OR) - these work with any types - if (expression->oper.type == AND) { - return isTruthy(left) ? right : left; - } - if (expression->oper.type == OR) { - return 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 = 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); - } - } - - // 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; - } - - if (errorReporter) { - errorReporter->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: - if (errorReporter) { - errorReporter->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 - if (errorReporter) { - errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error", - e.what(), expression->oper.lexeme); - } - throw; - } -} - -Value Interpreter::visitVarExpr(const std::shared_ptr& expression) -{ - return environment->get(expression->name); -} - -Value Interpreter::visitIncrementExpr(const std::shared_ptr& 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 or array element - if (auto varExpr = std::dynamic_pointer_cast(expression->operand)) { - environment->assign(varExpr->name, Value(newValue)); - } else if (auto arrayExpr = std::dynamic_pointer_cast(expression->operand)) { - // Handle array indexing increment/decrement - Value array = evaluate(arrayExpr->array); - Value index = evaluate(arrayExpr->index); - - if (!array.isArray()) { - if (errorReporter) { - errorReporter->reportError(expression->oper.line, expression->oper.column, - "Runtime Error", "Can only index arrays", ""); - } - throw std::runtime_error("Can only index arrays"); - } - - if (!index.isNumber()) { - if (errorReporter) { - errorReporter->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(index.asNumber()); - std::vector& arr = array.asArray(); - - if (idx < 0 || idx >= static_cast(arr.size())) { - if (errorReporter) { - errorReporter->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 { - if (errorReporter) { - errorReporter->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 -} -} - -void Interpreter::addStdLibFunctions() { - // Add standard library functions to the environment - BobStdLib::addToEnvironment(environment, *this, errorReporter); -} - -void Interpreter::addBuiltinFunction(std::shared_ptr func) { - builtinFunctions.push_back(func); -} - - - -Value Interpreter::visitAssignExpr(const std::shared_ptr& 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); - - // Check if the operation is supported before attempting it - std::string opName; - switch (expression->op.type) { - case PLUS_EQUAL: opName = "+="; break; - case MINUS_EQUAL: opName = "-="; break; - case STAR_EQUAL: opName = "*="; break; - case SLASH_EQUAL: opName = "/="; break; - case PERCENT_EQUAL: opName = "%="; break; - case BIN_AND_EQUAL: opName = "&="; break; - case BIN_OR_EQUAL: opName = "|="; break; - case BIN_XOR_EQUAL: opName = "^="; break; - case BIN_SLEFT_EQUAL: opName = "<<="; break; - case BIN_SRIGHT_EQUAL: opName = ">>="; break; - default: opName = expression->op.lexeme; break; - } - - // Check if the operation is supported for these types - bool operationSupported = false; - switch (expression->op.type) { - case PLUS_EQUAL: - operationSupported = (currentValue.isNumber() && value.isNumber()) || - (currentValue.isString() && value.isString()) || - (currentValue.isString() && value.isNumber()) || - (currentValue.isNumber() && value.isString()) || - (currentValue.isString() && value.isNone()) || - (currentValue.isNone() && value.isString()) || - (currentValue.isString() && !value.isString() && !value.isNumber()) || - (!currentValue.isString() && !currentValue.isNumber() && value.isString()); - break; - case MINUS_EQUAL: - case PERCENT_EQUAL: - case BIN_AND_EQUAL: - case BIN_OR_EQUAL: - case BIN_XOR_EQUAL: - case BIN_SLEFT_EQUAL: - case BIN_SRIGHT_EQUAL: - operationSupported = currentValue.isNumber() && value.isNumber(); - break; - case STAR_EQUAL: - operationSupported = (currentValue.isNumber() && value.isNumber()) || - (currentValue.isString() && value.isNumber()) || - (currentValue.isNumber() && value.isString()); - break; - case SLASH_EQUAL: - operationSupported = currentValue.isNumber() && value.isNumber(); - break; - default: - operationSupported = false; - break; - } - - if (!operationSupported) { - if (errorReporter) { - errorReporter->reportError(expression->op.line, expression->op.column, "Runtime Error", - ErrorUtils::makeOperatorError(opName, currentValue.getType(), value.getType()), - expression->op.lexeme); - } - throw std::runtime_error(ErrorUtils::makeOperatorError(opName, currentValue.getType(), value.getType())); - } - - // Perform the operation - 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::visitTernaryExpr(const std::shared_ptr& expression) { - Value condition = evaluate(expression->condition); - - if (isTruthy(condition)) { - return evaluate(expression->thenExpr); - } else { - return evaluate(expression->elseExpr); - } -} - -Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { - Value callee = evaluate(expression->callee); - - std::vector arguments; - for (const std::shared_ptr& 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()) { - if (errorReporter) { - errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error", - "Expected " + std::to_string(function->params.size()) + - " arguments but got " + std::to_string(arguments.size()) + ".", ""); - } - throw std::runtime_error("Expected " + std::to_string(function->params.size()) + - " arguments but got " + std::to_string(arguments.size()) + "."); - } - - // Check if this is a tail call - if (expression->isTailCall) { - // Create a thunk for tail call optimization using smart pointer - auto thunk = std::make_shared([this, function, arguments]() -> Value { - // Use RAII to manage environment - ScopedEnv _env(environment); - environment = std::make_shared(function->closure); - environment->setErrorReporter(errorReporter); - - for (size_t i = 0; i < function->params.size(); i++) { - environment->define(function->params[i], arguments[i]); - } - - ExecutionContext context; - context.isFunctionBody = true; - - // Use RAII to manage thunk execution flag - ScopedThunkFlag _inThunk(inThunkExecution); - - // Execute function body - for (const auto& stmt : function->body) { - execute(stmt, &context); - if (context.hasReturn) { - return context.returnValue; - } - } - - return context.returnValue; - }); - - // Store the thunk to keep it alive and return as Value - thunks.push_back(thunk); - - // Automatic cleanup check - thunkCreationCount++; - if (thunkCreationCount >= CLEANUP_THRESHOLD) { - cleanupUnusedThunks(); - thunkCreationCount = 0; - } - - return Value(thunk.get()); - } else { - // Normal function call - create new environment - ScopedEnv _env(environment); - environment = std::make_shared(function->closure); - environment->setErrorReporter(errorReporter); - - for (size_t i = 0; i < function->params.size(); i++) { - environment->define(function->params[i], arguments[i]); - } - - ExecutionContext context; - context.isFunctionBody = true; - - // Execute function body - for (const auto& stmt : function->body) { - execute(stmt, &context); - if (context.hasReturn) { - return context.returnValue; - } - } - - return context.returnValue; - } - } - - // Provide better error message with type information - std::string errorMsg = "Can only call functions, got " + callee.getType(); - if (errorReporter) { - errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error", - errorMsg, ""); - } - throw std::runtime_error(errorMsg); -} - -Value Interpreter::visitArrayLiteralExpr(const std::shared_ptr& expr) { - std::vector elements; - - for (const auto& element : expr->elements) { - elements.push_back(evaluate(element)); - } - - return Value(elements); -} - -Value Interpreter::visitArrayIndexExpr(const std::shared_ptr& expr) { - Value container = evaluate(expr->array); - Value index = evaluate(expr->index); - - if (container.isArray()) { - // Array indexing - if (!index.isNumber()) { - if (errorReporter) { - errorReporter->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(index.asNumber()); - const std::vector& arr = container.asArray(); - - if (idx < 0 || static_cast(idx) >= arr.size()) { - if (errorReporter) { - errorReporter->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 (container.isString()) { - // String indexing - if (!index.isNumber()) { - if (errorReporter) { - errorReporter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "String index must be a number", ""); - } - throw std::runtime_error("String index must be a number"); - } - - int idx = static_cast(index.asNumber()); - const std::string& str = container.asString(); - - if (idx < 0 || static_cast(idx) >= str.length()) { - if (errorReporter) { - errorReporter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "String index out of bounds", ""); - } - throw std::runtime_error("String index out of bounds"); - } - - return Value(std::string(1, str[idx])); - } else if (container.isDict()) { - // Dictionary indexing - if (!index.isString()) { - if (errorReporter) { - errorReporter->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"); - } - - const std::unordered_map& dict = container.asDict(); - auto it = dict.find(index.asString()); - - if (it == dict.end()) { - return NONE_VALUE; // Return none for missing keys - } - - return it->second; - } else { - if (errorReporter) { - errorReporter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "Can only index arrays, strings, and dictionaries", ""); - } - throw std::runtime_error("Can only index arrays, strings, and dictionaries"); - } -} - -Value Interpreter::visitArrayAssignExpr(const std::shared_ptr& expr) { - Value container = evaluate(expr->array); - Value index = evaluate(expr->index); - Value value = evaluate(expr->value); - - if (container.isArray()) { - // Array assignment - if (!index.isNumber()) { - if (errorReporter) { - errorReporter->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(index.asNumber()); - std::vector& arr = container.asArray(); - - if (idx < 0 || static_cast(idx) >= arr.size()) { - if (errorReporter) { - errorReporter->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 (container.isString()) { - // String assignment - strings are immutable - if (errorReporter) { - errorReporter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "Cannot assign to string indices - strings are immutable", ""); - } - throw std::runtime_error("Cannot assign to string indices - strings are immutable"); - } else if (container.isDict()) { - // Dictionary assignment - if (!index.isString()) { - if (errorReporter) { - errorReporter->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::unordered_map& dict = container.asDict(); - dict[index.asString()] = value; - return value; - } else { - if (errorReporter) { - errorReporter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error", - "Can only assign to arrays and dictionaries", ""); - } - throw std::runtime_error("Can only assign to arrays and dictionaries"); - } -} - - -Value Interpreter::visitDictLiteralExpr(const std::shared_ptr& expr) { - std::unordered_map dict; - - for (const auto& pair : expr->pairs) { - Value value = evaluate(pair.second); - dict[pair.first] = value; - } - - return Value(dict); -} - -Value Interpreter::visitFunctionExpr(const std::shared_ptr& expression) { - // Convert Token parameters to string parameters - std::vector paramNames; - for (const Token& param : expression->params) { - paramNames.push_back(param.lexeme); - } - - // Create a snapshot of the current environment for proper closure behavior - auto closureEnv = std::make_shared(*environment); - closureEnv->setErrorReporter(errorReporter); - - auto function = msptr(Function)("anonymous", paramNames, expression->body, closureEnv); - functions.push_back(function); // Keep the shared_ptr alive - - // Automatic cleanup check - functionCreationCount++; - if (functionCreationCount >= CLEANUP_THRESHOLD) { - cleanupUnusedFunctions(); - functionCreationCount = 0; - } - - return Value(function.get()); -} - -void Interpreter::visitBlockStmt(const std::shared_ptr& statement, ExecutionContext* context) { - auto newEnv = std::make_shared(environment); - newEnv->setErrorReporter(errorReporter); - executeBlock(statement->statements, newEnv, context); -} - -void Interpreter::visitExpressionStmt(const std::shared_ptr& statement, ExecutionContext* context) { - Value value = evaluate(statement->expression); - - if(isInteractive) - std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m\n"; -} - -void Interpreter::visitVarStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - Value value = NONE_VALUE; - if(statement->initializer != nullptr) - { - value = evaluate(statement->initializer); - } - - - - environment->define(statement->name.lexeme, value); -} - -void Interpreter::visitFunctionStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - // Convert Token parameters to string parameters - std::vector paramNames; - for (const Token& param : statement->params) { - paramNames.push_back(param.lexeme); - } - - // For named functions, use the current environment (not a snapshot) - // This allows mutual recursion and forward references - 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())); - - // Automatic cleanup check - functionCreationCount++; - if (functionCreationCount >= CLEANUP_THRESHOLD) { - cleanupUnusedFunctions(); - functionCreationCount = 0; - } -} - -void Interpreter::visitReturnStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - Value value = NONE_VALUE; - if (statement->value != nullptr) { - // For tail calls, the trampoline handling is done in visitCallExpr - // We just need to evaluate normally - 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& statement, ExecutionContext* context) -{ - if (isTruthy(evaluate(statement->condition))) { - execute(statement->thenBranch, context); - } else if (statement->elseBranch != nullptr) { - execute(statement->elseBranch, context); - } -} - -void Interpreter::visitWhileStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - ExecutionContext loopContext; - if (context) { - loopContext.isFunctionBody = context->isFunctionBody; - } - - while (isTruthy(evaluate(statement->condition))) { - execute(statement->body, &loopContext); - - // Check for return from function - if (loopContext.hasReturn) { - if (context) { - context->hasReturn = true; - context->returnValue = loopContext.returnValue; - } - break; - } - - // Check for break - if (loopContext.hasBreak) { - loopContext.hasBreak = false; - break; - } - - // Check for continue (just continue to next iteration) - if (loopContext.hasContinue) { - loopContext.hasContinue = false; - continue; - } - } -} - -void Interpreter::visitDoWhileStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - ExecutionContext loopContext; - if (context) { - loopContext.isFunctionBody = context->isFunctionBody; - } - - do { - execute(statement->body, &loopContext); - - // Check for return from function - if (loopContext.hasReturn) { - if (context) { - context->hasReturn = true; - context->returnValue = loopContext.returnValue; - } - break; - } - - // Check for break - if (loopContext.hasBreak) { - loopContext.hasBreak = false; - break; - } - - // Check for continue (just continue to next iteration) - if (loopContext.hasContinue) { - loopContext.hasContinue = false; - continue; - } - } while (isTruthy(evaluate(statement->condition))); -} - -void Interpreter::visitForStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - // For loop implementation - executes initializer, condition, body, and increment - if (statement->initializer != nullptr) { - execute(statement->initializer, context); - } - - ExecutionContext loopContext; - if (context) { - loopContext.isFunctionBody = context->isFunctionBody; - } - - while (statement->condition == nullptr || isTruthy(evaluate(statement->condition))) { - execute(statement->body, &loopContext); - - // Check for return from function - if (loopContext.hasReturn) { - if (context) { - context->hasReturn = true; - context->returnValue = loopContext.returnValue; - } - break; - } - - // Check for break - if (loopContext.hasBreak) { - loopContext.hasBreak = false; - break; - } - - // Check for continue (execute increment then continue to next iteration) - if (loopContext.hasContinue) { - loopContext.hasContinue = false; - if (statement->increment != nullptr) { - evaluate(statement->increment); - } - continue; - } - - if (statement->increment != nullptr) { - evaluate(statement->increment); - } - } -} - -void Interpreter::visitBreakStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - if (context) { - context->hasBreak = true; - } -} - -void Interpreter::visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - if (context) { - context->hasContinue = true; - } -} - -void Interpreter::visitAssignStmt(const std::shared_ptr& statement, ExecutionContext* context) -{ - Value value = evaluate(statement->value); - - // Handle different assignment operators - if (statement->op.type == EQUAL) { - // Check if the variable previously held an array - Value oldValue = environment->get(statement->name.lexeme); - if (oldValue.isArray()) { - forceCleanup(); // Clean up when breaking array references - } - - // Simple assignment - environment->assign(statement->name, value); - } else { - // Compound assignment - get current value first - Value currentValue = environment->get(statement->name.lexeme); - - // Check if the operation is supported before attempting it - std::string opName; - switch (statement->op.type) { - case PLUS_EQUAL: opName = "+="; break; - case MINUS_EQUAL: opName = "-="; break; - case STAR_EQUAL: opName = "*="; break; - case SLASH_EQUAL: opName = "/="; break; - case PERCENT_EQUAL: opName = "%="; break; - case BIN_AND_EQUAL: opName = "&="; break; - case BIN_OR_EQUAL: opName = "|="; break; - case BIN_XOR_EQUAL: opName = "^="; break; - case BIN_SLEFT_EQUAL: opName = "<<="; break; - case BIN_SRIGHT_EQUAL: opName = ">>="; break; - default: opName = statement->op.lexeme; break; - } - - // Check if the operation is supported for these types - bool operationSupported = false; - switch (statement->op.type) { - case PLUS_EQUAL: - operationSupported = (currentValue.isNumber() && value.isNumber()) || - (currentValue.isString() && value.isString()) || - (currentValue.isString() && value.isNumber()) || - (currentValue.isNumber() && value.isString()) || - (currentValue.isString() && value.isNone()) || - (currentValue.isNone() && value.isString()) || - (currentValue.isString() && !value.isString() && !value.isNumber()) || - (!currentValue.isString() && !currentValue.isNumber() && value.isString()); - break; - case MINUS_EQUAL: - case PERCENT_EQUAL: - case BIN_AND_EQUAL: - case BIN_OR_EQUAL: - case BIN_XOR_EQUAL: - case BIN_SLEFT_EQUAL: - case BIN_SRIGHT_EQUAL: - operationSupported = currentValue.isNumber() && value.isNumber(); - break; - case STAR_EQUAL: - operationSupported = (currentValue.isNumber() && value.isNumber()) || - (currentValue.isString() && value.isNumber()) || - (currentValue.isNumber() && value.isString()); - break; - case SLASH_EQUAL: - operationSupported = currentValue.isNumber() && value.isNumber(); - break; - default: - operationSupported = false; - break; - } - - if (!operationSupported) { - if (errorReporter) { - errorReporter->reportError(statement->op.line, statement->op.column, "Runtime Error", - ErrorUtils::makeOperatorError(opName, currentValue.getType(), value.getType()), - statement->op.lexeme); - } - throw std::runtime_error(ErrorUtils::makeOperatorError(opName, currentValue.getType(), value.getType())); - } - - // Apply the compound operation - Value result; - if (statement->op.type == PLUS_EQUAL) { - result = currentValue + value; - } else if (statement->op.type == MINUS_EQUAL) { - result = currentValue - value; - } else if (statement->op.type == STAR_EQUAL) { - result = currentValue * value; - } else if (statement->op.type == SLASH_EQUAL) { - result = currentValue / value; - } else if (statement->op.type == PERCENT_EQUAL) { - result = currentValue % value; - } else if (statement->op.type == BIN_AND_EQUAL) { - result = currentValue & value; - } else if (statement->op.type == BIN_OR_EQUAL) { - result = currentValue | value; - } else if (statement->op.type == BIN_XOR_EQUAL) { - result = currentValue ^ value; - } else if (statement->op.type == BIN_SLEFT_EQUAL) { - result = currentValue << value; - } else if (statement->op.type == BIN_SRIGHT_EQUAL) { - result = currentValue >> value; - } else { - throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme); - } - - environment->assign(statement->name, result); - } -} - -void Interpreter::interpret(std::vector> statements) { - for(const std::shared_ptr& s : statements) - { - execute(s, nullptr); // No context needed for top-level execution - } -} - -void Interpreter::execute(const std::shared_ptr& statement, ExecutionContext* context) -{ - statement->accept(this, context); -} - -void Interpreter::executeBlock(std::vector> statements, std::shared_ptr env, ExecutionContext* context) -{ - std::shared_ptr previous = this->environment; - this->environment = env; - - for(const std::shared_ptr& s : statements) - { - execute(s, context); - if (context && (context->hasReturn || context->hasBreak || context->hasContinue)) { - this->environment = previous; - return; - } - } - - this->environment = previous; -} - -Value Interpreter::evaluate(const std::shared_ptr& expr) { - Value result = expr->accept(this); - if (inThunkExecution) { - return result; // Don't use trampoline when inside a thunk - } - return runTrampoline(result); -} - -Value Interpreter::evaluateWithoutTrampoline(const std::shared_ptr& expr) { - return expr->accept(this); -} - -Value Interpreter::runTrampoline(Value initialResult) { - Value current = initialResult; - - while (current.isThunk()) { - // Execute the thunk to get the next result - current = current.asThunk()->execute(); - } - - return current; -} - -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) { - // 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& arrA = a.asArray(); - const std::vector& 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 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::epsilon()) - { - ss << std::fixed << std::setprecision(0) << integral; - return ss.str(); - } - else - { - ss << std::fixed << std::setprecision(std::numeric_limits::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 "name + ">"; - } - else if(object.isBuiltinFunction()) - { - return "name + ">"; - } - else if(object.isArray()) - { - const std::vector& arr = object.asArray(); - std::string result = "["; - - for (size_t i = 0; i < arr.size(); i++) { - if (i > 0) result += ", "; - result += stringify(arr[i]); - } - - result += "]"; - return result; - } - else if(object.isDict()) - { - const std::unordered_map& dict = object.asDict(); - 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; - } - - throw std::runtime_error("Could not convert object to string"); -} - - - -void Interpreter::cleanupUnusedFunctions() { - // 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& func) { - return func.use_count() == 1; // Only referenced by this vector, nowhere else - }), - functions.end() - ); -} - -void Interpreter::cleanupUnusedThunks() { - // 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) { - return thunk.use_count() == 1; // Only referenced by this vector, nowhere else - }), - thunks.end() - ); -} - -void Interpreter::forceCleanup() { - // More aggressive cleanup when breaking array references - functions.erase( - std::remove_if(functions.begin(), functions.end(), - [](const std::shared_ptr& 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) { - return thunk.use_count() <= 2; // More aggressive than == 1 - }), - thunks.end() - ); -} - - - - - - - - - - - - - - - - - diff --git a/source/TypeWrapper.cpp b/source/TypeWrapper.cpp deleted file mode 100644 index c9660c9..0000000 --- a/source/TypeWrapper.cpp +++ /dev/null @@ -1,4 +0,0 @@ - -#include "../headers/TypeWrapper.h" -#include - diff --git a/src/headers/Executor.h b/src/headers/Executor.h new file mode 100644 index 0000000..bea38a5 --- /dev/null +++ b/src/headers/Executor.h @@ -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>& statements); + void executeBlock(const std::vector>& statements, std::shared_ptr env, ExecutionContext* context); + + // Statement Visitors + void visitBlockStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitExpressionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitVarStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitFunctionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitReturnStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitIfStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitDoWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitForStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitBreakStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitAssignStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + +private: + void execute(const std::shared_ptr& statement, ExecutionContext* context); +}; \ No newline at end of file diff --git a/headers/bob.h b/src/headers/cli/bob.h similarity index 69% rename from headers/bob.h rename to src/headers/cli/bob.h index 911e9ca..ea69725 100644 --- a/headers/bob.h +++ b/src/headers/cli/bob.h @@ -3,10 +3,10 @@ #include #include #include -#include "../headers/Lexer.h" -#include "../headers/Interpreter.h" -#include "../headers/helperFunctions/ShortHands.h" -#include "../headers/ErrorReporter.h" +#include "Lexer.h" +#include "Interpreter.h" +#include "helperFunctions/ShortHands.h" +#include "ErrorReporter.h" #define VERSION "0.0.3" diff --git a/headers/helperFunctions/ErrorUtils.h b/src/headers/common/helperFunctions/ErrorUtils.h similarity index 100% rename from headers/helperFunctions/ErrorUtils.h rename to src/headers/common/helperFunctions/ErrorUtils.h diff --git a/headers/helperFunctions/HelperFunctions.h b/src/headers/common/helperFunctions/HelperFunctions.h similarity index 100% rename from headers/helperFunctions/HelperFunctions.h rename to src/headers/common/helperFunctions/HelperFunctions.h diff --git a/headers/helperFunctions/ShortHands.h b/src/headers/common/helperFunctions/ShortHands.h similarity index 100% rename from headers/helperFunctions/ShortHands.h rename to src/headers/common/helperFunctions/ShortHands.h diff --git a/headers/ErrorReporter.h b/src/headers/parsing/ErrorReporter.h similarity index 100% rename from headers/ErrorReporter.h rename to src/headers/parsing/ErrorReporter.h diff --git a/headers/Expression.h b/src/headers/parsing/Expression.h similarity index 100% rename from headers/Expression.h rename to src/headers/parsing/Expression.h diff --git a/headers/Lexer.h b/src/headers/parsing/Lexer.h similarity index 100% rename from headers/Lexer.h rename to src/headers/parsing/Lexer.h diff --git a/headers/Parser.h b/src/headers/parsing/Parser.h similarity index 100% rename from headers/Parser.h rename to src/headers/parsing/Parser.h diff --git a/headers/Statement.h b/src/headers/parsing/Statement.h similarity index 97% rename from headers/Statement.h rename to src/headers/parsing/Statement.h index f064c80..c2aaa92 100644 --- a/headers/Statement.h +++ b/src/headers/parsing/Statement.h @@ -18,13 +18,7 @@ struct BreakStmt; struct ContinueStmt; struct AssignStmt; -struct ExecutionContext { - bool isFunctionBody = false; - bool hasReturn = false; - bool hasBreak = false; - bool hasContinue = false; - Value returnValue; -}; +#include "ExecutionContext.h" struct StmtVisitor { diff --git a/headers/Environment.h b/src/headers/runtime/Environment.h similarity index 100% rename from headers/Environment.h rename to src/headers/runtime/Environment.h diff --git a/src/headers/runtime/Evaluator.h b/src/headers/runtime/Evaluator.h new file mode 100644 index 0000000..7cc14ce --- /dev/null +++ b/src/headers/runtime/Evaluator.h @@ -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& expression) override; + Value visitCallExpr(const std::shared_ptr& expression) override; + Value visitFunctionExpr(const std::shared_ptr& expression) override; + Value visitGroupingExpr(const std::shared_ptr& expression) override; + Value visitLiteralExpr(const std::shared_ptr& expression) override; + Value visitUnaryExpr(const std::shared_ptr& expression) override; + Value visitVarExpr(const std::shared_ptr& expression) override; + Value visitIncrementExpr(const std::shared_ptr& expression) override; + Value visitAssignExpr(const std::shared_ptr& expression) override; + Value visitTernaryExpr(const std::shared_ptr& expression) override; + Value visitArrayLiteralExpr(const std::shared_ptr& expression) override; + Value visitArrayIndexExpr(const std::shared_ptr& expression) override; + Value visitArrayAssignExpr(const std::shared_ptr& expression) override; + Value visitDictLiteralExpr(const std::shared_ptr& expression) override; +}; diff --git a/src/headers/runtime/ExecutionContext.h b/src/headers/runtime/ExecutionContext.h new file mode 100644 index 0000000..ee43d76 --- /dev/null +++ b/src/headers/runtime/ExecutionContext.h @@ -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; +}; diff --git a/src/headers/runtime/Executor.h b/src/headers/runtime/Executor.h new file mode 100644 index 0000000..bea38a5 --- /dev/null +++ b/src/headers/runtime/Executor.h @@ -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>& statements); + void executeBlock(const std::vector>& statements, std::shared_ptr env, ExecutionContext* context); + + // Statement Visitors + void visitBlockStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitExpressionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitVarStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitFunctionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitReturnStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitIfStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitDoWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitForStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitBreakStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitAssignStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + +private: + void execute(const std::shared_ptr& statement, ExecutionContext* context); +}; \ No newline at end of file diff --git a/src/headers/runtime/Interpreter.h b/src/headers/runtime/Interpreter.h new file mode 100644 index 0000000..942f343 --- /dev/null +++ b/src/headers/runtime/Interpreter.h @@ -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 +#include +#include +#include +#include +#include + +// 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& target; + std::shared_ptr prev; + ScopedEnv(std::shared_ptr& e) : target(e), prev(e) {} + ~ScopedEnv() { target = prev; } +}; + +// Thunk class for trampoline-based tail call optimization +struct Thunk { +public: + using ThunkFunction = std::function; + + 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; + bool isInteractive; + std::vector> builtinFunctions; + std::vector> functions; + std::vector> thunks; // Store thunks to prevent memory leaks + ErrorReporter* errorReporter; + bool inThunkExecution = false; + RuntimeDiagnostics diagnostics; // Utility functions for runtime operations + std::unique_ptr evaluator; + std::unique_ptr executor; + +public: + explicit Interpreter(bool isInteractive); + virtual ~Interpreter(); + + // Public interface for main + void interpret(std::vector> statements); + void setErrorReporter(ErrorReporter* reporter); + + // Methods needed by Evaluator + Value evaluate(const std::shared_ptr& expr); + void execute(const std::shared_ptr& statement, ExecutionContext* context = nullptr); + void executeBlock(std::vector> statements, std::shared_ptr 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 getEnvironment(); + void setEnvironment(std::shared_ptr env); + void addThunk(std::shared_ptr thunk); + void addFunction(std::shared_ptr function); + void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = ""); + void addBuiltinFunction(std::shared_ptr func); + void cleanupUnusedFunctions(); + void cleanupUnusedThunks(); + void forceCleanup(); + +private: + Value evaluateWithoutTrampoline(const std::shared_ptr& expr); + void addStdLibFunctions(); + Value runTrampoline(Value initialResult); +}; diff --git a/src/headers/runtime/RuntimeDiagnostics.h b/src/headers/runtime/RuntimeDiagnostics.h new file mode 100644 index 0000000..471277c --- /dev/null +++ b/src/headers/runtime/RuntimeDiagnostics.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Value.h" +#include +#include + +// 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>& functions); + void cleanupUnusedThunks(std::vector>& thunks); + void forceCleanup(std::vector>& functions, + std::vector>& thunks); + +private: + // Helper methods for stringify + std::string formatNumber(double value); + std::string formatArray(const std::vector& arr); + std::string formatDict(const std::unordered_map& dict); +}; \ No newline at end of file diff --git a/headers/TypeWrapper.h b/src/headers/runtime/TypeWrapper.h similarity index 100% rename from headers/TypeWrapper.h rename to src/headers/runtime/TypeWrapper.h diff --git a/headers/Value.h b/src/headers/runtime/Value.h similarity index 100% rename from headers/Value.h rename to src/headers/runtime/Value.h diff --git a/headers/BobStdLib.h b/src/headers/stdlib/BobStdLib.h similarity index 100% rename from headers/BobStdLib.h rename to src/headers/stdlib/BobStdLib.h diff --git a/src/runtime/Evaluator.cpp b/src/runtime/Evaluator.cpp new file mode 100644 index 0000000..bf91bd1 --- /dev/null +++ b/src/runtime/Evaluator.cpp @@ -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& 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& expression) { + return interpreter->evaluate(expression->expression); +} + +Value Evaluator::visitUnaryExpr(const std::shared_ptr& 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(~(static_cast(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& 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& expression) +{ + return interpreter->getEnvironment()->get(expression->name); +} + +Value Evaluator::visitIncrementExpr(const std::shared_ptr& 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(expression->operand)) { + interpreter->getEnvironment()->assign(varExpr->name, Value(newValue)); + } else if (auto arrayExpr = std::dynamic_pointer_cast(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(index.asNumber()); + std::vector& arr = array.asArray(); + + if (idx < 0 || idx >= static_cast(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& 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& 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& expression) { + Value callee = expression->callee->accept(this); + + std::vector 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(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& expr) { + std::vector elements; + + for (const auto& element : expr->elements) { + elements.push_back(interpreter->evaluate(element)); + } + + return Value(elements); +} + +Value Evaluator::visitArrayIndexExpr(const std::shared_ptr& 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(index.asNumber()); + const std::vector& arr = array.asArray(); + + if (idx < 0 || idx >= static_cast(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& 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& 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(index.asNumber()); + std::vector& arr = array.asArray(); + + if (idx < 0 || idx >= static_cast(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& 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& expr) { + std::unordered_map 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& expression) { + std::vector paramNames; + for (const Token& param : expression->params) { + paramNames.push_back(param.lexeme); + } + + auto function = std::make_shared("", paramNames, expression->body, interpreter->getEnvironment()); + interpreter->addFunction(function); + return Value(function.get()); +} diff --git a/src/runtime/Executor.cpp b/src/runtime/Executor.cpp new file mode 100644 index 0000000..7598c1e --- /dev/null +++ b/src/runtime/Executor.cpp @@ -0,0 +1,245 @@ +#include "Executor.h" +#include "Evaluator.h" +#include "Interpreter.h" +#include + +Executor::Executor(Interpreter* interpreter, Evaluator* evaluator) + : interpreter(interpreter), evaluator(evaluator) {} + +Executor::~Executor() {} + +void Executor::interpret(const std::vector>& statements) { + for (const auto& statement : statements) { + execute(statement, nullptr); + } +} + +void Executor::execute(const std::shared_ptr& statement, ExecutionContext* context) { + statement->accept(this, context); +} + +void Executor::executeBlock(const std::vector>& statements, std::shared_ptr env, ExecutionContext* context) { + std::shared_ptr 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& statement, ExecutionContext* context) { + auto newEnv = std::make_shared(interpreter->getEnvironment()); + executeBlock(statement->statements, newEnv, context); +} + +void Executor::visitExpressionStmt(const std::shared_ptr& 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& 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& statement, ExecutionContext* context) { + std::vector paramNames; + for (const Token& param : statement->params) { + paramNames.push_back(param.lexeme); + } + + auto function = std::make_shared(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& 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& 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& 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& 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& 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& statement, ExecutionContext* context) { + if (context) { + context->shouldBreak = true; + } +} + +void Executor::visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context) { + if (context) { + context->shouldContinue = true; + } +} + +void Executor::visitAssignStmt(const std::shared_ptr& 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); + } +} diff --git a/src/runtime/Interpreter.cpp b/src/runtime/Interpreter.cpp new file mode 100644 index 0000000..ac8b7a4 --- /dev/null +++ b/src/runtime/Interpreter.cpp @@ -0,0 +1,96 @@ +#include "Interpreter.h" +#include "Evaluator.h" +#include "Executor.h" +#include "BobStdLib.h" +#include + +Interpreter::Interpreter(bool isInteractive) + : isInteractive(isInteractive), errorReporter(nullptr) { + evaluator = std::make_unique(this); + executor = std::make_unique(this, evaluator.get()); + environment = std::make_shared(); +} + +Interpreter::~Interpreter() = default; + +void Interpreter::interpret(std::vector> statements) { + executor->interpret(statements); +} + +void Interpreter::execute(const std::shared_ptr& statement, ExecutionContext* context) { + statement->accept(executor.get(), context); +} + +void Interpreter::executeBlock(std::vector> statements, std::shared_ptr env, ExecutionContext* context) { + executor->executeBlock(statements, env, context); +} + +Value Interpreter::evaluate(const std::shared_ptr& 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 func) { + builtinFunctions.push_back(func); +} + +void Interpreter::addThunk(std::shared_ptr thunk) { + thunks.push_back(thunk); +} + +void Interpreter::addFunction(std::shared_ptr 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 Interpreter::getEnvironment() { + return environment; +} + +void Interpreter::setEnvironment(std::shared_ptr 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); + } +} diff --git a/source/bob.cpp b/src/sources/cli/bob.cpp similarity index 97% rename from source/bob.cpp rename to src/sources/cli/bob.cpp index af5086e..67588b0 100644 --- a/source/bob.cpp +++ b/src/sources/cli/bob.cpp @@ -1,7 +1,7 @@ #include -#include "../headers/bob.h" -#include "../headers/Parser.h" +#include "bob.h" +#include "Parser.h" void Bob::runFile(const std::string& path) { diff --git a/source/main.cpp b/src/sources/cli/main.cpp similarity index 86% rename from source/main.cpp rename to src/sources/cli/main.cpp index f9b7ce7..1f87377 100644 --- a/source/main.cpp +++ b/src/sources/cli/main.cpp @@ -1,7 +1,7 @@ // // -#include "../headers/bob.h" +#include "bob.h" int main(int argc, char* argv[]){ Bob bobLang; diff --git a/source/ErrorReporter.cpp b/src/sources/parsing/ErrorReporter.cpp similarity index 99% rename from source/ErrorReporter.cpp rename to src/sources/parsing/ErrorReporter.cpp index 73d9f95..5aa1fdc 100644 --- a/source/ErrorReporter.cpp +++ b/src/sources/parsing/ErrorReporter.cpp @@ -1,4 +1,4 @@ -#include "../headers/ErrorReporter.h" +#include "ErrorReporter.h" #include #include #include diff --git a/src/sources/parsing/Expression.cpp b/src/sources/parsing/Expression.cpp new file mode 100644 index 0000000..992b548 --- /dev/null +++ b/src/sources/parsing/Expression.cpp @@ -0,0 +1,3 @@ + + +#include "Expression.h" diff --git a/source/Lexer.cpp b/src/sources/parsing/Lexer.cpp similarity index 99% rename from source/Lexer.cpp rename to src/sources/parsing/Lexer.cpp index 9c254af..feba7b6 100644 --- a/source/Lexer.cpp +++ b/src/sources/parsing/Lexer.cpp @@ -1,6 +1,6 @@ -#include "../headers/Lexer.h" -#include "../headers/ErrorReporter.h" -#include "../headers/helperFunctions/HelperFunctions.h" +#include "Lexer.h" +#include "ErrorReporter.h" +#include "helperFunctions/HelperFunctions.h" #include #include diff --git a/source/Parser.cpp b/src/sources/parsing/Parser.cpp similarity index 99% rename from source/Parser.cpp rename to src/sources/parsing/Parser.cpp index b5cfff1..88c22e9 100644 --- a/source/Parser.cpp +++ b/src/sources/parsing/Parser.cpp @@ -1,5 +1,5 @@ -#include "../headers/Parser.h" +#include "Parser.h" #include diff --git a/source/Environment.cpp b/src/sources/runtime/Environment.cpp similarity index 94% rename from source/Environment.cpp rename to src/sources/runtime/Environment.cpp index dcc8206..f44d5f5 100644 --- a/source/Environment.cpp +++ b/src/sources/runtime/Environment.cpp @@ -1,5 +1,5 @@ -#include "../headers/Environment.h" -#include "../headers/ErrorReporter.h" +#include "Environment.h" +#include "ErrorReporter.h" void Environment::assign(const Token& name, const Value& value) { auto it = variables.find(name.lexeme); diff --git a/src/sources/runtime/Evaluator.cpp b/src/sources/runtime/Evaluator.cpp new file mode 100644 index 0000000..bf91bd1 --- /dev/null +++ b/src/sources/runtime/Evaluator.cpp @@ -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& 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& expression) { + return interpreter->evaluate(expression->expression); +} + +Value Evaluator::visitUnaryExpr(const std::shared_ptr& 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(~(static_cast(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& 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& expression) +{ + return interpreter->getEnvironment()->get(expression->name); +} + +Value Evaluator::visitIncrementExpr(const std::shared_ptr& 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(expression->operand)) { + interpreter->getEnvironment()->assign(varExpr->name, Value(newValue)); + } else if (auto arrayExpr = std::dynamic_pointer_cast(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(index.asNumber()); + std::vector& arr = array.asArray(); + + if (idx < 0 || idx >= static_cast(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& 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& 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& expression) { + Value callee = expression->callee->accept(this); + + std::vector 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(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& expr) { + std::vector elements; + + for (const auto& element : expr->elements) { + elements.push_back(interpreter->evaluate(element)); + } + + return Value(elements); +} + +Value Evaluator::visitArrayIndexExpr(const std::shared_ptr& 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(index.asNumber()); + const std::vector& arr = array.asArray(); + + if (idx < 0 || idx >= static_cast(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& 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& 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(index.asNumber()); + std::vector& arr = array.asArray(); + + if (idx < 0 || idx >= static_cast(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& 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& expr) { + std::unordered_map 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& expression) { + std::vector paramNames; + for (const Token& param : expression->params) { + paramNames.push_back(param.lexeme); + } + + auto function = std::make_shared("", paramNames, expression->body, interpreter->getEnvironment()); + interpreter->addFunction(function); + return Value(function.get()); +} diff --git a/src/sources/runtime/Executor.cpp b/src/sources/runtime/Executor.cpp new file mode 100644 index 0000000..7598c1e --- /dev/null +++ b/src/sources/runtime/Executor.cpp @@ -0,0 +1,245 @@ +#include "Executor.h" +#include "Evaluator.h" +#include "Interpreter.h" +#include + +Executor::Executor(Interpreter* interpreter, Evaluator* evaluator) + : interpreter(interpreter), evaluator(evaluator) {} + +Executor::~Executor() {} + +void Executor::interpret(const std::vector>& statements) { + for (const auto& statement : statements) { + execute(statement, nullptr); + } +} + +void Executor::execute(const std::shared_ptr& statement, ExecutionContext* context) { + statement->accept(this, context); +} + +void Executor::executeBlock(const std::vector>& statements, std::shared_ptr env, ExecutionContext* context) { + std::shared_ptr 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& statement, ExecutionContext* context) { + auto newEnv = std::make_shared(interpreter->getEnvironment()); + executeBlock(statement->statements, newEnv, context); +} + +void Executor::visitExpressionStmt(const std::shared_ptr& 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& 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& statement, ExecutionContext* context) { + std::vector paramNames; + for (const Token& param : statement->params) { + paramNames.push_back(param.lexeme); + } + + auto function = std::make_shared(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& 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& 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& 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& 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& 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& statement, ExecutionContext* context) { + if (context) { + context->shouldBreak = true; + } +} + +void Executor::visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context) { + if (context) { + context->shouldContinue = true; + } +} + +void Executor::visitAssignStmt(const std::shared_ptr& 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); + } +} diff --git a/src/sources/runtime/Interpreter.cpp b/src/sources/runtime/Interpreter.cpp new file mode 100644 index 0000000..ac8b7a4 --- /dev/null +++ b/src/sources/runtime/Interpreter.cpp @@ -0,0 +1,96 @@ +#include "Interpreter.h" +#include "Evaluator.h" +#include "Executor.h" +#include "BobStdLib.h" +#include + +Interpreter::Interpreter(bool isInteractive) + : isInteractive(isInteractive), errorReporter(nullptr) { + evaluator = std::make_unique(this); + executor = std::make_unique(this, evaluator.get()); + environment = std::make_shared(); +} + +Interpreter::~Interpreter() = default; + +void Interpreter::interpret(std::vector> statements) { + executor->interpret(statements); +} + +void Interpreter::execute(const std::shared_ptr& statement, ExecutionContext* context) { + statement->accept(executor.get(), context); +} + +void Interpreter::executeBlock(std::vector> statements, std::shared_ptr env, ExecutionContext* context) { + executor->executeBlock(statements, env, context); +} + +Value Interpreter::evaluate(const std::shared_ptr& 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 func) { + builtinFunctions.push_back(func); +} + +void Interpreter::addThunk(std::shared_ptr thunk) { + thunks.push_back(thunk); +} + +void Interpreter::addFunction(std::shared_ptr 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 Interpreter::getEnvironment() { + return environment; +} + +void Interpreter::setEnvironment(std::shared_ptr 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); + } +} diff --git a/src/sources/runtime/RuntimeDiagnostics.cpp b/src/sources/runtime/RuntimeDiagnostics.cpp new file mode 100644 index 0000000..1ce17e6 --- /dev/null +++ b/src/sources/runtime/RuntimeDiagnostics.cpp @@ -0,0 +1,215 @@ +#include "RuntimeDiagnostics.h" +#include "Value.h" +#include "TypeWrapper.h" // For Function and BuiltinFunction definitions +#include +#include +#include +#include +#include + +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& arrA = a.asArray(); + const std::vector& 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 "name + ">"; + } + else if(object.isBuiltinFunction()) { + return "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::epsilon()) { + ss << std::fixed << std::setprecision(0) << integral; + return ss.str(); + } + else { + ss << std::fixed << std::setprecision(std::numeric_limits::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& 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& 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>& 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& func) { + return func.use_count() == 1; // Only referenced by this vector, nowhere else + }), + functions.end() + ); +} + +void RuntimeDiagnostics::cleanupUnusedThunks(std::vector>& 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) { + return thunk.use_count() == 1; // Only referenced by this vector, nowhere else + }), + thunks.end() + ); +} + +void RuntimeDiagnostics::forceCleanup(std::vector>& functions, + std::vector>& thunks) { + // More aggressive cleanup when breaking array references + functions.erase( + std::remove_if(functions.begin(), functions.end(), + [](const std::shared_ptr& 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) { + return thunk.use_count() <= 2; // More aggressive than == 1 + }), + thunks.end() + ); +} \ No newline at end of file diff --git a/src/sources/runtime/TypeWrapper.cpp b/src/sources/runtime/TypeWrapper.cpp new file mode 100644 index 0000000..8f9bd2a --- /dev/null +++ b/src/sources/runtime/TypeWrapper.cpp @@ -0,0 +1,4 @@ + +#include "TypeWrapper.h" +#include + diff --git a/source/Value.cpp b/src/sources/runtime/Value.cpp similarity index 89% rename from source/Value.cpp rename to src/sources/runtime/Value.cpp index 9179617..404b670 100644 --- a/source/Value.cpp +++ b/src/sources/runtime/Value.cpp @@ -1,4 +1,4 @@ -#include "../headers/Value.h" +#include "Value.h" // Global constants for common values (no heap allocation) const Value NONE_VALUE = Value(); diff --git a/source/BobStdLib.cpp b/src/sources/stdlib/BobStdLib.cpp similarity index 99% rename from source/BobStdLib.cpp rename to src/sources/stdlib/BobStdLib.cpp index ac2c6b8..9dd9662 100644 --- a/source/BobStdLib.cpp +++ b/src/sources/stdlib/BobStdLib.cpp @@ -1,8 +1,8 @@ -#include "../headers/BobStdLib.h" -#include "../headers/Interpreter.h" -#include "../headers/ErrorReporter.h" -#include "../headers/Lexer.h" -#include "../headers/Parser.h" +#include "BobStdLib.h" +#include "Interpreter.h" +#include "ErrorReporter.h" +#include "Lexer.h" +#include "Parser.h" #include #include #include diff --git a/tco.bob b/tco.bob deleted file mode 100644 index 9a51ada..0000000 --- a/tco.bob +++ /dev/null @@ -1,13 +0,0 @@ -func countdown(n) -{ - - if(n == 0) - return 0; - - print(n); - - return countdown(n - 1); - -} - -countdown(1000000); diff --git a/test_bracket_conflict.bob b/test_bracket_conflict.bob deleted file mode 100644 index 9b4dff8..0000000 --- a/test_bracket_conflict.bob +++ /dev/null @@ -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"); \ No newline at end of file