posts : tags : programs

Owl Lisp

Owl Lisp is a purely functional dialect of Scheme. It is based on the applicable subset of R7RSstandard. The main extension to R7RS is pre-emptive threading, and main difference is lack of mutable data structures.

Installation

Owl consists of a single binary ol, which contains the REPL, compiler and builtin libraries. Releases have a precompiled ol.c file, which can be downloaded and used as follows:

$ curl https://gitlab.com/owl-lisp/owl/uploads/87ffa9dad2bc59fc0ac5efcd90db4c7c/ol-0.1.16.c.gz \
   | gzip -d \
   | gcc -x c -O2 -o ol -
$ ./ol
You see a prompt.
> (cons 1 (list 2))
'(1 2)
> ,quit
bye bye _o/~

Alternatively you can download all of the sources and make a traditional install.

$ git clone https://gitlab.com/owl-lisp/owl.git
$ cd owl-lisp
$ make
$ sudo make install

Usage

Implementation

Startup

Owl currently runs on top of a small virtual machine. The VM boot consists of decoding a FASL-encoded value, converting the command line arguments to a list, and calling the decoded value with the list. The value to boot can be stored either initially one Owl value, which is assumed to be a function of one argument, converts the command line inputs to a list of strings, and then calls the decoded value with the list. If a value to be decoded is not stored to the executable (meaning `heap` is null), then the VM tries to read a FASL-encoded value from the first argument and omits the VM from the arguments passed to the program. This way all three ways to run programs with arguments work the same.

$ echo '(lambda (args) (print args) 0)' > program.scm
$ ol -o program.fasl program.scm
$ ol -o program.c program.scm && cc -o program program.c
$ ol --run program.scm 1 2 3
(ol 1 2 3)
$ bin/vm program.fasl 1 2 3
(program.fasl 1 2 3)
$ ./program 1 2 3
(./program 1 2 3)

FASL-encoded values compiled with ol contain also the thread scheduler, which brings in much of bignum arithmetic, IO and other dependent code. FASL-encoding can also be done directly, if necessary.

$ echo '(lambda (args) 42)' | ol -o test-1.fasl -
$ ol -e '(vector->file (list->vector (fasl-encode (lambda (x) 42))) "test-2.fasl")'
#true
$ bin/vm test-1.fasl; echo $?
42
$ bin/vm test-2.fasl; echo $?
42
$ wc -c test-*
53825 test-1.fasl
   14 test-2.fasl
53839 total

Bootstrap

When Owl code is checked out, the source tree contains a bytecode image of some previous state of the Owl world in `fasl/init.fasl`. The VM uses this image to load the sources from `owl/*.scm` and outputs the final value as the next world image. This is repeated until a fixed point is reached, meaning the compiler compiles an identical version of itself. The initial FASL image is occasionally updated to the current fixed point image, when the build starts to take too many such rounds.

Oddities

Owl tries to keep the core language elegant and orthogonal, which has resulted in some rather rare features:

FAQ

Q: Where can I get help?: You can stop by at #owl-lisp on freenode, file tickets to gitlab or send email to aki.helin@iki.fi.

Q: What does functional even mean?!1: You cannot change data.

Q: But Owl does I/O, so it can't be functional!: Yes, it can.

Q: But Scheme is functional too!: No, it's not.

Q: But what's the point?: To make a tool I needed, and to share it with others.

Q: How can I use third party libraries?: Grab them to source directory and include them. `(import (foo bar))` attempts to load ./foo/bar.scm, which should have (define-library (foo bar) ...)

Q: The error messages suck.: True. Best practice in Owl programming is to use Feynman debugging.

Q: Why is it not called a Scheme?: I don't want people filing issues about set-car! not working.

Q: Is this the last question?: No, that one was already asked some years ago.

#project #owl