JLOX is a lightweight, dynamically-typed programming language based on Lox, designed with simplicity and flexibility in mind. It is implemented using recursive descent parsing and runs on the Java Virtual Machine (JVM). This document details the grammar, evaluation model, and execution workflow of JLOX.
- Dynamic Typing: Variables can hold any type at runtime.
- Garbage Collection: Managed by the JVM.
- Lexical Scoping: Variables are scoped within blocks.
- Functions as First-Class Citizens: Functions can be assigned to variables, passed as arguments, and returned.
- Interpreter Execution: Runs code line by line without prior compilation.
- Native Interoperability: Java functions can be called using Foreign Function Interface (FFI).
- Nox Mechanism: A unique value influencing control flow in unconventional ways.
JLOX follows a structured execution model that includes lexical analysis, parsing, semantic analysis, and interpretation.
The Scanner converts source code into tokens.
var x = 10;
Produces tokens:
VAR IDENTIFIER( x ) EQUAL NUMBER( 10 ) SEMICOLON
The Parser constructs an AST from tokens.
Example AST for var x = 10;
:
VarDecl(
name = "x",
initializer = Literal(10)
)
- Resolves variable scope
- Validates function calls
The Interpreter walks the AST and executes code.
print x;
Would produce output:
10
var x = 10;
var name = "Alice";
if (x > 5) {
print "Greater than 5";
} else {
print "Not greater than 5";
}
while (x < 20) {
print x;
x = x + 1;
}
fun greet(name) {
print "Hello, " + name;
}
greet("Alice");
if (nox) {
print "This runs";
} else {
print "This also runs";
}
JLOX's parser processes the source code from the top-level program rule down to primary expressions by recursively calling functions corresponding to each production rule. This method ensures clarity and modularity in parsing.
The Abstract Syntax Tree (AST) generated by the parser is traversed in post-order, meaning child nodes are evaluated before their parent nodes. This guarantees correct evaluation of nested expressions and operations.
If a syntax error is encountered, JLOX's parser enters panic mode, skipping tokens until it reaches a synchronization point (such as a semicolon). This prevents the entire parsing process from halting due to a single error.
JLOX utilizes the Visitor Pattern to decouple the AST structure from operations performed on its nodes. This makes it easy to extend and maintain language features without modifying the core AST structure.
The nox literal propagates through expressions, affecting control flow. If an operation involves nox, the result is typically nox, allowing for controlled execution paths in conditional statements.
Example:
var x = 5 / nox; // This will result in 'nox' being propagated, affecting subsequent evaluation.
├── src/ # Source code
│ ├── lox/ # Main interpreter source files
│ │ ├── AstPrinter.java # Prints AST representation of expressions
│ │ ├── Environment.java # Stores variables and their scopes
│ │ ├── Expr.java # Defines expression nodes for AST
│ │ ├── Interpreter.java # Evaluates AST; handles dynamic typing, nox propagation
│ │ ├── Lox.java # Entry point; bootstraps REPL and file execution
│ │ ├── LoxCallable.java # Interface for callable objects like functions
│ │ ├── LoxFunction.java # Implements LoxCallable for function execution
│ │ ├── Nox.java # Handles special nox literal behavior
│ │ ├── Parser.java # Recursive descent parser that generates AST
│ │ ├── Return.java # Represents function return statements
│ │ ├── RuntimeError.java # Handles runtime errors in interpreter
│ │ ├── Scanner.java # Lexical analyzer that tokenizes source code
│ │ ├── Stmt.java # Defines statement nodes for AST
│ │ ├── Token.java # Represents individual tokens produced by the scanner
│ │ └── TokenType.java # Enum defining all token types
│ ├── tool/ # Tools for Jlox
│ └── └── GenerateAst.java # Generates AST classes from grammar definitions
│
├── grammar/ # Jlox grammar definitions
│ └── grammar.txt # Contains full recursive descent grammar
│
└── tests/ # Test files
├── all_test.jlox # general testing
├── issue_test.jlox # Tests for known issues
├── nox_test.jlox # Tests specific to nox behavior
└── test.jlox # simplified testing
JLOX's syntax is defined using a recursive descent grammar, expressed in an EBNF-like notation:
program → declaration* EOF ;
declaration → varDecl | statement ;
varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
statement → exprStmt | printStmt | block | ifStmt | whileStmt | returnStmt ;
exprStmt → expression ";" ;
printStmt → "print" expression ";" ;
block → "{" declaration* "}" ;
ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
whileStmt → "while" "(" expression ")" statement ;
returnStmt → "return" expression? ";" ;
expression → assignment ;
assignment → IDENTIFIER "=" assignment | logic_or ;
logic_or → logic_and ( "or" logic_and )* ;
logic_and → equality ( "and" equality )* ;
equality → comparison ( ( "!=" | "==" ) comparison )* ;
comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
term → factor ( ( "-" | "+" ) factor )* ;
factor → unary ( ( "/" | "*" ) unary )* ;
unary → ( "!" | "-" ) unary | primary ;
primary → NUMBER | STRING | "true" | "false" | "nox" | "(" expression ")" ;
JLOX supports FFI, enabling Java interoperability.
- Calling Java Methods: JLOX can invoke Java methods.
- Binding Native Functions: Java functions are wrapped in
LoxCallable
. - JVM Interoperability: JLOX functions interact with Java at runtime.
class NativeTimeFunction implements LoxCallable {
@Override
public Object call(Interpreter interpreter, List<Object> arguments) {
return (double) System.currentTimeMillis() / 1000.0;
}
@Override
public int arity() { return 0; }
}
environment.define("clock", new NativeTimeFunction());
print clock(); // Prints current time in seconds
- Java Development Kit (JDK) 8 or higher
- Makefile
git clone https://github.com/Ahmad-Faraj/JLOX-Interpreter.git
cd jlox
javac -d bin src/lox/*.java
java -cp bin lox.Lox tests/test.jlox
java -cp bin lox.Lox tests/all_test.jlox
java -cp bin lox.Lox tests/issue_test.jlox
java -cp bin lox.Lox tests/nox_test.jlox
JLOX uses a tool to generate AST classes from grammar definitions.
javac -d bin src/tool/GenerateAst.java
java -cp bin tool.GenerateAst src/lox
make
make run
make tool
make clean
Contributions are welcome! Feel free to fork the repository and submit pull requests.
If you find JLOX useful, please consider starring the repository!
- Crafting Interpreters: Inspired by Crafting Interpreters by Robert Nystrom.