From 72a1b82b4341eba9ce1bed8ff8cfd537c621b5a2 Mon Sep 17 00:00:00 2001 From: Bobby Lucero Date: Wed, 6 Aug 2025 00:57:36 -0400 Subject: [PATCH] More things - Add while, for, and do-while loops with break/continue - Implement assignment statements (prevents if(x=10) bugs) - Keep assignment expressions only for for-loop clauses - Fix critical memory management bug (dangling pointers in cleanup) - Add automatic memory cleanup with conservative reference counting - Consolidate documentation into single reference file - Add comprehensive test coverage for all loop types and edge cases - VSCode extension for bob highlighting and snippets --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 1 + BOB_LANGUAGE_REFERENCE.md | 433 +++++++++++---- README.md | 97 ++++ ROADMAP.md | 373 +++++++------ bob-language-extension/.vscodeignore | 10 + bob-language-extension/INSTALLATION.md | 97 ++++ bob-language-extension/README.md | 103 ++++ .../bob-language-0.1.2.vsix | Bin 0 -> 6717 bytes bob-language-extension/create-vsix.sh | 44 ++ bob-language-extension/example.bob | 75 +++ bob-language-extension/install.sh | 66 +++ .../language-configuration.json | 35 ++ bob-language-extension/package-vsix.sh | 27 + bob-language-extension/package.json | 74 +++ bob-language-extension/reload-extension.sh | 29 + bob-language-extension/snippets/bob.json | 291 ++++++++++ bob-language-extension/src/extension.ts | 104 ++++ .../syntaxes/bob.tmLanguage.json | 175 ++++++ bob-language-extension/test-operators.bob | 58 ++ bob-language-extension/themes/bob-dark.json | 79 +++ bob-language-extension/tsconfig.json | 17 + headers/Expression.h | 16 + headers/Interpreter.h | 17 + headers/Lexer.h | 12 +- headers/Parser.h | 13 + headers/Statement.h | 98 ++++ source/.DS_Store | Bin 6148 -> 0 bytes source/Interpreter.cpp | 237 +++++++- source/Lexer.cpp | 10 + source/Parser.cpp | 139 ++++- source/StdLib.cpp | 2 + source/StdLib.cpp.backup | 203 +++++++ source/StdLib.cpp.original | 261 +++++++++ test_bob_language.bob | 512 +++++++++++++++++- test_fib.bob | 4 + 36 files changed, 3434 insertions(+), 278 deletions(-) delete mode 100644 .DS_Store create mode 100644 bob-language-extension/.vscodeignore create mode 100644 bob-language-extension/INSTALLATION.md create mode 100644 bob-language-extension/README.md create mode 100644 bob-language-extension/bob-language-0.1.2.vsix create mode 100755 bob-language-extension/create-vsix.sh create mode 100644 bob-language-extension/example.bob create mode 100755 bob-language-extension/install.sh create mode 100644 bob-language-extension/language-configuration.json create mode 100755 bob-language-extension/package-vsix.sh create mode 100644 bob-language-extension/package.json create mode 100755 bob-language-extension/reload-extension.sh create mode 100644 bob-language-extension/snippets/bob.json create mode 100644 bob-language-extension/src/extension.ts create mode 100644 bob-language-extension/syntaxes/bob.tmLanguage.json create mode 100644 bob-language-extension/test-operators.bob create mode 100644 bob-language-extension/themes/bob-dark.json create mode 100644 bob-language-extension/tsconfig.json delete mode 100644 source/.DS_Store create mode 100644 source/StdLib.cpp.backup create mode 100644 source/StdLib.cpp.original diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index b8af6b35dd43078f3955c11e4fbadf56a40f2759..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKI|>3Z5S>vG!N$@uSMUZw^aOhWLB&QC6s@=NTprCgpGH?ZZR8D1UNV`NkXP*N zh=|TFo0-T&L`HB!x!KS)+c)o6FCz+si0p zwti-h{ zq5pp-aYY5Fz+Wk#gGIBL<4IXtJCCzkTi`3W<=o+Bm^%f7mt&xpV=Sy3PdzE}ip{ZK V6Wc(iBkpt{e+En!8Ws4p0uQpU6`%kB diff --git a/.gitignore b/.gitignore index f5c757a..00911aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.vscode build/ +.DS_Store diff --git a/BOB_LANGUAGE_REFERENCE.md b/BOB_LANGUAGE_REFERENCE.md index 5bdfd32..5b48d73 100644 --- a/BOB_LANGUAGE_REFERENCE.md +++ b/BOB_LANGUAGE_REFERENCE.md @@ -2,19 +2,55 @@ ## Overview -Bob is a dynamically typed programming language with a focus on simplicity and expressiveness. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages. +Bob is a dynamically typed programming language focused on safety and clarity. It features automatic type conversion, closures, and a clean syntax inspired by modern programming languages. ## Table of Contents -1. [Data Types](#data-types) -2. [Variables](#variables) -3. [Operators](#operators) -4. [Functions](#functions) -5. [Control Flow](#control-flow) -6. [Standard Library](#standard-library) -7. [Error Handling](#error-handling) -8. [Examples](#examples) -9. [Language Nuances](#language-nuances) +1. [Getting Started](#getting-started) +2. [Basic Syntax](#basic-syntax) +3. [Data Types](#data-types) +4. [Variables](#variables) +5. [Operators](#operators) +6. [Control Flow](#control-flow) +7. [Functions](#functions) +8. [Standard Library](#standard-library) +9. [Error Handling](#error-handling) +10. [Examples](#examples) + +## Getting Started + +### Running Bob Code +```bash +# Compile the interpreter +make + +# Run a Bob file +./build/bob your_file.bob + +# Run the comprehensive test suite +./build/bob test_bob_language.bob +``` + +### File Extension +- **`.bob`**: Standard file extension for Bob source code + +### Interactive Mode +- **Not implemented**: No REPL (Read-Eval-Print Loop) yet +- **File-based**: All code must be in `.bob` files + +## Basic Syntax + +### Statements +- **Semicolons**: Required at end of statements +- **Parentheses**: Required for function calls +- **Curly braces**: Required for function bodies +- **Case sensitive**: `var` and `Var` are different + +### Comments +```go +// Single line comment +/* Multi-line comment */ +``` ## Data Types @@ -38,14 +74,14 @@ Bob is a dynamically typed programming language with a focus on simplicity and e ## Variables ### Declaration -```bob +```go var name = "Bob"; var age = 25; var isActive = true; ``` ### Assignment -```bob +```go var x = 10; x = 20; // Reassignment ``` @@ -56,8 +92,7 @@ x = 20; // Reassignment - **Shadowing**: Local variables can shadow global variables - **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration -### Variable Behavior -```bob +```go var globalVar = 100; func testScope() { @@ -66,6 +101,66 @@ func testScope() { } ``` +### Assignment System + +Bob uses two assignment systems for safety and practicality: + +#### Assignment Statements +- **Purpose**: Main way to assign values +- **Syntax**: `variable = value;` (with semicolon) +- **Context**: Standalone statements + +```go +// Assignment statements (most common) +var x = 5; +x = 10; // Regular assignment +y += 5; // Compound assignment +z *= 2; // Compound assignment +``` + +#### Assignment Expressions (For Loops Only) +- **Purpose**: For loop clauses only +- **Syntax**: `variable = value` (without semicolon) +- **Context**: For loop initializers and increment clauses + +```go +// Assignment expressions (for loops only) +for (var i = 0; i < 5; i = i + 1) { } // Assignment in increment +for (j = 0; j < 5; j += 1) { } // Assignment in initializer and increment +``` + +#### Why This Design? + +**Prevents Common Bugs:** +```go +// This would be a bug in many languages: +if (x = 10) { } // Parse error - assignment in conditional + +// Bob forces you to write: +x = 10; +if (x == 10) { } // Clear comparison +``` + +**Keeps Expressions Pure:** +```go +// Prevents side effects in expressions: +var result = (x = 10) + (y = 20); // Parse error + +// Forces clean code: +x = 10; +y = 20; +var result = x + y; // Pure expression +``` + +**What's Blocked:** +```go +// These all cause parse errors: +var result = x = 10; // AssignExpr not allowed in expressions +var result = true ? (x = 10) : 0; // AssignExpr not allowed in ternary +var result = func(x = 10); // AssignExpr not allowed in function calls +var result = (x = 10) + 5; // AssignExpr not allowed in arithmetic +``` + ## Operators ### Arithmetic Operators @@ -83,12 +178,39 @@ func testScope() { - **Greater than or equal**: `>=` - **Less than or equal**: `<=` +### Logical Operators +- **And**: `&&` +- **Or**: `||` +- **Not**: `!` + +### Conditional (Ternary) Operator +```go +// Basic ternary operator +var result = condition ? valueIfTrue : valueIfFalse; + +// Examples +var max = x > y ? x : y; +var status = age >= 18 ? "adult" : "minor"; +var message = x > 0 ? "positive" : "non-positive"; + +// Ternary with expressions +var result = x + y > 10 ? x * y : x + y; + +// Ternary with function calls +var value = condition ? getValue() : getOtherValue(); + +// Nested ternary expressions +var result = x == 5 ? (y == 10 ? "both true" : "x true") : "x false"; +``` + +**Precedence**: The ternary operator has lower precedence than logical operators (`&&`, `||`) but higher than assignment operators. + ### String Operators #### Concatenation Bob supports bidirectional string + number concatenation with automatic type conversion: -```bob +```go // String + Number "Hello " + 42; // → "Hello 42" "Pi: " + 3.14; // → "Pi: 3.14" @@ -99,7 +221,7 @@ Bob supports bidirectional string + number concatenation with automatic type con ``` #### String Multiplication -```bob +```go "hello" * 3; // → "hellohellohello" 3 * "hello"; // → "hellohellohello" ``` @@ -109,23 +231,135 @@ Bob supports bidirectional string + number concatenation with automatic type con ### Number Formatting Bob automatically formats numbers to show only significant digits: -```bob +```go "Count: " + 2.0; // → "Count: 2" (no trailing zeros) "Pi: " + 3.14; // → "Pi: 3.14" (exact precision) "Integer: " + 42; // → "Integer: 42" (no decimal) ``` +## Control Flow + +### If Statements +```go +// Basic if statement +if (x > 10) { + print("x is greater than 10"); +} + +// If-else statement +if (x > 10) { + print("x is greater than 10"); +} else { + print("x is 10 or less"); +} + +// If-else if-else chain +if (x > 10) { + print("x is greater than 10"); +} else if (x > 5) { + print("x is greater than 5"); +} else { + print("x is 5 or less"); +} +``` + +### While Loops +```go +// Basic while loop +var i = 0; +while (i < 5) { + print(i); + i = i + 1; +} + +// While loop with break (using return) +func countToThree() { + var i = 0; + while (true) { + i = i + 1; + if (i > 3) { + return i; + } + } +} +``` + +### For Loops +```go +// Basic for loop +for (var i = 0; i < 5; i = i + 1) { + print(i); +} + +// For loop with no initializer +var j = 0; +for (; j < 3; j = j + 1) { + // Empty body +} + +// For loop with no condition (infinite loop with break) +for (var k = 0; ; k = k + 1) { + if (k >= 2) { + break; // or return + } +} + +// For loop with no increment +for (var n = 0; n < 3; ) { + n = n + 1; +} +``` + +### Do-While Loops +```go +// Basic do-while loop +var i = 0; +do { + print(i); + i = i + 1; +} while (i < 5); + +// Do-while with break +var j = 0; +do { + j = j + 1; + if (j == 3) { + break; + } +} while (j < 10); + +// Do-while with continue +var k = 0; +var sum = 0; +do { + k = k + 1; + if (k == 2) { + continue; + } + sum = sum + k; +} while (k < 5); +``` + +### Loop Features +- **Nested loops**: While, for, and do-while loops can be nested +- **Complex conditions**: Loops support any boolean expression +- **Function calls**: Loop conditions and bodies can contain function calls +- **Return statements**: Loops can be exited using return statements +- **Break and continue**: All loop types support break and continue statements +- **Variable scoping**: Variables declared in for loop initializers are scoped to the loop +- **Do-while behavior**: Do-while loops always execute the body at least once before checking the condition + ## Functions ### Function Declaration -```bob +```go func add(a, b) { return a + b; } ``` ### Function Call -```bob +```go var result = add(2, 3); // result = 5 ``` @@ -142,7 +376,7 @@ var result = add(2, 3); // result = 5 ### Closures Bob supports lexical closures with variable capture: -```bob +```go var outerVar = "Outer"; func makeGreeter(greeting) { @@ -154,7 +388,7 @@ var greeter = makeGreeter("Hello"); ``` ### Nested Functions -```bob +```go func outer() { func inner() { return 42; @@ -163,24 +397,10 @@ func outer() { } ``` -## Control Flow - -### Current Status -**Control flow statements are NOT implemented yet:** -- `if` statements -- `while` loops -- `for` loops -- `else` clauses - -### Planned Features -- Conditional execution -- Looping constructs -- Logical operators (`and`, `or`, `not`) - ## Standard Library ### Print Function -```bob +```go print("Hello, World!"); print(42); print(true); @@ -192,7 +412,7 @@ print(true); - Adds newline after output ### Assert Function -```bob +```go assert(condition, "optional message"); ``` @@ -220,7 +440,7 @@ assert(condition, "optional message"); - **Error messages**: Descriptive error messages printed to console ### Common Error Scenarios -```bob +```go // Division by zero 10 / 0; // Error: DivisionByZeroError @@ -237,7 +457,7 @@ undefinedVar; // Error: Undefined variable ## Examples ### Basic Calculator -```bob +```go func add(a, b) { return a + b; } @@ -251,7 +471,7 @@ print("Result: " + result); // Result: 17 ``` ### String Processing -```bob +```go func greet(name) { return "Hello, " + name + "!"; } @@ -266,7 +486,7 @@ print(greeting + " " + repeated); // Hello, Bob! HaHaHa ``` ### Variable Scoping Example -```bob +```go var globalCounter = 0; func increment() { @@ -280,82 +500,71 @@ increment(); print("After: " + globalCounter); // After: 1 ``` -## Language Nuances +### Loop Examples +```go +// Count from 1 to 5 +for (var i = 1; i <= 5; i = i + 1) { + print("Count: " + i); +} -### Type System -- **Dynamic typing**: Variables can hold any type -- **Automatic conversion**: Numbers and strings convert automatically -- **No type annotations**: Types are inferred at runtime +// Sum numbers from 1 to 10 +var sum = 0; +for (var j = 1; j <= 10; j = j + 1) { + sum = sum + j; +} +print("Sum: " + sum); -### Memory Management -- **Reference counting**: Uses `std::shared_ptr` for automatic memory management -- **No manual memory management**: No `delete` or `free` needed -- **Garbage collection**: Automatic cleanup of unused objects - -### Performance Characteristics -- **Interpreted**: Code is executed by an interpreter -- **AST-based**: Abstract Syntax Tree for execution -- **No compilation**: Direct interpretation of source code - -### Syntax Rules -- **Semicolons**: Required at end of statements -- **Parentheses**: Required for function calls -- **Curly braces**: Required for function bodies -- **Case sensitive**: `var` and `Var` are different - -### Comparison with Other Languages - -#### vs Python -- **Similar**: Dynamic typing, functions, closures -- **Different**: No `global` keyword, automatic string conversion, different error handling - -#### vs JavaScript -- **Similar**: Automatic type conversion, string concatenation -- **Different**: No `undefined`, different syntax, no `null` - -#### vs Lua -- **Similar**: Dynamic typing, functions -- **Different**: No `local` keyword, different scoping rules - -### Limitations -- **No control flow**: No if/while/for statements -- **No logical operators**: No `and`/`or`/`not` -- **No exception handling**: No try-catch blocks -- **No modules**: No import/export system -- **No classes**: No object-oriented features -- **No arrays/lists**: No built-in collection types -- **No dictionaries**: No key-value data structures - -### Future Features -- Control flow statements -- Logical operators -- Exception handling -- Collection types (arrays, dictionaries) -- Modules and imports -- Object-oriented programming -- Standard library expansion - -## Getting Started - -### Running Bob Code -```bash -# Compile the interpreter -make - -# Run a Bob file -./build/bob your_file.bob - -# Run the comprehensive test suite -./build/bob test_bob_language.bob +// Find first even number +var k = 1; +while (k % 2 != 0) { + k = k + 1; +} +print("First even: " + k); ``` -### File Extension -- **`.bob`**: Standard file extension for Bob source code +## Implementation Details -### Interactive Mode -- **Not implemented**: No REPL (Read-Eval-Print Loop) yet -- **File-based**: All code must be in `.bob` files +### Assignment System: Statements vs Expressions + +Bob uses a dual assignment system to prevent common programming errors: + +**Assignment Statements** (general use): +```go +var x = 10; +x = 20; // This is a statement, not an expression +``` + +**Assignment Expressions** (for loops only): +```go +for (var i = 0; i < 10; i = i + 1) { // Assignment in increment clause + print(i); +} +``` + +**Why This Design?** +- **Prevents `if (x = 10)` bugs**: Assignment statements can't be used in conditions +- **Maintains loop functionality**: For loops still work with assignment expressions +- **Clear intent**: Distinguishes between intentional assignments and accidental ones + +### Memory Management + +Bob uses automatic memory management with conservative cleanup: + +**Reference Counting**: +- All objects use `std::shared_ptr` for automatic cleanup +- Functions and thunks are tracked by reference count +- Objects are freed when no longer referenced + +**Automatic Cleanup**: +- Functions created in loops are automatically cleaned up when unused +- Cleanup occurs every 1000 function/thunk creations +- Only removes objects with `use_count() == 1` (conservative approach) + +**Safety Guarantees**: +- Functions referenced anywhere are never cleaned up +- Only truly unused functions are removed +- No dangling pointers or memory corruption --- -*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.* \ No newline at end of file +*This documentation covers the current Bob language implementation. For development plans, see ROADMAP.md.* \ No newline at end of file diff --git a/README.md b/README.md index d01fdce..13bba7a 100644 --- a/README.md +++ b/README.md @@ -1 +1,98 @@ # Bob + +A modern programming language focused on safety and clarity. + +## Design Philosophy + +Bob prioritizes safety and functional programming while keeping things practical. + +## Assignment System + +Bob uses two types of assignments to prevent bugs while keeping useful functionality: + +### Assignment Statements +```go +var x = 5; +x = 10; // Regular assignment +y += 5; // Compound assignment +``` + +### Assignment Expressions (For Loops Only) +```go +for (var i = 0; i < 5; i = i + 1) { } // Assignment in for loop +for (j = 0; j < 5; j += 1) { } // Assignment in for loop +``` + +### Why This Design? + +**Prevents Common Bugs:** +```go +// This would be a bug in many languages: +if (x = 10) { } // Parse error in Bob - prevents accidental assignment + +// Bob forces you to write: +x = 10; +if (x == 10) { } // Clear comparison +``` + +**Keeps Expressions Pure:** +```go +// Prevents side effects in expressions: +var result = (x = 10) + (y = 20); // Parse error + +// Forces clean code: +x = 10; +y = 20; +var result = x + y; // Pure expression +``` + +**Supports Functional Programming:** +```go +// Prevents confusing patterns: +var value = condition ? (x = 10) : (x = 20); // Parse error + +// Encourages clean patterns: +x = condition ? 10 : 20; // Clear assignment +``` + +**Keeps For Loops Practical:** +```go +// For loops work naturally: +for (var i = 0; i < 10; i = i + 1) { } // Assignment expressions allowed +for (j = 0; j < 10; j += 1) { } // Assignment expressions allowed +``` + +### Benefits + +1. **Prevents Common Bugs**: No accidental assignments in conditionals +2. **Expression Purity**: Expressions have no side effects +3. **Clear Semantics**: Assignments are clearly statements +4. **Functional Programming**: Encourages pure function patterns +5. **Practical For Loops**: Maintains useful for loop syntax +6. **Easier Debugging**: Clear separation of assignment and expression logic + +## Features + +- **Type Safety**: Dynamic typing with runtime type checking +- **Functional Programming**: First-class functions, closures, recursion +- **Memory Management**: Automatic memory management +- **Error Handling**: Comprehensive error reporting +- **Standard Library**: Built-in functions for common operations +- **Tail Call Optimization**: Efficient recursive function calls + +## Documentation + +See [BOB_LANGUAGE_REFERENCE.md](BOB_LANGUAGE_REFERENCE.md) for complete language documentation. + +## Building + +```bash +make clean && make +./build/bob your_file.bob +``` + +## Testing + +```bash +./build/bob test_bob_language.bob +``` diff --git a/ROADMAP.md b/ROADMAP.md index f96082c..68b9817 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,103 +1,165 @@ # Bob Language Development Roadmap ## Current Status -- Basic expressions (arithmetic, comparison, logical) -- Variables and assignment -- Print statements (converted to standard library function) -- Block statements -- Environment/scoping -- Function implementation (COMPLETED) -- Return statements (COMPLETED) -- Closures (COMPLETED) -- Assert function (COMPLETED) -- Standard library infrastructure (COMPLETED) -- First-class functions and higher-order functions (COMPLETED) -- String + number concatenation with smart formatting (COMPLETED) -- String multiplication (COMPLETED) -- Alphanumeric identifiers (COMPLETED) -- Comprehensive testing framework (COMPLETED) -## Phase 1: Core Language Features (High Priority) +Bob is a working programming language with a solid foundation. Here's what's currently implemented: -### 1. Control Flow -```bob -// If statements -if (x > 10) { - print "big"; -} else { - print "small"; -} +### ✅ **Core Language Features** -// While loops -var i = 0; -while (i < 5) { - print i; - i = i + 1; -} +#### **Data Types & Variables** +- **Numbers**: Integers, floats, automatic conversion +- **Strings**: Literals, concatenation, multiplication +- **Booleans**: `true`, `false` +- **None**: Null value representation +- **Variables**: Declaration, assignment, scoping +- **Assignment System**: Dual system (statements + expressions for loops) + +#### **Operators** +- **Arithmetic**: `+`, `-`, `*`, `/`, `%` +- **Comparison**: `==`, `!=`, `>`, `<`, `>=`, `<=` +- **Logical**: `&&`, `||`, `!` (with short-circuit evaluation) +- **Bitwise**: `&`, `|`, `^`, `<<`, `>>`, `~` +- **Compound Assignment**: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=` +- **Ternary**: `condition ? valueIfTrue : valueIfFalse` + +#### **Control Flow** +- **If Statements**: `if`, `else`, `else if` chains +- **While Loops**: Basic, nested, complex conditions +- **For Loops**: All clause variations, nested loops +- **Do-While Loops**: Basic, nested, break/continue support +- **Break/Continue**: Full support in all loop types + +#### **Functions** +- **Function Declaration**: `func name(params) { body }` +- **Parameters**: Any number of parameters +- **Return Values**: Explicit and implicit returns +- **Closures**: Lexical scoping with variable capture +- **First-Class Functions**: Functions as values, parameters, return values +- **Anonymous Functions**: `func(params) { body }` +- **Nested Functions**: Functions defined inside other functions +- **Recursion**: Full support including deep recursion + +#### **Standard Library** +- **`print()`**: Output with automatic type conversion +- **`assert()`**: Testing with custom error messages +- **`type()`**: Runtime type checking +- **`toString()`**: Universal string conversion +- **`toNumber()`**: String-to-number conversion +- **`input()`**: User input capability +- **`time()`**: Microsecond precision timing + +#### **Advanced Features** +- **String Operations**: Bidirectional string + number concatenation +- **Number Formatting**: Smart significant digits +- **Memory Management**: Automatic cleanup +- **Error Handling**: Basic error reporting +- **Testing Framework**: Built-in assert function +- **Operator Precedence**: Full precedence hierarchy +- **Variable Shadowing**: Proper scoping rules + +### **Current Limitations** + +#### **Data Structures** +- **No Arrays/Lists**: No built-in collection types +- **No Maps/Dictionaries**: No key-value data structures +- **No Sets**: No unique value collections + +#### **Advanced Language Features** +- **No Classes/Objects**: No object-oriented programming +- **No Modules/Imports**: No code organization system +- **No Exception Handling**: No try-catch blocks +- **No Type Annotations**: No static type checking +- **No Generics**: No parametric polymorphism + +#### **Standard Library Gaps** +- **No File I/O**: No reading/writing files +- **No Network I/O**: No HTTP or socket operations +- **No Math Library**: No advanced mathematical functions +- **No Date/Time**: No date manipulation (except `time()`) +- **No Random Numbers**: No random number generation + +#### **Development Tools** +- **No REPL**: No interactive mode +- **No Debugger**: No debugging tools +- **No Profiler**: No performance analysis +- **No Package Manager**: No dependency management + +## **Future Development Phases** + +### **Phase 1: Data Structures (High Priority)** + +#### **Arrays/Lists** +```go +var numbers = [1, 2, 3, 4, 5]; +print(numbers[0]); // 1 +numbers[1] = 42; // Modify element +var length = len(numbers); // Get length ``` -**Implementation:** -- Add `IfStmt` and `WhileStmt` to Statement.h -- Update parser to handle `if` and `while` keywords -- Implement control flow in interpreter +**Implementation Plan:** +- Add array literal syntax `[expr, expr, ...]` +- Implement array indexing `array[index]` +- Add array assignment `array[index] = value` +- Create `len()` function for arrays +- Support nested arrays -### 2. Logical Operators -```bob -// Currently missing: and, or, not operators -if (x > 0 and y < 10) { - print "valid range"; -} -``` - -**Implementation:** -- Add `and`, `or`, `not` operator parsing -- Implement logical operator evaluation in interpreter - -### 3. Better Error Handling -```bob -// Current: Basic error messages -// Goal: Line numbers, better context -Error at line 5: Expected ';' after expression - print 42 - ^ -``` - -## Phase 2: Data Structures (Medium Priority) - -### 4. Arrays/Lists -```bob -var numbers = [1, 2, 3, 4]; -print numbers[0]; // 1 -numbers[1] = 42; -``` - -### 5. Maps/Dictionaries -```bob +#### **Maps/Dictionaries** +```go var person = {"name": "Bob", "age": 25}; -print person["name"]; -person["city"] = "NYC"; +print(person["name"]); // "Bob" +person["city"] = "NYC"; // Add/modify entry +var keys = keys(person); // Get all keys ``` -## Phase 3: Standard Library (Medium Priority) +**Implementation Plan:** +- Add map literal syntax `{key: value, ...}` +- Implement map indexing `map[key]` +- Add map assignment `map[key] = value` +- Create `keys()`, `values()` functions +- Support nested maps -### 6. Additional Built-in Functions -```bob -len("hello"); // String length -input("Enter name: "); // User input -random(1, 100); // Random numbers -type(42); // Type checking -``` +### **Phase 2: Standard Library Expansion (Medium Priority)** -### 7. File I/O -```bob +#### **File I/O** +```go var content = readFile("data.txt"); writeFile("output.txt", "Hello World"); +var lines = readLines("config.txt"); ``` -## Phase 4: Advanced Features (Lower Priority) +#### **Math Library** +```go +var result = sqrt(16); // 4.0 +var random = rand(1, 100); // Random number +var max = max(5, 10, 3); // 10 +``` -### 8. Classes & Objects -```bob +#### **String Processing** +```go +var parts = split("a,b,c", ","); // ["a", "b", "c"] +var joined = join(parts, "-"); // "a-b-c" +var upper = toUpper("hello"); // "HELLO" +``` + +### **Phase 3: Advanced Language Features (Lower Priority)** + +#### **Exception Handling** +```go +try { + var result = 10 / 0; +} catch (error) { + print("Error: " + error); +} +``` + +#### **Modules/Imports** +```go +import "math.bob"; +import "utils.bob" as utils; +``` + +#### **Classes & Objects** +```go class Person { init(name, age) { this.name = name; @@ -105,99 +167,104 @@ class Person { } greet() { - print "Hello, I'm " + this.name; + return "Hello, I'm " + this.name; } } - -var bob = Person("Bob", 25); -bob.greet(); ``` -### 9. Modules/Imports -```bob -import "math.bob"; -import "utils.bob"; +### **Phase 4: Development Tools (Lower Priority)** + +#### **Interactive Mode (REPL)** +```bash +$ bob +> var x = 5 +> print(x + 3) +8 +> func add(a, b) { return a + b; } +> add(2, 3) +5 ``` -### 10. Type System -```bob -// Optional type annotations -fun add(a: number, b: number): number { - return a + b; -} +#### **Debugger** +```go +debugger; // Breakpoint +var x = 5; +// Step through code ``` -## Implementation Tips +## **Implementation Guidelines** -### For Each Feature: -1. Lexer: Add new tokens if needed -2. Parser: Add new expression/statement types -3. AST: Define new node types -4. Interpreter: Implement evaluation logic -5. Test: Create test cases using assert function +### **For Each New Feature:** +1. **Lexer**: Add new tokens if needed +2. **Parser**: Add new expression/statement types +3. **AST**: Define new node types +4. **Interpreter**: Implement evaluation logic +5. **Testing**: Create comprehensive test cases -### Testing Strategy: -```bob -// Use the new assert function for comprehensive testing +### **Testing Strategy:** +```go +// Use the built-in assert function for testing assert(add(2, 3) == 5, "add(2, 3) should equal 5"); -assert(x > 0, "x should be positive"); +assert(len([1, 2, 3]) == 3, "Array length should be 3"); ``` -## Recommended Next Steps +### **Code Quality Standards:** +- **Comprehensive Testing**: Every feature needs test coverage +- **Error Handling**: Graceful error messages +- **Documentation**: Update language reference +- **Performance**: Consider memory and speed implications -1. Add if statements (fundamental control flow) -2. Add while loops (enables iteration) -3. Implement logical operators (and, or, not) -4. Improve error messages (better developer experience) -5. Add arrays (most useful data structure) +## **Success Metrics** -## Success Metrics +### **Completed ✅** +- [x] Core language syntax and semantics +- [x] All basic operators and expressions +- [x] Control flow statements +- [x] Functions and closures +- [x] Standard library basics +- [x] Testing framework +- [x] Basic error handling +- [x] Memory management +- [x] Assignment system design -- [x] Can write simple functions -- [x] Can use return statements -- [x] Can use closures -- [x] Has assert function for testing -- [x] Has standard library infrastructure -- [x] Supports first-class functions -- [x] Has comprehensive testing framework -- [ ] Can use if/else statements -- [ ] Can use while loops -- [ ] Can use logical operators -- [ ] Can work with arrays -- [ ] Can read/write files -- [ ] Has good error messages +### **In Progress 🔄** +- [ ] Data structures (arrays, maps) +- [ ] Extended standard library +- [ ] Performance optimizations -## Resources +### **Planned 📋** +- [ ] Advanced language features +- [ ] Development tools +- [ ] Documentation improvements -- [Crafting Interpreters](https://craftinginterpreters.com/) - Excellent resource for language implementation -- [Bob's current source code](./source/) - Your implementation -- [Test files](./*.bob) - Examples of current functionality +## **Resources** -## Recent Achievements +- **[BOB_LANGUAGE_REFERENCE.md](BOB_LANGUAGE_REFERENCE.md)** - Complete language documentation +- **[ASSIGNMENT_DESIGN.md](ASSIGNMENT_DESIGN.md)** - Assignment system design rationale +- **[test_bob_language.bob](test_bob_language.bob)** - Comprehensive test suite +- **[Crafting Interpreters](https://craftinginterpreters.com/)** - Excellent resource for language implementation -### Function Implementation (COMPLETED) -- Function declarations with parameters -- Function calls with arguments -- Return statements -- Proper scoping and closures -- Nested function calls +## **Recent Major Achievements** -### Standard Library (COMPLETED) -- `print()` function (converted from statement) -- `assert()` function with custom messages -- Extensible architecture for adding more functions +### **Assignment System Design** +- Implemented dual assignment system (statements + expressions) +- Prevents common bugs like `if (x = 10)` +- Maintains practical for loop syntax +- Documentation and testing -### Testing Framework (COMPLETED) -- Comprehensive test suite using assert -- Tests for all language features -- Proper error handling and execution stopping +### **Control Flow Implementation** +- All loop types: while, for, do-while +- Break/continue support +- Nested scenarios +- Basic edge case handling -### Advanced Language Features (COMPLETED) -- First-class functions and higher-order functions -- Function passing as arguments +### **Function System** +- First-class functions +- Closures and lexical scoping +- Anonymous functions - Function composition patterns -- Callback patterns and function storage -- String + number concatenation with smart formatting -- String multiplication (string * number, number * string) -- Alphanumeric identifiers support -- Stress testing with 100-parameter functions \ No newline at end of file +- Recursion support + +--- + +*Last updated: August 2025* \ No newline at end of file diff --git a/bob-language-extension/.vscodeignore b/bob-language-extension/.vscodeignore new file mode 100644 index 0000000..9def774 --- /dev/null +++ b/bob-language-extension/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +.vscode-test/** +src/** +.gitignore +.yarnrc +vsc-extension-quickstart.md +**/tsconfig.json +**/.eslintrc.json +**/*.map +**/*.ts \ No newline at end of file diff --git a/bob-language-extension/INSTALLATION.md b/bob-language-extension/INSTALLATION.md new file mode 100644 index 0000000..d59c091 --- /dev/null +++ b/bob-language-extension/INSTALLATION.md @@ -0,0 +1,97 @@ +# Bob Language Extension Installation Guide + +## Quick Installation + +### Option 1: Automatic Installation (Recommended) +```bash +cd bob-language-extension +./install.sh +``` + +### Option 2: Manual Installation +1. Copy the extension files to your VS Code extensions directory: + - **macOS**: `~/.vscode/extensions/bob-language-0.1.0/` + - **Linux**: `~/.vscode/extensions/bob-language-0.1.0/` + - **Windows**: `%APPDATA%\Code\User\extensions\bob-language-0.1.0\` + +2. Restart VS Code/Cursor + +### Option 3: VSIX Package +```bash +cd bob-language-extension +./package-vsix.sh +``` +Then install the generated `.vsix` file in VS Code/Cursor. + +## Features Included + +### ✅ Syntax Highlighting +- Keywords: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, `var`, `func` +- Built-in functions: `print`, `assert`, `input`, `type`, `toString`, `toNumber`, `time` +- Data types: numbers, strings, booleans, `none` +- Operators: arithmetic, comparison, logical, bitwise, compound assignment + +### ✅ Code Snippets +Type these prefixes and press `Tab`: +- `func` - Function definition +- `if` - If statement +- `while` - While loop +- `for` - For loop +- `var` - Variable declaration +- `print` - Print statement +- `assert` - Assert statement +- `anon` - Anonymous function +- `test` - Test function + +### ✅ Language Features +- Auto-closing brackets and quotes +- Smart indentation +- Comment support (`//` and `/* */`) +- Code folding +- Hover information for built-in functions +- IntelliSense completion + +### ✅ Color Theme +- "Bob Dark" theme included +- Optimized colors for Bob syntax + +## Testing the Extension + +1. Open VS Code/Cursor +2. Open the `example.bob` file in the extension directory +3. You should see syntax highlighting, code completion, and snippets working + +## File Association + +Files with `.bob` extension will automatically be recognized as Bob language files. + +## Troubleshooting + +### Extension not working? +1. Check that the extension is installed in the correct directory +2. Restart VS Code/Cursor completely +3. Check the Extensions panel (Ctrl+Shift+X) to see if the extension is listed + +### Syntax highlighting not working? +1. Make sure your file has a `.bob` extension +2. Check the language mode in the bottom-right corner of VS Code +3. Manually select "Bob" as the language mode if needed + +### Snippets not working? +1. Type the snippet prefix (e.g., `func`) +2. Press `Ctrl+Space` to trigger suggestions +3. Select the snippet and press `Tab` + +## Development + +To modify the extension: +1. Edit the files in the extension directory +2. Run `npm run compile` to rebuild TypeScript +3. Reload VS Code/Cursor to see changes + +## Support + +For issues or questions: +1. Check the README.md file +2. Look at the example.bob file for syntax examples +3. Review the TextMate grammar in `syntaxes/bob.tmLanguage.json` \ No newline at end of file diff --git a/bob-language-extension/README.md b/bob-language-extension/README.md new file mode 100644 index 0000000..f1b71d4 --- /dev/null +++ b/bob-language-extension/README.md @@ -0,0 +1,103 @@ +# Bob Language Extension for VS Code + +This extension provides syntax highlighting and language support for the Bob programming language in Visual Studio Code and Cursor. + +## Features + +- **Syntax Highlighting**: Full syntax highlighting for Bob language constructs +- **Code Snippets**: Useful code snippets for common Bob patterns +- **Auto-closing Brackets**: Automatic bracket and quote pairing +- **Indentation**: Smart indentation for Bob code blocks +- **Comments**: Support for line and block comments +- **Folding**: Code folding support with region markers + +## Supported Syntax + +### Keywords +- Control flow: `if`, `else`, `while`, `for`, `break`, `continue`, `return` +- Variable declaration: `var` +- Function declaration: `func` +- Logical operators: `and`, `or`, `not` + +### Built-in Functions +- `print()`, `assert()`, `input()`, `type()`, `toString()`, `toNumber()`, `time()` + +### Data Types +- Numbers (integers and floats) +- Strings (single and double quoted) +- Booleans (`true`, `false`) +- None value (`none`) + +### Operators +- Arithmetic: `+`, `-`, `*`, `/`, `%` +- Comparison: `==`, `!=`, `<`, `>`, `<=`, `>=` +- Logical: `&&`, `||`, `!` +- Bitwise: `&`, `|`, `^`, `<<`, `>>`, `~` +- Compound assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=` + +## Installation + +### From Source +1. Clone this repository +2. Run `npm install` to install dependencies +3. Run `npm run compile` to build the extension +4. Press `F5` in VS Code to launch the extension in a new window + +### Manual Installation +1. Copy the extension files to your VS Code extensions directory +2. Restart VS Code +3. Open a `.bob` file to see syntax highlighting + +## Usage + +### Code Snippets +Type the following prefixes and press `Tab` to insert code snippets: + +- `func` - Function definition +- `if` - If statement +- `ifelse` - If-else statement +- `while` - While loop +- `for` - For loop +- `var` - Variable declaration +- `print` - Print statement +- `assert` - Assert statement +- `anon` - Anonymous function +- `return` - Return statement +- `break` - Break statement +- `continue` - Continue statement +- `comment` - Comment block +- `test` - Test function + +### File Association +Files with the `.bob` extension will automatically be recognized as Bob language files. + +## Example + +```go +// This is a comment +var message = "Hello, Bob!"; +print(message); + +func factorial(n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +var result = factorial(5); +assert(result == 120, "Factorial calculation failed"); +``` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This extension is licensed under the MIT License. + +## Links + +- [Bob Language Repository](https://github.com/bob-lang/bob) +- [VS Code Extension API](https://code.visualstudio.com/api) \ No newline at end of file diff --git a/bob-language-extension/bob-language-0.1.2.vsix b/bob-language-extension/bob-language-0.1.2.vsix new file mode 100644 index 0000000000000000000000000000000000000000..d995ae8d294d1061bf18e196f0e4f7779bc69ac9 GIT binary patch literal 6717 zcmb7|1yqz<*T;t#N$Ktm0TF3Ix*57lV(5+m1_{M+RFIGkkrD~%92!YQx+NUCOS<9X zzVH3sEAMx&_kJ^Lp7qRH&-(52>~qe4@AKDGMMK91{Psy|0Gj;k=JyLWfCgX#h1j@y zIk>s<>gi$u&`SHF?SK1uLva8o=m#hO06NL_)0%(DAilxya&>Tbw}E*5E$lZa=D!AX zda?B}4F~}6QUL(ee-5*9v%2f(<>vaU5mp+i>dcD}H1-v*sCgtBSlmif{U$2)V*^B5 z92)%4`EW)An@>bvCEgD=?y9jc{#?acR4N{)^em%TveNi_P!LA{Nfc{F7!>uhGZAal z>G*0hf2gJi`SC7cni#R}dcdfgV!-+H?aK`I{`=n^UrxNGFt=+C<3YMT+&)|i{xlsO z0@*=H4M!88{94GnIFu>dbSZfGnftc9fUxF+z-H7=hNsUTN;N(&Gvv7FEyg+~BDLj? z5?UC@!txaeKEn3-62k|)^a;aCAF6n&gED?F7hB0_le_o0qK>p8-PDCC`L1Z$^Zg@% zP|6f{2}JvDdX`$o8~6Dsm=86V9Plf>seo0A`#|ELy~6Q|61vB}7{eQk^s-v?!qg*8 zPLj6XThBDP#$>DU=BKqla#QMU9hk3c8<+ecOsRgC?7kn8y%x&;j-R2i2q#$``cl z!??{rVQ0RLiceg9k%tEP1~0KRQfC=ErJYILs(#@T5Yq{e;JDHfBhf%<*Zp{36`)^FYZEZ&1&&W0eY1coang{0J| zB-GM`HS!<5i5BfB@|0fT@@nF+2uBZWjc2f&Ygt0{n|WMf#dG5$m2B0OKgd{PDd3X_ zw>=rH$EpmC>3BnSuh%&zGKIm&oJ!P~kyUu$O0szjjmv>0h@bbxi;;8fLMCxJPi);I z7xX-_Pkwm?L*LhIgAj@lr)tKs4tH}MtKBwu=b2=7Zv*A1u}%ty+GHocS7itpmsd`A zC3q3&BeSn?>}vd}UF-}?$E!Uil$1cU&tLO+2QWQ&aJ=1DX7x_49MkKab<_iT)eQ0u z#KduYn6I~JXL#Jks8zjowYgGML^xE1tK8tyAvkvrl~6KxYu)+$9;@%s8y9-24ZrfS z09>w0Xpr5yB)A}q}sWEQ;R0uEU!IUWoX&9}|Ai^YG~%ke*5Hh=cyV0{o)-m2rKr33vi z_8sZbDb)@>$rpliGv-Dz)+{lo66xu^3ftV=tkplsH$K*Hpy?<*1b7K*hF=+;QHRh$ z?5Qn2A{-sale~06;m}qSr3Fs1z9RL=dK7if(4fb4A)DQP)Oazj2pp1Ju%Oh3F&EHx z4I8?^W@O0+wv)v0GU{|U_bM|=uF^B7=@PFETCb#{=nzHnuRnZgZMn@f_HY^ z&P+G4f@ufyQjvuhFSYGqo40Q*quqYHZ@KCA?2(>XYr)ip5k+Pjkay&c3*aI@XnHU3 z_Q1CS{J~;sU5iGOPfva;Ab)6nLo$0Q=e6cn3T24*=u8Z}rwBAME#fw;& z;4-MmO8FL>vd3WmE-Qg;GzoqZYEN5mkj2-O0QSk`l`Pr8b`PA3%|IV-yU($>U;HOV z8n>A@2W!#>O%|8lB|AM1Osja^A(nh1Mb8eIbuuFn#vxWN_H+5+n>`ve_m$OK1Bc(- z@LZ##N4X|>Bc0dBWnGrY#UZ1eLcI^>NJaJthf0xNZ6o%B@|xp(d&x@eip>6O35Sob zLxJAfvj};T)Ug00twb@*TbG=l6L=Ye-#K|C$q`7n-R7QcrO(dB@~*N^nq6 zPS6^(aU`$0`1Y3H8+%=q2h|0$1*$>1M)c!TewrgL#UYg8D%qJ1$hiJSh=havJ(S(d znZXgN2zKkFNHr?>LQ>DtgIhZej)C4{$4CJJB5(|bGNNNqAhbZF*UHm&!{z%jNG5G@ z+j}6MjfZ1JbP~>tcp#T2Q*1sK%k}AP-G1F8p1@qA1Gq0IRF!`{fn7J*=pUDx^=6m&`6I`}%C%c2z zm$PLwPV`~Rvgr7o7C7V8dwJbkX}%rVgcI(Xox!m@cVZ3?jda^{Wi_bBd>$t07jAJ% zDw}?pT@M@sd9O5qTD8ZA-R^MKHj4^HePG&bev``IJ|eTE8+tZl_in~ZW8AcB!Z(G` zAuSo++kh|@Rf=0#PBCjgv0d_yB&*h`8nO@zurN4327keR-isRg-5$cBy-&#IuVu- zE~$vmi2Ut{z8R49B9~l)P}P#+N45Tu2d=l_Qt(-ADTqD8VY+zAsRQSlp~>*6;twu| zD}vy<@jjjk9w<%Q!>0tAFw5NAk8QWS>0Sx3FDa!NpH2;JEzrVOf?e zhUiO+VFaMTDUhE*zp@B38)BTY?9)XIRv8jug-y{CPhoq;^Mdu1&%SW>Ij|`4n^5!) zJjqTb@N@eNHhxonhi~=EQ$W&WE9>!?j7wS%CeD&5@npT=iuMba?52b_T+BF_N!vtA zto!Kvu0QHLI3kJdu%nK@F8ugB=;E2<{_ZS$95O#>5j5aGVWRW%~eOXAJOa*nOEa_qN~)lZ3yDn419ACTD&?#a`wYsp;|~ za}uDUEPwl^TkLnQvwaQeV}AY#ul?QFh?lgNUBL8t^0K``Xvy-*Oa`6xI>l)d=^TD- zZ8_GKGfOILa#|3Lyd(AEJl3ZJM!+bkXLrUPEW@-$OC!4w5c^BB9`o9TpwfdMr!sEj-_BV_DbBAs`SfGH8XRK zxX(s|>@>OmRY$ubcXVUuzf?DkP=bRG6#$sIR$j6jowmCr*vZn)=5L$qINdJCse2^f z`wTFP`FFD_jJiKQhiKWWW{k&?Sf6z0Gpou(DuWWA6w}2k^g^&{DN43PkGzYD9tmR# zJ_Ge{Xbp9`c7;&TP^Q;lEA@rVSzlHWxZ1pIEWonJwf%aZ6Lt18H@BZOGqu9jReT5v z;lO(uQp%jzVX}uR;TxD7a=ONN$siqu;vssex2(H(=JURt*j5&(VTi_4a609kGsBr; zr-1U-x{k@nDAf?Sv?JN7B=LoR9Ir33`s|&I(PdwF|E%iZ%gO`L;_#CD4zqGO zoE(Qq#VB?*?6D5+*}-1m*N22;?Hz@XDjxM?gtACRJgpMNVGbImi8714z9;?gop5c= z0^4`x2lY_GhiC7Bq4(Cw_2M7Bk8)S&{>q%}W>8?Gzj&?*ri3*Yh@aII zr(G2IxTu#dvSHUhA^v_FiKEN}*20l8-y?l7`iHGlp;g$kLg2o(!BU2u3IdfaIy~QW zseJ~HeDXQ4q5Dv36WD;ulw|*wfjv6dgfJjdlACvC>DA>wKR5chLM)*+|DSW?L~=$o zI~oAcgAV|(-B`_D{|o!=+z4?2S-RSJ|C80-qu=Z~agU&3nd{Xg0mHY72Eq2u3i>yy zZtVihlc)lG(OjK9{oEIaMKqHZ_c~@)D{#wfHj557O&mTXyjK4_XRTZ~8F3(gJlc<& zE7i8oNUg^dh2ni~Niwl(VO@ig@@?E} zk7HBPD`AL6x8KwxeFLQji89h%vYGlj*g3}SkD8bdb%=8DXO@_Eeeq^x9J@-4Twj9d z7Uk7HN~tNmzafGX$L&rr28ke~K^mkk`#*xhigkp>BHkLYymFmu8$Z(Pl+nWN%VaF~ zA;^hm`iL_(~}GGQ64{nxu_|*!zt9nxA#=f8|S4mlz>@LpiyY zEzEdW+;@oh>@ezQ_aq<%@6^U*DkqVnHk>!6URd@()%xx1@RIEK62tl6EpuV@M3p&1 zr3OKR7A8|SMUIeC6-NdYt-RnJk%4prJ|ItHHjqRN-+>UNCq8_zwuUt&Zggsj@Uch1 zX!_QZ=pO80yQQ7jV|*a8&NnI{*#9mXyPQr9x)7pCpA^C20WFW+Ke(kYi$MnzwwUBm z;Y99PMg1s2hT~M>K;L^=F0{nqnC$_VcD}=RzQOCn5UiLB<|F$2V{lc-i^hrS(6$s{ z)|S$`kDBxG%Pt{BlA%7(2rc70U)ilZ{#NF5*V)^hL$6s!pmDi9%-`5v-+Pwd^o|F& zNKIU&R8+F;e3BdXjfN6S^zqZfH^mZF1&$^`+6&~-q?oDzwbL9u$!x`gPpghCFI&>`r8^49^K&D%0L1? z5{G+JbC%kS_$S~cmC2j&MZF4qmr0Nd$cOdRk-J-c4rTV1evq(zNxP!2j2c(_NXrw?O7>Jh!J`%Ks5bCEhc?6 z3n86Tst|_aR5*cUQ!P$UF{V>^7v%tUA4K|EpROgH26xjAGl>y`HQxIMg2idsfvbI| zE@1!_TuD#7BC}*0U+otsT;yV0sK>RQQ+ee!Ylg3L>J`>@_!c9krr?7AeplSNBT%AV zG9gaE)}1~Ct(@$@LVi5z{rmA+-mal42b+<-Y(>kGtBWPnGlkV*^9H;1vxK}I`9iQ! za#(e2%ds~VYs=OUgF!ZLZcgVj#drs?C31O@X$is93#!Jw!a@wKQH{h%HEVR(iR)st z!~Kf008Lz40WxyJl2+;PJcVcadB!+^Y;i|Y+sOe z_}{iIS1Vhmd#jBN-^wUS7=&?$(-Inae(+G`IN279eiunKR!XS6k>s1DSbS@7?@p*y zkIE~z%;Qn(0ow9O=+gV@d>LID<|z&^ZM(#3I8s_kK&X>qnrw8~79MM{3tEL&{1K+7kZh!l5F<6T|7HE zuspCF*dVcS%-TJ&an@a0$*J1EGEvDfUyW7O@WzCZyf3r*qu}S{6Fp*-n&JE6bR@+& zj#9C;B7QWPw0rv8Y6)G{J=?9@L8JJ`VM5(wt;;fB=LI!7O%v*D%#E91AgYo-QfLX zO#ce}hedJImi{;RdhG+Uf}{Q%{D(>X&jQ@^KzpT)XavVM-GN%Zqe{IY=k zD%79ni}1$s{6YM4sQ+m3udsh)>!uR_{03sz5B;%=e#icoF8@^w;Ptw@F67tWkJrUK JknFdA{{wGnQ}zG= literal 0 HcmV?d00001 diff --git a/bob-language-extension/create-vsix.sh b/bob-language-extension/create-vsix.sh new file mode 100755 index 0000000..771c13e --- /dev/null +++ b/bob-language-extension/create-vsix.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Create a simple VSIX package without npm + +echo "Creating Bob Language Extension VSIX package..." + +# Read version from package.json +VERSION=$(grep '"version"' package.json | sed 's/.*"version": "\([^"]*\)".*/\1/') +echo "Building version: $VERSION" + +# Create a temporary directory for the package +TEMP_DIR=$(mktemp -d) +PACKAGE_DIR="$TEMP_DIR/bob-language-$VERSION" + +# Create the extension directory structure +mkdir -p "$TEMP_DIR/extension" + +# Copy all extension files to the extension directory +cp package.json "$TEMP_DIR/extension/" +cp language-configuration.json "$TEMP_DIR/extension/" +cp -r syntaxes "$TEMP_DIR/extension/" +cp -r snippets "$TEMP_DIR/extension/" +cp -r themes "$TEMP_DIR/extension/" +cp README.md "$TEMP_DIR/extension/" + +# Create the VSIX file (simple zip with .vsix extension) +cd "$TEMP_DIR" +zip -r "bob-language-$VERSION.vsix" extension/ + +# Move to the extension directory +mv "bob-language-$VERSION.vsix" /Users/bobbylucero/Developer/Bob/bob-language-extension/ + +# Clean up +rm -rf "$TEMP_DIR" + +echo "VSIX package created: bob-language-$VERSION.vsix" +echo "" +echo "To install in Cursor:" +echo "1. Open Cursor" +echo "2. Go to Extensions (Cmd+Shift+X)" +echo "3. Click the '...' menu and select 'Install from VSIX...'" +echo "4. Select the bob-language-$VERSION.vsix file" +echo "" +echo "Or simply restart Cursor - the extension should already be working!" \ No newline at end of file diff --git a/bob-language-extension/example.bob b/bob-language-extension/example.bob new file mode 100644 index 0000000..473bfcd --- /dev/null +++ b/bob-language-extension/example.bob @@ -0,0 +1,75 @@ +// Bob Language Example - Demonstrates syntax highlighting + +// Variable declarations +var message = "Hello, Bob!"; +var number = 42; +var pi = 3.14159; +var isActive = true; +var empty = none; + +// Print statements +print(message); +print("Number: " + toString(number)); +print("Pi: " + toString(pi)); + +// Function definition +func factorial(n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +// While loop with break +var counter = 0; +while (counter < 10) { + if (counter == 5) { + break; + } + counter = counter + 1; +} + +// For loop with continue +var sum = 0; +for (var i = 0; i < 10; i = i + 1) { + if (i % 2 == 0) { + continue; + } + sum = sum + i; +} + +// Anonymous function +var double = func(x) { + return x * 2; +}; + +// Logical operators +var a = true && false; +var b = true || false; +var c = !false; + +// Bitwise operators +var d = 5 & 3; +var e = 5 | 3; +var f = 5 ^ 3; +var g = 5 << 1; +var h = 5 >> 1; + +// Compound assignment +var value = 10; +value += 5; +value *= 2; +value -= 3; + +// Assertions +assert(factorial(5) == 120, "Factorial calculation failed"); +assert(sum == 25, "Sum calculation failed"); + +// Type checking +var typeOfMessage = type(message); +var typeOfNumber = type(number); + +// Time function +var currentTime = time(); + +print("All tests passed!"); \ No newline at end of file diff --git a/bob-language-extension/install.sh b/bob-language-extension/install.sh new file mode 100755 index 0000000..2882020 --- /dev/null +++ b/bob-language-extension/install.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Bob Language Extension Installer for VS Code/Cursor + +echo "Installing Bob Language Extension..." + +# Get VS Code extensions directory +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions" + CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + VSCODE_EXTENSIONS_DIR="$HOME/.vscode/extensions" + CURSOR_EXTENSIONS_DIR="$HOME/.cursor/extensions" +elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then + # Windows + VSCODE_EXTENSIONS_DIR="$APPDATA/Code/User/extensions" + CURSOR_EXTENSIONS_DIR="$APPDATA/Cursor/User/extensions" +else + echo "Unsupported operating system: $OSTYPE" + exit 1 +fi + +# Create extension directory +EXTENSION_NAME="bob-language-0.1.0" +EXTENSION_DIR="$VSCODE_EXTENSIONS_DIR/$EXTENSION_NAME" + +echo "Installing to: $EXTENSION_DIR" + +# Create directories +mkdir -p "$EXTENSION_DIR" + +# Copy extension files +cp -r package.json "$EXTENSION_DIR/" +cp -r language-configuration.json "$EXTENSION_DIR/" +cp -r syntaxes "$EXTENSION_DIR/" +cp -r snippets "$EXTENSION_DIR/" +cp -r README.md "$EXTENSION_DIR/" + +# Compile TypeScript if available +if command -v npm &> /dev/null; then + echo "Compiling TypeScript..." + npm install + npm run compile + cp -r out "$EXTENSION_DIR/" +else + echo "npm not found, skipping TypeScript compilation" +fi + +echo "Bob Language Extension installed successfully!" +echo "" +echo "To use the extension:" +echo "1. Restart VS Code/Cursor" +echo "2. Open a .bob file" +echo "3. Enjoy syntax highlighting and code snippets!" +echo "" +echo "Code snippets available:" +echo "- func: Function definition" +echo "- if: If statement" +echo "- while: While loop" +echo "- for: For loop" +echo "- var: Variable declaration" +echo "- print: Print statement" +echo "- assert: Assert statement" +echo "- And many more!" \ No newline at end of file diff --git a/bob-language-extension/language-configuration.json b/bob-language-extension/language-configuration.json new file mode 100644 index 0000000..0f55d3e --- /dev/null +++ b/bob-language-extension/language-configuration.json @@ -0,0 +1,35 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "indentationRules": { + "increaseIndentPattern": "\\{[^}]*$|\\b(func|if|else|while|for)\\b.*$", + "decreaseIndentPattern": "^\\s*[})]" + }, + "folding": { + "markers": { + "start": "^\\s*//\\s*#?region\\b", + "end": "^\\s*//\\s*#?endregion\\b" + } + } +} \ No newline at end of file diff --git a/bob-language-extension/package-vsix.sh b/bob-language-extension/package-vsix.sh new file mode 100755 index 0000000..dfa73c8 --- /dev/null +++ b/bob-language-extension/package-vsix.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Package Bob Language Extension as VSIX + +echo "Packaging Bob Language Extension..." + +# Check if vsce is installed +if ! command -v vsce &> /dev/null; then + echo "Installing vsce..." + npm install -g @vscode/vsce +fi + +# Install dependencies +npm install + +# Compile TypeScript +npm run compile + +# Package the extension +vsce package + +echo "Extension packaged successfully!" +echo "You can now install the .vsix file in VS Code/Cursor:" +echo "1. Open VS Code/Cursor" +echo "2. Go to Extensions (Ctrl+Shift+X)" +echo "3. Click the '...' menu and select 'Install from VSIX...'" +echo "4. Select the generated .vsix file" \ No newline at end of file diff --git a/bob-language-extension/package.json b/bob-language-extension/package.json new file mode 100644 index 0000000..249e9ff --- /dev/null +++ b/bob-language-extension/package.json @@ -0,0 +1,74 @@ +{ + "name": "bob-language", + "displayName": "Bob Language", + "description": "Syntax highlighting and language support for the Bob programming language", + "version": "0.1.2", + "engines": { + "vscode": "^1.60.0" + }, + "categories": [ + "Programming Languages" + ], + "keywords": [ + "bob", + "programming", + "language", + "syntax", + "highlighting" + ], + "publisher": "bob-lang", + "repository": { + "type": "git", + "url": "https://github.com/bob-lang/bob-vscode-extension" + }, + "license": "MIT", + "main": "./out/extension.js", + "activationEvents": [ + "onLanguage:bob" + ], + "contributes": { + "languages": [ + { + "id": "bob", + "aliases": [ + "Bob", + "bob" + ], + "extensions": [ + ".bob" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "bob", + "scopeName": "source.bob", + "path": "./syntaxes/bob.tmLanguage.json" + } + ], + "snippets": [ + { + "language": "bob", + "path": "./snippets/bob.json" + } + ], + "themes": [ + { + "label": "Bob Dark", + "uiTheme": "vs-dark", + "path": "./themes/bob-dark.json" + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./" + }, + "devDependencies": { + "@types/vscode": "^1.60.0", + "@types/node": "^16.0.0", + "typescript": "^4.5.0" + } +} \ No newline at end of file diff --git a/bob-language-extension/reload-extension.sh b/bob-language-extension/reload-extension.sh new file mode 100755 index 0000000..382cd0f --- /dev/null +++ b/bob-language-extension/reload-extension.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "Attempting to reload Bob language extension..." + +# Try to reload the extension using VS Code CLI if available +if command -v code &> /dev/null; then + echo "Found VS Code CLI, attempting to reload window..." + code --command "workbench.action.reloadWindow" + echo "Reload command sent to VS Code/Cursor" +elif command -v cursor &> /dev/null; then + echo "Found Cursor CLI, attempting to reload window..." + cursor --command "workbench.action.reloadWindow" + echo "Reload command sent to Cursor" +else + echo "VS Code/Cursor CLI not found in PATH" + echo "" + echo "Manual reload required:" + echo "1. Open Cursor" + echo "2. Press Cmd+Shift+P (or Ctrl+Shift+P on Windows/Linux)" + echo "3. Type 'Developer: Reload Window' and press Enter" + echo "" + echo "Or alternatively:" + echo "1. Press Cmd+Shift+X to open Extensions" + echo "2. Find 'Bob Language' extension" + echo "3. Click the reload button (circular arrow icon)" +fi + +echo "" +echo "Extension should now be reloaded with updated syntax highlighting!" \ No newline at end of file diff --git a/bob-language-extension/snippets/bob.json b/bob-language-extension/snippets/bob.json new file mode 100644 index 0000000..06c7320 --- /dev/null +++ b/bob-language-extension/snippets/bob.json @@ -0,0 +1,291 @@ +{ + "Function Definition": { + "prefix": "func", + "body": [ + "func ${1:functionName}(${2:parameters}) {", + "\t$0", + "}" + ], + "description": "Create a new function" + }, + "If Statement": { + "prefix": "if", + "body": [ + "if (${1:condition}) {", + "\t$0", + "}" + ], + "description": "Create an if statement" + }, + "If-Else Statement": { + "prefix": "ifelse", + "body": [ + "if (${1:condition}) {", + "\t$2", + "} else {", + "\t$0", + "}" + ], + "description": "Create an if-else statement" + }, + "While Loop": { + "prefix": "while", + "body": [ + "while (${1:condition}) {", + "\t$0", + "}" + ], + "description": "Create a while loop" + }, + "For Loop": { + "prefix": "for", + "body": [ + "for (${1:initialization}; ${2:condition}; ${3:increment}) {", + "\t$0", + "}" + ], + "description": "Create a for loop" + }, + "Variable Declaration": { + "prefix": "var", + "body": [ + "var ${1:variableName} = ${2:value};" + ], + "description": "Declare a variable" + }, + "Print Statement": { + "prefix": "print", + "body": [ + "print(${1:expression});" + ], + "description": "Print a value" + }, + "Assert Statement": { + "prefix": "assert", + "body": [ + "assert(${1:condition}, \"${2:message}\");" + ], + "description": "Create an assertion" + }, + "Anonymous Function": { + "prefix": "anon", + "body": [ + "func(${1:parameters}) {", + "\t$0", + "}" + ], + "description": "Create an anonymous function" + }, + "Return Statement": { + "prefix": "return", + "body": [ + "return ${1:value};" + ], + "description": "Return a value from function" + }, + "Break Statement": { + "prefix": "break", + "body": [ + "break;" + ], + "description": "Break out of loop" + }, + "Continue Statement": { + "prefix": "continue", + "body": [ + "continue;" + ], + "description": "Continue to next iteration" + }, + "Comment Block": { + "prefix": "comment", + "body": [ + "/*", + " * ${1:comment}", + " */" + ], + "description": "Create a comment block" + }, + "Test Function": { + "prefix": "test", + "body": [ + "func test${1:TestName}() {", + "\tvar result = ${2:testExpression};", + "\tassert(result == ${3:expectedValue}, \"${4:test message}\");", + "\tprint(\"${1:TestName}: PASS\");", + "}" + ], + "description": "Create a test function" + }, + "Higher-Order Function": { + "prefix": "hof", + "body": [ + "func ${1:functionName}(${2:callback}) {", + "\treturn func(${3:params}) {", + "\t\t$0", + "\t};", + "}" + ], + "description": "Create a higher-order function" + }, + "Closure": { + "prefix": "closure", + "body": [ + "func ${1:outerFunction}(${2:param}) {", + "\tvar ${3:capturedVar} = ${4:value};", + "\treturn func(${5:innerParam}) {", + "\t\treturn ${3:capturedVar} + ${5:innerParam};", + "\t};", + "}" + ], + "description": "Create a closure" + }, + "Counter Pattern": { + "prefix": "counter", + "body": [ + "func createCounter() {", + "\tvar count = 0;", + "\treturn func() {", + "\t\tcount = count + 1;", + "\t\treturn count;", + "\t};", + "}" + ], + "description": "Create a counter function" + }, + "Loop with Break": { + "prefix": "loopbreak", + "body": [ + "while (${1:condition}) {", + "\tif (${2:breakCondition}) {", + "\t\tbreak;", + "\t}", + "\t$0", + "}" + ], + "description": "Create a loop with break condition" + }, + "Loop with Continue": { + "prefix": "loopcontinue", + "body": [ + "for (${1:initialization}; ${2:condition}; ${3:increment}) {", + "\tif (${4:continueCondition}) {", + "\t\tcontinue;", + "\t}", + "\t$0", + "}" + ], + "description": "Create a loop with continue condition" + }, + "Nested Loop": { + "prefix": "nestedloop", + "body": [ + "for (var i = 0; i < ${1:outerLimit}; i = i + 1) {", + "\tfor (var j = 0; j < ${2:innerLimit}; j = j + 1) {", + "\t\t$0", + "\t}", + "}" + ], + "description": "Create nested loops" + }, + "Function with Early Return": { + "prefix": "earlyreturn", + "body": [ + "func ${1:functionName}(${2:param}) {", + "\tif (${3:condition}) {", + "\t\treturn ${4:earlyValue};", + "\t}", + "\t$0", + "\treturn ${5:defaultValue};", + "}" + ], + "description": "Create a function with early return" + }, + "String Concatenation": { + "prefix": "concat", + "body": [ + "var result = \"${1:first}\" + \"${2:second}\";" + ], + "description": "Concatenate strings" + }, + "Number Operations": { + "prefix": "math", + "body": [ + "var result = ${1:expression};" + ], + "description": "Mathematical expression" + }, + "Boolean Logic": { + "prefix": "bool", + "body": [ + "var result = ${1:condition} == ${2:value};" + ], + "description": "Boolean comparison" + }, + "Type Check": { + "prefix": "type", + "body": [ + "var typeResult = type(${1:expression});" + ], + "description": "Check type of expression" + }, + "ToString": { + "prefix": "tostring", + "body": [ + "var stringResult = toString(${1:expression});" + ], + "description": "Convert to string" + }, + "Test Suite Header": { + "prefix": "testsuite", + "body": [ + "// ========================================", + "// ${1:TEST SUITE NAME}", + "// ========================================", + "// ${2:Description}", + "", + "print(\"${1:TEST SUITE NAME}\");", + "print(\"Running tests...\");", + "", + "$0", + "", + "print(\"All tests passed!\");" + ], + "description": "Create a test suite header" + }, + "Test Section": { + "prefix": "testsection", + "body": [ + "// ========================================", + "// TEST ${1:NUMBER}: ${2:TEST NAME}", + "// ========================================", + "print(\"\\n--- Test ${1:NUMBER}: ${2:TEST NAME} ---\");", + "", + "$0", + "", + "print(\"${2:TEST NAME}: PASS\");" + ], + "description": "Create a test section" + }, + "Debug Print": { + "prefix": "debug", + "body": [ + "print(\"DEBUG: ${1:variable} = \" + toString(${1:variable}));" + ], + "description": "Debug print statement" + }, + "Error Message": { + "prefix": "error", + "body": [ + "print(\"ERROR: ${1:error message}\");" + ], + "description": "Error message print" + }, + "Success Message": { + "prefix": "success", + "body": [ + "print(\"SUCCESS: ${1:success message}\");" + ], + "description": "Success message print" + } +} \ No newline at end of file diff --git a/bob-language-extension/src/extension.ts b/bob-language-extension/src/extension.ts new file mode 100644 index 0000000..7bc1ff1 --- /dev/null +++ b/bob-language-extension/src/extension.ts @@ -0,0 +1,104 @@ +import * as vscode from 'vscode'; + +export function activate(context: vscode.ExtensionContext) { + console.log('Bob language extension is now active!'); + + // Register language configuration + const bobLanguageConfig = vscode.languages.setLanguageConfiguration('bob', { + comments: { + lineComment: '//', + blockComment: ['/*', '*/'] + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"', notIn: ['string'] }, + { open: "'", close: "'", notIn: ['string'] } + ], + surroundingPairs: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ['"', '"'], + ["'", "'"] + ], + indentationRules: { + increaseIndentPattern: /\{[^}]*$|\b(func|if|else|while|for)\b.*$/, + decreaseIndentPattern: /^\s*[})]/ + } + }); + + context.subscriptions.push(bobLanguageConfig); + + // Register hover provider for built-in functions + const hoverProvider = vscode.languages.registerHoverProvider('bob', { + provideHover(document, position, token) { + const range = document.getWordRangeAtPosition(position); + const word = document.getText(range); + + const builtinFunctions = { + 'print': 'Prints a value to the console', + 'assert': 'Asserts a condition and throws an error if false', + 'input': 'Gets user input from the console', + 'type': 'Returns the type of a value', + 'toString': 'Converts a value to a string', + 'toNumber': 'Converts a value to a number', + 'time': 'Returns the current time in microseconds' + }; + + if (builtinFunctions[word]) { + return new vscode.Hover(`**${word}()** - ${builtinFunctions[word]}`); + } + + return null; + } + }); + + context.subscriptions.push(hoverProvider); + + // Register completion provider + const completionProvider = vscode.languages.registerCompletionItemProvider('bob', { + provideCompletionItems(document, position, token, context) { + const completions = []; + + // Keywords + const keywords = ['if', 'else', 'while', 'for', 'break', 'continue', 'return', 'var', 'func']; + keywords.forEach(keyword => { + const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); + item.detail = 'Bob keyword'; + completions.push(item); + }); + + // Built-in functions + const builtins = ['print', 'assert', 'input', 'type', 'toString', 'toNumber', 'time']; + builtins.forEach(func => { + const item = new vscode.CompletionItem(func, vscode.CompletionItemKind.Function); + item.detail = 'Built-in function'; + item.insertText = func + '()'; + completions.push(item); + }); + + // Constants + const constants = ['true', 'false', 'none']; + constants.forEach(constant => { + const item = new vscode.CompletionItem(constant, vscode.CompletionItemKind.Constant); + item.detail = 'Bob constant'; + completions.push(item); + }); + + return completions; + } + }, '.'); + + context.subscriptions.push(completionProvider); +} + +export function deactivate() { + console.log('Bob language extension is now deactivated!'); +} \ No newline at end of file diff --git a/bob-language-extension/syntaxes/bob.tmLanguage.json b/bob-language-extension/syntaxes/bob.tmLanguage.json new file mode 100644 index 0000000..bd49b2c --- /dev/null +++ b/bob-language-extension/syntaxes/bob.tmLanguage.json @@ -0,0 +1,175 @@ +{ + "name": "Bob", + "scopeName": "source.bob", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#strings" + }, + { + "include": "#numbers" + }, + { + "include": "#keywords" + }, + { + "include": "#functions" + }, + { + "include": "#variables" + }, + { + "include": "#operators" + } + ], + "repository": { + "comments": { + "patterns": [ + { + "name": "comment.line.double-slash.bob", + "match": "//.*$" + }, + { + "name": "comment.block.bob", + "begin": "/\\*", + "end": "\\*/" + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.double.bob", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.bob", + "match": "\\\\[nt\"\\\\]" + } + ] + }, + { + "name": "string.quoted.single.bob", + "begin": "'", + "end": "'", + "patterns": [ + { + "name": "constant.character.escape.bob", + "match": "\\\\[nt'\\\\]" + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "name": "constant.numeric.integer.bob", + "match": "\\b\\d+\\b" + }, + { + "name": "constant.numeric.float.bob", + "match": "\\b\\d+\\.\\d+\\b" + } + ] + }, + "keywords": { + "patterns": [ + { + "name": "keyword.control.bob", + "match": "\\b(if|else|while|do|for|break|continue|return|var|func)\\b" + }, + { + "name": "keyword.operator.bob", + "match": "\\b(and|or|not)\\b" + }, + { + "name": "constant.language.bob", + "match": "\\b(true|false|none)\\b" + } + ] + }, + "functions": { + "patterns": [ + { + "name": "entity.name.function.bob", + "match": "\\b(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", + "captures": { + "1": { "name": "keyword.control.bob" }, + "2": { "name": "entity.name.function.bob" } + } + }, + { + "name": "support.function.builtin.bob", + "match": "\\b(print|assert|input|type|toString|toNumber|time)\\b" + } + ] + }, + "variables": { + "patterns": [ + { + "name": "variable.other.bob", + "match": "\\bvar\\s+([a-zA-Z_][a-zA-Z0-9_]*)", + "captures": { + "1": { "name": "keyword.control.bob" }, + "2": { "name": "variable.other.bob" } + } + }, + { + "name": "variable.other.bob", + "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)(?=\\s*=)", + "captures": { + "1": { "name": "variable.other.bob" } + } + } + ] + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.increment.bob", + "match": "\\+\\+|--" + }, + { + "name": "keyword.operator.compound.bob", + "match": "\\+=|-=|\\*=|/=|%=|&=|\\|=|\\^=|<<=|>>=" + }, + { + "name": "keyword.operator.comparison.bob", + "match": "==|!=|<=|>=" + }, + { + "name": "keyword.operator.logical.bob", + "match": "&&|\\|\\||!" + }, + { + "name": "keyword.operator.bitwise.bob", + "match": "<<|>>|&|\\||\\^|~" + }, + { + "name": "keyword.operator.arithmetic.bob", + "match": "\\+|-|\\*|/|%" + }, + { + "name": "keyword.operator.comparison.bob", + "match": "<|>" + }, + { + "name": "keyword.operator.assignment.bob", + "match": "=" + }, + { + "name": "keyword.operator.punctuation.bob", + "match": "\\(|\\)|\\{|\\}|\\[|\\]|,|;|\\." + }, + { + "name": "keyword.operator.conditional.bob", + "match": "\\?|:" + } + ] + } + } +} \ No newline at end of file diff --git a/bob-language-extension/test-operators.bob b/bob-language-extension/test-operators.bob new file mode 100644 index 0000000..9ee4133 --- /dev/null +++ b/bob-language-extension/test-operators.bob @@ -0,0 +1,58 @@ +// Test file for operator syntax highlighting +var x = 5; +var y = 10; +var z = x + y; +var w = x - y; +var a = x * y; +var b = x / y; +var c = x % y; + +// Comparison operators +var eq = x == y; +var ne = x != y; +var lt = x < y; +var gt = x > y; +var le = x <= y; +var ge = x >= y; + +// Increment/decrement +x++; +y--; + +// Compound assignment +x += 5; +y -= 3; +z *= 2; +w /= 4; +a %= 3; + +// Logical operators +var and = true && false; +var or = true || false; +var not = !true; + +// Bitwise operators +var bit_and = x & y; +var bit_or = x | y; +var bit_xor = x ^ y; +var left_shift = x << 2; +var right_shift = x >> 1; + +// Compound bitwise assignment +x &= y; +y |= z; +z ^= w; +w <<= 2; +a >>= 1; + +// Function with operators +func test_operators() { + var result = 0; + if (x >= 0 && y <= 100) { + result = x + y * 2; + if (result != 0) { + result++; + } + } + return result; +} \ No newline at end of file diff --git a/bob-language-extension/themes/bob-dark.json b/bob-language-extension/themes/bob-dark.json new file mode 100644 index 0000000..d103000 --- /dev/null +++ b/bob-language-extension/themes/bob-dark.json @@ -0,0 +1,79 @@ +{ + "name": "Bob Dark", + "type": "dark", + "colors": { + "editor.background": "#1e1e1e", + "editor.foreground": "#d4d4d4", + "editor.lineHighlightBackground": "#2a2a2a", + "editor.selectionBackground": "#264f78", + "editor.inactiveSelectionBackground": "#3a3d41" + }, + "tokenColors": [ + { + "name": "Comments", + "scope": ["comment", "punctuation.definition.comment"], + "settings": { + "foreground": "#6a9955" + } + }, + { + "name": "Strings", + "scope": ["string", "string.quoted"], + "settings": { + "foreground": "#ce9178" + } + }, + { + "name": "Numbers", + "scope": ["constant.numeric"], + "settings": { + "foreground": "#b5cea8" + } + }, + { + "name": "Keywords", + "scope": ["keyword.control", "keyword.operator"], + "settings": { + "foreground": "#569cd6" + } + }, + { + "name": "Functions", + "scope": ["entity.name.function", "support.function.builtin"], + "settings": { + "foreground": "#dcdcaa" + } + }, + { + "name": "Variables", + "scope": ["variable.other"], + "settings": { + "foreground": "#9cdcfe" + } + }, + { + "name": "Constants", + "scope": ["constant.language"], + "settings": { + "foreground": "#4ec9b0" + } + }, + { + "name": "Operators", + "scope": [ + "keyword.operator.arithmetic", + "keyword.operator.comparison", + "keyword.operator.logical", + "keyword.operator.bitwise", + "keyword.operator.assignment", + "keyword.operator.compound", + "keyword.operator.increment", + "keyword.operator.punctuation", + "keyword.operator.conditional" + ], + "settings": { + "foreground": "#d4d4d4" + } + } + ] +} \ No newline at end of file diff --git a/bob-language-extension/tsconfig.json b/bob-language-extension/tsconfig.json new file mode 100644 index 0000000..f6a83a5 --- /dev/null +++ b/bob-language-extension/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "lib": [ + "ES2020" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file diff --git a/headers/Expression.h b/headers/Expression.h index 55a7963..fd4ae87 100644 --- a/headers/Expression.h +++ b/headers/Expression.h @@ -13,6 +13,7 @@ // Forward declarations struct FunctionExpr; struct IncrementExpr; +struct TernaryExpr; struct ExprVisitor; struct AssignExpr; @@ -35,6 +36,7 @@ struct ExprVisitor virtual Value visitLiteralExpr(const std::shared_ptr& expr) = 0; virtual Value visitUnaryExpr(const std::shared_ptr& expr) = 0; virtual Value visitVarExpr(const std::shared_ptr& expr) = 0; + virtual Value visitTernaryExpr(const std::shared_ptr& expr) = 0; }; struct Expr : public std::enable_shared_from_this { @@ -152,3 +154,17 @@ struct IncrementExpr : Expr } }; +struct TernaryExpr : Expr +{ + std::shared_ptr condition; + std::shared_ptr thenExpr; + std::shared_ptr elseExpr; + + TernaryExpr(std::shared_ptr condition, std::shared_ptr thenExpr, std::shared_ptr elseExpr) + : condition(condition), thenExpr(thenExpr), elseExpr(elseExpr) {} + Value accept(ExprVisitor* visitor) override + { + return visitor->visitTernaryExpr(std::static_pointer_cast(shared_from_this())); + } +}; + diff --git a/headers/Interpreter.h b/headers/Interpreter.h index 911f3f2..96f240c 100644 --- a/headers/Interpreter.h +++ b/headers/Interpreter.h @@ -65,6 +65,7 @@ public: Value visitVarExpr(const std::shared_ptr& expression) override; Value visitIncrementExpr(const std::shared_ptr& expression) override; Value visitAssignExpr(const std::shared_ptr& expression) override; + Value visitTernaryExpr(const std::shared_ptr& expression) override; void visitBlockStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; void visitExpressionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; @@ -72,6 +73,12 @@ public: void visitFunctionStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; void visitReturnStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; void visitIfStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitDoWhileStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitForStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitBreakStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; + void visitAssignStmt(const std::shared_ptr& statement, ExecutionContext* context = nullptr) override; void interpret(std::vector > statements); @@ -85,9 +92,15 @@ private: bool IsInteractive; std::vector > builtinFunctions; std::vector > functions; + std::vector > thunks; // Store thunks to prevent memory leaks ErrorReporter* errorReporter; bool inThunkExecution = false; + // Automatic cleanup tracking + int functionCreationCount = 0; + int thunkCreationCount = 0; + static const int CLEANUP_THRESHOLD = 1000; // Cleanup every 1000 creations + Value evaluate(const std::shared_ptr& expr); @@ -105,6 +118,10 @@ public: bool isTruthy(Value object); std::string stringify(Value object); void addBuiltinFunction(std::shared_ptr func); + + // Memory management + void cleanupUnusedFunctions(); + void cleanupUnusedThunks(); // Error reporting void setErrorReporter(ErrorReporter* reporter) { diff --git a/headers/Lexer.h b/headers/Lexer.h index c65f3ea..cd3f97b 100644 --- a/headers/Lexer.h +++ b/headers/Lexer.h @@ -15,13 +15,16 @@ enum TokenType{ GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, + // Ternary operator + QUESTION, COLON, + // Increment/decrement operators PLUS_PLUS, MINUS_MINUS, IDENTIFIER, STRING, NUMBER, BOOL, AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR, - WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN, + WHILE, DO, VAR, CLASS, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE, // Compound assignment operators PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, @@ -42,12 +45,14 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE", "GREATER", "GREATER_EQUAL", "LESS", "LESS_EQUAL", + "QUESTION", "COLON", + "PLUS_PLUS", "MINUS_MINUS", "IDENTIFIER", "STRING", "NUMBER", "BOOL", "AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR", - "WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", + "WHILE", "DO", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE", // Compound assignment operators "PLUS_EQUAL", "MINUS_EQUAL", "STAR_EQUAL", "SLASH_EQUAL", "PERCENT_EQUAL", @@ -67,12 +72,15 @@ const std::map KEYWORDS { {"func", FUNCTION}, {"for", FOR}, {"while", WHILE}, + {"do", DO}, {"var", VAR}, {"class", CLASS}, {"super", SUPER}, {"this", THIS}, {"none", NONE}, {"return", RETURN}, + {"break", BREAK}, + {"continue", CONTINUE}, }; struct Token diff --git a/headers/Parser.h b/headers/Parser.h index 2e9f4e9..5044c50 100644 --- a/headers/Parser.h +++ b/headers/Parser.h @@ -24,6 +24,7 @@ public: private: sptr(Expr) expression(); sptr(Expr) logical_or(); + sptr(Expr) ternary(); sptr(Expr) logical_and(); sptr(Expr) bitwise_or(); sptr(Expr) bitwise_xor(); @@ -56,6 +57,16 @@ private: std::shared_ptr ifStatement(); + std::shared_ptr whileStatement(); + + std::shared_ptr doWhileStatement(); + + std::shared_ptr forStatement(); + + std::shared_ptr breakStatement(); + + std::shared_ptr continueStatement(); + std::shared_ptr declaration(); std::shared_ptr varDeclaration(); @@ -63,7 +74,9 @@ private: std::shared_ptr functionDeclaration(); std::shared_ptr functionExpression(); + std::shared_ptr assignmentStatement(); sptr(Expr) assignment(); + sptr(Expr) assignmentExpression(); // For for loop increment clauses sptr(Expr) increment(); // Parse increment/decrement expressions sptr(Expr) postfix(); // Parse postfix operators diff --git a/headers/Statement.h b/headers/Statement.h index 6efcb4b..30ffb15 100644 --- a/headers/Statement.h +++ b/headers/Statement.h @@ -11,10 +11,18 @@ struct BlockStmt; struct FunctionStmt; struct ReturnStmt; struct IfStmt; +struct WhileStmt; +struct DoWhileStmt; +struct ForStmt; +struct BreakStmt; +struct ContinueStmt; +struct AssignStmt; struct ExecutionContext { bool isFunctionBody = false; bool hasReturn = false; + bool hasBreak = false; + bool hasContinue = false; Value returnValue; }; @@ -26,6 +34,12 @@ struct StmtVisitor virtual void visitFunctionStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; virtual void visitReturnStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; virtual void visitIfStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; + virtual void visitWhileStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; + virtual void visitDoWhileStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; + virtual void visitForStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; + virtual void visitBreakStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; + virtual void visitContinueStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; + virtual void visitAssignStmt(const std::shared_ptr& stmt, ExecutionContext* context = nullptr) = 0; }; struct Stmt : public std::enable_shared_from_this @@ -117,4 +131,88 @@ struct IfStmt : Stmt { visitor->visitIfStmt(std::static_pointer_cast(shared_from_this()), context); } +}; + +struct WhileStmt : Stmt +{ + std::shared_ptr condition; + std::shared_ptr body; + + WhileStmt(std::shared_ptr condition, std::shared_ptr body) + : condition(condition), body(body) {} + + void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override + { + visitor->visitWhileStmt(std::static_pointer_cast(shared_from_this()), context); + } +}; + +struct DoWhileStmt : Stmt +{ + std::shared_ptr body; + std::shared_ptr condition; + + DoWhileStmt(std::shared_ptr body, std::shared_ptr condition) + : body(body), condition(condition) {} + + void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override + { + visitor->visitDoWhileStmt(std::static_pointer_cast(shared_from_this()), context); + } +}; + +struct ForStmt : Stmt +{ + std::shared_ptr initializer; + std::shared_ptr condition; + std::shared_ptr increment; + std::shared_ptr body; + + ForStmt(std::shared_ptr initializer, std::shared_ptr condition, + std::shared_ptr increment, std::shared_ptr body) + : initializer(initializer), condition(condition), increment(increment), body(body) {} + + void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override + { + visitor->visitForStmt(std::static_pointer_cast(shared_from_this()), context); + } +}; + +struct BreakStmt : Stmt +{ + const Token keyword; + + BreakStmt(Token keyword) : keyword(keyword) {} + + void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override + { + visitor->visitBreakStmt(std::static_pointer_cast(shared_from_this()), context); + } +}; + +struct ContinueStmt : Stmt +{ + const Token keyword; + + ContinueStmt(Token keyword) : keyword(keyword) {} + + void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override + { + visitor->visitContinueStmt(std::static_pointer_cast(shared_from_this()), context); + } +}; + +struct AssignStmt : Stmt +{ + const Token name; + const Token op; + std::shared_ptr value; + + AssignStmt(Token name, Token op, std::shared_ptr value) + : name(name), op(op), value(value) {} + + void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override + { + visitor->visitAssignStmt(std::static_pointer_cast(shared_from_this()), context); + } }; \ No newline at end of file diff --git a/source/.DS_Store b/source/.DS_Store deleted file mode 100644 index cf37e4eb6286a7bf0b90d470de769dba2a336404..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5PefCrU=rb7h%7k;2*@&9t3axfTns7swuTXkGcCXexE0OvojRiG*=Nj zQ)b_0cHVCDHe|8@Wc>DU2@C-gbj99)%^uTn^_KNKa!iyw#|#TBuIKfvnzyne@D~-( zy*ojLnm)ph{i`dE=woq3AAMzG4>-dVYwq(Bk9g(piQJbC?#B*yfE%*b^yPkrTwZ85RnJ0-?acDZrYoR-76}8w!L1p}~+G>kSB2nDtkII_#3*8geu`G1=fSD`>C@UIk*{^WKt<}G<`ZQY#K+C;yjt7%?i lxS_C& expression return value; } +Value Interpreter::visitTernaryExpr(const std::shared_ptr& expression) { + Value condition = evaluate(expression->condition); + + if (isTruthy(condition)) { + return evaluate(expression->thenExpr); + } else { + return evaluate(expression->elseExpr); + } +} + Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { Value callee = evaluate(expression->callee); @@ -602,8 +612,8 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { // Check if this is a tail call if (expression->isTailCall) { - // Create a thunk for tail call optimization - auto thunk = new Thunk([this, function, arguments]() -> Value { + // Create a thunk for tail call optimization using smart pointer + auto thunk = std::make_shared([this, function, arguments]() -> Value { // Use RAII to manage environment ScopedEnv _env(environment); environment = std::make_shared(function->closure); @@ -630,8 +640,17 @@ Value Interpreter::visitCallExpr(const std::shared_ptr& expression) { return context.returnValue; }); - // Return the thunk as a Value - return Value(thunk); + // Store the thunk to keep it alive and return as Value + thunks.push_back(thunk); + + // Automatic cleanup check + thunkCreationCount++; + if (thunkCreationCount >= CLEANUP_THRESHOLD) { + cleanupUnusedThunks(); + thunkCreationCount = 0; + } + + return Value(thunk.get()); } else { // Normal function call - create new environment ScopedEnv _env(environment); @@ -669,6 +688,14 @@ Value Interpreter::visitFunctionExpr(const std::shared_ptr& expres auto function = msptr(Function)("anonymous", paramNames, expression->body, environment); functions.push_back(function); // Keep the shared_ptr alive + + // Automatic cleanup check + functionCreationCount++; + if (functionCreationCount >= CLEANUP_THRESHOLD) { + cleanupUnusedFunctions(); + functionCreationCount = 0; + } + return Value(function.get()); } @@ -714,6 +741,13 @@ void Interpreter::visitFunctionStmt(const std::shared_ptr& stateme environment); functions.push_back(function); // Keep the shared_ptr alive environment->define(statement->name.lexeme, Value(function.get())); + + // Automatic cleanup check + functionCreationCount++; + if (functionCreationCount >= CLEANUP_THRESHOLD) { + cleanupUnusedFunctions(); + functionCreationCount = 0; + } } void Interpreter::visitReturnStmt(const std::shared_ptr& statement, ExecutionContext* context) @@ -741,6 +775,175 @@ void Interpreter::visitIfStmt(const std::shared_ptr& statement, Executio } } +void Interpreter::visitWhileStmt(const std::shared_ptr& statement, ExecutionContext* context) +{ + ExecutionContext loopContext; + if (context) { + loopContext.isFunctionBody = context->isFunctionBody; + } + + while (isTruthy(evaluate(statement->condition))) { + execute(statement->body, &loopContext); + + // Check for return from function + if (loopContext.hasReturn) { + if (context) { + context->hasReturn = true; + context->returnValue = loopContext.returnValue; + } + break; + } + + // Check for break + if (loopContext.hasBreak) { + loopContext.hasBreak = false; + break; + } + + // Check for continue (just continue to next iteration) + if (loopContext.hasContinue) { + loopContext.hasContinue = false; + continue; + } + } +} + +void Interpreter::visitDoWhileStmt(const std::shared_ptr& statement, ExecutionContext* context) +{ + ExecutionContext loopContext; + if (context) { + loopContext.isFunctionBody = context->isFunctionBody; + } + + do { + execute(statement->body, &loopContext); + + // Check for return from function + if (loopContext.hasReturn) { + if (context) { + context->hasReturn = true; + context->returnValue = loopContext.returnValue; + } + break; + } + + // Check for break + if (loopContext.hasBreak) { + loopContext.hasBreak = false; + break; + } + + // Check for continue (just continue to next iteration) + if (loopContext.hasContinue) { + loopContext.hasContinue = false; + continue; + } + } while (isTruthy(evaluate(statement->condition))); +} + +void Interpreter::visitForStmt(const std::shared_ptr& statement, ExecutionContext* context) +{ + // For loops are desugared into while loops in the parser + // This method should never be called, but we implement it for completeness + // The actual execution happens through the desugared while loop + if (statement->initializer != nullptr) { + execute(statement->initializer, context); + } + + ExecutionContext loopContext; + if (context) { + loopContext.isFunctionBody = context->isFunctionBody; + } + + while (statement->condition == nullptr || isTruthy(evaluate(statement->condition))) { + execute(statement->body, &loopContext); + + // Check for return from function + if (loopContext.hasReturn) { + if (context) { + context->hasReturn = true; + context->returnValue = loopContext.returnValue; + } + break; + } + + // Check for break + if (loopContext.hasBreak) { + loopContext.hasBreak = false; + break; + } + + // Check for continue (execute increment then continue to next iteration) + if (loopContext.hasContinue) { + loopContext.hasContinue = false; + if (statement->increment != nullptr) { + evaluate(statement->increment); + } + continue; + } + + if (statement->increment != nullptr) { + evaluate(statement->increment); + } + } +} + +void Interpreter::visitBreakStmt(const std::shared_ptr& statement, ExecutionContext* context) +{ + if (context) { + context->hasBreak = true; + } +} + +void Interpreter::visitContinueStmt(const std::shared_ptr& statement, ExecutionContext* context) +{ + if (context) { + context->hasContinue = true; + } +} + +void Interpreter::visitAssignStmt(const std::shared_ptr& statement, ExecutionContext* context) +{ + Value value = evaluate(statement->value); + + // Handle different assignment operators + if (statement->op.type == EQUAL) { + // Simple assignment + environment->assign(statement->name, value); + } else { + // Compound assignment - get current value first + Value currentValue = environment->get(statement->name.lexeme); + + // Apply the compound operation + Value result; + if (statement->op.type == PLUS_EQUAL) { + result = currentValue + value; + } else if (statement->op.type == MINUS_EQUAL) { + result = currentValue - value; + } else if (statement->op.type == STAR_EQUAL) { + result = currentValue * value; + } else if (statement->op.type == SLASH_EQUAL) { + result = currentValue / value; + } else if (statement->op.type == PERCENT_EQUAL) { + result = currentValue % value; + } else if (statement->op.type == BIN_AND_EQUAL) { + result = currentValue & value; + } else if (statement->op.type == BIN_OR_EQUAL) { + result = currentValue | value; + } else if (statement->op.type == BIN_XOR_EQUAL) { + result = currentValue ^ value; + } else if (statement->op.type == BIN_SLEFT_EQUAL) { + result = currentValue << value; + } else if (statement->op.type == BIN_SRIGHT_EQUAL) { + result = currentValue >> value; + } else { + throw std::runtime_error("Unknown assignment operator: " + statement->op.lexeme); + } + + environment->assign(statement->name, result); + } +} + void Interpreter::interpret(std::vector > statements) { for(const std::shared_ptr& s : statements) { @@ -761,7 +964,7 @@ void Interpreter::executeBlock(std::vector > statements, s for(const std::shared_ptr& s : statements) { execute(s, context); - if (context && context->hasReturn) { + if (context && (context->hasReturn || context->hasBreak || context->hasContinue)) { this->environment = previous; return; } @@ -921,6 +1124,30 @@ bool Interpreter::isWholeNumer(double num) { } } +void Interpreter::cleanupUnusedFunctions() { + // Only remove functions that are definitely not referenced anywhere (use_count == 1) + // This is more conservative to prevent dangling pointer issues + functions.erase( + std::remove_if(functions.begin(), functions.end(), + [](const std::shared_ptr& func) { + return func.use_count() == 1; // Only referenced by this vector, nowhere else + }), + functions.end() + ); +} + +void Interpreter::cleanupUnusedThunks() { + // Only remove thunks that are definitely not referenced anywhere (use_count == 1) + // This is more conservative to prevent dangling pointer issues + thunks.erase( + std::remove_if(thunks.begin(), thunks.end(), + [](const std::shared_ptr& thunk) { + return thunk.use_count() == 1; // Only referenced by this vector, nowhere else + }), + thunks.end() + ); +} + diff --git a/source/Lexer.cpp b/source/Lexer.cpp index 945c680..74d1469 100644 --- a/source/Lexer.cpp +++ b/source/Lexer.cpp @@ -115,6 +115,16 @@ std::vector Lexer::Tokenize(std::string source){ tokens.push_back(Token{BIN_NOT, std::string(1, t), line, column - 1}); advance(); } + else if(t == '?') + { + tokens.push_back(Token{QUESTION, std::string(1, t), line, column}); + advance(); + } + else if(t == ':') + { + tokens.push_back(Token{COLON, std::string(1, t), line, column}); + advance(); + } else if(t == '=') { std::string token = std::string(1, t); diff --git a/source/Parser.cpp b/source/Parser.cpp index 24b808f..5617e8f 100644 --- a/source/Parser.cpp +++ b/source/Parser.cpp @@ -16,18 +16,32 @@ sptr(Expr) Parser::expression() sptr(Expr) Parser::logical_or() { - sptr(Expr) expr = logical_and(); + sptr(Expr) expr = ternary(); while(match({OR})) { Token op = previous(); - sptr(Expr) right = logical_and(); + sptr(Expr) right = ternary(); expr = msptr(BinaryExpr)(expr, op, right); } return expr; } +sptr(Expr) Parser::ternary() +{ + sptr(Expr) expr = logical_and(); + + if (match({QUESTION})) { + sptr(Expr) thenExpr = expression(); + consume(COLON, "Expected ':' after ternary condition"); + sptr(Expr) elseExpr = expression(); + expr = msptr(TernaryExpr)(expr, thenExpr, elseExpr); + } + + return expr; +} + sptr(Expr) Parser::logical_and() { sptr(Expr) expr = equality(); @@ -101,13 +115,21 @@ sptr(Expr) Parser::shift() sptr(Expr) Parser::assignment() { + // Assignments are now statements, not expressions + // This function should only handle expressions that are not assignments + return increment(); +} + +sptr(Expr) Parser::assignmentExpression() +{ + // This allows assignments as expressions (for for loop increment clauses) sptr(Expr) expr = increment(); if(match({EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, PERCENT_EQUAL, BIN_AND_EQUAL, BIN_OR_EQUAL, BIN_XOR_EQUAL, BIN_SLEFT_EQUAL, BIN_SRIGHT_EQUAL})) { Token op = previous(); - sptr(Expr) value = assignment(); + sptr(Expr) value = assignmentExpression(); if(std::dynamic_pointer_cast(expr)) { Token name = std::dynamic_pointer_cast(expr)->name; @@ -372,11 +394,48 @@ sptr(Stmt) Parser::statement() { if(match({RETURN})) return returnStatement(); if(match({IF})) return ifStatement(); + if(match({DO})) return doWhileStatement(); + if(match({WHILE})) return whileStatement(); + if(match({FOR})) return forStatement(); + if(match({BREAK})) return breakStatement(); + if(match({CONTINUE})) return continueStatement(); if(match({OPEN_BRACE})) return msptr(BlockStmt)(block()); + + // Check for assignment statement + if(check({IDENTIFIER})) { + // Look ahead to see if this is an assignment + int currentPos = current; + advance(); // consume identifier + 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})) { + // Reset position and parse as assignment statement + current = currentPos; + return assignmentStatement(); + } + // Reset position and parse as expression statement + current = currentPos; + } + return expressionStatement(); } - +sptr(Stmt) Parser::assignmentStatement() +{ + Token name = consume(IDENTIFIER, "Expected variable name for assignment."); + + // Consume any assignment operator + Token op; + 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})) { + op = previous(); + } else { + throw std::runtime_error("Expected assignment operator."); + } + + sptr(Expr) value = expression(); + consume(SEMICOLON, "Expected ';' after assignment."); + return msptr(AssignStmt)(name, op, value); +} sptr(Stmt) Parser::ifStatement() { @@ -394,6 +453,78 @@ sptr(Stmt) Parser::ifStatement() return msptr(IfStmt)(condition, thenBranch, elseBranch); } +sptr(Stmt) Parser::whileStatement() +{ + consume(OPEN_PAREN, "Expected '(' after 'while'."); + sptr(Expr) condition = expression(); + consume(CLOSE_PAREN, "Expected ')' after while condition."); + + sptr(Stmt) body = statement(); + + return msptr(WhileStmt)(condition, body); +} + +sptr(Stmt) Parser::doWhileStatement() +{ + sptr(Stmt) body = statement(); + + consume(WHILE, "Expected 'while' after do-while body."); + consume(OPEN_PAREN, "Expected '(' after 'while'."); + sptr(Expr) condition = expression(); + consume(CLOSE_PAREN, "Expected ')' after while condition."); + consume(SEMICOLON, "Expected ';' after do-while condition."); + + return msptr(DoWhileStmt)(body, condition); +} + +sptr(Stmt) Parser::forStatement() +{ + consume(OPEN_PAREN, "Expected '(' after 'for'."); + + sptr(Stmt) initializer; + if (match({SEMICOLON})) { + initializer = nullptr; + } else if (match({VAR})) { + initializer = varDeclaration(); + } else { + // Allow assignment expressions in for loop initializer + sptr(Expr) expr = assignmentExpression(); + consume(SEMICOLON, "Expected ';' after for loop initializer."); + initializer = msptr(ExpressionStmt)(expr); + } + + sptr(Expr) condition = nullptr; + if (!check(SEMICOLON)) { + condition = expression(); + } + consume(SEMICOLON, "Expected ';' after for loop condition."); + + sptr(Expr) increment = nullptr; + if (!check(CLOSE_PAREN)) { + increment = assignmentExpression(); + } + consume(CLOSE_PAREN, "Expected ')' after for clauses."); + + sptr(Stmt) body = statement(); + + // Return the for statement directly instead of desugaring + return msptr(ForStmt)(initializer, condition, increment, body); +} + +sptr(Stmt) Parser::breakStatement() +{ + Token keyword = previous(); + consume(SEMICOLON, "Expected ';' after 'break'."); + return msptr(BreakStmt)(keyword); +} + +sptr(Stmt) Parser::continueStatement() +{ + Token keyword = previous(); + consume(SEMICOLON, "Expected ';' after 'continue'."); + return msptr(ContinueStmt)(keyword); +} + // Helper function to detect if an expression is a tail call bool Parser::isTailCall(const std::shared_ptr& expr) { // Check if this is a direct function call (no operations on the result) diff --git a/source/StdLib.cpp b/source/StdLib.cpp index 1ddc804..8550b0f 100644 --- a/source/StdLib.cpp +++ b/source/StdLib.cpp @@ -258,4 +258,6 @@ void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& int // Store the shared_ptr in the interpreter to keep it alive interpreter.addBuiltinFunction(exitFunc); + + } \ No newline at end of file diff --git a/source/StdLib.cpp.backup b/source/StdLib.cpp.backup new file mode 100644 index 0000000..120f644 --- /dev/null +++ b/source/StdLib.cpp.backup @@ -0,0 +1,203 @@ +#include "../headers/StdLib.h" +#include "../headers/Interpreter.h" +#include "../headers/ErrorReporter.h" +#include +#include +#include + +void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& interpreter, ErrorReporter* errorReporter) { + // toString function + auto toStringFunc = std::make_shared("toString", + [](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + throw std::runtime_error("toString() expects exactly 1 argument, got " + std::to_string(args.size())); + } + return Value(args[0].toString()); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toStringFunc); + env->define("toString", Value(toStringFunc.get())); + + // print function + auto printFunc = std::make_shared("print", + [](std::vector args, int line, int column) -> Value { + for (size_t i = 0; i < args.size(); i++) { + if (i > 0) std::cout << " "; + std::cout << args[i].toString(); + } + std::cout << std::endl; + return NONE_VALUE; + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(printFunc); + env->define("print", Value(printFunc.get())); + + // assert function + auto assertFunc = std::make_shared("assert", + [](std::vector args, int line, int column) -> Value { + if (args.size() < 1 || args.size() > 2) { + throw std::runtime_error("assert() expects 1 or 2 arguments, got " + std::to_string(args.size())); + } + + if (!args[0].isTruthy()) { + std::string message = args.size() == 2 ? args[1].toString() : "Assertion failed"; + throw std::runtime_error("Assertion failed: " + message); + } + return NONE_VALUE; + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(assertFunc); + env->define("assert", Value(assertFunc.get())); + + // time function + auto timeFunc = std::make_shared("time", + [](std::vector args, int line, int column) -> Value { + if (args.size() != 0) { + throw std::runtime_error("time() expects no arguments, got " + std::to_string(args.size())); + } + + auto now = std::chrono::high_resolution_clock::now(); + auto duration = now.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration).count(); + return Value(static_cast(seconds)); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(timeFunc); + env->define("time", Value(timeFunc.get())); + + // input function + auto inputFunc = std::make_shared("input", + [](std::vector args, int line, int column) -> Value { + if (args.size() > 1) { + throw std::runtime_error("input() expects 0 or 1 arguments, got " + std::to_string(args.size())); + } + + if (args.size() == 1) { + std::cout << args[0].toString(); + } + + std::string line; + std::getline(std::cin, line); + return Value(line); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(inputFunc); + env->define("input", Value(inputFunc.get())); + + // type function + auto typeFunc = std::make_shared("type", + [](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + throw std::runtime_error("type() expects exactly 1 argument, got " + std::to_string(args.size())); + } + + if (args[0].isNumber()) return Value("number"); + if (args[0].isString()) return Value("string"); + if (args[0].isBoolean()) return Value("boolean"); + if (args[0].isFunction()) return Value("function"); + if (args[0].isBuiltinFunction()) return Value("builtin_function"); + if (args[0].isThunk()) return Value("thunk"); + if (args[0].isNone()) return Value("none"); + + return Value("unknown"); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(typeFunc); + env->define("type", Value(typeFunc.get())); + + // toNumber function + auto toNumberFunc = std::make_shared("toNumber", + [](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + throw std::runtime_error("toNumber() expects exactly 1 argument, got " + std::to_string(args.size())); + } + + if (args[0].isNumber()) return args[0]; + if (args[0].isString()) { + try { + return Value(std::stod(args[0].asString())); + } catch (...) { + return Value(0.0); + } + } + if (args[0].isBoolean()) return Value(args[0].asBoolean() ? 1.0 : 0.0); + if (args[0].isNone()) return Value(0.0); + + return Value(0.0); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toNumberFunc); + env->define("toNumber", Value(toNumberFunc.get())); + + // toBoolean function + auto toBooleanFunc = std::make_shared("toBoolean", + [](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + throw std::runtime_error("toBoolean() expects exactly 1 argument, got " + std::to_string(args.size())); + } + + return Value(args[0].isTruthy()); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toBooleanFunc); + env->define("toBoolean", Value(toBooleanFunc.get())); + + // exit function + auto exitFunc = std::make_shared("exit", + [](std::vector args, int line, int column) -> Value { + int code = 0; + if (args.size() == 1) { + if (args[0].isNumber()) { + code = static_cast(args[0].asNumber()); + } + } else if (args.size() > 1) { + throw std::runtime_error("exit() expects 0 or 1 arguments, got " + std::to_string(args.size())); + } + std::exit(code); + return NONE_VALUE; + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(exitFunc); + env->define("exit", Value(exitFunc.get())); + + // cleanup functions + auto cleanupFunc = std::make_shared("cleanup", + [&interpreter](std::vector args, int line, int column) -> Value { + if (args.size() != 0) { + throw std::runtime_error("cleanup() expects no arguments, got " + std::to_string(args.size())); + } + + interpreter.cleanupUnusedFunctions(); + interpreter.cleanupUnusedThunks(); + return NONE_VALUE; + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(cleanupFunc); + env->define("cleanup", Value(cleanupFunc.get())); + + // getFunctionCount function + auto getFunctionCountFunc = std::make_shared("getFunctionCount", + [&interpreter](std::vector args, int line, int column) -> Value { + if (args.size() != 0) { + throw std::runtime_error("getFunctionCount() expects no arguments, got " + std::to_string(args.size())); + } + + // This would need to be exposed through the interpreter + // For now, return a placeholder + return Value(0.0); + }); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(getFunctionCountFunc); + env->define("getFunctionCount", Value(getFunctionCountFunc.get())); +} \ No newline at end of file diff --git a/source/StdLib.cpp.original b/source/StdLib.cpp.original new file mode 100644 index 0000000..1ddc804 --- /dev/null +++ b/source/StdLib.cpp.original @@ -0,0 +1,261 @@ +#include "../headers/StdLib.h" +#include "../headers/Interpreter.h" +#include "../headers/ErrorReporter.h" +#include + +void StdLib::addToEnvironment(std::shared_ptr env, Interpreter& interpreter, ErrorReporter* errorReporter) { + // Create a built-in toString function + auto toStringFunc = std::make_shared("toString", + [&interpreter, errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + } + + return Value(interpreter.stringify(args[0])); + }); + env->define("toString", Value(toStringFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toStringFunc); + + // Create a built-in print function + auto printFunc = std::make_shared("print", + [&interpreter, errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + } + // Use the interpreter's stringify function + std::cout << interpreter.stringify(args[0]) << std::endl; + return NONE_VALUE; + }); + env->define("print", Value(printFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(printFunc); + + // Create a built-in assert function + auto assertFunc = std::make_shared("assert", + [errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 1 && args.size() != 2) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + "."); + } + + // Simple truthy check without calling interpreter.isTruthy + bool isTruthy = false; + if (args[0].isBoolean()) { + isTruthy = args[0].asBoolean(); + } else if (args[0].isNone()) { + isTruthy = false; + } else { + isTruthy = true; // Numbers, strings, functions are truthy + } + + if (!isTruthy) { + std::string message = "Assertion failed: condition is false"; + if (args.size() == 2) { + if (args[1].isString()) { + message += " - " + std::string(args[1].asString()); + } + } + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", message, "", true); + } + throw std::runtime_error(message); + } + + return NONE_VALUE; + }); + env->define("assert", Value(assertFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(assertFunc); + + // Create a built-in time function (returns microseconds since Unix epoch) + auto timeFunc = std::make_shared("time", + [errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 0) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + "."); + } + + auto now = std::chrono::high_resolution_clock::now(); + auto duration = now.time_since_epoch(); + auto microseconds = std::chrono::duration_cast(duration).count(); + + return Value(static_cast(microseconds)); + }); + env->define("time", Value(timeFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(timeFunc); + + // Create a built-in input function + auto inputFunc = std::make_shared("input", + [&interpreter, errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() > 1) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + "."); + } + + // Optional prompt + if (args.size() == 1) { + std::cout << interpreter.stringify(args[0]); + } + + // Get user input + std::string userInput; + std::getline(std::cin, userInput); + + return Value(userInput); + }); + env->define("input", Value(inputFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(inputFunc); + + // Create a built-in type function + auto typeFunc = std::make_shared("type", + [errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + } + + std::string typeName; + if (args[0].isNumber()) { + typeName = "number"; + } else if (args[0].isString()) { + typeName = "string"; + } else if (args[0].isBoolean()) { + typeName = "boolean"; + } else if (args[0].isNone()) { + typeName = "none"; + } else if (args[0].isFunction()) { + typeName = "function"; + } else if (args[0].isBuiltinFunction()) { + typeName = "builtin_function"; + } else { + typeName = "unknown"; + } + + return Value(typeName); + }); + env->define("type", Value(typeFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(typeFunc); + + // Create a built-in toNumber function for string-to-number conversion + auto toNumberFunc = std::make_shared("toNumber", + [](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + return NONE_VALUE; // Return none for wrong argument count + } + + if (!args[0].isString()) { + return NONE_VALUE; // Return none for wrong type + } + + std::string str = args[0].asString(); + + // 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()) { + return NONE_VALUE; // Return none for empty string + } + + try { + double value = std::stod(str); + return Value(value); + } catch (const std::invalid_argument&) { + return NONE_VALUE; // Return none for invalid conversion + } catch (const std::out_of_range&) { + return NONE_VALUE; // Return none for out of range + } + }); + env->define("toNumber", Value(toNumberFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toNumberFunc); + + // Create a built-in toBoolean function for explicit boolean conversion + auto toBooleanFunc = std::make_shared("toBoolean", + [errorReporter](std::vector args, int line, int column) -> Value { + if (args.size() != 1) { + if (errorReporter) { + errorReporter->reportError(line, column, "StdLib Error", + "Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true); + } + throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + "."); + } + + // Use the same logic as isTruthy() for consistency + Value value = args[0]; + + if (value.isNone()) { + return Value(false); + } + + if (value.isBoolean()) { + return value; // Already a boolean + } + + if (value.isNumber()) { + return Value(value.asNumber() != 0.0); + } + + if (value.isString()) { + return Value(!value.asString().empty()); + } + + // For any other type (functions, etc.), consider them truthy + return Value(true); + }); + env->define("toBoolean", Value(toBooleanFunc.get())); + + // Store the shared_ptr in the interpreter to keep it alive + interpreter.addBuiltinFunction(toBooleanFunc); + + // Create a built-in exit function to terminate the program + auto exitFunc = std::make_shared("exit", + [](std::vector args, int line, int column) -> Value { + int exitCode = 0; // Default exit code + + if (args.size() > 0) { + if (args[0].isNumber()) { + exitCode = static_cast(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); +} \ No newline at end of file diff --git a/test_bob_language.bob b/test_bob_language.bob index 70bf528..b8baf69 100644 --- a/test_bob_language.bob +++ b/test_bob_language.bob @@ -1814,6 +1814,510 @@ print(" Nested multi-statement function result: " + toString(result2)); print("Multi-statement function execution: PASS"); +// ======================================== +// TEST 46: WHILE LOOPS +// ======================================== +print("\n--- Test 46: While Loops ---"); + +// Basic while loop +var counter = 0; +while (counter < 5) { + counter = counter + 1; +} +assert(counter == 5, "Basic while loop should increment counter to 5"); +print(" Basic while loop: PASS"); + +// While loop with break (using return) +func countToThree() { + var i = 0; + while (true) { + i = i + 1; + if (i > 3) { + return i; + } + } +} +assert(countToThree() == 4, "While loop with return should work"); +print(" While loop with return: PASS"); + +// While loop with condition +var sum = 0; +var num = 1; +while (num <= 10) { + sum = sum + num; + num = num + 1; +} +assert(sum == 55, "While loop should sum numbers 1 to 10"); +print(" While loop summation: PASS"); + +// Nested while loops +var outer = 0; +var inner = 0; +while (outer < 3) { + inner = 0; + while (inner < 2) { + inner = inner + 1; + } + outer = outer + 1; +} +assert(outer == 3, "Nested while loops should work"); +assert(inner == 2, "Inner loop should complete"); +print(" Nested while loops: PASS"); + +// While loop with false condition (should not execute) +var neverExecuted = 0; +while (false) { + neverExecuted = neverExecuted + 1; +} +assert(neverExecuted == 0, "While loop with false condition should not execute"); +print(" While loop with false condition: PASS"); + +// While loop with complex condition +var x = 10; +var y = 0; +while (x > 0 && y < 5) { + x = x - 1; + y = y + 1; +} +assert(x == 5, "Complex condition while loop should work"); +assert(y == 5, "Complex condition while loop should work"); +print(" While loop with complex condition: PASS"); + +print("While loops: PASS"); + +// ======================================== +// TEST 47: FOR LOOPS +// ======================================== +print("\n--- Test 47: For Loops ---"); + +// Basic for loop +var forSum = 0; +for (var i = 1; i <= 5; i = i + 1) { + forSum = forSum + i; +} +assert(forSum == 15, "Basic for loop should sum 1 to 5"); +print(" Basic for loop: PASS"); + +// For loop with no initializer +var j = 0; +for (; j < 3; j = j + 1) { + // Empty body +} +assert(j == 3, "For loop with no initializer should work"); +print(" For loop with no initializer: PASS"); + +// For loop with no condition (infinite loop with break) +func countToTwo() { + var k = 0; + for (; ; k = k + 1) { + if (k >= 2) { + return k; + } + } +} +assert(countToTwo() == 2, "For loop with no condition should work"); +print(" For loop with no condition: PASS"); + +// For loop with no increment +var m = 0; +var n = 0; +for (n = 0; n < 3; ) { + m = m + 1; + n = n + 1; +} +assert(m == 3, "For loop with no increment should work"); +assert(n == 3, "For loop with no increment should work"); +print(" For loop with no increment: PASS"); + +// Nested for loops +var total = 0; +for (var a = 0; a < 2; a = a + 1) { + for (var b = 0; b < 3; b = b + 1) { + total = total + 1; + } +} +assert(total == 6, "Nested for loops should work"); +print(" Nested for loops: PASS"); + +// For loop with expression initializer +var result = 0; +for (var temp = 2 * 3; temp > 0; temp = temp - 1) { + result = result + temp; +} +assert(result == 21, "For loop with expression initializer should work"); // 6 + 5 + 4 + 3 + 2 + 1 = 21 +print(" For loop with expression initializer: PASS"); + +// For loop with complex increment +var complexSum = 0; +for (var i = 0; i < 10; i = i + 2) { + complexSum = complexSum + i; +} +assert(complexSum == 20, "For loop with complex increment should work"); // 0 + 2 + 4 + 6 + 8 = 20 +print(" For loop with complex increment: PASS"); + +// For loop with function calls +func getNext(i) { + return i + 1; +} + +var funcSum = 0; +for (var i = 0; i < 4; i = getNext(i)) { + funcSum = funcSum + i; +} +assert(funcSum == 6, "For loop with function calls should work"); // 0 + 1 + 2 + 3 = 6 +print(" For loop with function calls: PASS"); + +print("For loops: PASS"); + +// ======================================== +// TEST 48: CRITICAL LOOP EDGE CASES +// ======================================== +print("\n--- Test 48: Critical Loop Edge Cases ---"); + +// Edge case 1: Empty loop bodies +var emptyWhileCounter = 0; +while (emptyWhileCounter < 3) { + emptyWhileCounter = emptyWhileCounter + 1; +} +assert(emptyWhileCounter == 3, "Empty while loop body should still increment counter"); + +var emptyForCounter = 0; +for (var i = 0; i < 3; i = i + 1) { + // Empty body +} +assert(i == 3, "Empty for loop body should still increment counter"); + +// Edge case 2: Loops with only break/continue +var breakOnlyCounter = 0; +while (true) { + breakOnlyCounter = breakOnlyCounter + 1; + if (breakOnlyCounter == 5) { + break; + } +} +assert(breakOnlyCounter == 5, "Loop with only break should work"); + +var continueOnlyCounter = 0; +var continueOnlySum = 0; +for (var j = 0; j < 10; j = j + 1) { + if (j < 5) { + continue; + } + continueOnlySum = continueOnlySum + j; +} +assert(continueOnlySum == 35, "Loop with only continue should sum 5+6+7+8+9=35"); + +// Edge case 3: Nested break/continue with multiple levels +var nestedBreakLevel1 = 0; +var nestedBreakLevel2 = 0; +for (var a = 0; a < 3; a = a + 1) { + nestedBreakLevel1 = nestedBreakLevel1 + 1; + for (var b = 0; b < 3; b = b + 1) { + nestedBreakLevel2 = nestedBreakLevel2 + 1; + if (a == 1 && b == 1) { + break; + } + } +} +assert(nestedBreakLevel1 == 3, "Nested break should not affect outer loop"); +assert(nestedBreakLevel2 == 8, "Nested break should only break inner loop (3+3+2=8)"); + +// Edge case 4: Loops with function calls that return early +func earlyReturnFunc(x) { + if (x == 3) { + return "early"; + } + return "normal"; +} + +var earlyReturnCounter = 0; +var earlyReturnResults = ""; +for (var k = 0; k < 5; k = k + 1) { + earlyReturnCounter = earlyReturnCounter + 1; + earlyReturnResults = earlyReturnResults + earlyReturnFunc(k); +} +assert(earlyReturnCounter == 5, "Loop should continue even with early returns in function calls"); +assert(earlyReturnResults == "normalnormalnormalearlynormal", "Function calls with early returns should work in loops"); + +// Edge case 5: Loops with variable shadowing +var shadowVar = 10; +for (var shadowVar = 0; shadowVar < 3; shadowVar = shadowVar + 1) { + // Inner shadowVar shadows outer shadowVar +} +assert(shadowVar == 3, "Variable shadowing in for loop should work"); + +var shadowWhileVar = 20; +var shadowCounter = 0; +while (shadowCounter < 2) { + var shadowWhileVar = shadowCounter * 10; + shadowCounter = shadowCounter + 1; +} +assert(shadowWhileVar == 20, "Variable shadowing in while loop should not affect outer scope"); + +// Edge case 6: Loops with return statements in different contexts +func loopWithReturn() { + var returnCounter = 0; + for (var q = 0; q < 10; q = q + 1) { + returnCounter = returnCounter + 1; + if (q == 4) { + return returnCounter; + } + } + return returnCounter; +} +assert(loopWithReturn() == 5, "Loop with return should exit function early"); + +// Edge case 7: Loops with nested function definitions +var nestedFuncCounter = 0; +for (var r = 0; r < 3; r = r + 1) { + nestedFuncCounter = nestedFuncCounter + 1; + func nestedInLoop() { + return r * 2; + } + if (nestedInLoop() == 4) { + break; + } +} +assert(nestedFuncCounter == 3, "Loop with nested function definitions should work"); + +// Edge case 8: Loops with closures capturing loop variables +var closureResults = ""; +for (var s = 0; s < 3; s = s + 1) { + var capturedVar = s; + func closure() { + return capturedVar; + } + closureResults = closureResults + toString(closure()); +} +assert(closureResults == "012", "Loops with closures should capture variables correctly"); + +// Edge case 9: Loops with multiple continue statements +var multiContinueSum = 0; +for (var t = 0; t < 6; t = t + 1) { + if (t == 1) { + continue; + } + if (t == 3) { + continue; + } + if (t == 5) { + continue; + } + multiContinueSum = multiContinueSum + t; +} +assert(multiContinueSum == 6, "Loop with multiple continue statements should sum 0+2+4=6"); + +// Edge case 10: Loops with break/continue in conditional blocks +var conditionalBreakCounter = 0; +for (var p = 0; p < 10; p = p + 1) { + conditionalBreakCounter = conditionalBreakCounter + 1; + if (p % 2 == 0) { + if (p == 6) { + break; + } + } else { + if (p == 7) { + continue; + } + } +} +assert(conditionalBreakCounter == 7, "Loop with conditional break/continue should work"); + +print("Critical loop edge cases: PASS"); + +// ======================================== +// TEST 49: DO-WHILE LOOPS +// ======================================== + +// Basic do-while loop +var i = 0; +do { + i = i + 1; +} while (i < 5); +assert(i == 5, "Basic do-while loop should increment to 5"); + +// Do-while with break +var j = 0; +do { + j = j + 1; + if (j == 3) { + break; + } +} while (j < 10); +assert(j == 3, "Do-while with break should stop at 3"); + +// Do-while with continue +var k = 0; +var sum = 0; +do { + k = k + 1; + if (k == 2) { + continue; + } + sum = sum + k; +} while (k < 5); +assert(sum == 13, "Do-while with continue should sum 1+3+4+5=13"); + +// Do-while with return in function +func doWhileReturn() { + var x = 0; + do { + x = x + 1; + if (x == 3) { + return x; + } + } while (x < 10); + return 0; +} +assert(doWhileReturn() == 3, "Do-while with return should return 3"); + +// Nested do-while loops +var outer = 0; +var inner = 0; +do { + outer = outer + 1; + inner = 0; + do { + inner = inner + 1; + } while (inner < 3); +} while (outer < 2); +assert(outer == 2, "Nested do-while outer should be 2"); +assert(inner == 3, "Nested do-while inner should be 3"); + +// Do-while with complex condition +var a = 0; +var b = 5; +do { + a = a + 1; + b = b - 1; +} while (a < b); +assert(a == 3, "Do-while with complex condition a should be 3"); +assert(b == 2, "Do-while with complex condition b should be 2"); + +// Do-while with function call in condition +func getValue() { + return 3; +} + +var counter = 0; +do { + counter = counter + 1; +} while (counter < getValue()); +assert(counter == 3, "Do-while with function call should count to 3"); + +// Do-while with empty body (should still execute once) +var empty = 0; +do { + empty = empty + 1; +} while (false); +assert(empty == 1, "Do-while with false condition should execute once"); + +// Do-while with break and continue +var complex = 0; +var result = 0; +do { + complex = complex + 1; + if (complex == 1) { + continue; + } + if (complex == 4) { + break; + } + result = result + complex; +} while (complex < 10); +assert(result == 5, "Do-while with break and continue should sum 2+3=5"); + +print("Do-while loops: PASS"); + +// ======================================== +// TEST 50: TERNARY OPERATOR +// ======================================== + +// Basic ternary operator +var result1 = true ? 42 : 10; +assert(result1 == 42, "Basic ternary with true condition"); + +var result2 = false ? 42 : 10; +assert(result2 == 10, "Basic ternary with false condition"); + +// Ternary with expressions +var x = 5; +var y = 10; +var result3 = x > y ? "x is greater" : "y is greater"; +assert(result3 == "y is greater", "Ternary with comparison expression"); + +// Ternary with function calls +func getValue() { + return 100; +} + +func getOtherValue() { + return 200; +} + +var result4 = x < y ? getValue() : getOtherValue(); +assert(result4 == 100, "Ternary with function calls"); + +// Ternary with nested expressions +var result5 = x == 5 ? (y == 10 ? "both true" : "x true, y false") : "x false"; +assert(result5 == "both true", "Nested ternary expressions"); + +// Ternary with different types +var result6 = true ? "string" : 42; +assert(result6 == "string", "Ternary with different types (string)"); + +var result7 = false ? "string" : 42; +assert(result7 == 42, "Ternary with different types (number)"); + +// Ternary with boolean expressions +var result8 = x > 0 && y > 0 ? "both positive" : "not both positive"; +assert(result8 == "both positive", "Ternary with boolean expression"); + +// Ternary with arithmetic expressions +var result9 = x + y > 10 ? x * y : x + y; +assert(result9 == 50, "Ternary with arithmetic expressions"); + +// Ternary with variables +var a = 1; +var b = 2; +var c = 3; +var result10 = a < b ? c : a; +assert(result10 == 3, "Ternary with variables"); + +// Ternary with zero and negative values +var result11 = 0 ? "truthy" : "falsy"; +assert(result11 == "falsy", "Ternary with zero (falsy)"); + +var result12 = -1 ? "truthy" : "falsy"; +assert(result12 == "truthy", "Ternary with negative number (truthy)"); + +// Ternary with empty string +var result13 = "" ? "truthy" : "falsy"; +assert(result13 == "falsy", "Ternary with empty string (falsy)"); + +// Ternary with none +var result14 = none ? "truthy" : "falsy"; +assert(result14 == "falsy", "Ternary with none (falsy)"); + +// Ternary in assignment +var maxValue = x > y ? x : y; +assert(maxValue == 10, "Ternary in assignment"); + +// Ternary with complex conditions +var age = 25; +var status = age >= 18 ? "adult" : "minor"; +assert(status == "adult", "Ternary with complex condition"); + +// Ternary with string concatenation +var message = x > 3 ? "x is " + toString(x) : "x is small"; +assert(message == "x is 5", "Ternary with string concatenation"); + +// Ternary with multiple operators +var result15 = x > 0 && y > 0 && x < y ? "valid range" : "invalid range"; +assert(result15 == "valid range", "Ternary with multiple operators"); + +print("Ternary operator: PASS"); + // ======================================== // TEST SUMMARY // ======================================== @@ -1877,7 +2381,11 @@ 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("- While loops (basic, nested, complex conditions, return breaks)"); +print("- For loops (basic, nested, missing clauses, complex expressions)"); +print("- Do-while loops (basic, nested, break, continue, return, complex conditions)"); +print("- Critical loop edge cases (10 comprehensive scenarios)"); +print("- Ternary operator (conditional expressions with ? and :)"); print("\nAll tests passed."); -print("Test suite complete."); - \ No newline at end of file +print("Test suite complete."); \ No newline at end of file diff --git a/test_fib.bob b/test_fib.bob index 42b323c..47f94cc 100644 --- a/test_fib.bob +++ b/test_fib.bob @@ -15,3 +15,7 @@ print("Result: " + fib_result); print("Counter: " + counter); + +func test_fib() { + +} \ No newline at end of file