Fixed performance, added enhanced error reporting, anon funcs, toBoolean, various other things

This commit is contained in:
Bobby Lucero 2025-08-01 13:43:35 -04:00
parent 1e65b344ae
commit adb00d496f
19 changed files with 1773 additions and 333 deletions

View File

@ -6,39 +6,32 @@
#include "Value.h" #include "Value.h"
#include "Lexer.h" #include "Lexer.h"
// Forward declaration
class ErrorReporter;
class Environment { class Environment {
public: public:
Environment() : parent(nullptr) {} Environment() : parent(nullptr), errorReporter(nullptr) {}
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env) {} Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env), errorReporter(nullptr) {}
// Set error reporter for enhanced error reporting
void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
}
// Optimized define with inline // Optimized define with inline
inline void define(const std::string& name, const Value& value) { inline void define(const std::string& name, const Value& value) {
variables[name] = value; variables[name] = value;
} }
// Optimized assign with inline // Enhanced assign with error reporting
inline void assign(const Token& name, const Value& value) { 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 + "'.");
}
}
// Optimized get with inline and move semantics // Enhanced get with error reporting
inline Value get(const Token& name) { Value get(const Token& name);
auto it = variables.find(name.lexeme);
if (it != variables.end()) { // Get by string name with error reporting
return it->second; // Return by value (will use move if possible) Value get(const std::string& name);
}
if (parent != nullptr) {
return parent->get(name);
}
throw std::runtime_error("Undefined variable '" + name.lexeme + "'.");
}
std::shared_ptr<Environment> getParent() const { return parent; } std::shared_ptr<Environment> getParent() const { return parent; }
inline void clear() { variables.clear(); } inline void clear() { variables.clear(); }
@ -46,4 +39,5 @@ public:
private: private:
std::unordered_map<std::string, Value> variables; std::unordered_map<std::string, Value> variables;
std::shared_ptr<Environment> parent; std::shared_ptr<Environment> parent;
ErrorReporter* errorReporter;
}; };

63
headers/ErrorReporter.h Normal file
View File

@ -0,0 +1,63 @@
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
// 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<std::string> callStack;
};
class ErrorReporter {
private:
std::vector<std::string> sourceLines;
std::string currentFileName;
std::vector<std::string> 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<std::string>& callStack);
std::string getLineWithArrow(int line, int column);
std::string colorize(const std::string& text, const std::string& color);
};

View File

@ -10,6 +10,10 @@
#include "TypeWrapper.h" #include "TypeWrapper.h"
#include "Value.h" #include "Value.h"
// Forward declarations
struct FunctionExpr;
struct ExprVisitor;
struct AssignExpr; struct AssignExpr;
struct BinaryExpr; struct BinaryExpr;
struct GroupingExpr; struct GroupingExpr;
@ -23,14 +27,14 @@ struct ExprVisitor
{ {
virtual Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expr) = 0; virtual Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expr) = 0;
virtual Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expr) = 0; virtual Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expr) = 0;
virtual Value visitCallExpr(const std::shared_ptr<CallExpr>& expr) = 0;
virtual Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expr) = 0;
virtual Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expr) = 0; virtual Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expr) = 0;
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0; virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0; virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
virtual Value visitVariableExpr(const std::shared_ptr<VarExpr>& expr) = 0; virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
virtual Value visitCallExpr(const std::shared_ptr<CallExpr>& expr) = 0;
}; };
struct Expr : public std::enable_shared_from_this<Expr> { struct Expr : public std::enable_shared_from_this<Expr> {
virtual Value accept(ExprVisitor* visitor) = 0; virtual Value accept(ExprVisitor* visitor) = 0;
virtual ~Expr() = default; virtual ~Expr() = default;
@ -39,11 +43,10 @@ struct Expr : public std::enable_shared_from_this<Expr> {
struct AssignExpr : Expr struct AssignExpr : Expr
{ {
const Token name; const Token name;
const Token op;
std::shared_ptr<Expr> value; std::shared_ptr<Expr> value;
AssignExpr(Token name, std::shared_ptr<Expr> value) : name(name), value(value) AssignExpr(Token name, Token op, std::shared_ptr<Expr> value)
{ : name(name), op(op), value(value) {}
}
Value accept(ExprVisitor* visitor) override Value accept(ExprVisitor* visitor) override
{ {
return visitor->visitAssignExpr(std::static_pointer_cast<AssignExpr>(shared_from_this())); return visitor->visitAssignExpr(std::static_pointer_cast<AssignExpr>(shared_from_this()));
@ -57,9 +60,7 @@ struct BinaryExpr : Expr
std::shared_ptr<Expr> right; std::shared_ptr<Expr> right;
BinaryExpr(std::shared_ptr<Expr> left, Token oper, std::shared_ptr<Expr> right) BinaryExpr(std::shared_ptr<Expr> left, Token oper, std::shared_ptr<Expr> right)
: left(left), oper(oper), right(right) : left(left), oper(oper), right(right) {}
{
}
Value accept(ExprVisitor* visitor) override{ Value accept(ExprVisitor* visitor) override{
return visitor->visitBinaryExpr(std::static_pointer_cast<BinaryExpr>(shared_from_this())); return visitor->visitBinaryExpr(std::static_pointer_cast<BinaryExpr>(shared_from_this()));
} }
@ -69,9 +70,7 @@ struct GroupingExpr : Expr
{ {
std::shared_ptr<Expr> expression; std::shared_ptr<Expr> expression;
explicit GroupingExpr(std::shared_ptr<Expr> expression) : expression(expression) explicit GroupingExpr(std::shared_ptr<Expr> expression) : expression(expression) {}
{
}
Value accept(ExprVisitor* visitor) override{ Value accept(ExprVisitor* visitor) override{
return visitor->visitGroupingExpr(std::static_pointer_cast<GroupingExpr>(shared_from_this())); return visitor->visitGroupingExpr(std::static_pointer_cast<GroupingExpr>(shared_from_this()));
} }
@ -93,12 +92,9 @@ struct LiteralExpr : Expr
struct UnaryExpr : Expr struct UnaryExpr : Expr
{ {
const Token oper; Token oper;
std::shared_ptr<Expr> right; std::shared_ptr<Expr> right;
UnaryExpr(Token oper, std::shared_ptr<Expr> right) : oper(oper), right(right) {}
UnaryExpr(Token oper, std::shared_ptr<Expr> right) : oper(oper), right(right)
{
}
Value accept(ExprVisitor* visitor) override{ Value accept(ExprVisitor* visitor) override{
return visitor->visitUnaryExpr(std::static_pointer_cast<UnaryExpr>(shared_from_this())); return visitor->visitUnaryExpr(std::static_pointer_cast<UnaryExpr>(shared_from_this()));
} }
@ -106,30 +102,35 @@ struct UnaryExpr : Expr
struct VarExpr : Expr struct VarExpr : Expr
{ {
const Token name; Token name;
explicit VarExpr(Token name) : name(name){}; explicit VarExpr(Token name) : name(name){};
Value accept(ExprVisitor* visitor) override Value accept(ExprVisitor* visitor) override
{ {
return visitor->visitVariableExpr(std::static_pointer_cast<VarExpr>(shared_from_this())); return visitor->visitVarExpr(std::static_pointer_cast<VarExpr>(shared_from_this()));
}
};
struct FunctionExpr : Expr {
std::vector<Token> params;
std::vector<std::shared_ptr<Stmt>> body;
FunctionExpr(const std::vector<Token>& params, const std::vector<std::shared_ptr<Stmt>>& body)
: params(params), body(body) {}
Value accept(ExprVisitor* visitor) override
{
return visitor->visitFunctionExpr(std::static_pointer_cast<FunctionExpr>(shared_from_this()));
} }
}; };
struct CallExpr : Expr struct CallExpr : Expr
{ {
std::shared_ptr<Expr> callee; std::shared_ptr<Expr> callee;
const Token paren; Token paren;
std::vector<std::shared_ptr<Expr>> arguments; std::vector<std::shared_ptr<Expr>> arguments;
CallExpr(std::shared_ptr<Expr> callee, Token paren, std::vector<std::shared_ptr<Expr>> arguments) CallExpr(std::shared_ptr<Expr> callee, Token paren, std::vector<std::shared_ptr<Expr>> arguments)
: callee(callee), paren(paren), arguments(arguments) : callee(callee), paren(paren), arguments(arguments) {}
{
}
Value accept(ExprVisitor* visitor) override Value accept(ExprVisitor* visitor) override
{ {
return visitor->visitCallExpr(std::static_pointer_cast<CallExpr>(shared_from_this())); return visitor->visitCallExpr(std::static_pointer_cast<CallExpr>(shared_from_this()));
} }
}; };
////

View File

@ -6,33 +6,24 @@
#include "Environment.h" #include "Environment.h"
#include "Value.h" #include "Value.h"
#include "StdLib.h" #include "StdLib.h"
#include "ErrorReporter.h"
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include <stack> #include <stack>
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 { class Interpreter : public ExprVisitor, public StmtVisitor {
public: public:
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override; Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override; Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override; Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override; Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
Value visitVariableExpr(const std::shared_ptr<VarExpr>& expression) override; Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override; Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) override; void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) override;
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) override; void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) override;
@ -41,48 +32,40 @@ public:
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement) override; void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement) override;
void visitIfStmt(const std::shared_ptr<IfStmt>& statement) override; void visitIfStmt(const std::shared_ptr<IfStmt>& statement) override;
void interpret(std::vector<std::shared_ptr<Stmt>> statements); void interpret(std::vector<std::shared_ptr<Stmt> > statements);
explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive){ explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive), errorReporter(nullptr){
environment = std::make_shared<Environment>(); environment = std::make_shared<Environment>();
// Pre-allocate environment pool
for (size_t i = 0; i < POOL_SIZE; ++i) {
envPool.push(std::make_shared<Environment>());
}
// Add standard library functions
addStdLibFunctions();
} }
virtual ~Interpreter() = default; virtual ~Interpreter() = default;
private: private:
std::shared_ptr<Environment> environment; std::shared_ptr<Environment> environment;
bool IsInteractive; bool IsInteractive;
std::vector<std::shared_ptr<BuiltinFunction>> builtinFunctions; std::vector<std::shared_ptr<BuiltinFunction> > builtinFunctions;
std::vector<std::shared_ptr<Function>> functions; std::vector<std::shared_ptr<Function> > functions;
ErrorReporter* errorReporter;
// Environment pool for fast function calls
std::stack<std::shared_ptr<Environment>> 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<ReturnContext> returnStack;
Value evaluate(const std::shared_ptr<Expr>& expr); Value evaluate(const std::shared_ptr<Expr>& expr);
bool isEqual(Value a, Value b); bool isEqual(Value a, Value b);
bool isWholeNumer(double num); bool isWholeNumer(double num);
void execute(const std::shared_ptr<Stmt>& statement); void execute(const std::shared_ptr<Stmt>& statement);
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env); void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env);
void addStdLibFunctions(); void addStdLibFunctions();
public: public:
bool isTruthy(Value object); bool isTruthy(Value object);
std::string stringify(Value object); std::string stringify(Value object);
void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func); void addBuiltinFunction(std::shared_ptr<BuiltinFunction> func);
// Error reporting
void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
if (environment) {
environment->setErrorReporter(reporter);
}
// Add standard library functions after error reporter is set
addStdLibFunctions();
}
}; };

View File

@ -20,13 +20,19 @@ enum TokenType{
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR, AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN, 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 END_OF_FILE
}; };
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE", inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT", "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", "BANG", "BANG_EQUAL",
"EQUAL", "DOUBLE_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", "AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "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"}; "END_OF_FILE"};
const std::map<std::string, TokenType> KEYWORDS { const std::map<std::string, TokenType> KEYWORDS {
@ -63,15 +75,30 @@ struct Token
TokenType type; TokenType type;
std::string lexeme; std::string lexeme;
int line; int line;
int column;
}; };
// Forward declaration
class ErrorReporter;
class Lexer{ class Lexer{
public: public:
Lexer() : errorReporter(nullptr) {}
std::vector<Token> Tokenize(std::string source); std::vector<Token> Tokenize(std::string source);
// Set error reporter for enhanced error reporting
void setErrorReporter(ErrorReporter* reporter) {
errorReporter = reporter;
}
private: private:
int line; int line;
int column;
std::vector<char> src; std::vector<char> src;
ErrorReporter* errorReporter;
private: private:
bool matchOn(char expected); bool matchOn(char expected);

View File

@ -6,19 +6,29 @@
#include "Statement.h" #include "Statement.h"
#include "TypeWrapper.h" #include "TypeWrapper.h"
#include "helperFunctions/ShortHands.h" #include "helperFunctions/ShortHands.h"
#include "ErrorReporter.h"
class Parser class Parser
{ {
private: private:
const std::vector<Token> tokens; const std::vector<Token> tokens;
int current = 0; int current = 0;
int functionDepth = 0; // Track nesting level of functions
ErrorReporter* errorReporter = nullptr;
public: public:
explicit Parser(std::vector<Token> tokens) : tokens(std::move(tokens)){}; explicit Parser(std::vector<Token> tokens) : tokens(std::move(tokens)){};
std::vector<sptr(Stmt)> parse(); std::vector<sptr(Stmt)> parse();
void setErrorReporter(ErrorReporter* reporter) { errorReporter = reporter; }
private: private:
sptr(Expr) expression(); 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) equality();
sptr(Expr) comparison(); sptr(Expr) comparison();
sptr(Expr) term(); sptr(Expr) term();
@ -51,10 +61,16 @@ private:
std::shared_ptr<Stmt> varDeclaration(); std::shared_ptr<Stmt> varDeclaration();
std::shared_ptr<Stmt> functionDeclaration(); std::shared_ptr<Stmt> functionDeclaration();
std::shared_ptr<Expr> functionExpression();
sptr(Expr) assignment(); sptr(Expr) assignment();
std::vector<std::shared_ptr<Stmt>> block(); std::vector<std::shared_ptr<Stmt>> block();
sptr(Expr) finishCall(sptr(Expr) callee); sptr(Expr) finishCall(sptr(Expr) callee);
// Helper methods for function scope tracking
void enterFunction() { functionDepth++; }
void exitFunction() { functionDepth--; }
bool isInFunction() const { return functionDepth > 0; }
}; };

View File

@ -5,8 +5,9 @@
#include <memory> #include <memory>
class Interpreter; class Interpreter;
class ErrorReporter;
class StdLib { class StdLib {
public: public:
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter); static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
}; };

View File

@ -57,9 +57,9 @@ struct Function : public Object
struct BuiltinFunction : public Object struct BuiltinFunction : public Object
{ {
const std::string name; const std::string name;
const std::function<Value(std::vector<Value>)> func; const std::function<Value(std::vector<Value>, int, int)> func;
BuiltinFunction(std::string name, std::function<Value(std::vector<Value>)> func) BuiltinFunction(std::string name, std::function<Value(std::vector<Value>, int, int)> func)
: name(name), func(func) {} : name(name), func(func) {}
}; };

View File

@ -3,6 +3,9 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <cmath>
#include <stdexcept>
#include <algorithm>
// Forward declarations // Forward declarations
class Environment; class Environment;
@ -151,6 +154,103 @@ struct Value {
default: return "unknown"; 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<int>(other.number); ++i) {
result += string_value;
}
return Value(result);
}
if (isNumber() && other.isString()) {
std::string result;
for (int i = 0; i < static_cast<int>(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<double>(static_cast<long>(number) & static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for & operator");
}
Value operator|(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for | operator");
}
Value operator^(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for ^ operator");
}
Value operator<<(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for << operator");
}
Value operator>>(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
}
throw std::runtime_error("Invalid operands for >> operator");
}
}; };
// Global constants for common values // Global constants for common values

View File

@ -6,6 +6,7 @@
#include "../headers/Lexer.h" #include "../headers/Lexer.h"
#include "../headers/Interpreter.h" #include "../headers/Interpreter.h"
#include "../headers/helperFunctions/ShortHands.h" #include "../headers/helperFunctions/ShortHands.h"
#include "../headers/ErrorReporter.h"
#define VERSION "0.0.1" #define VERSION "0.0.1"
@ -14,23 +15,15 @@ class Bob
public: public:
Lexer lexer; Lexer lexer;
sptr(Interpreter) interpreter; sptr(Interpreter) interpreter;
ErrorReporter errorReporter;
~Bob() = default; ~Bob() = default;
public: public:
void runFile(const std::string& path); void runFile(const std::string& path);
void runPrompt(); void runPrompt();
void error(int line, const std::string& message);
private:
bool hadError = false;
private: private:
void run(std::string source); void run(std::string source);
void report(int line, std::string where, std::string message);
}; };

51
source/Environment.cpp Normal file
View File

@ -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 + "'");
}

207
source/ErrorReporter.cpp Normal file
View File

@ -0,0 +1,207 @@
#include "../headers/ErrorReporter.h"
#include <algorithm>
#include <iomanip>
#include <string>
// 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<int>(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<int>(sourceLines.size()), line + 2);
for (int i = startLine; i <= endLine; i++) {
if (i > 0 && i <= static_cast<int>(sourceLines.size())) {
int lineWidth = static_cast<int>(sourceLines[i-1].length()) + 8;
maxWidth = std::max(maxWidth, lineWidth);
}
}
int errorLineWidth = 8 + column + 1 + static_cast<int>(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<int>(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<int>(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<std::string>& callStack) {
if (callStack.empty()) return;
int maxWidth = 65;
for (const auto& func : callStack) {
int funcWidth = static_cast<int>(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<int>(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<int>(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;
}
}

View File

@ -10,6 +10,13 @@
#include "../headers/Interpreter.h" #include "../headers/Interpreter.h"
#include "../headers/helperFunctions/HelperFunctions.h" #include "../headers/helperFunctions/HelperFunctions.h"
#include <unordered_map> #include <unordered_map>
#include "../headers/Interpreter.h"
#include "../headers/StdLib.h"
#include <iostream>
#include <chrono>
#include <cmath>
#include <stdexcept>
#include <algorithm>
struct ReturnContext { struct ReturnContext {
Value returnValue; Value returnValue;
@ -19,6 +26,8 @@ struct ReturnContext {
static ReturnContext g_returnContext; static ReturnContext g_returnContext;
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) { Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
if(expr->isNull) return NONE_VALUE; if(expr->isNull) return NONE_VALUE;
if(expr->isNumber){ if(expr->isNumber){
@ -86,211 +95,366 @@ Value Interpreter::visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression)
} }
Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) {
{
Value left = evaluate(expression->left); Value left = evaluate(expression->left);
Value right = evaluate(expression->right); Value right = evaluate(expression->right);
switch (expression->oper.type) { if (left.isNumber() && right.isNumber()) {
case BANG_EQUAL: double leftNum = left.asNumber();
return Value(!isEqual(left, right)); double rightNum = right.asNumber();
case DOUBLE_EQUAL:
return Value(isEqual(left, right));
default:
;
}
if(left.isNumber() && right.isNumber())
{
double left_double = left.asNumber();
double right_double = right.asNumber();
switch (expression->oper.type) { switch (expression->oper.type) {
case GREATER: case PLUS: return Value(leftNum + rightNum);
return Value(left_double > right_double); case MINUS: return Value(leftNum - rightNum);
case GREATER_EQUAL: case SLASH: {
return Value(left_double >= right_double); if (rightNum == 0) {
case LESS: if (errorReporter) {
return Value(left_double < right_double); errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero",
case LESS_EQUAL: "Cannot divide by zero", expression->oper.lexeme);
return Value(left_double <= right_double); }
case MINUS: throw std::runtime_error("Division by zero");
return Value(left_double - right_double); }
case PLUS: return Value(leftNum / rightNum);
return Value(left_double + right_double); }
case SLASH: case STAR: return Value(leftNum * rightNum);
if(right_double == 0) throw std::runtime_error("DivisionByZeroError: Cannot divide by 0"); case PERCENT: {
return Value(left_double / right_double); if (rightNum == 0) {
case STAR: if (errorReporter) {
return Value(left_double * right_double); errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero",
case PERCENT: "Cannot perform modulo operation with zero", expression->oper.lexeme);
return Value(fmod(left_double, right_double)); }
default: throw std::runtime_error("Modulo by zero");
return NONE_VALUE; //unreachable }
} return Value(std::fmod(leftNum, rightNum));
} }
else if(left.isString() && right.isString()) case GREATER: return Value(leftNum > rightNum);
{ case GREATER_EQUAL: return Value(leftNum >= rightNum);
switch (expression->oper.type) { case LESS: return Value(leftNum < rightNum);
case PLUS: { case LESS_EQUAL: return Value(leftNum <= rightNum);
std::string left_string = left.asString(); case DOUBLE_EQUAL: return Value(leftNum == rightNum);
std::string right_string = right.asString(); case BANG_EQUAL: return Value(leftNum != rightNum);
return Value(left_string + right_string); case BIN_AND: return Value(static_cast<double>(static_cast<int>(leftNum) & static_cast<int>(rightNum)));
case BIN_OR: return Value(static_cast<double>(static_cast<int>(leftNum) | static_cast<int>(rightNum)));
case BIN_XOR: return Value(static_cast<double>(static_cast<int>(leftNum) ^ static_cast<int>(rightNum)));
case BIN_SLEFT: return Value(static_cast<double>(static_cast<int>(leftNum) << static_cast<int>(rightNum)));
case BIN_SRIGHT: return Value(static_cast<double>(static_cast<int>(leftNum) >> static_cast<int>(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(); 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<double>::epsilon())
{
ss << std::fixed << std::setprecision(0) << integral;
}
else
{
ss << std::fixed << std::setprecision(std::numeric_limits<double>::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(); std::string right_string = right.asString();
switch (expression->oper.type) { switch (expression->oper.type) {
case PLUS: { case PLUS: return Value(left_string + right_string);
// Use the same logic as stringify for consistent formatting case DOUBLE_EQUAL: return Value(left_string == right_string);
double integral = left_number; case BANG_EQUAL: return Value(left_string != right_string);
double fractional = std::modf(left_number, &integral); case AND: {
if (!isTruthy(left)) {
std::stringstream ss; return left; // Return the falsy value
if(std::abs(fractional) < std::numeric_limits<double>::epsilon()) } else {
{ return right; // Return the second value
ss << std::fixed << std::setprecision(0) << integral;
} }
else
{
ss << std::fixed << std::setprecision(std::numeric_limits<double>::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: case OR: {
if(isWholeNumer(left_number)) if (isTruthy(left)) {
{ return left; // Return the truthy value
std::string s; } else {
for (int i = 0; i < (int)left_number; ++i) { return right; // Return the second value
s += right_string;
}
return Value(s);
} }
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"); throw std::runtime_error("String multiplier must be whole number");
} }
std::string result;
for (int i = 0; i < static_cast<int>(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<int>(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(); bool left_bool = left.asBoolean();
std::string right_string = right.asString(); std::string right_string = right.asString();
switch (expression->oper.type) { switch (expression->oper.type) {
case PLUS: { case PLUS: return Value(stringify(left) + right_string);
std::string bool_str = left_bool ? "true" : "false";
return Value(bool_str + 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(); std::string left_string = left.asString();
bool right_bool = right.asBoolean(); bool right_bool = right.asBoolean();
switch (expression->oper.type) { switch (expression->oper.type) {
case PLUS: { case PLUS: return Value(left_string + stringify(right));
std::string bool_str = right_bool ? "true" : "false";
return Value(left_string + bool_str);
}
} }
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a boolean");
} }
else if(left.isString() && right.isNone())
{ if (left.isNumber() && right.isBoolean()) {
std::string left_string = left.asString(); double left_num = left.asNumber();
bool right_bool = right.asBoolean();
switch (expression->oper.type) { switch (expression->oper.type) {
case PLUS: { case AND: {
return Value(left_string + "none"); 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<int>(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<int>(left_num); i++) {
result += right.asString();
}
return Value(result);
}
}
}
if (left.isNone() && right.isString()) {
std::string right_string = right.asString(); std::string right_string = right.asString();
switch (expression->oper.type) { switch (expression->oper.type) {
case PLUS: { case PLUS: return Value("none" + right_string);
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"); throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
} }
else 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); throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme);
} }
} }
Value Interpreter::visitVariableExpr(const std::shared_ptr<VarExpr>& expression) Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
{ {
return environment->get(expression->name); return environment->get(expression->name);
} }
void Interpreter::addStdLibFunctions() { void Interpreter::addStdLibFunctions() {
// Add standard library functions to the environment // Add standard library functions to the environment
StdLib::addToEnvironment(environment, *this); StdLib::addToEnvironment(environment, *this, errorReporter);
} }
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) { void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
@ -299,6 +463,58 @@ void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) { Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
Value value = evaluate(expression->value); 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); environment->assign(expression->name, value);
return value; return value;
} }
@ -312,8 +528,8 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
} }
if (callee.isBuiltinFunction()) { if (callee.isBuiltinFunction()) {
// Builtin functions now work directly with Value // Builtin functions now work directly with Value and receive line and column
return callee.asBuiltinFunction()->func(arguments); return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column);
} }
if (callee.isFunction()) { if (callee.isFunction()) {
@ -325,13 +541,13 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
auto previousEnv = environment; auto previousEnv = environment;
environment = std::make_shared<Environment>(function->closure); environment = std::make_shared<Environment>(function->closure);
environment->setErrorReporter(errorReporter);
for (size_t i = 0; i < function->params.size(); i++) { for (size_t i = 0; i < function->params.size(); i++) {
environment->define(function->params[i], arguments[i]); environment->define(function->params[i], arguments[i]);
} }
Value returnValue = NONE_VALUE; Value returnValue = NONE_VALUE;
bool hasReturn = false;
for (const auto& stmt : function->body) { for (const auto& stmt : function->body) {
// Reset return context for each statement // Reset return context for each statement
@ -341,7 +557,6 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
execute(stmt); execute(stmt);
if (g_returnContext.hasReturn) { if (g_returnContext.hasReturn) {
returnValue = g_returnContext.returnValue; returnValue = g_returnContext.returnValue;
hasReturn = true;
break; break;
} }
} }
@ -353,8 +568,21 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
throw std::runtime_error("Can only call functions and classes."); throw std::runtime_error("Can only call functions and classes.");
} }
Value Interpreter::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) {
// Convert Token parameters to string parameters
std::vector<std::string> 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<BlockStmt>& statement) { void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) {
auto newEnv = std::make_shared<Environment>(environment); auto newEnv = std::make_shared<Environment>(environment);
newEnv->setErrorReporter(errorReporter);
executeBlock(statement->statements, newEnv); executeBlock(statement->statements, newEnv);
} }
@ -457,6 +685,16 @@ bool Interpreter::isTruthy(Value object) {
return false; return false;
} }
if(object.isNumber())
{
return object.asNumber() != 0;
}
if(object.isString())
{
return object.asString().length() > 0;
}
return true; return true;
} }

View File

@ -1,4 +1,5 @@
#include "../headers/Lexer.h" #include "../headers/Lexer.h"
#include "../headers/ErrorReporter.h"
#include "../headers/helperFunctions/HelperFunctions.h" #include "../headers/helperFunctions/HelperFunctions.h"
#include <cctype> #include <cctype>
#include <stdexcept> #include <stdexcept>
@ -8,69 +9,94 @@ using namespace std;
std::vector<Token> Lexer::Tokenize(std::string source){ std::vector<Token> Lexer::Tokenize(std::string source){
std::vector<Token> tokens; std::vector<Token> tokens;
src = std::vector<char>{source.begin(), source.end()}; src = std::vector<char>{source.begin(), source.end()};
line = 0; line = 1;
column = 1;
while(!src.empty()) while(!src.empty())
{ {
char t = src[0]; char t = src[0];
if(t == '(') 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(); advance();
} }
else if(t == ')') 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(); advance();
} }
else if(t == '{') 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(); advance();
} }
else if(t == '}') 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(); advance();
} }
else if(t == ',') 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(); advance();
} }
else if(t == '.') 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(); advance();
} }
else if(t == ';') 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(); advance();
} }
else if(t == '+') else if(t == '+')
{ {
tokens.push_back(Token{PLUS, std::string(1, t), line}); std::string token = std::string(1, t);
advance(); 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 == '-') else if(t == '-')
{ {
tokens.push_back(Token{MINUS, std::string(1, t), line}); std::string token = std::string(1, t);
advance(); 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 == '*') else if(t == '*')
{ {
tokens.push_back(Token{STAR, std::string(1, t), line}); std::string token = std::string(1, t);
advance(); 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 == '%') else if(t == '%')
{ {
tokens.push_back(Token{PERCENT, std::string(1, t), line}); std::string token = std::string(1, t);
advance(); 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 == '~') 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(); advance();
} }
else if(t == '=') else if(t == '=')
@ -79,7 +105,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
advance(); advance();
bool match = matchOn('='); bool match = matchOn('=');
token += match ? "=" : ""; 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<int>(token.length())});
} }
else if(t == '!') else if(t == '!')
{ {
@ -87,7 +113,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
advance(); advance();
bool match = matchOn('='); bool match = matchOn('=');
token += match ? "=" : ""; 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<int>(token.length())});
} }
else if(t == '<') else if(t == '<')
{ {
@ -95,15 +121,20 @@ std::vector<Token> Lexer::Tokenize(std::string source){
advance(); advance();
if(matchOn('=')) if(matchOn('='))
{ {
tokens.push_back(Token{LESS_EQUAL, "<=", line}); tokens.push_back(Token{LESS_EQUAL, "<=", line, column - 1});
} }
else if(matchOn('<')) 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 else
{ {
tokens.push_back(Token{LESS, token, line}); tokens.push_back(Token{LESS, token, line, column - static_cast<int>(token.length())});
} }
} }
@ -111,17 +142,66 @@ std::vector<Token> Lexer::Tokenize(std::string source){
{ {
std::string token = std::string(1, t); std::string token = std::string(1, t);
advance(); advance();
bool match = matchOn('='); if(matchOn('='))
token += match ? "=" : ""; {
tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token, line}); 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<int>(token.length())});
}
} }
else if(t == '&') else if(t == '&')
{ {
std::string token = std::string(1, t); std::string token = std::string(1, t);
advance(); advance();
bool match = matchOn('&'); bool match = matchOn('&');
token += match ? "&" : ""; if(match) {
if(match) tokens.push_back(Token{AND, token, line}); 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 == '/') else if(t == '/')
{ {
@ -137,13 +217,19 @@ std::vector<Token> Lexer::Tokenize(std::string source){
} }
else 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 == '"') else if(t == '"')
{ {
bool last_was_escape = false; bool last_was_escape = false;
std::string str; std::string str;
int startColumn = column;
advance(); advance();
while(!src.empty()) while(!src.empty())
@ -156,7 +242,6 @@ std::vector<Token> Lexer::Tokenize(std::string source){
next_c = "\\" + std::string(1, src[0]); next_c = "\\" + std::string(1, src[0]);
} }
if(next_c == "\n") line++;
str += next_c; str += next_c;
advance(); advance();
} }
@ -172,14 +257,13 @@ std::vector<Token> Lexer::Tokenize(std::string source){
std::string escaped_str = parseEscapeCharacters(str); 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') else if(t == '\n')
{ {
line++;
advance(); advance();
} }
else else
@ -191,6 +275,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
if(std::isdigit(t)) if(std::isdigit(t))
{ {
std::string num; std::string num;
int startColumn = column;
if(src[0] != '0') notationInvalidated = true; if(src[0] != '0') notationInvalidated = true;
@ -260,11 +345,12 @@ std::vector<Token> 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)) else if(std::isalpha(t))
{ {
std::string ident; std::string ident;
int startColumn = column;
while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_')) while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_'))
{ {
ident += src[0]; ident += src[0];
@ -273,11 +359,11 @@ std::vector<Token> Lexer::Tokenize(std::string source){
if(KEYWORDS.find(ident) != KEYWORDS.end()) //identifier is a keyword 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 else
{ {
tokens.push_back(Token{IDENTIFIER, ident, line}); tokens.push_back(Token{IDENTIFIER, ident, line, startColumn});
} }
} }
@ -287,7 +373,10 @@ std::vector<Token> Lexer::Tokenize(std::string source){
} }
else 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) + "'"); 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) void Lexer::advance(int by)
{ {
for (int i = 0; i < by; ++i) { 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++;
}
}
} }
} }

View File

@ -2,6 +2,7 @@
// Created by Bobby Lucero on 5/26/23. // Created by Bobby Lucero on 5/26/23.
// //
#include "../headers/Parser.h" #include "../headers/Parser.h"
#include <stdexcept>
// Precedence // Precedence
@ -13,25 +14,110 @@ sptr(Expr) Parser::expression()
return assignment(); 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(); 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(); sptr(Expr) value = assignment();
if(std::dynamic_pointer_cast<VarExpr>(expr)) if(std::dynamic_pointer_cast<VarExpr>(expr))
{ {
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name; Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
return msptr(AssignExpr)(name, op, value);
return msptr(AssignExpr)(name, value);
} }
if (errorReporter) {
errorReporter->reportError(op.line, op.column, "Parse Error",
"Invalid assignment target", "");
}
throw std::runtime_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) Parser::comparison()
{ {
sptr(Expr) expr = term(); sptr(Expr) expr = bitwise_or();
while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL})) while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL}))
{ {
Token op = previous(); Token op = previous();
sptr(Expr) right = term(); sptr(Expr) right = bitwise_or();
expr = msptr(BinaryExpr)(expr, op, right); expr = msptr(BinaryExpr)(expr, op, right);
} }
@ -132,6 +218,14 @@ sptr(Expr) Parser::primary()
return msptr(GroupingExpr)(expr); 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)); 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(CLOSE_PAREN, "Expected ')' after parameters.");
consume(OPEN_BRACE, "Expected '{' before function body."); consume(OPEN_BRACE, "Expected '{' before function body.");
// Enter function scope
enterFunction();
std::vector<sptr(Stmt)> body = block(); std::vector<sptr(Stmt)> body = block();
// Exit function scope
exitFunction();
return msptr(FunctionStmt)(name, parameters, body); return msptr(FunctionStmt)(name, parameters, body);
} }
std::shared_ptr<Expr> Parser::functionExpression() {
consume(OPEN_PAREN, "Expect '(' after 'func'.");
std::vector<Token> 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<std::shared_ptr<Stmt>> body = block();
// Exit function scope
exitFunction();
return msptr(FunctionExpr)(parameters, body);
}
sptr(Stmt) Parser::statement() sptr(Stmt) Parser::statement()
{ {
if(match({RETURN})) return returnStatement(); if(match({RETURN})) return returnStatement();
@ -225,6 +355,16 @@ sptr(Stmt) Parser::ifStatement()
sptr(Stmt) Parser::returnStatement() sptr(Stmt) Parser::returnStatement()
{ {
Token keyword = previous(); 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); sptr(Expr) value = msptr(LiteralExpr)("none", false, true, false);
if (!check(SEMICOLON)) { if (!check(SEMICOLON)) {
@ -310,6 +450,24 @@ Token Parser::previous() {
Token Parser::consume(TokenType type, const std::string& message) { Token Parser::consume(TokenType type, const std::string& message) {
if(check(type)) return advance(); 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); throw std::runtime_error("Unexpected symbol '" + peek().lexeme +"': "+ message);
} }

View File

@ -1,12 +1,17 @@
#include "../headers/StdLib.h" #include "../headers/StdLib.h"
#include "../headers/Interpreter.h" #include "../headers/Interpreter.h"
#include "../headers/ErrorReporter.h"
#include <chrono> #include <chrono>
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter) { void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
// Create a built-in toString function // Create a built-in toString function
auto toStringFunc = std::make_shared<BuiltinFunction>("toString", auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
[&interpreter](std::vector<Value> args) -> Value { [&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) { 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()) + "."); throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
} }
@ -19,8 +24,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
// Create a built-in print function // Create a built-in print function
auto printFunc = std::make_shared<BuiltinFunction>("print", auto printFunc = std::make_shared<BuiltinFunction>("print",
[&interpreter](std::vector<Value> args) -> Value { [&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) { 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()) + "."); throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
} }
// Use the interpreter's stringify function // Use the interpreter's stringify function
@ -34,8 +43,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
// Create a built-in assert function // Create a built-in assert function
auto assertFunc = std::make_shared<BuiltinFunction>("assert", auto assertFunc = std::make_shared<BuiltinFunction>("assert",
[](std::vector<Value> args) -> Value { [errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1 && args.size() != 2) { 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()) + "."); 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<Environment> env, Interpreter& int
message += " - " + std::string(args[1].asString()); message += " - " + std::string(args[1].asString());
} }
} }
if (errorReporter) {
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
}
throw std::runtime_error(message); throw std::runtime_error(message);
} }
@ -68,8 +84,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
// Create a built-in time function (returns microseconds since Unix epoch) // Create a built-in time function (returns microseconds since Unix epoch)
auto timeFunc = std::make_shared<BuiltinFunction>("time", auto timeFunc = std::make_shared<BuiltinFunction>("time",
[](std::vector<Value> args) -> Value { [errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 0) { 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()) + "."); throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
} }
@ -86,8 +106,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
// Create a built-in input function // Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input", auto inputFunc = std::make_shared<BuiltinFunction>("input",
[&interpreter](std::vector<Value> args) -> Value { [&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() > 1) { 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()) + "."); 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<Environment> env, Interpreter& int
// Create a built-in type function // Create a built-in type function
auto typeFunc = std::make_shared<BuiltinFunction>("type", auto typeFunc = std::make_shared<BuiltinFunction>("type",
[](std::vector<Value> args) -> Value { [errorReporter](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) { 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()) + "."); throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
} }
@ -140,13 +168,13 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
// Create a built-in toNumber function for string-to-number conversion // Create a built-in toNumber function for string-to-number conversion
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber", auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
[](std::vector<Value> args) -> Value { [](std::vector<Value> args, int line, int column) -> Value {
if (args.size() != 1) { 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()) { 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(); std::string str = args[0].asString();
@ -156,20 +184,58 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
str.erase(str.find_last_not_of(" \t\n\r") + 1); str.erase(str.find_last_not_of(" \t\n\r") + 1);
if (str.empty()) { if (str.empty()) {
throw std::runtime_error("Cannot convert empty string to number."); return NONE_VALUE; // Return none for empty string
} }
try { try {
double value = std::stod(str); double value = std::stod(str);
return Value(value); return Value(value);
} catch (const std::invalid_argument&) { } 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&) { } 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())); env->define("toNumber", Value(toNumberFunc.get()));
// Store the shared_ptr in the interpreter to keep it alive // Store the shared_ptr in the interpreter to keep it alive
interpreter.addBuiltinFunction(toNumberFunc); interpreter.addBuiltinFunction(toNumberFunc);
// Create a built-in toBoolean function for explicit boolean conversion
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
[errorReporter](std::vector<Value> 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);
} }

View File

@ -20,6 +20,12 @@ void Bob::runFile(const string& path)
return; return;
} }
// Load source code into error reporter for context
errorReporter.loadSource(source, path);
// Connect error reporter to interpreter
interpreter->setErrorReporter(&errorReporter);
this->run(source); this->run(source);
} }
@ -39,42 +45,49 @@ void Bob::runPrompt()
break; break;
} }
// Load source code into error reporter for context
errorReporter.loadSource(line, "REPL");
// Connect error reporter to interpreter
interpreter->setErrorReporter(&errorReporter);
this->run(line); this->run(line);
hadError = false;
} }
} }
void Bob::error(int line, const string& message)
{
}
void Bob::run(string source) void Bob::run(string source)
{ {
try { try {
// Connect error reporter to lexer
lexer.setErrorReporter(&errorReporter);
vector<Token> tokens = lexer.Tokenize(std::move(source)); vector<Token> tokens = lexer.Tokenize(std::move(source));
Parser p(tokens); Parser p(tokens);
// Connect error reporter to parser
p.setErrorReporter(&errorReporter);
vector<sptr(Stmt)> statements = p.parse(); vector<sptr(Stmt)> statements = p.parse();
interpreter->interpret(statements); interpreter->interpret(statements);
} }
catch(std::exception &e) 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; return;
} }
catch(...) 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; return;
} }
}
void Bob::report(int line, string where, string message)
{
hadError = true;
} }

View File

@ -756,7 +756,7 @@ assert(hex3 == 4294967295, "Hex FFFFFFFF should be 4294967295");
var add_num = 5 + 3; var add_num = 5 + 3;
var sub_num = 10 - 4; var sub_num = 10 - 4;
var mul_num = 6 * 7; 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(add_num == 8, "5 + 3 should be 8");
assert(sub_num == 6, "10 - 4 should be 6"); assert(sub_num == 6, "10 - 4 should be 6");
assert(mul_num == 42, "6 * 7 should be 42"); 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"); 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 // FINAL SUMMARY
// ======================================== // ========================================
@ -1176,7 +1586,14 @@ print("- toString function (universal string conversion)");
print("- Enhanced print function (works with all object types)"); print("- Enhanced print function (works with all object types)");
print("- Redefinable functions (including built-in function override)"); print("- Redefinable functions (including built-in function override)");
print("- Recursion (factorial, fibonacci, mutual recursion, deep recursion)"); 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("\n🎉 ALL TESTS PASSED! 🎉");
print("Bob language is working correctly!"); print("Bob language is working correctly!");
print("Ready for next phase: Control Flow (if statements, while loops)"); print("Ready for next phase: Control Flow (while loops, data structures)");

View File

@ -10,3 +10,6 @@ print("Fibonacci test:");
var fib_result = fib(30); var fib_result = fib(30);
print("Result: " + fib_result); print("Result: " + fib_result);
print(10 / 0);