Cached at:
06/02/26, 07:36 PM
# revo
Source: https://gills.pages.dev/revo/
``
⣄⠔⠄⡨⣀⣹⣥⣣⡚⣿⣓⣾⣫⣷⠮⡧⣬⣬⣑⢤⠤⡉⣿⡥⣂⢟⣕⡴⠬⠆⠸⡈⡆⠀⠀⠀⡋⠄⠂⠨⡆⠀⠠⠃⡀⠀⠀⠈⢰⣿
⠩⣽⢟⢭⠶⢷⢵⣻⠿⢍⣟⡅⡧⡭⣽⡯⡯⣶⠭⣽⡢⠱⢿⢧⠓⢥⠨⠧⠉⣡⠐⠀⢑⣀⠂⠈⠆⡅⠀⡀⢥⠤⢁⠀⢣⠀⠀⢘⠽⡏
⠬⡓⣼⢟⢜⡧⣷⣇⣧⡷⢯⣿⣿⣿⡶⣭⠍⣯⣻⢥⠞⣮⣬⣩⠅⢏⠆⠁⡀⠌⢩⡁⢁⠅⠡⢀⠠⠀⢉⢈⠀⠀⡅⠀⡇⠀⠀⠃⠭⣯
⡎⣟⣵⠅⣾⢭⣿⣾⣯⣿⣿⢿⣿⣿⣿⣿⣧⡳⣥⢜⣟⡤⠉⣁⢧⢨⠌⡂⠀⠒⠬⡪⣊⠄⠀⠘⠀⠢⡁⠀⡀⠅⠅⠀⡄⠀⠈⠀⢕⡟
⡕⣧⡗⠎⠷⣯⣿⣿⣷⣿⡻⢵⣿⣿⣿⣭⣿⡿⣟⣇⡧⣿⡭⢨⠫⠮⠄⠖⠀⠆⠦⠕⠁⠅⠀⠆⡁⠁⡀⠀⠄⡉⠁⠀⠄⡀⡂⠀⢼⣇
⠃⣶⣯⠢⡗⣟⣿⡿⣿⣯⢿⣷⢾⢿⣛⣿⣿⣯⡻⣾⡾⣾⢵⣏⣩⠅⠅⠥⠀⠃⠠⠅⠃⠀⡈⠀⠭⠅⠀⠀⠁⠔⠀⠀⢂⠅⠅⠀⠽⡇
⠱⣻⣻⡥⢫⢯⢿⣷⣷⣿⣿⢾⢿⣷⢯⣉⣟⣟⣾⣿⠳⣿⣇⣃⠯⠘⠔⡇⠒⠅⡖⠁⠄⠡⠑⣀⠀⡁⠀⡀⠃⠈⠀⠀⠅⠀⠀⠂⣋⡇
⢂⠵⣓⣿⣍⢯⢯⡯⣟⣝⠷⣟⢝⣋⡏⣧⠯⣿⣿⢯⣼⣯⡂⡂⣟⡧⠐⣅⠂⠥⠏⠀⠂⡅⠅⡁⠁⠄⠠⠊⠀⠨⠀⢈⠀⠄⠉⢘⡟⡆
⡥⣯⢌⡤⢽⣗⣏⠗⢿⣻⡭⡞⡷⣭⢿⡻⡯⡟⣕⣾⢽⢯⡭⠂⡍⠀⠏⠀⢌⠴⠂⠊⡄⢗⠂⠰⠀⠤⠣⠀⠀⠀⠁⠀⠀⠀⠅⠀⢕⡽
⠵⡭⡩⠋⠽⣽⡛⡹⢯⠍⡛⡏⣯⢟⠟⠿⣩⣳⡿⡟⢗⣭⡤⡯⠁⡬⠠⠂⢄⠂⢤⠀⢖⠀⠀⢀⠂⢖⠀⠘⠀⠀⠂⠡⢤⠠⠀⠀⣫⡿
⠀⢰⡇⢂⣃⠙⣧⡵⡙⠇⡷⠶⡷⠶⡟⡙⣩⡌⠯⡺⡍⡲⡸⣆⠼⠅⡱⠌⠡⠂⢄⣠⠃⠀⠀⡂⠈⠂⠠⢀⠀⠀⠀⠔⠀⠂⠂⠀⠨⣯
⡔⠰⠼⣝⢪⢋⢋⠣⣧⡦⣦⡁⡭⠽⡛⠁⡫⢡⡄⠿⡫⣕⣔⢳⠌⢡⡸⠁⠁⠂⣠⡇⠀⠀⠀⠍⠈⠄⠀⠀⢀⠀⠔⠆⠁⠈⠠⠀⡹⡏
⡖⣠⠄⠀⠯⢜⢨⠊⣒⢡⠨⡀⠢⢭⠴⡊⢕⠐⣄⡓⡾⠋⠗⠉⢀⠚⠀⢃⠂⠥⠲⠀⠀⠀⠄⠐⠀⠀⠐⠀⠁⠄⠱⠁⠊⠈⡀⠀⠊⣽
⠀⡉⠒⣃⠰⡀⢁⠘⡉⠪⠋⡲⡅⠍⡎⡪⢣⠽⢭⠍⡥⠁⠂⡰⠠⣄⠥⠐⠀⠣⠄⢀⠐⠁⡀⠀⢐⠐⠀⠠⠁⠂⠄⠠⠀⠁⡀⠐⣫⡟
⠐⢔⠁⠀⠬⠑⠗⠴⠀⠢⡁⢌⠅⢘⠓⡛⠈⡀⠄⠆⠠⠠⠢⢂⠕⠷⠈⢀⠈⡒⠄⠂⠀⠀⠈⠀⡀⠡⠈⠀⠆⡐⠀⠀⠓⠀⠀⠀⢐⢼
⡀⠀⠈⠐⠠⠀⠀⠁⠁⠣⢀⡁⡄⠂⠔⠂⠂⢉⠁⡌⠄⠜⠈⠡⠐⠀⡐⠀⢨⠀⠁⠀⠀⡀⠍⠄⠠⠁⠀⠠⢄⠈⠀⠀⡄⠀⠀⡀⠒⣸
⢘⠉⡒⠤⠌⢀⠑⠕⠀⠄⠄⠄⠀⠄⠑⠉⠨⠀⠄⠀⠂⠃⠀⠀⠈⠄⠄⠠⠈⠀⠀⡀⠠⠁⢠⠁⠁⠀⠐⠱⠀⠀⠀⡐⠀⠉⠄⠀⢸⠾
⠀⠘⠈⡀⠈⠀⠈⠨⠐⠠⠠⠐⡀⡄⢠⠀⢀⠨⠀⠃⠀⠀⡁⡠⠀⠩⠀⠂⠁⠀⠀⠀⢒⡄⠊⠀⠀⢀⠈⠁⠀⠀⠀⠆⠘⠁⠄⠀⠄⣿
⠄⡄⠀⠈⠠⠀⡀⠀⠀⠄⠀⠂⠀⠀⠄⠀⢀⠁⠁⢁⠁⠆⠀⠀⠀⠀⡀⠀⠈⠀⡀⠀⠱⠀⠀⠀⠀⠂⢄⠈⠀⠀⡀⡨⠠⡀⠠⠀⠄⡼
⠠⠀⠁⠊⢀⠀⠠⠁⠁⠂⠂⡄⢄⠐⡀⢀⠊⠂⡀⡁⡀⢀⢀⠠⠀⠄⠂⠡⠠⠁⠀⠀⠀⠂⠀⢠⠒⠀⠀⠀⠐⠢⠀⢀⠀⠀⠄⠄⣿⣿
⠁⠋⠙⠊⠐⠕⠁⡂⠈⠅⠠⠈⠐⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠀⠉⠀⠐⠀⠀⠀⠀⠀⣈⠀⠀⠀⠀⠈⡂⠈⠀⠀⠘⠌⢀⠥
⠀⠀⠀⠀⠔⠔⠀⡀⡉⠀⠀⠀⠂⠠⠠⠀⠄⠄⠀⣀⢀⠀⡈⠀⠌⡐⠈⠈⠀⠀⠀⠀⠅⠀⣐⠉⠄⠀⠀⣂⠠⠁⡈⠀⠄⠄⢀⠣⢶⢿
⠄⠀⠀⡁⠀⠄⠀⠀⠁⠈⠀⠈⠀⠁⠀⠁⡀⠁⠀⠁⠀⠀⠀⡀⠀⠀⠀⠀⠀⠠⡀⠑⠀⠀⠎⠀⢬⢠⠀⢂⠠⠢⠠⠈⡐⠸⠀⡐⣹⢷
⠩⠥⠂⠆⡆⢠⠀⢄⠀⣀⡀⣄⠀⢈⠀⠀⣀⡀⡀⡀⠄⡄⠡⠀⠐⠀⠑⠀⠀⠁⠒⠒⠉⠡⡀⠈⠈⢀⠥⠈⠀⠀⠀⠀⠈⣞⣏⣿⡏⣿
⠾⠝⠷⢿⣯⣿⠧⡧⠭⠣⠭⡭⡮⢭⠶⠶⠶⠦⣗⡶⠽⠭⣿⣶⠷⠵⠮⠽⠿⠮⠿⢷⡶⣖⡷⠲⠞⠷⠾⢿⢷⣿⣿⣿⣿⣯⢇⠟⡧⢈
``
## pipes
Clean data flow without nesting.
Things flow from top to bottom.
``
"hello!!"
|> string.upper
|> string.sub(0, 4)
|> fn(s) s + ", world!"
|> inspect
# or with placeholders,
"hello!!"
|> _:upper() # obj:method() == obj.method(obj)
|> _:sub(0, 4)
|> do
# you can even put any expression here
# even do-end blocks
_ + ", world!"
end
|> print
# this is the same, but look at the order of operations
print(log("hello":upper():sub(0, 4) + ", world!"))
``
## errors-as-values
nil and booleans are replaced by atoms.
You can’t use a value without handling an error – all crashes are explicit (WIP).
Aided massively by pattern matching, `?`, `orelse`, and `:unwrap()`.
``
# f might be
# (:ok, "file-contents")
# or (:err, :IoError)
const f = fs.open({path = "./readme.md"})
# `?` unwraps :ok and panics on :err at top-level
const f2 = fs.open({path = "./readme.md"})?
# crashes if :err
const f = fs.open({path = "./readme.md"}):unwrap()
``
## everything is an expression
No statements – everything (really) always returns a value …but the code still looks procedural.
``
let x = 10 # this line evaluates to 10
let label = if x > 0 "positive" else "zero"
let a = let b = 5 # this whole line evaluates to 5
fn is_true() 5 + 5 == 10
# both x and is_true are the same function
const x = fn is_true() do # do-end is one too
# return and break are special
return 5 + 5 == 10
end
``
## comp
Execute any (really) expression at compile time.
Any script can be compiled into bytecode and get any value baked in.
``
revo -b script.rv
revo script.rvo
``
The compile-time VM does not differ from the runtime one.
``
# asks for a line of input at build-time
# then keeps the result at run-time
const x = comp read()
const long = do
let t = 0
for x in 0..100
t += x
t # similar to rust's {},
# revo do-end blocks return the last value
end
``
## procedural macros
Along with an AST-substituting macro system,
This lets you just get an iterator over the raw AST tokens, run any code to transform them, then return back a table of the new AST.
``
# > num, num, num -> Sigma^4_n=1(a * b + c)
proc cmul!(iter) do
print("inner: ", add3!(10,20,12))
print("peek: ", iter:peek())
match iter:peek()
| (:number, n) => print("is number", n)
| (other, n) => print(other, n)
| x => print("not tuple: ", x)
let a = 10 + (iter:next_of(:number))
let b = iter:next_of(:number)
let c = iter:next_of(:number)
let acc = 0
for i in 1..5 do
acc += a * b + c
end
{(:number, acc)}
end
print(cmul!(10,20,30))
``
## pattern matching
Destructure and branch in place.
You will be using atoms and tuples – they are beautiful solutions to their problems.
``
match (:ok, 42)
| (:ok, v) => v
| (:err, e) => panic(e)
| _ => panic()
| _ => panic()
# _ is wildcard, when nothing else matched
# if you want to grab the actual value
# , just put any binding name there
const response = match "hello!"
| "hello!" => "hi!"
| x when (x:len() > 10) => ""
| x when string?(x) => x + " to you too!"
| _ => ":("
let f = match read({path = "./readme.md"})
| (:ok, file) => file
| (:err, error) when error == :FileDNE
=> panic("file does not exist")
| (error) => panic("error")
| x => panic("unknown: ", x)
``
## fibers
Make all your blocking code become non-blocking by just adding a spawn before it.
``
fn serve(peer, message) do
peer:send(message)?
end
while :true do
# accept the next connection; if none is ready, this fiber parks until the
# runtime sees a connection on the listening socket.
let conn = server:accept()?
# the only thing you have to do to make it async is to add `spawn` here!
spawn serve(conn, port - 1)
end
``
## tables
Represent everything.
Used for:
- module exports
- arrays
- maps
``
let t = {1, 2, 3, key = "value"}
let rec = {name = "revo", version = 1}
rec.name
t[0]
let mt = {
name = fn(self) self.name,
set_version = fn(self, v) self.version = v,
DELTA = 0.0,
}
# a metatable is just a "table overlay"
# which you can slap onto other tables
set_metatable(rec, mt)
let rec = {name = "revo", version = 2}
set_metatable(rec2, mt)
# name does not exist in rec or rec2,
# but you can still call them
assert_eq(rec:version(), 1)
assert_eq(rec2:version(), 2)
assert_eq(rec.DELTA, rec2.DELTA)
``
## convenient typing
The type system is optional, but very well integrated.
Untyped code works just fine, but typed code is faster and gets optimized better (and ensures code correctness at compile-time!). Most of your code will be inferred automatically.
``
type Result =
(:ok, any)
| (:err, atom)
struct User {
name: string = "me",
age: int = 21,
fn get_age(self) -> Result
(:ok, self.age),
}
# type is inferred
let user = User{}
print(user:get_age())
``
## first-class tests
They’re just closures, and they fail when you return an error. The `?` postfix operator propagates errors, giving you a pretty simple experience.
``
fn add(a, b) a + b
suite "add" do
test "addition" do
expect(add(20, 22) == 42)?
expect(add(20, 22) != 22)?
end
test/skip "adds two tables" do
expect(add({1,2}, {3, 4}) == {4,6})?
end
end
``
## quick start
The only dependency is [zig](https://ziglang.org/) version 0.16.0. The repository is also available on [Codeberg](https://codeberg.org/lung/revo).
``
cd /tmp
git clone https://github.com/if-not-nil/revo
zig build -Doptimize=ReleaseFast # static build with vendored
./zig-out/bin/revo -e 'print("hello " + "world")'
# then, put that binary in your path
cp ./zig-out/bin/revo ~/.local/bin/revo
``
## usage
Please check `revo -h` first.
``
revo script.rv # run script
revo # start repl
revo -e "1 + 2" # inline code
revo -b script.rv # script.rv -> script.rvo
revo -b -o output.rvo script # custom output path
revo --bench1 script.rv # benchmarks script.rv
revo --test script.rv # runs with test blocks
revo --dis script.rv # bytecode disassembly
``
## lsp
See [./lsp](https://gills.pages.dev/revo/lsp).