A comprehensive, object-oriented programming language built from scratch in Java, featuring a complete lexer, parser, evaluator, and interactive REPL environment.
- 🌟 Overview
- 🏗️ Architecture
- 🎯 Language Features
- 🔧 Getting Started
- 📖 Language Reference
- 🎮 Interactive REPL
- 🏛️ Object-Oriented Programming
- 🔍 Advanced Features
- 🛠️ Development
- 📚 Examples
This project implements a complete programming language from first principles, demonstrating fundamental computer science concepts including:
- Lexical Analysis: Converting source code into meaningful tokens
- Syntax Parsing: Building Abstract Syntax Trees (AST) using Pratt parsing
- Semantic Analysis: Type checking and scope resolution
- Code Evaluation: Tree-walking interpreter with environment-based scoping
- Object-Oriented Programming: Classes, inheritance, polymorphism
- Interactive Development: Feature-rich REPL with debugging capabilities
Understanding how programming languages work requires building one yourself. This implementation covers:
- Tokenization: How source code becomes structured data
- Parsing: How syntax rules create tree structures
- Evaluation: How abstract trees become executable programs
- Type Systems: How different data types interact
- Scoping: How variables are resolved in different contexts
- Object Models: How classes and inheritance work under the hood
┌─────────────────────────────────────────────────────────────┐ │ Source Code │ └─────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ LEXER │ │ • Tokenization • Comments • String/Number Parsing │ │ • Keywords • Operators • Position Tracking │ └─────────────────────┬───────────────────────────────────────┘ │ Token Stream ▼ ┌─────────────────────────────────────────────────────────────┐ │ PARSER │ │ • Pratt Parsing • Precedence • AST Construction │ │ • Error Recovery • Expressions • Statement Parsing │ └─────────────────────┬───────────────────────────────────────┘ │ Abstract Syntax Tree ▼ ┌─────────────────────────────────────────────────────────────┐ │ EVALUATOR │ │ • Tree Walking • Environments • Type System │ │ • Built-ins • Error Handling• Stack Traces │ └─────────────────────┬───────────────────────────────────────┘ │ Program Result ▼ ┌─────────────────────────────────────────────────────────────┐ │ REPL │ │ • Interactive • Debugging • Command History │ │ • Syntax Colors • Help System • Error Display │ └─────────────────────────────────────────────────────────────┘ - Visitor Pattern: AST traversal and operations
- Registry Pattern: Modular parser and evaluator components
- Strategy Pattern: Different parsing strategies for expressions/statements
- Factory Pattern: Object creation and type conversion
- Observer Pattern: Debug event system
// Numbers (integers and floats) let age = 25; // Integer let price = 19.99; // Float let scientific = 1e6; // Scientific notation // Strings with escape sequences let name = "Alice"; let message = "Hello\nWorld"; // Booleans let isActive = true; let isComplete = false; // Arrays (heterogeneous) let numbers = [1, 2, 3, 4, 5]; let mixed = [1, "hello", true, [1, 2]]; // Hash maps (objects) let person = { "name": "Bob", "age": 30, "active": true }; // Null values let empty = null;// Mutable variables let counter = 0; counter = counter + 1; // ✅ Allowed // Immutable constants const PI = 3.14159; PI = 3.14; // ❌ Error: Cannot reassign constant // Block scoping { let local = "inside block"; const BLOCK_CONST = 42; } // local is not accessible here// Function definition fn greet(name) { return "Hello, " + name + "!"; } // Function calls let message = greet("World"); // Functions are first-class values let operation = fn(x, y) { return x + y; }; let result = operation(5, 3); // Closures and lexical scoping fn createCounter() { let count = 0; return fn() { count = count + 1; return count; }; } let counter = createCounter(); counter(); // Returns 1 counter(); // Returns 2// Conditional statements if (age >= 18) { print("Adult"); } elif (age >= 13) { print("Teenager"); } else { print("Child"); } // While loops let i = 0; while (i < 5) { print("Count:", i); i = i + 1; } // For loops for (let j = 0; j < 10; j = j + 1) { if (j == 5) { break; } if (j % 2 == 0) { continue; } print("Odd number:", j); }let name = "Alice"; let age = 25; // F-string with embedded expressions let intro = f"My name is {name} and I'm {age} years old"; let calculation = f"2 + 3 = {2 + 3}"; let nested = f"Hello {getUser().name}!";// Base class definition class Animal { constructor(name, species) { this.name = name; this.species = species; this.energy = 100; } speak() { return f"{this.name} makes a sound"; } move(distance) { this.energy = this.energy - distance; return f"{this.name} moved {distance} units"; } } // Inheritance with method overriding class Dog extends Animal { constructor(name, breed) { super(name, "Canine"); // Call parent constructor this.breed = breed; } speak() { return f"{this.name} barks loudly!"; } fetch() { return f"{this.name} fetches the ball"; } } // Creating instances let buddy = new Dog("Buddy", "Golden Retriever"); print(buddy.speak()); // "Buddy barks loudly!" print(buddy.move(10)); // "Buddy moved 10 units" print(buddy.fetch()); // "Buddy fetches the ball"class Vehicle { constructor(brand, model) { this.brand = brand; this.model = model; this.speed = 0; } accelerate(amount) { this.speed = this.speed + amount; return this.getStatus(); } getStatus() { return f"{this.brand} {this.model} traveling at {this.speed} mph"; } } class Car extends Vehicle { constructor(brand, model, doors) { super(brand, model); // Initialize parent this.doors = doors; } // Override parent method accelerate(amount) { // Call parent method and add car-specific behavior super.accelerate(amount); if (this.speed > 80) { return this.getStatus() + " - Warning: High speed!"; } return this.getStatus(); } honk() { return f"{this.brand} {this.model} goes BEEP BEEP!"; } } // Method chaining and polymorphism let myCar = new Car("Toyota", "Camry", 4); print(myCar.accelerate(30)); // Uses overridden method print(myCar.honk()); // Car-specific methodclass Counter { constructor(start) { this.value = start; // 'this' refers to current instance } increment() { this.value = this.value + 1; return this; // Return this for chaining } getValue() { return this.value; } } class AdvancedCounter extends Counter { constructor(start, step) { super(start); // Call parent constructor this.step = step; } increment() { this.value = this.value + this.step; return super.getValue(); // Call parent method } }// Array operations let arr = [1, 2, 3, 4, 5]; print(len(arr)); // 5 print(first(arr)); // 1 print(last(arr)); // 5 print(rest(arr)); // [2, 3, 4, 5] // Hash operations let person = {"name": "Alice", "age": 30}; print(keys(person)); // ["name", "age"] print(values(person)); // ["Alice", 30] // String operations print(len("hello")); // 5 // Type checking print(type(42)); // "INTEGER" print(type(3.14)); // "FLOAT" print(type("hello")); // "STRING" // I/O operations print("Hello", "World", 123); // Multiple arguments// Array indexing and assignment let numbers = [10, 20, 30]; numbers[1] = 25; // Modify element print(numbers[1]); // 25 // Hash key access and assignment let config = {"debug": true, "port": 8080}; config["timeout"] = 30; // Add new key config.debug = false; // Property-style access print(config["port"]); // 8080let x = 10; x += 5; // Equivalent to: x = x + 5 x -= 3; // Equivalent to: x = x - 3 x *= 2; // Equivalent to: x = x * 2 x /= 4; // Equivalent to: x = x / 4 x %= 3; // Equivalent to: x = x % 3# Single-line comment /* * Multi-line comment * Can span multiple lines */ let value = 42; # End-of-line comment /* * Nested comments are supported * /* This is nested */ * Still inside the outer comment */- 🎨 Syntax Highlighting: Color-coded output for different data types
- 📜 Command History: Navigate through previous commands
- 🔍 Debug Information: Stack traces and error context
- 📚 Built-in Help: Comprehensive help system
- ⚡ Live Evaluation: Immediate feedback on expressions
:help # Show all available commands :examples # Display language examples :builtins # List all built-in functions :env # Show current variables :history # Display command history :clear # Clear the screen :reset # Reset environment :exit # Exit REPL🚀 Welcome to the Interactive Language REPL! 🚀 [1] ❯ let greeting = "Hello, World!" ⟹ "Hello, World!" [2] ❯ class Person { constructor(name) { this.name = name; } greet() { return f"Hi, I'm {this.name}"; } } ⟹ class Person { constructor, 1 methods } [3] ❯ let alice = new Person("Alice") ⟹ instance of Person {"name": "Alice"} [4] ❯ alice.greet() ⟹ "Hi, I'm Alice" - Java 24+ with preview features enabled
- Gradle 8.8+
# Clone the repository git clone <repository-url> cd lang # Build the project ./gradlew build # Run the REPL ./gradlew run # Run tests ./gradlew test# Run a source file echo 'print("Hello from file!")' > hello.lang java -jar build/libs/lang.jar hello.lang # Interactive mode java -jar build/libs/lang.jarfn fibonacci(n) { if (n <= 1) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); } for (let i = 0; i < 10; i = i + 1) { print(f"fib({i}) = {fibonacci(i)}"); }let students = [ {"name": "Alice", "grade": 85}, {"name": "Bob", "grade": 92}, {"name": "Charlie", "grade": 78} ]; let total = 0; let count = len(students); for (let i = 0; i < count; i = i + 1) { let student = students[i]; total = total + student["grade"]; print(f"{student['name']}: {student['grade']}"); } let average = total / count; print(f"Class average: {average}");class BankAccount { constructor(owner, initialBalance) { this.owner = owner; this.balance = initialBalance; this.transactions = []; } deposit(amount) { if (amount > 0) { this.balance = this.balance + amount; this.addTransaction("deposit", amount); return f"Deposited ${amount}. New balance: ${this.balance}"; } return "Invalid deposit amount"; } withdraw(amount) { if (amount > 0 && amount <= this.balance) { this.balance = this.balance - amount; this.addTransaction("withdrawal", amount); return f"Withdrew ${amount}. New balance: ${this.balance}"; } return "Invalid withdrawal amount or insufficient funds"; } addTransaction(type, amount) { let transaction = { "type": type, "amount": amount, "balance": this.balance }; this.transactions = this.transactions + [transaction]; } getStatement() { print(f"Account Statement for {this.owner}"); print(f"Current Balance: ${this.balance}"); print("Recent Transactions:"); let txCount = len(this.transactions); for (let i = 0; i < txCount; i = i + 1) { let tx = this.transactions[i]; print(f" {tx['type']}: ${tx['amount']} (Balance: ${tx['balance']})"); } } } class SavingsAccount extends BankAccount { constructor(owner, initialBalance, interestRate) { super(owner, initialBalance); this.interestRate = interestRate; } addInterest() { let interest = this.balance * this.interestRate; this.balance = this.balance + interest; this.addTransaction("interest", interest); return f"Added ${interest} in interest. New balance: ${this.balance}"; } } # Usage let checking = new BankAccount("Alice", 1000); print(checking.deposit(500)); print(checking.withdraw(200)); let savings = new SavingsAccount("Bob", 5000, 0.05); print(savings.addInterest()); savings.getStatement();The lexer converts raw source code into a stream of tokens using finite state automata:
// Key components: - Character stream processing - Keyword recognition - Operator tokenization - String/number parsing with escape sequences - Position tracking for error reporting - Comment handling (single and multi-line)Uses Pratt parsing (Top-Down Operator Precedence) for expression parsing:
// Parsing strategy: - Recursive descent for statements - Pratt parsing for expressions with precedence - Error recovery mechanisms - AST node construction - Registry pattern for extensible parsersTree-walking interpreter with environment-based scoping:
// Evaluation features: - Visitor pattern for AST traversal - Lexical scoping with environment chains - Dynamic dispatch for method calls - Stack trace generation - Built-in function registry - Type system with automatic promotion- Immutable AST Nodes: Thread-safe and easier to reason about
- Environment Chains: Efficient lexical scoping implementation
- Type Promotion: Automatic numeric type conversion (int → float)
- Error Recovery: Parser continues after errors to find multiple issues
- Position Tracking: Every token tracks source location for debugging
# Run all tests ./gradlew test # Run specific test categories ./gradlew test --tests "*LexerTest*" ./gradlew test --tests "*ParserTest*" ./gradlew test --tests "*EvaluatorTest*" # Generate coverage report ./gradlew jacocoTestReport- Stack Traces: Full call stack with source positions
- Error Context: Source code snippets around errors
- REPL Debug Commands: Environment inspection and debugging
- Lexer Debugging: Token stream visualization
- Parser Debug Mode: AST structure visualization
This project demonstrates fundamental programming language implementation concepts. Areas for extension:
- Standard Library: More built-in functions and modules
- Optimization: Bytecode compilation, JIT compilation
- Type System: Static typing, type inference
- Memory Management: Garbage collection, reference counting
- Concurrency: Threads, async/await, actors
- Package System: Modules, imports, namespaces
- Dragon Book: Compilers: Principles, Techniques, and Tools
- Crafting Interpreters: Robert Nystrom's excellent guide
- Pratt Parsing: Top-Down Operator Precedence parsing
- Tree Walking: Simple interpretation technique
- Environment Chains: Lexical scoping implementation
This implementation serves as a comprehensive example of programming language design and implementation, covering everything from lexical analysis to object-oriented programming features.