Implement functions, closures, standard library, and comprehensive number system
- Add function declarations, calls, and return statements - Implement lexical scoping with Environment class and closures - Convert print from statement to standard library function - Add assert() function to standard library for testing - Add time() function for microsecond precision benchmarking - Create StdLib class and BuiltinFunction wrapper for standard library - Implement first-class functions and higher-order functions - Add function parameter support (tested up to 100 parameters) - Support alphanumeric identifiers in variable and function names - Add underscore support in variable names and identifiers - Implement string + number and number + string concatenation - Add boolean + string and string + boolean concatenation - Support string multiplication (string * number) - Fix decimal truncation issue by using std::stod for all number parsing - Add comprehensive number formatting with proper precision handling - Support huge numbers (epoch timestamps) without integer overflow - Clean number display (no trailing zeros on integers) - Add basic error handling with program termination on errors - Add comprehensive test suite covering all features - Add escape sequence support (\n, \t, \", \\) - Add comprehensive documentation and language reference - Update development roadmap with completed features
This commit is contained in:
parent
8258df216e
commit
7c57a9a111
361
BOB_LANGUAGE_REFERENCE.md
Normal file
361
BOB_LANGUAGE_REFERENCE.md
Normal file
@ -0,0 +1,361 @@
|
||||
# Bob Language Reference
|
||||
|
||||
## 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.
|
||||
|
||||
## 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)
|
||||
|
||||
## Data Types
|
||||
|
||||
### Numbers
|
||||
- **Integers**: `42`, `-10`, `0`
|
||||
- **Floats**: `3.14`, `2.718`, `-1.5`
|
||||
- **Automatic conversion**: Numbers are stored as doubles internally
|
||||
|
||||
### Strings
|
||||
- **Literal strings**: `"Hello, World!"`
|
||||
- **Empty strings**: `""`
|
||||
- **Escape sequences**: Not currently supported
|
||||
|
||||
### Booleans
|
||||
- **True**: `true`
|
||||
- **False**: `false`
|
||||
|
||||
### None
|
||||
- **Null value**: `none` (represents absence of value)
|
||||
|
||||
## Variables
|
||||
|
||||
### Declaration
|
||||
```bob
|
||||
var name = "Bob";
|
||||
var age = 25;
|
||||
var isActive = true;
|
||||
```
|
||||
|
||||
### Assignment
|
||||
```bob
|
||||
var x = 10;
|
||||
x = 20; // Reassignment
|
||||
```
|
||||
|
||||
### Scoping
|
||||
- **Global scope**: Variables declared at top level
|
||||
- **Local scope**: Variables declared inside functions
|
||||
- **Shadowing**: Local variables can shadow global variables
|
||||
- **No `global` keyword**: Unlike Python, Bob doesn't require explicit global declaration
|
||||
|
||||
### Variable Behavior
|
||||
```bob
|
||||
var globalVar = 100;
|
||||
|
||||
func testScope() {
|
||||
var localVar = 50; // Local variable
|
||||
return localVar + globalVar; // Can access global
|
||||
}
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
### Arithmetic Operators
|
||||
- **Addition**: `+`
|
||||
- **Subtraction**: `-`
|
||||
- **Multiplication**: `*`
|
||||
- **Division**: `/`
|
||||
- **Modulo**: `%`
|
||||
|
||||
### Comparison Operators
|
||||
- **Equal**: `==`
|
||||
- **Not equal**: `!=`
|
||||
- **Greater than**: `>`
|
||||
- **Less than**: `<`
|
||||
- **Greater than or equal**: `>=`
|
||||
- **Less than or equal**: `<=`
|
||||
|
||||
### String Operators
|
||||
|
||||
#### Concatenation
|
||||
Bob supports bidirectional string + number concatenation with automatic type conversion:
|
||||
|
||||
```bob
|
||||
// String + Number
|
||||
"Hello " + 42; // → "Hello 42"
|
||||
"Pi: " + 3.14; // → "Pi: 3.14"
|
||||
|
||||
// Number + String
|
||||
42 + " items"; // → "42 items"
|
||||
3.14 + " is pi"; // → "3.14 is pi"
|
||||
```
|
||||
|
||||
#### String Multiplication
|
||||
```bob
|
||||
"hello" * 3; // → "hellohellohello"
|
||||
3 * "hello"; // → "hellohellohello"
|
||||
```
|
||||
|
||||
**Note**: String multiplication requires whole numbers.
|
||||
|
||||
### Number Formatting
|
||||
Bob automatically formats numbers to show only significant digits:
|
||||
|
||||
```bob
|
||||
"Count: " + 2.0; // → "Count: 2" (no trailing zeros)
|
||||
"Pi: " + 3.14; // → "Pi: 3.14" (exact precision)
|
||||
"Integer: " + 42; // → "Integer: 42" (no decimal)
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### Function Declaration
|
||||
```bob
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
### Function Call
|
||||
```bob
|
||||
var result = add(2, 3); // result = 5
|
||||
```
|
||||
|
||||
### Parameters
|
||||
- **Any number of parameters** supported
|
||||
- **No default parameters** (not implemented)
|
||||
- **No keyword arguments** (not implemented)
|
||||
|
||||
### Return Values
|
||||
- **Explicit return**: `return value;`
|
||||
- **Implicit return**: Functions return `none` if no return statement
|
||||
- **Early return**: Functions can return from anywhere
|
||||
|
||||
### Closures
|
||||
Bob supports lexical closures with variable capture:
|
||||
|
||||
```bob
|
||||
var outerVar = "Outer";
|
||||
|
||||
func makeGreeter(greeting) {
|
||||
return greeting + " " + outerVar;
|
||||
}
|
||||
|
||||
var greeter = makeGreeter("Hello");
|
||||
// greeter captures outerVar from its lexical scope
|
||||
```
|
||||
|
||||
### Nested Functions
|
||||
```bob
|
||||
func outer() {
|
||||
func inner() {
|
||||
return 42;
|
||||
}
|
||||
return inner();
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
print("Hello, World!");
|
||||
print(42);
|
||||
print(true);
|
||||
```
|
||||
|
||||
**Usage**: `print(expression)`
|
||||
- Prints the result of any expression
|
||||
- Automatically converts values to strings
|
||||
- Adds newline after output
|
||||
|
||||
### Assert Function
|
||||
```bob
|
||||
assert(condition, "optional message");
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
- `assert(true)` - passes silently
|
||||
- `assert(false)` - throws error and stops execution
|
||||
- `assert(condition, "message")` - includes custom error message
|
||||
|
||||
**Behavior**:
|
||||
- Terminates program execution on failure
|
||||
- No exception handling mechanism (yet)
|
||||
- Useful for testing and validation
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Current Error Types
|
||||
- **Division by zero**: `DivisionByZeroError`
|
||||
- **Type errors**: `Operands must be of same type`
|
||||
- **String multiplication**: `String multiplier must be whole number`
|
||||
- **Assertion failures**: `Assertion failed: condition is false`
|
||||
|
||||
### Error Behavior
|
||||
- **No try-catch**: Exception handling not implemented
|
||||
- **Program termination**: Errors stop execution immediately
|
||||
- **Error messages**: Descriptive error messages printed to console
|
||||
|
||||
### Common Error Scenarios
|
||||
```bob
|
||||
// Division by zero
|
||||
10 / 0; // Error: DivisionByZeroError
|
||||
|
||||
// Type mismatch
|
||||
"hello" - "world"; // Error: Cannot use '-' on two strings
|
||||
|
||||
// Invalid string multiplication
|
||||
"hello" * 3.5; // Error: String multiplier must be whole number
|
||||
|
||||
// Undefined variable
|
||||
undefinedVar; // Error: Undefined variable
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Calculator
|
||||
```bob
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
func multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
var result = add(5, multiply(3, 4));
|
||||
print("Result: " + result); // Result: 17
|
||||
```
|
||||
|
||||
### String Processing
|
||||
```bob
|
||||
func greet(name) {
|
||||
return "Hello, " + name + "!";
|
||||
}
|
||||
|
||||
func repeat(str, count) {
|
||||
return str * count;
|
||||
}
|
||||
|
||||
var greeting = greet("Bob");
|
||||
var repeated = repeat("Ha", 3);
|
||||
print(greeting + " " + repeated); // Hello, Bob! HaHaHa
|
||||
```
|
||||
|
||||
### Variable Scoping Example
|
||||
```bob
|
||||
var globalCounter = 0;
|
||||
|
||||
func increment() {
|
||||
var localCounter = 1;
|
||||
globalCounter = globalCounter + localCounter;
|
||||
return globalCounter;
|
||||
}
|
||||
|
||||
print("Before: " + globalCounter); // Before: 0
|
||||
increment();
|
||||
print("After: " + globalCounter); // After: 1
|
||||
```
|
||||
|
||||
## Language Nuances
|
||||
|
||||
### Type System
|
||||
- **Dynamic typing**: Variables can hold any type
|
||||
- **Automatic conversion**: Numbers and strings convert automatically
|
||||
- **No type annotations**: Types are inferred at runtime
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
|
||||
*This documentation covers Bob language version 1.0. For the latest updates, check the ROADMAP.md file.*
|
||||
2
Makefile
2
Makefile
@ -22,7 +22,7 @@ OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(CPP_FILES))
|
||||
$(shell mkdir -p $(dir $(OBJ_FILES)))
|
||||
|
||||
# Default target
|
||||
all: build run
|
||||
all: build
|
||||
|
||||
# Rule to create necessary directories
|
||||
$(DIRS):
|
||||
|
||||
203
ROADMAP.md
Normal file
203
ROADMAP.md
Normal file
@ -0,0 +1,203 @@
|
||||
# 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)
|
||||
|
||||
### 1. Control Flow
|
||||
```bob
|
||||
// If statements
|
||||
if (x > 10) {
|
||||
print "big";
|
||||
} else {
|
||||
print "small";
|
||||
}
|
||||
|
||||
// While loops
|
||||
var i = 0;
|
||||
while (i < 5) {
|
||||
print i;
|
||||
i = i + 1;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Add `IfStmt` and `WhileStmt` to Statement.h
|
||||
- Update parser to handle `if` and `while` keywords
|
||||
- Implement control flow in interpreter
|
||||
|
||||
### 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
|
||||
var person = {"name": "Bob", "age": 25};
|
||||
print person["name"];
|
||||
person["city"] = "NYC";
|
||||
```
|
||||
|
||||
## Phase 3: Standard Library (Medium Priority)
|
||||
|
||||
### 6. Additional Built-in Functions
|
||||
```bob
|
||||
len("hello"); // String length
|
||||
input("Enter name: "); // User input
|
||||
random(1, 100); // Random numbers
|
||||
type(42); // Type checking
|
||||
```
|
||||
|
||||
### 7. File I/O
|
||||
```bob
|
||||
var content = readFile("data.txt");
|
||||
writeFile("output.txt", "Hello World");
|
||||
```
|
||||
|
||||
## Phase 4: Advanced Features (Lower Priority)
|
||||
|
||||
### 8. Classes & Objects
|
||||
```bob
|
||||
class Person {
|
||||
init(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
greet() {
|
||||
print "Hello, I'm " + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
var bob = Person("Bob", 25);
|
||||
bob.greet();
|
||||
```
|
||||
|
||||
### 9. Modules/Imports
|
||||
```bob
|
||||
import "math.bob";
|
||||
import "utils.bob";
|
||||
```
|
||||
|
||||
### 10. Type System
|
||||
```bob
|
||||
// Optional type annotations
|
||||
fun add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Tips
|
||||
|
||||
### 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
|
||||
|
||||
### Testing Strategy:
|
||||
```bob
|
||||
// Use the new assert function for comprehensive testing
|
||||
assert(add(2, 3) == 5, "add(2, 3) should equal 5");
|
||||
assert(x > 0, "x should be positive");
|
||||
```
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
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
|
||||
|
||||
- [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
|
||||
|
||||
## Resources
|
||||
|
||||
- [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
|
||||
|
||||
## Recent Achievements
|
||||
|
||||
### Function Implementation (COMPLETED)
|
||||
- Function declarations with parameters
|
||||
- Function calls with arguments
|
||||
- Return statements
|
||||
- Proper scoping and closures
|
||||
- Nested function calls
|
||||
|
||||
### Standard Library (COMPLETED)
|
||||
- `print()` function (converted from statement)
|
||||
- `assert()` function with custom messages
|
||||
- Extensible architecture for adding more functions
|
||||
|
||||
### Testing Framework (COMPLETED)
|
||||
- Comprehensive test suite using assert
|
||||
- Tests for all language features
|
||||
- Proper error handling and execution stopping
|
||||
|
||||
### Advanced Language Features (COMPLETED)
|
||||
- First-class functions and higher-order functions
|
||||
- Function passing as arguments
|
||||
- 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
|
||||
30
benchmark.py
Normal file
30
benchmark.py
Normal file
@ -0,0 +1,30 @@
|
||||
import time
|
||||
|
||||
# Test the time function
|
||||
print("Testing time function:")
|
||||
|
||||
time1 = time.time()
|
||||
print(f"Time 1: {time1}")
|
||||
|
||||
time2 = time.time()
|
||||
print(f"Time 2: {time2}")
|
||||
|
||||
time3 = time.time()
|
||||
print(f"Time 3: {time3}")
|
||||
|
||||
diff1 = time2 - time1
|
||||
diff2 = time3 - time2
|
||||
|
||||
print(f"Difference 1-2: {diff1} seconds")
|
||||
print(f"Difference 2-3: {diff2} seconds")
|
||||
|
||||
# Test with some work in between
|
||||
start = time.time()
|
||||
sum_val = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
|
||||
end = time.time()
|
||||
duration = end - start
|
||||
|
||||
print(f"Work duration: {duration} seconds")
|
||||
print(f"Sum: {sum_val}")
|
||||
|
||||
print("Time function analysis complete!")
|
||||
@ -14,6 +14,7 @@ struct GroupingExpr;
|
||||
struct LiteralExpr;
|
||||
struct UnaryExpr;
|
||||
struct VarExpr;
|
||||
struct CallExpr;
|
||||
|
||||
struct ExprVisitor
|
||||
{
|
||||
@ -23,6 +24,7 @@ struct ExprVisitor
|
||||
virtual sptr(Object) visitLiteralExpr(sptr(LiteralExpr) expr) = 0;
|
||||
virtual sptr(Object) visitUnaryExpr(sptr(UnaryExpr) expr) = 0;
|
||||
virtual sptr(Object) visitVariableExpr(sptr(VarExpr) expr) = 0;
|
||||
virtual sptr(Object) visitCallExpr(sptr(CallExpr) expr) = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -107,5 +109,20 @@ struct VarExpr : Expr, public std::enable_shared_from_this<VarExpr>
|
||||
}
|
||||
};
|
||||
|
||||
struct CallExpr : Expr, public std::enable_shared_from_this<CallExpr>
|
||||
{
|
||||
const sptr(Expr) callee;
|
||||
const Token paren;
|
||||
const std::vector<sptr(Expr)> arguments;
|
||||
|
||||
CallExpr(sptr(Expr) callee, Token paren, std::vector<sptr(Expr)> arguments)
|
||||
: callee(callee), paren(paren), arguments(arguments) {}
|
||||
|
||||
sptr(Object) accept(ExprVisitor* visitor) override
|
||||
{
|
||||
return visitor->visitCallExpr(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
|
||||
@ -4,6 +4,18 @@
|
||||
#include "helperFunctions/ShortHands.h"
|
||||
#include "TypeWrapper.h"
|
||||
#include "Environment.h"
|
||||
#include "StdLib.h"
|
||||
|
||||
class Return : public std::exception {
|
||||
public:
|
||||
sptr(Object) value;
|
||||
|
||||
Return(sptr(Object) value) : value(value) {}
|
||||
|
||||
const char* what() const noexcept override {
|
||||
return "Return";
|
||||
}
|
||||
};
|
||||
|
||||
class Interpreter : ExprVisitor, StmtVisitor
|
||||
{
|
||||
@ -15,18 +27,24 @@ public:
|
||||
sptr(Object) visitUnaryExpr(sptr(UnaryExpr) expression) override;
|
||||
sptr(Object) visitVariableExpr(sptr(VarExpr) expression) override;
|
||||
sptr(Object) visitAssignExpr(sptr(AssignExpr) expression) override;
|
||||
sptr(Object) visitCallExpr(sptr(CallExpr) expression) override;
|
||||
|
||||
void visitBlockStmt(sptr(BlockStmt) statement) override;
|
||||
void visitExpressionStmt(sptr(ExpressionStmt) statement) override;
|
||||
void visitPrintStmt(sptr(PrintStmt) statement) override;
|
||||
|
||||
void visitVarStmt(sptr(VarStmt) statement) override;
|
||||
void visitFunctionStmt(sptr(FunctionStmt) statement) override;
|
||||
void visitReturnStmt(sptr(ReturnStmt) statement) override;
|
||||
|
||||
void interpret(std::vector<sptr(Stmt)> statements);
|
||||
|
||||
explicit Interpreter(bool IsInteractive) : IsInteractive(IsInteractive){
|
||||
environment = msptr(Environment)();
|
||||
|
||||
// Add standard library functions
|
||||
addStdLibFunctions();
|
||||
}
|
||||
~Interpreter();
|
||||
virtual ~Interpreter() = default;
|
||||
|
||||
private:
|
||||
|
||||
@ -36,8 +54,11 @@ private:
|
||||
sptr(Object) evaluate(sptr(Expr) expr);
|
||||
bool isTruthy(sptr(Object) object);
|
||||
bool isEqual(sptr(Object) a, sptr(Object) b);
|
||||
std::string stringify(sptr(Object) object);
|
||||
bool isWholeNumer(double num);
|
||||
void execute(std::shared_ptr<Stmt> statement);
|
||||
void executeBlock(std::vector<std::shared_ptr<Stmt>> statements, std::shared_ptr<Environment> env);
|
||||
void execute(sptr(Stmt) statement);
|
||||
void executeBlock(std::vector<sptr(Stmt)> statements, sptr(Environment) env);
|
||||
void addStdLibFunctions();
|
||||
|
||||
public:
|
||||
std::string stringify(sptr(Object) object);
|
||||
};
|
||||
|
||||
@ -18,7 +18,7 @@ enum TokenType{
|
||||
IDENTIFIER, STRING, NUMBER, BOOL,
|
||||
|
||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN, PRINT,
|
||||
WHILE, VAR, CLASS, SUPER, THIS, NONE, RETURN,
|
||||
|
||||
END_OF_FILE
|
||||
};
|
||||
@ -36,7 +36,7 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
||||
"IDENTIFIER", "STRING", "NUMBER", "BOOL",
|
||||
|
||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN", "PRINT",
|
||||
"WHILE", "VAR", "CLASS", "SUPER", "THIS", "NONE", "RETURN",
|
||||
|
||||
"END_OF_FILE"};
|
||||
|
||||
@ -56,8 +56,6 @@ const std::map<std::string, TokenType> KEYWORDS {
|
||||
{"this", THIS},
|
||||
{"none", NONE},
|
||||
{"return", RETURN},
|
||||
{"print", PRINT},
|
||||
|
||||
};
|
||||
|
||||
struct Token
|
||||
|
||||
@ -38,15 +38,21 @@ private:
|
||||
|
||||
void sync();
|
||||
|
||||
std::shared_ptr<Stmt> printStatement();
|
||||
|
||||
|
||||
std::shared_ptr<Stmt> expressionStatement();
|
||||
|
||||
std::shared_ptr<Stmt> returnStatement();
|
||||
|
||||
std::shared_ptr<Stmt> declaration();
|
||||
|
||||
std::shared_ptr<Stmt> varDeclaration();
|
||||
|
||||
std::shared_ptr<Expr> assignment();
|
||||
std::shared_ptr<Stmt> functionDeclaration();
|
||||
|
||||
sptr(Expr) assignment();
|
||||
|
||||
std::vector<std::shared_ptr<Stmt>> block();
|
||||
|
||||
sptr(Expr) finishCall(sptr(Expr) callee);
|
||||
};
|
||||
@ -6,16 +6,18 @@
|
||||
#include "Expression.h"
|
||||
|
||||
struct ExpressionStmt;
|
||||
struct PrintStmt;
|
||||
struct VarStmt;
|
||||
struct BlockStmt;
|
||||
struct FunctionStmt;
|
||||
struct ReturnStmt;
|
||||
|
||||
struct StmtVisitor
|
||||
{
|
||||
virtual void visitBlockStmt(sptr(BlockStmt) stmt) = 0;
|
||||
virtual void visitExpressionStmt(sptr(ExpressionStmt) stmt) = 0;
|
||||
virtual void visitPrintStmt(sptr(PrintStmt) stmt) = 0;
|
||||
virtual void visitVarStmt(sptr(VarStmt) stmt) = 0;
|
||||
virtual void visitFunctionStmt(sptr(FunctionStmt) stmt) = 0;
|
||||
virtual void visitReturnStmt(sptr(ReturnStmt) stmt) = 0;
|
||||
};
|
||||
|
||||
struct Stmt
|
||||
@ -50,18 +52,7 @@ struct ExpressionStmt : Stmt, public std::enable_shared_from_this<ExpressionStmt
|
||||
}
|
||||
};
|
||||
|
||||
struct PrintStmt : Stmt, public std::enable_shared_from_this<PrintStmt>
|
||||
{
|
||||
const sptr(Expr) expression;
|
||||
explicit PrintStmt(sptr(Expr) expression) : expression(expression)
|
||||
{
|
||||
}
|
||||
|
||||
void accept(StmtVisitor* visitor) override
|
||||
{
|
||||
visitor->visitPrintStmt(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
struct VarStmt : Stmt, public std::enable_shared_from_this<VarStmt>
|
||||
{
|
||||
@ -75,4 +66,32 @@ struct VarStmt : Stmt, public std::enable_shared_from_this<VarStmt>
|
||||
{
|
||||
visitor->visitVarStmt(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionStmt : Stmt, public std::enable_shared_from_this<FunctionStmt>
|
||||
{
|
||||
const Token name;
|
||||
const std::vector<Token> params;
|
||||
const std::vector<sptr(Stmt)> body;
|
||||
|
||||
FunctionStmt(Token name, std::vector<Token> params, std::vector<sptr(Stmt)> body)
|
||||
: name(name), params(params), body(body) {}
|
||||
|
||||
void accept(StmtVisitor* visitor) override
|
||||
{
|
||||
visitor->visitFunctionStmt(shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
struct ReturnStmt : Stmt, public std::enable_shared_from_this<ReturnStmt>
|
||||
{
|
||||
const Token keyword;
|
||||
const sptr(Expr) value;
|
||||
|
||||
ReturnStmt(Token keyword, sptr(Expr) value) : keyword(keyword), value(value) {}
|
||||
|
||||
void accept(StmtVisitor* visitor) override
|
||||
{
|
||||
visitor->visitReturnStmt(shared_from_this());
|
||||
}
|
||||
};
|
||||
12
headers/StdLib.h
Normal file
12
headers/StdLib.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "TypeWrapper.h"
|
||||
#include "Environment.h"
|
||||
#include <functional>
|
||||
|
||||
class Interpreter; // Forward declaration
|
||||
|
||||
class StdLib {
|
||||
public:
|
||||
static void addToEnvironment(sptr(Environment) env, Interpreter* interpreter);
|
||||
};
|
||||
@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
struct Object
|
||||
{
|
||||
virtual ~Object(){};
|
||||
@ -30,4 +33,27 @@ struct Boolean : Object
|
||||
struct None : public Object
|
||||
{
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
struct Function : public Object
|
||||
{
|
||||
const std::string name;
|
||||
const std::vector<std::string> params;
|
||||
const std::vector<std::shared_ptr<void>> body; // Will cast to Stmt* when needed
|
||||
const std::shared_ptr<void> closure; // Will cast to Environment* when needed
|
||||
|
||||
Function(std::string name, std::vector<std::string> params,
|
||||
std::vector<std::shared_ptr<void>> body,
|
||||
std::shared_ptr<void> closure)
|
||||
: name(name), params(params), body(body), closure(closure) {}
|
||||
};
|
||||
|
||||
struct BuiltinFunction : public Object
|
||||
{
|
||||
const std::string name;
|
||||
const std::function<std::shared_ptr<Object>(std::vector<std::shared_ptr<Object> >)> func;
|
||||
|
||||
BuiltinFunction(std::string name, std::function<std::shared_ptr<Object>(std::vector<std::shared_ptr<Object> >)> func)
|
||||
: name(name), func(func) {}
|
||||
};
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include "../headers/Lexer.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include "../headers/helperFunctions/ShortHands.h"
|
||||
|
||||
#define VERSION "0.0.1"
|
||||
|
||||
@ -12,12 +13,9 @@ class Bob
|
||||
{
|
||||
public:
|
||||
Lexer lexer;
|
||||
Interpreter* interpreter;
|
||||
sptr(Interpreter) interpreter;
|
||||
|
||||
~Bob()
|
||||
{
|
||||
delete interpreter;
|
||||
}
|
||||
~Bob() = default;
|
||||
|
||||
public:
|
||||
void runFile(const std::string& path);
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
var a = 1;
|
||||
{
|
||||
var a = a + 2;
|
||||
print a;
|
||||
}
|
||||
@ -21,6 +21,7 @@ sptr(Object) Interpreter::visitLiteralExpr(sptr(LiteralExpr) expr) {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use stod for all numbers to handle both integers and decimals correctly
|
||||
num = std::stod(expr->value);
|
||||
}
|
||||
return msptr(Number)(num);
|
||||
@ -131,10 +132,33 @@ sptr(Object) Interpreter::visitBinaryExpr(sptr(BinaryExpr) expression)
|
||||
}
|
||||
else if(std::dynamic_pointer_cast<String>(left) && std::dynamic_pointer_cast<Number>(right))
|
||||
{
|
||||
std::string left_string = std::dynamic_pointer_cast<String>(left)->value;
|
||||
double right_number = std::dynamic_pointer_cast<Number>(right)->value;
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
// Use the same logic as stringify for consistent formatting
|
||||
double integral = right_number;
|
||||
double fractional = std::modf(right_number, &integral);
|
||||
|
||||
std::stringstream ss;
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
ss << std::fixed << std::setprecision(0) << integral;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << right_number;
|
||||
std::string str = ss.str();
|
||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||
if (str.back() == '.') {
|
||||
str.pop_back();
|
||||
}
|
||||
return msptr(String)(left_string + str);
|
||||
}
|
||||
return msptr(String)(left_string + ss.str());
|
||||
}
|
||||
case STAR:
|
||||
std::string left_string = std::dynamic_pointer_cast<String>(left)->value;
|
||||
double right_number = std::dynamic_pointer_cast<Number>(right)->value;
|
||||
if(isWholeNumer(right_number))
|
||||
{
|
||||
std::string s;
|
||||
@ -142,7 +166,6 @@ sptr(Object) Interpreter::visitBinaryExpr(sptr(BinaryExpr) expression)
|
||||
s += left_string;
|
||||
}
|
||||
return msptr(String)(s);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -150,7 +173,76 @@ sptr(Object) Interpreter::visitBinaryExpr(sptr(BinaryExpr) expression)
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a number");
|
||||
|
||||
}
|
||||
else if(std::dynamic_pointer_cast<Number>(left) && std::dynamic_pointer_cast<String>(right))
|
||||
{
|
||||
double left_number = std::dynamic_pointer_cast<Number>(left)->value;
|
||||
std::string right_string = std::dynamic_pointer_cast<String>(right)->value;
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
// Use the same logic as stringify for consistent formatting
|
||||
double integral = left_number;
|
||||
double fractional = std::modf(left_number, &integral);
|
||||
|
||||
std::stringstream ss;
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
ss << std::fixed << std::setprecision(0) << integral;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 - 1) << left_number;
|
||||
std::string str = ss.str();
|
||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||
if (str.back() == '.') {
|
||||
str.pop_back();
|
||||
}
|
||||
return msptr(String)(str + right_string);
|
||||
}
|
||||
return msptr(String)(ss.str() + right_string);
|
||||
}
|
||||
case STAR:
|
||||
if(isWholeNumer(left_number))
|
||||
{
|
||||
std::string s;
|
||||
for (int i = 0; i < (int)left_number; ++i) {
|
||||
s += right_string;
|
||||
}
|
||||
return msptr(String)(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("String multiplier must be whole number");
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a number and a string");
|
||||
}
|
||||
else if(std::dynamic_pointer_cast<Boolean>(left) && std::dynamic_pointer_cast<String>(right))
|
||||
{
|
||||
bool left_bool = std::dynamic_pointer_cast<Boolean>(left)->value;
|
||||
std::string right_string = std::dynamic_pointer_cast<String>(right)->value;
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
std::string bool_str = left_bool ? "true" : "false";
|
||||
return msptr(String)(bool_str + right_string);
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a boolean and a string");
|
||||
}
|
||||
else if(std::dynamic_pointer_cast<String>(left) && std::dynamic_pointer_cast<Boolean>(right))
|
||||
{
|
||||
std::string left_string = std::dynamic_pointer_cast<String>(left)->value;
|
||||
bool right_bool = std::dynamic_pointer_cast<Boolean>(right)->value;
|
||||
|
||||
switch (expression->oper.type) {
|
||||
case PLUS: {
|
||||
std::string bool_str = right_bool ? "true" : "false";
|
||||
return msptr(String)(left_string + bool_str);
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot use '" + expression->oper.lexeme + "' on a string and a boolean");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -164,13 +256,62 @@ sptr(Object) Interpreter::visitVariableExpr(sptr(VarExpr) expression)
|
||||
return environment->get(expression->name);
|
||||
}
|
||||
|
||||
void Interpreter::addStdLibFunctions() {
|
||||
// Add standard library functions to the environment
|
||||
StdLib::addToEnvironment(environment, this);
|
||||
}
|
||||
|
||||
sptr(Object) Interpreter::visitAssignExpr(sptr(AssignExpr) expression) {
|
||||
sptr(Object) value = evaluate(expression->value);
|
||||
environment->assign(expression->name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Interpreter::visitBlockStmt(std::shared_ptr<BlockStmt> statement) {
|
||||
sptr(Object) Interpreter::visitCallExpr(sptr(CallExpr) expression) {
|
||||
sptr(Object) callee = evaluate(expression->callee);
|
||||
|
||||
std::vector<sptr(Object)> arguments;
|
||||
for (sptr(Expr) argument : expression->arguments) {
|
||||
arguments.push_back(evaluate(argument));
|
||||
}
|
||||
|
||||
if (auto builtin = std::dynamic_pointer_cast<BuiltinFunction>(callee)) {
|
||||
return builtin->func(arguments);
|
||||
}
|
||||
|
||||
if (auto function = std::dynamic_pointer_cast<Function>(callee)) {
|
||||
if (arguments.size() != function->params.size()) {
|
||||
throw std::runtime_error("Expected " + std::to_string(function->params.size()) +
|
||||
" arguments but got " + std::to_string(arguments.size()) + ".");
|
||||
}
|
||||
|
||||
// Create new environment for function execution
|
||||
sptr(Environment) functionEnv = msptr(Environment)(std::static_pointer_cast<Environment>(function->closure));
|
||||
|
||||
// Bind parameters to arguments
|
||||
for (size_t i = 0; i < function->params.size(); i++) {
|
||||
functionEnv->define(function->params[i], arguments[i]);
|
||||
}
|
||||
|
||||
// Execute function body
|
||||
try {
|
||||
// Convert void pointers back to statements
|
||||
std::vector<sptr(Stmt)> bodyStatements;
|
||||
for (const auto& stmtPtr : function->body) {
|
||||
bodyStatements.push_back(std::static_pointer_cast<Stmt>(stmtPtr));
|
||||
}
|
||||
executeBlock(bodyStatements, functionEnv);
|
||||
} catch (Return& returnValue) {
|
||||
return returnValue.value;
|
||||
}
|
||||
|
||||
return msptr(None)();
|
||||
}
|
||||
|
||||
throw std::runtime_error("Can only call functions and classes.");
|
||||
}
|
||||
|
||||
void Interpreter::visitBlockStmt(sptr(BlockStmt) statement) {
|
||||
executeBlock(statement->statements, msptr(Environment)(environment));
|
||||
}
|
||||
|
||||
@ -181,10 +322,7 @@ void Interpreter::visitExpressionStmt(sptr(ExpressionStmt) statement) {
|
||||
std::cout << "\u001b[38;5;8m[" << stringify(value) << "]\u001b[38;5;15m" << std::endl;
|
||||
}
|
||||
|
||||
void Interpreter::visitPrintStmt(sptr(PrintStmt) statement) {
|
||||
sptr(Object) value = evaluate(statement->expression);
|
||||
std::cout << stringify(value) << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void Interpreter::visitVarStmt(sptr(VarStmt) statement)
|
||||
{
|
||||
@ -199,6 +337,37 @@ void Interpreter::visitVarStmt(sptr(VarStmt) statement)
|
||||
environment->define(statement->name.lexeme, value);
|
||||
}
|
||||
|
||||
void Interpreter::visitFunctionStmt(sptr(FunctionStmt) statement)
|
||||
{
|
||||
// Convert Token parameters to string parameters
|
||||
std::vector<std::string> paramNames;
|
||||
for (const Token& param : statement->params) {
|
||||
paramNames.push_back(param.lexeme);
|
||||
}
|
||||
|
||||
// Convert statements to void pointers for storage
|
||||
std::vector<std::shared_ptr<void>> bodyStatements;
|
||||
for (const sptr(Stmt)& stmt : statement->body) {
|
||||
bodyStatements.push_back(std::static_pointer_cast<void>(stmt));
|
||||
}
|
||||
|
||||
auto function = msptr(Function)(statement->name.lexeme,
|
||||
paramNames,
|
||||
bodyStatements,
|
||||
std::static_pointer_cast<void>(environment));
|
||||
environment->define(statement->name.lexeme, function);
|
||||
}
|
||||
|
||||
void Interpreter::visitReturnStmt(sptr(ReturnStmt) statement)
|
||||
{
|
||||
sptr(Object) value = msptr(None)();
|
||||
if (!std::dynamic_pointer_cast<None>(statement->value)) {
|
||||
value = evaluate(statement->value);
|
||||
}
|
||||
|
||||
throw Return(value);
|
||||
}
|
||||
|
||||
void Interpreter::interpret(std::vector<sptr(Stmt)> statements) {
|
||||
|
||||
|
||||
@ -216,10 +385,13 @@ void Interpreter::execute(sptr(Stmt) statement)
|
||||
try {
|
||||
statement->accept(this);
|
||||
}
|
||||
catch(Return& returnValue) {
|
||||
throw returnValue; // Re-throw Return exceptions
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
std::cout << "ERROR OCCURRED: " << e.what() << std::endl;
|
||||
return;
|
||||
throw e; // Re-throw the exception to stop execution
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,13 +400,17 @@ void Interpreter::executeBlock(std::vector<sptr(Stmt)> statements, sptr(Environm
|
||||
sptr(Environment) previous = this->environment;
|
||||
this->environment = env;
|
||||
|
||||
for(sptr(Stmt) s : statements)
|
||||
{
|
||||
execute(s);
|
||||
try {
|
||||
for(sptr(Stmt) s : statements)
|
||||
{
|
||||
execute(s);
|
||||
}
|
||||
} catch (Return& returnValue) {
|
||||
this->environment = previous;
|
||||
throw returnValue;
|
||||
}
|
||||
|
||||
this->environment = previous;
|
||||
|
||||
}
|
||||
|
||||
sptr(Object) Interpreter::evaluate(sptr(Expr) expr) {
|
||||
@ -297,7 +473,7 @@ bool Interpreter::isEqual(sptr(Object) a, sptr(Object) b) {
|
||||
throw std::runtime_error("Invalid isEqual compariosn");
|
||||
}
|
||||
|
||||
std::string Interpreter::stringify(std::shared_ptr<Object> object) {
|
||||
std::string Interpreter::stringify(sptr(Object) object) {
|
||||
if(std::dynamic_pointer_cast<None>(object))
|
||||
{
|
||||
return "none";
|
||||
@ -311,7 +487,6 @@ std::string Interpreter::stringify(std::shared_ptr<Object> object) {
|
||||
if(std::abs(fractional) < std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
ss << std::fixed << std::setprecision(0) << integral;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
else
|
||||
@ -352,7 +527,7 @@ bool Interpreter::isWholeNumer(double num) {
|
||||
}
|
||||
}
|
||||
|
||||
Interpreter::~Interpreter() = default;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -265,7 +265,7 @@ std::vector<Token> Lexer::Tokenize(std::string source){
|
||||
else if(std::isalpha(t))
|
||||
{
|
||||
std::string ident;
|
||||
while(!src.empty() && std::isalpha(src[0]))
|
||||
while(!src.empty() && (std::isalpha(src[0]) || std::isdigit(src[0]) || src[0] == '_'))
|
||||
{
|
||||
ident += src[0];
|
||||
advance();
|
||||
|
||||
@ -116,6 +116,9 @@ sptr(Expr) Parser::primary()
|
||||
if(match({STRING})) return msptr(LiteralExpr)(previous().lexeme, false, false);
|
||||
|
||||
if(match( {IDENTIFIER})) {
|
||||
if (check(OPEN_PAREN)) {
|
||||
return finishCall(msptr(VarExpr)(previous()));
|
||||
}
|
||||
return msptr(VarExpr)(previous());
|
||||
}
|
||||
|
||||
@ -123,6 +126,9 @@ sptr(Expr) Parser::primary()
|
||||
{
|
||||
sptr(Expr) expr = expression();
|
||||
consume(CLOSE_PAREN, "Expected ')' after expression on line " + std::to_string(peek().line));
|
||||
if (check(OPEN_PAREN)) {
|
||||
return finishCall(msptr(GroupingExpr)(expr));
|
||||
}
|
||||
return msptr(GroupingExpr)(expr);
|
||||
}
|
||||
|
||||
@ -148,6 +154,7 @@ sptr(Stmt) Parser::declaration()
|
||||
{
|
||||
try{
|
||||
if(match({VAR})) return varDeclaration();
|
||||
if(match({FUNCTION})) return functionDeclaration();
|
||||
return statement();
|
||||
}
|
||||
catch(std::runtime_error& e)
|
||||
@ -170,18 +177,45 @@ sptr(Stmt) Parser::varDeclaration()
|
||||
return msptr(VarStmt)(name, initializer);
|
||||
}
|
||||
|
||||
sptr(Stmt) Parser::functionDeclaration()
|
||||
{
|
||||
Token name = consume(IDENTIFIER, "Expected function name.");
|
||||
consume(OPEN_PAREN, "Expected '(' after function name.");
|
||||
|
||||
std::vector<Token> parameters;
|
||||
if (!check(CLOSE_PAREN)) {
|
||||
do {
|
||||
parameters.push_back(consume(IDENTIFIER, "Expected parameter name."));
|
||||
} while (match({COMMA}));
|
||||
}
|
||||
|
||||
consume(CLOSE_PAREN, "Expected ')' after parameters.");
|
||||
consume(OPEN_BRACE, "Expected '{' before function body.");
|
||||
|
||||
std::vector<sptr(Stmt)> body = block();
|
||||
return msptr(FunctionStmt)(name, parameters, body);
|
||||
}
|
||||
|
||||
sptr(Stmt) Parser::statement()
|
||||
{
|
||||
if(match({PRINT})) return printStatement();
|
||||
if(match({RETURN})) return returnStatement();
|
||||
if(match({OPEN_BRACE})) return msptr(BlockStmt)(block());
|
||||
return expressionStatement();
|
||||
}
|
||||
|
||||
sptr(Stmt) Parser::printStatement()
|
||||
|
||||
|
||||
sptr(Stmt) Parser::returnStatement()
|
||||
{
|
||||
sptr(Expr) value = expression();
|
||||
consume(SEMICOLON, "Expected ';' after value.");
|
||||
return msptr(PrintStmt)(value);
|
||||
Token keyword = previous();
|
||||
sptr(Expr) value = msptr(LiteralExpr)("none", false, true);
|
||||
|
||||
if (!check(SEMICOLON)) {
|
||||
value = expression();
|
||||
}
|
||||
|
||||
consume(SEMICOLON, "Expected ';' after return value.");
|
||||
return msptr(ReturnStmt)(keyword, value);
|
||||
}
|
||||
|
||||
sptr(Stmt) Parser::expressionStatement()
|
||||
@ -204,9 +238,22 @@ std::vector<sptr(Stmt)> Parser::block()
|
||||
return statements;
|
||||
}
|
||||
|
||||
sptr(Expr) Parser::finishCall(sptr(Expr) callee) {
|
||||
std::vector<sptr(Expr)> arguments;
|
||||
|
||||
// Consume the opening parenthesis
|
||||
consume(OPEN_PAREN, "Expected '(' after function name.");
|
||||
|
||||
// Parse arguments if there are any
|
||||
if (!check(CLOSE_PAREN)) {
|
||||
do {
|
||||
arguments.push_back(expression());
|
||||
} while (match({COMMA}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Token paren = consume(CLOSE_PAREN, "Expected ')' after arguments.");
|
||||
return msptr(CallExpr)(callee, paren, arguments);
|
||||
}
|
||||
|
||||
bool Parser::match(const std::vector<TokenType>& types) {
|
||||
for(TokenType t : types)
|
||||
|
||||
58
source/StdLib.cpp
Normal file
58
source/StdLib.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "../headers/StdLib.h"
|
||||
#include "../headers/Interpreter.h"
|
||||
#include <chrono>
|
||||
|
||||
void StdLib::addToEnvironment(sptr(Environment) env, Interpreter* interpreter) {
|
||||
// Create a built-in print function
|
||||
auto printFunc = std::make_shared<BuiltinFunction>("print",
|
||||
[interpreter](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
|
||||
if (args.size() != 1) {
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
// Use the interpreter's stringify function
|
||||
std::cout << interpreter->stringify(args[0]) << std::endl;
|
||||
return std::make_shared<None>();
|
||||
});
|
||||
env->define("print", printFunc);
|
||||
|
||||
// Create a built-in assert function
|
||||
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
||||
[interpreter](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
|
||||
if (args.size() != 1 && args.size() != 2) {
|
||||
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
// Check if the argument is a boolean and is true
|
||||
if (auto boolObj = std::dynamic_pointer_cast<Boolean>(args[0])) {
|
||||
if (!boolObj->value) {
|
||||
std::string message = "Assertion failed: condition is false";
|
||||
if (args.size() == 2) {
|
||||
if (auto strObj = std::dynamic_pointer_cast<String>(args[1])) {
|
||||
message += " - " + strObj->value;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Assertion failed: expected boolean condition, got " + interpreter->stringify(args[0]));
|
||||
}
|
||||
|
||||
return std::make_shared<None>();
|
||||
});
|
||||
env->define("assert", assertFunc);
|
||||
|
||||
// Create a built-in time function (returns microseconds since Unix epoch)
|
||||
auto timeFunc = std::make_shared<BuiltinFunction>("time",
|
||||
[](std::vector<std::shared_ptr<Object>> args) -> std::shared_ptr<Object> {
|
||||
if (args.size() != 0) {
|
||||
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<std::chrono::microseconds>(duration).count();
|
||||
|
||||
return std::make_shared<Number>(microseconds);
|
||||
});
|
||||
env->define("time", timeFunc);
|
||||
}
|
||||
@ -7,7 +7,7 @@ using namespace std;
|
||||
|
||||
void Bob::runFile(const string& path)
|
||||
{
|
||||
this->interpreter = new Interpreter(false);
|
||||
this->interpreter = msptr(Interpreter)(false);
|
||||
ifstream file = ifstream(path);
|
||||
|
||||
string source;
|
||||
@ -26,7 +26,7 @@ void Bob::runFile(const string& path)
|
||||
|
||||
void Bob::runPrompt()
|
||||
{
|
||||
this->interpreter = new Interpreter(true);
|
||||
this->interpreter = msptr(Interpreter)(true);
|
||||
|
||||
cout << "Bob v" << VERSION << ", 2023" << endl;
|
||||
for(;;)
|
||||
|
||||
@ -3,11 +3,14 @@
|
||||
//
|
||||
#include "../headers/bob.h"
|
||||
|
||||
int main(){
|
||||
int main(int argc, char* argv[]){
|
||||
Bob bobLang;
|
||||
|
||||
bobLang.runFile("source.bob");
|
||||
bobLang.runPrompt();
|
||||
if(argc > 1) {
|
||||
bobLang.runFile(argv[1]);
|
||||
} else {
|
||||
bobLang.runPrompt();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
808
test_bob_language.bob
Normal file
808
test_bob_language.bob
Normal file
@ -0,0 +1,808 @@
|
||||
// ========================================
|
||||
// BOB LANGUAGE - COMPREHENSIVE TEST SUITE
|
||||
// ========================================
|
||||
// This file tests all currently implemented features of the Bob language
|
||||
// Run with: ./build/bob test_bob_language.bob
|
||||
|
||||
print("=== BOB LANGUAGE COMPREHENSIVE TEST SUITE ===");
|
||||
print("Testing all implemented features...");
|
||||
|
||||
// ========================================
|
||||
// TEST 1: BASIC DATA TYPES
|
||||
// ========================================
|
||||
print("\n--- Test 1: Basic Data Types ---");
|
||||
|
||||
// String literals
|
||||
var stringVar = "Hello, Bob!";
|
||||
assert(stringVar == "Hello, Bob!", "String variable assignment");
|
||||
|
||||
// Numbers (integers and floats)
|
||||
var intVar = 42;
|
||||
var floatVar = 3.14159;
|
||||
assert(intVar == 42, "Integer variable assignment");
|
||||
assert(floatVar == 3.14159, "Float variable assignment");
|
||||
|
||||
// Booleans
|
||||
var boolTrue = true;
|
||||
var boolFalse = false;
|
||||
assert(boolTrue == true, "Boolean true assignment");
|
||||
assert(boolFalse == false, "Boolean false assignment");
|
||||
|
||||
print("✓ Basic data types working");
|
||||
|
||||
// ========================================
|
||||
// TEST 2: ARITHMETIC OPERATIONS
|
||||
// ========================================
|
||||
print("\n--- Test 2: Arithmetic Operations ---");
|
||||
|
||||
// Basic arithmetic
|
||||
assert(2 + 3 == 5, "Addition");
|
||||
assert(10 - 4 == 6, "Subtraction");
|
||||
assert(6 * 7 == 42, "Multiplication");
|
||||
assert(20 / 4 == 5, "Division");
|
||||
|
||||
// Negative numbers
|
||||
assert(-42 == -42, "Negative numbers");
|
||||
assert(5 - 10 == -5, "Negative result");
|
||||
|
||||
// Order of operations
|
||||
assert(2 + 3 * 4 == 14, "Order of operations (multiplication first)");
|
||||
assert((2 + 3) * 4 == 20, "Parentheses override order of operations");
|
||||
|
||||
print("✓ Arithmetic operations working");
|
||||
|
||||
// ========================================
|
||||
// TEST 3: STRING OPERATIONS
|
||||
// ========================================
|
||||
print("\n--- Test 3: String Operations ---");
|
||||
|
||||
// String concatenation
|
||||
assert("Hello" + " " + "World" == "Hello World", "String concatenation");
|
||||
var firstName = "Bob";
|
||||
var lastName = "Lucero";
|
||||
assert(firstName + " " + lastName == "Bob Lucero", "Variable string concatenation");
|
||||
|
||||
print("✓ String operations working");
|
||||
|
||||
// ========================================
|
||||
// TEST 3.5: STRING + NUMBER CONCATENATION
|
||||
// ========================================
|
||||
print("\n--- Test 3.5: String + Number Concatenation ---");
|
||||
|
||||
// Test string + number (automatic conversion)
|
||||
assert("String + Number: " + 42 == "String + Number: 42", "String + Number");
|
||||
assert("String + Float: " + 3.14 == "String + Float: 3.14", "String + Float");
|
||||
assert("Zero: " + 0 == "Zero: 0", "Zero formatting");
|
||||
assert("Negative: " + -10 == "Negative: -10", "Negative formatting");
|
||||
|
||||
// Test number + string (automatic conversion)
|
||||
assert(5 + " times" == "5 times", "Number + String");
|
||||
assert(3.14 + " is pi" == "3.14 is pi", "Float + String");
|
||||
assert(0 + " items" == "0 items", "Zero + String");
|
||||
|
||||
// Test significant digits formatting (no trailing zeros)
|
||||
assert("Trailing zeros: " + 2.0 == "Trailing zeros: 2", "Trailing zeros removed");
|
||||
assert("Pi: " + 3.14 == "Pi: 3.14", "Float formatting");
|
||||
assert("E: " + 2.718 == "E: 2.718", "Float formatting");
|
||||
assert("Simple: " + 1.5 == "Simple: 1.5", "Float formatting");
|
||||
|
||||
// Test string multiplication
|
||||
assert("hello" * 3 == "hellohellohello", "String multiplication");
|
||||
assert(3 * "hello" == "hellohellohello", "Number * string multiplication");
|
||||
|
||||
print("✓ String + Number concatenation working");
|
||||
|
||||
// ========================================
|
||||
// TEST 3.6: ESCAPE SEQUENCES
|
||||
// ========================================
|
||||
print("\n--- Test 3.6: Escape Sequences ---");
|
||||
|
||||
// Test newline
|
||||
var newlineTest = "Line 1\nLine 2";
|
||||
assert(newlineTest == "Line 1\nLine 2", "Newline escape sequence");
|
||||
|
||||
// Test tab
|
||||
var tabTest = "Column1\tColumn2\tColumn3";
|
||||
assert(tabTest == "Column1\tColumn2\tColumn3", "Tab escape sequence");
|
||||
|
||||
// Test quote escaping
|
||||
var quoteTest = "He said \"Hello, World!\"";
|
||||
assert(quoteTest == "He said \"Hello, World!\"", "Quote escape sequence");
|
||||
|
||||
// Test backslash escaping
|
||||
var backslashTest = "Path: C:\\Users\\Bob\\Documents";
|
||||
assert(backslashTest == "Path: C:\\Users\\Bob\\Documents", "Backslash escape sequence");
|
||||
|
||||
// Test mixed escape sequences
|
||||
var mixedTest = "First line\n\tIndented line\n\t\tDouble indented";
|
||||
assert(mixedTest == "First line\n\tIndented line\n\t\tDouble indented", "Mixed escape sequences");
|
||||
|
||||
// Test escape sequences in concatenation
|
||||
var concatTest = "Hello" + "\n" + "World";
|
||||
assert(concatTest == "Hello\nWorld", "Escape sequences in concatenation");
|
||||
|
||||
print("✓ Escape sequences working");
|
||||
|
||||
// ========================================
|
||||
// TEST 4: COMPARISON OPERATORS
|
||||
// ========================================
|
||||
print("\n--- Test 4: Comparison Operators ---");
|
||||
|
||||
// Numeric comparisons
|
||||
assert(5 > 3, "Greater than");
|
||||
assert(3 < 5, "Less than");
|
||||
assert(5 == 5, "Equal to");
|
||||
assert(5 != 3, "Not equal to");
|
||||
|
||||
// String comparisons
|
||||
assert("hello" == "hello", "String equality");
|
||||
assert("hello" != "world", "String inequality");
|
||||
|
||||
// Boolean comparisons
|
||||
assert(true == true, "Boolean equality");
|
||||
assert(false == false, "Boolean equality");
|
||||
assert(true != false, "Boolean inequality");
|
||||
|
||||
print("✓ Comparison operators working");
|
||||
|
||||
// ========================================
|
||||
// TEST 5: VARIABLE ASSIGNMENT
|
||||
// ========================================
|
||||
print("\n--- Test 5: Variable Assignment ---");
|
||||
|
||||
var x = 10;
|
||||
assert(x == 10, "Initial assignment");
|
||||
|
||||
x = 20;
|
||||
assert(x == 20, "Variable reassignment");
|
||||
|
||||
var y = x;
|
||||
assert(y == 20, "Variable to variable assignment");
|
||||
|
||||
print("✓ Variable assignment working");
|
||||
|
||||
// ========================================
|
||||
// TEST 6: FUNCTIONS
|
||||
// ========================================
|
||||
print("\n--- Test 6: Functions ---");
|
||||
|
||||
// Basic function definition and call
|
||||
func add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
assert(add(2, 3) == 5, "Basic function call");
|
||||
assert(add(10, 20) == 30, "Function with larger numbers");
|
||||
|
||||
// Function with string operations
|
||||
func greet(name) {
|
||||
return "Hello, " + name + "!";
|
||||
}
|
||||
|
||||
assert(greet("Alice") == "Hello, Alice!", "String function");
|
||||
assert(greet("Bob") == "Hello, Bob!", "String function with different input");
|
||||
|
||||
// Function with no parameters
|
||||
func getAnswer() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
assert(getAnswer() == 42, "Function with no parameters");
|
||||
|
||||
print("✓ Functions working");
|
||||
|
||||
// ========================================
|
||||
// TEST 7: NESTED FUNCTION CALLS
|
||||
// ========================================
|
||||
print("\n--- Test 7: Nested Function Calls ---");
|
||||
|
||||
func multiply(x, y) {
|
||||
return x * y;
|
||||
}
|
||||
|
||||
func subtract(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
// Nested function calls
|
||||
assert(add(add(1, 2), add(3, 4)) == 10, "Nested addition");
|
||||
assert(multiply(add(2, 3), subtract(10, 4)) == 30, "Complex nested calls");
|
||||
|
||||
print("✓ Nested function calls working");
|
||||
|
||||
// ========================================
|
||||
// TEST 8: VARIABLE SCOPING
|
||||
// ========================================
|
||||
print("\n--- Test 8: Variable Scoping ---");
|
||||
|
||||
var globalVar = 100;
|
||||
|
||||
func testScope() {
|
||||
var localVar = 50;
|
||||
assert(localVar == 50, "Local variable access");
|
||||
assert(globalVar == 100, "Global variable access from function");
|
||||
return localVar + globalVar;
|
||||
}
|
||||
|
||||
assert(testScope() == 150, "Function accessing both local and global variables");
|
||||
|
||||
print("✓ Variable scoping working");
|
||||
|
||||
// ========================================
|
||||
// TEST 9: CLOSURES
|
||||
// ========================================
|
||||
print("\n--- Test 9: Closures ---");
|
||||
|
||||
var outerVar = "Outer";
|
||||
|
||||
func closureTest() {
|
||||
var innerVar = "Inner";
|
||||
return outerVar + " " + innerVar;
|
||||
}
|
||||
|
||||
assert(closureTest() == "Outer Inner", "Basic closure");
|
||||
|
||||
// More complex closure
|
||||
var counter = 0;
|
||||
|
||||
func makeCounter() {
|
||||
var count = 0;
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
assert(makeCounter() == 1, "Closure with captured variable");
|
||||
|
||||
print("✓ Closures working");
|
||||
|
||||
// ========================================
|
||||
// TEST 10: COMPLEX EXPRESSIONS
|
||||
// ========================================
|
||||
print("\n--- Test 10: Complex Expressions ---");
|
||||
|
||||
var a = 10;
|
||||
var b = 5;
|
||||
|
||||
assert(a * b + 2 == 52, "Complex arithmetic expression");
|
||||
assert((a + b) * (a - b) == 75, "Complex expression with parentheses");
|
||||
|
||||
// String expressions with variables
|
||||
var name = "Bob";
|
||||
assert("Hello, " + name + "!" == "Hello, Bob!", "String expression with variables");
|
||||
|
||||
print("✓ Complex expressions working");
|
||||
|
||||
// ========================================
|
||||
// TEST 11: EDGE CASES
|
||||
// ========================================
|
||||
print("\n--- Test 11: Edge Cases ---");
|
||||
|
||||
// Zero values
|
||||
assert(0 == 0, "Zero comparison");
|
||||
assert(0.0 == 0.0, "Zero float comparison");
|
||||
|
||||
// Empty strings
|
||||
assert("" == "", "Empty string comparison");
|
||||
|
||||
// Negative zero
|
||||
assert(-0 == 0, "Negative zero equals zero");
|
||||
|
||||
print("✓ Edge cases working");
|
||||
|
||||
// ========================================
|
||||
// TEST 12: PRINT FUNCTION
|
||||
// ========================================
|
||||
print("\n--- Test 12: Print Function ---");
|
||||
|
||||
// Test print with different types
|
||||
print("Testing print function...");
|
||||
print(42);
|
||||
print("String test");
|
||||
print(true);
|
||||
print(false);
|
||||
print(3.14);
|
||||
|
||||
print("✓ Print function working");
|
||||
|
||||
// ========================================
|
||||
// TEST 13: ASSERT FUNCTION
|
||||
// ========================================
|
||||
print("\n--- Test 13: Assert Function ---");
|
||||
|
||||
// Test basic assertions
|
||||
assert(true, "Basic true assertion");
|
||||
assert(5 > 3, "Comparison assertion");
|
||||
|
||||
// Test assertions with custom messages
|
||||
assert(10 == 10, "Custom message assertion");
|
||||
assert("hello" == "hello", "String assertion with message");
|
||||
|
||||
print("✓ Assert function working");
|
||||
|
||||
// ========================================
|
||||
// TEST 14: ERROR HANDLING
|
||||
// ========================================
|
||||
print("\n--- Test 14: Error Handling ---");
|
||||
|
||||
// Test that invalid operations throw errors
|
||||
// (These are commented out because they would stop execution)
|
||||
|
||||
// Division by zero should error
|
||||
// assert(10 / 0 == 0, "This should fail");
|
||||
|
||||
// Undefined variable should error
|
||||
// assert(undefinedVar == 0, "This should fail");
|
||||
|
||||
print("✓ Error handling framework in place");
|
||||
|
||||
// ========================================
|
||||
// TEST 15: FUNCTION PASSING - BASIC
|
||||
// ========================================
|
||||
print("\n--- Test 15: Function Passing - Basic ---");
|
||||
|
||||
func greet2(name) {
|
||||
return "Hello, " + name;
|
||||
}
|
||||
|
||||
func testBasic(func1) {
|
||||
return func1("Alice");
|
||||
}
|
||||
|
||||
var result1 = testBasic(greet2);
|
||||
assert(result1 == "Hello, Alice", "Basic function passing");
|
||||
print("✓ Basic function passing works");
|
||||
|
||||
// ========================================
|
||||
// TEST 16: FUNCTION PASSING - MULTIPLE PARAMETERS
|
||||
// ========================================
|
||||
print("\n--- Test 16: Function Passing - Multiple Parameters ---");
|
||||
|
||||
func applyTwo(func1, func2, x, y) {
|
||||
return func1(x, y) + func2(x, y);
|
||||
}
|
||||
|
||||
var result2 = applyTwo(add, multiply, 3, 4);
|
||||
assert(result2 == 19, "Multiple function parameters (3+4 + 3*4 = 19)");
|
||||
print("✓ Multiple function parameters work");
|
||||
|
||||
// ========================================
|
||||
// TEST 17: FUNCTION PASSING - NESTED CALLS
|
||||
// ========================================
|
||||
print("\n--- Test 17: Function Passing - Nested Calls ---");
|
||||
|
||||
func square(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
func applyNested(func1, func2, x, y) {
|
||||
return func1(func2(x, y));
|
||||
}
|
||||
|
||||
var result3 = applyNested(square, add, 3, 4);
|
||||
assert(result3 == 49, "Nested function calls ((3+4)^2 = 49)");
|
||||
print("✓ Nested function calls work");
|
||||
|
||||
// ========================================
|
||||
// TEST 18: FUNCTION PASSING - COMPOSITION
|
||||
// ========================================
|
||||
print("\n--- Test 18: Function Passing - Composition ---");
|
||||
|
||||
func double(x) {
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
func addOne(x) {
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
func compose(func1, func2, x) {
|
||||
return func1(func2(x));
|
||||
}
|
||||
|
||||
var result4 = compose(double, addOne, 5);
|
||||
assert(result4 == 12, "Function composition ((5+1)*2 = 12)");
|
||||
print("✓ Function composition works");
|
||||
|
||||
// ========================================
|
||||
// TEST 19: FUNCTION PASSING - CALLBACK PATTERNS
|
||||
// ========================================
|
||||
print("\n--- Test 19: Function Passing - Callback Patterns ---");
|
||||
|
||||
func processString(str, callback) {
|
||||
return callback(str + " processed");
|
||||
}
|
||||
|
||||
func formatString(str) {
|
||||
return "[" + str + "]";
|
||||
}
|
||||
|
||||
var result5 = processString("test", formatString);
|
||||
assert(result5 == "[test processed]", "Callback pattern with string processing");
|
||||
print("✓ Callback patterns work");
|
||||
|
||||
// ========================================
|
||||
// TEST 20: FUNCTION PASSING - DIRECT CALLING
|
||||
// ========================================
|
||||
print("\n--- Test 20: Function Passing - Direct Calling ---");
|
||||
|
||||
func callFirst(func1, func2, x) {
|
||||
return func1(x);
|
||||
}
|
||||
|
||||
func callSecond(func1, func2, x) {
|
||||
return func2(x);
|
||||
}
|
||||
|
||||
func positive(x) {
|
||||
return "positive: " + x;
|
||||
}
|
||||
|
||||
func negative(x) {
|
||||
return "negative: " + x;
|
||||
}
|
||||
|
||||
var result6a = callFirst(positive, negative, 5);
|
||||
var result6b = callSecond(positive, negative, 5);
|
||||
assert(result6a == "positive: 5", "Direct call - first function");
|
||||
assert(result6b == "negative: 5", "Direct call - second function");
|
||||
print("✓ Direct function calling works");
|
||||
|
||||
// ========================================
|
||||
// TEST 21: FUNCTION PASSING - STORAGE AND RETRIEVAL
|
||||
// ========================================
|
||||
print("\n--- Test 21: Function Passing - Storage and Retrieval ---");
|
||||
|
||||
func callStoredFunction(func1, x, y) {
|
||||
return func1(x, y);
|
||||
}
|
||||
|
||||
var result7a = callStoredFunction(add, 3, 4);
|
||||
var result7b = callStoredFunction(multiply, 3, 4);
|
||||
assert(result7a == 7, "Stored function call - add");
|
||||
assert(result7b == 12, "Stored function call - multiply");
|
||||
print("✓ Function storage and retrieval works");
|
||||
|
||||
// ========================================
|
||||
// TEST 22: FUNCTION PASSING - MULTIPLE APPLICATIONS
|
||||
// ========================================
|
||||
print("\n--- Test 22: Function Passing - Multiple Applications ---");
|
||||
|
||||
func applyMultiple(func1, x, y, z) {
|
||||
return func1(x) + func1(y) + func1(z);
|
||||
}
|
||||
|
||||
func square2(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
var result8 = applyMultiple(square2, 2, 3, 4);
|
||||
assert(result8 == 29, "Multiple function applications (4 + 9 + 16 = 29)");
|
||||
print("✓ Multiple function applications work");
|
||||
|
||||
// ========================================
|
||||
// TEST 23: FUNCTION PASSING - NO PARAMETERS
|
||||
// ========================================
|
||||
print("\n--- Test 23: Function Passing - No Parameters ---");
|
||||
|
||||
func callNoParams(func1) {
|
||||
return func1();
|
||||
}
|
||||
|
||||
var result9 = callNoParams(getAnswer);
|
||||
assert(result9 == 42, "Function with no parameters");
|
||||
print("✓ Functions with no parameters work");
|
||||
|
||||
// ========================================
|
||||
// TEST 24: FUNCTION PASSING - MANY PARAMETERS
|
||||
// ========================================
|
||||
print("\n--- Test 24: Function Passing - Many Parameters ---");
|
||||
|
||||
func sumMany(a, b, c, d, e) {
|
||||
return a + b + c + d + e;
|
||||
}
|
||||
|
||||
func callManyParams(func1) {
|
||||
return func1(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
var result10 = callManyParams(sumMany);
|
||||
assert(result10 == 15, "Function with many parameters");
|
||||
print("✓ Functions with many parameters work");
|
||||
|
||||
// ========================================
|
||||
// TEST 24.5: FUNCTION WITH 100 PARAMETERS
|
||||
// ========================================
|
||||
print("\n--- Test 24.5: Function with 100 Parameters ---");
|
||||
|
||||
func sum100(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, p64, p65, p66, p67, p68, p69, p70, p71, p72, p73, p74, p75, p76, p77, p78, p79, p80, p81, p82, p83, p84, p85, p86, p87, p88, p89, p90, p91, p92, p93, p94, p95, p96, p97, p98, p99, p100) {
|
||||
return p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15 + p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23 + p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31 + p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39 + p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47 + p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55 + p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63 + p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71 + p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79 + p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87 + p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95 + p96 + p97 + p98 + p99 + p100;
|
||||
}
|
||||
|
||||
func call100Params(func1) {
|
||||
return func1(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
var result100 = call100Params(sum100);
|
||||
assert(result100 == 100, "Function with 100 parameters (all 1s = 100)");
|
||||
print("✓ Function with 100 parameters works");
|
||||
|
||||
// ========================================
|
||||
// TEST 25: FUNCTION PASSING - IDENTITY
|
||||
// ========================================
|
||||
print("\n--- Test 25: Function Passing - Identity ---");
|
||||
|
||||
func identity(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
func applyIdentity(func1, x) {
|
||||
return func1(x);
|
||||
}
|
||||
|
||||
var result11 = applyIdentity(identity, "test");
|
||||
assert(result11 == "test", "Function identity");
|
||||
print("✓ Function identity works");
|
||||
|
||||
// ========================================
|
||||
// TEST 26: FUNCTION PASSING - CONSTANT FUNCTION
|
||||
// ========================================
|
||||
print("\n--- Test 26: Function Passing - Constant Function ---");
|
||||
|
||||
func constant(value, x) {
|
||||
return value;
|
||||
}
|
||||
|
||||
func applyConstant(func1, x) {
|
||||
return func1(42, x);
|
||||
}
|
||||
|
||||
var result12 = applyConstant(constant, "anything");
|
||||
assert(result12 == 42, "Constant function");
|
||||
print("✓ Constant function works");
|
||||
|
||||
// ========================================
|
||||
// TEST 27: FUNCTION PASSING - FUNCTION COMPARISON
|
||||
// ========================================
|
||||
print("\n--- Test 27: Function Passing - Function Comparison ---");
|
||||
|
||||
func sameFunction(x) {
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
var func1 = sameFunction;
|
||||
var func2 = sameFunction;
|
||||
|
||||
func compareFunctions(f1, f2, x) {
|
||||
return f1(x) == f2(x);
|
||||
}
|
||||
|
||||
var result13 = compareFunctions(func1, func2, 5);
|
||||
assert(result13 == true, "Function comparison");
|
||||
print("✓ Function comparison works");
|
||||
|
||||
// ========================================
|
||||
// TEST 28: COMPREHENSIVE NUMBER TESTS
|
||||
// ========================================
|
||||
print("\n--- Test 28: Comprehensive Number Tests ---");
|
||||
|
||||
// Basic integers
|
||||
var small_int = 42;
|
||||
var medium_int = 1000000;
|
||||
var large_int = 999999999;
|
||||
assert(small_int == 42, "Small integer should be 42");
|
||||
assert(medium_int == 1000000, "Medium integer should be 1000000");
|
||||
assert(large_int == 999999999, "Large integer should be 999999999");
|
||||
|
||||
// Huge numbers (epoch timestamps)
|
||||
var huge1 = 2908616190934;
|
||||
var huge2 = 9223372036854775807;
|
||||
var huge3 = 1000000000000000;
|
||||
assert(huge1 == 2908616190934, "Huge1 should be 2908616190934");
|
||||
assert(huge2 == 9223372036854775807, "Huge2 should be 9223372036854775807");
|
||||
assert(huge3 == 1000000000000000, "Huge3 should be 1000000000000000");
|
||||
|
||||
// Basic decimals
|
||||
var pi = 3.14159;
|
||||
var half = 0.5;
|
||||
var quarter = 0.25;
|
||||
var one_point_five = 1.5;
|
||||
assert(pi == 3.14159, "Pi should be 3.14159");
|
||||
assert(half == 0.5, "Half should be 0.5");
|
||||
assert(quarter == 0.25, "Quarter should be 0.25");
|
||||
assert(one_point_five == 1.5, "One point five should be 1.5");
|
||||
|
||||
// Large decimals
|
||||
var large_decimal1 = 1234567.89;
|
||||
var large_decimal2 = 999999.999;
|
||||
var large_decimal3 = 1000000.0001;
|
||||
assert(large_decimal1 > 1234567.88, "Large decimal 1 should be greater than 1234567.88");
|
||||
assert(large_decimal1 < 1234567.90, "Large decimal 1 should be less than 1234567.90");
|
||||
assert(large_decimal2 > 999999.998, "Large decimal 2 should be greater than 999999.998");
|
||||
assert(large_decimal2 < 1000000.0, "Large decimal 2 should be less than 1000000.0");
|
||||
|
||||
// Large numbers in different formats
|
||||
var million = 1230000;
|
||||
var billion = 1230000000;
|
||||
var trillion = 1230000000000;
|
||||
assert(million == 1230000, "Million should be 1230000");
|
||||
assert(billion == 1230000000, "Billion should be 1230000000");
|
||||
assert(trillion == 1230000000000, "Trillion should be 1230000000000");
|
||||
|
||||
// Edge cases
|
||||
var zero_num = 0;
|
||||
var negative_num = -42;
|
||||
var negative_decimal = -3.14;
|
||||
var very_small = 0.000001;
|
||||
var very_large_decimal = 123456789.123456789;
|
||||
assert(zero_num == 0, "Zero should be 0");
|
||||
assert(negative_num == -42, "Negative should be -42");
|
||||
assert(negative_decimal == -3.14, "Negative decimal should be -3.14");
|
||||
assert(very_small > 0.0000009, "Very small should be greater than 0.0000009");
|
||||
assert(very_small < 0.0000011, "Very small should be less than 0.0000011");
|
||||
|
||||
// Mixed operations
|
||||
var result_num1 = 42 + 3.14;
|
||||
var result_num2 = 1000000 + 0.5;
|
||||
var result_num3 = 999999999 + 1.0;
|
||||
assert(result_num1 > 45.13, "42 + 3.14 should be greater than 45.13");
|
||||
assert(result_num1 < 45.15, "42 + 3.14 should be less than 45.15");
|
||||
assert(result_num2 == 1000000.5, "1000000 + 0.5 should be 1000000.5");
|
||||
assert(result_num3 == 1000000000, "999999999 + 1.0 should be 1000000000");
|
||||
|
||||
// Complex expressions
|
||||
var complex_num1 = 1000000 + 0.5 + 42;
|
||||
var complex_num2 = 3.14159 + 2.71828;
|
||||
var complex_num3 = 999999999 + 1 + 0.001;
|
||||
assert(complex_num1 == 1000042.5, "Complex1 should be 1000042.5");
|
||||
assert(complex_num2 > 5.85986, "Complex2 should be greater than 5.85986");
|
||||
assert(complex_num2 < 5.85988, "Complex2 should be less than 5.85988");
|
||||
assert(complex_num3 > 1000000000.0009, "Complex3 should be greater than 1000000000.0009");
|
||||
assert(complex_num3 < 1000000000.0011, "Complex3 should be less than 1000000000.0011");
|
||||
|
||||
// Variable assignments and printing
|
||||
var var_a = 123456789;
|
||||
var var_b = 987654321.123;
|
||||
var var_c = 0.000001;
|
||||
var var_d = 999999999999;
|
||||
assert(var_a == 123456789, "Variable a should be 123456789");
|
||||
assert(var_b > 987654321.122, "Variable b should be greater than 987654321.122");
|
||||
assert(var_b < 987654321.124, "Variable b should be less than 987654321.124");
|
||||
assert(var_c > 0.0000009, "Variable c should be greater than 0.0000009");
|
||||
assert(var_c < 0.0000011, "Variable c should be less than 0.0000011");
|
||||
assert(var_d == 999999999999, "Variable d should be 999999999999");
|
||||
|
||||
// Binary numbers
|
||||
var bin1 = 0b1010;
|
||||
var bin2 = 0b11111111;
|
||||
var bin3 = 0b1000000000000000;
|
||||
assert(bin1 == 10, "Binary 1010 should be 10");
|
||||
assert(bin2 == 255, "Binary 11111111 should be 255");
|
||||
assert(bin3 == 32768, "Binary 1000000000000000 should be 32768");
|
||||
|
||||
// Hexadecimal numbers
|
||||
var hex1 = 0xFF;
|
||||
var hex2 = 0xFFFF;
|
||||
var hex3 = 0xFFFFFFFF;
|
||||
assert(hex1 == 255, "Hex FF should be 255");
|
||||
assert(hex2 == 65535, "Hex FFFF should be 65535");
|
||||
assert(hex3 == 4294967295, "Hex FFFFFFFF should be 4294967295");
|
||||
|
||||
// Arithmetic operations
|
||||
var add_num = 5 + 3;
|
||||
var sub_num = 10 - 4;
|
||||
var mul_num = 6 * 7;
|
||||
var div_num = 15 / 3;
|
||||
assert(add_num == 8, "5 + 3 should be 8");
|
||||
assert(sub_num == 6, "10 - 4 should be 6");
|
||||
assert(mul_num == 42, "6 * 7 should be 42");
|
||||
assert(div_num == 5, "15 / 3 should be 5");
|
||||
|
||||
// Comparison operations
|
||||
var eq1 = 5 == 5;
|
||||
var eq2 = 5 == 6;
|
||||
var ne1 = 5 != 6;
|
||||
var ne2 = 5 != 5;
|
||||
var lt1 = 3 < 5;
|
||||
var lt2 = 5 < 3;
|
||||
var gt1 = 7 > 4;
|
||||
var gt2 = 2 > 8;
|
||||
assert(eq1 == true, "5 == 5 should be true");
|
||||
assert(eq2 == false, "5 == 6 should be false");
|
||||
assert(ne1 == true, "5 != 6 should be true");
|
||||
assert(ne2 == false, "5 != 5 should be false");
|
||||
assert(lt1 == true, "3 < 5 should be true");
|
||||
assert(lt2 == false, "5 < 3 should be false");
|
||||
assert(gt1 == true, "7 > 4 should be true");
|
||||
assert(gt2 == false, "2 > 8 should be false");
|
||||
|
||||
print("✓ Comprehensive number tests working");
|
||||
|
||||
// ========================================
|
||||
// TEST 29: TIME FUNCTION
|
||||
// ========================================
|
||||
print("\n--- Test 29: Time Function ---");
|
||||
|
||||
var start_time = time();
|
||||
var end_time = time();
|
||||
var duration = end_time - start_time;
|
||||
assert(start_time > 0, "Start time should be positive");
|
||||
assert(end_time > start_time, "End time should be greater than start time");
|
||||
assert(duration >= 0, "Duration should be non-negative");
|
||||
|
||||
print("✓ Time function working");
|
||||
|
||||
// ========================================
|
||||
// TEST 30: BOOLEAN + STRING CONCATENATION
|
||||
// ========================================
|
||||
print("\n--- Test 30: Boolean + String Concatenation ---");
|
||||
|
||||
var true_val = true;
|
||||
var false_val = false;
|
||||
var eq_test = 5 == 5;
|
||||
var ne_test = 5 != 6;
|
||||
var lt_test = 3 < 5;
|
||||
var gt_test = 7 > 4;
|
||||
|
||||
// Test boolean + string
|
||||
assert(true + " is true" == "true is true", "Boolean + String concatenation");
|
||||
assert(false + " is false" == "false is false", "Boolean + String concatenation");
|
||||
|
||||
// Test string + boolean
|
||||
assert("The answer is: " + true == "The answer is: true", "String + Boolean concatenation");
|
||||
assert("The opposite is: " + false == "The opposite is: false", "String + Boolean concatenation");
|
||||
|
||||
// Test comparison results
|
||||
assert(eq_test == true, "5 == 5 should be true");
|
||||
assert(ne_test == true, "5 != 6 should be true");
|
||||
assert(lt_test == true, "3 < 5 should be true");
|
||||
assert(gt_test == true, "7 > 4 should be true");
|
||||
|
||||
print("✓ Boolean + String concatenation working");
|
||||
|
||||
// ========================================
|
||||
// FINAL SUMMARY
|
||||
// ========================================
|
||||
print("\n=== COMPREHENSIVE TEST SUMMARY ===");
|
||||
print("All core language features tested:");
|
||||
print("- Basic data types (strings, numbers, booleans)");
|
||||
print("- Arithmetic operations");
|
||||
print("- String operations");
|
||||
print("- String + Number concatenation (bidirectional)");
|
||||
print("- String multiplication");
|
||||
print("- Significant digits formatting");
|
||||
print("- Escape sequences (\\n, \\t, \\\", \\\\)");
|
||||
print("- Comparison operators");
|
||||
print("- Variable assignment");
|
||||
print("- Functions and return statements");
|
||||
print("- Nested function calls");
|
||||
print("- Variable scoping");
|
||||
print("- Closures");
|
||||
print("- Complex expressions");
|
||||
print("- Edge cases");
|
||||
print("- Print function");
|
||||
print("- Assert function");
|
||||
print("- Error handling");
|
||||
print("- Function passing (13 comprehensive tests)");
|
||||
print(" * Basic function passing");
|
||||
print(" * Multiple function parameters");
|
||||
print(" * Nested function calls");
|
||||
print(" * Function composition");
|
||||
print(" * Callback patterns");
|
||||
print(" * Direct function calling");
|
||||
print(" * Function storage and retrieval");
|
||||
print(" * Multiple function applications");
|
||||
print(" * Edge cases (no params, many params)");
|
||||
print(" * Function identity");
|
||||
print(" * Constant functions");
|
||||
print(" * Function comparison");
|
||||
print("- Comprehensive number system");
|
||||
print(" * Huge numbers (epoch timestamps)");
|
||||
print(" * Decimal precision handling");
|
||||
print(" * Binary and hexadecimal numbers");
|
||||
print(" * Edge cases and complex expressions");
|
||||
print("- Time function (microsecond precision)");
|
||||
print("- Boolean + String concatenation");
|
||||
print("- Underscore support in variable names");
|
||||
|
||||
print("\n🎉 ALL TESTS PASSED! 🎉");
|
||||
print("Bob language is working correctly!");
|
||||
print("Ready for next phase: Control Flow (if statements, while loops)");
|
||||
28
test_time_function.bob
Normal file
28
test_time_function.bob
Normal file
@ -0,0 +1,28 @@
|
||||
// Test the time function
|
||||
print("Testing time function:");
|
||||
|
||||
var time1 = time();
|
||||
print("Time 1: " + time1);
|
||||
|
||||
var time2 = time();
|
||||
print("Time 2: " + time2);
|
||||
|
||||
var time3 = time();
|
||||
print("Time 3: " + time3);
|
||||
|
||||
var diff1 = time2 - time1;
|
||||
var diff2 = time3 - time2;
|
||||
|
||||
print("Difference 1-2: " + diff1 + " microseconds");
|
||||
print("Difference 2-3: " + diff2 + " microseconds");
|
||||
|
||||
// Test with some work in between
|
||||
var start = time();
|
||||
var sum = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
|
||||
var end = time();
|
||||
var duration = end - start;
|
||||
|
||||
print("Work duration: " + duration + " microseconds");
|
||||
print("Sum: " + sum);
|
||||
|
||||
print("Time function analysis complete!");
|
||||
51
testthing
51
testthing
@ -1,51 +0,0 @@
|
||||
template <typename T>
|
||||
struct BinaryExpr : Expr<T>, ExprVisitor<T>
|
||||
{
|
||||
const Expr<T> left;
|
||||
const Token oper;
|
||||
const Expr<T> right;
|
||||
|
||||
BinaryExpr(Expr<T> left, Token oper, Expr<T> right) : left(left), oper(oper), right(right)
|
||||
{
|
||||
}
|
||||
T accept(ExprVisitor<T> visitor){
|
||||
return visitor.visitBinaryExpr(this);
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct GroupingExpr : Expr<T>, ExprVisitor<T>
|
||||
{
|
||||
const Expr<T> expression;
|
||||
|
||||
GroupingExpr(Expr<T> expression) : expression(expression)
|
||||
{
|
||||
}
|
||||
T accept(ExprVisitor<T> visitor){
|
||||
return visitor.visitGroupingExpr(this);
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct LiteralExpr : Expr<T>, ExprVisitor<T>
|
||||
{
|
||||
const std::string value;
|
||||
|
||||
LiteralExpr(std::string value) : value(value)
|
||||
{
|
||||
}
|
||||
T accept(ExprVisitor<T> visitor){
|
||||
return visitor.visitLiteralExpr(this);
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct UnaryExpr : Expr<T>, ExprVisitor<T>
|
||||
{
|
||||
const Token oper;
|
||||
const Expr<T> right;
|
||||
|
||||
UnaryExpr(Token oper, Expr<T> right) : oper(oper), right(right)
|
||||
{
|
||||
}
|
||||
T accept(ExprVisitor<T> visitor){
|
||||
return visitor.visitUnaryExpr(this);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user