Sylvan

A general-purpose programming language with that aspires to have support for attribute grammars, managed effects, and efficient pure functional programming.

Examples

pub def main() -{ OS }> () {
println("Hello, world!");
}
pub variant Counter[res] {
Tick(): Counter[()],
}

def tick() -{ Counter }> () :=
perform(Counter::Tick());

def with_counter[effs](body: fn() -{ Counter, ...effs }> ()) -{ ...effs }> Int :=
with_local_mutable(fn[scope]() {
def counter := ref_new(0);
handle(fn[res](eff: Counter[res], k: fn(res) -{ Counter, LocalMutable[scope], ...effs }> ()) {
eff.match {
Counter::Tick() => {
ref_modify(counter, fn(i) => i + 1);
k(())
}
}
}, fn() => body());
ref_read(counter)
});

pub def main() -{ OS }> () {
def n := with_counter(fn() {
tick();
tick();
tick();
});
(n == 3).if {
println("Yay!");
} else {
println("Oops...");
exit(-1)
}
}
import std::{
data::array::{array_length, array_make, array_read},
os::{
Event, EventKind, FdFlags, Handle, Subscription,
fd_read, fd_write, handle_get_named, poll_oneoff, sock_accept,
},
};

pub def main() -{ OS }> () {
def listen_sock := handle_get_named("listen").match {
Some(x) => x,
None() => throw_pure_exception("no handle named listen"),
};

def loop(state_machines: Array[StateMachine]) {
// TODO: transducers...
def subscriptions := array_make(array_length(state_machines), fn(i) {
def state := array_read(state_machines, i);
(i, to_subscription(state))
});
def events := poll_oneoff(subscriptions);
array_make(array_length(state_machines), fn(i) => i)
|> fold_map(fn(i) => {
def state := array_read(state_machines, i);
find_event(events, i).match {
Some(event) => accept_event(state, event),
None() => [state],
}
}, _)
|> loop
}
loop([StateMachine::Accept(listen_sock)])
}

def find_event[a](events: Array[Event[a]], key: a) -> Option[EventKind]
where Eq[a]
{
def loop(i: Int) {
if i == array_length(events) {
None()
} else if array_read(events, i).user_data == key {
Some(array_read(events, i).event_kind)
} else {
loop(i + 1)
}
}
loop(0)
}

pub variant StateMachine {
Accept(Handle),
Read(Handle),
Write(Handle, Bytes, Int),
}

def accept_event(state: StateMachine, event: EventKind) -{ OS }> Array[StateMachine] :=
(state, event).match {
(StateMachine::Accept(hdl), EventKind::FdRead()) => {
def new_sock := sock_accept(hdl, FdFlags(0b00100));
[StateMachine::Accept(hdl), StateMachine::Read(new_sock)]
},
(StateMachine::Read(hdl), EventKind::FdRead()) => {
def buf := fd_read(hdl, 1024);
[StateMachine::Write(hdl, buf, 0)]
},
(StateMachine::Write(hdl, buf, i), EventKind::FdWrite()) => {
if i == bytes_length(buf) {
[StateMachine::Read(hdl)]
} else {
def len := fd_write(hdl, buf, i, bytes_length(buf) - i);
[StateMachine::Write(hdl, buf, i + len)]
}
}
};

def to_subscription(state: StateMachine) -> Subscription :=
state.match {
StateMachine::Accept(hdl) => Subscription::FdRead(hdl),
StateMachine::Read(hdl) => Subscription::FdRead(hdl),
StateMachine::Write(hdl, _, _) => Subscription::FdWrite(hdl),
};

Getting Started

Note: This is a super-preliminary release; expect tons of stuff to be broken!

Currently, the best way to try Sylvan is via our dev container image. You can download or update it by running:

$ docker pull oci.sylvan-lang.org/dev:0.0.1
0.0.1: Pulling from dev
[...]
Status: Downloaded newer image for oci.sylvan-lang.org/dev:0.0.1
oci.sylvan-lang.org/dev:0.0.1

(This is an OCI image, and so should work with any other container runtime, e.g. Buildah or Podman.)

You can create a container, mount the current working directory to /code inside it, and run commands inside it with:

$ docker run --rm -itv "$(pwd):/code" \
> oci.sylvan-lang.org/dev:0.0.1

# sylvan --help
NAME
sylvan-stage0 v0.0.1 - Sylvan stage0 interpreter

SYNOPSIS

sylvan [options...] src-dir libs-to-load... \
[-- main::module [args-to-main...]]

[...]

Inside the dev container, source files and libraries will be searched for inside of /code. /code is also the default working directory.

Let’s try making and running a “Hello, world” program. Create a file in /code named hello_world.syl.

# nvim hello_world.syl

Here, we use the Neovim text editor, which is installed and configured inside the dev container to support editing Sylvan code. If you prefer a different text editor, it can be installed with apt. Since the /code directory is mounted from the host, you can also use a text editor on the host machine.

Write the following code into the file, then save and exit:

pub def main() -{ OS }> () {
println("Hello, world!");
}

We can load the standard library and our code to check both for errors and then run the hello_world::main function by passing them to the sylvan binary on the command line.

# sylvan std hello_world -- hello_world
Hello, world!

At this point, you’ve seen how to get the Sylvan compiler, what a simple program looks like, and how to run it.

Documentation has not yet been written for the full Sylvan language or standard library, so at this point the best thing to do to learn more might be to poke around the src directory in the repo.