diff --git a/Makefile b/Makefile index 77e3824..291f369 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ CC = g++ # 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 SRC_DIR = ./source diff --git a/headers/Interpreter.h b/headers/Interpreter.h index ae2e399..b9191a7 100644 --- a/headers/Interpreter.h +++ b/headers/Interpreter.h @@ -35,6 +35,7 @@ public: void visitVarStmt(sptr(VarStmt) statement) override; void visitFunctionStmt(sptr(FunctionStmt) statement) override; void visitReturnStmt(sptr(ReturnStmt) statement) override; + void visitIfStmt(sptr(IfStmt) statement) override; void interpret(std::vector statements); diff --git a/headers/Parser.h b/headers/Parser.h index 42a8053..9adc63a 100644 --- a/headers/Parser.h +++ b/headers/Parser.h @@ -44,6 +44,8 @@ private: std::shared_ptr returnStatement(); + std::shared_ptr ifStatement(); + std::shared_ptr declaration(); std::shared_ptr varDeclaration(); diff --git a/headers/Statement.h b/headers/Statement.h index f478dda..ddb3b89 100644 --- a/headers/Statement.h +++ b/headers/Statement.h @@ -10,6 +10,7 @@ struct VarStmt; struct BlockStmt; struct FunctionStmt; struct ReturnStmt; +struct IfStmt; struct StmtVisitor { @@ -18,6 +19,7 @@ struct StmtVisitor virtual void visitVarStmt(sptr(VarStmt) stmt) = 0; virtual void visitFunctionStmt(sptr(FunctionStmt) stmt) = 0; virtual void visitReturnStmt(sptr(ReturnStmt) stmt) = 0; + virtual void visitIfStmt(sptr(IfStmt) stmt) = 0; }; struct Stmt @@ -94,4 +96,19 @@ struct ReturnStmt : Stmt, public std::enable_shared_from_this { visitor->visitReturnStmt(shared_from_this()); } +}; + +struct IfStmt : Stmt, public std::enable_shared_from_this +{ + 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()); + } }; \ No newline at end of file diff --git a/source/Environment.cpp b/source/Environment.cpp index 44c75c6..ce5d97d 100644 --- a/source/Environment.cpp +++ b/source/Environment.cpp @@ -22,10 +22,8 @@ sptr(Object) Environment::get(Token name) } void Environment::define(std::string name, sptr(Object) value) { - if(variables.count(name) > 0){ - throw std::runtime_error("'" + name + "' already defined."); - } - variables.insert(std::make_pair(name, value)); + // Allow redefinition - just overwrite the existing value + variables[name] = value; } void Environment::assign(Token name, std::shared_ptr value) { diff --git a/source/Interpreter.cpp b/source/Interpreter.cpp index 151f105..4875647 100644 --- a/source/Interpreter.cpp +++ b/source/Interpreter.cpp @@ -9,6 +9,8 @@ #include #include "../headers/Interpreter.h" #include "../headers/helperFunctions/HelperFunctions.h" +#include + 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 num = std::stod(expr->value); } - return msptr(Number)(num); + return std::make_shared(num); } if(expr->value == "true") return msptr(Boolean)(true); if(expr->value == "false") return msptr(Boolean)(false); @@ -105,14 +107,14 @@ sptr(Object) Interpreter::visitBinaryExpr(sptr(BinaryExpr) expression) case LESS_EQUAL: return msptr(Boolean)(left_double <= right_double); case MINUS: - return msptr(Number)(left_double - right_double); + return std::make_shared(left_double - right_double); case PLUS: - return msptr(Number)(left_double + right_double); + return std::make_shared(left_double + right_double); case SLASH: if(right_double == 0) throw std::runtime_error("DivisionByZeroError: Cannot divide by 0"); - return msptr(Number)(left_double / right_double); + return std::make_shared(left_double / right_double); case STAR: - return msptr(Number)(left_double * right_double); + return std::make_shared(left_double * right_double); case PERCENT: return msptr(Number)(fmod(left_double, right_double)); default: @@ -286,7 +288,7 @@ sptr(Object) Interpreter::visitCallExpr(sptr(CallExpr) expression) { } // Create new environment for function execution - sptr(Environment) functionEnv = msptr(Environment)(std::static_pointer_cast(function->closure)); + sptr(Environment) functionEnv = std::make_shared(std::static_pointer_cast(function->closure)); // Bind parameters to arguments for (size_t i = 0; i < function->params.size(); i++) { @@ -368,6 +370,15 @@ void Interpreter::visitReturnStmt(sptr(ReturnStmt) statement) 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 statements) { @@ -509,6 +520,14 @@ std::string Interpreter::stringify(sptr(Object) object) { { return Bool->value == 1 ? "true" : "false"; } + else if(auto func = std::dynamic_pointer_cast(object)) + { + return "name + ">"; + } + else if(auto builtinFunc = std::dynamic_pointer_cast(object)) + { + return "name + ">"; + } throw std::runtime_error("Could not convert object to string"); } @@ -537,3 +556,5 @@ bool Interpreter::isWholeNumer(double num) { + + diff --git a/source/StdLib.cpp b/source/StdLib.cpp index 9bb735f..35741f8 100644 --- a/source/StdLib.cpp +++ b/source/StdLib.cpp @@ -3,6 +3,17 @@ #include void StdLib::addToEnvironment(sptr(Environment) env, Interpreter* interpreter) { + // Create a built-in toString function + auto toStringFunc = std::make_shared("toString", + [interpreter](std::vector> args) -> std::shared_ptr { + if (args.size() != 1) { + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + } + + return std::make_shared(interpreter->stringify(args[0])); + }); + env->define("toString", toStringFunc); + // Create a built-in print function auto printFunc = std::make_shared("print", [interpreter](std::vector> args) -> std::shared_ptr { @@ -55,4 +66,85 @@ void StdLib::addToEnvironment(sptr(Environment) env, Interpreter* interpreter) { return std::make_shared(microseconds); }); env->define("time", timeFunc); + + // Create a built-in input function + auto inputFunc = std::make_shared("input", + [interpreter](std::vector> args) -> std::shared_ptr { + 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(userInput); + }); + env->define("input", inputFunc); + + // Create a built-in type function + auto typeFunc = std::make_shared("type", + [](std::vector> args) -> std::shared_ptr { + 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(args[0])) { + typeName = "number"; + } else if (std::dynamic_pointer_cast(args[0])) { + typeName = "string"; + } else if (std::dynamic_pointer_cast(args[0])) { + typeName = "boolean"; + } else if (std::dynamic_pointer_cast(args[0])) { + typeName = "none"; + } else if (std::dynamic_pointer_cast(args[0])) { + typeName = "function"; + } else if (std::dynamic_pointer_cast(args[0])) { + typeName = "builtin_function"; + } else { + typeName = "unknown"; + } + + return std::make_shared(typeName); + }); + env->define("type", typeFunc); + + // Create a built-in toNumber function for string-to-number conversion + auto toNumberFunc = std::make_shared("toNumber", + [](std::vector> args) -> std::shared_ptr { + if (args.size() != 1) { + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + } + + auto strObj = std::dynamic_pointer_cast(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(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); } \ No newline at end of file diff --git a/test_bob_language.bob b/test_bob_language.bob index 8ea38c3..aa71e0f 100644 --- a/test_bob_language.bob +++ b/test_bob_language.bob @@ -758,6 +758,306 @@ assert(gt_test == true, "7 > 4 should be true"); 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 // ======================================== @@ -802,6 +1102,14 @@ print(" * Edge cases and complex expressions"); print("- Time function (microsecond precision)"); print("- Boolean + String concatenation"); 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("Bob language is working correctly!"); diff --git a/test_time_function.bob b/test_time_function.bob deleted file mode 100644 index 13ccb44..0000000 --- a/test_time_function.bob +++ /dev/null @@ -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!"); \ No newline at end of file