Cleaned up project structure
This commit is contained in:
parent
2104fbe1f5
commit
6c17ce96f0
49
Makefile
49
Makefile
@ -1,4 +1,4 @@
|
||||
# Makefile
|
||||
# Makefile - Bob language interpreter
|
||||
|
||||
# Compiler
|
||||
CC = g++
|
||||
@ -6,43 +6,50 @@ CC = g++
|
||||
# Compiler flags
|
||||
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
|
||||
|
||||
# Source directory
|
||||
SRC_DIR = ./source
|
||||
# Source layout:
|
||||
# src/
|
||||
# ├── headers/ - All header files (public interface)
|
||||
# │ ├── runtime/ - Runtime execution headers
|
||||
# │ ├── parsing/ - Front-end processing headers
|
||||
# │ ├── stdlib/ - Standard library headers
|
||||
# │ ├── cli/ - CLI headers
|
||||
# │ └── common/ - Shared utilities (helperFunctions)
|
||||
# └── sources/ - All source files (implementation)
|
||||
# ├── runtime/ - Interpreter, Evaluator, Executor, Environment, Value, TypeWrapper
|
||||
# ├── parsing/ - Lexer, Parser, ErrorReporter, Expression AST
|
||||
# ├── stdlib/ - Built-in functions (BobStdLib)
|
||||
# └── cli/ - Command-line interface (main, bob)
|
||||
|
||||
# Output directory
|
||||
SRC_ROOT = ./src
|
||||
BUILD_DIR = ./build
|
||||
|
||||
# Find all C++ files recursively in the source directory
|
||||
CPP_FILES := $(shell find $(SRC_DIR) -type f -name '*.cpp')
|
||||
# Find all .cpp files under src/sources (recursively)
|
||||
CPP_FILES := $(shell find $(SRC_ROOT)/sources -type f -name '*.cpp')
|
||||
|
||||
# Generate object file names by replacing the source directory with the build directory
|
||||
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||
# Map every source file to its corresponding object file inside build/
|
||||
# Strip src/sources/ prefix and add build/ prefix
|
||||
OBJ_FILES := $(patsubst $(SRC_ROOT)/sources/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||
|
||||
# Create directories for object files
|
||||
# Make sure every object directory exists ahead of time
|
||||
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
||||
|
||||
# Default target
|
||||
# Default goal
|
||||
all: build
|
||||
|
||||
# Rule to create necessary directories
|
||||
$(DIRS):
|
||||
mkdir -p $(patsubst $(SRC_DIR)/%, $(OUTPUT_DIR)/%, $@)
|
||||
# Pattern rule – compile each .cpp from sources/ into a mirrored .o inside build/
|
||||
$(BUILD_DIR)/%.o: $(SRC_ROOT)/sources/%.cpp
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) -I$(SRC_ROOT)/headers/runtime -I$(SRC_ROOT)/headers/parsing -I$(SRC_ROOT)/headers/stdlib -I$(SRC_ROOT)/headers/cli -I$(SRC_ROOT)/headers/common -c $< -o $@
|
||||
|
||||
# Rule to compile object files
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Rule to link object files into the final executable
|
||||
# Link all objects into the final executable
|
||||
$(BUILD_DIR)/bob: $(OBJ_FILES)
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
|
||||
# Convenience targets
|
||||
run:
|
||||
./$(BUILD_DIR)/bob
|
||||
|
||||
build: clean $(BUILD_DIR)/bob
|
||||
|
||||
|
||||
# Clean build directory
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)/*
|
||||
|
||||
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 <fstream>
|
||||
#include <string>
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/helperFunctions/ShortHands.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include "Lexer.h"
|
||||
#include "Interpreter.h"
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
#define VERSION "0.0.3"
|
||||
|
||||
@ -18,13 +18,7 @@ struct BreakStmt;
|
||||
struct ContinueStmt;
|
||||
struct AssignStmt;
|
||||
|
||||
struct ExecutionContext {
|
||||
bool isFunctionBody = false;
|
||||
bool hasReturn = false;
|
||||
bool hasBreak = false;
|
||||
bool hasContinue = false;
|
||||
Value returnValue;
|
||||
};
|
||||
#include "ExecutionContext.h"
|
||||
|
||||
struct StmtVisitor
|
||||
{
|
||||
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 "../headers/bob.h"
|
||||
#include "../headers/Parser.h"
|
||||
#include "bob.h"
|
||||
#include "Parser.h"
|
||||
|
||||
void Bob::runFile(const std::string& path)
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
//
|
||||
|
||||
//
|
||||
#include "../headers/bob.h"
|
||||
#include "bob.h"
|
||||
|
||||
int main(int argc, char* argv[]){
|
||||
Bob bobLang;
|
||||
@ -1,4 +1,4 @@
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include "ErrorReporter.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#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 "../headers/ErrorReporter.h"
|
||||
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||
#include "Lexer.h"
|
||||
#include "ErrorReporter.h"
|
||||
#include "helperFunctions/HelperFunctions.h"
|
||||
#include <cctype>
|
||||
#include <stdexcept>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
#include "../headers/Parser.h"
|
||||
#include "Parser.h"
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#include "../headers/Environment.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include "Environment.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
void Environment::assign(const Token& name, const Value& value) {
|
||||
auto it = variables.find(name.lexeme);
|
||||
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)
|
||||
const Value NONE_VALUE = Value();
|
||||
@ -1,8 +1,8 @@
|
||||
#include "../headers/BobStdLib.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/Parser.h"
|
||||
#include "BobStdLib.h"
|
||||
#include "Interpreter.h"
|
||||
#include "ErrorReporter.h"
|
||||
#include "Lexer.h"
|
||||
#include "Parser.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#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