Quilt is a hobby project of mine. I designed it to be a simple, with a syntax similar to rust and python. I needed a language that I could use within my own projects that could be easily embedded and extended. I also wanted to learn more about how to build a programming language.
crafting interpreters was a great resource during the development of the backend.
- Dynamically typed
- Bytecode interpreter
- First class functions
- Closures
- Extendable native rust builtins
- Relatively fast (about
1.5x
-2.5x
slower than python to calculatefib(30)
&fib(40)
) - Python style dissasembler
- optimizations (ie, loop unrolling, tail call optimization, etc)
- Spread operator
- Short circuiting
break
andcontinue
statements- error handling (
try
/catch
) repl
clone the repo
git clone https://github.com/nathanielfernandes/quilt
install the binary
cargo install --path ./cli
once installed, you can run the binary with a file path
quilt main.ql
the binary also as a few options, check them out with
quilt --help
// includes
include "std/math.ql"
// recursion
fn fib(n) {
if n < 2 {
return n
}
return fib(n - 1) + fib(n - 2)
}
fib(30)
// switch case statements
fn fib(n) {
switch n {
case 0, 1 {
n
}
default {
fib(n - 1) + fib(n - 2)
}
}
}
fib(20)
// loops
let i = 0
// assignements are expressions (as well as if statements and loops)
let res = while (i = i + 1) < 10 {
i * 20
}
// native rust function
@print(res)
// for loops
for i in 0:10 {
for j in [1, 2, 3, i] {
@print(i, j)
}
@print(i)
}
// destructuring
let a, b = (1, 2)
for a, b, c in [[1, 2, 3], [4, 5, 6], [7, 8, 9]] {
@print(a, b, c)
}
// closures
fn NewCounter() {
let count = 0
fn Increment() {
count += 1
}
fn Decrement() {
count -= 1
}
// lambdas
let Get = fn() { count }
// implicit return
[Increment, Decrement, Get]
}
let incr, decr, get = NewCounter()
Complete errors, with tracebacks:
fn fib(n) {
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
fib(30)
Builtins can be created with an easy to use macro. The macro will generate the callable functions and put them all into the same module. The module can then be added to the vm.
generate_builtins! {
[export=math] // builtin module name
///Compute the sqrt of a number.
fn @sqrt(a: num) -> float {
Value::Float(a.sqrt())
}
///Get a random number between 0 and 1.
fn @random() -> float {
Value::Float(rand::random())
}
///Get a random integer between two values.
fn @randint(start: int, end: int) -> int {
if start >= end {
Err(error("start must be less than end"))?
}
Value::Int(rand::thread_rng().gen_range(start..end))
}
///Get a random float between two values.
fn @randfloat(start: double, end: double) -> float {
if start >= end {
Err(error("start must be less than end"))?
}
Value::Float(rand::thread_rng().gen_range(start..end))
}
///Compute the luminance of a color.
fn @luma(c: color) -> float {
Value::Float(c[0] as f64 * 0.2126 + c[1] as f64 * 0.7152 + c[2] as f64 * 0.0722)
}
///Truncate a string to a given length and add a custom ellipsis.
fn @truncate(s: str, len: int, ?ellipsis: str = String::from("...")) -> str {
...
}
}
// custom mutable state
let state: i32 = 0;
let ops = VmOptions::default();
let mut vm: VM<i32> = VM::new(state, script, ops);
vm.add_builtins(math);
Instructions u8 |
Args | Description |
---|---|---|
Halt | Stops the program | |
Return | Pops the top value from the stack, returns from the current function, and pushes the value back to the stack | |
Pop | Pops the top value from the stack | |
PopMany | n: u16 |
Pops the top n values from the stack |
Swap | Swaps the top two values on the stack | |
SwapPop | Swaps the top two values on the stack, then pops the top value | |
LoadConst | idx: u16 |
Pushes the constant at idx (in the constant pool) to the stack |
LoadLocal | offset: u16 |
Pushes the local variable at offset (on the stack) to the stack |
SetLocal | offset: u16 |
Peeks the top value from the stack, and sets the local variable at offset to the value |
DefineGlobal | id: u16 |
Pops the top value from the stack, and defines a global variable with the value |
LoadUpvalue | offset: u16 |
Pushes the upvalue at offset to the stack |
SetUpvalue | offset: u16 |
Peeks the top value from the stack, and sets the upvalue at offset to the value |
LoadGlobal | id: u16 |
Pushes the global variable with the id id to the stack |
SetGlobal | id: u16 |
Peeks the top value from the stack, and sets the global variable with the id id to the value |
CloseUpvalue | offset: u16 |
Closes the upvalue at offset |
CreateFunction | idx: u16 |
Creates a function from the constant at idx and pushes it to the stack |
CreateClosure | idx: u16 , repeats(offset: u16 , is_local: u8 ) for n captured variables |
Creates a closure from the function at idx and pushes it to the stack |
CreatePair | Pops the top two values from the stack, and pushes a pair of the values to the stack | |
CreateArray | n: u8 |
Pops the top n values from the stack, and pushes an array of the values to the stack |
CreateRange | Pops the top two values from the stack, and pushes a range of the values to the stack | |
CallFunction | n: u8 |
Pops the top n values from the stack, and calls the function at the top of the stack with the values |
CallBuiltin | id: u16 , n: u8 |
Pops the top n values from the stack, and calls the builtin function with the id id with the values |
EnterContext | idx: u16 , n_args: u8 |
Creates a new context with builtin idx , and n_args arguments, and pushes it to the stack |
ExitContext | Pops the top context from the stack | |
BlockResult | Pops the top value from the stack, and pushes it to the parent context | |
BlockReturn | Pops the return value from the parent context, and pushes it to the stack | |
JumpIfFalse | offset: u16 |
Pops the top value from the stack, and jumps to offset if the value is false |
JumpForward | offset: u16 |
Jumps forward offset instructions |
JumpBackward | offset: u16 |
Jumps backward offset instructions |
Unpack | n: u8 |
Pops the top value from the stack, and unpacks it into n values, pushing them to the stack |
LoadNone | Pushes None to the stack |
|
LoadNoneMany | n: u8 |
Pushes None to the stack n times |
NewLoopCtx | Creates a new loop context, and pushes it to the stack | |
IterNext | end: u16 , loopctx: u16 |
uses the iterable at the top of the stack, and pushes the next value to the stack, or jumps to end if the iterable is exhausted |
UnaryNegate | Pops the top value from the stack, and pushes the negated value to the stack | |
UnaryNot | Pops the top value from the stack, and pushes the boolean negated value to the stack | |
UnarySpread | Pops the top value from the stack, and pushes the spread value to the stack | |
BinaryAdd | Pops the top two values from the stack, and pushes the sum to the stack | |
BinarySubtract | Pops the top two values from the stack, and pushes the difference to the stack | |
BinaryMultiply | Pops the top two values from the stack, and pushes the product to the stack | |
BinaryDivide | Pops the top two values from the stack, and pushes the quotient to the stack | |
BinaryModulo | Pops the top two values from the stack, and pushes the remainder to the stack | |
BinaryPower | Pops the top two values from the stack, and pushes the power to the stack | |
BinaryEqual | Pops the top two values from the stack, and pushes the equality to the stack | |
BinaryNotEqual | Pops the top two values from the stack, and pushes the inequality to the stack | |
BinaryGreater | Pops the top two values from the stack, and pushes the greater than to the stack | |
BinaryGreaterEqual | Pops the top two values from the stack, and pushes the greater than or equal to to the stack | |
BinaryLess | Pops the top two values from the stack, and pushes the less than to the stack | |
BinaryLessEqual | Pops the top two values from the stack, and pushes the less than or equal to to the stack | |
BinaryAnd | Pops the top two values from the stack, and pushes the boolean and to the stack | |
BinaryOr | Pops the top two values from the stack, and pushes the boolean or to the stack | |
BinaryJoin | Pops the top two values from the stack, and pushes the joined value to the stack |