Awesome
Awl
Lisp: from late Old English awlyspian, meaning "to lisp."
Awl is an experimental mini-language based on the Lisp family of programming languages.
Synopsis
It was written for fun and <s>profit</s> 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.
Math:
; Math example
;;
(func (mean l) (/ (sum l) (len l)))
(func (variance l)
(let ((mu (mean l)))
(/
(sum (map (fn (x) (^ (- x mu) 2)) l))
(len l))))
(define xs {1 2 3 4 5 6})
(println (mean xs)) ; prints 3.5
(println (variance xs)) ; prints 2.916
Recursion:
; Recursive cycle
;;
(func (cycle xs)
(let ((f (fn (n)
(let ((i (% n (len xs))))
(cons (head (slice xs i (+ i 1)))
(list (fn () (f (+ n 1)))))))))
(fn () (f 0))))
(define xs {1 2 3 4})
(println ((cycle xs)))
(println ((head (tail ((cycle xs))))))
Mergesort:
; Merge sort
;;
(func (merge-sort l)
(if (<= (len l) 1)
l
(let ((middle (// (len l) 2))
(left (slice l 0 middle))
(right (slice l middle))
(left-sorted (merge-sort left))
(right-sorted (merge-sort right)))
(merge left-sorted right-sorted))))
(func (merge l r)
(if (nil? l)
r
(if (nil? r)
l
(let ((hl (head l))
(hr (head r)))
(if (< hl hr)
(cons hl (merge (tail l) r))
(cons hr (merge l (tail r))))))))
(println (merge-sort {4 3 2 1}))
; prints -> {1 2 3 4}
(println (merge-sort {54 83 1274 83 74 218 9}))
; prints -> {9 54 74 83 83 218 1274}
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) andgcc
(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 needuglifyjs
, 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:
- Dynamic typing
- First-class functions, including anonymous (lambda) functions
- Function closures
- Partial application
- Tail-call optimization
- Data immutability
- Lists as the primary data structure
- Homoiconicity - that is, similar representations of code and data
- Metaprogramming in the form of simple macros
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
<table> <colgroup> <col style="width: 15%;"> <col style="width: 35%;"> <col style="width: 50%;"> </colgroup> <thead> <th>Type</th> <th>Example</th> <th>Description</th> </thead> <tbody> <tr> <td>Integer</td> <td><code>5</code>, <code>-9</code></td> <td>A standard integer (<code>long</code>)</td> </tr> <tr> <td>Floating point</td> <td><code>-5.</code>, <code>3.14</code></td> <td>A standard floating point (<code>double</code>)</td> </tr> <tr> <td>Boolean</td> <td><code>true</code>, <code>false</code></td> <td>A standard... boolean</td> </tr> <tr> <td>String</td> <td><code>"\"escapes\" OK"</code>, <code>'foobar'</code></td> <td>A string type - either single or double quotes</td> </tr> <tr> <td>Q-Symbol</td> <td><code>:like-in-ruby</code>, <code>:'foo'</code></td> <td>A quoted symbol (identifier), can also be written similar to strings</td> </tr> <tr> <td>Q-Expr</td> <td><code>{1 'b' (+ 1 2) x y}</code></td> <td>A quoted expression. The basic data structure - acts like a list</td> </tr> <tr> <td>Dictionary</td> <td><code>[:x 42 :y 'yes' :z {c}]</code></td> <td>A key-value store. Keys are Q-Symbols, values can be anything</td> </tr> <tr> <td>Function</td> <td><code>(fn (x) (/ 1 x))</code></td> <td>An anonymous function. The basic mechanism of function definition</td> </tr> <tr> <td>Error</td> <td><code>(error 'somebody set up us the bomb')</code></td> <td>An error. Stops evaluation</td> </tr> </tbody> </table>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:
awl> (define xs {1 2 3 4})
awl> (define square (map (fn (x) (* x x))))
awl> (square xs)
{1 4 9 16}
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:
awl> (head {1 2 3})
1
awl> (tail {1 2 3})
{2 3}
awl> (define x {* 3 (+ 2 2)})
awl> x
{* 3 (+ 2 2)}
awl> (eval x)
12
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:
awl> (dict-get [:foo 12 :bar 43] :foo)
12
awl> (dict-set [:x 1 :y 2] :z 3)
[:'x' 1 :'y' 2 :'z' 3]
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.
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.
<table> <colgroup> <col style="width: 15%;"> <col style="width: 35%;"> <col style="width: 50%;"> </colgroup> <thead> <th>Symbol</th> <th>Signature</th> <th>Description</th> </thead> <tbody> <tr> <td><code>nil</code></td> <td></td> <td>Alias for <code>{}</code></td> </tr> <tr> <td><code>func</code></td> <td><code>(func ([name] [args]) [body])</code></td> <td>Macro that defines a named function</td> </tr> <tr> <td><code>int?</code></td> <td><code>(int? [arg1])</code></td> <td>Checks that argument is an integer</td> </tr> <tr> <td><code>float?</code></td> <td><code>(float? [arg1])</code></td> <td>Checks that argument is a floating point</td> </tr> <tr> <td><code>str?</code></td> <td><code>(str? [arg1])</code></td> <td>Checks that argument is a string</td> </tr> <tr> <td><code>builtin?</code></td> <td><code>(builtin? [arg1])</code></td> <td>Checks that argument is a builtin</td> </tr> <tr> <td><code>fn?</code></td> <td><code>(fn? [arg1])</code></td> <td>Checks that argument is a user-defined function</td> </tr> <tr> <td><code>macro?</code></td> <td><code>(macro? [arg1])</code></td> <td>Checks that argument is a macro</td> </tr> <tr> <td><code>bool?</code></td> <td><code>(bool? [arg1])</code></td> <td>Checks that argument is a boolean</td> </tr> <tr> <td><code>qexpr?</code></td> <td><code>(qexpr? [arg1])</code></td> <td>Checks that argument is a Q-Expression</td> </tr> <tr> <td><code>dict?</code></td> <td><code>(dict? [arg1])</code></td> <td>Checks that argument is a Dictionary</td> </tr> <tr> <td><code>list?</code></td> <td><code>(list? [arg1])</code></td> <td>Alias for <code>qexpr?</code></td> </tr> <tr> <td><code>nil?</code></td> <td><code>(nil? [arg1])</code></td> <td>Checks that argument is <code>nil</code></td> </tr> <tr> <td><code>to-str</code></td> <td><code>(to-str [arg1])</code></td> <td>Converts argument to a string</td> </tr> <tr> <td><code>to-qsym</code></td> <td><code>(to-qsym [arg1])</code></td> <td>Converts argument to a Q-Symbol</td> </tr> <tr> <td><code>do</code></td> <td><code>(do [expr1] [expr2] ... [exprn])</code></td> <td>Evaluates its arguments one by one, and returns the result of the last argument</td> </tr> <tr> <td><code>compose</code></td> <td><code>(compose [f] [g] [xs...])</code></td> <td>Composes two functions</td> </tr> <tr> <td><code>flip</code></td> <td><code>(flip [f] [x] [y])</code></td> <td>Takes a function and two argument, and flip the ordering of the arguments</td> </tr> <tr> <td><code>id</code></td> <td><code>(id [x])</code></td> <td>The identity function, returns whatever is passed</td> </tr> <tr> <td><code>reduce</code></td> <td><code>(reduce [f] [l] [acc])</code></td> <td>Reduces a list to a single value using a reducer function</td> </tr> <tr> <td><code>reduce-left</code></td> <td><code>(reduce-left [f] [l] [acc])</code></td> <td>Like <code>reduce</code>, but traverses the list in the opposite direction</td> </tr> <tr> <td><code>map</code></td> <td><code>(map [f] [l])</code></td> <td>Applies a function to each element of a list</td> </tr> <tr> <td><code>filter</code></td> <td><code>(filter [f] [l])</code></td> <td>Uses a predicate function to filter out elements from a list</td> </tr> <tr> <td><code>any</code></td> <td><code>(any [f] [l])</code></td> <td>Checks whether any value in list <code>l</code> satisfies <code>f</code></td> </tr> <tr> <td><code>all</code></td> <td><code>(all [f] [l])</code></td> <td>Checks whether all values in list <code>l</code> satisfy <code>f</code></td> </tr> <tr> <td><code>sum</code></td> <td><code>(sum [l])</code></td> <td>Sums elements of a list</td> </tr> <tr> <td><code>product</code></td> <td><code>(product [l])</code></td> <td>Multiplies together elements of a list</td> </tr> <tr> <td><code>pack</code></td> <td><code>(pack [f] [args...])</code></td> <td>Takes multiple argument and feeds it to a function as a single list argument</td> </tr> <tr> <td><code>unpack</code></td> <td><code>(unpack [f] [l])</code></td> <td>Evaluates a function using a list of arguments</td> </tr> <tr> <td><code>nth</code></td> <td><code>(nth [n] [l])</code></td> <td>Returns the <code>nth</code> element of a list</td> </tr> <tr> <td><code>zip</code></td> <td><code>(zip [lists...])</code></td> <td>Returns a list of lists, each containing the i-th element of the argument lists</td> </tr> <tr> <td><code>take</code></td> <td><code>(take [n] [l])</code></td> <td>Takes the first <code>n</code> elements of a list</td> </tr> <tr> <td><code>drop</code></td> <td><code>(drop [n] [l])</code></td> <td>Drops the first <code>n</code> elements of a list, returning what's left</td> </tr> <tr> <td><code>member?</code></td> <td><code>(member? [x] [l])</code></td> <td>Checks if an element is a member of a list</td> </tr> <tr> <td><code>range</code></td> <td><code>(range [s] [e])</code></td> <td>Returns a list of integers starting with <code>s</code> and going up to <code>e</code></td> </tr> <tr> <td><code>dict-items</code></td> <td><code>(dict-items [dict])</code></td> <td>Returns a list of key-value pairs from the given dict</td> </tr> <tr> <td><code>random-between</code></td> <td><code>(random-between [s] [e])</code></td> <td>Returns a random floating point between <code>s</code> and <code>e</code></td> </tr> </tbody> </table>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.