owllisp

Owl Lisp 0.2.2

Owl Lisp is a simple programming language. The main motivation for writing it was to get a portable system for writing standalone programs in a subjectively pleasant dialect of LISP, which in this case means a minimal core language and runtime, purely functional operation and support for asynchronous evaluation.

#index

Getting Started

Requirements

It should be easy to get owl up and running on most somewhat POSIX-compliant systems, such as Linux, any BSD. Owl is mainly developed on OpenBSD and Linux. You should have make and a working C-compiler installed. For example in Debian-based Linux distributions you can use:

$ sudo apt-get install gcc

You may also need git and make if you download the sources from git or want to build the full source package.

Building

The easiest option is to download the current precompiled C-version of ol, and compile with with a C-compiler. Ol is the standalone repl and compiler, which also has the builtin libraries described in this manual.

$ curl https://haltp.org/files/ol-0.2.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/~

This version of ol is compiled with no C-code optimizations, so the resulting C-code is small and takes very little time and resources to compile. 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

Installation

If you just built ol, you can use it from wherever convenient. Usually it is convenient to put such binaries to your home directory under bin/ -directory.

You can install owl and the manual pages with sudo make install after building the sources or a release tarball.

Testing Operation

When started, owl greets the user is ready to proceed evaluating terms given to it. This is the REPL, or read-eval-print -loop familiar from many modern programming languages.

$ ol
You see a prompt.
> (+ 1 2)
3
> (reverse (list 1 2 3))
'(3 2 1)
>

You can exit owl by pressing Ctrl-d, denoting end of input in UNIX, asking the REPL to exit via ,quit, or by asking the thread scheduler to stop everything with (halt 1).

Compiler mode can be tested for example by doing

$ echo '(lambda (args) (print "hello world") 0)' \
   | ol  -x c -o - \
   | gcc -x c -o test -
$ ./test
hello world

Libraries

It is possible to write fairly large programs using just definitions, but they tend to become rather messy and hard to maintain. Programming languages usually allow encapsulating related definitions into a library or a module, which has a well defined interface or interfaces. In Owl these are called libraries. R7RS Scheme happened to defined modules almost exactly equally to Owl libraries, so the naming was converted to match that of R7RS.

A library consists of a listing of other libraries it depends on, a listing of values it intends to expose, and the actual definitions of the values. A simple library can be defined and imported for use as follows:

(define-library (my test) (import (owl base)) ;; required (export barrow barrow?) (begin (define barrow (cdr '(wheel barrow))) (define barrow? (lambda (x) (eq? x barrow))))) ;; Library (my test) added (import (my test)) ;; Imported (my test) barrow '(barrow) (barrow? barrow) #true

Owl contains a number of builtin libraries. Some of them are general purpose ones and others were mainly needed to implement the repl and compiler. A listing of readily available libraries can be shown with the ,libraries REPL command. They are also available in the *libraries* variable, which is an association list of library names and the values of the library. There is nothing magical about libraries - they are just values like everything else.

Libraries can be defined anywhere at toplevel whether using the REPL or loading files. There is however a naming and loading convention, which makes it easier to load libraries. If a library (foo bar baz) is to be imported and one is not already loaded, then Owl will automatically attempt to load it from foo/bar/baz.scm.

If you know the name of the value you would like to import, but don't know the library, then you can search for if using ,find.

,find stat ,find stat current toplevel: state, type-thread-state (owl base): type-thread-state (owl sys): stat (owl core): type-thread-state (import (owl sys)) ;; Imported (owl sys) (assoc 'size (stat "Makefile" #t)) '(size . 3869)

Random Numbers: (owl random)

Randomness is an interesting thing to work with in a purely functional setting. Owl builds randomness around streams of typically deterministically generated 24-bit fixnums. These are usually called rands in the code.

A function involving randomness typically receives a rand stream, and also returns one after possibly consuming some rands. Behavior like this would be easy to hide using macros or monadic code, but Owl generally strives to be explicit and simple, so the rand streams are handled just like any other value.

(define rs (seed->rands 9)) (rand rs 10000) '(values ## 3942) ;; the new rand stream and 3942 (lets ((rs a (rand rs 10000))) a) 3942 (lets ((rs elem (rand-elem rs '(a b c d e f g)))) elem) 'c (lets ((rs sub (rand-subset rs '(a b c d e f g)))) sub) '(b e f) (lets ((rs perm (random-permutation rs '(a b c d e f g)))) perm) '(g e c b d a f) (lets ((rs ns (random-numbers rs 100 10))) ns) '(95 39 69 99 2 98 56 85 77 39)

Exported values:

Association Lists: (owl alist)

Association lists a lists of pairs of keys and their corresponding values. This library has functions for processing them following the naming and argument order conventions used also in other data structures.

Exported values:

Examples:

Functions for handling symbols.: (owl symbol)

Exported values:

owl/equal.scm: (owl equal)

##false Exported values:

owl/vector.scm: (owl vector)

##false Exported values:

Examples:

owl/terminal.scm: (owl terminal)

##false Exported values:

Hygienic Macros: (owl syntax-rules)

This library implements hygienic macro expansion. The role of this library is to construct the transformer functions out of the usual define-syntax definitions as specified in R7RS Scheme.

A macro mainly consists of a set of patterns to be tried on code. If one of them matches, then the corresponding rewrite rule is used to transform the expression. A pattern may contain literals, which means symbols that must be the same as in the pattern, and rest are generally used as syntax variables. A syntax variable is matched to any expression in the input expression, and it can be used to place the expression somewhere in the rewrite rule.

It is also possible to use the ellipsis pattern, denoted by suffixing a pattern ..., which means that the pattern may occur zero or more times. Suffixing a syntax variable bound within such pattern with ... in the rewrite rule causes all of the matches to be added.

Exported values:

Examples:

Sorting: (owl sort)

Exported values:

Examples:

Fresh Symbols: (owl gensym)

It is sometimes useful to get symbols, which do not occur elsewhere. This is typically needed in the compiler, but it may also be needed elsewhere. Gensyms in Owl are just regular symbols, which do not occur in a given expression. This requires walking through the whole expression. To avoid having to walk the original expression in many cases when gensyms are needed, they work in a way that ensures that the gensym of the gensym of an expression also does not occur in the original expression.

Exported values:

Examples:

owl/core.scm: (owl core)

##false Exported values:

Default Math: (owl math)

This library exports the usual arbitrary precision bignum arithmetic functions. You can also use specific (owl math X) libraries if necessary.

Exported values:

Date and Time: (owl date)

This library attempts to implement date processing functions. Dates are typically represented as seconds or milliseconds since UNIX Epoch 1.1.1970. These are generally a good way to work with time, apart from assuming that an absolute time exists at all. Sometimes it is however necessary to convert time stamps to human readable form, which means splitting the time according to various more and less sensible rules.

(time)                → current time in seconds since epoch
(time-ms)             → current time in milliseconds since epoch
(date-str 0)          → "00:00:00 1.1.1970 UTC+00:00"
(date-str (time) 2.5) → "20:49:08 19.3.2018 UTC+02:30"
(date 0)              → 1 1 1970 0 0 0 ; as multiple values
(leap-year? 2018)     → ##false

Exported values:

Examples:

Parsing DSL: (owl parse)

This library implements a simple DSL for constructing parsing functions. The operation is traditional top down backtracking parsing.

Exported values:

Examples:

owl/eof.scm: (owl eof)

##false Exported values:

Program Serialization: (owl compile)

This library writes programs in formats required from the compiler. Programs can currently be written as FASL-encoded bytecode for use in the VM, or a mixture of C and FASL when compiling to C.

Exported values:

Tuples: (owl tuple)

Tuples are an early simple data structure for holding multiple values. Values are indexed from 1 and there is little error detection apart from range checks.

(define x (list->tuple '(a b c))) (ref x 1) 'a (size x) 3 (equal? x (tuple 'a 'b 'c)) ##true

Exported values:

Evaluation: (owl eval)

Evaluation ties together all the separate compiler passes. Each pass is a function of the environment in which the evaluation occurs and the current version of the expression to be evaluated, and returns either an error or next versions of the environment and expression. In the final step the expression has been converted to a thunk, meaning a function of zero arguments, which when called gives the value(s) of the expression.

Evaluate returns the success/failure data structure used in the compiler. Exported-eval is the function typically found at toplevel as eval.

Exported values:

Examples:

owl/function.scm: (owl function)

##false Exported values:

Random Access Lists: (owl rlist)

Random access lists are a data structure similar to lists, but with very different efficiency characteristics. A regular list built out of cons cells is an optimal solution, if one needs to work mainly with the initial elements or the whole list at a time. However, if you need to frequently find and maybe update values in the middle of the list, you have to perform operations taking time proportional to the length of the list. In other words, those list operations are linear time, or have complexity O(n). Cons, car and cdr on the other hand are very efficient for regular lists. Regardless of the size of a list, it will always take a fixed amount of time to add, take or remove a value from it. In other words, these operations are constant time, or have complexity O(1).

A random access list is a data structure, which unsurprisingly attempts to make random access and update efficient.

The performance characteristics of this random access list library are:

car → O(1) cdr → O(log n) cons → O(log n) get → O(log n) set → O(log n) len → O(log n) fold → O(n) foldr → O(n) map → O(n) rlist->list → O(n) append → O(n log n) del → O(n log n) list->rlist → O(n log n)

The operation is based on two ideas. Firstly, a random access list consists of a sequence of complete binary trees. The binary trees are built out of cons cells when needed. The first tree is always of height 1, meaning it just holds the value, much like a regular cons cell. The next node always holds a binary tree either of the same or next height. There can be at most two trees of the same height next to each other. Therefore, tree heights (1 1), (1 2 4) and (1 1 2 4 4) are valid, whereas (1 1 1), (2 2 4) and (1 2 2 8) are not. (5) is right out.

Secondly, trees can be addressed directly with bits. It takes a n-bit number to address each node of a complete binary tree of height n. Finding a value from a random access list works here by first finding the tree in which the value is held, and then using the remaining bits to find the correct leaf node in the tree.

It is easy to see that it takes O(log n) steps to find the tree in which some particular value is held, and then another O(log n) steps to walk the tree to a given position, Threfore we have a total complexity of O(log n) for access and update.

Exported values:

Examples:

Storing Values in Threads: (owl variable)

You cannot mutate values, but threads can also be used to encapsulate state. This library introduces seemingly variable values implemented threads which allow setting and getting a value.

Exported values:

Command Line Arguments: (owl args)

This library makes it easy to write tools which use standard UNIX command line tool conventions. These include accepting short and long versions of flags (-h or --help), packing multiple flags to a single short one (-hai = -h -a -i), handling and verification of flag arguments (--output foo.txt), ability to handle flags anywhere in the arguments and use of -- to stop command line argument processing (e.g. to remove a file called -h or process globbed files coming from an untrusted source).

Exported values:

owl/equal-prim.scm: (owl equal-prim)

##false Exported values:

Extra List Functions: (owl list-extra)

These common list processing operations require numbers, so they are defined in a library loaded after math code.

Exported values:

Examples:

owl/repl.scm: (owl repl)

##false Exported values:

Owl System Calls: (owl syscall)

Typical Scheme programs have a continuation underlying in the execution of a program. Depending on the implementation strategy, this continuation may be an actual variable and a function, as is done in Owl, or it is simulated on a more traditional stack-based system.

Owl has in fact two continuations. One is the normal one you can capture with call-with-current-continuation, but the other one is threaded even through that operation. The other continuation is that of the thread scheduler. It is also a regular program. The main difference in it is that it is expected to be called initially when the program starts and the second continuation is blank. Calling the blank continuation is what eventually terminates the whole program.

Normal progams can only access the second continuation via a primop. When this happens, the continuation of the running program is captured as usual, and the second thread scheduler continuation is called with the information provided in the call along with the continuation allowing resuming execution of the program from the corresponding state.

Wrappers for calling the thread scheduler are defined in this library.

Exported values:

Formatting Numbers: (owl metric)

This library is for converting potentially large integers to more readable and less accurate form. Mainly used for formatting output of ,time repl command.

Exported values:

Examples:

Extra Math Functions: (owl math extra)

Exported values:

Examples:

Rationals Numbers: (owl math rational)

This library defines arbitrary precision rational arithmetic operations. A rational number p/q is a typed pair of arbitrary precision integers. A valid rational number has q != 0, q != 1, and gcd(p, q) = 1.

Exported values:

Complex Numbers: (owl math complex)

This library defines complex arbitrary precision math functions.

Exported values:

Examples:

Bignums: (owl math integer)

This library defines arbitrary precision integer arithmetic. Some of the functions are only defined for integers, whereas others are typically extended to handle also more complex kinds of numbers.

Operations to be extended typically export both the operation defined for integers only, and a corresponding mk-* version which calls a given function in case the types of inputs are not integers. This allows extending the definitions in other modules while checking the most common cases of integer inputs in first branches.

Owl currently has 24 bits available for encoding a value in an immediate value, meaning a value that does not have to be stored in memory. A fixnum, or a fixed size number, is a number which fits these bits. The sign of a fixnum is stored the type of the immediate object.

When a number is bigger or smaller than the maximum fixnum, it is converted to an allocated integer. In this case the representation of the number is a linked sequence of base 2²⁴ digits of the number starting from the least significant digits. Again the sign of the number is stored in the type.

A valid fixnum zero is always positive, and a valid negative bignum has the negative type only at the root node.

Exported values:

Hash Algorithms: (owl digest)

The digest library provides functions for computing cryptographic signatures. Currently SHA1 and SHA256 digests and corresponding message authentication codes are supported.

The hash functions also have hasher-raw and hasher-bytes -variants, which return the state words and raw signature bytes correspondingly.

(sha1 data) → hash-string (sha256 data) → hash-string (hmac-sha1 key message) → hash-string (hmac-sha256 key message) → hash-string

Exported values:

Examples:

Automatic Tests & Documentation: (owl proof)

The goal of this library is to make writing software tests and documenting functionality as simple as possible. The operation is as follows:

Exported values:

Examples:

Number Indexed Stores: (owl iff)

Number stores (radix trees with a ff at each node)

Exported values:

Thread Scheduler: (owl thread)

This library defines the thread controller. It handles activation, suspension and requested external actions of continuation-based threads. It is much like a very small kernel.

A thread is simply a function. Owl programs have two continuations, one for the regular one and one for the thread scheduler under which the function is running. Every function eventually calls its own final continuation, which results in the thread being removed from the thread scheduler. It can also call the second continuation and request operations from this thread scheduler.

The task of the thread scheduler is to run each running thread for a while, suspend and activate them, and handle message passing.

Threads have a primitive only for asynchronous message passing, but they can voluntarily wait for a specific kind of response, resulting in synchronous operation where desired.

Exported values:

Default Toplevel: (owl toplevel)

Values exported in this library are available when starting owl interactively. The form (exports ...) allows exporting all values exported by another library.

Exported values:

Lambda Calculus Data: (owl lcd)

This library defines macros for using functions (which are lambda expressions) as data structures.

Exported values:

Basic List Functions: (owl list)

Operations in this library depend only on primitive operations. The rest of the usual operations typically depend on numbers, which are implemented in (owl math).

Exported values:

Examples:

owl/sexp.scm: (owl sexp)

##false Exported values:

Boolean Values: (owl boolean)

This trivial library defines boolean values.

Exported values:

owl/char.scm: (owl char)

##false Exported values:

Byte Vectors: (owl bytevector)

Byte vectors are vectors holding only numbers in the range 0-255. They are internally represented as chunks of memory in the garbage collected heap. Typical use cases for them is packing data for an operating system call, or receiving data from an external source.

Vectors and byte vectors differ from Scheme in that they are not disjoint types. Regular vectors can be, or can contain, byte vectors if the content happens to fit in the range representable by bytes. This makes it possible to use a more compact representation of data where possible. Representation changes to regular vectors where necessary, much like numbers are converted from fixnums to bignums where the values would no longer be representable by fixnums.

Exported values:

owl/json.scm: (owl json)

##false Exported values:

Examples:

Queues (double-ended lists): (owl queue)

Exported values:

Lazy Lists: (owl lazy)

Lazy lists (or streams) are like lists, but they are computed only as far as needed. You can for example define a lazy list of all integers below a million, and then proceed to run computations on them, without worrying whether you have enough memory to hold a million numbers. Lazy lists are for example useful in computations, where you know how something is constructed, but don't yet know how many of them will be needed, or know that you only need them one at a time and don't want to waste memory.

A lazy list is either ##null, a pair of a value and rest of the lazy list, or a function of zero arguments (a thunk) which when called will return the rest of the lazy list. Therefore, since normal lists are a subset of lazy lists, all lazy list functions can also take normal lists as arguments.

Scheme warning: recall that Owl does not have mutable data structures, so lazy lists do not cache their results.

(pair head exp) → ll, lazy equivalent of (cons head exp), but exp is not evaluated yet (force-ll ll) → list, turn a lazy list into a regular one

Exported values:

Examples:

Strings: (owl string)

Exported values:

Interactive Input: (owl readline)

This library is used by by the REPL to read input similarly to what the traditional readline library does.

Exported values:

Register Transfer Language: (owl eval rtl)

Lambdas can have any number of variables. The variables eventually end up representing virtual machine registers. There are only a fixed number of registers in the VM, and certain values should be in certain registers at certain points in evaluation. Therefore we need a transformation from the AST expression to something closer to bytecode operating on registers. This format uses a similar tuple structure and is called RTL, short for register transfer language.

The task of this library is mainly to assign a VM register to each variable and convert references accordingly.

This module is quite old. The register allocator could be updated at some point, after which the number of VM registers could be reduced.

Exported values:

Compiler Data Structures: (owl eval data)

This library contains shared data structures used in the compiler. Originally all the data structures were either S-expressions or tuples with a fixed structure. They are being converted to sum types, which makes it possible to verify some aspects of use of the data structure at compile time.

Exported values:

Environment: (owl eval env)

Evaluation happens in an environment. An environment maps variables to values. This library defines operations on the environment structure used by the compiler.

Exported values:

Fixed Point Computation: (owl eval fixedpoint)

Implementing recursion becomes an interesting problem when mutation is not allowed. Scheme allows recursion in definitions and via the letrec primitive form, which under the hood require mutable data structures. We don't have them here, and the only way to construct bindings and functions is a run of the mill lambda.

Luckily, a lambda is all it takes to recurse. This library takes a version of the program in which one can use a recursive lambda, or rlambda, and converts it to a corresponding expression using only normal lambdas.

The conversion is a custom one developed for the first version of Owl. It operates by exteding each recursive lambda with the other recursive functions it may call, propagating the required values to required call sites at required places, and constructing wrapper functions which convert the originally intended arguments to ones the actual recursive operations need.

The added extra arguments are added after the original ones. This is important, because we want the wrapper functions to just consist of a few loads to free registers from their environment and a jump.

Exported values:

Closure Conversion: (owl eval closure)

Even though all bindings and operations are just lambdas, we need to differentiate a few kinds of them depending on their content and use.

A lambda occurring in the operator position does not need to be treated as a function, because it simply means assigning names to values. They are leaft intact and generally end up being implemented as register moves.

The lambdas we need to represent somehow at runtime are further split to closures and procedures. A procedure is lambda which does not depend on values known only at runtime, so the corresponding object can be constructed at compile time. In the special case where it also does not require any references to non-trivial other values, the value ends up being just the bytecode.

A closure is a lambda which requires some values known only at runtime. For these a procedure is generated at compile time, and instructions to add the remaining values at runtime are added to bytecode.

Exported values:

Bytecode Assembly: (owl eval assemble)

This library converts the RTL expressions to bytecode executable by the VM.

CPS conversion usually produces a lot of small functions with equal bytecode but potentially different bindings in the environment. In order to make programs smaller and improve cache friendliness the bytecode is interned, just like we do with symbols.

Exported values:

Continuation Passing Style: (owl eval cps)

The continuation passing style, or CPS, transformation converts a lambda expression to another lambda expression, with some useful properties. The output is a function with one extra argument, which will be called with the result of the original function.

There are three main reasons for using the conversion. For one, the resulting lambda expression can be evaluated without the need for a stack, because nested function calls are eliminated and replaced by tail calls. What would normally be stored on a stack now ends up being stored in a closure.

The second reason is that Scheme allows capturing the added argument, which is called the continuation, to a variable. This requires no special support from the runtime, because the continuation is just a regular lambda.

Thirdly the presence of continuations makes implementing multithreading easy. Owl has pre-emptive multithreading, meaning a thread does not need to voluntarily give up control for other threads to run. This is done by jumping from the thread-local continuation to thread-scheduler continuation whenever a thread switch occurs.

Exported values:

AST transformation: (owl eval ast)

S-expressions could be used internally by the compiler at each step. Here we however construct a simple tree of tuples format out of the S-expression while checking its structure. This is done so that the subsequent compiler steps don't have to check S-expression structure.

The AST format could switch to using sum types at some point.

Exported values:

Register allocation: (owl eval register)

The VM has only a finite number of registers. This library attempts to reduce the number of registers needed by the RTL, and choose better registers for values to avoid unnecessary shuffling of values.

Old code. Scheduled for a rewrite.

Exported values:

C Translator: (owl eval cgen)

This library is needed when compiling programs to C-programs. This library is also used to compile Owl itself.

The simples way to build standalone C-programs out of owl programs is to write the virtual machine code and the FASL-encoded program to run to a file. This happens when owl is invoked without the -O flags. On the plus side the resulting program has very little C-code, so the C-compiler won't take much time when compiling it. This is how the small Owl version is built.

Typically the output is optimized. This is done by translating some of the bytecode sequences of the program to be compiled to corresponding C-code fragments, adding them as new instructions to the vanilla virtual machine, and replacing them from the program with calls to the new instructions. The resulting C-code is larger and may take quite a bit of memory to compile using some C-compilers, but the resulting binary will be faster. This is how the usual bin/ol interpreter is built.

Exported values:

Alpha Conversion: (owl eval alpha)

This step renames all variables to fresh symbols. This is not strictly necessary, but it makes some compilation steps easier.

Exported values:

Macro Expansion: (owl macro)

Macros are operations on code to be performed before evaluating it. This is done by the macro expander defined in this library. The role of the macro expander is to apply macros at appropriate positions on the code and pass required information to it. The actual transformations are done by the functions stored as values of the macros.

A function to be used as a macro receives two arguments, the form to be expanded and the next free fresh symbol not occurring in the form or the parent expression of it, and returns a tuple of the expanded form and the next free symbol.

The macro expander can be tested from toplevel by using ,expand.

Exported values:

Input and Output: (owl io)

Owl is is by default a multitasking system. Multiple threads can be working on tasks and waiting for input or output at the same time, so we cannot simply define input and output (I/O) as lisp functions calling the corresponding operating system code via the VM.

There are a few ways how I/O could work in a multithreaded setting. Having a lot of I/O code in the thread scheduler is convenient to use, but results in an ugly thread scheduler. Having separate threads for each file descriptor and using only message passing for I/O is elegant, but turned out to be somewhat cumbersome to use. The current version is something in between. One thread, called iomux, handles port blocking and timers. Threads attempt to do I/O directly when calling e.g. print or read, but if the operation would require waiting for input or output, then the thread sends a synchronous message to iomux and gets a response when the task can be completed without blocking.

Notice that the I/O defined in this library cannot in general be used unless iomux is running. This may happen when working with code that has not called (start-muxer), and when trying to debug the thread scheduler.

Exported values:

Finite Functions: (owl ff)

This library defines finite functions. They are commonly used in Owl to construct efficient key-value mappings. A finite function is much like an association list, which are frequently used in Lisp. The main difference is that finite functions are represented as red-black trees internally, so the performance is much better when there are many keys.

The value empty is an empty finite function. It contains no mappings. You can read the value of of a mapping with get, which accepts a finite function, a key to be fetched and optionally a default value to return if the key is not mapped. put can be used to extend a ff and del removes a mapping.

(define f (put (put empty 'foo 100) 'bar 42)) f ## (get f 'foo ##f) 100 (get f 'x ##f) ##f (get (del f 'foo) 'foo ##f) ##f This data structure is made possible by the fact, that Owl has an order-preserving garbage collector. Therefore we have a total order on objects, which makes it possible to build balanced trees by comparison.

Exported values:

Examples:

Value interning and conversions: (owl intern)

Exported values:

UTF-8 encoding and decoding.: (owl unicode)

Exported values:

Rendering Values: (owl render)

There are two ways to serialize values to sequences of bytes: S-expressions and the FASL encoding. S-expressions represent trees of certain values and can thus only be used to accurately represent a subset of all possible values. FASL encoding can represent all values, apart from from their external semantics such as open network connections.

This library implements the S-expression part. There are further two flavors of rendering: the one intended to be printed out of programs and the one which results in expressions that can be read back in. The first one is usually seen as the output of print, whereas the other one is typically written with write.

Some R7RS Scheme extensions to represent shared structure in S-expressions is also implemented.

Str and str* are used to translate values to strings, whereas render and render* are typically used in a fold to construct lists of bytes to be later converted to strings or other formats.

Exported values:

Examples:

Data Serialization: (owl fasl)

This library implements serialization of objects to byte lists, and parsing of the byte lists to corresponding objects. The format is used internally for storing memory images to disk. Files with .fasl suffix used in booting up Owl are just fasl-encoded functions.

Exported values:

Examples:

System Interface: (owl sys)

This library defined various system calls and wrappers for calling them. The calls available are mainly frequently needed ones defined in the POSIX standard, or otherwise portable enough to be available in most systems these days.

Exported values:

owl/time.scm: (owl time)

##false Exported values:

POSIX regular expressions: (owl regex)

this library implements a mostly complete POSIX-compatible regular expressions. at the moment lib-regex tries to just get all the features right. lots of non-constant-factor optimizations are missing.

spec: http://pubs.opengroup.org/onlinepubs/007908799/xbd/re.html syntax ref of portable scheme regexps (Dorai Sitaram): http://evalwhen.com/pregexp/index-Z-H-3.html

Exported values:

owl/port.scm: (owl port)

##false Exported values:

scheme/process-context.scm: (scheme process-context)

##false Exported values:

scheme/read.scm: (scheme read)

##false Exported values:

scheme/base.scm: (scheme base)

##false Exported values:

scheme/complex.scm: (scheme complex)

##false Exported values:

scheme/cxr.scm: (scheme cxr)

##false Exported values:

scheme/case-lambda.scm: (scheme case-lambda)

##false Exported values:

scheme/write.scm: (scheme write)

##false Exported values:

scheme/time.scm: (scheme time)

##false Exported values:

Thanks

Owl Lisp is being written mainly as a standalone pet project, but it has gotten many useful bug reports, suggestions and patches during the course of its development. Here is a partial list of people who have helped in the development:

Jo Henke A ton of improvements Doug Currie GC improvements, data representation optimizations, etc. John Cowan A slew of filed bugs. Pekka Pietikäinen Portability suggestions and patches. Erno Kuusela Portability suggestions and general insight.

The code similarly isn't based on any implementation or document, but many have definitely had an impact on it. A partial list of such sources follows:

Recursive Programming Techniques, William H. Burge, 1975 Essentials of Programming Languages, Friedman, Wand, Haynes, 1992 Efficient Garbage Compaction Algorithm, Johannes Martin, 1982 Scheme48, http://s48.org/ and various papers on it Structure and Interpretation of Computer Programs, http://mitpress.mit.edu/sicp/

Related Resources

#[https://github.com/yuriy-chumak/ol, Otus Lisp] is a related language. It adds features such as FFI to be able to write graphical programs and interface with other external libraries.

#[https://small.r7rs.org/attachment/r7rs.pdf, R7RS Scheme (pdf)]

FAQ

#Q: Where can I get help?: #'You can stop by at #owl-lisp on libera.chat', file tickets to #[https://gitlab.com/owl-lisp/owl,gitlab] or send email to aki.helin@iki.fi.

#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. Current best practice in Owl programs is not to make errors.

#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 #post