Fixed performance, added enhanced error reporting, anon funcs, toBoolean, various other things
This commit is contained in:
parent
1e65b344ae
commit
adb00d496f
@ -6,39 +6,32 @@
|
||||
#include "Value.h"
|
||||
#include "Lexer.h"
|
||||
|
||||
// Forward declaration
|
||||
class ErrorReporter;
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
Environment() : parent(nullptr) {}
|
||||
Environment(std::shared_ptr<Environment> parent_env) : parent(parent_env) {}
|
||||
Environment() : parent(nullptr), errorReporter(nullptr) {}
|
||||
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
|
||||
inline void define(const std::string& name, const Value& value) {
|
||||
variables[name] = value;
|
||||
}
|
||||
|
||||
// Optimized assign with inline
|
||||
inline void assign(const Token& name, const Value& value) {
|
||||
auto it = variables.find(name.lexeme);
|
||||
if (it != variables.end()) {
|
||||
it->second = value;
|
||||
} else if (parent != nullptr) {
|
||||
parent->assign(name, value);
|
||||
} else {
|
||||
throw std::runtime_error("Undefined variable '" + name.lexeme + "'.");
|
||||
}
|
||||
}
|
||||
// Enhanced assign with error reporting
|
||||
void assign(const Token& name, const Value& value);
|
||||
|
||||
// Optimized get with inline and move semantics
|
||||
inline Value get(const Token& name) {
|
||||
auto it = variables.find(name.lexeme);
|
||||
if (it != variables.end()) {
|
||||
return it->second; // Return by value (will use move if possible)
|
||||
}
|
||||
if (parent != nullptr) {
|
||||
return parent->get(name);
|
||||
}
|
||||
throw std::runtime_error("Undefined variable '" + name.lexeme + "'.");
|
||||
}
|
||||
// Enhanced get with error reporting
|
||||
Value get(const Token& name);
|
||||
|
||||
// Get by string name with error reporting
|
||||
Value get(const std::string& name);
|
||||
|
||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||
inline void clear() { variables.clear(); }
|
||||
@ -46,4 +39,5 @@ public:
|
||||
private:
|
||||
std::unordered_map<std::string, Value> variables;
|
||||
std::shared_ptr<Environment> parent;
|
||||
ErrorReporter* errorReporter;
|
||||
};
|
||||
63
headers/ErrorReporter.h
Normal file
63
headers/ErrorReporter.h
Normal 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);
|
||||
};
|
||||
@ -10,6 +10,10 @@
|
||||
#include "TypeWrapper.h"
|
||||
#include "Value.h"
|
||||
|
||||
// Forward declarations
|
||||
struct FunctionExpr;
|
||||
struct ExprVisitor;
|
||||
|
||||
struct AssignExpr;
|
||||
struct BinaryExpr;
|
||||
struct GroupingExpr;
|
||||
@ -23,14 +27,14 @@ struct ExprVisitor
|
||||
{
|
||||
virtual Value visitAssignExpr(const std::shared_ptr<AssignExpr>& 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 visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
||||
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
|
||||
virtual Value visitVariableExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
||||
virtual Value visitCallExpr(const std::shared_ptr<CallExpr>& expr) = 0;
|
||||
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct Expr : public std::enable_shared_from_this<Expr> {
|
||||
virtual Value accept(ExprVisitor* visitor) = 0;
|
||||
virtual ~Expr() = default;
|
||||
@ -39,11 +43,10 @@ struct Expr : public std::enable_shared_from_this<Expr> {
|
||||
struct AssignExpr : Expr
|
||||
{
|
||||
const Token name;
|
||||
const Token op;
|
||||
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
|
||||
{
|
||||
return visitor->visitAssignExpr(std::static_pointer_cast<AssignExpr>(shared_from_this()));
|
||||
@ -57,9 +60,7 @@ struct BinaryExpr : Expr
|
||||
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{
|
||||
return visitor->visitBinaryExpr(std::static_pointer_cast<BinaryExpr>(shared_from_this()));
|
||||
}
|
||||
@ -69,9 +70,7 @@ struct GroupingExpr : Expr
|
||||
{
|
||||
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{
|
||||
return visitor->visitGroupingExpr(std::static_pointer_cast<GroupingExpr>(shared_from_this()));
|
||||
}
|
||||
@ -93,12 +92,9 @@ struct LiteralExpr : Expr
|
||||
|
||||
struct UnaryExpr : Expr
|
||||
{
|
||||
const Token oper;
|
||||
Token oper;
|
||||
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{
|
||||
return visitor->visitUnaryExpr(std::static_pointer_cast<UnaryExpr>(shared_from_this()));
|
||||
}
|
||||
@ -106,30 +102,35 @@ struct UnaryExpr : Expr
|
||||
|
||||
struct VarExpr : Expr
|
||||
{
|
||||
const Token name;
|
||||
Token name;
|
||||
explicit VarExpr(Token name) : name(name){};
|
||||
Value accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitVariableExpr(std::static_pointer_cast<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
|
||||
{
|
||||
std::shared_ptr<Expr> callee;
|
||||
const Token paren;
|
||||
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
|
||||
{
|
||||
return visitor->visitCallExpr(std::static_pointer_cast<CallExpr>(shared_from_this()));
|
||||
}
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
|
||||
@ -6,33 +6,24 @@
|
||||
#include "Environment.h"
|
||||
#include "Value.h"
|
||||
#include "StdLib.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#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 {
|
||||
|
||||
public:
|
||||
Value visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression) override;
|
||||
Value visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
||||
Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expression) override;
|
||||
Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expression) override;
|
||||
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
||||
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
||||
Value 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 visitCallExpr(const std::shared_ptr<CallExpr>& expression) override;
|
||||
|
||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) override;
|
||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) override;
|
||||
@ -43,16 +34,8 @@ public:
|
||||
|
||||
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>();
|
||||
|
||||
// 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;
|
||||
|
||||
@ -61,18 +44,7 @@ private:
|
||||
bool IsInteractive;
|
||||
std::vector<std::shared_ptr<BuiltinFunction> > builtinFunctions;
|
||||
std::vector<std::shared_ptr<Function> > functions;
|
||||
|
||||
// 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;
|
||||
ErrorReporter* errorReporter;
|
||||
|
||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
||||
bool isEqual(Value a, Value b);
|
||||
@ -85,4 +57,15 @@ public:
|
||||
bool isTruthy(Value object);
|
||||
std::string stringify(Value object);
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
@ -20,13 +20,19 @@ enum TokenType{
|
||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
|
||||
|
||||
// Compound assignment operators
|
||||
PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||
|
||||
// Compound bitwise assignment operators
|
||||
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL,
|
||||
|
||||
END_OF_FILE
|
||||
};
|
||||
|
||||
inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "CLOSE_BRACE",
|
||||
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", "PERCENT",
|
||||
|
||||
"BINARY_OP",
|
||||
"BIN_OR", "BIN_AND", "BIN_NOT", "BIN_XOR", "BIN_SLEFT", "BIN_SRIGHT",
|
||||
|
||||
"BANG", "BANG_EQUAL",
|
||||
"EQUAL", "DOUBLE_EQUAL",
|
||||
@ -38,6 +44,12 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
|
||||
|
||||
// Compound assignment operators
|
||||
"PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL",
|
||||
|
||||
// Compound bitwise assignment operators
|
||||
"BIN_AND_EQUAL", "BIN_OR_EQUAL", "BIN_XOR_EQUAL", "BIN_SLEFT_EQUAL", "BIN_SRIGHT_EQUAL",
|
||||
|
||||
"END_OF_FILE"};
|
||||
|
||||
const std::map<std::string, TokenType> KEYWORDS {
|
||||
@ -63,15 +75,30 @@ struct Token
|
||||
TokenType type;
|
||||
std::string lexeme;
|
||||
int line;
|
||||
int column;
|
||||
};
|
||||
|
||||
|
||||
// Forward declaration
|
||||
class ErrorReporter;
|
||||
|
||||
class Lexer{
|
||||
public:
|
||||
Lexer() : errorReporter(nullptr) {}
|
||||
|
||||
std::vector<Token> Tokenize(std::string source);
|
||||
|
||||
// Set error reporter for enhanced error reporting
|
||||
void setErrorReporter(ErrorReporter* reporter) {
|
||||
errorReporter = reporter;
|
||||
}
|
||||
|
||||
private:
|
||||
int line;
|
||||
int column;
|
||||
std::vector<char> src;
|
||||
ErrorReporter* errorReporter;
|
||||
|
||||
private:
|
||||
bool matchOn(char expected);
|
||||
|
||||
|
||||
@ -6,19 +6,29 @@
|
||||
#include "Statement.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
class Parser
|
||||
{
|
||||
private:
|
||||
const std::vector<Token> tokens;
|
||||
int current = 0;
|
||||
int functionDepth = 0; // Track nesting level of functions
|
||||
ErrorReporter* errorReporter = nullptr;
|
||||
|
||||
public:
|
||||
explicit Parser(std::vector<Token> tokens) : tokens(std::move(tokens)){};
|
||||
std::vector<sptr(Stmt)> parse();
|
||||
void setErrorReporter(ErrorReporter* reporter) { errorReporter = reporter; }
|
||||
|
||||
private:
|
||||
sptr(Expr) expression();
|
||||
sptr(Expr) logical_or();
|
||||
sptr(Expr) logical_and();
|
||||
sptr(Expr) bitwise_or();
|
||||
sptr(Expr) bitwise_xor();
|
||||
sptr(Expr) bitwise_and();
|
||||
sptr(Expr) shift();
|
||||
sptr(Expr) equality();
|
||||
sptr(Expr) comparison();
|
||||
sptr(Expr) term();
|
||||
@ -51,10 +61,16 @@ private:
|
||||
std::shared_ptr<Stmt> varDeclaration();
|
||||
|
||||
std::shared_ptr<Stmt> functionDeclaration();
|
||||
std::shared_ptr<Expr> functionExpression();
|
||||
|
||||
sptr(Expr) assignment();
|
||||
|
||||
std::vector<std::shared_ptr<Stmt>> block();
|
||||
|
||||
sptr(Expr) finishCall(sptr(Expr) callee);
|
||||
|
||||
// Helper methods for function scope tracking
|
||||
void enterFunction() { functionDepth++; }
|
||||
void exitFunction() { functionDepth--; }
|
||||
bool isInFunction() const { return functionDepth > 0; }
|
||||
};
|
||||
@ -5,8 +5,9 @@
|
||||
#include <memory>
|
||||
|
||||
class Interpreter;
|
||||
class ErrorReporter;
|
||||
|
||||
class StdLib {
|
||||
public:
|
||||
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter);
|
||||
static void addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter = nullptr);
|
||||
};
|
||||
@ -57,9 +57,9 @@ struct Function : public Object
|
||||
struct BuiltinFunction : public Object
|
||||
{
|
||||
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) {}
|
||||
};
|
||||
|
||||
|
||||
100
headers/Value.h
100
headers/Value.h
@ -3,6 +3,9 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
// Forward declarations
|
||||
class Environment;
|
||||
@ -151,6 +154,103 @@ struct Value {
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Arithmetic operators
|
||||
Value operator+(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number + other.number);
|
||||
}
|
||||
if (isString() && other.isString()) {
|
||||
return Value(string_value + other.string_value);
|
||||
}
|
||||
if (isString() && other.isNumber()) {
|
||||
return Value(string_value + other.toString());
|
||||
}
|
||||
if (isNumber() && other.isString()) {
|
||||
return Value(toString() + other.string_value);
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for + operator");
|
||||
}
|
||||
|
||||
Value operator-(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number - other.number);
|
||||
}
|
||||
throw std::runtime_error("Invalid operands for - operator");
|
||||
}
|
||||
|
||||
Value operator*(const Value& other) const {
|
||||
if (isNumber() && other.isNumber()) {
|
||||
return Value(number * other.number);
|
||||
}
|
||||
if (isString() && other.isNumber()) {
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<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
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/helperFunctions/ShortHands.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
|
||||
#define VERSION "0.0.1"
|
||||
|
||||
@ -14,23 +15,15 @@ class Bob
|
||||
public:
|
||||
Lexer lexer;
|
||||
sptr(Interpreter) interpreter;
|
||||
ErrorReporter errorReporter;
|
||||
|
||||
~Bob() = default;
|
||||
|
||||
public:
|
||||
void runFile(const std::string& path);
|
||||
|
||||
void runPrompt();
|
||||
|
||||
void error(int line, const std::string& message);
|
||||
|
||||
|
||||
private:
|
||||
bool hadError = false;
|
||||
|
||||
private:
|
||||
void run(std::string source);
|
||||
|
||||
void report(int line, std::string where, std::string message);
|
||||
};
|
||||
|
||||
|
||||
51
source/Environment.cpp
Normal file
51
source/Environment.cpp
Normal 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
207
source/ErrorReporter.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,13 @@
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||
#include <unordered_map>
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/StdLib.h"
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
struct ReturnContext {
|
||||
Value returnValue;
|
||||
@ -19,6 +26,8 @@ struct ReturnContext {
|
||||
|
||||
static ReturnContext g_returnContext;
|
||||
|
||||
|
||||
|
||||
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||
if(expr->isNull) return NONE_VALUE;
|
||||
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 right = evaluate(expression->right);
|
||||
|
||||
if (left.isNumber() && right.isNumber()) {
|
||||
double leftNum = left.asNumber();
|
||||
double rightNum = right.asNumber();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case BANG_EQUAL:
|
||||
return Value(!isEqual(left, right));
|
||||
case DOUBLE_EQUAL:
|
||||
return Value(isEqual(left, right));
|
||||
default:
|
||||
;
|
||||
case PLUS: return Value(leftNum + rightNum);
|
||||
case MINUS: return Value(leftNum - rightNum);
|
||||
case SLASH: {
|
||||
if (rightNum == 0) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Division by Zero",
|
||||
"Cannot divide by zero", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Division by zero");
|
||||
}
|
||||
return Value(leftNum / rightNum);
|
||||
}
|
||||
case STAR: return Value(leftNum * rightNum);
|
||||
case PERCENT: {
|
||||
if (rightNum == 0) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Modulo by Zero",
|
||||
"Cannot perform modulo operation with zero", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Modulo by zero");
|
||||
}
|
||||
return Value(std::fmod(leftNum, rightNum));
|
||||
}
|
||||
case GREATER: return Value(leftNum > rightNum);
|
||||
case GREATER_EQUAL: return Value(leftNum >= rightNum);
|
||||
case LESS: return Value(leftNum < rightNum);
|
||||
case LESS_EQUAL: return Value(leftNum <= rightNum);
|
||||
case DOUBLE_EQUAL: return Value(leftNum == rightNum);
|
||||
case BANG_EQUAL: return Value(leftNum != rightNum);
|
||||
case BIN_AND: return Value(static_cast<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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(left.isNumber() && right.isNumber())
|
||||
{
|
||||
double left_double = left.asNumber();
|
||||
double right_double = right.asNumber();
|
||||
switch (expression->oper.type) {
|
||||
case GREATER:
|
||||
return Value(left_double > right_double);
|
||||
case GREATER_EQUAL:
|
||||
return Value(left_double >= right_double);
|
||||
case LESS:
|
||||
return Value(left_double < right_double);
|
||||
case LESS_EQUAL:
|
||||
return Value(left_double <= right_double);
|
||||
case MINUS:
|
||||
return Value(left_double - right_double);
|
||||
case PLUS:
|
||||
return Value(left_double + right_double);
|
||||
case SLASH:
|
||||
if(right_double == 0) throw std::runtime_error("DivisionByZeroError: Cannot divide by 0");
|
||||
return Value(left_double / right_double);
|
||||
case STAR:
|
||||
return Value(left_double * right_double);
|
||||
case PERCENT:
|
||||
return Value(fmod(left_double, right_double));
|
||||
default:
|
||||
return NONE_VALUE; //unreachable
|
||||
}
|
||||
}
|
||||
else if(left.isString() && right.isString())
|
||||
{
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
if (left.isString() && right.isString()) {
|
||||
std::string left_string = left.asString();
|
||||
std::string right_string = right.asString();
|
||||
return Value(left_string + right_string);
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: return Value(left_string + right_string);
|
||||
case DOUBLE_EQUAL: return Value(left_string == right_string);
|
||||
case BANG_EQUAL: return Value(left_string != right_string);
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
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");
|
||||
|
||||
}
|
||||
else if(left.isString() && right.isNumber())
|
||||
{
|
||||
}
|
||||
|
||||
if (left.isString() && right.isNumber()) {
|
||||
std::string left_string = left.asString();
|
||||
double right_number = right.asNumber();
|
||||
double right_num = 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;
|
||||
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);
|
||||
}
|
||||
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");
|
||||
}
|
||||
std::string result;
|
||||
for (int i = 0; i < static_cast<int>(right_num); i++) {
|
||||
result += left_string;
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a number");
|
||||
return Value(result);
|
||||
}
|
||||
else if(left.isNumber() && right.isString())
|
||||
{
|
||||
double left_number = left.asNumber();
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isNumber() && right.isString()) {
|
||||
double left_num = left.asNumber();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
// Use the same logic as stringify for consistent formatting
|
||||
double integral = left_number;
|
||||
double fractional = std::modf(left_number, &integral);
|
||||
|
||||
std::stringstream ss;
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
ss << std::fixed << std::setprecision(0) << integral;
|
||||
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);
|
||||
}
|
||||
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:
|
||||
if(isWholeNumer(left_number))
|
||||
{
|
||||
std::string s;
|
||||
for (int i = 0; i < (int)left_number; ++i) {
|
||||
s += right_string;
|
||||
}
|
||||
return Value(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a number and a string");
|
||||
return Value(result);
|
||||
}
|
||||
else if(left.isBoolean() && right.isString())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (left.isBoolean() && right.isBoolean()) {
|
||||
bool left_bool = left.asBoolean();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case AND: return Value(left_bool && right_bool);
|
||||
case OR: return Value(left_bool || right_bool);
|
||||
case DOUBLE_EQUAL: return Value(left_bool == right_bool);
|
||||
case BANG_EQUAL: return Value(left_bool != right_bool);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (left.isBoolean() && right.isString()) {
|
||||
bool left_bool = left.asBoolean();
|
||||
std::string right_string = right.asString();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
std::string bool_str = left_bool ? "true" : "false";
|
||||
return Value(bool_str + right_string);
|
||||
case PLUS: return Value(stringify(left) + right_string);
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a boolean and a string");
|
||||
}
|
||||
else if(left.isString() && right.isBoolean())
|
||||
{
|
||||
|
||||
if (left.isString() && right.isBoolean()) {
|
||||
std::string left_string = left.asString();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
std::string bool_str = right_bool ? "true" : "false";
|
||||
return Value(left_string + bool_str);
|
||||
case PLUS: return Value(left_string + stringify(right));
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a boolean");
|
||||
}
|
||||
else if(left.isString() && right.isNone())
|
||||
{
|
||||
std::string left_string = left.asString();
|
||||
|
||||
if (left.isNumber() && right.isBoolean()) {
|
||||
double left_num = left.asNumber();
|
||||
bool right_bool = right.asBoolean();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
return Value(left_string + "none");
|
||||
case AND: {
|
||||
if (!isTruthy(left)) {
|
||||
return left; // Return the falsy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none");
|
||||
case OR: {
|
||||
if (isTruthy(left)) {
|
||||
return left; // Return the truthy value
|
||||
} else {
|
||||
return right; // Return the second value
|
||||
}
|
||||
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();
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
return Value("none" + right_string);
|
||||
case PLUS: return Value("none" + right_string);
|
||||
}
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||
"Cannot use '" + expression->oper.lexeme + "' on none and a string", expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||
"Operands must be of same type when using: " + expression->oper.lexeme, expression->oper.lexeme);
|
||||
}
|
||||
throw std::runtime_error("Operands must be of same type when using: " + expression->oper.lexeme);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Value Interpreter::visitVariableExpr(const std::shared_ptr<VarExpr>& expression)
|
||||
Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
||||
{
|
||||
return environment->get(expression->name);
|
||||
}
|
||||
|
||||
void Interpreter::addStdLibFunctions() {
|
||||
// Add standard library functions to the environment
|
||||
StdLib::addToEnvironment(environment, *this);
|
||||
StdLib::addToEnvironment(environment, *this, errorReporter);
|
||||
}
|
||||
|
||||
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||
@ -299,6 +463,58 @@ void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||
|
||||
Value Interpreter::visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) {
|
||||
Value value = evaluate(expression->value);
|
||||
|
||||
switch (expression->op.type) {
|
||||
case PLUS_EQUAL:
|
||||
case MINUS_EQUAL:
|
||||
case STAR_EQUAL:
|
||||
case SLASH_EQUAL:
|
||||
case PERCENT_EQUAL:
|
||||
case BIN_AND_EQUAL:
|
||||
case BIN_OR_EQUAL:
|
||||
case BIN_XOR_EQUAL:
|
||||
case BIN_SLEFT_EQUAL:
|
||||
case BIN_SRIGHT_EQUAL: {
|
||||
Value currentValue = environment->get(expression->name.lexeme);
|
||||
switch (expression->op.type) {
|
||||
case PLUS_EQUAL:
|
||||
value = currentValue + value;
|
||||
break;
|
||||
case MINUS_EQUAL:
|
||||
value = currentValue - value;
|
||||
break;
|
||||
case STAR_EQUAL:
|
||||
value = currentValue * value;
|
||||
break;
|
||||
case SLASH_EQUAL:
|
||||
value = currentValue / value;
|
||||
break;
|
||||
case PERCENT_EQUAL:
|
||||
value = currentValue % value;
|
||||
break;
|
||||
case BIN_AND_EQUAL:
|
||||
value = currentValue & value;
|
||||
break;
|
||||
case BIN_OR_EQUAL:
|
||||
value = currentValue | value;
|
||||
break;
|
||||
case BIN_XOR_EQUAL:
|
||||
value = currentValue ^ value;
|
||||
break;
|
||||
case BIN_SLEFT_EQUAL:
|
||||
value = currentValue << value;
|
||||
break;
|
||||
case BIN_SRIGHT_EQUAL:
|
||||
value = currentValue >> value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
environment->assign(expression->name, value);
|
||||
return value;
|
||||
}
|
||||
@ -312,8 +528,8 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||
}
|
||||
|
||||
if (callee.isBuiltinFunction()) {
|
||||
// Builtin functions now work directly with Value
|
||||
return callee.asBuiltinFunction()->func(arguments);
|
||||
// Builtin functions now work directly with Value and receive line and column
|
||||
return callee.asBuiltinFunction()->func(arguments, expression->paren.line, expression->paren.column);
|
||||
}
|
||||
|
||||
if (callee.isFunction()) {
|
||||
@ -325,13 +541,13 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||
|
||||
auto previousEnv = environment;
|
||||
environment = std::make_shared<Environment>(function->closure);
|
||||
environment->setErrorReporter(errorReporter);
|
||||
|
||||
for (size_t i = 0; i < function->params.size(); i++) {
|
||||
environment->define(function->params[i], arguments[i]);
|
||||
}
|
||||
|
||||
Value returnValue = NONE_VALUE;
|
||||
bool hasReturn = false;
|
||||
|
||||
for (const auto& stmt : function->body) {
|
||||
// Reset return context for each statement
|
||||
@ -341,7 +557,6 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||
execute(stmt);
|
||||
if (g_returnContext.hasReturn) {
|
||||
returnValue = g_returnContext.returnValue;
|
||||
hasReturn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -353,8 +568,21 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
||||
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) {
|
||||
auto newEnv = std::make_shared<Environment>(environment);
|
||||
newEnv->setErrorReporter(errorReporter);
|
||||
executeBlock(statement->statements, newEnv);
|
||||
}
|
||||
|
||||
@ -457,6 +685,16 @@ bool Interpreter::isTruthy(Value object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(object.isNumber())
|
||||
{
|
||||
return object.asNumber() != 0;
|
||||
}
|
||||
|
||||
if(object.isString())
|
||||
{
|
||||
return object.asString().length() > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
171
source/Lexer.cpp
171
source/Lexer.cpp
@ -1,4 +1,5 @@
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#include "../headers/helperFunctions/HelperFunctions.h"
|
||||
#include <cctype>
|
||||
#include <stdexcept>
|
||||
@ -8,69 +9,94 @@ using namespace std;
|
||||
std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
std::vector<Token> tokens;
|
||||
src = std::vector<char>{source.begin(), source.end()};
|
||||
line = 0;
|
||||
line = 1;
|
||||
column = 1;
|
||||
|
||||
while(!src.empty())
|
||||
{
|
||||
char t = src[0];
|
||||
if(t == '(')
|
||||
{
|
||||
tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line}); //brace initialization in case you forget
|
||||
tokens.push_back(Token{OPEN_PAREN, std::string(1, t), line, column}); //brace initialization in case you forget
|
||||
advance();
|
||||
}
|
||||
else if(t == ')')
|
||||
{
|
||||
tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line});
|
||||
tokens.push_back(Token{CLOSE_PAREN, std::string(1, t), line, column});
|
||||
advance();
|
||||
}
|
||||
else if(t == '{')
|
||||
{
|
||||
tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line});
|
||||
tokens.push_back(Token{OPEN_BRACE, std::string(1, t), line, column});
|
||||
advance();
|
||||
}
|
||||
else if(t == '}')
|
||||
{
|
||||
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line});
|
||||
tokens.push_back(Token{CLOSE_BRACE, std::string(1, t), line, column});
|
||||
advance();
|
||||
}
|
||||
else if(t == ',')
|
||||
{
|
||||
tokens.push_back(Token{COMMA, std::string(1, t), line});
|
||||
tokens.push_back(Token{COMMA, std::string(1, t), line, column});
|
||||
advance();
|
||||
}
|
||||
else if(t == '.')
|
||||
{
|
||||
tokens.push_back(Token{DOT, std::string(1, t), line});
|
||||
tokens.push_back(Token{DOT, std::string(1, t), line, column});
|
||||
advance();
|
||||
}
|
||||
else if(t == ';')
|
||||
{
|
||||
tokens.push_back(Token{SEMICOLON, std::string(1, t), line});
|
||||
tokens.push_back(Token{SEMICOLON, std::string(1, t), line, column});
|
||||
advance();
|
||||
}
|
||||
else if(t == '+')
|
||||
{
|
||||
tokens.push_back(Token{PLUS, std::string(1, t), line});
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{PLUS_EQUAL, "+=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{PLUS, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '-')
|
||||
{
|
||||
tokens.push_back(Token{MINUS, std::string(1, t), line});
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{MINUS_EQUAL, "-=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{MINUS, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '*')
|
||||
{
|
||||
tokens.push_back(Token{STAR, std::string(1, t), line});
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{STAR_EQUAL, "*=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{STAR, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '%')
|
||||
{
|
||||
tokens.push_back(Token{PERCENT, std::string(1, t), line});
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{PERCENT_EQUAL, "%=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{PERCENT, token, line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '~')
|
||||
{
|
||||
tokens.push_back(Token{BIN_NOT, std::string(1, t), line});
|
||||
tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1});
|
||||
advance();
|
||||
}
|
||||
else if(t == '=')
|
||||
@ -79,7 +105,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line});
|
||||
tokens.push_back(Token{match ? DOUBLE_EQUAL : EQUAL, token, line, column - static_cast<int>(token.length())});
|
||||
}
|
||||
else if(t == '!')
|
||||
{
|
||||
@ -87,7 +113,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line});
|
||||
tokens.push_back(Token{match ? BANG_EQUAL : BANG, token, line, column - static_cast<int>(token.length())});
|
||||
}
|
||||
else if(t == '<')
|
||||
{
|
||||
@ -95,15 +121,20 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
advance();
|
||||
if(matchOn('='))
|
||||
{
|
||||
tokens.push_back(Token{LESS_EQUAL, "<=", line});
|
||||
tokens.push_back(Token{LESS_EQUAL, "<=", line, column - 1});
|
||||
}
|
||||
else if(matchOn('<'))
|
||||
{
|
||||
tokens.push_back(Token{BIN_SLEFT, "<<", line});
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_SLEFT_EQUAL, "<<=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_SLEFT, "<<", line, column - 1});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{LESS, token, line});
|
||||
tokens.push_back(Token{LESS, token, line, column - static_cast<int>(token.length())});
|
||||
}
|
||||
|
||||
}
|
||||
@ -111,17 +142,66 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
token += match ? "=" : "";
|
||||
tokens.push_back(Token{match ? GREATER_EQUAL : GREATER, token, line});
|
||||
if(matchOn('='))
|
||||
{
|
||||
tokens.push_back(Token{GREATER_EQUAL, ">=", line, column - 1});
|
||||
}
|
||||
else if(matchOn('>'))
|
||||
{
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_SRIGHT_EQUAL, ">>=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_SRIGHT, ">>", line, column - 1});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{GREATER, token, line, column - static_cast<int>(token.length())});
|
||||
}
|
||||
}
|
||||
else if(t == '&')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('&');
|
||||
token += match ? "&" : "";
|
||||
if(match) tokens.push_back(Token{AND, token, line});
|
||||
if(match) {
|
||||
tokens.push_back(Token{AND, "&&", line, column - 1});
|
||||
} else {
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_AND_EQUAL, "&=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_AND, "&", line, column - 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(t == '|')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('|');
|
||||
if(match) {
|
||||
tokens.push_back(Token{OR, "||", line, column - 1});
|
||||
} else {
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{BIN_OR_EQUAL, "|=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_OR, "|", line, column - 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(t == '^')
|
||||
{
|
||||
std::string token = std::string(1, t);
|
||||
advance();
|
||||
bool match = matchOn('=');
|
||||
if(match) {
|
||||
tokens.push_back(Token{BIN_XOR_EQUAL, "^=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{BIN_XOR, "^", line, column - 1});
|
||||
}
|
||||
}
|
||||
else if(t == '/')
|
||||
{
|
||||
@ -137,13 +217,19 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{SLASH, std::string(1, t), line});
|
||||
bool equalMatch = matchOn('=');
|
||||
if(equalMatch) {
|
||||
tokens.push_back(Token{SLASH_EQUAL, "/=", line, column - 1});
|
||||
} else {
|
||||
tokens.push_back(Token{SLASH, "/", line, column - 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(t == '"')
|
||||
{
|
||||
bool last_was_escape = false;
|
||||
std::string str;
|
||||
int startColumn = column;
|
||||
advance();
|
||||
|
||||
while(!src.empty())
|
||||
@ -156,7 +242,6 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
next_c = "\\" + std::string(1, src[0]);
|
||||
}
|
||||
|
||||
if(next_c == "\n") line++;
|
||||
str += next_c;
|
||||
advance();
|
||||
}
|
||||
@ -172,14 +257,13 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
|
||||
|
||||
std::string escaped_str = parseEscapeCharacters(str);
|
||||
tokens.push_back(Token{STRING, escaped_str, line});
|
||||
tokens.push_back(Token{STRING, escaped_str, line, startColumn});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if(t == '\n')
|
||||
{
|
||||
line++;
|
||||
advance();
|
||||
}
|
||||
else
|
||||
@ -191,6 +275,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
if(std::isdigit(t))
|
||||
{
|
||||
std::string num;
|
||||
int startColumn = column;
|
||||
|
||||
if(src[0] != '0') notationInvalidated = true;
|
||||
|
||||
@ -260,11 +345,12 @@ std::vector<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))
|
||||
{
|
||||
std::string ident;
|
||||
int startColumn = column;
|
||||
while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_'))
|
||||
{
|
||||
ident += src[0];
|
||||
@ -273,11 +359,11 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
|
||||
if(KEYWORDS.find(ident) != KEYWORDS.end()) //identifier is a keyword
|
||||
{
|
||||
tokens.push_back(Token{KEYWORDS.at(ident), ident, line});
|
||||
tokens.push_back(Token{KEYWORDS.at(ident), ident, line, startColumn});
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens.push_back(Token{IDENTIFIER, ident, line});
|
||||
tokens.push_back(Token{IDENTIFIER, ident, line, startColumn});
|
||||
}
|
||||
|
||||
}
|
||||
@ -287,7 +373,10 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "Lexer Error",
|
||||
"Unknown token '" + std::string(1, t) + "'", "");
|
||||
}
|
||||
throw std::runtime_error("LEXER: Unknown Token: '" + std::string(1, t) + "'");
|
||||
}
|
||||
|
||||
@ -310,7 +399,27 @@ bool Lexer::matchOn(char expected)
|
||||
void Lexer::advance(int by)
|
||||
{
|
||||
for (int i = 0; i < by; ++i) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Created by Bobby Lucero on 5/26/23.
|
||||
//
|
||||
#include "../headers/Parser.h"
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
// Precedence
|
||||
@ -13,25 +14,110 @@ sptr(Expr) Parser::expression()
|
||||
return assignment();
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::assignment()
|
||||
sptr(Expr) Parser::logical_or()
|
||||
{
|
||||
sptr(Expr) expr = logical_and();
|
||||
|
||||
sptr(Expr) expr = equality();
|
||||
|
||||
if(match({EQUAL}))
|
||||
while(match({OR}))
|
||||
{
|
||||
Token equals = previous();
|
||||
|
||||
sptr(Expr) value = assignment();
|
||||
|
||||
if(std::dynamic_pointer_cast<VarExpr>(expr))
|
||||
{
|
||||
|
||||
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
|
||||
|
||||
return msptr(AssignExpr)(name, value);
|
||||
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();
|
||||
|
||||
while(match({AND}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = equality();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
// bitwise_or now calls comparison (not bitwise_xor)
|
||||
sptr(Expr) Parser::bitwise_or()
|
||||
{
|
||||
sptr(Expr) expr = bitwise_xor();
|
||||
|
||||
while(match({BIN_OR}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = bitwise_xor();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::bitwise_xor()
|
||||
{
|
||||
sptr(Expr) expr = bitwise_and();
|
||||
|
||||
while(match({BIN_XOR}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = bitwise_and();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::bitwise_and()
|
||||
{
|
||||
sptr(Expr) expr = shift();
|
||||
|
||||
while(match({BIN_AND}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = shift();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::shift()
|
||||
{
|
||||
sptr(Expr) expr = term();
|
||||
|
||||
while(match({BIN_SLEFT, BIN_SRIGHT}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = term();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::assignment()
|
||||
{
|
||||
sptr(Expr) expr = logical_or();
|
||||
|
||||
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
||||
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) value = assignment();
|
||||
if(std::dynamic_pointer_cast<VarExpr>(expr))
|
||||
{
|
||||
Token name = std::dynamic_pointer_cast<VarExpr>(expr)->name;
|
||||
return msptr(AssignExpr)(name, op, value);
|
||||
}
|
||||
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(op.line, op.column, "Parse Error",
|
||||
"Invalid assignment target", "");
|
||||
}
|
||||
throw std::runtime_error("Invalid assignment target.");
|
||||
}
|
||||
|
||||
@ -54,12 +140,12 @@ sptr(Expr) Parser::equality()
|
||||
|
||||
sptr(Expr) Parser::comparison()
|
||||
{
|
||||
sptr(Expr) expr = term();
|
||||
sptr(Expr) expr = bitwise_or();
|
||||
|
||||
while(match({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL}))
|
||||
{
|
||||
Token op = previous();
|
||||
sptr(Expr) right = term();
|
||||
sptr(Expr) right = bitwise_or();
|
||||
expr = msptr(BinaryExpr)(expr, op, right);
|
||||
}
|
||||
|
||||
@ -132,6 +218,14 @@ sptr(Expr) Parser::primary()
|
||||
return msptr(GroupingExpr)(expr);
|
||||
}
|
||||
|
||||
if(match({FUNCTION})) {
|
||||
return functionExpression();
|
||||
}
|
||||
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(peek().line, peek().column, "Parse Error",
|
||||
"Expression expected", "");
|
||||
}
|
||||
throw std::runtime_error("Expression expected at: " + std::to_string(peek().line));
|
||||
}
|
||||
|
||||
@ -192,10 +286,46 @@ sptr(Stmt) Parser::functionDeclaration()
|
||||
consume(CLOSE_PAREN, "Expected ')' after parameters.");
|
||||
consume(OPEN_BRACE, "Expected '{' before function body.");
|
||||
|
||||
// Enter function scope
|
||||
enterFunction();
|
||||
|
||||
std::vector<sptr(Stmt)> body = block();
|
||||
|
||||
// Exit function scope
|
||||
exitFunction();
|
||||
|
||||
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()
|
||||
{
|
||||
if(match({RETURN})) return returnStatement();
|
||||
@ -225,6 +355,16 @@ sptr(Stmt) Parser::ifStatement()
|
||||
sptr(Stmt) Parser::returnStatement()
|
||||
{
|
||||
Token keyword = previous();
|
||||
|
||||
// Check if we're inside a function
|
||||
if (!isInFunction()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(keyword.line, 0, "Parse Error",
|
||||
"Cannot return from outside a function", "");
|
||||
}
|
||||
throw std::runtime_error("Cannot return from outside a function");
|
||||
}
|
||||
|
||||
sptr(Expr) value = msptr(LiteralExpr)("none", false, true, false);
|
||||
|
||||
if (!check(SEMICOLON)) {
|
||||
@ -310,6 +450,24 @@ Token Parser::previous() {
|
||||
Token Parser::consume(TokenType type, const std::string& message) {
|
||||
if(check(type)) return advance();
|
||||
|
||||
if (errorReporter) {
|
||||
// Use the precise column information from the token
|
||||
int errorColumn = peek().column;
|
||||
|
||||
// For missing closing parenthesis, point to where it should be
|
||||
if (type == CLOSE_PAREN) {
|
||||
// The closing parenthesis should be right after the previous token
|
||||
errorColumn = previous().column + previous().lexeme.length();
|
||||
|
||||
// For string tokens, add 2 to account for the opening and closing quotes
|
||||
if (previous().type == STRING) {
|
||||
errorColumn += 2;
|
||||
}
|
||||
}
|
||||
|
||||
errorReporter->reportError(peek().line, errorColumn, "Parse Error",
|
||||
"Unexpected symbol '" + peek().lexeme + "': " + message, "");
|
||||
}
|
||||
throw std::runtime_error("Unexpected symbol '" + peek().lexeme +"': "+ message);
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
#include "../headers/StdLib.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/ErrorReporter.h"
|
||||
#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
|
||||
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 (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
@ -19,8 +24,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
|
||||
// Create a built-in print function
|
||||
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 (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
// Use the interpreter's stringify function
|
||||
@ -34,8 +43,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
|
||||
// Create a built-in assert function
|
||||
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 (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
@ -56,6 +69,9 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
message += " - " + std::string(args[1].asString());
|
||||
}
|
||||
}
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
@ -68,8 +84,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
|
||||
// Create a built-in time function (returns microseconds since Unix epoch)
|
||||
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 (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
@ -86,8 +106,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
|
||||
// Create a built-in input function
|
||||
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 (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
@ -109,8 +133,12 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
|
||||
// Create a built-in type function
|
||||
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 (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
@ -140,13 +168,13 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
|
||||
// Create a built-in toNumber function for string-to-number conversion
|
||||
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) {
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
return NONE_VALUE; // Return none for wrong argument count
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
throw std::runtime_error("toNumber() expects a string argument.");
|
||||
return NONE_VALUE; // Return none for wrong type
|
||||
}
|
||||
|
||||
std::string str = args[0].asString();
|
||||
@ -156,20 +184,58 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
||||
str.erase(str.find_last_not_of(" \t\n\r") + 1);
|
||||
|
||||
if (str.empty()) {
|
||||
throw std::runtime_error("Cannot convert empty string to number.");
|
||||
return NONE_VALUE; // Return none for empty string
|
||||
}
|
||||
|
||||
try {
|
||||
double value = std::stod(str);
|
||||
return Value(value);
|
||||
} catch (const std::invalid_argument&) {
|
||||
throw std::runtime_error("Cannot convert '" + str + "' to number.");
|
||||
return NONE_VALUE; // Return none for invalid conversion
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error("Number '" + str + "' is out of range.");
|
||||
return NONE_VALUE; // Return none for out of range
|
||||
}
|
||||
});
|
||||
env->define("toNumber", Value(toNumberFunc.get()));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(toNumberFunc);
|
||||
|
||||
// Create a built-in toBoolean function for explicit boolean conversion
|
||||
auto toBooleanFunc = std::make_shared<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);
|
||||
}
|
||||
@ -20,6 +20,12 @@ void Bob::runFile(const string& path)
|
||||
return;
|
||||
}
|
||||
|
||||
// Load source code into error reporter for context
|
||||
errorReporter.loadSource(source, path);
|
||||
|
||||
// Connect error reporter to interpreter
|
||||
interpreter->setErrorReporter(&errorReporter);
|
||||
|
||||
this->run(source);
|
||||
}
|
||||
|
||||
@ -39,42 +45,49 @@ void Bob::runPrompt()
|
||||
break;
|
||||
}
|
||||
|
||||
// Load source code into error reporter for context
|
||||
errorReporter.loadSource(line, "REPL");
|
||||
|
||||
// Connect error reporter to interpreter
|
||||
interpreter->setErrorReporter(&errorReporter);
|
||||
|
||||
this->run(line);
|
||||
hadError = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Bob::error(int line, const string& message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Bob::run(string source)
|
||||
{
|
||||
try {
|
||||
// Connect error reporter to lexer
|
||||
lexer.setErrorReporter(&errorReporter);
|
||||
|
||||
vector<Token> tokens = lexer.Tokenize(std::move(source));
|
||||
Parser p(tokens);
|
||||
|
||||
// Connect error reporter to parser
|
||||
p.setErrorReporter(&errorReporter);
|
||||
|
||||
vector<sptr(Stmt)> statements = p.parse();
|
||||
interpreter->interpret(statements);
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
cout << "ERROR OCCURRED: " << e.what() << endl;
|
||||
// Only suppress errors that have already been reported by the error reporter
|
||||
if (errorReporter.hasReportedError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For errors that weren't reported (like parser errors, undefined variables, etc.)
|
||||
// print them normally
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
return;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
cout << "UNKNOWN ERROR OCCURRED" << endl;
|
||||
// Unknown error - report it since it wasn't handled by the interpreter
|
||||
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Bob::report(int line, string where, string message)
|
||||
{
|
||||
hadError = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -756,7 +756,7 @@ assert(hex3 == 4294967295, "Hex FFFFFFFF should be 4294967295");
|
||||
var add_num = 5 + 3;
|
||||
var sub_num = 10 - 4;
|
||||
var mul_num = 6 * 7;
|
||||
var div_num = 15 / 3;
|
||||
var div_num = 15 /* 3;
|
||||
assert(add_num == 8, "5 + 3 should be 8");
|
||||
assert(sub_num == 6, "10 - 4 should be 6");
|
||||
assert(mul_num == 42, "6 * 7 should be 42");
|
||||
@ -1124,6 +1124,416 @@ assert(deepFactorial(10) == 3628800, "deepFactorial(10) should be 3628800");
|
||||
|
||||
print("✓ Recursion working");
|
||||
|
||||
// ========================================
|
||||
// TEST 39: LOGICAL OPERATORS (&&, ||, !)
|
||||
// ========================================
|
||||
print("\n--- Test 39: Logical Operators ---");
|
||||
|
||||
// Test logical AND (&&)
|
||||
assert(5 && 3 == 3, "5 && 3 should return 3 (truthy && truthy = second value)");
|
||||
assert(0 && 5 == 0, "0 && 5 should return 0 (falsy && truthy = first value)");
|
||||
assert(5 && 0 == 0, "5 && 0 should return 0 (truthy && falsy = second value)");
|
||||
assert(0 && 0 == 0, "0 && 0 should return 0 (falsy && falsy = first value)");
|
||||
|
||||
// Test logical OR (||)
|
||||
assert(5 || 3 == 5, "5 || 3 should return 5 (truthy || truthy = first value)");
|
||||
assert(0 || 5 == 5, "0 || 5 should return 5 (falsy || truthy = second value)");
|
||||
assert(5 || 0 == 5, "5 || 0 should return 5 (truthy || falsy = first value)");
|
||||
assert(0 || 0 == 0, "0 || 0 should return 0 (falsy || falsy = second value)");
|
||||
|
||||
// Test logical NOT (!)
|
||||
assert(!0 == true, "!0 should be true");
|
||||
assert(!1 == false, "!1 should be false");
|
||||
assert(!5 == false, "!5 should be false");
|
||||
assert(!true == false, "!true should be false");
|
||||
assert(!false == true, "!false should be true");
|
||||
|
||||
// Test short-circuit evaluation
|
||||
var short_circuit_test = 0;
|
||||
func sideEffect() {
|
||||
short_circuit_test = 1;
|
||||
return 5;
|
||||
}
|
||||
|
||||
// Test that short-circuit evaluation works correctly
|
||||
// Note: We can't easily test side effects in Bob, so we test the return values instead
|
||||
var result_short1 = 0 && 5;
|
||||
assert(result_short1 == 0, "0 && 5 should return 0 (short-circuit)");
|
||||
|
||||
var result_short2 = 1 && 5;
|
||||
assert(result_short2 == 5, "1 && 5 should return 5 (no short-circuit)");
|
||||
|
||||
var result_short3 = 1 || 5;
|
||||
assert(result_short3 == 1, "1 || 5 should return 1 (short-circuit)");
|
||||
|
||||
var result_short4 = 0 || 5;
|
||||
assert(result_short4 == 5, "0 || 5 should return 5 (no short-circuit)");
|
||||
|
||||
// Test complex logical expressions
|
||||
assert((5 > 3) && (10 < 20) == true, "Complex AND with comparisons");
|
||||
assert((5 < 3) || (10 < 20) == true, "Complex OR with comparisons");
|
||||
assert(!(5 < 3) == true, "Complex NOT with comparison");
|
||||
|
||||
// Test truthiness rules
|
||||
assert("hello" && "world" == "world", "String && string should return second string");
|
||||
assert("" && "world" == "", "Empty string && string should return empty string");
|
||||
assert("hello" || "world" == "hello", "String || string should return first string");
|
||||
assert("" || "world" == "world", "Empty string || string should return second string");
|
||||
|
||||
print("✓ Logical operators working");
|
||||
|
||||
// ========================================
|
||||
// TEST 40: BITWISE OPERATORS (&, |, ^, <<, >>, ~)
|
||||
// ========================================
|
||||
print("\n--- Test 40: Bitwise Operators ---");
|
||||
|
||||
// Test bitwise AND (&)
|
||||
assert(10 & 3 == 2, "10 & 3 should be 2 (1010 & 0011 = 0010)");
|
||||
assert(15 & 7 == 7, "15 & 7 should be 7 (1111 & 0111 = 0111)");
|
||||
assert(255 & 128 == 128, "255 & 128 should be 128");
|
||||
|
||||
// Test bitwise OR (|)
|
||||
assert(10 | 3 == 11, "10 | 3 should be 11 (1010 | 0011 = 1011)");
|
||||
assert(5 | 10 == 15, "5 | 10 should be 15 (0101 | 1010 = 1111)");
|
||||
assert(128 | 64 == 192, "128 | 64 should be 192");
|
||||
|
||||
// Test bitwise XOR (^)
|
||||
assert(10 ^ 3 == 9, "10 ^ 3 should be 9 (1010 ^ 0011 = 1001)");
|
||||
assert(15 ^ 7 == 8, "15 ^ 7 should be 8 (1111 ^ 0111 = 1000)");
|
||||
assert(255 ^ 128 == 127, "255 ^ 128 should be 127");
|
||||
|
||||
// Test left shift (<<)
|
||||
assert(5 << 1 == 10, "5 << 1 should be 10 (0101 << 1 = 1010)");
|
||||
assert(3 << 2 == 12, "3 << 2 should be 12 (0011 << 2 = 1100)");
|
||||
assert(1 << 8 == 256, "1 << 8 should be 256");
|
||||
|
||||
// Test right shift (>>)
|
||||
assert(10 >> 1 == 5, "10 >> 1 should be 5 (1010 >> 1 = 0101)");
|
||||
assert(20 >> 2 == 5, "20 >> 2 should be 5 (10100 >> 2 = 00101)");
|
||||
assert(256 >> 8 == 1, "256 >> 8 should be 1");
|
||||
|
||||
// Test bitwise NOT (~)
|
||||
assert(~10 == -11, "~10 should be -11");
|
||||
assert(~0 == -1, "~0 should be -1");
|
||||
assert(~(-1) == 0, "~(-1) should be 0");
|
||||
|
||||
// Test complex bitwise expressions
|
||||
assert((10 & 3) | (5 & 2) == 2, "Complex bitwise expression 1");
|
||||
assert((15 << 1) >> 1 == 15, "Complex bitwise expression 2");
|
||||
assert(~(10 & 5) == -1, "Complex bitwise expression 3");
|
||||
|
||||
// Test edge cases
|
||||
assert(0 & 0 == 0, "0 & 0 should be 0");
|
||||
assert(0 | 0 == 0, "0 | 0 should be 0");
|
||||
assert(0 ^ 0 == 0, "0 ^ 0 should be 0");
|
||||
assert(0 << 5 == 0, "0 << 5 should be 0");
|
||||
assert(0 >> 5 == 0, "0 >> 5 should be 0");
|
||||
assert(~0 == -1, "~0 should be -1");
|
||||
|
||||
print("✓ Bitwise operators working");
|
||||
|
||||
// ========================================
|
||||
// TEST 41: COMPOUND ASSIGNMENT OPERATORS
|
||||
// ========================================
|
||||
print("\n--- Test 41: Compound Assignment Operators ---");
|
||||
|
||||
// Test arithmetic compound assignment
|
||||
var comp_x = 10;
|
||||
comp_x += 5;
|
||||
assert(comp_x == 15, "comp_x += 5 should make comp_x = 15");
|
||||
|
||||
comp_x -= 3;
|
||||
assert(comp_x == 12, "comp_x -= 3 should make comp_x = 12");
|
||||
|
||||
comp_x *= 2;
|
||||
assert(comp_x == 24, "comp_x *= 2 should make comp_x = 24");
|
||||
|
||||
comp_x /= 4;
|
||||
assert(comp_x == 6, "comp_x /= 4 should make comp_x = 6");
|
||||
|
||||
comp_x %= 4;
|
||||
assert(comp_x == 2, "comp_x %= 4 should make comp_x = 2");
|
||||
|
||||
// Test bitwise compound assignment
|
||||
var comp_y = 15;
|
||||
comp_y &= 7;
|
||||
assert(comp_y == 7, "comp_y &= 7 should make comp_y = 7");
|
||||
|
||||
comp_y |= 8;
|
||||
assert(comp_y == 15, "comp_y |= 8 should make comp_y = 15");
|
||||
|
||||
comp_y ^= 4;
|
||||
assert(comp_y == 11, "comp_y ^= 4 should make comp_y = 11");
|
||||
|
||||
comp_y <<= 1;
|
||||
assert(comp_y == 22, "comp_y <<= 1 should make comp_y = 22");
|
||||
|
||||
comp_y >>= 2;
|
||||
assert(comp_y == 5, "comp_y >>= 2 should make comp_y = 5");
|
||||
|
||||
// Test compound assignment with expressions
|
||||
var comp_z = 10;
|
||||
comp_z += 2 + 3;
|
||||
assert(comp_z == 15, "comp_z += 2 + 3 should make comp_z = 15");
|
||||
|
||||
comp_z *= 2 + 1;
|
||||
assert(comp_z == 45, "comp_z *= 2 + 1 should make comp_z = 45");
|
||||
|
||||
// Test compound assignment with variables
|
||||
var comp_a = 5;
|
||||
var comp_b = 3;
|
||||
comp_a += comp_b;
|
||||
assert(comp_a == 8, "comp_a += comp_b should make comp_a = 8");
|
||||
|
||||
comp_a *= comp_b;
|
||||
assert(comp_a == 24, "comp_a *= comp_b should make comp_a = 24");
|
||||
|
||||
print("✓ Compound assignment operators working");
|
||||
|
||||
// ========================================
|
||||
// TEST 42: ANONYMOUS FUNCTIONS
|
||||
// ========================================
|
||||
print("\n--- Test 42: Anonymous Functions ---");
|
||||
|
||||
// Test basic anonymous function
|
||||
var anonymous1 = func() {
|
||||
return 42;
|
||||
};
|
||||
assert(anonymous1() == 42, "Basic anonymous function should return 42");
|
||||
|
||||
// Test anonymous function with parameters
|
||||
var anonymous2 = func(x, y) {
|
||||
return x + y;
|
||||
};
|
||||
assert(anonymous2(5, 3) == 8, "Anonymous function with parameters should work");
|
||||
|
||||
// Test anonymous function with string operations
|
||||
var anonymous3 = func(name) {
|
||||
return "Hello, " + name + "!";
|
||||
};
|
||||
assert(anonymous3("Bob") == "Hello, Bob!", "Anonymous function with strings should work");
|
||||
|
||||
// Test anonymous function with conditionals
|
||||
var anonymous4 = func(x) {
|
||||
if (x > 0) {
|
||||
return "positive";
|
||||
} else {
|
||||
return "non-positive";
|
||||
}
|
||||
};
|
||||
assert(anonymous4(5) == "positive", "Anonymous function with conditionals should work");
|
||||
assert(anonymous4(-3) == "non-positive", "Anonymous function with conditionals should work");
|
||||
|
||||
// Test anonymous function composition
|
||||
var compose = func(f, g, x) {
|
||||
return f(g(x));
|
||||
};
|
||||
|
||||
var double = func(x) {
|
||||
return x * 2;
|
||||
};
|
||||
|
||||
var addOne = func(x) {
|
||||
return x + 1;
|
||||
};
|
||||
|
||||
var result_comp = compose(double, addOne, 5);
|
||||
assert(result_comp == 12, "Anonymous function composition should work");
|
||||
|
||||
// Test anonymous function as return value
|
||||
func createMultiplier(factor) {
|
||||
return func(x) {
|
||||
return x * factor;
|
||||
};
|
||||
}
|
||||
|
||||
var multiplyBy3 = createMultiplier(3);
|
||||
assert(multiplyBy3(4) == 12, "Anonymous function as return value should work");
|
||||
|
||||
// Test anonymous function with closure
|
||||
func createCounter() {
|
||||
var count = 0;
|
||||
return func() {
|
||||
count = count + 1;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
var counter1 = createCounter();
|
||||
var counter2 = createCounter();
|
||||
|
||||
assert(counter1() == 1, "Anonymous function closure should work");
|
||||
assert(counter1() == 2, "Anonymous function closure should maintain state");
|
||||
assert(counter2() == 1, "Different anonymous function instances should have separate state");
|
||||
|
||||
print("✓ Anonymous functions working");
|
||||
|
||||
// ========================================
|
||||
// TEST 43: FUNCTIONS RETURNING FUNCTIONS
|
||||
// ========================================
|
||||
print("\n--- Test 43: Functions Returning Functions ---");
|
||||
|
||||
// Test basic function returning function
|
||||
func createAdder(x) {
|
||||
return func(y) {
|
||||
return x + y;
|
||||
};
|
||||
}
|
||||
|
||||
var add5 = createAdder(5);
|
||||
assert(add5(3) == 8, "Function returning function should work");
|
||||
|
||||
// Test function returning multiple functions
|
||||
func createMathOps() {
|
||||
return func(x, y) {
|
||||
return x + y;
|
||||
};
|
||||
}
|
||||
|
||||
var mathFunc = createMathOps();
|
||||
assert(mathFunc(10, 20) == 30, "Function returning math function should work");
|
||||
|
||||
// Test function returning function with closure
|
||||
func createGreeter(greeting) {
|
||||
return func(name) {
|
||||
return greeting + ", " + name + "!";
|
||||
};
|
||||
}
|
||||
|
||||
var helloGreeter = createGreeter("Hello");
|
||||
var hiGreeter = createGreeter("Hi");
|
||||
|
||||
assert(helloGreeter("Alice") == "Hello, Alice!", "Greeter function should work");
|
||||
assert(hiGreeter("Bob") == "Hi, Bob!", "Different greeter should work");
|
||||
|
||||
// Test nested function returning functions
|
||||
func createCalculator() {
|
||||
return func(operation) {
|
||||
if (operation == "add") {
|
||||
return func(x, y) {
|
||||
return x + y;
|
||||
};
|
||||
} else if (operation == "multiply") {
|
||||
return func(x, y) {
|
||||
return x * y;
|
||||
};
|
||||
} else {
|
||||
return func(x, y) {
|
||||
return x - y;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var calculator = createCalculator();
|
||||
var addFunc = calculator("add");
|
||||
var multiplyFunc = calculator("multiply");
|
||||
|
||||
assert(addFunc(5, 3) == 8, "Calculator add function should work");
|
||||
assert(multiplyFunc(4, 6) == 24, "Calculator multiply function should work");
|
||||
|
||||
// Test function returning function with complex logic
|
||||
func createValidator(rule) {
|
||||
return func(value) {
|
||||
if (rule == "positive") {
|
||||
return value > 0;
|
||||
} else if (rule == "even") {
|
||||
return value % 2 == 0;
|
||||
} else if (rule == "string") {
|
||||
return type(value) == "string";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var positiveValidator = createValidator("positive");
|
||||
var evenValidator = createValidator("even");
|
||||
var stringValidator = createValidator("string");
|
||||
|
||||
assert(positiveValidator(5) == true, "Positive validator should work");
|
||||
assert(positiveValidator(-3) == false, "Positive validator should work");
|
||||
assert(evenValidator(4) == true, "Even validator should work");
|
||||
assert(evenValidator(7) == false, "Even validator should work");
|
||||
assert(stringValidator("hello") == true, "String validator should work");
|
||||
assert(stringValidator(42) == false, "String validator should work");
|
||||
|
||||
print("✓ Functions returning functions working");
|
||||
|
||||
// ========================================
|
||||
// TEST 44: COMPREHENSIVE OPERATOR PRECEDENCE
|
||||
// ========================================
|
||||
print("\n--- Test 44: Operator Precedence ---");
|
||||
|
||||
// Test logical operator precedence
|
||||
assert(5 && 3 || 0 == 3, "&& should have higher precedence than ||");
|
||||
assert(0 || 5 && 3 == 3, "&& should have higher precedence than ||");
|
||||
|
||||
// Test bitwise operator precedence
|
||||
assert(10 & 3 | 5 == 7, "& should have higher precedence than |");
|
||||
assert(10 | 3 & 5 == 11, "& should have higher precedence than |");
|
||||
|
||||
// Test shift operator precedence
|
||||
assert(10 << 1 + 2 == 80, "Shift should have higher precedence than addition");
|
||||
assert(10 + 1 << 2 == 44, "Addition should have higher precedence than shift");
|
||||
|
||||
// Test arithmetic operator precedence
|
||||
assert(2 + 3 * 4 == 14, "Multiplication should have higher precedence than addition");
|
||||
assert(10 - 4 / 2 == 8, "Division should have higher precedence than subtraction");
|
||||
|
||||
// Test complex precedence
|
||||
assert(5 && 3 | 2 << 1 == 7, "Complex precedence test 1");
|
||||
assert((5 && 3) | (2 << 1) == 7, "Complex precedence test 2");
|
||||
|
||||
print("✓ Operator precedence working");
|
||||
|
||||
// ========================================
|
||||
// TEST 45: EDGE CASES FOR NEW OPERATORS
|
||||
// ========================================
|
||||
print("\n--- Test 45: Edge Cases for New Operators ---");
|
||||
|
||||
// Test logical operators with edge cases
|
||||
assert(0 && 0 == 0, "0 && 0 should be 0");
|
||||
assert(0 || 0 == 0, "0 || 0 should be 0");
|
||||
assert(!0 == true, "!0 should be true");
|
||||
assert(!1 == false, "!1 should be false");
|
||||
|
||||
// Test bitwise operators with edge cases
|
||||
assert(0 & 0 == 0, "0 & 0 should be 0");
|
||||
assert(0 | 0 == 0, "0 | 0 should be 0");
|
||||
assert(0 ^ 0 == 0, "0 ^ 0 should be 0");
|
||||
assert(0 << 5 == 0, "0 << 5 should be 0");
|
||||
assert(0 >> 5 == 0, "0 >> 5 should be 0");
|
||||
assert(~0 == -1, "~0 should be -1");
|
||||
|
||||
// Test compound assignment with edge cases
|
||||
var edge_x = 0;
|
||||
edge_x += 5;
|
||||
assert(edge_x == 5, "0 += 5 should be 5");
|
||||
|
||||
edge_x -= 10;
|
||||
assert(edge_x == -5, "5 -= 10 should be -5");
|
||||
|
||||
edge_x *= 0;
|
||||
assert(edge_x == 0, "-5 *= 0 should be 0");
|
||||
|
||||
// Test anonymous functions with edge cases
|
||||
var edge_func = func() {
|
||||
return none;
|
||||
};
|
||||
assert(type(edge_func()) == "none", "Anonymous function returning none should work");
|
||||
|
||||
var edge_func2 = func(x) {
|
||||
if (x == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return edge_func2(x - 1) + 1;
|
||||
}
|
||||
};
|
||||
assert(edge_func2(3) == 3, "Recursive anonymous function should work");
|
||||
|
||||
print("✓ Edge cases for new operators working");
|
||||
|
||||
// ========================================
|
||||
// FINAL SUMMARY
|
||||
// ========================================
|
||||
@ -1176,7 +1586,14 @@ print("- toString function (universal string conversion)");
|
||||
print("- Enhanced print function (works with all object types)");
|
||||
print("- Redefinable functions (including built-in function override)");
|
||||
print("- Recursion (factorial, fibonacci, mutual recursion, deep recursion)");
|
||||
print("- NEW: Logical operators (&&, ||, !) with short-circuit evaluation");
|
||||
print("- NEW: Bitwise operators (&, |, ^, <<, >>, ~)");
|
||||
print("- NEW: Compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)");
|
||||
print("- NEW: Anonymous functions (func(...) { ... })");
|
||||
print("- NEW: Functions returning functions");
|
||||
print("- NEW: Operator precedence for all new operators");
|
||||
print("- NEW: Edge cases for all new operators");
|
||||
|
||||
print("\n🎉 ALL TESTS PASSED! 🎉");
|
||||
print("Bob language is working correctly!");
|
||||
print("Ready for next phase: Control Flow (if statements, while loops)");
|
||||
print("Ready for next phase: Control Flow (while loops, data structures)");
|
||||
@ -10,3 +10,6 @@ print("Fibonacci test:");
|
||||
var fib_result = fib(30);
|
||||
|
||||
print("Result: " + fib_result);
|
||||
|
||||
|
||||
print(10 / 0);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user