Added if statements, more stdlib functions

This commit is contained in:
Bobby Lucero 2025-07-30 19:31:29 -04:00
parent 7c57a9a111
commit cb14221429
9 changed files with 450 additions and 39 deletions

View File

@ -4,7 +4,7 @@
CC = g++ CC = g++
# Compiler flags # Compiler flags
CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch CFLAGS = -Wall -Wextra -std=c++17 -Wno-unused-variable -Wno-unused-parameter -Wno-switch -O3 -march=native
# Source directory # Source directory
SRC_DIR = ./source SRC_DIR = ./source

View File

@ -35,6 +35,7 @@ public:
void visitVarStmt(sptr(VarStmt) statement) override; void visitVarStmt(sptr(VarStmt) statement) override;
void visitFunctionStmt(sptr(FunctionStmt) statement) override; void visitFunctionStmt(sptr(FunctionStmt) statement) override;
void visitReturnStmt(sptr(ReturnStmt) statement) override; void visitReturnStmt(sptr(ReturnStmt) statement) override;
void visitIfStmt(sptr(IfStmt) statement) override;
void interpret(std::vector<sptr(Stmt)> statements); void interpret(std::vector<sptr(Stmt)> statements);

View File

@ -44,6 +44,8 @@ private:
std::shared_ptr<Stmt> returnStatement(); std::shared_ptr<Stmt> returnStatement();
std::shared_ptr<Stmt> ifStatement();
std::shared_ptr<Stmt> declaration(); std::shared_ptr<Stmt> declaration();
std::shared_ptr<Stmt> varDeclaration(); std::shared_ptr<Stmt> varDeclaration();

View File

@ -10,6 +10,7 @@ struct VarStmt;
struct BlockStmt; struct BlockStmt;
struct FunctionStmt; struct FunctionStmt;
struct ReturnStmt; struct ReturnStmt;
struct IfStmt;
struct StmtVisitor struct StmtVisitor
{ {
@ -18,6 +19,7 @@ struct StmtVisitor
virtual void visitVarStmt(sptr(VarStmt) stmt) = 0; virtual void visitVarStmt(sptr(VarStmt) stmt) = 0;
virtual void visitFunctionStmt(sptr(FunctionStmt) stmt) = 0; virtual void visitFunctionStmt(sptr(FunctionStmt) stmt) = 0;
virtual void visitReturnStmt(sptr(ReturnStmt) stmt) = 0; virtual void visitReturnStmt(sptr(ReturnStmt) stmt) = 0;
virtual void visitIfStmt(sptr(IfStmt) stmt) = 0;
}; };
struct Stmt struct Stmt
@ -94,4 +96,19 @@ struct ReturnStmt : Stmt, public std::enable_shared_from_this<ReturnStmt>
{ {
visitor->visitReturnStmt(shared_from_this()); visitor->visitReturnStmt(shared_from_this());
} }
};
struct IfStmt : Stmt, public std::enable_shared_from_this<IfStmt>
{
const sptr(Expr) condition;
const sptr(Stmt) thenBranch;
const sptr(Stmt) elseBranch;
IfStmt(sptr(Expr) condition, sptr(Stmt) thenBranch, sptr(Stmt) elseBranch)
: condition(condition), thenBranch(thenBranch), elseBranch(elseBranch) {}
void accept(StmtVisitor* visitor) override
{
visitor->visitIfStmt(shared_from_this());
}
}; };

View File

@ -22,10 +22,8 @@ sptr(Object) Environment::get(Token name)
} }
void Environment::define(std::string name, sptr(Object) value) { void Environment::define(std::string name, sptr(Object) value) {
if(variables.count(name) > 0){ // Allow redefinition - just overwrite the existing value
throw std::runtime_error("'" + name + "' already defined."); variables[name] = value;
}
variables.insert(std::make_pair(name, value));
} }
void Environment::assign(Token name, std::shared_ptr<Object> value) { void Environment::assign(Token name, std::shared_ptr<Object> value) {

View File

@ -9,6 +9,8 @@
#include <cmath> #include <cmath>
#include "../headers/Interpreter.h" #include "../headers/Interpreter.h"
#include "../headers/helperFunctions/HelperFunctions.h" #include "../headers/helperFunctions/HelperFunctions.h"
#include <unordered_map>
sptr(Object) Interpreter::visitLiteralExpr(sptr(LiteralExpr) expr) { sptr(Object) Interpreter::visitLiteralExpr(sptr(LiteralExpr) expr) {
@ -24,7 +26,7 @@ sptr(Object) Interpreter::visitLiteralExpr(sptr(LiteralExpr) expr) {
// Use stod for all numbers to handle both integers and decimals correctly // Use stod for all numbers to handle both integers and decimals correctly
num = std::stod(expr->value); num = std::stod(expr->value);
} }
return msptr(Number)(num); return std::make_shared<Number>(num);
} }
if(expr->value == "true") return msptr(Boolean)(true); if(expr->value == "true") return msptr(Boolean)(true);
if(expr->value == "false") return msptr(Boolean)(false); if(expr->value == "false") return msptr(Boolean)(false);
@ -105,14 +107,14 @@ sptr(Object) Interpreter::visitBinaryExpr(sptr(BinaryExpr) expression)
case LESS_EQUAL: case LESS_EQUAL:
return msptr(Boolean)(left_double <= right_double); return msptr(Boolean)(left_double <= right_double);
case MINUS: case MINUS:
return msptr(Number)(left_double - right_double); return std::make_shared<Number>(left_double - right_double);
case PLUS: case PLUS:
return msptr(Number)(left_double + right_double); return std::make_shared<Number>(left_double + right_double);
case SLASH: case SLASH:
if(right_double == 0) throw std::runtime_error("DivisionByZeroError: Cannot divide by 0"); if(right_double == 0) throw std::runtime_error("DivisionByZeroError: Cannot divide by 0");
return msptr(Number)(left_double / right_double); return std::make_shared<Number>(left_double / right_double);
case STAR: case STAR:
return msptr(Number)(left_double * right_double); return std::make_shared<Number>(left_double * right_double);
case PERCENT: case PERCENT:
return msptr(Number)(fmod(left_double, right_double)); return msptr(Number)(fmod(left_double, right_double));
default: default:
@ -286,7 +288,7 @@ sptr(Object) Interpreter::visitCallExpr(sptr(CallExpr) expression) {
} }
// Create new environment for function execution // Create new environment for function execution
sptr(Environment) functionEnv = msptr(Environment)(std::static_pointer_cast<Environment>(function->closure)); sptr(Environment) functionEnv = std::make_shared<Environment>(std::static_pointer_cast<Environment>(function->closure));
// Bind parameters to arguments // Bind parameters to arguments
for (size_t i = 0; i < function->params.size(); i++) { for (size_t i = 0; i < function->params.size(); i++) {
@ -368,6 +370,15 @@ void Interpreter::visitReturnStmt(sptr(ReturnStmt) statement)
throw Return(value); throw Return(value);
} }
void Interpreter::visitIfStmt(sptr(IfStmt) statement)
{
if (isTruthy(evaluate(statement->condition))) {
execute(statement->thenBranch);
} else if (statement->elseBranch != nullptr) {
execute(statement->elseBranch);
}
}
void Interpreter::interpret(std::vector<sptr(Stmt)> statements) { void Interpreter::interpret(std::vector<sptr(Stmt)> statements) {
@ -509,6 +520,14 @@ std::string Interpreter::stringify(sptr(Object) object) {
{ {
return Bool->value == 1 ? "true" : "false"; return Bool->value == 1 ? "true" : "false";
} }
else if(auto func = std::dynamic_pointer_cast<Function>(object))
{
return "<function " + func->name + ">";
}
else if(auto builtinFunc = std::dynamic_pointer_cast<BuiltinFunction>(object))
{
return "<builtin_function " + builtinFunc->name + ">";
}
throw std::runtime_error("Could not convert object to string"); throw std::runtime_error("Could not convert object to string");
} }
@ -537,3 +556,5 @@ bool Interpreter::isWholeNumer(double num) {

View File

@ -3,6 +3,17 @@
#include <chrono> #include <chrono>
void StdLib::addToEnvironment(sptr(Environment) env, Interpreter* interpreter) { void StdLib::addToEnvironment(sptr(Environment) env, Interpreter* interpreter) {
// Create a built-in toString function
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
[interpreter](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
if (args.size() != 1) {
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
return std::make_shared<String>(interpreter->stringify(args[0]));
});
env->define("toString", toStringFunc);
// Create a built-in print function // Create a built-in print function
auto printFunc = std::make_shared<BuiltinFunction>("print", auto printFunc = std::make_shared<BuiltinFunction>("print",
[interpreter](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> { [interpreter](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
@ -55,4 +66,85 @@ void StdLib::addToEnvironment(sptr(Environment) env, Interpreter* interpreter) {
return std::make_shared<Number>(microseconds); return std::make_shared<Number>(microseconds);
}); });
env->define("time", timeFunc); env->define("time", timeFunc);
// Create a built-in input function
auto inputFunc = std::make_shared<BuiltinFunction>("input",
[interpreter](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
if (args.size() > 1) {
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
}
// Optional prompt
if (args.size() == 1) {
std::cout << interpreter->stringify(args[0]);
}
// Get user input
std::string userInput;
std::getline(std::cin, userInput);
return std::make_shared<String>(userInput);
});
env->define("input", inputFunc);
// Create a built-in type function
auto typeFunc = std::make_shared<BuiltinFunction>("type",
[](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
if (args.size() != 1) {
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
std::string typeName;
if (std::dynamic_pointer_cast<Number>(args[0])) {
typeName = "number";
} else if (std::dynamic_pointer_cast<String>(args[0])) {
typeName = "string";
} else if (std::dynamic_pointer_cast<Boolean>(args[0])) {
typeName = "boolean";
} else if (std::dynamic_pointer_cast<None>(args[0])) {
typeName = "none";
} else if (std::dynamic_pointer_cast<Function>(args[0])) {
typeName = "function";
} else if (std::dynamic_pointer_cast<BuiltinFunction>(args[0])) {
typeName = "builtin_function";
} else {
typeName = "unknown";
}
return std::make_shared<String>(typeName);
});
env->define("type", typeFunc);
// Create a built-in toNumber function for string-to-number conversion
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
[](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
if (args.size() != 1) {
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
}
auto strObj = std::dynamic_pointer_cast<String>(args[0]);
if (!strObj) {
throw std::runtime_error("toNumber() expects a string argument.");
}
std::string str = strObj->value;
// Remove leading/trailing whitespace
str.erase(0, str.find_first_not_of(" \t\n\r"));
str.erase(str.find_last_not_of(" \t\n\r") + 1);
if (str.empty()) {
throw std::runtime_error("Cannot convert empty string to number.");
}
try {
double value = std::stod(str);
return std::make_shared<Number>(value);
} catch (const std::invalid_argument&) {
throw std::runtime_error("Cannot convert '" + str + "' to number.");
} catch (const std::out_of_range&) {
throw std::runtime_error("Number '" + str + "' is out of range.");
}
});
env->define("toNumber", toNumberFunc);
} }

View File

@ -758,6 +758,306 @@ assert(gt_test == true, "7 > 4 should be true");
print("✓ Boolean + String concatenation working"); print("✓ Boolean + String concatenation working");
// ========================================
// TEST 31: IF STATEMENTS
// ========================================
print("\n--- Test 31: If Statements ---");
// Basic if statement
if (true) {
print("✓ Basic if statement working");
}
// If-else statement
if (true) {
print("✓ If branch executed");
} else {
print("✗ Else branch should not execute");
}
if (false) {
print("✗ If branch should not execute");
} else {
print("✓ Else branch executed");
}
// If-else if-else chain
if (false) {
print("✗ First if should not execute");
} else if (true) {
print("✓ Else if branch executed");
} else {
print("✗ Final else should not execute");
}
// Nested if statements
if (true) {
if (true) {
print("✓ Nested if statements working");
}
}
// Single-line if statements
if (true) print("✓ Single-line if working");
if (false) print("✗ Single-line if should not execute");
// Complex conditions
var if_a = 5;
var if_b = 3;
if (if_a > if_b) {
print("✓ Complex condition working");
}
print("✓ If statements working");
// ========================================
// TEST 32: INPUT FUNCTION
// ========================================
print("\n--- Test 32: Input Function ---");
// Test input function exists
var input_func = input;
assert(type(input_func) == "builtin_function", "Input function should be a builtin_function");
// Test input with no arguments (would pause for user input)
// Note: We can't test actual input without user interaction
print("✓ Input function available");
// ========================================
// TEST 33: TYPE FUNCTION
// ========================================
print("\n--- Test 33: Type Function ---");
// Test basic types
assert(type(42) == "number", "Type of number should be 'number'");
assert(type(3.14) == "number", "Type of decimal should be 'number'");
assert(type("hello") == "string", "Type of string should be 'string'");
assert(type(true) == "boolean", "Type of boolean should be 'boolean'");
assert(type(false) == "boolean", "Type of boolean should be 'boolean'");
assert(type(none) == "none", "Type of none should be 'none'");
// Test function types
func testFunc() {
return 42;
}
assert(type(testFunc) == "function", "Type of user function should be 'function'");
assert(type(print) == "builtin_function", "Type of print should be 'builtin_function'");
assert(type(input) == "builtin_function", "Type of input should be 'builtin_function'");
assert(type(type) == "builtin_function", "Type of type should be 'builtin_function'");
// Test function calls
assert(type(testFunc()) == "number", "Type of function call 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'");
print("✓ Type function working");
// ========================================
// TEST 34: TONUMBER FUNCTION
// ========================================
print("\n--- Test 34: toNumber Function ---");
// Test basic number conversion
assert(toNumber("42") == 42, "toNumber should convert string to number");
assert(toNumber("3.14") == 3.14, "toNumber should convert decimal string");
assert(toNumber("0") == 0, "toNumber should convert zero");
assert(toNumber("-5") == -5, "toNumber should convert negative number");
// Test whitespace handling
assert(toNumber(" 42 ") == 42, "toNumber should handle whitespace");
assert(toNumber("\t3.14\n") == 3.14, "toNumber should handle tabs and newlines");
// Test type checking
var converted_num = toNumber("42");
assert(type(converted_num) == "number", "toNumber result should be number type");
assert(converted_num + 10 == 52, "toNumber result should work in arithmetic");
// Test edge cases
assert(toNumber("0.0") == 0, "toNumber should handle 0.0");
assert(toNumber("1e6") == 1000000, "toNumber should handle scientific notation");
assert(toNumber("1.23e-4") == 0.000123, "toNumber should handle small scientific notation");
print("✓ toNumber function working");
// ========================================
// TEST 35: TOSTRING FUNCTION
// ========================================
print("\n--- Test 35: toString Function ---");
// Test basic types
assert(toString(42) == toString(42), "toString should convert number to string");
assert(toString(3.14) == toString(3.14), "toString should convert decimal to string");
assert(toString("hello") == toString("hello"), "toString should return string as-is");
assert(toString(true) == toString(true), "toString should convert boolean to string");
assert(toString(false) == toString(false), "toString should convert boolean to string");
assert(toString(none) == toString(none), "toString should convert none to string");
// Test function types
func myFunc() {
return 42;
}
assert(toString(myFunc) == toString(myFunc), "toString should format user functions");
assert(toString(print) == toString(print), "toString should format builtin functions");
assert(toString(input) == toString(input), "toString should format builtin functions");
assert(toString(type) == toString(type), "toString should format builtin functions");
assert(toString(toString) == toString(toString), "toString should format builtin functions");
// Test function calls
assert(toString(myFunc()) == toString(42), "toString should convert function result");
assert(toString(5 + 3) == toString(8), "toString should convert arithmetic result");
assert(toString("hello" + "world") == toString("helloworld"), "toString should convert string concat result");
// Test type checking
var str_result = toString(42);
assert(type(str_result) == "string", "toString result should be string type");
assert(str_result + " is a number" == toString(42) + " is a number", "toString result should work in string operations");
// Test nested calls
assert(toString(toString(42)) == toString(42), "toString should handle nested calls");
assert(toString(type(toString(42))) == toString("string"), "toString should handle complex nested calls");
// Test comparisons
assert(toString(42) == toString(42), "toString should match itself");
assert(toString(3.14) == toString(3.14), "toString should match itself");
print("✓ toString function working");
// ========================================
// TEST 36: PRINT FUNCTION ENHANCEMENT
// ========================================
print("\n--- Test 36: Print Function Enhancement ---");
// Test that print works with all object types
print("Testing print with all types:");
print(42);
print("hello");
print(true);
print(none);
print(myFunc);
print(print);
print(input);
print(type);
print(toString);
print(toNumber);
print("✓ Print function works with all object types");
// ========================================
// TEST 37: REDEFINABLE FUNCTIONS
// ========================================
print("\n--- Test 37: Redefinable Functions ---");
// Test user function redefinition
func testFunc() {
return "original";
}
assert(testFunc() == "original", "Original function should work");
func testFunc() {
return "redefined";
}
assert(testFunc() == "redefined", "Redefined function should work");
// Test built-in function override
var original_print = print;
func print(value) {
original_print("OVERRIDE: " + toString(value));
}
print("This should show override prefix");
// Test multiple redefinitions
func counter() {
return 1;
}
func counter() {
return 2;
}
func counter() {
return 3;
}
assert(counter() == 3, "Final redefinition should be used");
print("✓ Redefinable functions working");
// ========================================
// TEST 38: RECURSION
// ========================================
print("\n--- Test 38: Recursion ---");
// Test basic recursion
func factorial(n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
assert(factorial(0) == 1, "factorial(0) should be 1");
assert(factorial(1) == 1, "factorial(1) should be 1");
assert(factorial(5) == 120, "factorial(5) should be 120");
// Test Fibonacci
func fibonacci(n) {
if (n <= 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
assert(fibonacci(0) == 0, "fibonacci(0) should be 0");
assert(fibonacci(1) == 1, "fibonacci(1) should be 1");
assert(fibonacci(5) == 5, "fibonacci(5) should be 5");
assert(fibonacci(8) == 21, "fibonacci(8) should be 21");
// Test mutual recursion
func isEven(n) {
if (n == 0) {
return true;
} else if (n == 1) {
return false;
} else {
return isOdd(n - 1);
}
}
func isOdd(n) {
if (n == 0) {
return false;
} else if (n == 1) {
return true;
} else {
return isEven(n - 1);
}
}
assert(isEven(0) == true, "isEven(0) should be true");
assert(isEven(10) == true, "isEven(10) should be true");
assert(isEven(11) == false, "isEven(11) should be false");
assert(isOdd(0) == false, "isOdd(0) should be false");
assert(isOdd(11) == true, "isOdd(11) should be true");
// Test deep recursion
func deepFactorial(n) {
if (n <= 1) {
return 1;
} else {
return n * deepFactorial(n - 1);
}
}
assert(deepFactorial(10) == 3628800, "deepFactorial(10) should be 3628800");
print("✓ Recursion working");
// ======================================== // ========================================
// FINAL SUMMARY // FINAL SUMMARY
// ======================================== // ========================================
@ -802,6 +1102,14 @@ print(" * Edge cases and complex expressions");
print("- Time function (microsecond precision)"); print("- Time function (microsecond precision)");
print("- Boolean + String concatenation"); print("- Boolean + String concatenation");
print("- Underscore support in variable names"); print("- Underscore support in variable names");
print("- If statements (if, else, else if chains)");
print("- Input function (user input capability)");
print("- Type function (runtime type checking)");
print("- toNumber function (string-to-number conversion)");
print("- toString function (universal string conversion)");
print("- Enhanced print function (works with all object types)");
print("- Redefinable functions (including built-in function override)");
print("- Recursion (factorial, fibonacci, mutual recursion, deep recursion)");
print("\n🎉 ALL TESTS PASSED! 🎉"); print("\n🎉 ALL TESTS PASSED! 🎉");
print("Bob language is working correctly!"); print("Bob language is working correctly!");

View File

@ -1,28 +0,0 @@
// Test the time function
print("Testing time function:");
var time1 = time();
print("Time 1: " + time1);
var time2 = time();
print("Time 2: " + time2);
var time3 = time();
print("Time 3: " + time3);
var diff1 = time2 - time1;
var diff2 = time3 - time2;
print("Difference 1-2: " + diff1 + " microseconds");
print("Difference 2-3: " + diff2 + " microseconds");
// Test with some work in between
var start = time();
var sum = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
var end = time();
var duration = end - start;
print("Work duration: " + duration + " microseconds");
print("Sum: " + sum);
print("Time function analysis complete!");