diff --git a/headers/Environment.h b/headers/Environment.h index 3fa33d6..5f705b7 100644 --- a/headers/Environment.h +++ b/headers/Environment.h @@ -6,39 +6,32 @@ #include "Value.h" #include "Lexer.h" +// Forward declaration +class ErrorReporter; + class Environment { public: - Environment() : parent(nullptr) {} - Environment(std::shared_ptr parent_env) : parent(parent_env) {} + Environment() : parent(nullptr), errorReporter(nullptr) {} + Environment(std::shared_ptr parent_env) : parent(parent_env), errorReporter(nullptr) {} + + // Set error reporter for enhanced error reporting + void setErrorReporter(ErrorReporter* reporter) { + errorReporter = reporter; + } // Optimized define with inline inline void define(const std::string& name, const Value& value) { variables[name] = value; } - // Optimized assign with inline - inline void assign(const Token& name, const Value& value) { - auto it = variables.find(name.lexeme); - if (it != variables.end()) { - it->second = value; - } else if (parent != nullptr) { - parent->assign(name, value); - } else { - throw std::runtime_error("Undefined variable '" + name.lexeme + "'."); - } - } + // Enhanced assign with error reporting + void assign(const Token& name, const Value& value); - // Optimized get with inline and move semantics - inline Value get(const Token& name) { - auto it = variables.find(name.lexeme); - if (it != variables.end()) { - return it->second; // Return by value (will use move if possible) - } - if (parent != nullptr) { - return parent->get(name); - } - throw std::runtime_error("Undefined variable '" + name.lexeme + "'."); - } + // Enhanced get with error reporting + Value get(const Token& name); + + // Get by string name with error reporting + Value get(const std::string& name); std::shared_ptr getParent() const { return parent; } inline void clear() { variables.clear(); } @@ -46,4 +39,5 @@ public: private: std::unordered_map variables; std::shared_ptr parent; + ErrorReporter* errorReporter; }; \ No newline at end of file diff --git a/headers/ErrorReporter.h b/headers/ErrorReporter.h new file mode 100644 index 0000000..76b5a16 --- /dev/null +++ b/headers/ErrorReporter.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace Colors { + const std::string RED = "\033[31m"; + const std::string GREEN = "\033[32m"; + const std::string YELLOW = "\033[33m"; + const std::string BLUE = "\033[34m"; + const std::string MAGENTA = "\033[35m"; + const std::string CYAN = "\033[36m"; + const std::string WHITE = "\033[37m"; + const std::string BOLD = "\033[1m"; + const std::string RESET = "\033[0m"; +} + +struct ErrorContext { + std::string errorType; + std::string message; + std::string fileName; + int line; + int column; + std::vector callStack; +}; + +class ErrorReporter { +private: + std::vector sourceLines; + std::string currentFileName; + std::vector callStack; + bool hadError = false; + +public: + ErrorReporter() = default; + ~ErrorReporter() = default; + + // Load source code for context + void loadSource(const std::string& source, const std::string& fileName); + + // Report errors with line and column information + void reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true); + + // Check if an error has been reported + bool hasReportedError() const { return hadError; } + + // Report errors with full context + void reportErrorWithContext(const ErrorContext& context); + + // Call stack management + void pushCallStack(const std::string& functionName); + void popCallStack(); + +private: + void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true); + void displayCallStack(const std::vector& callStack); + std::string getLineWithArrow(int line, int column); + std::string colorize(const std::string& text, const std::string& color); +}; \ No newline at end of file diff --git a/headers/Expression.h b/headers/Expression.h index ddb798b..395d5ea 100644 --- a/headers/Expression.h +++ b/headers/Expression.h @@ -10,6 +10,10 @@ #include "TypeWrapper.h" #include "Value.h" +// Forward declarations +struct FunctionExpr; +struct ExprVisitor; + struct AssignExpr; struct BinaryExpr; struct GroupingExpr; @@ -23,14 +27,14 @@ struct ExprVisitor { virtual Value visitAssignExpr(const std::shared_ptr& expr) = 0; virtual Value visitBinaryExpr(const std::shared_ptr& expr) = 0; + virtual Value visitCallExpr(const std::shared_ptr& expr) = 0; + virtual Value visitFunctionExpr(const std::shared_ptr& expr) = 0; virtual Value visitGroupingExpr(const std::shared_ptr& expr) = 0; virtual Value visitLiteralExpr(const std::shared_ptr& expr) = 0; virtual Value visitUnaryExpr(const std::shared_ptr& expr) = 0; - virtual Value visitVariableExpr(const std::shared_ptr& expr) = 0; - virtual Value visitCallExpr(const std::shared_ptr& expr) = 0; + virtual Value visitVarExpr(const std::shared_ptr& expr) = 0; }; - struct Expr : public std::enable_shared_from_this { virtual Value accept(ExprVisitor* visitor) = 0; virtual ~Expr() = default; @@ -39,11 +43,10 @@ struct Expr : public std::enable_shared_from_this { struct AssignExpr : Expr { const Token name; + const Token op; std::shared_ptr value; - AssignExpr(Token name, std::shared_ptr value) : name(name), value(value) - { - } - + AssignExpr(Token name, Token op, std::shared_ptr value) + : name(name), op(op), value(value) {} Value accept(ExprVisitor* visitor) override { return visitor->visitAssignExpr(std::static_pointer_cast(shared_from_this())); @@ -56,10 +59,8 @@ struct BinaryExpr : Expr const Token oper; std::shared_ptr right; - BinaryExpr(std::shared_ptr left, Token oper, std::shared_ptr right) - : left(left), oper(oper), right(right) - { - } + BinaryExpr(std::shared_ptr left, Token oper, std::shared_ptr right) + : left(left), oper(oper), right(right) {} Value accept(ExprVisitor* visitor) override{ return visitor->visitBinaryExpr(std::static_pointer_cast(shared_from_this())); } @@ -69,9 +70,7 @@ struct GroupingExpr : Expr { std::shared_ptr expression; - explicit GroupingExpr(std::shared_ptr expression) : expression(expression) - { - } + explicit GroupingExpr(std::shared_ptr expression) : expression(expression) {} Value accept(ExprVisitor* visitor) override{ return visitor->visitGroupingExpr(std::static_pointer_cast(shared_from_this())); } @@ -93,12 +92,9 @@ struct LiteralExpr : Expr struct UnaryExpr : Expr { - const Token oper; + Token oper; std::shared_ptr right; - - UnaryExpr(Token oper, std::shared_ptr right) : oper(oper), right(right) - { - } + UnaryExpr(Token oper, std::shared_ptr right) : oper(oper), right(right) {} Value accept(ExprVisitor* visitor) override{ return visitor->visitUnaryExpr(std::static_pointer_cast(shared_from_this())); } @@ -106,30 +102,35 @@ struct UnaryExpr : Expr struct VarExpr : Expr { - const Token name; + Token name; explicit VarExpr(Token name) : name(name){}; Value accept(ExprVisitor* visitor) override { - return visitor->visitVariableExpr(std::static_pointer_cast(shared_from_this())); + return visitor->visitVarExpr(std::static_pointer_cast(shared_from_this())); + } +}; + +struct FunctionExpr : Expr { + std::vector params; + std::vector> body; + FunctionExpr(const std::vector& params, const std::vector>& body) + : params(params), body(body) {} + Value accept(ExprVisitor* visitor) override + { + return visitor->visitFunctionExpr(std::static_pointer_cast(shared_from_this())); } }; struct CallExpr : Expr { std::shared_ptr callee; - const Token paren; + Token paren; std::vector> arguments; - CallExpr(std::shared_ptr callee, Token paren, std::vector> arguments) - : callee(callee), paren(paren), arguments(arguments) - { - } - + : callee(callee), paren(paren), arguments(arguments) {} Value accept(ExprVisitor* visitor) override { return visitor->visitCallExpr(std::static_pointer_cast(shared_from_this())); } }; -//// - diff --git a/headers/Interpreter.h b/headers/Interpreter.h index 0294d77..a5a51e1 100644 --- a/headers/Interpreter.h +++ b/headers/Interpreter.h @@ -6,33 +6,24 @@ #include "Environment.h" #include "Value.h" #include "StdLib.h" +#include "ErrorReporter.h" #include #include #include #include -class Return : public std::exception { -public: - Value value; - - Return(Value value) : value(value) {} - - const char* what() const noexcept override { - return "Return"; - } -}; - class Interpreter : public ExprVisitor, public StmtVisitor { public: Value visitBinaryExpr(const std::shared_ptr& expression) override; + Value visitCallExpr(const std::shared_ptr& expression) override; + Value visitFunctionExpr(const std::shared_ptr& expression) override; Value visitGroupingExpr(const std::shared_ptr& expression) override; Value visitLiteralExpr(const std::shared_ptr& expression) override; Value visitUnaryExpr(const std::shared_ptr& expression) override; - Value visitVariableExpr(const std::shared_ptr& expression) override; + Value visitVarExpr(const std::shared_ptr& expression) override; Value visitAssignExpr(const std::shared_ptr& expression) override; - Value visitCallExpr(const std::shared_ptr& expression) override; void visitBlockStmt(const std::shared_ptr& statement) override; void visitExpressionStmt(const std::shared_ptr& statement) override; @@ -41,48 +32,40 @@ public: void visitReturnStmt(const std::shared_ptr& statement) override; void visitIfStmt(const std::shared_ptr& statement) override; - void interpret(std::vector> statements); + void interpret(std::vector > statements); - explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive){ + explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){ environment = std::make_shared(); - - // Pre-allocate environment pool - for (size_t i = 0; i < POOL_SIZE; ++i) { - envPool.push(std::make_shared()); - } - - // Add standard library functions - addStdLibFunctions(); } virtual ~Interpreter() = default; private: std::shared_ptr environment; bool IsInteractive; - std::vector> builtinFunctions; - std::vector> functions; + std::vector > builtinFunctions; + std::vector > functions; + ErrorReporter* errorReporter; - // Environment pool for fast function calls - std::stack> envPool; - static const size_t POOL_SIZE = 1000; // Pre-allocate 1000 environments - - // Return value mechanism (replaces exceptions) - struct ReturnContext { - Value returnValue; - bool hasReturn; - ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {} - }; - std::stack returnStack; - Value evaluate(const std::shared_ptr& expr); bool isEqual(Value a, Value b); bool isWholeNumer(double num); void execute(const std::shared_ptr& statement); - void executeBlock(std::vector> statements, std::shared_ptr env); + void executeBlock(std::vector > statements, std::shared_ptr env); void addStdLibFunctions(); public: bool isTruthy(Value object); std::string stringify(Value object); void addBuiltinFunction(std::shared_ptr func); + + // Error reporting + void setErrorReporter(ErrorReporter* reporter) { + errorReporter = reporter; + if (environment) { + environment->setErrorReporter(reporter); + } + + // Add standard library functions after error reporter is set + addStdLibFunctions(); + } }; diff --git a/headers/Lexer.h b/headers/Lexer.h index 9c63749..a2ce48f 100644 --- a/headers/Lexer.h +++ b/headers/Lexer.h @@ -20,13 +20,19 @@ enum TokenType{ AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR, WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN, + // Compound assignment operators + PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, + + // Compound bitwise assignment operators + BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL, + END_OF_FILE }; inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE", "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT", - "BINARY_OP", + "BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT", "BANG", "BANG_EQUAL", "EQUAL", "DOUBLE_EQUAL", @@ -38,6 +44,12 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR", "WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", + // Compound assignment operators + "PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL", + + // Compound bitwise assignment operators + "BIN_AND_EQUAL", "BIN_OR_EQUAL", "BIN_XOR_EQUAL", "BIN_SLEFT_EQUAL", "BIN_SRIGHT_EQUAL", + "END_OF_FILE"}; const std::map KEYWORDS { @@ -63,15 +75,30 @@ struct Token TokenType type; std::string lexeme; int line; + int column; }; +// Forward declaration +class ErrorReporter; + class Lexer{ public: + Lexer() : errorReporter(nullptr) {} + std::vector Tokenize(std::string source); + + // Set error reporter for enhanced error reporting + void setErrorReporter(ErrorReporter* reporter) { + errorReporter = reporter; + } + private: int line; + int column; std::vector src; + ErrorReporter* errorReporter; + private: bool matchOn(char expected); diff --git a/headers/Parser.h b/headers/Parser.h index 9adc63a..03c22d4 100644 --- a/headers/Parser.h +++ b/headers/Parser.h @@ -6,19 +6,29 @@ #include "Statement.h" #include "TypeWrapper.h" #include "helperFunctions/ShortHands.h" +#include "ErrorReporter.h" class Parser { private: const std::vector tokens; int current = 0; + int functionDepth = 0; // Track nesting level of functions + ErrorReporter* errorReporter = nullptr; public: explicit Parser(std::vector tokens) : tokens(std::move(tokens)){}; std::vector parse(); + void setErrorReporter(ErrorReporter* reporter) { errorReporter = reporter; } private: sptr(Expr) expression(); + sptr(Expr) logical_or(); + sptr(Expr) logical_and(); + sptr(Expr) bitwise_or(); + sptr(Expr) bitwise_xor(); + sptr(Expr) bitwise_and(); + sptr(Expr) shift(); sptr(Expr) equality(); sptr(Expr) comparison(); sptr(Expr) term(); @@ -51,10 +61,16 @@ private: std::shared_ptr varDeclaration(); std::shared_ptr functionDeclaration(); + std::shared_ptr functionExpression(); sptr(Expr) assignment(); std::vector> block(); sptr(Expr) finishCall(sptr(Expr) callee); + + // Helper methods for function scope tracking + void enterFunction() { functionDepth++; } + void exitFunction() { functionDepth--; } + bool isInFunction() const { return functionDepth > 0; } }; \ No newline at end of file diff --git a/headers/StdLib.h b/headers/StdLib.h index 67a2c17..361204e 100644 --- a/headers/StdLib.h +++ b/headers/StdLib.h @@ -5,8 +5,9 @@ #include class Interpreter; +class ErrorReporter; class StdLib { public: - static void addToEnvironment(std::shared_ptr env, Interpreter& interpreter); + static void addToEnvironment(std::shared_ptr env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr); }; \ No newline at end of file diff --git a/headers/TypeWrapper.h b/headers/TypeWrapper.h index 84ff817..bb3c136 100644 --- a/headers/TypeWrapper.h +++ b/headers/TypeWrapper.h @@ -57,9 +57,9 @@ struct Function : public Object struct BuiltinFunction : public Object { const std::string name; - const std::function)> func; + const std::function, int, int)> func; - BuiltinFunction(std::string name, std::function)> func) + BuiltinFunction(std::string name, std::function, int, int)> func) : name(name), func(func) {} }; diff --git a/headers/Value.h b/headers/Value.h index 3ee661f..8f64c7e 100644 --- a/headers/Value.h +++ b/headers/Value.h @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include // Forward declarations class Environment; @@ -151,6 +154,103 @@ struct Value { default: return "unknown"; } } + + // Arithmetic operators + Value operator+(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(number + other.number); + } + if (isString() && other.isString()) { + return Value(string_value + other.string_value); + } + if (isString() && other.isNumber()) { + return Value(string_value + other.toString()); + } + if (isNumber() && other.isString()) { + return Value(toString() + other.string_value); + } + throw std::runtime_error("Invalid operands for + operator"); + } + + Value operator-(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(number - other.number); + } + throw std::runtime_error("Invalid operands for - operator"); + } + + Value operator*(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(number * other.number); + } + if (isString() && other.isNumber()) { + std::string result; + for (int i = 0; i < static_cast(other.number); ++i) { + result += string_value; + } + return Value(result); + } + if (isNumber() && other.isString()) { + std::string result; + for (int i = 0; i < static_cast(number); ++i) { + result += other.string_value; + } + return Value(result); + } + throw std::runtime_error("Invalid operands for * operator"); + } + + Value operator/(const Value& other) const { + if (isNumber() && other.isNumber()) { + if (other.number == 0) { + throw std::runtime_error("Division by zero"); + } + return Value(number / other.number); + } + throw std::runtime_error("Invalid operands for / operator"); + } + + Value operator%(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(fmod(number, other.number)); + } + throw std::runtime_error("Invalid operands for % operator"); + } + + Value operator&(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(static_cast(static_cast(number) & static_cast(other.number))); + } + throw std::runtime_error("Invalid operands for & operator"); + } + + Value operator|(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(static_cast(static_cast(number) | static_cast(other.number))); + } + throw std::runtime_error("Invalid operands for | operator"); + } + + Value operator^(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(static_cast(static_cast(number) ^ static_cast(other.number))); + } + throw std::runtime_error("Invalid operands for ^ operator"); + } + + Value operator<<(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(static_cast(static_cast(number) << static_cast(other.number))); + } + throw std::runtime_error("Invalid operands for << operator"); + } + + Value operator>>(const Value& other) const { + if (isNumber() && other.isNumber()) { + return Value(static_cast(static_cast(number) >> static_cast(other.number))); + } + throw std::runtime_error("Invalid operands for >> operator"); + } }; // Global constants for common values diff --git a/headers/bob.h b/headers/bob.h index 40ab43e..eaa113d 100644 --- a/headers/bob.h +++ b/headers/bob.h @@ -6,6 +6,7 @@ #include "../headers/Lexer.h" #include "../headers/Interpreter.h" #include "../headers/helperFunctions/ShortHands.h" +#include "../headers/ErrorReporter.h" #define VERSION "0.0.1" @@ -14,23 +15,15 @@ class Bob public: Lexer lexer; sptr(Interpreter) interpreter; + ErrorReporter errorReporter; ~Bob() = default; public: void runFile(const std::string& path); - void runPrompt(); - void error(int line, const std::string& message); - - -private: - bool hadError = false; - private: void run(std::string source); - - void report(int line, std::string where, std::string message); }; diff --git a/source/Environment.cpp b/source/Environment.cpp new file mode 100644 index 0000000..dcc8206 --- /dev/null +++ b/source/Environment.cpp @@ -0,0 +1,51 @@ +#include "../headers/Environment.h" +#include "../headers/ErrorReporter.h" + +void Environment::assign(const Token& name, const Value& value) { + auto it = variables.find(name.lexeme); + if (it != variables.end()) { + it->second = value; + return; + } + + if (parent != nullptr) { + parent->assign(name, value); + return; + } + + if (errorReporter) { + errorReporter->reportError(name.line, name.column, "Runtime Error", + "Undefined variable '" + name.lexeme + "'", ""); + } + throw std::runtime_error("Undefined variable '" + name.lexeme + "'"); +} + +Value Environment::get(const Token& name) { + auto it = variables.find(name.lexeme); + if (it != variables.end()) { + return it->second; + } + + if (parent != nullptr) { + return parent->get(name); + } + + if (errorReporter) { + errorReporter->reportError(name.line, name.column, "Runtime Error", + "Undefined variable '" + name.lexeme + "'", ""); + } + 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 + "'"); +} \ No newline at end of file diff --git a/source/ErrorReporter.cpp b/source/ErrorReporter.cpp new file mode 100644 index 0000000..f6f4d30 --- /dev/null +++ b/source/ErrorReporter.cpp @@ -0,0 +1,207 @@ +#include "../headers/ErrorReporter.h" +#include +#include +#include + +// Helper function to find operator in source line +int findOperatorInLine(const std::string& sourceLine, const std::string& operator_) { + size_t pos = 0; + while ((pos = sourceLine.find(operator_, pos)) != std::string::npos) { + // Check if this operator is not inside a string + bool inString = false; + for (size_t i = 0; i < pos; i++) { + if (sourceLine[i] == '"' && (i == 0 || sourceLine[i-1] != '\\')) { + inString = !inString; + } + } + + if (!inString) { + // Check if this is a standalone operator, not part of a larger operator + bool isStandalone = true; + + // Check character before the operator + if (pos > 0) { + char before = sourceLine[pos - 1]; + if (before == '&' || before == '|' || before == '=' || before == '<' || before == '>') { + isStandalone = false; + } + } + + // Check character after the operator + if (pos + operator_.length() < sourceLine.length()) { + char after = sourceLine[pos + operator_.length()]; + if (after == '&' || after == '|' || after == '=' || after == '<' || after == '>') { + isStandalone = false; + } + } + + if (isStandalone) { + return static_cast(pos + 1); // Convert to 1-based column + } + } + pos += 1; // Move to next position to continue searching + } + + return 1; // Default to column 1 if not found +} + +void ErrorReporter::loadSource(const std::string& source, const std::string& fileName) { + currentFileName = fileName; + sourceLines.clear(); + + std::istringstream iss(source); + std::string line; + while (std::getline(iss, line)) { + sourceLines.push_back(line); + } +} + +void ErrorReporter::reportError(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) { + hadError = true; + displaySourceContext(line, column, errorType, message, operator_, showArrow); +} + +void ErrorReporter::reportErrorWithContext(const ErrorContext& context) { + hadError = true; + + std::cout << "\n"; + std::cout << colorize("╔══════════════════════════════════════════════════════════════╗", Colors::RED) << "\n"; + std::cout << colorize("║ ERROR REPORT ║", Colors::RED) << "\n"; + std::cout << colorize("╚══════════════════════════════════════════════════════════════╝", Colors::RED) << "\n\n"; + + std::cout << colorize("Error Type: ", Colors::BOLD) << colorize(context.errorType, Colors::RED) << "\n"; + std::cout << colorize("Message: ", Colors::BOLD) << colorize(context.message, Colors::WHITE) << "\n"; + + if (!context.fileName.empty()) { + std::cout << colorize("File: ", Colors::BOLD) << colorize(context.fileName, Colors::CYAN) << "\n"; + } + + std::cout << colorize("Location: ", Colors::BOLD) << colorize("Line " + std::to_string(context.line) + + ", Column " + std::to_string(context.column), Colors::YELLOW) << "\n\n"; + + displaySourceContext(context.line, context.column, context.errorType, context.message); + + if (!context.callStack.empty()) { + displayCallStack(context.callStack); + } + + std::cout << "\n"; +} + +void ErrorReporter::pushCallStack(const std::string& functionName) { + // This would be called when entering a function + // Implementation depends on integration with the interpreter +} + +void ErrorReporter::popCallStack() { + // This would be called when exiting a function + // Implementation depends on integration with the interpreter +} + +void ErrorReporter::displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_, bool showArrow) { + if (sourceLines.empty()) { + std::cout << colorize("Error: ", Colors::RED) << colorize(errorType, Colors::BOLD) << "\n"; + std::cout << colorize("Message: ", Colors::BOLD) << message << "\n"; + std::cout << colorize("Location: ", Colors::BOLD) << "Line " << line << ", Column " << column << "\n"; + return; + } + + int maxWidth = 65; + int startLine = std::max(1, line - 4); + int endLine = std::min(static_cast(sourceLines.size()), line + 2); + + for (int i = startLine; i <= endLine; i++) { + if (i > 0 && i <= static_cast(sourceLines.size())) { + int lineWidth = static_cast(sourceLines[i-1].length()) + 8; + maxWidth = std::max(maxWidth, lineWidth); + } + } + + int errorLineWidth = 8 + column + 1 + static_cast(message.length()); + maxWidth = std::max(maxWidth, errorLineWidth); + maxWidth = std::max(maxWidth, 65); + + std::cout << colorize("Source Code Context:", Colors::BOLD) << "\n"; + std::cout << colorize("┌" + std::string(maxWidth, '-') + "┐", Colors::BLUE) << "\n"; + + for (int i = startLine; i <= endLine; i++) { + std::string lineNum = std::to_string(i); + std::string linePrefix = " " + std::string(4 - lineNum.length(), ' ') + lineNum + " | "; + + if (i > 0 && i <= static_cast(sourceLines.size())) { + if (i == line) { + std::string sourceLine = sourceLines[i-1]; + std::string fullLine = colorize(linePrefix, Colors::RED) + colorize(sourceLine, Colors::YELLOW); + + std::cout << fullLine << "\n"; + + // Draw arrow only if showArrow is true + if (showArrow) { + std::string arrowLine = colorize(" | ", Colors::RED); + int safeColumn = std::max(1, std::min(column, static_cast(sourceLine.length() + 1))); + arrowLine += std::string(safeColumn - 1, ' ') + colorize("^", Colors::RED) + colorize(" " + message, Colors::RED); + + std::cout << arrowLine << "\n"; + } + } else { + std::string sourceLine = sourceLines[i - 1]; + std::string fullLine = colorize(linePrefix, Colors::BLUE) + sourceLine; + std::cout << fullLine << "\n"; + } + } else { + std::string fullLine = linePrefix; + std::cout << colorize(fullLine, Colors::BLUE) << "\n"; + } + } + + std::cout << colorize("└" + std::string(maxWidth, '-') + "┘", Colors::BLUE) << "\n"; + std::cout << colorize("Error: ", Colors::RED) << colorize(errorType, Colors::BOLD) << "\n"; + std::cout << colorize("Message: ", Colors::BOLD) << message << "\n\n"; +} + +void ErrorReporter::displayCallStack(const std::vector& callStack) { + if (callStack.empty()) return; + + int maxWidth = 65; + for (const auto& func : callStack) { + int funcWidth = static_cast(func.length()) + 6; + maxWidth = std::max(maxWidth, funcWidth); + } + + std::cout << colorize("Call Stack:", Colors::BOLD) << "\n"; + std::cout << colorize("┌" + std::string(maxWidth - 2, '-') + "┐", Colors::MAGENTA) << "\n"; + + for (size_t i = 0; i < callStack.size(); i++) { + std::string prefix = "│ " + std::to_string(i + 1) + ". "; + std::string line = prefix + callStack[i]; + + int currentWidth = static_cast(line.length()); + if (currentWidth < maxWidth - 1) { + line += std::string(maxWidth - 1 - currentWidth, ' ') + "│"; + } else { + line += " │"; + } + + std::cout << colorize(line, Colors::MAGENTA) << "\n"; + } + + std::cout << colorize("└" + std::string(maxWidth - 2, '-') + "┘", Colors::MAGENTA) << "\n\n"; +} + +std::string ErrorReporter::getLineWithArrow(int line, int column) { + if (line < 1 || line > static_cast(sourceLines.size())) { + return ""; + } + + std::string sourceLine = sourceLines[line - 1]; + std::string arrow = std::string(column - 1, ' ') + "^"; + return sourceLine + "\n" + arrow; +} + +std::string ErrorReporter::colorize(const std::string& text, const std::string& color) { + try { + return color + text + Colors::RESET; + } catch (...) { + return text; + } +} diff --git a/source/Interpreter.cpp b/source/Interpreter.cpp index 548155d..29d8768 100644 --- a/source/Interpreter.cpp +++ b/source/Interpreter.cpp @@ -10,6 +10,13 @@ #include "../headers/Interpreter.h" #include "../headers/helperFunctions/HelperFunctions.h" #include +#include "../headers/Interpreter.h" +#include "../headers/StdLib.h" +#include +#include +#include +#include +#include struct ReturnContext { Value returnValue; @@ -19,6 +26,8 @@ struct ReturnContext { static ReturnContext g_returnContext; + + Value Interpreter::visitLiteralExpr(const std::shared_ptr& expr) { if(expr->isNull) return NONE_VALUE; if(expr->isNumber){ @@ -86,211 +95,366 @@ Value Interpreter::visitUnaryExpr(const std::shared_ptr& expression) } -Value Interpreter::visitBinaryExpr(const std::shared_ptr& expression) -{ +Value Interpreter::visitBinaryExpr(const std::shared_ptr& expression) { Value left = evaluate(expression->left); Value right = evaluate(expression->right); - switch (expression->oper.type) { - case BANG_EQUAL: - return Value(!isEqual(left, right)); - case DOUBLE_EQUAL: - return Value(isEqual(left, right)); - default: - ; - } + if (left.isNumber() && right.isNumber()) { + double leftNum = left.asNumber(); + double rightNum = right.asNumber(); - if(left.isNumber() && right.isNumber()) - { - double left_double = left.asNumber(); - double right_double = right.asNumber(); switch (expression->oper.type) { - case GREATER: - return Value(left_double > right_double); - case GREATER_EQUAL: - return Value(left_double >= right_double); - case LESS: - return Value(left_double < right_double); - case LESS_EQUAL: - return Value(left_double <= right_double); - case MINUS: - return Value(left_double - right_double); - case PLUS: - return Value(left_double + right_double); - case SLASH: - if(right_double == 0) throw std::runtime_error("DivisionByZeroError: Cannot divide by 0"); - return Value(left_double / right_double); - case STAR: - return Value(left_double * right_double); - case PERCENT: - return Value(fmod(left_double, right_double)); - default: - return NONE_VALUE; //unreachable - } - } - else if(left.isString() && right.isString()) - { - switch (expression->oper.type) { - case PLUS: { - std::string left_string = left.asString(); - std::string right_string = right.asString(); - return Value(left_string + right_string); + case PLUS: return Value(leftNum + rightNum); + case MINUS: return Value(leftNum - rightNum); + case SLASH: { + if (rightNum == 0) { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero", + "Cannot divide by zero", expression->oper.lexeme); + } + throw std::runtime_error("Division by zero"); + } + return Value(leftNum / rightNum); + } + case STAR: return Value(leftNum * rightNum); + case PERCENT: { + if (rightNum == 0) { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero", + "Cannot perform modulo operation with zero", expression->oper.lexeme); + } + throw std::runtime_error("Modulo by zero"); + } + return Value(std::fmod(leftNum, rightNum)); + } + 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); + case DOUBLE_EQUAL: return Value(leftNum == rightNum); + case BANG_EQUAL: return Value(leftNum != rightNum); + case BIN_AND: return Value(static_cast(static_cast(leftNum) & static_cast(rightNum))); + case BIN_OR: return Value(static_cast(static_cast(leftNum) | static_cast(rightNum))); + case BIN_XOR: return Value(static_cast(static_cast(leftNum) ^ static_cast(rightNum))); + case BIN_SLEFT: return Value(static_cast(static_cast(leftNum) << static_cast(rightNum))); + case BIN_SRIGHT: return Value(static_cast(static_cast(leftNum) >> static_cast(rightNum))); + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } } } - throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings"); - } - else if(left.isString() && right.isNumber()) - { + + if (left.isString() && right.isString()) { std::string left_string = left.asString(); - double right_number = right.asNumber(); - - switch (expression->oper.type) { - case PLUS: { - // Use the same logic as stringify for consistent formatting - double integral = right_number; - double fractional = std::modf(right_number, &integral); - - std::stringstream ss; - if(std::abs(fractional) < std::numeric_limits::epsilon()) - { - ss << std::fixed << std::setprecision(0) << integral; - } - else - { - ss << std::fixed << std::setprecision(std::numeric_limits::digits10 - 1) << right_number; - std::string str = ss.str(); - str.erase(str.find_last_not_of('0') + 1, std::string::npos); - if (str.back() == '.') { - str.pop_back(); - } - return Value(left_string + str); - } - return Value(left_string + ss.str()); - } - case STAR: - if(isWholeNumer(right_number)) - { - std::string s; - for (int i = 0; i < (int)right_number; ++i) { - s += left_string; - } - return Value(s); - } - else - { - throw std::runtime_error("String multiplier must be whole number"); - } - } - throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a number"); - } - else if(left.isNumber() && right.isString()) - { - double left_number = left.asNumber(); std::string right_string = right.asString(); switch (expression->oper.type) { - case PLUS: { - // Use the same logic as stringify for consistent formatting - double integral = left_number; - double fractional = std::modf(left_number, &integral); - - std::stringstream ss; - if(std::abs(fractional) < std::numeric_limits::epsilon()) - { - ss << std::fixed << std::setprecision(0) << integral; + case PLUS: return Value(left_string + right_string); + case DOUBLE_EQUAL: return Value(left_string == right_string); + case BANG_EQUAL: return Value(left_string != right_string); + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value } - else - { - ss << std::fixed << std::setprecision(std::numeric_limits::digits10 - 1) << left_number; - std::string str = ss.str(); - str.erase(str.find_last_not_of('0') + 1, std::string::npos); - if (str.back() == '.') { - str.pop_back(); - } - return Value(str + right_string); - } - return Value(ss.str() + right_string); } - case STAR: - if(isWholeNumer(left_number)) - { - std::string s; - for (int i = 0; i < (int)left_number; ++i) { - s += right_string; - } - return Value(s); + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value } - else - { + } + default: + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error", + "Cannot use '" + expression->oper.lexeme + "' on two strings", expression->oper.lexeme); + } + throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on two strings"); + } + } + + if (left.isString() && right.isNumber()) { + std::string left_string = left.asString(); + double right_num = right.asNumber(); + + switch (expression->oper.type) { + case PLUS: return Value(left_string + stringify(right)); + case STAR: { + if (!isWholeNumer(right_num)) { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication", + "String multiplier must be a whole number", expression->oper.lexeme); + } throw std::runtime_error("String multiplier must be whole number"); } + std::string result; + for (int i = 0; i < static_cast(right_num); i++) { + result += left_string; + } + return Value(result); + } } - throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a number and a string"); } - else if(left.isBoolean() && right.isString()) - { + + if (left.isNumber() && right.isString()) { + double left_num = left.asNumber(); + std::string right_string = right.asString(); + + switch (expression->oper.type) { + case PLUS: return Value(stringify(left) + right_string); + case STAR: { + if (!isWholeNumer(left_num)) { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication", + "String multiplier must be a whole number", expression->oper.lexeme); + } + throw std::runtime_error("String multiplier must be whole number"); + } + std::string result; + for (int i = 0; i < static_cast(left_num); i++) { + result += right_string; + } + return Value(result); + } + } + } + + if (left.isBoolean() && right.isBoolean()) { + bool left_bool = left.asBoolean(); + bool right_bool = right.asBoolean(); + + switch (expression->oper.type) { + case AND: return Value(left_bool && right_bool); + case OR: return Value(left_bool || right_bool); + case DOUBLE_EQUAL: return Value(left_bool == right_bool); + case BANG_EQUAL: return Value(left_bool != right_bool); + } + } + + + + if (left.isBoolean() && right.isString()) { bool left_bool = left.asBoolean(); std::string right_string = right.asString(); switch (expression->oper.type) { - case PLUS: { - std::string bool_str = left_bool ? "true" : "false"; - return Value(bool_str + right_string); - } + case PLUS: return Value(stringify(left) + right_string); } - throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a boolean and a string"); } - else if(left.isString() && right.isBoolean()) - { + + if (left.isString() && right.isBoolean()) { std::string left_string = left.asString(); bool right_bool = right.asBoolean(); switch (expression->oper.type) { - case PLUS: { - std::string bool_str = right_bool ? "true" : "false"; - return Value(left_string + bool_str); - } + case PLUS: return Value(left_string + stringify(right)); } - throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a boolean"); } - else if(left.isString() && right.isNone()) - { - std::string left_string = left.asString(); + + if (left.isNumber() && right.isBoolean()) { + double left_num = left.asNumber(); + bool right_bool = right.asBoolean(); switch (expression->oper.type) { - case PLUS: { - return Value(left_string + "none"); + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } } } - throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none"); } - else if(left.isNone() && right.isString()) - { + + if (left.isBoolean() && right.isNumber()) { + bool left_bool = left.asBoolean(); + double right_num = right.asNumber(); + + switch (expression->oper.type) { + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } + } + } + } + + // Mixed-type logical operations (string && boolean, etc.) + if (left.isString() && right.isBoolean()) { + bool right_bool = right.asBoolean(); + + switch (expression->oper.type) { + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } + } + case PLUS: return Value(left.asString() + stringify(right)); + } + } + + if (left.isBoolean() && right.isString()) { + bool left_bool = left.asBoolean(); + + switch (expression->oper.type) { + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } + } + case PLUS: return Value(stringify(left) + right.asString()); + } + } + + if (left.isString() && right.isNumber()) { + double right_num = right.asNumber(); + + switch (expression->oper.type) { + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } + } + case PLUS: return Value(left.asString() + stringify(right)); + case STAR: { + if (!isWholeNumer(right_num)) { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication", + "String multiplier must be a whole number"); + } + throw std::runtime_error("String multiplier must be whole number"); + } + std::string result; + for (int i = 0; i < static_cast(right_num); i++) { + result += left.asString(); + } + return Value(result); + } + } + } + + if (left.isNumber() && right.isString()) { + double left_num = left.asNumber(); + + switch (expression->oper.type) { + case AND: { + if (!isTruthy(left)) { + return left; // Return the falsy value + } else { + return right; // Return the second value + } + } + case OR: { + if (isTruthy(left)) { + return left; // Return the truthy value + } else { + return right; // Return the second value + } + } + case PLUS: return Value(stringify(left) + right.asString()); + case STAR: { + if (!isWholeNumer(left_num)) { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Invalid String Multiplication", + "String multiplier must be a whole number"); + } + throw std::runtime_error("String multiplier must be whole number"); + } + std::string result; + for (int i = 0; i < static_cast(left_num); i++) { + result += right.asString(); + } + return Value(result); + } + } + } + + if (left.isNone() && right.isString()) { std::string right_string = right.asString(); switch (expression->oper.type) { - case PLUS: { - return Value("none" + right_string); - } + case PLUS: return Value("none" + right_string); + } + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error", + "Cannot use '" + expression->oper.lexeme + "' on none and a string", expression->oper.lexeme); } throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string"); } else { + if (errorReporter) { + errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error", + "Operands must be of same type when using: " + expression->oper.lexeme, expression->oper.lexeme); + } throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme); } - } -Value Interpreter::visitVariableExpr(const std::shared_ptr& expression) +Value Interpreter::visitVarExpr(const std::shared_ptr& expression) { return environment->get(expression->name); } void Interpreter::addStdLibFunctions() { // Add standard library functions to the environment - StdLib::addToEnvironment(environment, *this); + StdLib::addToEnvironment(environment, *this, errorReporter); } void Interpreter::addBuiltinFunction(std::shared_ptr func) { @@ -299,6 +463,58 @@ void Interpreter::addBuiltinFunction(std::shared_ptr func) { Value Interpreter::visitAssignExpr(const std::shared_ptr& expression) { Value value = evaluate(expression->value); + + switch (expression->op.type) { + case PLUS_EQUAL: + case MINUS_EQUAL: + case STAR_EQUAL: + case SLASH_EQUAL: + case PERCENT_EQUAL: + case BIN_AND_EQUAL: + case BIN_OR_EQUAL: + case BIN_XOR_EQUAL: + case BIN_SLEFT_EQUAL: + case BIN_SRIGHT_EQUAL: { + Value currentValue = environment->get(expression->name.lexeme); + switch (expression->op.type) { + case PLUS_EQUAL: + value = currentValue + value; + break; + case MINUS_EQUAL: + value = currentValue - value; + break; + case STAR_EQUAL: + value = currentValue * value; + break; + case SLASH_EQUAL: + value = currentValue / value; + break; + case PERCENT_EQUAL: + value = currentValue % value; + break; + case BIN_AND_EQUAL: + value = currentValue & value; + break; + case BIN_OR_EQUAL: + value = currentValue | value; + break; + case BIN_XOR_EQUAL: + value = currentValue ^ value; + break; + case BIN_SLEFT_EQUAL: + value = currentValue << value; + break; + case BIN_SRIGHT_EQUAL: + value = currentValue >> value; + break; + default: + break; + } + break; + } + default: + break; + } environment->assign(expression->name, value); return value; } @@ -312,8 +528,8 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { } if (callee.isBuiltinFunction()) { - // Builtin functions now work directly with Value - return callee.asBuiltinFunction()->func(arguments); + // Builtin functions now work directly with Value and receive line and column + return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column); } if (callee.isFunction()) { @@ -325,13 +541,13 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { auto previousEnv = environment; environment = std::make_shared(function->closure); + environment->setErrorReporter(errorReporter); for (size_t i = 0; i < function->params.size(); i++) { environment->define(function->params[i], arguments[i]); } Value returnValue = NONE_VALUE; - bool hasReturn = false; for (const auto& stmt : function->body) { // Reset return context for each statement @@ -341,7 +557,6 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { execute(stmt); if (g_returnContext.hasReturn) { returnValue = g_returnContext.returnValue; - hasReturn = true; break; } } @@ -353,8 +568,21 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { throw std::runtime_error("Can only call functions and classes."); } +Value Interpreter::visitFunctionExpr(const std::shared_ptr& expression) { + // Convert Token parameters to string parameters + std::vector paramNames; + for (const Token& param : expression->params) { + paramNames.push_back(param.lexeme); + } + + auto function = msptr(Function)("anonymous", paramNames, expression->body, environment); + functions.push_back(function); // Keep the shared_ptr alive + return Value(function.get()); +} + void Interpreter::visitBlockStmt(const std::shared_ptr& statement) { auto newEnv = std::make_shared(environment); + newEnv->setErrorReporter(errorReporter); executeBlock(statement->statements, newEnv); } @@ -457,6 +685,16 @@ bool Interpreter::isTruthy(Value object) { return false; } + if(object.isNumber()) + { + return object.asNumber() != 0; + } + + if(object.isString()) + { + return object.asString().length() > 0; + } + return true; } diff --git a/source/Lexer.cpp b/source/Lexer.cpp index 4bbd0b2..b909798 100644 --- a/source/Lexer.cpp +++ b/source/Lexer.cpp @@ -1,4 +1,5 @@ #include "../headers/Lexer.h" +#include "../headers/ErrorReporter.h" #include "../headers/helperFunctions/HelperFunctions.h" #include #include @@ -8,69 +9,94 @@ using namespace std; std::vector Lexer::Tokenize(std::string source){ std::vector tokens; src = std::vector{source.begin(), source.end()}; - line = 0; + line = 1; + column = 1; while(!src.empty()) { char t = src[0]; if(t == '(') { - tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line}); //brace initialization in case you forget + tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line, column}); //brace initialization in case you forget advance(); } else if(t == ')') { - tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line}); + tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line, column}); advance(); } else if(t == '{') { - tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line}); + tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line, column}); advance(); } else if(t == '}') { - tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line}); + tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column}); advance(); } else if(t == ',') { - tokens.push_back(Token{COMMA, std::string(1, t), line}); + tokens.push_back(Token{COMMA, std::string(1, t), line, column}); advance(); } else if(t == '.') { - tokens.push_back(Token{DOT, std::string(1, t), line}); + tokens.push_back(Token{DOT, std::string(1, t), line, column}); advance(); } else if(t == ';') { - tokens.push_back(Token{SEMICOLON, std::string(1, t), line}); + tokens.push_back(Token{SEMICOLON, std::string(1, t), line, column}); advance(); } else if(t == '+') { - tokens.push_back(Token{PLUS, std::string(1, t), line}); + std::string token = std::string(1, t); advance(); + bool match = matchOn('='); + if(match) { + tokens.push_back(Token{PLUS_EQUAL, "+=", line, column - 1}); + } else { + tokens.push_back(Token{PLUS, token, line, column - 1}); + } } else if(t == '-') { - tokens.push_back(Token{MINUS, std::string(1, t), line}); + std::string token = std::string(1, t); advance(); + bool match = matchOn('='); + if(match) { + tokens.push_back(Token{MINUS_EQUAL, "-=", line, column - 1}); + } else { + tokens.push_back(Token{MINUS, token, line, column - 1}); + } } else if(t == '*') { - tokens.push_back(Token{STAR, std::string(1, t), line}); + std::string token = std::string(1, t); advance(); + bool match = matchOn('='); + if(match) { + tokens.push_back(Token{STAR_EQUAL, "*=", line, column - 1}); + } else { + tokens.push_back(Token{STAR, token, line, column - 1}); + } } else if(t == '%') { - tokens.push_back(Token{PERCENT, std::string(1, t), line}); + std::string token = std::string(1, t); advance(); + bool match = matchOn('='); + if(match) { + tokens.push_back(Token{PERCENT_EQUAL, "%=", line, column - 1}); + } else { + tokens.push_back(Token{PERCENT, token, line, column - 1}); + } } else if(t == '~') { - tokens.push_back(Token{BIN_NOT, std::string(1, t), line}); + tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1}); advance(); } else if(t == '=') @@ -79,7 +105,7 @@ std::vector Lexer::Tokenize(std::string source){ advance(); bool match = matchOn('='); token += match ? "=" : ""; - tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line}); + tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line, column - static_cast(token.length())}); } else if(t == '!') { @@ -87,7 +113,7 @@ std::vector Lexer::Tokenize(std::string source){ advance(); bool match = matchOn('='); token += match ? "=" : ""; - tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line}); + tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line, column - static_cast(token.length())}); } else if(t == '<') { @@ -95,15 +121,20 @@ std::vector Lexer::Tokenize(std::string source){ advance(); if(matchOn('=')) { - tokens.push_back(Token{LESS_EQUAL, "<=", line}); + tokens.push_back(Token{LESS_EQUAL, "<=", line, column - 1}); } else if(matchOn('<')) { - tokens.push_back(Token{BIN_SLEFT, "<<", line}); + bool equalMatch = matchOn('='); + if(equalMatch) { + tokens.push_back(Token{BIN_SLEFT_EQUAL, "<<=", line, column - 1}); + } else { + tokens.push_back(Token{BIN_SLEFT, "<<", line, column - 1}); + } } else { - tokens.push_back(Token{LESS, token, line}); + tokens.push_back(Token{LESS, token, line, column - static_cast(token.length())}); } } @@ -111,17 +142,66 @@ std::vector Lexer::Tokenize(std::string source){ { std::string token = std::string(1, t); advance(); - bool match = matchOn('='); - token += match ? "=" : ""; - tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token, line}); + if(matchOn('=')) + { + tokens.push_back(Token{GREATER_EQUAL, ">=", line, column - 1}); + } + else if(matchOn('>')) + { + bool equalMatch = matchOn('='); + if(equalMatch) { + tokens.push_back(Token{BIN_SRIGHT_EQUAL, ">>=", line, column - 1}); + } else { + tokens.push_back(Token{BIN_SRIGHT, ">>", line, column - 1}); + } + } + else + { + tokens.push_back(Token{GREATER, token, line, column - static_cast(token.length())}); + } } else if(t == '&') { std::string token = std::string(1, t); advance(); bool match = matchOn('&'); - token += match ? "&" : ""; - if(match) tokens.push_back(Token{AND, token, line}); + if(match) { + tokens.push_back(Token{AND, "&&", line, column - 1}); + } else { + bool equalMatch = matchOn('='); + if(equalMatch) { + tokens.push_back(Token{BIN_AND_EQUAL, "&=", line, column - 1}); + } else { + tokens.push_back(Token{BIN_AND, "&", line, column - 1}); + } + } + } + else if(t == '|') + { + std::string token = std::string(1, t); + advance(); + bool match = matchOn('|'); + if(match) { + tokens.push_back(Token{OR, "||", line, column - 1}); + } else { + bool equalMatch = matchOn('='); + if(equalMatch) { + tokens.push_back(Token{BIN_OR_EQUAL, "|=", line, column - 1}); + } else { + tokens.push_back(Token{BIN_OR, "|", line, column - 1}); + } + } + } + else if(t == '^') + { + std::string token = std::string(1, t); + advance(); + bool match = matchOn('='); + if(match) { + tokens.push_back(Token{BIN_XOR_EQUAL, "^=", line, column - 1}); + } else { + tokens.push_back(Token{BIN_XOR, "^", line, column - 1}); + } } else if(t == '/') { @@ -137,13 +217,19 @@ std::vector Lexer::Tokenize(std::string source){ } else { - tokens.push_back(Token{SLASH, std::string(1, t), line}); + bool equalMatch = matchOn('='); + if(equalMatch) { + tokens.push_back(Token{SLASH_EQUAL, "/=", line, column - 1}); + } else { + tokens.push_back(Token{SLASH, "/", line, column - 1}); + } } } else if(t == '"') { bool last_was_escape = false; std::string str; + int startColumn = column; advance(); while(!src.empty()) @@ -156,7 +242,6 @@ std::vector Lexer::Tokenize(std::string source){ next_c = "\\" + std::string(1, src[0]); } - if(next_c == "\n") line++; str += next_c; advance(); } @@ -172,14 +257,13 @@ std::vector Lexer::Tokenize(std::string source){ std::string escaped_str = parseEscapeCharacters(str); - tokens.push_back(Token{STRING, escaped_str, line}); + tokens.push_back(Token{STRING, escaped_str, line, startColumn}); } } else if(t == '\n') { - line++; advance(); } else @@ -191,6 +275,7 @@ std::vector Lexer::Tokenize(std::string source){ if(std::isdigit(t)) { std::string num; + int startColumn = column; if(src[0] != '0') notationInvalidated = true; @@ -260,11 +345,12 @@ std::vector Lexer::Tokenize(std::string source){ } } - tokens.push_back(Token{NUMBER, num, line}); + tokens.push_back(Token{NUMBER, num, line, startColumn}); } else if(std::isalpha(t)) { std::string ident; + int startColumn = column; while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_')) { ident += src[0]; @@ -273,11 +359,11 @@ std::vector Lexer::Tokenize(std::string source){ if(KEYWORDS.find(ident) != KEYWORDS.end()) //identifier is a keyword { - tokens.push_back(Token{KEYWORDS.at(ident), ident, line}); + tokens.push_back(Token{KEYWORDS.at(ident), ident, line, startColumn}); } else { - tokens.push_back(Token{IDENTIFIER, ident, line}); + tokens.push_back(Token{IDENTIFIER, ident, line, startColumn}); } } @@ -287,7 +373,10 @@ std::vector Lexer::Tokenize(std::string source){ } else { - + if (errorReporter) { + errorReporter->reportError(line, column, "Lexer Error", + "Unknown token '" + std::string(1, t) + "'", ""); + } throw std::runtime_error("LEXER: Unknown Token: '" + std::string(1, t) + "'"); } @@ -310,7 +399,27 @@ bool Lexer::matchOn(char expected) void Lexer::advance(int by) { for (int i = 0; i < by; ++i) { - src.erase(src.begin()); + if (!src.empty()) { + char c = src[0]; + src.erase(src.begin()); + + // Update column and line counters + if (c == '\n') { + line++; + column = 1; + } else if (c == '\r') { + // Handle \r\n sequence + if (!src.empty() && src[0] == '\n') { + src.erase(src.begin()); + line++; + column = 1; + } else { + column++; + } + } else { + column++; + } + } } } diff --git a/source/Parser.cpp b/source/Parser.cpp index 30a8d3d..0e697c9 100644 --- a/source/Parser.cpp +++ b/source/Parser.cpp @@ -2,6 +2,7 @@ // Created by Bobby Lucero on 5/26/23. // #include "../headers/Parser.h" +#include // Precedence @@ -13,25 +14,110 @@ sptr(Expr) Parser::expression() return assignment(); } -sptr(Expr) Parser::assignment() +sptr(Expr) Parser::logical_or() { + sptr(Expr) expr = logical_and(); + while(match({OR})) + { + Token op = previous(); + sptr(Expr) right = logical_and(); + expr = msptr(BinaryExpr)(expr, op, right); + } + + return expr; +} + +sptr(Expr) Parser::logical_and() +{ sptr(Expr) expr = equality(); - if(match({EQUAL})) + while(match({AND})) { - Token equals = previous(); + Token op = previous(); + sptr(Expr) right = equality(); + expr = msptr(BinaryExpr)(expr, op, right); + } + return expr; +} + +// bitwise_or now calls comparison (not bitwise_xor) +sptr(Expr) Parser::bitwise_or() +{ + sptr(Expr) expr = bitwise_xor(); + + while(match({BIN_OR})) + { + Token op = previous(); + sptr(Expr) right = bitwise_xor(); + expr = msptr(BinaryExpr)(expr, op, right); + } + + return expr; +} + +sptr(Expr) Parser::bitwise_xor() +{ + sptr(Expr) expr = bitwise_and(); + + while(match({BIN_XOR})) + { + Token op = previous(); + sptr(Expr) right = bitwise_and(); + expr = msptr(BinaryExpr)(expr, op, right); + } + + return expr; +} + +sptr(Expr) Parser::bitwise_and() +{ + sptr(Expr) expr = shift(); + + while(match({BIN_AND})) + { + Token op = previous(); + sptr(Expr) right = shift(); + expr = msptr(BinaryExpr)(expr, op, right); + } + + return expr; +} + +sptr(Expr) Parser::shift() +{ + sptr(Expr) expr = term(); + + while(match({BIN_SLEFT, BIN_SRIGHT})) + { + Token op = previous(); + sptr(Expr) right = term(); + expr = msptr(BinaryExpr)(expr, op, right); + } + + return expr; +} + +sptr(Expr) Parser::assignment() +{ + sptr(Expr) expr = logical_or(); + + if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, + BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL})) + { + Token op = previous(); sptr(Expr) value = assignment(); - if(std::dynamic_pointer_cast(expr)) { - Token name = std::dynamic_pointer_cast(expr)->name; - - return msptr(AssignExpr)(name, value); + return msptr(AssignExpr)(name, op, value); + } + + if (errorReporter) { + errorReporter->reportError(op.line, op.column, "Parse Error", + "Invalid assignment target", ""); } - throw std::runtime_error("Invalid assignment target."); } @@ -54,12 +140,12 @@ sptr(Expr) Parser::equality() sptr(Expr) Parser::comparison() { - sptr(Expr) expr = term(); + sptr(Expr) expr = bitwise_or(); while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL})) { Token op = previous(); - sptr(Expr) right = term(); + sptr(Expr) right = bitwise_or(); expr = msptr(BinaryExpr)(expr, op, right); } @@ -132,6 +218,14 @@ sptr(Expr) Parser::primary() return msptr(GroupingExpr)(expr); } + if(match({FUNCTION})) { + return functionExpression(); + } + + if (errorReporter) { + errorReporter->reportError(peek().line, peek().column, "Parse Error", + "Expression expected", ""); + } throw std::runtime_error("Expression expected at: " + std::to_string(peek().line)); } @@ -192,10 +286,46 @@ sptr(Stmt) Parser::functionDeclaration() consume(CLOSE_PAREN, "Expected ')' after parameters."); consume(OPEN_BRACE, "Expected '{' before function body."); + // Enter function scope + enterFunction(); + std::vector body = block(); + + // Exit function scope + exitFunction(); + return msptr(FunctionStmt)(name, parameters, body); } +std::shared_ptr Parser::functionExpression() { + consume(OPEN_PAREN, "Expect '(' after 'func'."); + std::vector parameters; + if (!check(CLOSE_PAREN)) { + do { + if (parameters.size() >= 255) { + 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."); + } + parameters.push_back(consume(IDENTIFIER, "Expect parameter name.")); + } while (match({COMMA})); + } + consume(CLOSE_PAREN, "Expect ')' after parameters."); + consume(OPEN_BRACE, "Expect '{' before function body."); + + // Enter function scope + enterFunction(); + + std::vector> body = block(); + + // Exit function scope + exitFunction(); + + return msptr(FunctionExpr)(parameters, body); +} + sptr(Stmt) Parser::statement() { if(match({RETURN})) return returnStatement(); @@ -225,6 +355,16 @@ sptr(Stmt) Parser::ifStatement() sptr(Stmt) Parser::returnStatement() { Token keyword = previous(); + + // Check if we're inside a function + if (!isInFunction()) { + if (errorReporter) { + errorReporter->reportError(keyword.line, 0, "Parse Error", + "Cannot return from outside a function", ""); + } + throw std::runtime_error("Cannot return from outside a function"); + } + sptr(Expr) value = msptr(LiteralExpr)("none", false, true, false); if (!check(SEMICOLON)) { @@ -310,6 +450,24 @@ Token Parser::previous() { Token Parser::consume(TokenType type, const std::string& message) { if(check(type)) return advance(); + if (errorReporter) { + // Use the precise column information from the token + int errorColumn = peek().column; + + // For missing closing parenthesis, point to where it should be + if (type == CLOSE_PAREN) { + // The closing parenthesis should be right after the previous token + errorColumn = previous().column + previous().lexeme.length(); + + // For string tokens, add 2 to account for the opening and closing quotes + if (previous().type == STRING) { + errorColumn += 2; + } + } + + errorReporter->reportError(peek().line, errorColumn, "Parse Error", + "Unexpected symbol '" + peek().lexeme + "': " + message, ""); + } throw std::runtime_error("Unexpected symbol '" + peek().lexeme +"': "+ message); } diff --git a/source/StdLib.cpp b/source/StdLib.cpp index c211ceb..d59a0f6 100644 --- a/source/StdLib.cpp +++ b/source/StdLib.cpp @@ -1,12 +1,17 @@ #include "../headers/StdLib.h" #include "../headers/Interpreter.h" +#include "../headers/ErrorReporter.h" #include -void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& interpreter) { +void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& interpreter, ErrorReporter* errorReporter) { // Create a built-in toString function auto toStringFunc = std::make_shared("toString", - [&interpreter](std::vector args) -> Value { + [&interpreter, errorReporter](std::vector args, int line, int column) -> Value { if (args.size() != 1) { + 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()) + "."); } @@ -19,8 +24,12 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Create a built-in print function auto printFunc = std::make_shared("print", - [&interpreter](std::vector args) -> Value { + [&interpreter, errorReporter](std::vector args, int line, int column) -> Value { if (args.size() != 1) { + 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()) + "."); } // Use the interpreter's stringify function @@ -34,8 +43,12 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Create a built-in assert function auto assertFunc = std::make_shared("assert", - [](std::vector args) -> Value { + [errorReporter](std::vector args, int line, int column) -> Value { if (args.size() != 1 && args.size() != 2) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true); + } throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + "."); } @@ -56,6 +69,9 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int message += " - " + std::string(args[1].asString()); } } + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", message, "", true); + } throw std::runtime_error(message); } @@ -68,8 +84,12 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Create a built-in time function (returns microseconds since Unix epoch) auto timeFunc = std::make_shared("time", - [](std::vector args) -> Value { + [errorReporter](std::vector args, int line, int column) -> Value { if (args.size() != 0) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true); + } throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + "."); } @@ -86,8 +106,12 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Create a built-in input function auto inputFunc = std::make_shared("input", - [&interpreter](std::vector args) -> Value { + [&interpreter, errorReporter](std::vector args, int line, int column) -> Value { if (args.size() > 1) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true); + } throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + "."); } @@ -109,8 +133,12 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Create a built-in type function auto typeFunc = std::make_shared("type", - [](std::vector args) -> Value { + [errorReporter](std::vector args, int line, int column) -> Value { if (args.size() != 1) { + 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()) + "."); } @@ -140,13 +168,13 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Create a built-in toNumber function for string-to-number conversion auto toNumberFunc = std::make_shared("toNumber", - [](std::vector args) -> Value { + [](std::vector args, int line, int column) -> Value { if (args.size() != 1) { - throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + return NONE_VALUE; // Return none for wrong argument count } if (!args[0].isString()) { - throw std::runtime_error("toNumber() expects a string argument."); + return NONE_VALUE; // Return none for wrong type } std::string str = args[0].asString(); @@ -156,20 +184,58 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int str.erase(str.find_last_not_of(" \t\n\r") + 1); if (str.empty()) { - throw std::runtime_error("Cannot convert empty string to number."); + return NONE_VALUE; // Return none for empty string } try { double value = std::stod(str); return Value(value); } catch (const std::invalid_argument&) { - throw std::runtime_error("Cannot convert '" + str + "' to number."); + return NONE_VALUE; // Return none for invalid conversion } catch (const std::out_of_range&) { - throw std::runtime_error("Number '" + str + "' is out of range."); + return NONE_VALUE; // Return none for out of range } }); env->define("toNumber", Value(toNumberFunc.get())); // Store the shared_ptr in the interpreter to keep it alive interpreter.addBuiltinFunction(toNumberFunc); + + // Create a built-in toBoolean function for explicit boolean conversion + auto toBooleanFunc = std::make_shared("toBoolean", + [errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + 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()) + "."); + } + + // Use the same logic as isTruthy() for consistency + Value value = args[0]; + + if (value.isNone()) { + return Value(false); + } + + if (value.isBoolean()) { + return value; // Already a boolean + } + + if (value.isNumber()) { + return Value(value.asNumber() != 0.0); + } + + if (value.isString()) { + return Value(!value.asString().empty()); + } + + // For any other type (functions, etc.), consider them truthy + return Value(true); + }); + env->define("toBoolean", Value(toBooleanFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toBooleanFunc); } \ No newline at end of file diff --git a/source/bob.cpp b/source/bob.cpp index 4c9dfed..53cca32 100644 --- a/source/bob.cpp +++ b/source/bob.cpp @@ -20,6 +20,12 @@ void Bob::runFile(const string& path) return; } + // Load source code into error reporter for context + errorReporter.loadSource(source, path); + + // Connect error reporter to interpreter + interpreter->setErrorReporter(&errorReporter); + this->run(source); } @@ -39,42 +45,49 @@ void Bob::runPrompt() break; } + // Load source code into error reporter for context + errorReporter.loadSource(line, "REPL"); + + // Connect error reporter to interpreter + interpreter->setErrorReporter(&errorReporter); + this->run(line); - hadError = false; } } -void Bob::error(int line, const string& message) -{ - -} - void Bob::run(string source) { try { + // Connect error reporter to lexer + lexer.setErrorReporter(&errorReporter); + vector tokens = lexer.Tokenize(std::move(source)); Parser p(tokens); + + // Connect error reporter to parser + p.setErrorReporter(&errorReporter); + vector statements = p.parse(); interpreter->interpret(statements); } catch(std::exception &e) { - cout << "ERROR OCCURRED: " << e.what() << endl; + // Only suppress errors that have already been reported by the error reporter + if (errorReporter.hasReportedError()) { + return; + } + + // For errors that weren't reported (like parser errors, undefined variables, etc.) + // print them normally + std::cout << "Error: " << e.what() << std::endl; return; } catch(...) { - cout << "UNKNOWN ERROR OCCURRED" << endl; + // Unknown error - report it since it wasn't handled by the interpreter + errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred"); return; } - - - -} - -void Bob::report(int line, string where, string message) -{ - hadError = true; } diff --git a/test_bob_language.bob b/test_bob_language.bob index a4f0d5a..1fa31e8 100644 --- a/test_bob_language.bob +++ b/test_bob_language.bob @@ -756,7 +756,7 @@ assert(hex3 == 4294967295, "Hex FFFFFFFF should be 4294967295"); var add_num = 5 + 3; var sub_num = 10 - 4; var mul_num = 6 * 7; -var div_num = 15 / 3; +var div_num = 15 /* 3; assert(add_num == 8, "5 + 3 should be 8"); assert(sub_num == 6, "10 - 4 should be 6"); assert(mul_num == 42, "6 * 7 should be 42"); @@ -1124,6 +1124,416 @@ assert(deepFactorial(10) == 3628800, "deepFactorial(10) should be 3628800"); print("✓ Recursion working"); +// ======================================== +// TEST 39: LOGICAL OPERATORS (&&, ||, !) +// ======================================== +print("\n--- Test 39: Logical Operators ---"); + +// Test logical AND (&&) +assert(5 && 3 == 3, "5 && 3 should return 3 (truthy && truthy = second value)"); +assert(0 && 5 == 0, "0 && 5 should return 0 (falsy && truthy = first value)"); +assert(5 && 0 == 0, "5 && 0 should return 0 (truthy && falsy = second value)"); +assert(0 && 0 == 0, "0 && 0 should return 0 (falsy && falsy = first value)"); + +// Test logical OR (||) +assert(5 || 3 == 5, "5 || 3 should return 5 (truthy || truthy = first value)"); +assert(0 || 5 == 5, "0 || 5 should return 5 (falsy || truthy = second value)"); +assert(5 || 0 == 5, "5 || 0 should return 5 (truthy || falsy = first value)"); +assert(0 || 0 == 0, "0 || 0 should return 0 (falsy || falsy = second value)"); + +// Test logical NOT (!) +assert(!0 == true, "!0 should be true"); +assert(!1 == false, "!1 should be false"); +assert(!5 == false, "!5 should be false"); +assert(!true == false, "!true should be false"); +assert(!false == true, "!false should be true"); + +// Test short-circuit evaluation +var short_circuit_test = 0; +func sideEffect() { + short_circuit_test = 1; + return 5; +} + +// Test that short-circuit evaluation works correctly +// Note: We can't easily test side effects in Bob, so we test the return values instead +var result_short1 = 0 && 5; +assert(result_short1 == 0, "0 && 5 should return 0 (short-circuit)"); + +var result_short2 = 1 && 5; +assert(result_short2 == 5, "1 && 5 should return 5 (no short-circuit)"); + +var result_short3 = 1 || 5; +assert(result_short3 == 1, "1 || 5 should return 1 (short-circuit)"); + +var result_short4 = 0 || 5; +assert(result_short4 == 5, "0 || 5 should return 5 (no short-circuit)"); + +// Test complex logical expressions +assert((5 > 3) && (10 < 20) == true, "Complex AND with comparisons"); +assert((5 < 3) || (10 < 20) == true, "Complex OR with comparisons"); +assert(!(5 < 3) == true, "Complex NOT with comparison"); + +// Test truthiness rules +assert("hello" && "world" == "world", "String && string should return second string"); +assert("" && "world" == "", "Empty string && string should return empty string"); +assert("hello" || "world" == "hello", "String || string should return first string"); +assert("" || "world" == "world", "Empty string || string should return second string"); + +print("✓ Logical operators working"); + +// ======================================== +// TEST 40: BITWISE OPERATORS (&, |, ^, <<, >>, ~) +// ======================================== +print("\n--- Test 40: Bitwise Operators ---"); + +// Test bitwise AND (&) +assert(10 & 3 == 2, "10 & 3 should be 2 (1010 & 0011 = 0010)"); +assert(15 & 7 == 7, "15 & 7 should be 7 (1111 & 0111 = 0111)"); +assert(255 & 128 == 128, "255 & 128 should be 128"); + +// Test bitwise OR (|) +assert(10 | 3 == 11, "10 | 3 should be 11 (1010 | 0011 = 1011)"); +assert(5 | 10 == 15, "5 | 10 should be 15 (0101 | 1010 = 1111)"); +assert(128 | 64 == 192, "128 | 64 should be 192"); + +// Test bitwise XOR (^) +assert(10 ^ 3 == 9, "10 ^ 3 should be 9 (1010 ^ 0011 = 1001)"); +assert(15 ^ 7 == 8, "15 ^ 7 should be 8 (1111 ^ 0111 = 1000)"); +assert(255 ^ 128 == 127, "255 ^ 128 should be 127"); + +// Test left shift (<<) +assert(5 << 1 == 10, "5 << 1 should be 10 (0101 << 1 = 1010)"); +assert(3 << 2 == 12, "3 << 2 should be 12 (0011 << 2 = 1100)"); +assert(1 << 8 == 256, "1 << 8 should be 256"); + +// Test right shift (>>) +assert(10 >> 1 == 5, "10 >> 1 should be 5 (1010 >> 1 = 0101)"); +assert(20 >> 2 == 5, "20 >> 2 should be 5 (10100 >> 2 = 00101)"); +assert(256 >> 8 == 1, "256 >> 8 should be 1"); + +// Test bitwise NOT (~) +assert(~10 == -11, "~10 should be -11"); +assert(~0 == -1, "~0 should be -1"); +assert(~(-1) == 0, "~(-1) should be 0"); + +// Test complex bitwise expressions +assert((10 & 3) | (5 & 2) == 2, "Complex bitwise expression 1"); +assert((15 << 1) >> 1 == 15, "Complex bitwise expression 2"); +assert(~(10 & 5) == -1, "Complex bitwise expression 3"); + +// Test edge cases +assert(0 & 0 == 0, "0 & 0 should be 0"); +assert(0 | 0 == 0, "0 | 0 should be 0"); +assert(0 ^ 0 == 0, "0 ^ 0 should be 0"); +assert(0 << 5 == 0, "0 << 5 should be 0"); +assert(0 >> 5 == 0, "0 >> 5 should be 0"); +assert(~0 == -1, "~0 should be -1"); + +print("✓ Bitwise operators working"); + +// ======================================== +// TEST 41: COMPOUND ASSIGNMENT OPERATORS +// ======================================== +print("\n--- Test 41: Compound Assignment Operators ---"); + +// Test arithmetic compound assignment +var comp_x = 10; +comp_x += 5; +assert(comp_x == 15, "comp_x += 5 should make comp_x = 15"); + +comp_x -= 3; +assert(comp_x == 12, "comp_x -= 3 should make comp_x = 12"); + +comp_x *= 2; +assert(comp_x == 24, "comp_x *= 2 should make comp_x = 24"); + +comp_x /= 4; +assert(comp_x == 6, "comp_x /= 4 should make comp_x = 6"); + +comp_x %= 4; +assert(comp_x == 2, "comp_x %= 4 should make comp_x = 2"); + +// Test bitwise compound assignment +var comp_y = 15; +comp_y &= 7; +assert(comp_y == 7, "comp_y &= 7 should make comp_y = 7"); + +comp_y |= 8; +assert(comp_y == 15, "comp_y |= 8 should make comp_y = 15"); + +comp_y ^= 4; +assert(comp_y == 11, "comp_y ^= 4 should make comp_y = 11"); + +comp_y <<= 1; +assert(comp_y == 22, "comp_y <<= 1 should make comp_y = 22"); + +comp_y >>= 2; +assert(comp_y == 5, "comp_y >>= 2 should make comp_y = 5"); + +// Test compound assignment with expressions +var comp_z = 10; +comp_z += 2 + 3; +assert(comp_z == 15, "comp_z += 2 + 3 should make comp_z = 15"); + +comp_z *= 2 + 1; +assert(comp_z == 45, "comp_z *= 2 + 1 should make comp_z = 45"); + +// Test compound assignment with variables +var comp_a = 5; +var comp_b = 3; +comp_a += comp_b; +assert(comp_a == 8, "comp_a += comp_b should make comp_a = 8"); + +comp_a *= comp_b; +assert(comp_a == 24, "comp_a *= comp_b should make comp_a = 24"); + +print("✓ Compound assignment operators working"); + +// ======================================== +// TEST 42: ANONYMOUS FUNCTIONS +// ======================================== +print("\n--- Test 42: Anonymous Functions ---"); + +// Test basic anonymous function +var anonymous1 = func() { + return 42; +}; +assert(anonymous1() == 42, "Basic anonymous function should return 42"); + +// Test anonymous function with parameters +var anonymous2 = func(x, y) { + return x + y; +}; +assert(anonymous2(5, 3) == 8, "Anonymous function with parameters should work"); + +// Test anonymous function with string operations +var anonymous3 = func(name) { + return "Hello, " + name + "!"; +}; +assert(anonymous3("Bob") == "Hello, Bob!", "Anonymous function with strings should work"); + +// Test anonymous function with conditionals +var anonymous4 = func(x) { + if (x > 0) { + return "positive"; + } else { + return "non-positive"; + } +}; +assert(anonymous4(5) == "positive", "Anonymous function with conditionals should work"); +assert(anonymous4(-3) == "non-positive", "Anonymous function with conditionals should work"); + +// Test anonymous function composition +var compose = func(f, g, x) { + return f(g(x)); +}; + +var double = func(x) { + return x * 2; +}; + +var addOne = func(x) { + return x + 1; +}; + +var result_comp = compose(double, addOne, 5); +assert(result_comp == 12, "Anonymous function composition should work"); + +// Test anonymous function as return value +func createMultiplier(factor) { + return func(x) { + return x * factor; + }; +} + +var multiplyBy3 = createMultiplier(3); +assert(multiplyBy3(4) == 12, "Anonymous function as return value should work"); + +// Test anonymous function with closure +func createCounter() { + var count = 0; + return func() { + count = count + 1; + return count; + }; +} + +var counter1 = createCounter(); +var counter2 = createCounter(); + +assert(counter1() == 1, "Anonymous function closure should work"); +assert(counter1() == 2, "Anonymous function closure should maintain state"); +assert(counter2() == 1, "Different anonymous function instances should have separate state"); + +print("✓ Anonymous functions working"); + +// ======================================== +// TEST 43: FUNCTIONS RETURNING FUNCTIONS +// ======================================== +print("\n--- Test 43: Functions Returning Functions ---"); + +// Test basic function returning function +func createAdder(x) { + return func(y) { + return x + y; + }; +} + +var add5 = createAdder(5); +assert(add5(3) == 8, "Function returning function should work"); + +// Test function returning multiple functions +func createMathOps() { + return func(x, y) { + return x + y; + }; +} + +var mathFunc = createMathOps(); +assert(mathFunc(10, 20) == 30, "Function returning math function should work"); + +// Test function returning function with closure +func createGreeter(greeting) { + return func(name) { + return greeting + ", " + name + "!"; + }; +} + +var helloGreeter = createGreeter("Hello"); +var hiGreeter = createGreeter("Hi"); + +assert(helloGreeter("Alice") == "Hello, Alice!", "Greeter function should work"); +assert(hiGreeter("Bob") == "Hi, Bob!", "Different greeter should work"); + +// Test nested function returning functions +func createCalculator() { + return func(operation) { + if (operation == "add") { + return func(x, y) { + return x + y; + }; + } else if (operation == "multiply") { + return func(x, y) { + return x * y; + }; + } else { + return func(x, y) { + return x - y; + }; + } + }; +} + +var calculator = createCalculator(); +var addFunc = calculator("add"); +var multiplyFunc = calculator("multiply"); + +assert(addFunc(5, 3) == 8, "Calculator add function should work"); +assert(multiplyFunc(4, 6) == 24, "Calculator multiply function should work"); + +// Test function returning function with complex logic +func createValidator(rule) { + return func(value) { + if (rule == "positive") { + return value > 0; + } else if (rule == "even") { + return value % 2 == 0; + } else if (rule == "string") { + return type(value) == "string"; + } else { + return false; + } + }; +} + +var positiveValidator = createValidator("positive"); +var evenValidator = createValidator("even"); +var stringValidator = createValidator("string"); + +assert(positiveValidator(5) == true, "Positive validator should work"); +assert(positiveValidator(-3) == false, "Positive validator should work"); +assert(evenValidator(4) == true, "Even validator should work"); +assert(evenValidator(7) == false, "Even validator should work"); +assert(stringValidator("hello") == true, "String validator should work"); +assert(stringValidator(42) == false, "String validator should work"); + +print("✓ Functions returning functions working"); + +// ======================================== +// TEST 44: COMPREHENSIVE OPERATOR PRECEDENCE +// ======================================== +print("\n--- Test 44: Operator Precedence ---"); + +// Test logical operator precedence +assert(5 && 3 || 0 == 3, "&& should have higher precedence than ||"); +assert(0 || 5 && 3 == 3, "&& should have higher precedence than ||"); + +// Test bitwise operator precedence +assert(10 & 3 | 5 == 7, "& should have higher precedence than |"); +assert(10 | 3 & 5 == 11, "& should have higher precedence than |"); + +// Test shift operator precedence +assert(10 << 1 + 2 == 80, "Shift should have higher precedence than addition"); +assert(10 + 1 << 2 == 44, "Addition should have higher precedence than shift"); + +// Test arithmetic operator precedence +assert(2 + 3 * 4 == 14, "Multiplication should have higher precedence than addition"); +assert(10 - 4 / 2 == 8, "Division should have higher precedence than subtraction"); + +// Test complex precedence +assert(5 && 3 | 2 << 1 == 7, "Complex precedence test 1"); +assert((5 && 3) | (2 << 1) == 7, "Complex precedence test 2"); + +print("✓ Operator precedence working"); + +// ======================================== +// TEST 45: EDGE CASES FOR NEW OPERATORS +// ======================================== +print("\n--- Test 45: Edge Cases for New Operators ---"); + +// Test logical operators with edge cases +assert(0 && 0 == 0, "0 && 0 should be 0"); +assert(0 || 0 == 0, "0 || 0 should be 0"); +assert(!0 == true, "!0 should be true"); +assert(!1 == false, "!1 should be false"); + +// Test bitwise operators with edge cases +assert(0 & 0 == 0, "0 & 0 should be 0"); +assert(0 | 0 == 0, "0 | 0 should be 0"); +assert(0 ^ 0 == 0, "0 ^ 0 should be 0"); +assert(0 << 5 == 0, "0 << 5 should be 0"); +assert(0 >> 5 == 0, "0 >> 5 should be 0"); +assert(~0 == -1, "~0 should be -1"); + +// Test compound assignment with edge cases +var edge_x = 0; +edge_x += 5; +assert(edge_x == 5, "0 += 5 should be 5"); + +edge_x -= 10; +assert(edge_x == -5, "5 -= 10 should be -5"); + +edge_x *= 0; +assert(edge_x == 0, "-5 *= 0 should be 0"); + +// Test anonymous functions with edge cases +var edge_func = func() { + return none; +}; +assert(type(edge_func()) == "none", "Anonymous function returning none should work"); + +var edge_func2 = func(x) { + if (x == 0) { + return 0; + } else { + return edge_func2(x - 1) + 1; + } +}; +assert(edge_func2(3) == 3, "Recursive anonymous function should work"); + +print("✓ Edge cases for new operators working"); + // ======================================== // FINAL SUMMARY // ======================================== @@ -1176,7 +1586,14 @@ print("- toString function (universal string conversion)"); print("- Enhanced print function (works with all object types)"); print("- Redefinable functions (including built-in function override)"); print("- Recursion (factorial, fibonacci, mutual recursion, deep recursion)"); +print("- NEW: Logical operators (&&, ||, !) with short-circuit evaluation"); +print("- NEW: Bitwise operators (&, |, ^, <<, >>, ~)"); +print("- NEW: Compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)"); +print("- NEW: Anonymous functions (func(...) { ... })"); +print("- NEW: Functions returning functions"); +print("- NEW: Operator precedence for all new operators"); +print("- NEW: Edge cases for all new operators"); print("\n🎉 ALL TESTS PASSED! 🎉"); print("Bob language is working correctly!"); -print("Ready for next phase: Control Flow (if statements, while loops)"); \ No newline at end of file +print("Ready for next phase: Control Flow (while loops, data structures)"); \ No newline at end of file diff --git a/test_fib.bob b/test_fib.bob index 573f15b..fd7a594 100644 --- a/test_fib.bob +++ b/test_fib.bob @@ -9,4 +9,7 @@ func fib(n) { print("Fibonacci test:"); var fib_result = fib(30); -print("Result: " + fib_result); \ No newline at end of file +print("Result: " + fib_result); + + +print(10 / 0);