diff --git a/headers/Expression.h b/headers/Expression.h index e5dd2a3..846227a 100644 --- a/headers/Expression.h +++ b/headers/Expression.h @@ -1,6 +1,4 @@ -// -// Created by Bobby Lucero on 5/21/23. -// +// Expression AST nodes for Bob language #pragma once #include diff --git a/headers/Interpreter.h b/headers/Interpreter.h index 4a9a654..4acca7b 100644 --- a/headers/Interpreter.h +++ b/headers/Interpreter.h @@ -86,14 +86,14 @@ public: void interpret(std::vector> statements); - explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){ + explicit Interpreter(bool isInteractive) : isInteractive(isInteractive), errorReporter(nullptr){ environment = std::make_shared(); } virtual ~Interpreter() = default; private: std::shared_ptr environment; - bool IsInteractive; + bool isInteractive; std::vector> builtinFunctions; std::vector> functions; std::vector> thunks; // Store thunks to prevent memory leaks @@ -104,6 +104,7 @@ private: 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 diff --git a/source/BobStdLib.cpp b/source/BobStdLib.cpp index ea87028..bb5a17b 100644 --- a/source/BobStdLib.cpp +++ b/source/BobStdLib.cpp @@ -5,6 +5,7 @@ #include "../headers/Parser.h" #include #include +#include void BobStdLib::addToEnvironment(std::shared_ptr env, Interpreter& interpreter, ErrorReporter* errorReporter) { // Create a built-in toString function @@ -36,7 +37,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr env, Interpreter& throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); } // Use the interpreter's stringify function - std::cout << interpreter.stringify(args[0]) << std::endl; + std::cout << interpreter.stringify(args[0]) << '\n'; return NONE_VALUE; }); env->define("print", Value(printFunc.get())); @@ -405,7 +406,6 @@ void BobStdLib::addToEnvironment(std::shared_ptr env, Interpreter& } std::exit(exitCode); - return NONE_VALUE; // This line should never be reached }); env->define("exit", Value(exitFunc.get())); @@ -414,18 +414,30 @@ void BobStdLib::addToEnvironment(std::shared_ptr env, Interpreter& // Create a built-in sleep function for animations and timing auto sleepFunc = std::make_shared("sleep", - [](std::vector args, int line, int column) -> Value { + [errorReporter](std::vector args, int line, int column) -> Value { if (args.size() != 1) { - return NONE_VALUE; // Return none for wrong argument count + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); } if (!args[0].isNumber()) { - return NONE_VALUE; // Return none for wrong type + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "sleep() argument must be a number", "", true); + } + throw std::runtime_error("sleep() argument must be a number"); } double seconds = args[0].asNumber(); if (seconds < 0) { - return NONE_VALUE; // Return none for negative time + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "sleep() argument cannot be negative", "", true); + } + throw std::runtime_error("sleep() argument cannot be negative"); } // Convert to milliseconds and sleep @@ -450,6 +462,13 @@ void BobStdLib::addToEnvironment(std::shared_ptr env, Interpreter& throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + "."); } + // Seed the random number generator if not already done + static bool seeded = false; + if (!seeded) { + srand(static_cast(time(nullptr))); + seeded = true; + } + return Value(static_cast(rand()) / RAND_MAX); }); env->define("random", Value(randomFunc.get())); diff --git a/source/ErrorReporter.cpp b/source/ErrorReporter.cpp index f10d0db..73d9f95 100644 --- a/source/ErrorReporter.cpp +++ b/source/ErrorReporter.cpp @@ -107,7 +107,8 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string return; } - int maxWidth = 65; + static const int ERROR_DISPLAY_MAX_WIDTH = 65; + int maxWidth = ERROR_DISPLAY_MAX_WIDTH; int startLine = std::max(1, line - 4); int endLine = std::min(static_cast(sourceLines.size()), line + 2); @@ -120,7 +121,7 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string int errorLineWidth = 8 + column + 1 + static_cast(message.length()); maxWidth = std::max(maxWidth, errorLineWidth); - maxWidth = std::max(maxWidth, 65); + maxWidth = std::max(maxWidth, ERROR_DISPLAY_MAX_WIDTH); std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n"; std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n"; @@ -163,7 +164,8 @@ void ErrorReporter::displaySourceContext(int line, int column, const std::string void ErrorReporter::displayCallStack(const std::vector& callStack) { if (callStack.empty()) return; - int maxWidth = 65; + static const int CALL_STACK_MAX_WIDTH = 65; + int maxWidth = CALL_STACK_MAX_WIDTH; for (const auto& func : callStack) { int funcWidth = static_cast(func.length()) + 6; maxWidth = std::max(maxWidth, funcWidth); diff --git a/source/Interpreter.cpp b/source/Interpreter.cpp index 0f4d583..dd484bb 100644 --- a/source/Interpreter.cpp +++ b/source/Interpreter.cpp @@ -13,46 +13,35 @@ #include "../headers/helperFunctions/HelperFunctions.h" #include "../headers/BobStdLib.h" -// 🎪 The Great Return Context Circus! 🎪 -// Where return values go to party before coming back home +// Return context for managing function return values struct ReturnContext { - Value returnValue; // The star of the show - bool hasReturn; // Did someone say "return"? + Value returnValue; + bool hasReturn; ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {} - // Constructor: "Hello, I'm a return context, and I'm here to make your day better!" }; -// 🎭 Trampoline-based tail call optimization - no exceptions needed -// Because we're too cool for stack overflow exceptions -// We bounce around like kangaroos on a trampoline! 🦘 +// Trampoline-based tail call optimization to prevent stack overflow -// 🎯 Literal Expression Interpreter - The Truth Teller! 🎯 -// "I speak only the truth, and sometimes binary!" Value Interpreter::visitLiteralExpr(const std::shared_ptr& expr) { if (expr->isNull) { - // Ah, the philosophical question: "To be or not to be?" - // Answer: "Not to be" (none) return NONE_VALUE; } if (expr->isNumber) { double num; if (expr->value[1] == 'b') { - // Binary numbers: Because 10 types of people exist - those who understand binary and those who don't! 🤓 num = binaryStringToLong(expr->value); } else { - // Decimal numbers: The boring but reliable ones num = std::stod(expr->value); } return Value(num); } if (expr->isBoolean) { - if (expr->value == "true") return TRUE_VALUE; // The optimist - if (expr->value == "false") return FALSE_VALUE; // The pessimist + if (expr->value == "true") return TRUE_VALUE; + if (expr->value == "false") return FALSE_VALUE; } - // Everything else is just a string, and strings are like people - unique and special! 💫 return Value(expr->value); } @@ -420,13 +409,13 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { if (callee.isFunction()) { Function* function = callee.asFunction(); if (arguments.size() != function->params.size()) { - std::string errorMsg = "Expected " + std::to_string(function->params.size()) + - " arguments but got " + std::to_string(arguments.size()) + "."; if (errorReporter) { errorReporter->reportError(expression->paren.line, expression->paren.column, "Runtime Error", - errorMsg, ""); + "Expected " + std::to_string(function->params.size()) + + " arguments but got " + std::to_string(arguments.size()) + ".", ""); } - throw std::runtime_error(errorMsg); + throw std::runtime_error("Expected " + std::to_string(function->params.size()) + + " arguments but got " + std::to_string(arguments.size()) + "."); } // Check if this is a tail call @@ -662,12 +651,10 @@ void Interpreter::visitBlockStmt(const std::shared_ptr& statement, Ex void Interpreter::visitExpressionStmt(const std::shared_ptr& statement, ExecutionContext* context) { Value value = evaluate(statement->expression); - if(IsInteractive) - std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m" << std::endl; + if(isInteractive) + std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m\n"; } - - void Interpreter::visitVarStmt(const std::shared_ptr& statement, ExecutionContext* context) { Value value = NONE_VALUE; @@ -676,7 +663,7 @@ void Interpreter::visitVarStmt(const std::shared_ptr& statement, Execut value = evaluate(statement->initializer); } - //std::cout << "Visit var stmt: " << statement->name.lexeme << " set to: " << stringify(value) << std::endl; + environment->define(statement->name.lexeme, value); } @@ -799,9 +786,7 @@ void Interpreter::visitDoWhileStmt(const std::shared_ptr& statement void Interpreter::visitForStmt(const std::shared_ptr& statement, ExecutionContext* context) { - // For loops are desugared into while loops in the parser - // This method should never be called, but we implement it for completeness - // The actual execution happens through the desugared while loop + // For loop implementation - executes initializer, condition, body, and increment if (statement->initializer != nullptr) { execute(statement->initializer, context); } diff --git a/source/Parser.cpp b/source/Parser.cpp index 2f17d26..fe02bca 100644 --- a/source/Parser.cpp +++ b/source/Parser.cpp @@ -1,12 +1,10 @@ -// -// #include "../headers/Parser.h" #include -// Precedence -// to all the morons on facebook who don't know what pemdas is, fuck you +// Operator Precedence Rules +// Following standard mathematical order of operations /////////////////////////////////////////// sptr(Expr) Parser::expression() @@ -369,8 +367,6 @@ sptr(Expr) Parser::call() return expr; } -/////////////////////////////////////////// - std::vector Parser::parse() { @@ -442,12 +438,13 @@ std::shared_ptr Parser::functionExpression() { std::vector parameters; if (!check(CLOSE_PAREN)) { do { - if (parameters.size() >= 255) { + static const size_t MAX_FUNCTION_PARAMETERS = 255; + if (parameters.size() >= MAX_FUNCTION_PARAMETERS) { if (errorReporter) { errorReporter->reportError(peek().line, 0, "Parse Error", - "Cannot have more than 255 parameters", ""); - } - throw std::runtime_error("Cannot have more than 255 parameters."); + "Cannot have more than " + std::to_string(MAX_FUNCTION_PARAMETERS) + " parameters", ""); + } + throw std::runtime_error("Cannot have more than " + std::to_string(MAX_FUNCTION_PARAMETERS) + " parameters."); } parameters.push_back(consume(IDENTIFIER, "Expect parameter name.")); } while (match({COMMA})); diff --git a/source/bob.cpp b/source/bob.cpp index 8e20bc8..af5086e 100644 --- a/source/bob.cpp +++ b/source/bob.cpp @@ -15,7 +15,7 @@ void Bob::runFile(const std::string& path) } else { - std::cout << "File not found" << std::endl; + std::cout << "File not found\n"; return; } @@ -32,8 +32,8 @@ void Bob::runPrompt() { this->interpreter = msptr(Interpreter)(true); - std::cout << "Bob v" << VERSION << ", 2025" << std::endl; - for(;;) + std::cout << "Bob v" << VERSION << ", 2025\n"; + while(true) { std::string line; std::cout << "\033[0;36m" << "-> " << "\033[0;37m"; @@ -81,13 +81,13 @@ void Bob::run(std::string source) // For errors that weren't reported (like parser errors, undefined variables, etc.) // print them normally - std::cout << "Error: " << e.what() << std::endl; + std::cout << "Error: " << e.what() << '\n'; return; } - catch(...) + catch(const std::exception& e) { // Unknown error - report it since it wasn't handled by the interpreter - errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred"); + errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred: " + std::string(e.what())); return; } }