The DOL REPL (Read-Eval-Print Loop) provides an interactive environment for exploring and testing DOL code. This tutorial covers everything from basic usage to advanced features.
- Getting Started
- Basic Expressions
- Defining Functions
- Working with Genes
- Type System
- Advanced Usage
- Under the Hood
Before using the DOL REPL, ensure you have:
- Rust 1.70+ installed
- The
metadolcrate with thewasmfeature enabled
Add DOL to your project's Cargo.toml:
[dependencies]
metadol = { version = "0.8", features = ["wasm"] }use metadol::repl::{SpiritRepl, EvalResult};
fn main() {
let mut repl = SpiritRepl::new();
// Evaluate DOL code
match repl.eval("42") {
Ok(EvalResult::Expression { value, .. }) => {
println!("Result: {}", value);
}
Ok(result) => println!("{:?}", result),
Err(e) => eprintln!("Error: {}", e),
}
}The REPL can evaluate expressions and return their computed values.
>>> 42
=> 42
>>> -17
=> -17
>>> 0
=> 0
>>> 3.14159
=> 3.14159
>>> -273.15
=> -273.15
>>> 1.0
=> 1
DOL supports standard arithmetic operations with proper precedence:
>>> 10 + 20
=> 30
>>> 100 - 37
=> 63
>>> 6 * 7
=> 42
>>> 100 / 4
=> 25
>>> 10 + 20 * 2
=> 50 // Multiplication has higher precedence
>>> (10 + 20) * 2
=> 60 // Parentheses override precedence
>>> 1.5 + 2.5
=> 4
>>> 3.14159 * 2.0
=> 6.28318
>>> 10.0 / 3.0
=> 3.333333333333333
The REPL maintains state across evaluations, allowing you to define and use functions.
Use the pub fun keyword to define functions:
>>> pub fun square(x: i64) -> i64 { x * x }
Defined function 'square'
>>> square(7)
=> 49
>>> square(12)
=> 144
>>> pub fun add(a: i64, b: i64) -> i64 { a + b }
Defined function 'add'
>>> add(10, 20)
=> 30
>>> pub fun multiply(x: i64, y: i64) -> i64 { x * y }
Defined function 'multiply'
>>> multiply(6, 7)
=> 42
>>> pub fun area(radius: f64) -> f64 { 3.14159 * radius * radius }
Defined function 'area'
>>> area(10.0)
=> 314.159
>>> pub fun circumference(radius: f64) -> f64 { 2.0 * 3.14159 * radius }
Defined function 'circumference'
>>> circumference(5.0)
=> 31.4159
>>> pub fun cube(x: i64) -> i64 { x * square(x) }
Defined function 'cube'
>>> cube(3)
=> 27
>>> cube(4)
=> 64
Genes are DOL's primary data structure, similar to structs in other languages.
>>> gen Point { has x: i64 has y: i64 }
Defined gene 'Point'
>>> pub fun getX() -> i64 {
... let p = Point { x: 42, y: 100 }
... p.x
... }
Defined function 'getX'
>>> getX()
=> 42
>>> gen Vector2D { has dx: f64 has dy: f64 }
Defined gene 'Vector2D'
>>> pub fun magnitude() -> f64 {
... let v = Vector2D { dx: 3.0, dy: 4.0 }
... // Would be sqrt(dx*dx + dy*dy), simplified here
... v.dx + v.dy
... }
Defined function 'magnitude'
>>> magnitude()
=> 7
>>> gen Rectangle {
... has width: i64
... has height: i64
... }
Defined gene 'Rectangle'
>>> pub fun calculateArea() -> i64 {
... let rect = Rectangle { width: 10, height: 5 }
... rect.width * rect.height
... }
Defined function 'calculateArea'
>>> calculateArea()
=> 50
The REPL performs automatic type inference for expressions.
| Type | Description | Example |
|---|---|---|
i64 |
64-bit signed integer | 42, -17 |
f64 |
64-bit floating point | 3.14, -273.15 |
bool |
Boolean value | true, false |
- Integer literals without decimal points are typed as
i64 - Float literals with decimal points are typed as
f64 - Arithmetic operations preserve the type of their operands
- Mixed operations follow standard promotion rules
>>> 42 // Inferred as i64
=> 42
>>> 3.14 // Inferred as f64
=> 3.14
>>> 10 + 20 // i64 + i64 = i64
=> 30
>>> 1.5 + 2.5 // f64 + f64 = f64
=> 4
All definitions persist within a REPL session:
let mut repl = SpiritRepl::new();
// Define a function
repl.eval("pub fun double(x: i64) -> i64 { x * 2 }").unwrap();
// Use it multiple times
repl.eval("double(5)"); // => 10
repl.eval("double(100)"); // => 200
// Define another function that uses the first
repl.eval("pub fun quadruple(x: i64) -> i64 { double(double(x)) }").unwrap();
repl.eval("quadruple(3)"); // => 12The REPL returns different result types:
pub enum EvalResult {
/// An expression that was evaluated
Expression {
value: String,
expr_type: String,
},
/// A definition (function, gene, trait, etc.)
Defined {
name: String,
kind: String,
},
/// An import statement
Imported {
module: String,
},
/// Empty input or comment-only
Empty,
}match repl.eval(input) {
Ok(EvalResult::Expression { value, expr_type }) => {
println!("=> {} : {}", value, expr_type);
}
Ok(EvalResult::Defined { name, kind }) => {
println!("Defined {} '{}'", kind, name);
}
Ok(EvalResult::Imported { module }) => {
println!("Imported module '{}'", module);
}
Ok(EvalResult::Empty) => {
// No output needed
}
Err(e) => {
eprintln!("Error: {}", e);
}
}The DOL REPL compiles and executes code through a sophisticated pipeline:
Input → Parse → Type Inference → WASM Compilation → Execution → Result
The input is parsed using DOL's recursive descent parser, producing an AST.
For expressions, the REPL infers the return type:
- Scans for float literals (
.in numbers) - Checks for boolean keywords (
true,false) - Defaults to
i64for integer expressions
Expressions are wrapped in a function for compilation:
// Input: 42
// Generated code:
pub fun dolReplEval() -> i64 {
42
}
The compiled WASM is executed via wasmtime:
- The module is instantiated
- The wrapper function is called
- Results are extracted and formatted
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Input │────▶│ Parser │────▶│ AST │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────┐ ┌──────▼──────┐
│ Result │◀────│ WASM │
└─────────────┘ │ Compiler │
▲ └──────┬──────┘
│ │
┌──────┴──────┐ ┌──────▼──────┐
│ Formatter │◀────│ wasmtime │
└─────────────┘ └─────────────┘
Current REPL limitations:
- No interactive debugging - Errors are reported but cannot be stepped through
- Expression-only evaluation - Statements require function wrapping
- WASM feature required - Expression evaluation needs
--features wasm - No top-level bindings - Variables must be defined within functions
Planned enhancements:
- Top-level
letbindings - Interactive debugging
- Command history
- Tab completion
- Multi-line editing
- REPL commands (
:help,:clear,:type)
Here's a complete REPL session demonstrating various features:
>>> // Basic arithmetic
>>> 10 + 20
=> 30
>>> 3.14159 * 2.0
=> 6.28318
>>> // Define a gene
>>> gen Circle { has radius: f64 }
Defined gene 'Circle'
>>> // Define helper functions
>>> pub fun square_f(x: f64) -> f64 { x * x }
Defined function 'square_f'
>>> pub fun pi() -> f64 { 3.14159 }
Defined function 'pi'
>>> // Calculate circle area
>>> pub fun circleArea(r: f64) -> f64 {
... pi() * square_f(r)
... }
Defined function 'circleArea'
>>> circleArea(5.0)
=> 78.53975
>>> // Use gene in a function
>>> pub fun getCircleArea() -> f64 {
... let c = Circle { radius: 10.0 }
... circleArea(c.radius)
... }
Defined function 'getCircleArea'
>>> getCircleArea()
=> 314.159
>>> // Integer operations
>>> pub fun factorial(n: i64) -> i64 {
... // Simplified - just multiply for demo
... n * (n - 1) * (n - 2)
... }
Defined function 'factorial'
>>> factorial(5)
=> 60
impl SpiritRepl {
/// Create a new REPL instance
pub fn new() -> Self;
/// Evaluate DOL code
pub fn eval(&mut self, input: &str) -> Result<EvalResult, EvalError>;
/// Get all defined functions
pub fn functions(&self) -> Vec<&str>;
/// Get all defined genes
pub fn genes(&self) -> Vec<&str>;
/// Clear all definitions
pub fn clear(&mut self);
}#[derive(Debug)]
pub enum EvalError {
/// Parse error with location
ParseError(String),
/// Compilation error
CompileError(String),
/// Runtime execution error
RuntimeError(String),
/// Type inference error
TypeError(String),
}- DOL Language Reference
- DOL CLI Reference
- DOL-to-WASM Compilation
- v0.8.0 Release Notes - "Clarity" syntax
Tutorial last updated: January 2026 DOL Version: 0.8.x