Awl

Experimental Lispy mini-language

Awl

Awl is an experimental mini-language based on the Lisp family of programming
languages.

Synopsis

It was written for fun and profit to learn more about interpreter
design, programming in C, and using
emscripten to transpile to JavaScript.

Note: This naturally goes without saying, but Awl is just an experimental
learning project, which means that it is lacking in thorough testing and
probably has many bugs. It should not be used for production code, lest it
summon Undefined Behavior™ upon you.

That being said, experimenting and hacking on non-production-ready code just
for fun can be worthwhile!

Examples

Here are a few examples that briefly demonstrate some of Awl’s features.

Compiling

Most of Awl’s dependencies are included in the repository, so you shouldn’t
need to install anything other than the build tools. Awl takes advantage of
some new features in C11, so you will need a fairly recent C compiler.

Both clang (tested with version 3.5.0) and gcc (tested with version
4.7.2) are known to successfully compile

You’ll need make

To transpile to JavaScript, you’ll need the emscripten toolkit, including
emcc.

To minify the output JavaScript from emscripten, you’ll need uglifyjs,
but it isn’t strictly necessary.

First, clone the repository, and then compile using make:

$ git clone https://github.com/voithos/awl.git
$ cd awl
$ make

This will create an awl binary under bin/.

You can also compile and execute tests:

$ make test

Or transpile to JavaScript (emcc will need to be in your $PATH):

$ make web

Clean up if you want to start over:

$ make clean

Usage

The awl binary can take a single argument - a path to a file to execute.

$ ./bin/awl [file]

If no argument is given, then it will drop into an interactive interpreter
(REPL):

$ ./bin/awl
awl v0.x.y
Ctrl+D to exit
awl>

Features

Awl is a mini-language that is inspired by the Lisp family of languages. Thus,
it shares most of its features with Lisp and Scheme. These include:

Currently, Awl’s data definition and manipulation capabilities are lacking, but
this will hopefully be changed in the future.

Language Reference

Awl is an expression-based language. Basically everything is an expression, and
can be arbitrarily nested. A program consists of a sequence of such
expressions.

Basic Features

Awl supports inline comments using semicolons (;):

; Can go on its own line
(func (plus-one x)
(+ x 1)) ; Or at the end of a line

Printing to standard output can be done using print and println:

awl> (println "Hello sekai!")
Hello sekai!

Variables are created with define (which affects the local environment) and
global (which, as the name suggests, affects the global environment):

awl> (define foo 'bar')
awl> (println foo)
bar

Primitive Data Types

Type

Example

Description

Integer

5, -9

A standard integer (long)

Floating point

-5., 3.14

A standard floating point (double)

Boolean

true, false

A standard... boolean

String

"\"escapes\" OK", 'foobar'

A string type - either single or double quotes

Q-Symbol

:like-in-ruby, :'foo'

A quoted symbol (identifier), can also be written similar to strings

Q-Expr

{1 'b' (+ 1 2) x y}

A quoted expression. The basic data structure - acts like a list

Dictionary

[:x 23 :y 'hello' :z {a b c}]

A key-value store. Keys are Q-Symbols, values can be anything

Function

(fn (x) (/ 1 x))

An anonymous function. The basic mechanism of function definition

Error

(error 'somebody set up us the bomb')

An error. Stops evaluation

Expressions

Function calls in Awl are defined as
S-Expressions (symbolic
expressions). They are syntactically enclosed in parentheses (). The first
argument of the expression must be a callable, and is evaluated in the current
environment with any following arguments as parameters (this is the iconic
“Polish notation” of Lisp).

awl> (+ 5 6)
11
awl> (println 'foo')
foo

When evaluating user-defined functions, partial application is done
automatically for any unfilled arguments (this is currently not done for
builtins). This makes it easy to use higher-order functions quickly:

Variable and function identifiers, called “symbols,” are evaluated to the
values that they map to, except in certain special forms (e.g. when they are
being defined):

awl> (define x 5)
awl> (+ x 6)
11

The primitive types evaluate to themselves.

Q-Expressions (quoted expressions, often referred to simply as ‘lists’) are
particularly important. They are enclosed inside curly braces {}. They are a
collection type and behave similar to lists in other languages. They can store
any number and mixture of primitive types. And they have one more important
ability: expressions that they contain which would normally be evaluated, such
as symbols and S-Expressions, are left unevaluated (i.e. they are “quoted”).
This allows them to contain arbitrary code, and then be converted and evaluated
as S-Expressions:

There are a few more expression types that are useful in special cases.

E-Expressions (escaped expressions) are denoted with a preceding backslash \,
and can be used to specifically evaluate a section within a Q-Expression
literal:

awl> {1 2 (+ 2 1)}
{1 2 (+ 2 1)}
awl> {1 2 \(+ 2 1)}
{1 2 3}

C-Expressions (concatenating expressions) are denoted with a preceding at-sign
@. They behave similarly to E-Expressions, with the exception that, when
given a list (Q-Expression), they “extract” the contents and include it
directly in the outer list:

awl> {1 2 \{3 4}}
{1 2 {3 4}}
awl> {1 2 @{3 4}}
{1 2 3 4}

Finally, there is another collection type that is slightly more mundane than
Q-Expressions and their ilk: Dictionaries. Dictionaries act as simple key-value
stores, and are similar to the dictionaries in other languages. They are
delimited with square brackets [], use Q-Symbols as their keys, and can store
any normal value:

Builtins

Builtins usually behave like normal functions, but they also have the special
role of enabling some of Awl’s basic features, since they are written in C (for
example, the fn builtin creates a new anonymous function).

Awl makes no distinction between “operators” (+, -, *) and other kinds of
builtins - they are simply named differently.

Builtin

Signature

Description

+

(+ [args...])

Addition. Takes 2 or more arguments

-

(- [args...])

Subtraction. Takes 2 or more arguments

*

(* [args...])

Multiplication. Takes 2 or more arguments

/

(/ [args...])

Division. Promotes integers to floats if necessary. Takes 2 or more arguments

Returns a slice of a collection based on start, stop, and step numbers

if

(if [pred] [then-branch] [else-branch])

If expression. Evaluates a predicate, and one of two branches based on the result

define

(define [sym] [value])

Defines a variable in the local environment

global

(global [sym] [value])

Defines a variable in the global environment

let

(let (([sym1] [val1])...) [expr])

Creates a local environment and defines variables within

fn

(fn ([args...]) [body])

Defines an anonymous function with the specified arguments and body. The
function also retains the current environment as a closure

macro

(macro [name] ([args...]) [body])

Defines a macro that can operate on code before it is evaluated

typeof

(typeof [arg1])

Returns a string representing the type of the argument

convert

(convert [type] [value])

Converts a value to type, which is represented by a qsym,
as returned by typeof

import

(import [path])

Attempts to import the awl file at the given path

print

(print [arg1])

Prints to standard output

println

(println [arg1])

Prints to standard output, adding a newline

random

(random)

Returns a floating point random number between 0 and 1

error

(error [arg1])

exit

(exit [arg1])

Exits the interactive REPL

Core Library

In addition to builtins, there exists a core library that Awl imports on
startup. Among other things, this library aims to exercise some of Awl’s
features, as well as provide some basic functional tools.

Symbol

Signature

Description

nil

Alias for {}

func

(func ([name] [args]) [body])

Macro that defines a named function

int?

(int? [arg1])

Checks that argument is an integer

float?

(float? [arg1])

Checks that argument is a floating point

str?

(str? [arg1])

Checks that argument is a string

builtin?

(builtin? [arg1])

Checks that argument is a builtin

fn?

(fn? [arg1])

Checks that argument is a user-defined function

macro?

(macro? [arg1])

Checks that argument is a macro

bool?

(bool? [arg1])

Checks that argument is a boolean

qexpr?

(qexpr? [arg1])

Checks that argument is a Q-Expression

dict?

(dict? [arg1])

Checks that argument is a Dictionary

list?

(list? [arg1])

Alias for qexpr?

nil?

(nil? [arg1])

Checks that argument is nil

to-str

(to-str [arg1])

Converts argument to a string

do

(do [expr1] [expr2] ... [exprn])

Evaluates its arguments one by one, and returns the result of the last
argument

compose

(compose [f] [g] [xs...])

Composes two functions

flip

(flip [f] [x] [y])

Takes a function and two argument, and flip the ordering of the arguments

id

(id [x])

The identity function, returns whatever is passed

reduce

(reduce [f] [l] [acc])

Reduces a list to a single value using a reducer function

reduce-left

(reduce-left [f] [l] [acc])

Like reduce, but traverses the list in the opposite direction

map

(map [f] [l])

Applies a function to each element of a list

filter

(filter [f] [l])

Uses a predicate function to filter out elements from a list

any

(any [f] [l])

Checks whether any value in list l satisfies f

all

(all [f] [l])

Checks whether all values in list l satisfy f

sum

(sum [l])

Sums elements of a list

product

(product [l])

Multiplies together elements of a list

pack

(pack [f] [args...])

Takes multiple argument and feeds it to a function as a single list
argument

unpack

(unpack [f] [l])

Evaluates a function using a list of arguments

nth

(nth [n] [l])

Returns the nth element of a list

zip

(zip [lists...])

Returns a list of lists, each containing the i-th element of the argument lists

take

(take [n] [l])

Takes the first n elements of a list

drop

(drop [n] [l])

Drops the first n elements of a list, returning what's
left

member?

(member? [x] [l])

Checks if an element is a member of a list

range

(range [s] [e])

Returns a list of integers starting with s and going up to
e

dict-items

(dict-items [dict])

Returns a list of key-value pairs from the given dict

random-between

(random-between [s] [e])

Returns a random floating point between s and
e

Open Source

Many thanks goes to the following awesome libraries and open source projects,
and their creators:

mpc.c

ptest.c

linenoise

clang / LLVM

emscripten

JQuery Terminal

Also, thanks goes to the creator of the free “Build Your Own Lisp” online book,
which is what Awl was inspired from.