Refactor to ExecutionContext pattern and fix string + none concatenation
- Add none value handling to Value::operator+ for string concatenation - Replace direct string concatenations with Value::operator+ calls in Interpreter - Add missing string+none and none+string type combinations - Standardize token literal generation in Lexer - Add ExecutionContext support across visitor pattern - Enhance error reporting integration - Add new standard library functions
This commit is contained in:
parent
adb00d496f
commit
72a31b28af
@ -36,6 +36,11 @@ public:
|
|||||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||||
inline void clear() { variables.clear(); }
|
inline void clear() { variables.clear(); }
|
||||||
|
|
||||||
|
// Set parent environment for TCO environment reuse
|
||||||
|
inline void setParent(std::shared_ptr<Environment> newParent) {
|
||||||
|
parent = newParent;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, Value> variables;
|
std::unordered_map<std::string, Value> variables;
|
||||||
std::shared_ptr<Environment> parent;
|
std::shared_ptr<Environment> parent;
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
struct FunctionExpr;
|
struct FunctionExpr;
|
||||||
|
struct IncrementExpr;
|
||||||
struct ExprVisitor;
|
struct ExprVisitor;
|
||||||
|
|
||||||
struct AssignExpr;
|
struct AssignExpr;
|
||||||
@ -30,6 +31,7 @@ struct ExprVisitor
|
|||||||
virtual Value visitCallExpr(const std::shared_ptr<CallExpr>& expr) = 0;
|
virtual Value visitCallExpr(const std::shared_ptr<CallExpr>& expr) = 0;
|
||||||
virtual Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expr) = 0;
|
virtual Value visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expr) = 0;
|
||||||
virtual Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expr) = 0;
|
virtual Value visitGroupingExpr(const std::shared_ptr<GroupingExpr>& expr) = 0;
|
||||||
|
virtual Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expr) = 0;
|
||||||
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
virtual Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) = 0;
|
||||||
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
|
virtual Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expr) = 0;
|
||||||
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
virtual Value visitVarExpr(const std::shared_ptr<VarExpr>& expr) = 0;
|
||||||
@ -126,6 +128,8 @@ struct CallExpr : Expr
|
|||||||
std::shared_ptr<Expr> callee;
|
std::shared_ptr<Expr> callee;
|
||||||
Token paren;
|
Token paren;
|
||||||
std::vector<std::shared_ptr<Expr>> arguments;
|
std::vector<std::shared_ptr<Expr>> arguments;
|
||||||
|
bool isTailCall = false; // Flag for tail call optimization
|
||||||
|
|
||||||
CallExpr(std::shared_ptr<Expr> callee, Token paren, std::vector<std::shared_ptr<Expr>> arguments)
|
CallExpr(std::shared_ptr<Expr> callee, Token paren, std::vector<std::shared_ptr<Expr>> arguments)
|
||||||
: callee(callee), paren(paren), arguments(arguments) {}
|
: callee(callee), paren(paren), arguments(arguments) {}
|
||||||
Value accept(ExprVisitor* visitor) override
|
Value accept(ExprVisitor* visitor) override
|
||||||
@ -134,3 +138,17 @@ struct CallExpr : Expr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct IncrementExpr : Expr
|
||||||
|
{
|
||||||
|
std::shared_ptr<Expr> operand;
|
||||||
|
Token oper;
|
||||||
|
bool isPrefix; // true for ++x, false for x++
|
||||||
|
|
||||||
|
IncrementExpr(std::shared_ptr<Expr> operand, Token oper, bool isPrefix)
|
||||||
|
: operand(operand), oper(oper), isPrefix(isPrefix) {}
|
||||||
|
Value accept(ExprVisitor* visitor) override
|
||||||
|
{
|
||||||
|
return visitor->visitIncrementExpr(std::static_pointer_cast<IncrementExpr>(shared_from_this()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@ -23,14 +23,15 @@ public:
|
|||||||
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
Value visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expression) override;
|
||||||
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
Value visitUnaryExpr(const std::shared_ptr<UnaryExpr>& expression) override;
|
||||||
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
Value visitVarExpr(const std::shared_ptr<VarExpr>& expression) override;
|
||||||
|
Value visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) override;
|
||||||
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
Value visitAssignExpr(const std::shared_ptr<AssignExpr>& expression) override;
|
||||||
|
|
||||||
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) override;
|
void visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) override;
|
void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitVarStmt(const std::shared_ptr<VarStmt>& statement) override;
|
void visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement) override;
|
void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement) override;
|
void visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitIfStmt(const std::shared_ptr<IfStmt>& statement) override;
|
void visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
|
||||||
void interpret(std::vector<std::shared_ptr<Stmt> > statements);
|
void interpret(std::vector<std::shared_ptr<Stmt> > statements);
|
||||||
|
|
||||||
@ -49,8 +50,8 @@ private:
|
|||||||
Value evaluate(const std::shared_ptr<Expr>& expr);
|
Value evaluate(const std::shared_ptr<Expr>& expr);
|
||||||
bool isEqual(Value a, Value b);
|
bool isEqual(Value a, Value b);
|
||||||
bool isWholeNumer(double num);
|
bool isWholeNumer(double num);
|
||||||
void execute(const std::shared_ptr<Stmt>& statement);
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context = nullptr);
|
||||||
void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env);
|
void executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context = nullptr);
|
||||||
void addStdLibFunctions();
|
void addStdLibFunctions();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -15,6 +15,9 @@ enum TokenType{
|
|||||||
GREATER, GREATER_EQUAL,
|
GREATER, GREATER_EQUAL,
|
||||||
LESS, LESS_EQUAL,
|
LESS, LESS_EQUAL,
|
||||||
|
|
||||||
|
// Increment/decrement operators
|
||||||
|
PLUS_PLUS, MINUS_MINUS,
|
||||||
|
|
||||||
IDENTIFIER, STRING, NUMBER, BOOL,
|
IDENTIFIER, STRING, NUMBER, BOOL,
|
||||||
|
|
||||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||||
@ -39,6 +42,8 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
|||||||
"GREATER", "GREATER_EQUAL",
|
"GREATER", "GREATER_EQUAL",
|
||||||
"LESS", "LESS_EQUAL",
|
"LESS", "LESS_EQUAL",
|
||||||
|
|
||||||
|
"PLUS_PLUS", "MINUS_MINUS",
|
||||||
|
|
||||||
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
||||||
|
|
||||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||||
|
|||||||
@ -64,6 +64,8 @@ private:
|
|||||||
std::shared_ptr<Expr> functionExpression();
|
std::shared_ptr<Expr> functionExpression();
|
||||||
|
|
||||||
sptr(Expr) assignment();
|
sptr(Expr) assignment();
|
||||||
|
sptr(Expr) increment(); // Parse increment/decrement expressions
|
||||||
|
sptr(Expr) postfix(); // Parse postfix operators
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Stmt>> block();
|
std::vector<std::shared_ptr<Stmt>> block();
|
||||||
|
|
||||||
@ -73,4 +75,7 @@ private:
|
|||||||
void enterFunction() { functionDepth++; }
|
void enterFunction() { functionDepth++; }
|
||||||
void exitFunction() { functionDepth--; }
|
void exitFunction() { functionDepth--; }
|
||||||
bool isInFunction() const { return functionDepth > 0; }
|
bool isInFunction() const { return functionDepth > 0; }
|
||||||
|
|
||||||
|
// Helper method for tail call detection
|
||||||
|
bool isTailCall(const std::shared_ptr<Expr>& expr);
|
||||||
};
|
};
|
||||||
@ -12,32 +12,38 @@ struct FunctionStmt;
|
|||||||
struct ReturnStmt;
|
struct ReturnStmt;
|
||||||
struct IfStmt;
|
struct IfStmt;
|
||||||
|
|
||||||
|
struct ExecutionContext {
|
||||||
|
bool isFunctionBody = false;
|
||||||
|
bool hasReturn = false;
|
||||||
|
Value returnValue;
|
||||||
|
};
|
||||||
|
|
||||||
struct StmtVisitor
|
struct StmtVisitor
|
||||||
{
|
{
|
||||||
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt) = 0;
|
virtual void visitBlockStmt(const std::shared_ptr<BlockStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt) = 0;
|
virtual void visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt) = 0;
|
virtual void visitVarStmt(const std::shared_ptr<VarStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt) = 0;
|
virtual void visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt) = 0;
|
virtual void visitReturnStmt(const std::shared_ptr<ReturnStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt) = 0;
|
virtual void visitIfStmt(const std::shared_ptr<IfStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||||
{
|
{
|
||||||
std::shared_ptr<Expr> expression;
|
std::shared_ptr<Expr> expression;
|
||||||
virtual void accept(StmtVisitor* visitor) = 0;
|
virtual void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual ~Stmt(){};
|
virtual ~Stmt(){};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlockStmt : Stmt
|
struct BlockStmt : Stmt
|
||||||
{
|
{
|
||||||
std::vector<std::shared_ptr<Stmt>> statements;
|
std::vector<std::shared_ptr<Stmt> > statements;
|
||||||
explicit BlockStmt(std::vector<std::shared_ptr<Stmt>> statements) : statements(statements)
|
explicit BlockStmt(std::vector<std::shared_ptr<Stmt> > statements) : statements(statements)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
void accept(StmtVisitor* visitor) override
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
{
|
{
|
||||||
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()));
|
visitor->visitBlockStmt(std::static_pointer_cast<BlockStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,9 +54,9 @@ struct ExpressionStmt : Stmt
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor) override
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
{
|
{
|
||||||
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()));
|
visitor->visitExpressionStmt(std::static_pointer_cast<ExpressionStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,9 +70,9 @@ struct VarStmt : Stmt
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor) override
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
{
|
{
|
||||||
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()));
|
visitor->visitVarStmt(std::static_pointer_cast<VarStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,14 +80,14 @@ struct FunctionStmt : Stmt
|
|||||||
{
|
{
|
||||||
const Token name;
|
const Token name;
|
||||||
const std::vector<Token> params;
|
const std::vector<Token> params;
|
||||||
std::vector<std::shared_ptr<Stmt>> body;
|
std::vector<std::shared_ptr<Stmt> > body;
|
||||||
|
|
||||||
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt>> body)
|
FunctionStmt(Token name, std::vector<Token> params, std::vector<std::shared_ptr<Stmt> > body)
|
||||||
: name(name), params(params), body(body) {}
|
: name(name), params(params), body(body) {}
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor) override
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
{
|
{
|
||||||
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()));
|
visitor->visitFunctionStmt(std::static_pointer_cast<FunctionStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,9 +98,9 @@ struct ReturnStmt : Stmt
|
|||||||
|
|
||||||
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
|
ReturnStmt(Token keyword, std::shared_ptr<Expr> value) : keyword(keyword), value(value) {}
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor) override
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
{
|
{
|
||||||
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()));
|
visitor->visitReturnStmt(std::static_pointer_cast<ReturnStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,8 +113,8 @@ struct IfStmt : Stmt
|
|||||||
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
|
IfStmt(std::shared_ptr<Expr> condition, std::shared_ptr<Stmt> thenBranch, std::shared_ptr<Stmt> elseBranch)
|
||||||
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
|
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
|
||||||
|
|
||||||
void accept(StmtVisitor* visitor) override
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override
|
||||||
{
|
{
|
||||||
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()));
|
visitor->visitIfStmt(std::static_pointer_cast<IfStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -169,6 +169,19 @@ struct Value {
|
|||||||
if (isNumber() && other.isString()) {
|
if (isNumber() && other.isString()) {
|
||||||
return Value(toString() + other.string_value);
|
return Value(toString() + other.string_value);
|
||||||
}
|
}
|
||||||
|
// Handle none values by converting to string
|
||||||
|
if (isString() && other.isNone()) {
|
||||||
|
return Value(string_value + "none");
|
||||||
|
}
|
||||||
|
if (isNone() && other.isString()) {
|
||||||
|
return Value("none" + other.string_value);
|
||||||
|
}
|
||||||
|
if (isString() && !other.isString() && !other.isNumber()) {
|
||||||
|
return Value(string_value + other.toString());
|
||||||
|
}
|
||||||
|
if (!isString() && !isNumber() && other.isString()) {
|
||||||
|
return Value(toString() + other.string_value);
|
||||||
|
}
|
||||||
throw std::runtime_error("Invalid operands for + operator");
|
throw std::runtime_error("Invalid operands for + operator");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,9 +24,6 @@ struct ReturnContext {
|
|||||||
ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {}
|
ReturnContext() : returnValue(NONE_VALUE), hasReturn(false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
static ReturnContext g_returnContext;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
Value Interpreter::visitLiteralExpr(const std::shared_ptr<LiteralExpr>& expr) {
|
||||||
if(expr->isNull) return NONE_VALUE;
|
if(expr->isNull) return NONE_VALUE;
|
||||||
@ -191,7 +188,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
double right_num = right.asNumber();
|
double right_num = right.asNumber();
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case PLUS: return Value(left_string + stringify(right));
|
case PLUS: return left + right;
|
||||||
case STAR: {
|
case STAR: {
|
||||||
if (!isWholeNumer(right_num)) {
|
if (!isWholeNumer(right_num)) {
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
@ -214,7 +211,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
std::string right_string = right.asString();
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case PLUS: return Value(stringify(left) + right_string);
|
case PLUS: return left + right;
|
||||||
case STAR: {
|
case STAR: {
|
||||||
if (!isWholeNumer(left_num)) {
|
if (!isWholeNumer(left_num)) {
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
@ -251,7 +248,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
std::string right_string = right.asString();
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case PLUS: return Value(stringify(left) + right_string);
|
case PLUS: return left + right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +257,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
bool right_bool = right.asBoolean();
|
bool right_bool = right.asBoolean();
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case PLUS: return Value(left_string + stringify(right));
|
case PLUS: return left + right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +324,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
return right; // Return the second value
|
return right; // Return the second value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case PLUS: return Value(left.asString() + stringify(right));
|
case PLUS: return left + right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,7 +346,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
return right; // Return the second value
|
return right; // Return the second value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case PLUS: return Value(stringify(left) + right.asString());
|
case PLUS: return left + right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +368,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
return right; // Return the second value
|
return right; // Return the second value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case PLUS: return Value(left.asString() + stringify(right));
|
case PLUS: return left + right;
|
||||||
case STAR: {
|
case STAR: {
|
||||||
if (!isWholeNumer(right_num)) {
|
if (!isWholeNumer(right_num)) {
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
@ -407,7 +404,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
return right; // Return the second value
|
return right; // Return the second value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case PLUS: return Value(stringify(left) + right.asString());
|
case PLUS: return left + right;
|
||||||
case STAR: {
|
case STAR: {
|
||||||
if (!isWholeNumer(left_num)) {
|
if (!isWholeNumer(left_num)) {
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
@ -429,7 +426,7 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
std::string right_string = right.asString();
|
std::string right_string = right.asString();
|
||||||
|
|
||||||
switch (expression->oper.type) {
|
switch (expression->oper.type) {
|
||||||
case PLUS: return Value("none" + right_string);
|
case PLUS: return left + right;
|
||||||
}
|
}
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
@ -437,6 +434,19 @@ Value Interpreter::visitBinaryExpr(const std::shared_ptr<BinaryExpr>& expression
|
|||||||
}
|
}
|
||||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
|
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on none and a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left.isString() && right.isNone()) {
|
||||||
|
std::string left_string = left.asString();
|
||||||
|
|
||||||
|
switch (expression->oper.type) {
|
||||||
|
case PLUS: return left + right;
|
||||||
|
}
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column, "Runtime Error",
|
||||||
|
"Cannot use '" + expression->oper.lexeme + "' on a string and none", expression->oper.lexeme);
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and none");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (errorReporter) {
|
if (errorReporter) {
|
||||||
@ -452,6 +462,53 @@ Value Interpreter::visitVarExpr(const std::shared_ptr<VarExpr>& expression)
|
|||||||
return environment->get(expression->name);
|
return environment->get(expression->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value Interpreter::visitIncrementExpr(const std::shared_ptr<IncrementExpr>& expression) {
|
||||||
|
// Get the current value of the operand
|
||||||
|
Value currentValue = evaluate(expression->operand);
|
||||||
|
|
||||||
|
if (!currentValue.isNumber()) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to numbers.", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to numbers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentNum = currentValue.asNumber();
|
||||||
|
double newValue;
|
||||||
|
|
||||||
|
// Determine the operation based on the operator
|
||||||
|
if (expression->oper.type == PLUS_PLUS) {
|
||||||
|
newValue = currentNum + 1.0;
|
||||||
|
} else if (expression->oper.type == MINUS_MINUS) {
|
||||||
|
newValue = currentNum - 1.0;
|
||||||
|
} else {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Invalid increment/decrement operator.", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid increment/decrement operator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the variable if it's a variable expression
|
||||||
|
if (auto varExpr = std::dynamic_pointer_cast<VarExpr>(expression->operand)) {
|
||||||
|
environment->assign(varExpr->name, Value(newValue));
|
||||||
|
} else {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(expression->oper.line, expression->oper.column,
|
||||||
|
"Runtime Error", "Increment/decrement can only be applied to variables.", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Increment/decrement can only be applied to variables.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the appropriate value based on prefix/postfix
|
||||||
|
if (expression->isPrefix) {
|
||||||
|
return Value(newValue); // Prefix: return new value
|
||||||
|
} else {
|
||||||
|
return currentValue; // Postfix: return old value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Interpreter::addStdLibFunctions() {
|
void Interpreter::addStdLibFunctions() {
|
||||||
// Add standard library functions to the environment
|
// Add standard library functions to the environment
|
||||||
StdLib::addToEnvironment(environment, *this, errorReporter);
|
StdLib::addToEnvironment(environment, *this, errorReporter);
|
||||||
@ -547,22 +604,19 @@ Value Interpreter::visitCallExpr(const std::shared_ptr<CallExpr>& expression) {
|
|||||||
environment->define(function->params[i], arguments[i]);
|
environment->define(function->params[i], arguments[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value returnValue = NONE_VALUE;
|
ExecutionContext context;
|
||||||
|
context.isFunctionBody = true;
|
||||||
|
|
||||||
for (const auto& stmt : function->body) {
|
for (const auto& stmt : function->body) {
|
||||||
// Reset return context for each statement
|
execute(stmt, &context);
|
||||||
g_returnContext.hasReturn = false;
|
if (context.hasReturn) {
|
||||||
g_returnContext.returnValue = NONE_VALUE;
|
environment = previousEnv;
|
||||||
|
return context.returnValue;
|
||||||
execute(stmt);
|
|
||||||
if (g_returnContext.hasReturn) {
|
|
||||||
returnValue = g_returnContext.returnValue;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
environment = previousEnv;
|
environment = previousEnv;
|
||||||
return returnValue;
|
return context.returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw std::runtime_error("Can only call functions and classes.");
|
throw std::runtime_error("Can only call functions and classes.");
|
||||||
@ -580,13 +634,13 @@ Value Interpreter::visitFunctionExpr(const std::shared_ptr<FunctionExpr>& expres
|
|||||||
return Value(function.get());
|
return Value(function.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement) {
|
void Interpreter::visitBlockStmt(const std::shared_ptr<BlockStmt>& statement, ExecutionContext* context) {
|
||||||
auto newEnv = std::make_shared<Environment>(environment);
|
auto newEnv = std::make_shared<Environment>(environment);
|
||||||
newEnv->setErrorReporter(errorReporter);
|
newEnv->setErrorReporter(errorReporter);
|
||||||
executeBlock(statement->statements, newEnv);
|
executeBlock(statement->statements, newEnv, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement) {
|
void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& statement, ExecutionContext* context) {
|
||||||
Value value = evaluate(statement->expression);
|
Value value = evaluate(statement->expression);
|
||||||
|
|
||||||
if(IsInteractive)
|
if(IsInteractive)
|
||||||
@ -595,7 +649,7 @@ void Interpreter::visitExpressionStmt(const std::shared_ptr<ExpressionStmt>& sta
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement)
|
void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement, ExecutionContext* context)
|
||||||
{
|
{
|
||||||
Value value = NONE_VALUE;
|
Value value = NONE_VALUE;
|
||||||
if(statement->initializer != nullptr)
|
if(statement->initializer != nullptr)
|
||||||
@ -608,7 +662,7 @@ void Interpreter::visitVarStmt(const std::shared_ptr<VarStmt>& statement)
|
|||||||
environment->define(statement->name.lexeme, value);
|
environment->define(statement->name.lexeme, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement)
|
void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& statement, ExecutionContext* context)
|
||||||
{
|
{
|
||||||
// Convert Token parameters to string parameters
|
// Convert Token parameters to string parameters
|
||||||
std::vector<std::string> paramNames;
|
std::vector<std::string> paramNames;
|
||||||
@ -624,46 +678,53 @@ void Interpreter::visitFunctionStmt(const std::shared_ptr<FunctionStmt>& stateme
|
|||||||
environment->define(statement->name.lexeme, Value(function.get()));
|
environment->define(statement->name.lexeme, Value(function.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement)
|
void Interpreter::visitReturnStmt(const std::shared_ptr<ReturnStmt>& statement, ExecutionContext* context)
|
||||||
{
|
{
|
||||||
Value value = NONE_VALUE;
|
Value value = NONE_VALUE;
|
||||||
if (statement->value != nullptr) {
|
if (statement->value != nullptr) {
|
||||||
value = evaluate(statement->value);
|
value = evaluate(statement->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_returnContext.hasReturn = true;
|
if (context && context->isFunctionBody) {
|
||||||
g_returnContext.returnValue = value;
|
context->hasReturn = true;
|
||||||
|
context->returnValue = value;
|
||||||
|
}
|
||||||
|
// If no context or not in function body, this is a top-level return (ignored)
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement)
|
void Interpreter::visitIfStmt(const std::shared_ptr<IfStmt>& statement, ExecutionContext* context)
|
||||||
{
|
{
|
||||||
if (isTruthy(evaluate(statement->condition))) {
|
if (isTruthy(evaluate(statement->condition))) {
|
||||||
execute(statement->thenBranch);
|
execute(statement->thenBranch, context);
|
||||||
} else if (statement->elseBranch != nullptr) {
|
} else if (statement->elseBranch != nullptr) {
|
||||||
execute(statement->elseBranch);
|
execute(statement->elseBranch, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt>> statements) {
|
void Interpreter::interpret(std::vector<std::shared_ptr<Stmt> > statements) {
|
||||||
for(const std::shared_ptr<Stmt>& s : statements)
|
for(const std::shared_ptr<Stmt>& s : statements)
|
||||||
{
|
{
|
||||||
execute(s);
|
execute(s, nullptr); // No context needed for top-level execution
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::execute(const std::shared_ptr<Stmt>& statement)
|
void Interpreter::execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context)
|
||||||
{
|
{
|
||||||
statement->accept(this);
|
statement->accept(this, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env)
|
void Interpreter::executeBlock(std::vector<std::shared_ptr<Stmt> > statements, std::shared_ptr<Environment> env, ExecutionContext* context)
|
||||||
{
|
{
|
||||||
std::shared_ptr<Environment> previous = this->environment;
|
std::shared_ptr<Environment> previous = this->environment;
|
||||||
this->environment = env;
|
this->environment = env;
|
||||||
|
|
||||||
for(const std::shared_ptr<Stmt>& s : statements)
|
for(const std::shared_ptr<Stmt>& s : statements)
|
||||||
{
|
{
|
||||||
execute(s);
|
execute(s, context);
|
||||||
|
if (context && context->hasReturn) {
|
||||||
|
this->environment = previous;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->environment = previous;
|
this->environment = previous;
|
||||||
@ -813,3 +874,5 @@ bool Interpreter::isWholeNumer(double num) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -54,22 +54,36 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
{
|
{
|
||||||
std::string token = std::string(1, t);
|
std::string token = std::string(1, t);
|
||||||
advance();
|
advance();
|
||||||
bool match = matchOn('=');
|
bool match = matchOn('+');
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push_back(Token{PLUS_EQUAL, "+=", line, column - 1});
|
token += '+';
|
||||||
|
tokens.push_back(Token{PLUS_PLUS, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
tokens.push_back(Token{PLUS, token, line, column - 1});
|
match = matchOn('=');
|
||||||
|
if(match) {
|
||||||
|
token += '=';
|
||||||
|
tokens.push_back(Token{PLUS_EQUAL, token, line, column - 1});
|
||||||
|
} else {
|
||||||
|
tokens.push_back(Token{PLUS, token, line, column - 1});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(t == '-')
|
else if(t == '-')
|
||||||
{
|
{
|
||||||
std::string token = std::string(1, t);
|
std::string token = std::string(1, t);
|
||||||
advance();
|
advance();
|
||||||
bool match = matchOn('=');
|
bool match = matchOn('-');
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push_back(Token{MINUS_EQUAL, "-=", line, column - 1});
|
token += '-';
|
||||||
|
tokens.push_back(Token{MINUS_MINUS, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
tokens.push_back(Token{MINUS, token, line, column - 1});
|
match = matchOn('=');
|
||||||
|
if(match) {
|
||||||
|
token += '=';
|
||||||
|
tokens.push_back(Token{MINUS_EQUAL, token, line, column - 1});
|
||||||
|
} else {
|
||||||
|
tokens.push_back(Token{MINUS, token, line, column - 1});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(t == '*')
|
else if(t == '*')
|
||||||
@ -78,7 +92,8 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
advance();
|
advance();
|
||||||
bool match = matchOn('=');
|
bool match = matchOn('=');
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push_back(Token{STAR_EQUAL, "*=", line, column - 1});
|
token += '=';
|
||||||
|
tokens.push_back(Token{STAR_EQUAL, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
tokens.push_back(Token{STAR, token, line, column - 1});
|
tokens.push_back(Token{STAR, token, line, column - 1});
|
||||||
}
|
}
|
||||||
@ -89,7 +104,8 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
advance();
|
advance();
|
||||||
bool match = matchOn('=');
|
bool match = matchOn('=');
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push_back(Token{PERCENT_EQUAL, "%=", line, column - 1});
|
token += '=';
|
||||||
|
tokens.push_back(Token{PERCENT_EQUAL, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
tokens.push_back(Token{PERCENT, token, line, column - 1});
|
tokens.push_back(Token{PERCENT, token, line, column - 1});
|
||||||
}
|
}
|
||||||
@ -166,13 +182,15 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
advance();
|
advance();
|
||||||
bool match = matchOn('&');
|
bool match = matchOn('&');
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push_back(Token{AND, "&&", line, column - 1});
|
token += '&';
|
||||||
|
tokens.push_back(Token{AND, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
bool equalMatch = matchOn('=');
|
bool equalMatch = matchOn('=');
|
||||||
if(equalMatch) {
|
if(equalMatch) {
|
||||||
tokens.push_back(Token{BIN_AND_EQUAL, "&=", line, column - 1});
|
token += '=';
|
||||||
|
tokens.push_back(Token{BIN_AND_EQUAL, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
tokens.push_back(Token{BIN_AND, "&", line, column - 1});
|
tokens.push_back(Token{BIN_AND, token, line, column - 1});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,13 +200,15 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
advance();
|
advance();
|
||||||
bool match = matchOn('|');
|
bool match = matchOn('|');
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push_back(Token{OR, "||", line, column - 1});
|
token += '|';
|
||||||
|
tokens.push_back(Token{OR, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
bool equalMatch = matchOn('=');
|
bool equalMatch = matchOn('=');
|
||||||
if(equalMatch) {
|
if(equalMatch) {
|
||||||
tokens.push_back(Token{BIN_OR_EQUAL, "|=", line, column - 1});
|
token += '=';
|
||||||
|
tokens.push_back(Token{BIN_OR_EQUAL, token, line, column - 1});
|
||||||
} else {
|
} else {
|
||||||
tokens.push_back(Token{BIN_OR, "|", line, column - 1});
|
tokens.push_back(Token{BIN_OR, token, line, column - 1});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,11 +237,28 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool equalMatch = matchOn('=');
|
bool starMatch = matchOn('*');
|
||||||
if(equalMatch) {
|
if(starMatch)
|
||||||
tokens.push_back(Token{SLASH_EQUAL, "/=", line, column - 1});
|
{
|
||||||
} else {
|
// Multi-line comment /* ... */
|
||||||
tokens.push_back(Token{SLASH, "/", line, column - 1});
|
while(!src.empty())
|
||||||
|
{
|
||||||
|
if(src[0] == '*' && !src.empty() && src.size() > 1 && src[1] == '/')
|
||||||
|
{
|
||||||
|
advance(2); // Skip */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool equalMatch = matchOn('=');
|
||||||
|
if(equalMatch) {
|
||||||
|
tokens.push_back(Token{SLASH_EQUAL, "/=", line, column - 1});
|
||||||
|
} else {
|
||||||
|
tokens.push_back(Token{SLASH, "/", line, column - 1});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,7 +101,7 @@ sptr(Expr) Parser::shift()
|
|||||||
|
|
||||||
sptr(Expr) Parser::assignment()
|
sptr(Expr) Parser::assignment()
|
||||||
{
|
{
|
||||||
sptr(Expr) expr = logical_or();
|
sptr(Expr) expr = increment();
|
||||||
|
|
||||||
if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL,
|
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}))
|
BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL}))
|
||||||
@ -124,6 +124,11 @@ sptr(Expr) Parser::assignment()
|
|||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::increment()
|
||||||
|
{
|
||||||
|
return logical_or();
|
||||||
|
}
|
||||||
|
|
||||||
sptr(Expr) Parser::equality()
|
sptr(Expr) Parser::equality()
|
||||||
{
|
{
|
||||||
sptr(Expr) expr = comparison();
|
sptr(Expr) expr = comparison();
|
||||||
@ -182,14 +187,51 @@ sptr(Expr) Parser::factor()
|
|||||||
|
|
||||||
sptr(Expr) Parser::unary()
|
sptr(Expr) Parser::unary()
|
||||||
{
|
{
|
||||||
if(match({BANG, MINUS, BIN_NOT}))
|
if(match({BANG, MINUS, BIN_NOT, PLUS_PLUS, MINUS_MINUS}))
|
||||||
{
|
{
|
||||||
Token op = previous();
|
Token op = previous();
|
||||||
sptr(Expr) right = unary();
|
sptr(Expr) right = unary();
|
||||||
|
|
||||||
|
// Handle prefix increment/decrement
|
||||||
|
if (op.type == PLUS_PLUS || op.type == MINUS_MINUS) {
|
||||||
|
// Ensure the operand is a variable
|
||||||
|
if (!std::dynamic_pointer_cast<VarExpr>(right)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(op.line, op.column, "Parse Error",
|
||||||
|
"Prefix increment/decrement can only be applied to variables", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Prefix increment/decrement can only be applied to variables.");
|
||||||
|
}
|
||||||
|
return msptr(IncrementExpr)(right, op, true); // true = prefix
|
||||||
|
}
|
||||||
|
|
||||||
return msptr(UnaryExpr)(op, right);
|
return msptr(UnaryExpr)(op, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
return primary();
|
return postfix();
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr(Expr) Parser::postfix()
|
||||||
|
{
|
||||||
|
sptr(Expr) expr = primary();
|
||||||
|
|
||||||
|
// Check for postfix increment/decrement
|
||||||
|
if (match({PLUS_PLUS, MINUS_MINUS})) {
|
||||||
|
Token oper = previous();
|
||||||
|
|
||||||
|
// Ensure the expression is a variable
|
||||||
|
if (!std::dynamic_pointer_cast<VarExpr>(expr)) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(oper.line, oper.column, "Parse Error",
|
||||||
|
"Postfix increment/decrement can only be applied to variables", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Postfix increment/decrement can only be applied to variables.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return msptr(IncrementExpr)(expr, oper, false); // false = postfix
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
sptr(Expr) Parser::primary()
|
sptr(Expr) Parser::primary()
|
||||||
@ -352,6 +394,15 @@ sptr(Stmt) Parser::ifStatement()
|
|||||||
return msptr(IfStmt)(condition, thenBranch, elseBranch);
|
return msptr(IfStmt)(condition, thenBranch, elseBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to detect if an expression is a tail call
|
||||||
|
bool Parser::isTailCall(const std::shared_ptr<Expr>& expr) {
|
||||||
|
// Check if this is a direct function call (no operations on the result)
|
||||||
|
if (auto callExpr = std::dynamic_pointer_cast<CallExpr>(expr)) {
|
||||||
|
return true; // Direct function call in return statement
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
sptr(Stmt) Parser::returnStatement()
|
sptr(Stmt) Parser::returnStatement()
|
||||||
{
|
{
|
||||||
Token keyword = previous();
|
Token keyword = previous();
|
||||||
@ -369,6 +420,13 @@ sptr(Stmt) Parser::returnStatement()
|
|||||||
|
|
||||||
if (!check(SEMICOLON)) {
|
if (!check(SEMICOLON)) {
|
||||||
value = expression();
|
value = expression();
|
||||||
|
|
||||||
|
// Check if this is a tail call and mark it
|
||||||
|
if (isTailCall(value)) {
|
||||||
|
if (auto callExpr = std::dynamic_pointer_cast<CallExpr>(value)) {
|
||||||
|
callExpr->isTailCall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(SEMICOLON, "Expected ';' after return value.");
|
consume(SEMICOLON, "Expected ';' after return value.");
|
||||||
|
|||||||
@ -238,4 +238,24 @@ void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& int
|
|||||||
|
|
||||||
// Store the shared_ptr in the interpreter to keep it alive
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
interpreter.addBuiltinFunction(toBooleanFunc);
|
interpreter.addBuiltinFunction(toBooleanFunc);
|
||||||
|
|
||||||
|
// Create a built-in exit function to terminate the program
|
||||||
|
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
|
||||||
|
[](std::vector<Value> args, int line, int column) -> Value {
|
||||||
|
int exitCode = 0; // Default exit code
|
||||||
|
|
||||||
|
if (args.size() > 0) {
|
||||||
|
if (args[0].isNumber()) {
|
||||||
|
exitCode = static_cast<int>(args[0].asNumber());
|
||||||
|
}
|
||||||
|
// If not a number, just use default exit code 0
|
||||||
|
}
|
||||||
|
|
||||||
|
std::exit(exitCode);
|
||||||
|
return NONE_VALUE; // This line should never be reached
|
||||||
|
});
|
||||||
|
env->define("exit", Value(exitFunc.get()));
|
||||||
|
|
||||||
|
// Store the shared_ptr in the interpreter to keep it alive
|
||||||
|
interpreter.addBuiltinFunction(exitFunc);
|
||||||
}
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
- binary number interpretation broken (0b will throw error, but 0ba will not)
|
|
||||||
@ -1,11 +1,11 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// BOB LANGUAGE - COMPREHENSIVE TEST SUITE
|
// BOB LANGUAGE TEST SUITE
|
||||||
// ========================================
|
// ========================================
|
||||||
// This file tests all currently implemented features of the Bob language
|
// Tests all implemented language features
|
||||||
// Run with: ./build/bob test_bob_language.bob
|
// Usage: ./build/bob test_bob_language.bob
|
||||||
|
|
||||||
print("=== BOB LANGUAGE COMPREHENSIVE TEST SUITE ===");
|
print("Bob Language Test Suite");
|
||||||
print("Testing all implemented features...");
|
print("Running feature tests...");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 1: BASIC DATA TYPES
|
// TEST 1: BASIC DATA TYPES
|
||||||
@ -15,25 +15,25 @@ print("\n--- Test 1: Basic Data Types ---");
|
|||||||
// String literals
|
// String literals
|
||||||
var stringVar = "Hello, Bob!";
|
var stringVar = "Hello, Bob!";
|
||||||
assert(toString(stringVar) == "Hello, Bob!", "String variable assignment");
|
assert(toString(stringVar) == "Hello, Bob!", "String variable assignment");
|
||||||
print(" ✓ String: " + toString(stringVar));
|
print(" String: " + toString(stringVar));
|
||||||
|
|
||||||
// Numbers (integers and floats)
|
// Numbers (integers and floats)
|
||||||
var intVar = 42;
|
var intVar = 42;
|
||||||
var floatVar = 3.14159;
|
var floatVar = 3.14159;
|
||||||
assert(toString(intVar) == "42", "Integer variable assignment");
|
assert(toString(intVar) == "42", "Integer variable assignment");
|
||||||
assert(toString(floatVar) == "3.14159", "Float variable assignment");
|
assert(toString(floatVar) == "3.14159", "Float variable assignment");
|
||||||
print(" ✓ Integer: " + toString(intVar));
|
print(" Integer: " + toString(intVar));
|
||||||
print(" ✓ Float: " + toString(floatVar));
|
print(" Float: " + toString(floatVar));
|
||||||
|
|
||||||
// Booleans
|
// Booleans
|
||||||
var boolTrue = true;
|
var boolTrue = true;
|
||||||
var boolFalse = false;
|
var boolFalse = false;
|
||||||
assert(boolTrue == true, "Boolean true assignment");
|
assert(boolTrue == true, "Boolean true assignment");
|
||||||
assert(boolFalse == false, "Boolean false assignment");
|
assert(boolFalse == false, "Boolean false assignment");
|
||||||
print(" ✓ Boolean true: " + toString(boolTrue));
|
print(" Boolean true: " + toString(boolTrue));
|
||||||
print(" ✓ Boolean false: " + toString(boolFalse));
|
print(" Boolean false: " + toString(boolFalse));
|
||||||
|
|
||||||
print("✓ Basic data types working");
|
print("Basic data types: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 2: ARITHMETIC OPERATIONS
|
// TEST 2: ARITHMETIC OPERATIONS
|
||||||
@ -43,39 +43,39 @@ print("\n--- Test 2: Arithmetic Operations ---");
|
|||||||
// Basic arithmetic
|
// Basic arithmetic
|
||||||
var addResult = 2 + 3;
|
var addResult = 2 + 3;
|
||||||
assert(toString(addResult) == "5", "Addition");
|
assert(toString(addResult) == "5", "Addition");
|
||||||
print(" ✓ Addition: 2 + 3 = " + toString(addResult));
|
print(" Addition: 2 + 3 = " + toString(addResult));
|
||||||
|
|
||||||
var subResult = 10 - 4;
|
var subResult = 10 - 4;
|
||||||
assert(toString(subResult) == "6", "Subtraction");
|
assert(toString(subResult) == "6", "Subtraction");
|
||||||
print(" ✓ Subtraction: 10 - 4 = " + toString(subResult));
|
print(" Subtraction: 10 - 4 = " + toString(subResult));
|
||||||
|
|
||||||
var mulResult = 6 * 7;
|
var mulResult = 6 * 7;
|
||||||
assert(toString(mulResult) == "42", "Multiplication");
|
assert(toString(mulResult) == "42", "Multiplication");
|
||||||
print(" ✓ Multiplication: 6 * 7 = " + toString(mulResult));
|
print(" Multiplication: 6 * 7 = " + toString(mulResult));
|
||||||
|
|
||||||
var divResult = 20 / 4;
|
var divResult = 20 / 4;
|
||||||
assert(toString(divResult) == "5", "Division");
|
assert(toString(divResult) == "5", "Division");
|
||||||
print(" ✓ Division: 20 / 4 = " + toString(divResult));
|
print(" Division: 20 / 4 = " + toString(divResult));
|
||||||
|
|
||||||
// Negative numbers
|
// Negative numbers
|
||||||
var negResult = -42;
|
var negResult = -42;
|
||||||
assert(toString(negResult) == "-42", "Negative numbers");
|
assert(toString(negResult) == "-42", "Negative numbers");
|
||||||
print(" ✓ Negative: " + toString(negResult));
|
print(" Negative: " + toString(negResult));
|
||||||
|
|
||||||
var negCalc = 5 - 10;
|
var negCalc = 5 - 10;
|
||||||
assert(toString(negCalc) == "-5", "Negative result");
|
assert(toString(negCalc) == "-5", "Negative result");
|
||||||
print(" ✓ Negative calculation: 5 - 10 = " + toString(negCalc));
|
print(" Negative calculation: 5 - 10 = " + toString(negCalc));
|
||||||
|
|
||||||
// Order of operations
|
// Order of operations
|
||||||
var orderResult1 = 2 + 3 * 4;
|
var orderResult1 = 2 + 3 * 4;
|
||||||
assert(toString(orderResult1) == "14", "Order of operations (multiplication first)");
|
assert(toString(orderResult1) == "14", "Order of operations (multiplication first)");
|
||||||
print(" ✓ Order of operations: 2 + 3 * 4 = " + toString(orderResult1));
|
print(" Order of operations: 2 + 3 * 4 = " + toString(orderResult1));
|
||||||
|
|
||||||
var orderResult2 = (2 + 3) * 4;
|
var orderResult2 = (2 + 3) * 4;
|
||||||
assert(toString(orderResult2) == "20", "Parentheses override order of operations");
|
assert(toString(orderResult2) == "20", "Parentheses override order of operations");
|
||||||
print(" ✓ Parentheses: (2 + 3) * 4 = " + toString(orderResult2));
|
print(" Parentheses: (2 + 3) * 4 = " + toString(orderResult2));
|
||||||
|
|
||||||
print("✓ Arithmetic operations working");
|
print("Arithmetic operations: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 3: STRING OPERATIONS
|
// TEST 3: STRING OPERATIONS
|
||||||
@ -85,15 +85,15 @@ print("\n--- Test 3: String Operations ---");
|
|||||||
// String concatenation
|
// String concatenation
|
||||||
var concatResult = "Hello" + " " + "World";
|
var concatResult = "Hello" + " " + "World";
|
||||||
assert(toString(concatResult) == "Hello World", "String concatenation");
|
assert(toString(concatResult) == "Hello World", "String concatenation");
|
||||||
print(" ✓ String concatenation: " + toString(concatResult));
|
print(" String concatenation: " + toString(concatResult));
|
||||||
|
|
||||||
var firstName = "Bob";
|
var firstName = "Bob";
|
||||||
var lastName = "Lucero";
|
var lastName = "Lucero";
|
||||||
var nameResult = firstName + " " + lastName;
|
var nameResult = firstName + " " + lastName;
|
||||||
assert(toString(nameResult) == "Bob Lucero", "Variable string concatenation");
|
assert(toString(nameResult) == "Bob Lucero", "Variable string concatenation");
|
||||||
print(" ✓ Variable concatenation: " + toString(nameResult));
|
print(" Variable concatenation: " + toString(nameResult));
|
||||||
|
|
||||||
print("✓ String operations working");
|
print("String operations: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 3.5: STRING + NUMBER CONCATENATION
|
// TEST 3.5: STRING + NUMBER CONCATENATION
|
||||||
@ -103,60 +103,60 @@ print("\n--- Test 3.5: String + Number Concatenation ---");
|
|||||||
// Test string + number (automatic conversion)
|
// Test string + number (automatic conversion)
|
||||||
var strNumResult = "String + Number: " + 42;
|
var strNumResult = "String + Number: " + 42;
|
||||||
assert(toString(strNumResult) == "String + Number: 42", "String + Number");
|
assert(toString(strNumResult) == "String + Number: 42", "String + Number");
|
||||||
print(" ✓ String + Number: " + toString(strNumResult));
|
print(" String + Number: " + toString(strNumResult));
|
||||||
|
|
||||||
var strFloatResult = "String + Float: " + 3.14;
|
var strFloatResult = "String + Float: " + 3.14;
|
||||||
assert(toString(strFloatResult) == "String + Float: 3.14", "String + Float");
|
assert(toString(strFloatResult) == "String + Float: 3.14", "String + Float");
|
||||||
print(" ✓ String + Float: " + toString(strFloatResult));
|
print(" String + Float: " + toString(strFloatResult));
|
||||||
|
|
||||||
var zeroResult = "Zero: " + 0;
|
var zeroResult = "Zero: " + 0;
|
||||||
assert(toString(zeroResult) == "Zero: 0", "Zero formatting");
|
assert(toString(zeroResult) == "Zero: 0", "Zero formatting");
|
||||||
print(" ✓ Zero formatting: " + toString(zeroResult));
|
print(" Zero formatting: " + toString(zeroResult));
|
||||||
|
|
||||||
var negResult = "Negative: " + -10;
|
var negResult = "Negative: " + -10;
|
||||||
assert(toString(negResult) == "Negative: -10", "Negative formatting");
|
assert(toString(negResult) == "Negative: -10", "Negative formatting");
|
||||||
print(" ✓ Negative formatting: " + toString(negResult));
|
print(" Negative formatting: " + toString(negResult));
|
||||||
|
|
||||||
// Test number + string (automatic conversion)
|
// Test number + string (automatic conversion)
|
||||||
var numStrResult = 5 + " times";
|
var numStrResult = 5 + " times";
|
||||||
assert(toString(numStrResult) == "5 times", "Number + String");
|
assert(toString(numStrResult) == "5 times", "Number + String");
|
||||||
print(" ✓ Number + String: " + toString(numStrResult));
|
print(" Number + String: " + toString(numStrResult));
|
||||||
|
|
||||||
var floatStrResult = 3.14 + " is pi";
|
var floatStrResult = 3.14 + " is pi";
|
||||||
assert(toString(floatStrResult) == "3.14 is pi", "Float + String");
|
assert(toString(floatStrResult) == "3.14 is pi", "Float + String");
|
||||||
print(" ✓ Float + String: " + toString(floatStrResult));
|
print(" Float + String: " + toString(floatStrResult));
|
||||||
|
|
||||||
var zeroStrResult = 0 + " items";
|
var zeroStrResult = 0 + " items";
|
||||||
assert(toString(zeroStrResult) == "0 items", "Zero + String");
|
assert(toString(zeroStrResult) == "0 items", "Zero + String");
|
||||||
print(" ✓ Zero + String: " + toString(zeroStrResult));
|
print(" Zero + String: " + toString(zeroStrResult));
|
||||||
|
|
||||||
// Test significant digits formatting (no trailing zeros)
|
// Test significant digits formatting (no trailing zeros)
|
||||||
var trailingResult = "Trailing zeros: " + 2.0;
|
var trailingResult = "Trailing zeros: " + 2.0;
|
||||||
assert(toString(trailingResult) == "Trailing zeros: 2", "Trailing zeros removed");
|
assert(toString(trailingResult) == "Trailing zeros: 2", "Trailing zeros removed");
|
||||||
print(" ✓ Trailing zeros: " + toString(trailingResult));
|
print(" Trailing zeros: " + toString(trailingResult));
|
||||||
|
|
||||||
var piResult = "Pi: " + 3.14;
|
var piResult = "Pi: " + 3.14;
|
||||||
assert(toString(piResult) == "Pi: 3.14", "Float formatting");
|
assert(toString(piResult) == "Pi: 3.14", "Float formatting");
|
||||||
print(" ✓ Pi formatting: " + toString(piResult));
|
print(" Pi formatting: " + toString(piResult));
|
||||||
|
|
||||||
var eResult = "E: " + 2.718;
|
var eResult = "E: " + 2.718;
|
||||||
assert(toString(eResult) == "E: 2.718", "Float formatting");
|
assert(toString(eResult) == "E: 2.718", "Float formatting");
|
||||||
print(" ✓ E formatting: " + toString(eResult));
|
print(" E formatting: " + toString(eResult));
|
||||||
|
|
||||||
var simpleResult = "Simple: " + 1.5;
|
var simpleResult = "Simple: " + 1.5;
|
||||||
assert(toString(simpleResult) == "Simple: 1.5", "Float formatting");
|
assert(toString(simpleResult) == "Simple: 1.5", "Float formatting");
|
||||||
print(" ✓ Simple formatting: " + toString(simpleResult));
|
print(" Simple formatting: " + toString(simpleResult));
|
||||||
|
|
||||||
// Test string multiplication
|
// Test string multiplication
|
||||||
var strMulResult = "hello" * 3;
|
var strMulResult = "hello" * 3;
|
||||||
assert(toString(strMulResult) == "hellohellohello", "String multiplication");
|
assert(toString(strMulResult) == "hellohellohello", "String multiplication");
|
||||||
print(" ✓ String multiplication: " + toString(strMulResult));
|
print(" String multiplication: " + toString(strMulResult));
|
||||||
|
|
||||||
var numStrMulResult = 3 * "hello";
|
var numStrMulResult = 3 * "hello";
|
||||||
assert(toString(numStrMulResult) == "hellohellohello", "Number * string multiplication");
|
assert(toString(numStrMulResult) == "hellohellohello", "Number * string multiplication");
|
||||||
print(" ✓ Number * string: " + toString(numStrMulResult));
|
print(" Number * string: " + toString(numStrMulResult));
|
||||||
|
|
||||||
print("✓ String + Number concatenation working");
|
print("String + Number concatenation: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 3.6: ESCAPE SEQUENCES
|
// TEST 3.6: ESCAPE SEQUENCES
|
||||||
@ -187,7 +187,7 @@ assert(mixedTest == "First line\n\tIndented line\n\t\tDouble indented", "Mixed e
|
|||||||
var concatTest = "Hello" + "\n" + "World";
|
var concatTest = "Hello" + "\n" + "World";
|
||||||
assert(concatTest == "Hello\nWorld", "Escape sequences in concatenation");
|
assert(concatTest == "Hello\nWorld", "Escape sequences in concatenation");
|
||||||
|
|
||||||
print("✓ Escape sequences working");
|
print("Escape sequences: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 4: COMPARISON OPERATORS
|
// TEST 4: COMPARISON OPERATORS
|
||||||
@ -209,7 +209,7 @@ assert(true == true, "Boolean equality");
|
|||||||
assert(false == false, "Boolean equality");
|
assert(false == false, "Boolean equality");
|
||||||
assert(true != false, "Boolean inequality");
|
assert(true != false, "Boolean inequality");
|
||||||
|
|
||||||
print("✓ Comparison operators working");
|
print("Comparison operators: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 5: VARIABLE ASSIGNMENT
|
// TEST 5: VARIABLE ASSIGNMENT
|
||||||
@ -225,7 +225,7 @@ assert(x == 20, "Variable reassignment");
|
|||||||
var y = x;
|
var y = x;
|
||||||
assert(y == 20, "Variable to variable assignment");
|
assert(y == 20, "Variable to variable assignment");
|
||||||
|
|
||||||
print("✓ Variable assignment working");
|
print("Variable assignment: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 6: FUNCTIONS
|
// TEST 6: FUNCTIONS
|
||||||
@ -255,7 +255,7 @@ func getAnswer() {
|
|||||||
|
|
||||||
assert(getAnswer() == 42, "Function with no parameters");
|
assert(getAnswer() == 42, "Function with no parameters");
|
||||||
|
|
||||||
print("✓ Functions working");
|
print("Functions: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 7: NESTED FUNCTION CALLS
|
// TEST 7: NESTED FUNCTION CALLS
|
||||||
@ -274,7 +274,7 @@ func subtract(a, b) {
|
|||||||
assert(add(add(1, 2), add(3, 4)) == 10, "Nested addition");
|
assert(add(add(1, 2), add(3, 4)) == 10, "Nested addition");
|
||||||
assert(multiply(add(2, 3), subtract(10, 4)) == 30, "Complex nested calls");
|
assert(multiply(add(2, 3), subtract(10, 4)) == 30, "Complex nested calls");
|
||||||
|
|
||||||
print("✓ Nested function calls working");
|
print("Nested function calls: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 8: VARIABLE SCOPING
|
// TEST 8: VARIABLE SCOPING
|
||||||
@ -292,7 +292,7 @@ func testScope() {
|
|||||||
|
|
||||||
assert(testScope() == 150, "Function accessing both local and global variables");
|
assert(testScope() == 150, "Function accessing both local and global variables");
|
||||||
|
|
||||||
print("✓ Variable scoping working");
|
print("Variable scoping: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 9: CLOSURES
|
// TEST 9: CLOSURES
|
||||||
@ -318,7 +318,7 @@ func makeCounter() {
|
|||||||
|
|
||||||
assert(makeCounter() == 1, "Closure with captured variable");
|
assert(makeCounter() == 1, "Closure with captured variable");
|
||||||
|
|
||||||
print("✓ Closures working");
|
print("Closures: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 10: COMPLEX EXPRESSIONS
|
// TEST 10: COMPLEX EXPRESSIONS
|
||||||
@ -335,7 +335,7 @@ assert((a + b) * (a - b) == 75, "Complex expression with parentheses");
|
|||||||
var name = "Bob";
|
var name = "Bob";
|
||||||
assert("Hello, " + name + "!" == "Hello, Bob!", "String expression with variables");
|
assert("Hello, " + name + "!" == "Hello, Bob!", "String expression with variables");
|
||||||
|
|
||||||
print("✓ Complex expressions working");
|
print("Complex expressions: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 11: EDGE CASES
|
// TEST 11: EDGE CASES
|
||||||
@ -352,7 +352,7 @@ assert("" == "", "Empty string comparison");
|
|||||||
// Negative zero
|
// Negative zero
|
||||||
assert(-0 == 0, "Negative zero equals zero");
|
assert(-0 == 0, "Negative zero equals zero");
|
||||||
|
|
||||||
print("✓ Edge cases working");
|
print("Edge cases: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 12: PRINT FUNCTION
|
// TEST 12: PRINT FUNCTION
|
||||||
@ -367,7 +367,7 @@ print(true);
|
|||||||
print(false);
|
print(false);
|
||||||
print(3.14);
|
print(3.14);
|
||||||
|
|
||||||
print("✓ Print function working");
|
print("Print function: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 13: ASSERT FUNCTION
|
// TEST 13: ASSERT FUNCTION
|
||||||
@ -382,7 +382,7 @@ assert(5 > 3, "Comparison assertion");
|
|||||||
assert(10 == 10, "Custom message assertion");
|
assert(10 == 10, "Custom message assertion");
|
||||||
assert("hello" == "hello", "String assertion with message");
|
assert("hello" == "hello", "String assertion with message");
|
||||||
|
|
||||||
print("✓ Assert function working");
|
print("Assert function: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 14: ERROR HANDLING
|
// TEST 14: ERROR HANDLING
|
||||||
@ -398,7 +398,7 @@ print("\n--- Test 14: Error Handling ---");
|
|||||||
// Undefined variable should error
|
// Undefined variable should error
|
||||||
// assert(undefinedVar == 0, "This should fail");
|
// assert(undefinedVar == 0, "This should fail");
|
||||||
|
|
||||||
print("✓ Error handling framework in place");
|
print("Error handling framework in place");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 15: FUNCTION PASSING - BASIC
|
// TEST 15: FUNCTION PASSING - BASIC
|
||||||
@ -415,7 +415,7 @@ func testBasic(func1) {
|
|||||||
|
|
||||||
var result1 = testBasic(greet2);
|
var result1 = testBasic(greet2);
|
||||||
assert(result1 == "Hello, Alice", "Basic function passing");
|
assert(result1 == "Hello, Alice", "Basic function passing");
|
||||||
print("✓ Basic function passing works");
|
print("Basic function passing works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 16: FUNCTION PASSING - MULTIPLE PARAMETERS
|
// TEST 16: FUNCTION PASSING - MULTIPLE PARAMETERS
|
||||||
@ -428,7 +428,7 @@ func applyTwo(func1, func2, x, y) {
|
|||||||
|
|
||||||
var result2 = applyTwo(add, multiply, 3, 4);
|
var result2 = applyTwo(add, multiply, 3, 4);
|
||||||
assert(result2 == 19, "Multiple function parameters (3+4 + 3*4 = 19)");
|
assert(result2 == 19, "Multiple function parameters (3+4 + 3*4 = 19)");
|
||||||
print("✓ Multiple function parameters work");
|
print("Multiple function parameters work");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 17: FUNCTION PASSING - NESTED CALLS
|
// TEST 17: FUNCTION PASSING - NESTED CALLS
|
||||||
@ -445,7 +445,7 @@ func applyNested(func1, func2, x, y) {
|
|||||||
|
|
||||||
var result3 = applyNested(square, add, 3, 4);
|
var result3 = applyNested(square, add, 3, 4);
|
||||||
assert(result3 == 49, "Nested function calls ((3+4)^2 = 49)");
|
assert(result3 == 49, "Nested function calls ((3+4)^2 = 49)");
|
||||||
print("✓ Nested function calls work");
|
print("Nested function calls work");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 18: FUNCTION PASSING - COMPOSITION
|
// TEST 18: FUNCTION PASSING - COMPOSITION
|
||||||
@ -466,7 +466,7 @@ func compose(func1, func2, x) {
|
|||||||
|
|
||||||
var result4 = compose(double, addOne, 5);
|
var result4 = compose(double, addOne, 5);
|
||||||
assert(result4 == 12, "Function composition ((5+1)*2 = 12)");
|
assert(result4 == 12, "Function composition ((5+1)*2 = 12)");
|
||||||
print("✓ Function composition works");
|
print("Function composition works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 19: FUNCTION PASSING - CALLBACK PATTERNS
|
// TEST 19: FUNCTION PASSING - CALLBACK PATTERNS
|
||||||
@ -483,7 +483,7 @@ func formatString(str) {
|
|||||||
|
|
||||||
var result5 = processString("test", formatString);
|
var result5 = processString("test", formatString);
|
||||||
assert(result5 == "[test processed]", "Callback pattern with string processing");
|
assert(result5 == "[test processed]", "Callback pattern with string processing");
|
||||||
print("✓ Callback patterns work");
|
print("Callback patterns work");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 20: FUNCTION PASSING - DIRECT CALLING
|
// TEST 20: FUNCTION PASSING - DIRECT CALLING
|
||||||
@ -510,7 +510,7 @@ var result6a = callFirst(positive, negative, 5);
|
|||||||
var result6b = callSecond(positive, negative, 5);
|
var result6b = callSecond(positive, negative, 5);
|
||||||
assert(result6a == "positive: 5", "Direct call - first function");
|
assert(result6a == "positive: 5", "Direct call - first function");
|
||||||
assert(result6b == "negative: 5", "Direct call - second function");
|
assert(result6b == "negative: 5", "Direct call - second function");
|
||||||
print("✓ Direct function calling works");
|
print("Direct function calling works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 21: FUNCTION PASSING - STORAGE AND RETRIEVAL
|
// TEST 21: FUNCTION PASSING - STORAGE AND RETRIEVAL
|
||||||
@ -525,7 +525,7 @@ var result7a = callStoredFunction(add, 3, 4);
|
|||||||
var result7b = callStoredFunction(multiply, 3, 4);
|
var result7b = callStoredFunction(multiply, 3, 4);
|
||||||
assert(result7a == 7, "Stored function call - add");
|
assert(result7a == 7, "Stored function call - add");
|
||||||
assert(result7b == 12, "Stored function call - multiply");
|
assert(result7b == 12, "Stored function call - multiply");
|
||||||
print("✓ Function storage and retrieval works");
|
print("Function storage and retrieval works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 22: FUNCTION PASSING - MULTIPLE APPLICATIONS
|
// TEST 22: FUNCTION PASSING - MULTIPLE APPLICATIONS
|
||||||
@ -542,7 +542,7 @@ func square2(x) {
|
|||||||
|
|
||||||
var result8 = applyMultiple(square2, 2, 3, 4);
|
var result8 = applyMultiple(square2, 2, 3, 4);
|
||||||
assert(result8 == 29, "Multiple function applications (4 + 9 + 16 = 29)");
|
assert(result8 == 29, "Multiple function applications (4 + 9 + 16 = 29)");
|
||||||
print("✓ Multiple function applications work");
|
print("Multiple function applications work");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 23: FUNCTION PASSING - NO PARAMETERS
|
// TEST 23: FUNCTION PASSING - NO PARAMETERS
|
||||||
@ -555,7 +555,7 @@ func callNoParams(func1) {
|
|||||||
|
|
||||||
var result9 = callNoParams(getAnswer);
|
var result9 = callNoParams(getAnswer);
|
||||||
assert(result9 == 42, "Function with no parameters");
|
assert(result9 == 42, "Function with no parameters");
|
||||||
print("✓ Functions with no parameters work");
|
print("Functions with no parameters work");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 24: FUNCTION PASSING - MANY PARAMETERS
|
// TEST 24: FUNCTION PASSING - MANY PARAMETERS
|
||||||
@ -572,7 +572,7 @@ func callManyParams(func1) {
|
|||||||
|
|
||||||
var result10 = callManyParams(sumMany);
|
var result10 = callManyParams(sumMany);
|
||||||
assert(result10 == 15, "Function with many parameters");
|
assert(result10 == 15, "Function with many parameters");
|
||||||
print("✓ Functions with many parameters work");
|
print("Functions with many parameters work");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 24.5: FUNCTION WITH 100 PARAMETERS
|
// TEST 24.5: FUNCTION WITH 100 PARAMETERS
|
||||||
@ -589,7 +589,7 @@ func call100Params(func1) {
|
|||||||
|
|
||||||
var result100 = call100Params(sum100);
|
var result100 = call100Params(sum100);
|
||||||
assert(result100 == 100, "Function with 100 parameters (all 1s = 100)");
|
assert(result100 == 100, "Function with 100 parameters (all 1s = 100)");
|
||||||
print("✓ Function with 100 parameters works");
|
print("Function with 100 parameters works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 25: FUNCTION PASSING - IDENTITY
|
// TEST 25: FUNCTION PASSING - IDENTITY
|
||||||
@ -606,7 +606,7 @@ func applyIdentity(func1, x) {
|
|||||||
|
|
||||||
var result11 = applyIdentity(identity, "test");
|
var result11 = applyIdentity(identity, "test");
|
||||||
assert(result11 == "test", "Function identity");
|
assert(result11 == "test", "Function identity");
|
||||||
print("✓ Function identity works");
|
print("Function identity works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 26: FUNCTION PASSING - CONSTANT FUNCTION
|
// TEST 26: FUNCTION PASSING - CONSTANT FUNCTION
|
||||||
@ -623,7 +623,7 @@ func applyConstant(func1, x) {
|
|||||||
|
|
||||||
var result12 = applyConstant(constant, "anything");
|
var result12 = applyConstant(constant, "anything");
|
||||||
assert(result12 == 42, "Constant function");
|
assert(result12 == 42, "Constant function");
|
||||||
print("✓ Constant function works");
|
print("Constant function works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 27: FUNCTION PASSING - FUNCTION COMPARISON
|
// TEST 27: FUNCTION PASSING - FUNCTION COMPARISON
|
||||||
@ -643,7 +643,7 @@ func compareFunctions(f1, f2, x) {
|
|||||||
|
|
||||||
var result13 = compareFunctions(func1, func2, 5);
|
var result13 = compareFunctions(func1, func2, 5);
|
||||||
assert(result13 == true, "Function comparison");
|
assert(result13 == true, "Function comparison");
|
||||||
print("✓ Function comparison works");
|
print("Function comparison works");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 28: COMPREHENSIVE NUMBER TESTS
|
// TEST 28: COMPREHENSIVE NUMBER TESTS
|
||||||
@ -756,7 +756,7 @@ assert(hex3 == 4294967295, "Hex FFFFFFFF should be 4294967295");
|
|||||||
var add_num = 5 + 3;
|
var add_num = 5 + 3;
|
||||||
var sub_num = 10 - 4;
|
var sub_num = 10 - 4;
|
||||||
var mul_num = 6 * 7;
|
var mul_num = 6 * 7;
|
||||||
var div_num = 15 /* 3;
|
var div_num = 15 / 3;
|
||||||
assert(add_num == 8, "5 + 3 should be 8");
|
assert(add_num == 8, "5 + 3 should be 8");
|
||||||
assert(sub_num == 6, "10 - 4 should be 6");
|
assert(sub_num == 6, "10 - 4 should be 6");
|
||||||
assert(mul_num == 42, "6 * 7 should be 42");
|
assert(mul_num == 42, "6 * 7 should be 42");
|
||||||
@ -780,7 +780,7 @@ assert(lt2 == false, "5 < 3 should be false");
|
|||||||
assert(gt1 == true, "7 > 4 should be true");
|
assert(gt1 == true, "7 > 4 should be true");
|
||||||
assert(gt2 == false, "2 > 8 should be false");
|
assert(gt2 == false, "2 > 8 should be false");
|
||||||
|
|
||||||
print("✓ Comprehensive number tests working");
|
print("Comprehensive number tests: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 29: TIME FUNCTION
|
// TEST 29: TIME FUNCTION
|
||||||
@ -794,7 +794,7 @@ assert(start_time > 0, "Start time should be positive");
|
|||||||
assert(end_time >= start_time, "End time should be >= start time");
|
assert(end_time >= start_time, "End time should be >= start time");
|
||||||
assert(duration >= 0, "Duration should be non-negative");
|
assert(duration >= 0, "Duration should be non-negative");
|
||||||
|
|
||||||
print("✓ Time function working");
|
print("Time function: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 30: BOOLEAN + STRING CONCATENATION
|
// TEST 30: BOOLEAN + STRING CONCATENATION
|
||||||
@ -822,7 +822,7 @@ assert(ne_test == true, "5 != 6 should be true");
|
|||||||
assert(lt_test == true, "3 < 5 should be true");
|
assert(lt_test == true, "3 < 5 should be true");
|
||||||
assert(gt_test == true, "7 > 4 should be true");
|
assert(gt_test == true, "7 > 4 should be true");
|
||||||
|
|
||||||
print("✓ Boolean + String concatenation working");
|
print("Boolean + String concatenation: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 31: IF STATEMENTS
|
// TEST 31: IF STATEMENTS
|
||||||
@ -831,12 +831,12 @@ print("\n--- Test 31: If Statements ---");
|
|||||||
|
|
||||||
// Basic if statement
|
// Basic if statement
|
||||||
if (true) {
|
if (true) {
|
||||||
print("✓ Basic if statement working");
|
print("Basic if statement: PASS");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If-else statement
|
// If-else statement
|
||||||
if (true) {
|
if (true) {
|
||||||
print("✓ If branch executed");
|
print("If branch executed");
|
||||||
} else {
|
} else {
|
||||||
print("✗ Else branch should not execute");
|
print("✗ Else branch should not execute");
|
||||||
}
|
}
|
||||||
@ -844,14 +844,14 @@ if (true) {
|
|||||||
if (false) {
|
if (false) {
|
||||||
print("✗ If branch should not execute");
|
print("✗ If branch should not execute");
|
||||||
} else {
|
} else {
|
||||||
print("✓ Else branch executed");
|
print("Else branch executed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If-else if-else chain
|
// If-else if-else chain
|
||||||
if (false) {
|
if (false) {
|
||||||
print("✗ First if should not execute");
|
print("✗ First if should not execute");
|
||||||
} else if (true) {
|
} else if (true) {
|
||||||
print("✓ Else if branch executed");
|
print("Else if branch executed");
|
||||||
} else {
|
} else {
|
||||||
print("✗ Final else should not execute");
|
print("✗ Final else should not execute");
|
||||||
}
|
}
|
||||||
@ -859,22 +859,22 @@ if (false) {
|
|||||||
// Nested if statements
|
// Nested if statements
|
||||||
if (true) {
|
if (true) {
|
||||||
if (true) {
|
if (true) {
|
||||||
print("✓ Nested if statements working");
|
print("Nested if statements: PASS");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single-line if statements
|
// Single-line if statements
|
||||||
if (true) print("✓ Single-line if working");
|
if (true) print("Single-line if: PASS");
|
||||||
if (false) print("✗ Single-line if should not execute");
|
if (false) print("✗ Single-line if should not execute");
|
||||||
|
|
||||||
// Complex conditions
|
// Complex conditions
|
||||||
var if_a = 5;
|
var if_a = 5;
|
||||||
var if_b = 3;
|
var if_b = 3;
|
||||||
if (if_a > if_b) {
|
if (if_a > if_b) {
|
||||||
print("✓ Complex condition working");
|
print("Complex condition: PASS");
|
||||||
}
|
}
|
||||||
|
|
||||||
print("✓ If statements working");
|
print("If statements: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 32: INPUT FUNCTION
|
// TEST 32: INPUT FUNCTION
|
||||||
@ -887,7 +887,7 @@ assert(type(input_func) == "builtin_function", "Input function should be a built
|
|||||||
|
|
||||||
// Test input with no arguments (would pause for user input)
|
// Test input with no arguments (would pause for user input)
|
||||||
// Note: We can't test actual input without user interaction
|
// Note: We can't test actual input without user interaction
|
||||||
print("✓ Input function available");
|
print("Input function available");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 33: TYPE FUNCTION
|
// TEST 33: TYPE FUNCTION
|
||||||
@ -916,7 +916,7 @@ assert(type(testFunc()) == "number", "Type of function call should be 'number'")
|
|||||||
assert(type(5 + 3) == "number", "Type of arithmetic should be 'number'");
|
assert(type(5 + 3) == "number", "Type of arithmetic should be 'number'");
|
||||||
assert(type("hello" + "world") == "string", "Type of string concat should be 'string'");
|
assert(type("hello" + "world") == "string", "Type of string concat should be 'string'");
|
||||||
|
|
||||||
print("✓ Type function working");
|
print("Type function: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 34: TONUMBER FUNCTION
|
// TEST 34: TONUMBER FUNCTION
|
||||||
@ -943,7 +943,7 @@ assert(toNumber("0.0") == 0, "toNumber should handle 0.0");
|
|||||||
assert(toNumber("1e6") == 1000000, "toNumber should handle scientific notation");
|
assert(toNumber("1e6") == 1000000, "toNumber should handle scientific notation");
|
||||||
assert(toNumber("1.23e-4") == 0.000123, "toNumber should handle small scientific notation");
|
assert(toNumber("1.23e-4") == 0.000123, "toNumber should handle small scientific notation");
|
||||||
|
|
||||||
print("✓ toNumber function working");
|
print("toNumber function: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 35: TOSTRING FUNCTION
|
// TEST 35: TOSTRING FUNCTION
|
||||||
@ -986,7 +986,7 @@ assert(toString(type(toString(42))) == toString("string"), "toString should hand
|
|||||||
assert(toString(42) == toString(42), "toString should match itself");
|
assert(toString(42) == toString(42), "toString should match itself");
|
||||||
assert(toString(3.14) == toString(3.14), "toString should match itself");
|
assert(toString(3.14) == toString(3.14), "toString should match itself");
|
||||||
|
|
||||||
print("✓ toString function working");
|
print("toString function: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 36: PRINT FUNCTION ENHANCEMENT
|
// TEST 36: PRINT FUNCTION ENHANCEMENT
|
||||||
@ -1006,7 +1006,7 @@ print(type);
|
|||||||
print(toString);
|
print(toString);
|
||||||
print(toNumber);
|
print(toNumber);
|
||||||
|
|
||||||
print("✓ Print function works with all object types");
|
print("Print function works with all object types");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 37: REDEFINABLE FUNCTIONS
|
// TEST 37: REDEFINABLE FUNCTIONS
|
||||||
@ -1035,6 +1035,11 @@ func print(value) {
|
|||||||
|
|
||||||
print("This should show override prefix");
|
print("This should show override prefix");
|
||||||
|
|
||||||
|
// Restore original print function
|
||||||
|
func print(value) {
|
||||||
|
original_print(value);
|
||||||
|
}
|
||||||
|
|
||||||
// Test multiple redefinitions
|
// Test multiple redefinitions
|
||||||
func counter() {
|
func counter() {
|
||||||
return 1;
|
return 1;
|
||||||
@ -1050,7 +1055,7 @@ func counter() {
|
|||||||
|
|
||||||
assert(counter() == 3, "Final redefinition should be used");
|
assert(counter() == 3, "Final redefinition should be used");
|
||||||
|
|
||||||
print("✓ Redefinable functions working");
|
print("Redefinable functions: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 38: RECURSION
|
// TEST 38: RECURSION
|
||||||
@ -1122,7 +1127,7 @@ func deepFactorial(n) {
|
|||||||
|
|
||||||
assert(deepFactorial(10) == 3628800, "deepFactorial(10) should be 3628800");
|
assert(deepFactorial(10) == 3628800, "deepFactorial(10) should be 3628800");
|
||||||
|
|
||||||
print("✓ Recursion working");
|
print("Recursion: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 39: LOGICAL OPERATORS (&&, ||, !)
|
// TEST 39: LOGICAL OPERATORS (&&, ||, !)
|
||||||
@ -1180,7 +1185,7 @@ assert("" && "world" == "", "Empty string && string should return empty string")
|
|||||||
assert("hello" || "world" == "hello", "String || string should return first string");
|
assert("hello" || "world" == "hello", "String || string should return first string");
|
||||||
assert("" || "world" == "world", "Empty string || string should return second string");
|
assert("" || "world" == "world", "Empty string || string should return second string");
|
||||||
|
|
||||||
print("✓ Logical operators working");
|
print("Logical operators: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 40: BITWISE OPERATORS (&, |, ^, <<, >>, ~)
|
// TEST 40: BITWISE OPERATORS (&, |, ^, <<, >>, ~)
|
||||||
@ -1230,7 +1235,7 @@ assert(0 << 5 == 0, "0 << 5 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");
|
assert(~0 == -1, "~0 should be -1");
|
||||||
|
|
||||||
print("✓ Bitwise operators working");
|
print("Bitwise operators: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 41: COMPOUND ASSIGNMENT OPERATORS
|
// TEST 41: COMPOUND ASSIGNMENT OPERATORS
|
||||||
@ -1288,7 +1293,7 @@ assert(comp_a == 8, "comp_a += comp_b should make comp_a = 8");
|
|||||||
comp_a *= comp_b;
|
comp_a *= comp_b;
|
||||||
assert(comp_a == 24, "comp_a *= comp_b should make comp_a = 24");
|
assert(comp_a == 24, "comp_a *= comp_b should make comp_a = 24");
|
||||||
|
|
||||||
print("✓ Compound assignment operators working");
|
print("Compound assignment operators: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 42: ANONYMOUS FUNCTIONS
|
// TEST 42: ANONYMOUS FUNCTIONS
|
||||||
@ -1366,7 +1371,7 @@ assert(counter1() == 1, "Anonymous function closure should work");
|
|||||||
assert(counter1() == 2, "Anonymous function closure should maintain state");
|
assert(counter1() == 2, "Anonymous function closure should maintain state");
|
||||||
assert(counter2() == 1, "Different anonymous function instances should have separate state");
|
assert(counter2() == 1, "Different anonymous function instances should have separate state");
|
||||||
|
|
||||||
print("✓ Anonymous functions working");
|
print("Anonymous functions: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 43: FUNCTIONS RETURNING FUNCTIONS
|
// TEST 43: FUNCTIONS RETURNING FUNCTIONS
|
||||||
@ -1458,7 +1463,7 @@ assert(evenValidator(7) == false, "Even validator should work");
|
|||||||
assert(stringValidator("hello") == true, "String validator should work");
|
assert(stringValidator("hello") == true, "String validator should work");
|
||||||
assert(stringValidator(42) == false, "String validator should work");
|
assert(stringValidator(42) == false, "String validator should work");
|
||||||
|
|
||||||
print("✓ Functions returning functions working");
|
print("Functions returning functions: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 44: COMPREHENSIVE OPERATOR PRECEDENCE
|
// TEST 44: COMPREHENSIVE OPERATOR PRECEDENCE
|
||||||
@ -1485,7 +1490,7 @@ assert(10 - 4 / 2 == 8, "Division should have higher precedence than subtraction
|
|||||||
assert(5 && 3 | 2 << 1 == 7, "Complex precedence test 1");
|
assert(5 && 3 | 2 << 1 == 7, "Complex precedence test 1");
|
||||||
assert((5 && 3) | (2 << 1) == 7, "Complex precedence test 2");
|
assert((5 && 3) | (2 << 1) == 7, "Complex precedence test 2");
|
||||||
|
|
||||||
print("✓ Operator precedence working");
|
print("Operator precedence: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TEST 45: EDGE CASES FOR NEW OPERATORS
|
// TEST 45: EDGE CASES FOR NEW OPERATORS
|
||||||
@ -1532,13 +1537,288 @@ var edge_func2 = func(x) {
|
|||||||
};
|
};
|
||||||
assert(edge_func2(3) == 3, "Recursive anonymous function should work");
|
assert(edge_func2(3) == 3, "Recursive anonymous function should work");
|
||||||
|
|
||||||
print("✓ Edge cases for new operators working");
|
print("Edge cases for new operators: PASS");
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// FINAL SUMMARY
|
// TEST 46: LEXER CONSISTENCY IMPROVEMENTS
|
||||||
// ========================================
|
// ========================================
|
||||||
print("\n=== COMPREHENSIVE TEST SUMMARY ===");
|
print("\n--- Test 46: Lexer Consistency Improvements ---");
|
||||||
print("All core language features tested:");
|
|
||||||
|
// Test 1: Increment/Decrement Operators
|
||||||
|
var x = 5;
|
||||||
|
assert(x == 5, "Initial value should be 5");
|
||||||
|
|
||||||
|
x++;
|
||||||
|
assert(x == 6, "Postfix increment should work");
|
||||||
|
|
||||||
|
++x;
|
||||||
|
assert(x == 7, "Prefix increment should work");
|
||||||
|
|
||||||
|
x--;
|
||||||
|
assert(x == 6, "Postfix decrement should work");
|
||||||
|
|
||||||
|
--x;
|
||||||
|
assert(x == 5, "Prefix decrement should work");
|
||||||
|
|
||||||
|
// Test 2: Compound Assignment Operators
|
||||||
|
var y = 10;
|
||||||
|
assert(y == 10, "Initial value should be 10");
|
||||||
|
|
||||||
|
y += 5;
|
||||||
|
assert(y == 15, "+= should work");
|
||||||
|
|
||||||
|
y -= 3;
|
||||||
|
assert(y == 12, "-= should work");
|
||||||
|
|
||||||
|
y *= 2;
|
||||||
|
assert(y == 24, "*= should work");
|
||||||
|
|
||||||
|
y /= 4;
|
||||||
|
assert(y == 6, "/= should work");
|
||||||
|
|
||||||
|
y %= 4;
|
||||||
|
assert(y == 2, "%= should work");
|
||||||
|
|
||||||
|
// Test 3: Bitwise Assignment Operators
|
||||||
|
var z = 15;
|
||||||
|
assert(z == 15, "Initial value should be 15");
|
||||||
|
|
||||||
|
z &= 3;
|
||||||
|
assert(z == 3, "&= should work");
|
||||||
|
|
||||||
|
z |= 8;
|
||||||
|
assert(z == 11, "|= should work");
|
||||||
|
|
||||||
|
z ^= 5;
|
||||||
|
assert(z == 14, "^= should work");
|
||||||
|
|
||||||
|
z <<= 1;
|
||||||
|
assert(z == 28, "<<= should work");
|
||||||
|
|
||||||
|
z >>= 2;
|
||||||
|
assert(z == 7, ">>= should work");
|
||||||
|
|
||||||
|
// Test 4: Logical Operators
|
||||||
|
var a = true;
|
||||||
|
var b = false;
|
||||||
|
assert(a == true, "a should be true");
|
||||||
|
assert(b == false, "b should be false");
|
||||||
|
|
||||||
|
var result1 = a && b;
|
||||||
|
assert(result1 == false, "true && false should be false");
|
||||||
|
|
||||||
|
var result2 = a || b;
|
||||||
|
assert(result2 == true, "true || false should be true");
|
||||||
|
|
||||||
|
var result3 = !a;
|
||||||
|
assert(result3 == false, "!true should be false");
|
||||||
|
|
||||||
|
var result4 = !b;
|
||||||
|
assert(result4 == true, "!false should be true");
|
||||||
|
|
||||||
|
// Test 5: Comparison Operators
|
||||||
|
var c = 10;
|
||||||
|
var d = 20;
|
||||||
|
assert(c == 10, "c should be 10");
|
||||||
|
assert(d == 20, "d should be 20");
|
||||||
|
|
||||||
|
var eq = c == d;
|
||||||
|
assert(eq == false, "10 == 20 should be false");
|
||||||
|
|
||||||
|
var ne = c != d;
|
||||||
|
assert(ne == true, "10 != 20 should be true");
|
||||||
|
|
||||||
|
var lt = c < d;
|
||||||
|
assert(lt == true, "10 < 20 should be true");
|
||||||
|
|
||||||
|
var gt = c > d;
|
||||||
|
assert(gt == false, "10 > 20 should be false");
|
||||||
|
|
||||||
|
var le = c <= d;
|
||||||
|
assert(le == true, "10 <= 20 should be true");
|
||||||
|
|
||||||
|
var ge = c >= d;
|
||||||
|
assert(ge == false, "10 >= 20 should be false");
|
||||||
|
|
||||||
|
// Test 6: Bitwise Operators
|
||||||
|
var e = 5;
|
||||||
|
var f = 3;
|
||||||
|
assert(e == 5, "e should be 5");
|
||||||
|
assert(f == 3, "f should be 3");
|
||||||
|
|
||||||
|
var bitwise_and = e & f;
|
||||||
|
assert(bitwise_and == 1, "5 & 3 should be 1");
|
||||||
|
|
||||||
|
var bitwise_or = e | f;
|
||||||
|
assert(bitwise_or == 7, "5 | 3 should be 7");
|
||||||
|
|
||||||
|
var bitwise_xor = e ^ f;
|
||||||
|
assert(bitwise_xor == 6, "5 ^ 3 should be 6");
|
||||||
|
|
||||||
|
var bitwise_not = ~e;
|
||||||
|
assert(bitwise_not == -6, "~5 should be -6");
|
||||||
|
|
||||||
|
var left_shift = e << 1;
|
||||||
|
assert(left_shift == 10, "5 << 1 should be 10");
|
||||||
|
|
||||||
|
var right_shift = e >> 1;
|
||||||
|
assert(right_shift == 2, "5 >> 1 should be 2");
|
||||||
|
|
||||||
|
// Test 7: Complex Expressions
|
||||||
|
var complex = 1;
|
||||||
|
assert(complex == 1, "Initial complex value should be 1");
|
||||||
|
|
||||||
|
// Test complex expression with multiple operators
|
||||||
|
var result5 = (++complex) * (complex++) + (--complex);
|
||||||
|
assert(result5 == 6, "Complex expression should be 6");
|
||||||
|
assert(complex == 2, "Variable should be 2");
|
||||||
|
|
||||||
|
// Test another complex expression
|
||||||
|
var result6 = complex++ + ++complex + complex-- + --complex;
|
||||||
|
assert(result6 == 12, "Complex expression should be 12");
|
||||||
|
assert(complex == 2, "Variable should be 2");
|
||||||
|
|
||||||
|
// Test 8: Edge Cases
|
||||||
|
var edge = 0;
|
||||||
|
assert(edge == 0, "Initial edge value should be 0");
|
||||||
|
|
||||||
|
edge++;
|
||||||
|
assert(edge == 1, "0++ should be 1");
|
||||||
|
|
||||||
|
edge--;
|
||||||
|
assert(edge == 0, "1-- should be 0");
|
||||||
|
|
||||||
|
++edge;
|
||||||
|
assert(edge == 1, "++0 should be 1");
|
||||||
|
|
||||||
|
--edge;
|
||||||
|
assert(edge == 0, "--1 should be 0");
|
||||||
|
|
||||||
|
edge += 0;
|
||||||
|
assert(edge == 0, "0 += 0 should be 0");
|
||||||
|
|
||||||
|
edge -= 0;
|
||||||
|
assert(edge == 0, "0 -= 0 should be 0");
|
||||||
|
|
||||||
|
edge *= 5;
|
||||||
|
assert(edge == 0, "0 *= 5 should be 0");
|
||||||
|
|
||||||
|
print("Lexer consistency improvements: PASS");
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TEST 20: NONE VALUE CONCATENATION (CRITICAL FIX TEST)
|
||||||
|
// ========================================
|
||||||
|
print("\n--- Test 20: None Value Concatenation (Critical Fix Test) ---");
|
||||||
|
|
||||||
|
// Test string + none concatenation
|
||||||
|
var testString = "Hello";
|
||||||
|
var testNone = none;
|
||||||
|
var concatResult1 = testString + testNone;
|
||||||
|
assert(toString(concatResult1) == "Hellonone", "String + none should concatenate to 'Hellonone'");
|
||||||
|
print(" String + none: " + toString(concatResult1));
|
||||||
|
|
||||||
|
// Test none + string concatenation
|
||||||
|
var concatResult2 = testNone + testString;
|
||||||
|
assert(toString(concatResult2) == "noneHello", "None + string should concatenate to 'noneHello'");
|
||||||
|
print(" None + string: " + toString(concatResult2));
|
||||||
|
|
||||||
|
// Test in print statements
|
||||||
|
print(" Print with none: " + testNone);
|
||||||
|
|
||||||
|
// Test variable reassignment to none
|
||||||
|
var y = "Another string";
|
||||||
|
y = none;
|
||||||
|
print(" Variable set to none: " + y);
|
||||||
|
|
||||||
|
print("None value concatenation: PASS");
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TEST 21: MEMORY MANAGEMENT
|
||||||
|
// ========================================
|
||||||
|
print("\n--- Test 21: Memory Management ---");
|
||||||
|
|
||||||
|
// Test variable reassignment
|
||||||
|
var x = "Hello World";
|
||||||
|
x = 42;
|
||||||
|
x = "New String";
|
||||||
|
assert(toString(x) == "New String", "Variable reassignment should work");
|
||||||
|
|
||||||
|
// Test function reassignment
|
||||||
|
var func1 = func() { return "Function 1"; };
|
||||||
|
var func2 = func() { return "Function 2"; };
|
||||||
|
func1 = func2;
|
||||||
|
assert(func1() == "Function 2", "Function reassignment should work");
|
||||||
|
|
||||||
|
// Test large string allocation and cleanup
|
||||||
|
var bigString = "This is a very long string that should use significant memory. " +
|
||||||
|
"It contains many characters and should be properly freed when " +
|
||||||
|
"the variable is reassigned or set to none. " +
|
||||||
|
"Let's make it even longer by repeating some content.";
|
||||||
|
bigString = "Small";
|
||||||
|
assert(toString(bigString) == "Small", "Large string cleanup should work");
|
||||||
|
|
||||||
|
// Test multiple reassignments
|
||||||
|
var z = "First";
|
||||||
|
z = "Second";
|
||||||
|
z = "Third";
|
||||||
|
z = "Fourth";
|
||||||
|
z = "Fifth";
|
||||||
|
assert(toString(z) == "Fifth", "Multiple reassignments should work");
|
||||||
|
|
||||||
|
print("Memory management: PASS");
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TEST 22: MULTI-STATEMENT FUNCTION EXECUTION (CRITICAL BUG TEST)
|
||||||
|
// ========================================
|
||||||
|
print("\n--- Test 22: Multi-Statement Function Execution (Critical Bug Test) ---");
|
||||||
|
|
||||||
|
// Test the bug where functions only executed their first statement
|
||||||
|
func createMultiStatementFunction() {
|
||||||
|
return func(x) {
|
||||||
|
print(" Statement 1: Starting with " + toString(x));
|
||||||
|
var result = x * 2;
|
||||||
|
print(" Statement 2: Doubled to " + toString(result));
|
||||||
|
result += 5;
|
||||||
|
print(" Statement 3: Added 5 to get " + toString(result));
|
||||||
|
result *= 3;
|
||||||
|
print(" Statement 4: Multiplied by 3 to get " + toString(result));
|
||||||
|
result -= 10;
|
||||||
|
print(" Statement 5: Subtracted 10 to get " + toString(result));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiFunc = createMultiStatementFunction();
|
||||||
|
var result1 = multiFunc(7);
|
||||||
|
assert(result1 == 47, "Multi-statement function should execute all statements"); // (7*2+5)*3-10 = 47
|
||||||
|
print(" Multi-statement function result: " + toString(result1));
|
||||||
|
|
||||||
|
// Test nested multi-statement functions
|
||||||
|
func createNestedMultiFunction() {
|
||||||
|
return func(x) {
|
||||||
|
var temp = x + 10;
|
||||||
|
var innerFunc = func(y) {
|
||||||
|
var innerResult = y * 2;
|
||||||
|
innerResult += 3;
|
||||||
|
return innerResult;
|
||||||
|
};
|
||||||
|
var innerResult = innerFunc(temp);
|
||||||
|
return innerResult + 5;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var nestedFunc = createNestedMultiFunction();
|
||||||
|
var result2 = nestedFunc(5);
|
||||||
|
assert(result2 == 38, "Nested multi-statement functions should work"); // ((5+10)*2+3)+5 = 38
|
||||||
|
print(" Nested multi-statement function result: " + toString(result2));
|
||||||
|
|
||||||
|
print("Multi-statement function execution: PASS");
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TEST SUMMARY
|
||||||
|
// ========================================
|
||||||
|
print("\nTest Summary:");
|
||||||
|
print("Features tested:");
|
||||||
print("- Basic data types (strings, numbers, booleans)");
|
print("- Basic data types (strings, numbers, booleans)");
|
||||||
print("- Arithmetic operations");
|
print("- Arithmetic operations");
|
||||||
print("- String operations");
|
print("- String operations");
|
||||||
@ -1586,14 +1866,18 @@ print("- toString function (universal string conversion)");
|
|||||||
print("- Enhanced print function (works with all object types)");
|
print("- Enhanced print function (works with all object types)");
|
||||||
print("- Redefinable functions (including built-in function override)");
|
print("- Redefinable functions (including built-in function override)");
|
||||||
print("- Recursion (factorial, fibonacci, mutual recursion, deep recursion)");
|
print("- Recursion (factorial, fibonacci, mutual recursion, deep recursion)");
|
||||||
print("- NEW: Logical operators (&&, ||, !) with short-circuit evaluation");
|
print("- Logical operators (&&, ||, !) with short-circuit evaluation");
|
||||||
print("- NEW: Bitwise operators (&, |, ^, <<, >>, ~)");
|
print("- Bitwise operators (&, |, ^, <<, >>, ~)");
|
||||||
print("- NEW: Compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)");
|
print("- Compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)");
|
||||||
print("- NEW: Anonymous functions (func(...) { ... })");
|
print("- Anonymous functions (func(...) { ... })");
|
||||||
print("- NEW: Functions returning functions");
|
print("- Functions returning functions");
|
||||||
print("- NEW: Operator precedence for all new operators");
|
print("- Operator precedence for all operators");
|
||||||
print("- NEW: Edge cases for all new operators");
|
print("- Edge cases for all operators");
|
||||||
|
print("- Lexer consistency improvements");
|
||||||
|
print("- None value concatenation (string + none, none + string)");
|
||||||
|
print("- Memory management (variable reassignment, function reassignment, large string cleanup)");
|
||||||
|
print("- Multi-statement function execution");
|
||||||
|
|
||||||
|
print("\nAll tests passed.");
|
||||||
|
print("Test suite complete.");
|
||||||
|
|
||||||
print("\n🎉 ALL TESTS PASSED! 🎉");
|
|
||||||
print("Bob language is working correctly!");
|
|
||||||
print("Ready for next phase: Control Flow (while loops, data structures)");
|
|
||||||
@ -1,8 +1,10 @@
|
|||||||
|
var counter = 0;
|
||||||
|
|
||||||
func fib(n) {
|
func fib(n) {
|
||||||
if (n <= 1) {
|
if (n <= 1) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
//print("Current operation: " + (n - 1) + ":" + (n-2));
|
counter++; // Only increment for recursive calls
|
||||||
return fib(n - 1) + fib(n - 2);
|
return fib(n - 1) + fib(n - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,5 +13,5 @@ var fib_result = fib(30);
|
|||||||
|
|
||||||
print("Result: " + fib_result);
|
print("Result: " + fib_result);
|
||||||
|
|
||||||
|
print("Counter: " + counter);
|
||||||
|
|
||||||
print(10 / 0);
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user