Code cleanup

This commit is contained in:
Bobby Lucero 2025-08-10 16:33:48 -04:00
parent 85d3381575
commit 266cca5b42
15 changed files with 114 additions and 398 deletions

View File

@ -0,0 +1,35 @@
#pragma once
#include "Value.h"
#include "Lexer.h"
// Utility to compute the result of a compound assignment (e.g., +=, -=, etc.)
// Leaves error reporting to callers; throws std::runtime_error on unknown operator
inline Value computeCompoundAssignment(const Value& currentValue, TokenType opType, const Value& rhs) {
switch (opType) {
case PLUS_EQUAL:
return currentValue + rhs;
case MINUS_EQUAL:
return currentValue - rhs;
case STAR_EQUAL:
return currentValue * rhs;
case SLASH_EQUAL:
return currentValue / rhs;
case PERCENT_EQUAL:
return currentValue % rhs;
case BIN_AND_EQUAL:
return currentValue & rhs;
case BIN_OR_EQUAL:
return currentValue | rhs;
case BIN_XOR_EQUAL:
return currentValue ^ rhs;
case BIN_SLEFT_EQUAL:
return currentValue << rhs;
case BIN_SRIGHT_EQUAL:
return currentValue >> rhs;
default:
throw std::runtime_error("Unknown compound assignment operator");
}
}

View File

@ -41,19 +41,11 @@ public:
// Enhanced get with error reporting
Value get(const Token& name);
// Get by string name with error reporting
Value get(const std::string& name);
// Prune heavy containers in a snapshot to avoid capture cycles
void pruneForClosureCapture();
std::shared_ptr<Environment> getParent() const { return parent; }
inline void clear() { variables.clear(); }
// Set parent environment for TCO environment reuse
inline void setParent(std::shared_ptr<Environment> newParent) {
parent = newParent;
}
private:
std::unordered_map<std::string, Value> variables;

View File

@ -1,15 +1,4 @@
#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>
@ -17,6 +6,21 @@
#include <optional>
#include <functional>
#include "Value.h"
#include "RuntimeDiagnostics.h"
struct Expr;
struct Stmt;
struct Environment;
struct BuiltinFunction;
struct Function;
struct Thunk;
class ErrorReporter;
struct ExecutionContext;
struct CallExpr;
// Forward declaration
class Evaluator;
@ -66,9 +70,8 @@ private:
bool inThunkExecution = false;
// Automatic cleanup tracking
int functionCreationCount = 0;
int thunkCreationCount = 0;
static const int CLEANUP_THRESHOLD = 1000000;
static const int CLEANUP_THRESHOLD = 10000;
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
std::unique_ptr<Evaluator> evaluator;
@ -93,25 +96,17 @@ public:
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();
void addStdLibFunctions();
// Function creation count management
void incrementFunctionCreationCount();
int getFunctionCreationCount() const;
void resetFunctionCreationCount();
int getCleanupThreshold() const;
// Public access for Evaluator
bool& getInThunkExecutionRef() { return inThunkExecution; }
private:
Value evaluateWithoutTrampoline(const std::shared_ptr<Expr>& expr);
void addStdLibFunctions();
Value runTrampoline(Value initialResult);
};

View File

@ -28,8 +28,6 @@ public:
void cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions);
void cleanupUnusedFunctions(std::vector<std::shared_ptr<Function>>& 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);
void forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
std::vector<std::shared_ptr<Function>>& functions,
std::vector<std::shared_ptr<Thunk>>& thunks);

View File

@ -10,38 +10,7 @@
struct Stmt;
struct Environment;
struct Object
{
virtual ~Object(){};
};
struct Number : Object
{
double value;
explicit Number(double value) : value(value) {}
};
struct String : Object
{
std::string value;
explicit String(std::string str) : value(str) {}
~String(){
}
};
struct Boolean : Object
{
bool value;
explicit Boolean(bool value) : value(value) {}
};
struct None : public Object
{
};
struct Function : public Object
struct Function
{
const std::string name;
const std::vector<std::string> params;
@ -54,7 +23,7 @@ struct Function : public Object
: name(name), params(params), body(body), closure(closure) {}
};
struct BuiltinFunction : public Object
struct BuiltinFunction
{
const std::string name;
const std::function<Value(std::vector<Value>, int, int)> func;

View File

@ -424,5 +424,3 @@ struct Value {
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;
extern const Value ZERO_VALUE;
extern const Value ONE_VALUE;

View File

@ -22,8 +22,8 @@ void Bob::runFile(const std::string& path)
// Load source code into error reporter for context
errorReporter.loadSource(source, path);
// Connect error reporter to interpreter
interpreter->setErrorReporter(&errorReporter);
interpreter->addStdLibFunctions();
this->run(source);
}
@ -52,6 +52,7 @@ void Bob::runPrompt()
// Connect error reporter to interpreter
interpreter->setErrorReporter(&errorReporter);
interpreter->addStdLibFunctions();
this->run(line);
}

View File

@ -37,27 +37,14 @@ Value Environment::get(const Token& name) {
throw std::runtime_error("Undefined variable '" + name.lexeme + "'");
}
Value Environment::get(const std::string& name) {
auto it = variables.find(name);
if (it != variables.end()) {
return it->second;
}
if (parent != nullptr) {
return parent->get(name);
}
throw std::runtime_error("Undefined variable '" + name + "'");
}
void Environment::pruneForClosureCapture() {
for (auto &entry : variables) {
Value &v = entry.second;
if (v.isArray()) {
// Replace with a new empty array to avoid mutating original shared storage
entry.second = Value(std::vector<Value>{});
} else if (v.isDict()) {
// Replace with a new empty dict to avoid mutating original shared storage
entry.second = Value(std::unordered_map<std::string, Value>{});
}
}

View File

@ -1,5 +1,7 @@
#include "Evaluator.h"
#include "Interpreter.h"
#include "Environment.h"
#include "AssignmentUtils.h"
#include "helperFunctions/HelperFunctions.h"
Evaluator::Evaluator(Interpreter* interpreter) : interpreter(interpreter) {}
@ -213,75 +215,25 @@ Value Evaluator::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expres
Value Evaluator::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = interpreter->evaluate(expression->value);
if (expression->op.type == EQUAL) {
try {
// Check if the variable existed and whether it held a collection
bool existed = false;
bool wasCollection = false;
try {
Value oldValue = interpreter->getEnvironment()->get(expression->name);
existed = true;
wasCollection = oldValue.isArray() || oldValue.isDict();
} catch (...) {
existed = false;
}
// Assign first to release references held by the old values
interpreter->getEnvironment()->assign(expression->name, value);
// Now that the old values are released, perform cleanup on any reassignment
interpreter->forceCleanup();
} catch (const std::exception& e) {
std::cerr << "Error during assignment: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
}
} else {
// Handle compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(expression->name);
Value newValue;
switch (expression->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(expression->op.line, expression->op.column, "Runtime Error",
"Unknown assignment operator: " + expression->op.lexeme, "");
throw std::runtime_error("Unknown assignment operator: " + expression->op.lexeme);
}
interpreter->getEnvironment()->assign(expression->name, newValue);
return newValue;
if (expression->op.type == EQUAL) {
// Assign first to release references held by the old values
interpreter->getEnvironment()->assign(expression->name, value);
// Perform cleanup on any reassignment
interpreter->forceCleanup();
return value;
}
return value;
// Compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(expression->name);
try {
Value newValue = computeCompoundAssignment(currentValue, expression->op.type, value);
interpreter->getEnvironment()->assign(expression->name, newValue);
return newValue;
} catch (const std::runtime_error&) {
interpreter->reportError(expression->op.line, expression->op.column, "Runtime Error",
"Unknown assignment operator: " + expression->op.lexeme, "");
throw;
}
}
Value Evaluator::visitTernaryExpr(const std::shared_ptr<TernaryExpr>& expression) {
@ -453,10 +405,8 @@ Value Evaluator::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expressi
paramNames.push_back(param.lexeme);
}
// Capture a snapshot of the current environment so loop vars like 'i' are frozen per iteration
auto closureEnv = std::make_shared<Environment>(*interpreter->getEnvironment());
closureEnv->pruneForClosureCapture();
auto function = std::make_shared<Function>("", paramNames, expression->body, closureEnv);
return Value(function);
}

View File

@ -1,6 +1,8 @@
#include "Executor.h"
#include "Evaluator.h"
#include "Interpreter.h"
#include "Environment.h"
#include "AssignmentUtils.h"
#include <iostream>
Executor::Executor(Interpreter* interpreter, Evaluator* evaluator)
@ -194,66 +196,24 @@ void Executor::visitContinueStmt(const std::shared_ptr<ContinueStmt>& statement,
}
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
try {
Value value = statement->value->accept(evaluator);
Value value = statement->value->accept(evaluator);
if (statement->op.type == EQUAL) {
try {
// Assign first to release references held by the old value
interpreter->getEnvironment()->assign(statement->name, value);
// Clean up on any reassignment, regardless of old/new type
interpreter->forceCleanup();
} catch (const std::exception& e) {
std::cerr << "Error during assignment: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
}
} 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);
if (statement->op.type == EQUAL) {
// Assign first to release references held by the old value
interpreter->getEnvironment()->assign(statement->name, value);
// Clean up on any reassignment
interpreter->forceCleanup();
return;
}
} catch (const std::exception& e) {
std::cerr << "Error in visitAssignStmt: " << e.what() << std::endl;
throw; // Re-throw to see the full stack trace
// Compound assignment operators
Value currentValue = interpreter->getEnvironment()->get(statement->name);
try {
Value newValue = computeCompoundAssignment(currentValue, statement->op.type, value);
interpreter->getEnvironment()->assign(statement->name, newValue);
} catch (const std::runtime_error&) {
interpreter->reportError(statement->op.line, statement->op.column, "Runtime Error",
"Unknown assignment operator: " + statement->op.lexeme, "");
throw;
}
}

View File

@ -2,6 +2,8 @@
#include "Evaluator.h"
#include "Executor.h"
#include "BobStdLib.h"
#include "ErrorReporter.h"
#include "Environment.h"
#include <iostream>
Interpreter::Interpreter(bool isInteractive)
@ -61,9 +63,7 @@ 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);
@ -74,7 +74,6 @@ void Interpreter::setErrorReporter(ErrorReporter* reporter) {
if (environment) {
environment->setErrorReporter(reporter);
}
addStdLibFunctions();
}
bool Interpreter::isInteractiveMode() const {
@ -121,7 +120,6 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
}
if (!callee.isFunction()) {
// Provide better error message with type information (like original)
std::string errorMsg = "Can only call functions, got " + callee.getType();
if (errorReporter) {
errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error",
@ -150,9 +148,8 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
// Check if this is a tail call for inline TCO
if (expression->isTailCall) {
// Create a thunk for tail call optimization - original inline version
auto thunk = std::make_shared<Thunk>([this, function, arguments]() -> Value {
// Use RAII to manage environment (exactly like original)
ScopedEnv _env(environment);
environment = std::make_shared<Environment>(function->closure);
environment->setErrorReporter(errorReporter);
@ -164,12 +161,10 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
ExecutionContext context;
context.isFunctionBody = true;
// Use RAII to manage thunk execution flag
ScopedThunkFlag _inThunk(inThunkExecution);
// Execute function body (inline like original - direct accept for performance)
for (const auto& stmt : function->body) {
stmt->accept(executor.get(), &context); // Direct call like original
stmt->accept(executor.get(), &context);
if (context.hasReturn) {
return context.returnValue;
}
@ -178,10 +173,8 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
return context.returnValue;
});
// Store the thunk to keep it alive and return as Value (exactly like original)
thunks.push_back(thunk);
// Automatic cleanup check
thunkCreationCount++;
if (thunkCreationCount >= CLEANUP_THRESHOLD) {
cleanupUnusedThunks();
@ -190,7 +183,6 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
return Value(thunk);
} else {
// Normal function call - create new environment (exactly like original)
ScopedEnv _env(environment);
environment = std::make_shared<Environment>(function->closure);
environment->setErrorReporter(errorReporter);
@ -202,9 +194,8 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
ExecutionContext context;
context.isFunctionBody = true;
// Execute function body (exactly like original - direct accept for performance)
for (const auto& stmt : function->body) {
stmt->accept(executor.get(), &context); // Direct call like original
stmt->accept(executor.get(), &context);
if (context.hasReturn) {
return context.returnValue;
}
@ -214,19 +205,3 @@ Value Interpreter::evaluateCallExprInline(const std::shared_ptr<CallExpr>& expre
}
}
// Function creation count management
void Interpreter::incrementFunctionCreationCount() {
functionCreationCount++;
}
int Interpreter::getFunctionCreationCount() const {
return functionCreationCount;
}
void Interpreter::resetFunctionCreationCount() {
functionCreationCount = 0;
}
int Interpreter::getCleanupThreshold() const {
return 1000000; // Same as CLEANUP_THRESHOLD used for thunks
}

View File

@ -13,122 +13,19 @@
#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::isTruthy(Value object) { return object.isTruthy(); }
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;
}
return b.asBoolean() ? (a.asNumber() != 0.0) : (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;
}
return a.asBoolean() ? (b.asNumber() != 0.0) : (b.asNumber() == 0.0);
}
// For all other type combinations, return false
return false;
return a.equals(b);
}
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::stringify(Value object) { return object.toString(); }
std::string RuntimeDiagnostics::formatNumber(double value) {
double integral = value;
@ -150,31 +47,9 @@ std::string RuntimeDiagnostics::formatNumber(double value) {
}
}
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) {
std::string result = "[";
std::string RuntimeDiagnostics::formatArray(const std::vector<Value>& arr) { return Value(arr).toString(); }
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;
}
std::string RuntimeDiagnostics::formatDict(const std::unordered_map<std::string, Value>& dict) { return Value(dict).toString(); }
void RuntimeDiagnostics::cleanupUnusedFunctions(std::vector<std::shared_ptr<BuiltinFunction>>& functions) {
// Only remove functions that are definitely not referenced anywhere (use_count == 1)
@ -212,25 +87,7 @@ void RuntimeDiagnostics::cleanupUnusedThunks(std::vector<std::shared_ptr<Thunk>>
);
}
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()
);
}
void RuntimeDiagnostics::forceCleanup(std::vector<std::shared_ptr<BuiltinFunction>>& builtinFunctions,
std::vector<std::shared_ptr<Function>>& functions,

View File

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

View File

@ -4,5 +4,4 @@
const Value NONE_VALUE = Value();
const Value TRUE_VALUE = Value(true);
const Value FALSE_VALUE = Value(false);
const Value ZERO_VALUE = Value(0.0);
const Value ONE_VALUE = Value(1.0);

View File

@ -42,7 +42,7 @@ if (len(a[3692]) > 0) {
if (a[3693]["func"]) {
a[3693]["func"](); // Nested dict function
}
print(a);
//print(a);
//writeFile("array_contents.txt", toString(a));
print("Array contents written to array_contents.txt");
print("Memory before cleanup: " + memoryUsage() + " MB");