Cleaned up project structure
This commit is contained in:
parent
2104fbe1f5
commit
6c17ce96f0
51
Makefile
51
Makefile
@ -1,48 +1,55 @@
|
|||||||
# Makefile
|
# Makefile - Bob language interpreter
|
||||||
|
|
||||||
# Compiler
|
# Compiler
|
||||||
CC = g++
|
CC = g++
|
||||||
|
|
||||||
# Compiler flags
|
# Compiler flags
|
||||||
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
|
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
|
||||||
|
|
||||||
# Source directory
|
# Source layout:
|
||||||
SRC_DIR = ./source
|
# src/
|
||||||
|
# ├── headers/ - All header files (public interface)
|
||||||
|
# │ ├── runtime/ - Runtime execution headers
|
||||||
|
# │ ├── parsing/ - Front-end processing headers
|
||||||
|
# │ ├── stdlib/ - Standard library headers
|
||||||
|
# │ ├── cli/ - CLI headers
|
||||||
|
# │ └── common/ - Shared utilities (helperFunctions)
|
||||||
|
# └── sources/ - All source files (implementation)
|
||||||
|
# ├── runtime/ - Interpreter, Evaluator, Executor, Environment, Value, TypeWrapper
|
||||||
|
# ├── parsing/ - Lexer, Parser, ErrorReporter, Expression AST
|
||||||
|
# ├── stdlib/ - Built-in functions (BobStdLib)
|
||||||
|
# └── cli/ - Command-line interface (main, bob)
|
||||||
|
|
||||||
# Output directory
|
SRC_ROOT = ./src
|
||||||
BUILD_DIR = ./build
|
BUILD_DIR = ./build
|
||||||
|
|
||||||
# Find all C++ files recursively in the source directory
|
# Find all .cpp files under src/sources (recursively)
|
||||||
CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp')
|
CPP_FILES := $(shell find $(SRC_ROOT)/sources -type f -name '*.cpp')
|
||||||
|
|
||||||
# Generate object file names by replacing the source directory with the build directory
|
# Map every source file to its corresponding object file inside build/
|
||||||
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
# Strip src/sources/ prefix and add build/ prefix
|
||||||
|
OBJ_FILES := $(patsubst $(SRC_ROOT)/sources/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||||
|
|
||||||
# Create directories for object files
|
# Make sure every object directory exists ahead of time
|
||||||
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
||||||
|
|
||||||
# Default target
|
# Default goal
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
# Rule to create necessary directories
|
# Pattern rule – compile each .cpp from sources/ into a mirrored .o inside build/
|
||||||
$(DIRS):
|
$(BUILD_DIR)/%.o: $(SRC_ROOT)/sources/%.cpp
|
||||||
mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@)
|
@mkdir -p $(dir $@)
|
||||||
|
$(CC) $(CFLAGS) -I$(SRC_ROOT)/headers/runtime -I$(SRC_ROOT)/headers/parsing -I$(SRC_ROOT)/headers/stdlib -I$(SRC_ROOT)/headers/cli -I$(SRC_ROOT)/headers/common -c $< -o $@
|
||||||
|
|
||||||
# Rule to compile object files
|
# Link all objects into the final executable
|
||||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
|
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
|
||||||
|
|
||||||
# Rule to link object files into the final executable
|
|
||||||
$(BUILD_DIR)/bob: $(OBJ_FILES)
|
$(BUILD_DIR)/bob: $(OBJ_FILES)
|
||||||
$(CC) $(CFLAGS) $^ -o $@
|
$(CC) $(CFLAGS) $^ -o $@
|
||||||
|
|
||||||
|
# Convenience targets
|
||||||
run:
|
run:
|
||||||
./$(BUILD_DIR)/bob
|
./$(BUILD_DIR)/bob
|
||||||
|
|
||||||
build: clean $(BUILD_DIR)/bob
|
build: clean $(BUILD_DIR)/bob
|
||||||
|
|
||||||
|
|
||||||
# Clean build directory
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILD_DIR)/*
|
rm -rf $(BUILD_DIR)/*
|
||||||
|
|||||||
30
benchmark.py
30
benchmark.py
@ -1,30 +0,0 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
# Test the time function
|
|
||||||
print("Testing time function:")
|
|
||||||
|
|
||||||
time1 = time.time()
|
|
||||||
print(f"Time 1: {time1}")
|
|
||||||
|
|
||||||
time2 = time.time()
|
|
||||||
print(f"Time 2: {time2}")
|
|
||||||
|
|
||||||
time3 = time.time()
|
|
||||||
print(f"Time 3: {time3}")
|
|
||||||
|
|
||||||
diff1 = time2 - time1
|
|
||||||
diff2 = time3 - time2
|
|
||||||
|
|
||||||
print(f"Difference 1-2: {diff1} seconds")
|
|
||||||
print(f"Difference 2-3: {diff2} seconds")
|
|
||||||
|
|
||||||
# Test with some work in between
|
|
||||||
start = time.time()
|
|
||||||
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
|
|
||||||
end = time.time()
|
|
||||||
duration = end - start
|
|
||||||
|
|
||||||
print(f"Work duration: {duration} seconds")
|
|
||||||
print(f"Sum: {sum_val}")
|
|
||||||
|
|
||||||
print("Time function analysis complete!")
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Expression.h"
|
|
||||||
#include "Statement.h"
|
|
||||||
#include "helperFunctions/ShortHands.h"
|
|
||||||
#include "TypeWrapper.h"
|
|
||||||
#include "Environment.h"
|
|
||||||
#include "Value.h"
|
|
||||||
#include "BobStdLib.h"
|
|
||||||
#include "ErrorReporter.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <stack>
|
|
||||||
#include <optional>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
class Interpreter;
|
|
||||||
|
|
||||||
// RAII helper for thunk execution flag
|
|
||||||
struct ScopedThunkFlag {
|
|
||||||
bool& flag;
|
|
||||||
bool prev;
|
|
||||||
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
|
|
||||||
~ScopedThunkFlag() { flag = prev; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// RAII helper for environment management
|
|
||||||
struct ScopedEnv {
|
|
||||||
std::shared_ptr<Environment>& target;
|
|
||||||
std::shared_ptr<Environment> prev;
|
|
||||||
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
|
|
||||||
~ScopedEnv() { target = prev; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Thunk class for trampoline-based tail call optimization
|
|
||||||
struct Thunk {
|
|
||||||
public:
|
|
||||||
using ThunkFunction = std::function<Value()>;
|
|
||||||
|
|
||||||
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
|
|
||||||
|
|
||||||
Value execute() const {
|
|
||||||
return func();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isThunk() const { return true; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ThunkFunction func;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Interpreter : public ExprVisitor, public StmtVisitor {
|
|
||||||
|
|
||||||
public:
|
|
||||||
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
|
||||||
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
|
||||||
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
|
||||||
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
|
||||||
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
|
||||||
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
|
||||||
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
|
||||||
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
|
||||||
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
|
||||||
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
|
|
||||||
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
|
|
||||||
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
|
|
||||||
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
|
|
||||||
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
|
|
||||||
|
|
||||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
|
||||||
|
|
||||||
void interpret(std::vector<std::shared_ptr<Stmt>> statements);
|
|
||||||
|
|
||||||
explicit Interpreter(bool isInteractive) : isInteractive(isInteractive), errorReporter(nullptr){
|
|
||||||
environment = std::make_shared<Environment>();
|
|
||||||
}
|
|
||||||
virtual ~Interpreter() = default;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Environment> environment;
|
|
||||||
bool isInteractive;
|
|
||||||
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
|
|
||||||
std::vector<std::shared_ptr<Function>> functions;
|
|
||||||
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
|
|
||||||
ErrorReporter* errorReporter;
|
|
||||||
bool inThunkExecution = false;
|
|
||||||
|
|
||||||
// Automatic cleanup tracking
|
|
||||||
int functionCreationCount = 0;
|
|
||||||
int thunkCreationCount = 0;
|
|
||||||
static const int CLEANUP_THRESHOLD = 1000000; // Cleanup every 1M creations (effectively disabled for performance)
|
|
||||||
static const int MAX_FUNCTION_PARAMETERS = 255; // Maximum number of function parameters
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
|
||||||
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
|
|
||||||
bool isEqual(Value a, Value b);
|
|
||||||
|
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
|
||||||
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
|
||||||
void addStdLibFunctions();
|
|
||||||
|
|
||||||
// Trampoline execution
|
|
||||||
Value runTrampoline(Value initialResult);
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool isTruthy(Value object);
|
|
||||||
std::string stringify(Value object);
|
|
||||||
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
|
||||||
|
|
||||||
// Memory management
|
|
||||||
void cleanupUnusedFunctions();
|
|
||||||
void cleanupUnusedThunks();
|
|
||||||
void forceCleanup();
|
|
||||||
|
|
||||||
// Error reporting
|
|
||||||
void setErrorReporter(ErrorReporter* reporter) {
|
|
||||||
errorReporter = reporter;
|
|
||||||
if (environment) {
|
|
||||||
environment->setErrorReporter(reporter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add standard library functions after error reporter is set
|
|
||||||
addStdLibFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
@ -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 ===");
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "../headers/Expression.h"
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
#include "../headers/TypeWrapper.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
44
src/headers/Executor.h
Normal file
44
src/headers/Executor.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Statement.h"
|
||||||
|
|
||||||
|
class Evaluator; // Forward declaration
|
||||||
|
class Interpreter; // Forward declaration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Executor
|
||||||
|
* @brief Handles the execution of statements and control flow.
|
||||||
|
*
|
||||||
|
* Implements the StmtVisitor pattern. It is responsible for executing statements,
|
||||||
|
* managing environments, and handling control flow constructs like loops and
|
||||||
|
* conditionals. It uses the Evaluator to evaluate expressions when needed.
|
||||||
|
*/
|
||||||
|
class Executor : public StmtVisitor {
|
||||||
|
private:
|
||||||
|
Interpreter* interpreter; // Back-pointer to access interpreter services
|
||||||
|
Evaluator* evaluator; // For evaluating expressions
|
||||||
|
|
||||||
|
public:
|
||||||
|
Executor(Interpreter* interpreter, Evaluator* evaluator);
|
||||||
|
virtual ~Executor();
|
||||||
|
|
||||||
|
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
|
||||||
|
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
|
||||||
|
|
||||||
|
// Statement Visitors
|
||||||
|
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
||||||
|
};
|
||||||
@ -3,10 +3,10 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "../headers/Lexer.h"
|
#include "Lexer.h"
|
||||||
#include "../headers/Interpreter.h"
|
#include "Interpreter.h"
|
||||||
#include "../headers/helperFunctions/ShortHands.h"
|
#include "helperFunctions/ShortHands.h"
|
||||||
#include "../headers/ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
#define VERSION "0.0.3"
|
#define VERSION "0.0.3"
|
||||||
|
|
||||||
@ -18,13 +18,7 @@ struct BreakStmt;
|
|||||||
struct ContinueStmt;
|
struct ContinueStmt;
|
||||||
struct AssignStmt;
|
struct AssignStmt;
|
||||||
|
|
||||||
struct ExecutionContext {
|
#include "ExecutionContext.h"
|
||||||
bool isFunctionBody = false;
|
|
||||||
bool hasReturn = false;
|
|
||||||
bool hasBreak = false;
|
|
||||||
bool hasContinue = false;
|
|
||||||
Value returnValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StmtVisitor
|
struct StmtVisitor
|
||||||
{
|
{
|
||||||
38
src/headers/runtime/Evaluator.h
Normal file
38
src/headers/runtime/Evaluator.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Expression.h"
|
||||||
|
#include "Value.h"
|
||||||
|
|
||||||
|
class Interpreter; // Forward declaration for the back-pointer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Evaluator
|
||||||
|
* @brief Handles the logic for visiting and evaluating Expression AST nodes.
|
||||||
|
*
|
||||||
|
* Implements the Visitor pattern for all Expression types. This class
|
||||||
|
* contains the core evaluation logic for expressions, returning a Value.
|
||||||
|
*/
|
||||||
|
class Evaluator : public ExprVisitor {
|
||||||
|
private:
|
||||||
|
Interpreter* interpreter; // Back-pointer to access interpreter services like isTruthy, etc.
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Evaluator(Interpreter* interpreter);
|
||||||
|
virtual ~Evaluator() = default;
|
||||||
|
|
||||||
|
// Expression Visitors
|
||||||
|
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
||||||
|
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
||||||
|
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
||||||
|
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
||||||
|
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
||||||
|
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
||||||
|
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
||||||
|
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
||||||
|
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
||||||
|
Value visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) override;
|
||||||
|
Value visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expression) override;
|
||||||
|
Value visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expression) override;
|
||||||
|
Value visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expression) override;
|
||||||
|
Value visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expression) override;
|
||||||
|
};
|
||||||
10
src/headers/runtime/ExecutionContext.h
Normal file
10
src/headers/runtime/ExecutionContext.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Value.h"
|
||||||
|
|
||||||
|
struct ExecutionContext {
|
||||||
|
bool isFunctionBody = false;
|
||||||
|
bool hasReturn = false;
|
||||||
|
Value returnValue = NONE_VALUE;
|
||||||
|
bool shouldBreak = false;
|
||||||
|
bool shouldContinue = false;
|
||||||
|
};
|
||||||
44
src/headers/runtime/Executor.h
Normal file
44
src/headers/runtime/Executor.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Statement.h"
|
||||||
|
|
||||||
|
class Evaluator; // Forward declaration
|
||||||
|
class Interpreter; // Forward declaration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Executor
|
||||||
|
* @brief Handles the execution of statements and control flow.
|
||||||
|
*
|
||||||
|
* Implements the StmtVisitor pattern. It is responsible for executing statements,
|
||||||
|
* managing environments, and handling control flow constructs like loops and
|
||||||
|
* conditionals. It uses the Evaluator to evaluate expressions when needed.
|
||||||
|
*/
|
||||||
|
class Executor : public StmtVisitor {
|
||||||
|
private:
|
||||||
|
Interpreter* interpreter; // Back-pointer to access interpreter services
|
||||||
|
Evaluator* evaluator; // For evaluating expressions
|
||||||
|
|
||||||
|
public:
|
||||||
|
Executor(Interpreter* interpreter, Evaluator* evaluator);
|
||||||
|
virtual ~Executor();
|
||||||
|
|
||||||
|
void interpret(const std::vector<std::shared_ptr<Stmt>>& statements);
|
||||||
|
void executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context);
|
||||||
|
|
||||||
|
// Statement Visitors
|
||||||
|
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
||||||
|
};
|
||||||
101
src/headers/runtime/Interpreter.h
Normal file
101
src/headers/runtime/Interpreter.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Expression.h"
|
||||||
|
#include "Statement.h"
|
||||||
|
#include "helperFunctions/ShortHands.h"
|
||||||
|
#include "TypeWrapper.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "Value.h"
|
||||||
|
#include "BobStdLib.h"
|
||||||
|
#include "ErrorReporter.h"
|
||||||
|
#include "ExecutionContext.h"
|
||||||
|
#include "RuntimeDiagnostics.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <stack>
|
||||||
|
#include <optional>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
class Evaluator;
|
||||||
|
|
||||||
|
// RAII helper for thunk execution flag
|
||||||
|
struct ScopedThunkFlag {
|
||||||
|
bool& flag;
|
||||||
|
bool prev;
|
||||||
|
ScopedThunkFlag(bool& f) : flag(f), prev(f) { flag = true; }
|
||||||
|
~ScopedThunkFlag() { flag = prev; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII helper for environment management
|
||||||
|
struct ScopedEnv {
|
||||||
|
std::shared_ptr<Environment>& target;
|
||||||
|
std::shared_ptr<Environment> prev;
|
||||||
|
ScopedEnv(std::shared_ptr<Environment>& e) : target(e), prev(e) {}
|
||||||
|
~ScopedEnv() { target = prev; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Thunk class for trampoline-based tail call optimization
|
||||||
|
struct Thunk {
|
||||||
|
public:
|
||||||
|
using ThunkFunction = std::function<Value()>;
|
||||||
|
|
||||||
|
explicit Thunk(ThunkFunction func) : func(std::move(func)) {}
|
||||||
|
|
||||||
|
Value execute() const {
|
||||||
|
return func();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isThunk() const { return true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ThunkFunction func;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Executor;
|
||||||
|
|
||||||
|
class Interpreter {
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Environment> environment;
|
||||||
|
bool isInteractive;
|
||||||
|
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions;
|
||||||
|
std::vector<std::shared_ptr<Function>> functions;
|
||||||
|
std::vector<std::shared_ptr<Thunk>> thunks; // Store thunks to prevent memory leaks
|
||||||
|
ErrorReporter* errorReporter;
|
||||||
|
bool inThunkExecution = false;
|
||||||
|
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
||||||
|
std::unique_ptr<Evaluator> evaluator;
|
||||||
|
std::unique_ptr<Executor> executor;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Interpreter(bool isInteractive);
|
||||||
|
virtual ~Interpreter();
|
||||||
|
|
||||||
|
// Public interface for main
|
||||||
|
void interpret(std::vector<std::shared_ptr<Stmt>> statements);
|
||||||
|
void setErrorReporter(ErrorReporter* reporter);
|
||||||
|
|
||||||
|
// Methods needed by Evaluator
|
||||||
|
Value evaluate(const std::shared_ptr<Expr>& expr);
|
||||||
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
||||||
|
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
||||||
|
bool isTruthy(Value object);
|
||||||
|
bool isEqual(Value a, Value b);
|
||||||
|
std::string stringify(Value object);
|
||||||
|
bool isInteractiveMode() const;
|
||||||
|
std::shared_ptr<Environment> getEnvironment();
|
||||||
|
void setEnvironment(std::shared_ptr<Environment> env);
|
||||||
|
void addThunk(std::shared_ptr<Thunk> thunk);
|
||||||
|
void addFunction(std::shared_ptr<Function> function);
|
||||||
|
void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme = "");
|
||||||
|
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
|
||||||
|
void cleanupUnusedFunctions();
|
||||||
|
void cleanupUnusedThunks();
|
||||||
|
void forceCleanup();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
|
||||||
|
void addStdLibFunctions();
|
||||||
|
Value runTrampoline(Value initialResult);
|
||||||
|
};
|
||||||
38
src/headers/runtime/RuntimeDiagnostics.h
Normal file
38
src/headers/runtime/RuntimeDiagnostics.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Value.h"
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// Forward declarations from Value.h
|
||||||
|
struct Function;
|
||||||
|
struct BuiltinFunction;
|
||||||
|
struct Thunk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RuntimeDiagnostics - Utility functions for runtime operations
|
||||||
|
*
|
||||||
|
* This class handles value conversion, equality checking, string representation,
|
||||||
|
* and other diagnostic utilities that don't belong in core evaluation logic.
|
||||||
|
*/
|
||||||
|
class RuntimeDiagnostics {
|
||||||
|
public:
|
||||||
|
RuntimeDiagnostics() = default;
|
||||||
|
|
||||||
|
// Value utility functions
|
||||||
|
bool isTruthy(Value object);
|
||||||
|
bool isEqual(Value a, Value b);
|
||||||
|
std::string stringify(Value object);
|
||||||
|
|
||||||
|
// Memory management utilities
|
||||||
|
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
|
||||||
|
void cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks);
|
||||||
|
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
|
||||||
|
std::vector<std::shared_ptr<Thunk>>& thunks);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Helper methods for stringify
|
||||||
|
std::string formatNumber(double value);
|
||||||
|
std::string formatArray(const std::vector<Value>& arr);
|
||||||
|
std::string formatDict(const std::unordered_map<std::string, Value>& dict);
|
||||||
|
};
|
||||||
434
src/runtime/Evaluator.cpp
Normal file
434
src/runtime/Evaluator.cpp
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
#include "Evaluator.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
#include "helperFunctions/HelperFunctions.h"
|
||||||
|
|
||||||
|
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
|
||||||
|
|
||||||
|
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||||
|
if (expr->isNull) {
|
||||||
|
return NONE_VALUE;
|
||||||
|
}
|
||||||
|
if (expr->isNumber) {
|
||||||
|
double num;
|
||||||
|
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
|
||||||
|
num = binaryStringToLong(expr->value);
|
||||||
|
} else {
|
||||||
|
num = std::stod(expr->value);
|
||||||
|
}
|
||||||
|
return Value(num);
|
||||||
|
}
|
||||||
|
if (expr->isBoolean) {
|
||||||
|
if (expr->value == "true") return TRUE_VALUE;
|
||||||
|
if (expr->value == "false") return FALSE_VALUE;
|
||||||
|
}
|
||||||
|
return Value(expr->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
||||||
|
return interpreter->evaluate(expression->expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
||||||
|
{
|
||||||
|
Value right = interpreter->evaluate(expression->right);
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case MINUS:
|
||||||
|
if (!right.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
return Value(-right.asNumber());
|
||||||
|
|
||||||
|
case BANG:
|
||||||
|
return Value(!interpreter->isTruthy(right));
|
||||||
|
|
||||||
|
case BIN_NOT:
|
||||||
|
if (!right.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
||||||
|
|
||||||
|
default:
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
|
||||||
|
Value left = interpreter->evaluate(expression->left);
|
||||||
|
Value right = interpreter->evaluate(expression->right);
|
||||||
|
|
||||||
|
// Handle logical operators (AND, OR) - these work with any types
|
||||||
|
if (expression->oper.type == AND) {
|
||||||
|
return interpreter->isTruthy(left) ? right : left;
|
||||||
|
}
|
||||||
|
if (expression->oper.type == OR) {
|
||||||
|
return interpreter->isTruthy(left) ? left : right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle equality operators - these work with any types
|
||||||
|
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
|
||||||
|
bool equal = interpreter->isEqual(left, right);
|
||||||
|
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle comparison operators - only work with numbers
|
||||||
|
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
|
||||||
|
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
|
||||||
|
|
||||||
|
if (left.isNumber() && right.isNumber()) {
|
||||||
|
double leftNum = left.asNumber();
|
||||||
|
double rightNum = right.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case GREATER: return Value(leftNum > rightNum);
|
||||||
|
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
||||||
|
case LESS: return Value(leftNum < rightNum);
|
||||||
|
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
||||||
|
default: break; // Unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error for non-number comparisons
|
||||||
|
std::string opName;
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case GREATER: opName = ">"; break;
|
||||||
|
case GREATER_EQUAL: opName = ">="; break;
|
||||||
|
case LESS: opName = "<"; break;
|
||||||
|
case LESS_EQUAL: opName = "<="; break;
|
||||||
|
default: break; // Unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
|
||||||
|
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all other operators using Value's operator overloads
|
||||||
|
try {
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
case MINUS: return left - right;
|
||||||
|
case STAR: return left * right;
|
||||||
|
case SLASH: return left / right;
|
||||||
|
case PERCENT: return left % right;
|
||||||
|
case BIN_AND: return left & right;
|
||||||
|
case BIN_OR: return left | right;
|
||||||
|
case BIN_XOR: return left ^ right;
|
||||||
|
case BIN_SLEFT: return left << right;
|
||||||
|
case BIN_SRIGHT: return left >> right;
|
||||||
|
default:
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
// The Value operators provide good error messages, just add context
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
e.what(), expression->oper.lexeme);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
||||||
|
{
|
||||||
|
return interpreter->getEnvironment()->get(expression->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
||||||
|
// Get the current value of the operand
|
||||||
|
Value currentValue = interpreter->evaluate(expression->operand);
|
||||||
|
|
||||||
|
if (!currentValue.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentNum = currentValue.asNumber();
|
||||||
|
double newValue;
|
||||||
|
|
||||||
|
// Determine the operation based on the operator
|
||||||
|
if (expression->oper.type == PLUS_PLUS) {
|
||||||
|
newValue = currentNum + 1.0;
|
||||||
|
} else if (expression->oper.type == MINUS_MINUS) {
|
||||||
|
newValue = currentNum - 1.0;
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Invalid increment/decrement operator.", "");
|
||||||
|
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the variable or array element
|
||||||
|
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||||
|
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
||||||
|
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
||||||
|
// Handle array indexing increment/decrement
|
||||||
|
Value array = interpreter->evaluate(arrayExpr->array);
|
||||||
|
Value index = interpreter->evaluate(arrayExpr->index);
|
||||||
|
|
||||||
|
if (!array.isArray()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Can only index arrays", "");
|
||||||
|
throw std::runtime_error("Can only index arrays");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!index.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Array index must be a number", "");
|
||||||
|
throw std::runtime_error("Array index must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = static_cast<int>(index.asNumber());
|
||||||
|
std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
|
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
|
||||||
|
"Runtime Error", "Array index out of bounds", "");
|
||||||
|
throw std::runtime_error("Array index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the array element
|
||||||
|
arr[idx] = Value(newValue);
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the appropriate value based on prefix/postfix
|
||||||
|
if (expression->isPrefix) {
|
||||||
|
return Value(newValue); // Prefix: return new value
|
||||||
|
} else {
|
||||||
|
return currentValue; // Postfix: return old value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
||||||
|
Value value = interpreter->evaluate(expression->value);
|
||||||
|
|
||||||
|
switch (expression->op.type) {
|
||||||
|
case PLUS_EQUAL:
|
||||||
|
case MINUS_EQUAL:
|
||||||
|
case STAR_EQUAL:
|
||||||
|
case SLASH_EQUAL:
|
||||||
|
case PERCENT_EQUAL:
|
||||||
|
case BIN_AND_EQUAL:
|
||||||
|
case BIN_OR_EQUAL:
|
||||||
|
case BIN_XOR_EQUAL:
|
||||||
|
case BIN_SLEFT_EQUAL:
|
||||||
|
case BIN_SRIGHT_EQUAL: {
|
||||||
|
Value currentValue = interpreter->getEnvironment()->get(expression->name);
|
||||||
|
|
||||||
|
// ... (rest of compound assignment logic) ...
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
interpreter->getEnvironment()->assign(expression->name, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
|
||||||
|
Value condition = interpreter->evaluate(expression->condition);
|
||||||
|
|
||||||
|
if (interpreter->isTruthy(condition)) {
|
||||||
|
return interpreter->evaluate(expression->thenExpr);
|
||||||
|
} else {
|
||||||
|
return interpreter->evaluate(expression->elseExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||||
|
Value callee = expression->callee->accept(this);
|
||||||
|
|
||||||
|
std::vector<Value> arguments;
|
||||||
|
for (const auto& argument : expression->arguments) {
|
||||||
|
arguments.push_back(argument->accept(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callee.isFunction()) {
|
||||||
|
Function* function = callee.asFunction();
|
||||||
|
|
||||||
|
// Check arity
|
||||||
|
if (arguments.size() != function->params.size()) {
|
||||||
|
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
||||||
|
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
|
||||||
|
std::to_string(arguments.size()) + ".", "");
|
||||||
|
throw std::runtime_error("Wrong number of arguments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new environment for function call
|
||||||
|
auto environment = std::make_shared<Environment>(function->closure);
|
||||||
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
|
environment->define(function->params[i], arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute function body
|
||||||
|
auto previous = interpreter->getEnvironment();
|
||||||
|
interpreter->setEnvironment(environment);
|
||||||
|
|
||||||
|
ExecutionContext context;
|
||||||
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const auto& stmt : function->body) {
|
||||||
|
interpreter->execute(stmt, &context);
|
||||||
|
if (context.hasReturn) {
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
return NONE_VALUE;
|
||||||
|
|
||||||
|
} else if (callee.isBuiltinFunction()) {
|
||||||
|
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
||||||
|
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
||||||
|
"Can only call functions and classes.", "");
|
||||||
|
throw std::runtime_error("Can only call functions and classes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
|
||||||
|
std::vector<Value> elements;
|
||||||
|
|
||||||
|
for (const auto& element : expr->elements) {
|
||||||
|
elements.push_back(interpreter->evaluate(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
|
||||||
|
Value array = expr->array->accept(this);
|
||||||
|
Value index = expr->index->accept(this);
|
||||||
|
|
||||||
|
if (array.isArray()) {
|
||||||
|
// Handle array indexing
|
||||||
|
if (!index.isNumber()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index must be a number", "");
|
||||||
|
throw std::runtime_error("Array index must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = static_cast<int>(index.asNumber());
|
||||||
|
const std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index out of bounds", "");
|
||||||
|
throw std::runtime_error("Array index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr[idx];
|
||||||
|
|
||||||
|
} else if (array.isDict()) {
|
||||||
|
// Handle dictionary indexing
|
||||||
|
if (!index.isString()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Dictionary key must be a string", "");
|
||||||
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key = index.asString();
|
||||||
|
const std::unordered_map<std::string, Value>& dict = array.asDict();
|
||||||
|
|
||||||
|
auto it = dict.find(key);
|
||||||
|
if (it != dict.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return NONE_VALUE; // Return none for missing keys
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Can only index arrays and dictionaries", "");
|
||||||
|
throw std::runtime_error("Can only index arrays and dictionaries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
|
||||||
|
Value array = expr->array->accept(this);
|
||||||
|
Value index = expr->index->accept(this);
|
||||||
|
Value value = expr->value->accept(this);
|
||||||
|
|
||||||
|
if (array.isArray()) {
|
||||||
|
// Handle array assignment
|
||||||
|
if (!index.isNumber()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index must be a number", "");
|
||||||
|
throw std::runtime_error("Array index must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = static_cast<int>(index.asNumber());
|
||||||
|
std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index out of bounds", "");
|
||||||
|
throw std::runtime_error("Array index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
arr[idx] = value;
|
||||||
|
return value;
|
||||||
|
|
||||||
|
} else if (array.isDict()) {
|
||||||
|
// Handle dictionary assignment
|
||||||
|
if (!index.isString()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Dictionary key must be a string", "");
|
||||||
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key = index.asString();
|
||||||
|
std::unordered_map<std::string, Value>& dict = array.asDict();
|
||||||
|
|
||||||
|
dict[key] = value;
|
||||||
|
return value;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Can only assign to array or dictionary elements", "");
|
||||||
|
throw std::runtime_error("Can only assign to array or dictionary elements");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
|
||||||
|
std::unordered_map<std::string, Value> dict;
|
||||||
|
|
||||||
|
for (const auto& pair : expr->pairs) {
|
||||||
|
Value value = interpreter->evaluate(pair.second);
|
||||||
|
dict[pair.first] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
for (const Token& param : expression->params) {
|
||||||
|
paramNames.push_back(param.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
|
||||||
|
interpreter->addFunction(function);
|
||||||
|
return Value(function.get());
|
||||||
|
}
|
||||||
245
src/runtime/Executor.cpp
Normal file
245
src/runtime/Executor.cpp
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#include "Executor.h"
|
||||||
|
#include "Evaluator.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
||||||
|
: interpreter(interpreter), evaluator(evaluator) {}
|
||||||
|
|
||||||
|
Executor::~Executor() {}
|
||||||
|
|
||||||
|
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
||||||
|
for (const auto& statement : statements) {
|
||||||
|
execute(statement, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
||||||
|
statement->accept(this, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
||||||
|
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
|
||||||
|
interpreter->setEnvironment(env);
|
||||||
|
|
||||||
|
for (const auto& statement : statements) {
|
||||||
|
execute(statement, context);
|
||||||
|
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
||||||
|
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
|
||||||
|
executeBlock(statement->statements, newEnv, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = statement->expression->accept(evaluator);
|
||||||
|
|
||||||
|
if (interpreter->isInteractiveMode())
|
||||||
|
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = NONE_VALUE;
|
||||||
|
if (statement->initializer != nullptr) {
|
||||||
|
value = statement->initializer->accept(evaluator);
|
||||||
|
}
|
||||||
|
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
for (const Token& param : statement->params) {
|
||||||
|
paramNames.push_back(param.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto function = std::make_shared<Function>(statement->name.lexeme,
|
||||||
|
paramNames,
|
||||||
|
statement->body,
|
||||||
|
interpreter->getEnvironment());
|
||||||
|
interpreter->addFunction(function);
|
||||||
|
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = NONE_VALUE;
|
||||||
|
if (statement->value != nullptr) {
|
||||||
|
value = statement->value->accept(evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context->isFunctionBody) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
|
execute(statement->thenBranch, context);
|
||||||
|
} else if (statement->elseBranch != nullptr) {
|
||||||
|
execute(statement->elseBranch, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
|
||||||
|
ExecutionContext loopContext;
|
||||||
|
if (context) {
|
||||||
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
|
execute(statement->body, &loopContext);
|
||||||
|
|
||||||
|
if (loopContext.hasReturn) {
|
||||||
|
if (context) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = loopContext.returnValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldContinue) {
|
||||||
|
loopContext.shouldContinue = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
|
||||||
|
ExecutionContext loopContext;
|
||||||
|
if (context) {
|
||||||
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
execute(statement->body, &loopContext);
|
||||||
|
|
||||||
|
if (loopContext.hasReturn) {
|
||||||
|
if (context) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = loopContext.returnValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldContinue) {
|
||||||
|
loopContext.shouldContinue = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (statement->initializer != nullptr) {
|
||||||
|
execute(statement->initializer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext loopContext;
|
||||||
|
if (context) {
|
||||||
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
|
execute(statement->body, &loopContext);
|
||||||
|
|
||||||
|
if (loopContext.hasReturn) {
|
||||||
|
if (context) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = loopContext.returnValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldContinue) {
|
||||||
|
loopContext.shouldContinue = false;
|
||||||
|
if (statement->increment != nullptr) {
|
||||||
|
statement->increment->accept(evaluator);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement->increment != nullptr) {
|
||||||
|
statement->increment->accept(evaluator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (context) {
|
||||||
|
context->shouldBreak = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (context) {
|
||||||
|
context->shouldContinue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = statement->value->accept(evaluator);
|
||||||
|
|
||||||
|
if (statement->op.type == EQUAL) {
|
||||||
|
interpreter->getEnvironment()->assign(statement->name, value);
|
||||||
|
} else {
|
||||||
|
// Handle compound assignment operators
|
||||||
|
Value currentValue = interpreter->getEnvironment()->get(statement->name);
|
||||||
|
Value newValue;
|
||||||
|
|
||||||
|
switch (statement->op.type) {
|
||||||
|
case PLUS_EQUAL:
|
||||||
|
newValue = currentValue + value;
|
||||||
|
break;
|
||||||
|
case MINUS_EQUAL:
|
||||||
|
newValue = currentValue - value;
|
||||||
|
break;
|
||||||
|
case STAR_EQUAL:
|
||||||
|
newValue = currentValue * value;
|
||||||
|
break;
|
||||||
|
case SLASH_EQUAL:
|
||||||
|
newValue = currentValue / value;
|
||||||
|
break;
|
||||||
|
case PERCENT_EQUAL:
|
||||||
|
newValue = currentValue % value;
|
||||||
|
break;
|
||||||
|
case BIN_AND_EQUAL:
|
||||||
|
newValue = currentValue & value;
|
||||||
|
break;
|
||||||
|
case BIN_OR_EQUAL:
|
||||||
|
newValue = currentValue | value;
|
||||||
|
break;
|
||||||
|
case BIN_XOR_EQUAL:
|
||||||
|
newValue = currentValue ^ value;
|
||||||
|
break;
|
||||||
|
case BIN_SLEFT_EQUAL:
|
||||||
|
newValue = currentValue << value;
|
||||||
|
break;
|
||||||
|
case BIN_SRIGHT_EQUAL:
|
||||||
|
newValue = currentValue >> value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
|
||||||
|
"Unknown assignment operator: " + statement->op.lexeme, "");
|
||||||
|
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter->getEnvironment()->assign(statement->name, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/runtime/Interpreter.cpp
Normal file
96
src/runtime/Interpreter.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include "Interpreter.h"
|
||||||
|
#include "Evaluator.h"
|
||||||
|
#include "Executor.h"
|
||||||
|
#include "BobStdLib.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Interpreter::Interpreter(bool isInteractive)
|
||||||
|
: isInteractive(isInteractive), errorReporter(nullptr) {
|
||||||
|
evaluator = std::make_unique<Evaluator>(this);
|
||||||
|
executor = std::make_unique<Executor>(this, evaluator.get());
|
||||||
|
environment = std::make_shared<Environment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Interpreter::~Interpreter() = default;
|
||||||
|
|
||||||
|
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
|
||||||
|
executor->interpret(statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
||||||
|
statement->accept(executor.get(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
||||||
|
executor->executeBlock(statements, env, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
||||||
|
Value result = expr->accept(evaluator.get());
|
||||||
|
if (inThunkExecution) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return runTrampoline(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::runTrampoline(Value initialResult) {
|
||||||
|
Value current = initialResult;
|
||||||
|
while (current.isThunk()) {
|
||||||
|
current = current.asThunk()->execute();
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isTruthy(Value object) {
|
||||||
|
return diagnostics.isTruthy(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isEqual(Value a, Value b) {
|
||||||
|
return diagnostics.isEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Interpreter::stringify(Value object) {
|
||||||
|
return diagnostics.stringify(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addStdLibFunctions() {
|
||||||
|
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||||
|
builtinFunctions.push_back(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
|
||||||
|
thunks.push_back(thunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addFunction(std::shared_ptr<Function> function) {
|
||||||
|
functions.push_back(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::setErrorReporter(ErrorReporter* reporter) {
|
||||||
|
errorReporter = reporter;
|
||||||
|
if (environment) {
|
||||||
|
environment->setErrorReporter(reporter);
|
||||||
|
}
|
||||||
|
addStdLibFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isInteractiveMode() const {
|
||||||
|
return isInteractive;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Environment> Interpreter::getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
|
||||||
|
environment = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, errorType, message, lexeme);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "../headers/bob.h"
|
#include "bob.h"
|
||||||
#include "../headers/Parser.h"
|
#include "Parser.h"
|
||||||
|
|
||||||
void Bob::runFile(const std::string& path)
|
void Bob::runFile(const std::string& path)
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
//
|
//
|
||||||
#include "../headers/bob.h"
|
#include "bob.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]){
|
int main(int argc, char* argv[]){
|
||||||
Bob bobLang;
|
Bob bobLang;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "../headers/ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <string>
|
#include <string>
|
||||||
3
src/sources/parsing/Expression.cpp
Normal file
3
src/sources/parsing/Expression.cpp
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#include "Expression.h"
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#include "../headers/Lexer.h"
|
#include "Lexer.h"
|
||||||
#include "../headers/ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
#include "../headers/helperFunctions/HelperFunctions.h"
|
#include "helperFunctions/HelperFunctions.h"
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
#include "../headers/Parser.h"
|
#include "Parser.h"
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#include "../headers/Environment.h"
|
#include "Environment.h"
|
||||||
#include "../headers/ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
void Environment::assign(const Token& name, const Value& value) {
|
void Environment::assign(const Token& name, const Value& value) {
|
||||||
auto it = variables.find(name.lexeme);
|
auto it = variables.find(name.lexeme);
|
||||||
434
src/sources/runtime/Evaluator.cpp
Normal file
434
src/sources/runtime/Evaluator.cpp
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
#include "Evaluator.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
#include "helperFunctions/HelperFunctions.h"
|
||||||
|
|
||||||
|
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
|
||||||
|
|
||||||
|
Value Evaluator::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||||
|
if (expr->isNull) {
|
||||||
|
return NONE_VALUE;
|
||||||
|
}
|
||||||
|
if (expr->isNumber) {
|
||||||
|
double num;
|
||||||
|
if (expr->value.length() > 2 && expr->value[0] == '0' && expr->value[1] == 'b') {
|
||||||
|
num = binaryStringToLong(expr->value);
|
||||||
|
} else {
|
||||||
|
num = std::stod(expr->value);
|
||||||
|
}
|
||||||
|
return Value(num);
|
||||||
|
}
|
||||||
|
if (expr->isBoolean) {
|
||||||
|
if (expr->value == "true") return TRUE_VALUE;
|
||||||
|
if (expr->value == "false") return FALSE_VALUE;
|
||||||
|
}
|
||||||
|
return Value(expr->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) {
|
||||||
|
return interpreter->evaluate(expression->expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
|
||||||
|
{
|
||||||
|
Value right = interpreter->evaluate(expression->right);
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case MINUS:
|
||||||
|
if (!right.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
return Value(-right.asNumber());
|
||||||
|
|
||||||
|
case BANG:
|
||||||
|
return Value(!interpreter->isTruthy(right));
|
||||||
|
|
||||||
|
case BIN_NOT:
|
||||||
|
if (!right.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Operand must be a number when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Operand must be a number when using: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
return Value(static_cast<double>(~(static_cast<long>(right.asNumber()))));
|
||||||
|
|
||||||
|
default:
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Invalid unary operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Invalid unary operator: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
|
||||||
|
Value left = interpreter->evaluate(expression->left);
|
||||||
|
Value right = interpreter->evaluate(expression->right);
|
||||||
|
|
||||||
|
// Handle logical operators (AND, OR) - these work with any types
|
||||||
|
if (expression->oper.type == AND) {
|
||||||
|
return interpreter->isTruthy(left) ? right : left;
|
||||||
|
}
|
||||||
|
if (expression->oper.type == OR) {
|
||||||
|
return interpreter->isTruthy(left) ? left : right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle equality operators - these work with any types
|
||||||
|
if (expression->oper.type == DOUBLE_EQUAL || expression->oper.type == BANG_EQUAL) {
|
||||||
|
bool equal = interpreter->isEqual(left, right);
|
||||||
|
return Value(expression->oper.type == DOUBLE_EQUAL ? equal : !equal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle comparison operators - only work with numbers
|
||||||
|
if (expression->oper.type == GREATER || expression->oper.type == GREATER_EQUAL ||
|
||||||
|
expression->oper.type == LESS || expression->oper.type == LESS_EQUAL) {
|
||||||
|
|
||||||
|
if (left.isNumber() && right.isNumber()) {
|
||||||
|
double leftNum = left.asNumber();
|
||||||
|
double rightNum = right.asNumber();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case GREATER: return Value(leftNum > rightNum);
|
||||||
|
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
||||||
|
case LESS: return Value(leftNum < rightNum);
|
||||||
|
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
||||||
|
default: break; // Unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error for non-number comparisons
|
||||||
|
std::string opName;
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case GREATER: opName = ">"; break;
|
||||||
|
case GREATER_EQUAL: opName = ">="; break;
|
||||||
|
case LESS: opName = "<"; break;
|
||||||
|
case LESS_EQUAL: opName = "<="; break;
|
||||||
|
default: break; // Unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()), opName);
|
||||||
|
throw std::runtime_error(ErrorUtils::makeOperatorError(opName, left.getType(), right.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all other operators using Value's operator overloads
|
||||||
|
try {
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
case MINUS: return left - right;
|
||||||
|
case STAR: return left * right;
|
||||||
|
case SLASH: return left / right;
|
||||||
|
case PERCENT: return left % right;
|
||||||
|
case BIN_AND: return left & right;
|
||||||
|
case BIN_OR: return left | right;
|
||||||
|
case BIN_XOR: return left ^ right;
|
||||||
|
case BIN_SLEFT: return left << right;
|
||||||
|
case BIN_SRIGHT: return left >> right;
|
||||||
|
default:
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Unknown operator: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||||
|
throw std::runtime_error("Unknown operator: " + expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
// The Value operators provide good error messages, just add context
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
e.what(), expression->oper.lexeme);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
||||||
|
{
|
||||||
|
return interpreter->getEnvironment()->get(expression->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
||||||
|
// Get the current value of the operand
|
||||||
|
Value currentValue = interpreter->evaluate(expression->operand);
|
||||||
|
|
||||||
|
if (!currentValue.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentNum = currentValue.asNumber();
|
||||||
|
double newValue;
|
||||||
|
|
||||||
|
// Determine the operation based on the operator
|
||||||
|
if (expression->oper.type == PLUS_PLUS) {
|
||||||
|
newValue = currentNum + 1.0;
|
||||||
|
} else if (expression->oper.type == MINUS_MINUS) {
|
||||||
|
newValue = currentNum - 1.0;
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Invalid increment/decrement operator.", "");
|
||||||
|
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the variable or array element
|
||||||
|
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||||
|
interpreter->getEnvironment()->assign(varExpr->name, Value(newValue));
|
||||||
|
} else if (auto arrayExpr = std::dynamic_pointer_cast<ArrayIndexExpr>(expression->operand)) {
|
||||||
|
// Handle array indexing increment/decrement
|
||||||
|
Value array = interpreter->evaluate(arrayExpr->array);
|
||||||
|
Value index = interpreter->evaluate(arrayExpr->index);
|
||||||
|
|
||||||
|
if (!array.isArray()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Can only index arrays", "");
|
||||||
|
throw std::runtime_error("Can only index arrays");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!index.isNumber()) {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Array index must be a number", "");
|
||||||
|
throw std::runtime_error("Array index must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = static_cast<int>(index.asNumber());
|
||||||
|
std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
|
interpreter->reportError(arrayExpr->bracket.line, arrayExpr->bracket.column,
|
||||||
|
"Runtime Error", "Array index out of bounds", "");
|
||||||
|
throw std::runtime_error("Array index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the array element
|
||||||
|
arr[idx] = Value(newValue);
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to variables or array elements.", "");
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to variables or array elements.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the appropriate value based on prefix/postfix
|
||||||
|
if (expression->isPrefix) {
|
||||||
|
return Value(newValue); // Prefix: return new value
|
||||||
|
} else {
|
||||||
|
return currentValue; // Postfix: return old value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
||||||
|
Value value = interpreter->evaluate(expression->value);
|
||||||
|
|
||||||
|
switch (expression->op.type) {
|
||||||
|
case PLUS_EQUAL:
|
||||||
|
case MINUS_EQUAL:
|
||||||
|
case STAR_EQUAL:
|
||||||
|
case SLASH_EQUAL:
|
||||||
|
case PERCENT_EQUAL:
|
||||||
|
case BIN_AND_EQUAL:
|
||||||
|
case BIN_OR_EQUAL:
|
||||||
|
case BIN_XOR_EQUAL:
|
||||||
|
case BIN_SLEFT_EQUAL:
|
||||||
|
case BIN_SRIGHT_EQUAL: {
|
||||||
|
Value currentValue = interpreter->getEnvironment()->get(expression->name);
|
||||||
|
|
||||||
|
// ... (rest of compound assignment logic) ...
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
interpreter->getEnvironment()->assign(expression->name, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
|
||||||
|
Value condition = interpreter->evaluate(expression->condition);
|
||||||
|
|
||||||
|
if (interpreter->isTruthy(condition)) {
|
||||||
|
return interpreter->evaluate(expression->thenExpr);
|
||||||
|
} else {
|
||||||
|
return interpreter->evaluate(expression->elseExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||||
|
Value callee = expression->callee->accept(this);
|
||||||
|
|
||||||
|
std::vector<Value> arguments;
|
||||||
|
for (const auto& argument : expression->arguments) {
|
||||||
|
arguments.push_back(argument->accept(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callee.isFunction()) {
|
||||||
|
Function* function = callee.asFunction();
|
||||||
|
|
||||||
|
// Check arity
|
||||||
|
if (arguments.size() != function->params.size()) {
|
||||||
|
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
||||||
|
"Expected " + std::to_string(function->params.size()) + " arguments but got " +
|
||||||
|
std::to_string(arguments.size()) + ".", "");
|
||||||
|
throw std::runtime_error("Wrong number of arguments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new environment for function call
|
||||||
|
auto environment = std::make_shared<Environment>(function->closure);
|
||||||
|
for (size_t i = 0; i < function->params.size(); i++) {
|
||||||
|
environment->define(function->params[i], arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute function body
|
||||||
|
auto previous = interpreter->getEnvironment();
|
||||||
|
interpreter->setEnvironment(environment);
|
||||||
|
|
||||||
|
ExecutionContext context;
|
||||||
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const auto& stmt : function->body) {
|
||||||
|
interpreter->execute(stmt, &context);
|
||||||
|
if (context.hasReturn) {
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
return context.returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
return NONE_VALUE;
|
||||||
|
|
||||||
|
} else if (callee.isBuiltinFunction()) {
|
||||||
|
BuiltinFunction* builtinFunction = callee.asBuiltinFunction();
|
||||||
|
return builtinFunction->func(arguments, expression->paren.line, expression->paren.column);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
|
||||||
|
"Can only call functions and classes.", "");
|
||||||
|
throw std::runtime_error("Can only call functions and classes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitArrayLiteralExpr(const std::shared_ptr<ArrayLiteralExpr>& expr) {
|
||||||
|
std::vector<Value> elements;
|
||||||
|
|
||||||
|
for (const auto& element : expr->elements) {
|
||||||
|
elements.push_back(interpreter->evaluate(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitArrayIndexExpr(const std::shared_ptr<ArrayIndexExpr>& expr) {
|
||||||
|
Value array = expr->array->accept(this);
|
||||||
|
Value index = expr->index->accept(this);
|
||||||
|
|
||||||
|
if (array.isArray()) {
|
||||||
|
// Handle array indexing
|
||||||
|
if (!index.isNumber()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index must be a number", "");
|
||||||
|
throw std::runtime_error("Array index must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = static_cast<int>(index.asNumber());
|
||||||
|
const std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index out of bounds", "");
|
||||||
|
throw std::runtime_error("Array index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr[idx];
|
||||||
|
|
||||||
|
} else if (array.isDict()) {
|
||||||
|
// Handle dictionary indexing
|
||||||
|
if (!index.isString()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Dictionary key must be a string", "");
|
||||||
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key = index.asString();
|
||||||
|
const std::unordered_map<std::string, Value>& dict = array.asDict();
|
||||||
|
|
||||||
|
auto it = dict.find(key);
|
||||||
|
if (it != dict.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return NONE_VALUE; // Return none for missing keys
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Can only index arrays and dictionaries", "");
|
||||||
|
throw std::runtime_error("Can only index arrays and dictionaries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitArrayAssignExpr(const std::shared_ptr<ArrayAssignExpr>& expr) {
|
||||||
|
Value array = expr->array->accept(this);
|
||||||
|
Value index = expr->index->accept(this);
|
||||||
|
Value value = expr->value->accept(this);
|
||||||
|
|
||||||
|
if (array.isArray()) {
|
||||||
|
// Handle array assignment
|
||||||
|
if (!index.isNumber()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index must be a number", "");
|
||||||
|
throw std::runtime_error("Array index must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = static_cast<int>(index.asNumber());
|
||||||
|
std::vector<Value>& arr = array.asArray();
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(arr.size())) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Array index out of bounds", "");
|
||||||
|
throw std::runtime_error("Array index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
arr[idx] = value;
|
||||||
|
return value;
|
||||||
|
|
||||||
|
} else if (array.isDict()) {
|
||||||
|
// Handle dictionary assignment
|
||||||
|
if (!index.isString()) {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Dictionary key must be a string", "");
|
||||||
|
throw std::runtime_error("Dictionary key must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key = index.asString();
|
||||||
|
std::unordered_map<std::string, Value>& dict = array.asDict();
|
||||||
|
|
||||||
|
dict[key] = value;
|
||||||
|
return value;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interpreter->reportError(expr->bracket.line, expr->bracket.column, "Runtime Error",
|
||||||
|
"Can only assign to array or dictionary elements", "");
|
||||||
|
throw std::runtime_error("Can only assign to array or dictionary elements");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Value Evaluator::visitDictLiteralExpr(const std::shared_ptr<DictLiteralExpr>& expr) {
|
||||||
|
std::unordered_map<std::string, Value> dict;
|
||||||
|
|
||||||
|
for (const auto& pair : expr->pairs) {
|
||||||
|
Value value = interpreter->evaluate(pair.second);
|
||||||
|
dict[pair.first] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
for (const Token& param : expression->params) {
|
||||||
|
paramNames.push_back(param.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto function = std::make_shared<Function>("", paramNames, expression->body, interpreter->getEnvironment());
|
||||||
|
interpreter->addFunction(function);
|
||||||
|
return Value(function.get());
|
||||||
|
}
|
||||||
245
src/sources/runtime/Executor.cpp
Normal file
245
src/sources/runtime/Executor.cpp
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#include "Executor.h"
|
||||||
|
#include "Evaluator.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
|
||||||
|
: interpreter(interpreter), evaluator(evaluator) {}
|
||||||
|
|
||||||
|
Executor::~Executor() {}
|
||||||
|
|
||||||
|
void Executor::interpret(const std::vector<std::shared_ptr<Stmt>>& statements) {
|
||||||
|
for (const auto& statement : statements) {
|
||||||
|
execute(statement, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
||||||
|
statement->accept(this, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::executeBlock(const std::vector<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
||||||
|
std::shared_ptr<Environment> previous = interpreter->getEnvironment();
|
||||||
|
interpreter->setEnvironment(env);
|
||||||
|
|
||||||
|
for (const auto& statement : statements) {
|
||||||
|
execute(statement, context);
|
||||||
|
if (context && (context->hasReturn || context->shouldBreak || context->shouldContinue)) {
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interpreter->setEnvironment(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
||||||
|
auto newEnv = std::make_shared<Environment>(interpreter->getEnvironment());
|
||||||
|
executeBlock(statement->statements, newEnv, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = statement->expression->accept(evaluator);
|
||||||
|
|
||||||
|
if (interpreter->isInteractiveMode())
|
||||||
|
std::cout << "\u001b[38;5;8m[" << interpreter->stringify(value) << "]\u001b[38;5;15m\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = NONE_VALUE;
|
||||||
|
if (statement->initializer != nullptr) {
|
||||||
|
value = statement->initializer->accept(evaluator);
|
||||||
|
}
|
||||||
|
interpreter->getEnvironment()->define(statement->name.lexeme, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context) {
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
for (const Token& param : statement->params) {
|
||||||
|
paramNames.push_back(param.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto function = std::make_shared<Function>(statement->name.lexeme,
|
||||||
|
paramNames,
|
||||||
|
statement->body,
|
||||||
|
interpreter->getEnvironment());
|
||||||
|
interpreter->addFunction(function);
|
||||||
|
interpreter->getEnvironment()->define(statement->name.lexeme, Value(function.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = NONE_VALUE;
|
||||||
|
if (statement->value != nullptr) {
|
||||||
|
value = statement->value->accept(evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context->isFunctionBody) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
|
execute(statement->thenBranch, context);
|
||||||
|
} else if (statement->elseBranch != nullptr) {
|
||||||
|
execute(statement->elseBranch, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitWhileStmt(const std::shared_ptr<WhileStmt>& statement, ExecutionContext* context) {
|
||||||
|
ExecutionContext loopContext;
|
||||||
|
if (context) {
|
||||||
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
|
execute(statement->body, &loopContext);
|
||||||
|
|
||||||
|
if (loopContext.hasReturn) {
|
||||||
|
if (context) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = loopContext.returnValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldContinue) {
|
||||||
|
loopContext.shouldContinue = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitDoWhileStmt(const std::shared_ptr<DoWhileStmt>& statement, ExecutionContext* context) {
|
||||||
|
ExecutionContext loopContext;
|
||||||
|
if (context) {
|
||||||
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
execute(statement->body, &loopContext);
|
||||||
|
|
||||||
|
if (loopContext.hasReturn) {
|
||||||
|
if (context) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = loopContext.returnValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldContinue) {
|
||||||
|
loopContext.shouldContinue = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} while (interpreter->isTruthy(statement->condition->accept(evaluator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitForStmt(const std::shared_ptr<ForStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (statement->initializer != nullptr) {
|
||||||
|
execute(statement->initializer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext loopContext;
|
||||||
|
if (context) {
|
||||||
|
loopContext.isFunctionBody = context->isFunctionBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (statement->condition == nullptr || interpreter->isTruthy(statement->condition->accept(evaluator))) {
|
||||||
|
execute(statement->body, &loopContext);
|
||||||
|
|
||||||
|
if (loopContext.hasReturn) {
|
||||||
|
if (context) {
|
||||||
|
context->hasReturn = true;
|
||||||
|
context->returnValue = loopContext.returnValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopContext.shouldContinue) {
|
||||||
|
loopContext.shouldContinue = false;
|
||||||
|
if (statement->increment != nullptr) {
|
||||||
|
statement->increment->accept(evaluator);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement->increment != nullptr) {
|
||||||
|
statement->increment->accept(evaluator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitBreakStmt(const std::shared_ptr<BreakStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (context) {
|
||||||
|
context->shouldBreak = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement, ExecutionContext* context) {
|
||||||
|
if (context) {
|
||||||
|
context->shouldContinue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
||||||
|
Value value = statement->value->accept(evaluator);
|
||||||
|
|
||||||
|
if (statement->op.type == EQUAL) {
|
||||||
|
interpreter->getEnvironment()->assign(statement->name, value);
|
||||||
|
} else {
|
||||||
|
// Handle compound assignment operators
|
||||||
|
Value currentValue = interpreter->getEnvironment()->get(statement->name);
|
||||||
|
Value newValue;
|
||||||
|
|
||||||
|
switch (statement->op.type) {
|
||||||
|
case PLUS_EQUAL:
|
||||||
|
newValue = currentValue + value;
|
||||||
|
break;
|
||||||
|
case MINUS_EQUAL:
|
||||||
|
newValue = currentValue - value;
|
||||||
|
break;
|
||||||
|
case STAR_EQUAL:
|
||||||
|
newValue = currentValue * value;
|
||||||
|
break;
|
||||||
|
case SLASH_EQUAL:
|
||||||
|
newValue = currentValue / value;
|
||||||
|
break;
|
||||||
|
case PERCENT_EQUAL:
|
||||||
|
newValue = currentValue % value;
|
||||||
|
break;
|
||||||
|
case BIN_AND_EQUAL:
|
||||||
|
newValue = currentValue & value;
|
||||||
|
break;
|
||||||
|
case BIN_OR_EQUAL:
|
||||||
|
newValue = currentValue | value;
|
||||||
|
break;
|
||||||
|
case BIN_XOR_EQUAL:
|
||||||
|
newValue = currentValue ^ value;
|
||||||
|
break;
|
||||||
|
case BIN_SLEFT_EQUAL:
|
||||||
|
newValue = currentValue << value;
|
||||||
|
break;
|
||||||
|
case BIN_SRIGHT_EQUAL:
|
||||||
|
newValue = currentValue >> value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
|
||||||
|
"Unknown assignment operator: " + statement->op.lexeme, "");
|
||||||
|
throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter->getEnvironment()->assign(statement->name, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/sources/runtime/Interpreter.cpp
Normal file
96
src/sources/runtime/Interpreter.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include "Interpreter.h"
|
||||||
|
#include "Evaluator.h"
|
||||||
|
#include "Executor.h"
|
||||||
|
#include "BobStdLib.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Interpreter::Interpreter(bool isInteractive)
|
||||||
|
: isInteractive(isInteractive), errorReporter(nullptr) {
|
||||||
|
evaluator = std::make_unique<Evaluator>(this);
|
||||||
|
executor = std::make_unique<Executor>(this, evaluator.get());
|
||||||
|
environment = std::make_shared<Environment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Interpreter::~Interpreter() = default;
|
||||||
|
|
||||||
|
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
|
||||||
|
executor->interpret(statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context) {
|
||||||
|
statement->accept(executor.get(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env, ExecutionContext* context) {
|
||||||
|
executor->executeBlock(statements, env, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
||||||
|
Value result = expr->accept(evaluator.get());
|
||||||
|
if (inThunkExecution) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return runTrampoline(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::runTrampoline(Value initialResult) {
|
||||||
|
Value current = initialResult;
|
||||||
|
while (current.isThunk()) {
|
||||||
|
current = current.asThunk()->execute();
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isTruthy(Value object) {
|
||||||
|
return diagnostics.isTruthy(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isEqual(Value a, Value b) {
|
||||||
|
return diagnostics.isEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Interpreter::stringify(Value object) {
|
||||||
|
return diagnostics.stringify(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addStdLibFunctions() {
|
||||||
|
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||||
|
builtinFunctions.push_back(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addThunk(std::shared_ptr<Thunk> thunk) {
|
||||||
|
thunks.push_back(thunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::addFunction(std::shared_ptr<Function> function) {
|
||||||
|
functions.push_back(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::setErrorReporter(ErrorReporter* reporter) {
|
||||||
|
errorReporter = reporter;
|
||||||
|
if (environment) {
|
||||||
|
environment->setErrorReporter(reporter);
|
||||||
|
}
|
||||||
|
addStdLibFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::isInteractiveMode() const {
|
||||||
|
return isInteractive;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Environment> Interpreter::getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::setEnvironment(std::shared_ptr<Environment> env) {
|
||||||
|
environment = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& lexeme) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(line, column, errorType, message, lexeme);
|
||||||
|
}
|
||||||
|
}
|
||||||
215
src/sources/runtime/RuntimeDiagnostics.cpp
Normal file
215
src/sources/runtime/RuntimeDiagnostics.cpp
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
#include "RuntimeDiagnostics.h"
|
||||||
|
#include "Value.h"
|
||||||
|
#include "TypeWrapper.h" // For Function and BuiltinFunction definitions
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <limits>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
bool RuntimeDiagnostics::isTruthy(Value object) {
|
||||||
|
if(object.isBoolean()) {
|
||||||
|
return object.asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object.isNone()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object.isNumber()) {
|
||||||
|
return object.asNumber() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object.isString()) {
|
||||||
|
return object.asString().length() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeDiagnostics::isEqual(Value a, Value b) {
|
||||||
|
// Handle none comparisons first
|
||||||
|
if (a.isNone() || b.isNone()) {
|
||||||
|
return a.isNone() && b.isNone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle same type comparisons
|
||||||
|
if (a.isNumber() && b.isNumber()) {
|
||||||
|
return a.asNumber() == b.asNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isBoolean() && b.isBoolean()) {
|
||||||
|
return a.asBoolean() == b.asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isString() && b.isString()) {
|
||||||
|
return a.asString() == b.asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isArray() && b.isArray()) {
|
||||||
|
const std::vector<Value>& arrA = a.asArray();
|
||||||
|
const std::vector<Value>& arrB = b.asArray();
|
||||||
|
|
||||||
|
if (arrA.size() != arrB.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arrA.size(); i++) {
|
||||||
|
if (!isEqual(arrA[i], arrB[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isFunction() && b.isFunction()) {
|
||||||
|
// Functions are equal only if they are the same object
|
||||||
|
return a.asFunction() == b.asFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isBuiltinFunction() && b.isBuiltinFunction()) {
|
||||||
|
// Builtin functions are equal only if they are the same object
|
||||||
|
return a.asBuiltinFunction() == b.asBuiltinFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross-type comparisons that make sense
|
||||||
|
if (a.isNumber() && b.isBoolean()) {
|
||||||
|
// Numbers and booleans: 0 and false are equal, non-zero and true are equal
|
||||||
|
if (b.asBoolean()) {
|
||||||
|
return a.asNumber() != 0.0;
|
||||||
|
} else {
|
||||||
|
return a.asNumber() == 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isBoolean() && b.isNumber()) {
|
||||||
|
// Same as above, but reversed
|
||||||
|
if (a.asBoolean()) {
|
||||||
|
return b.asNumber() != 0.0;
|
||||||
|
} else {
|
||||||
|
return b.asNumber() == 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other type combinations, return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeDiagnostics::stringify(Value object) {
|
||||||
|
if(object.isNone()) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
else if(object.isNumber()) {
|
||||||
|
return formatNumber(object.asNumber());
|
||||||
|
}
|
||||||
|
else if(object.isString()) {
|
||||||
|
return object.asString();
|
||||||
|
}
|
||||||
|
else if(object.isBoolean()) {
|
||||||
|
return object.asBoolean() == 1 ? "true" : "false";
|
||||||
|
}
|
||||||
|
else if(object.isFunction()) {
|
||||||
|
return "<function " + object.asFunction()->name + ">";
|
||||||
|
}
|
||||||
|
else if(object.isBuiltinFunction()) {
|
||||||
|
return "<builtin_function " + object.asBuiltinFunction()->name + ">";
|
||||||
|
}
|
||||||
|
else if(object.isArray()) {
|
||||||
|
return formatArray(object.asArray());
|
||||||
|
}
|
||||||
|
else if(object.isDict()) {
|
||||||
|
return formatDict(object.asDict());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Could not convert object to string");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeDiagnostics::formatNumber(double value) {
|
||||||
|
double integral = value;
|
||||||
|
double fractional = std::modf(value, &integral);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
if(std::abs(fractional) < std::numeric_limits<double>::epsilon()) {
|
||||||
|
ss << std::fixed << std::setprecision(0) << integral;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << value;
|
||||||
|
std::string str = ss.str();
|
||||||
|
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||||
|
if (str.back() == '.') {
|
||||||
|
str.pop_back();
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
|
||||||
|
std::string result = "[";
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arr.size(); i++) {
|
||||||
|
if (i > 0) result += ", ";
|
||||||
|
result += stringify(arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "]";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) {
|
||||||
|
std::string result = "{";
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for (const auto& pair : dict) {
|
||||||
|
if (!first) result += ", ";
|
||||||
|
result += "\"" + pair.first + "\": " + stringify(pair.second);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "}";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
|
||||||
|
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
|
||||||
|
// This is more conservative to prevent dangling pointer issues
|
||||||
|
functions.erase(
|
||||||
|
std::remove_if(functions.begin(), functions.end(),
|
||||||
|
[](const std::shared_ptr<BuiltinFunction>& func) {
|
||||||
|
return func.use_count() == 1; // Only referenced by this vector, nowhere else
|
||||||
|
}),
|
||||||
|
functions.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>& thunks) {
|
||||||
|
// Only remove thunks that are definitely not referenced anywhere (use_count == 1)
|
||||||
|
// This is more conservative to prevent dangling pointer issues
|
||||||
|
thunks.erase(
|
||||||
|
std::remove_if(thunks.begin(), thunks.end(),
|
||||||
|
[](const std::shared_ptr<Thunk>& thunk) {
|
||||||
|
return thunk.use_count() == 1; // Only referenced by this vector, nowhere else
|
||||||
|
}),
|
||||||
|
thunks.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& functions,
|
||||||
|
std::vector<std::shared_ptr<Thunk>>& thunks) {
|
||||||
|
// More aggressive cleanup when breaking array references
|
||||||
|
functions.erase(
|
||||||
|
std::remove_if(functions.begin(), functions.end(),
|
||||||
|
[](const std::shared_ptr<BuiltinFunction>& func) {
|
||||||
|
return func.use_count() <= 2; // More aggressive than == 1
|
||||||
|
}),
|
||||||
|
functions.end()
|
||||||
|
);
|
||||||
|
|
||||||
|
thunks.erase(
|
||||||
|
std::remove_if(thunks.begin(), thunks.end(),
|
||||||
|
[](const std::shared_ptr<Thunk>& thunk) {
|
||||||
|
return thunk.use_count() <= 2; // More aggressive than == 1
|
||||||
|
}),
|
||||||
|
thunks.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
4
src/sources/runtime/TypeWrapper.cpp
Normal file
4
src/sources/runtime/TypeWrapper.cpp
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
#include "TypeWrapper.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "../headers/Value.h"
|
#include "Value.h"
|
||||||
|
|
||||||
// Global constants for common values (no heap allocation)
|
// Global constants for common values (no heap allocation)
|
||||||
const Value NONE_VALUE = Value();
|
const Value NONE_VALUE = Value();
|
||||||
@ -1,8 +1,8 @@
|
|||||||
#include "../headers/BobStdLib.h"
|
#include "BobStdLib.h"
|
||||||
#include "../headers/Interpreter.h"
|
#include "Interpreter.h"
|
||||||
#include "../headers/ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
#include "../headers/Lexer.h"
|
#include "Lexer.h"
|
||||||
#include "../headers/Parser.h"
|
#include "Parser.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
13
tco.bob
13
tco.bob
@ -1,13 +0,0 @@
|
|||||||
func countdown(n)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(n == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
print(n);
|
|
||||||
|
|
||||||
return countdown(n - 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
countdown(1000000);
|
|
||||||
@ -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");
|
|
||||||
Loading…
Reference in New Issue
Block a user