Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/voithos/awl
Experimental Lispy mini-language
https://github.com/voithos/awl
awl emscripten interpreter lisp-variant toy-language
Last synced: about 1 month ago
JSON representation
Experimental Lispy mini-language
- Host: GitHub
- URL: https://github.com/voithos/awl
- Owner: voithos
- License: mit
- Created: 2014-04-29T09:24:41.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2021-05-02T19:50:04.000Z (over 3 years ago)
- Last Synced: 2024-08-03T18:15:56.385Z (5 months ago)
- Topics: awl, emscripten, interpreter, lisp-variant, toy-language
- Language: C
- Homepage: http://voithos.io/awl
- Size: 1.03 MB
- Stars: 7
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: MIT-LICENSE.txt
Awesome Lists containing this project
- AwesomeInterpreter - awl
README
# 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 profit to learn more about interpreter
design, programming in C, and using
[emscripten](https://github.com/kripken/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.916Recursion:
; 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) 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
$ makeThis 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](http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)):$ ./bin/awl
awl v0.x.y
Ctrl+D to exitawl>
## 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](http://en.wikipedia.org/wiki/Anonymous_function)
- Function
[closures](http://en.wikipedia.org/wiki/Closure_(computer_programming))
- [Partial application](http://en.wikipedia.org/wiki/Partial_application)
- [Tail-call optimization](http://en.wikipedia.org/wiki/Tail_recursion)
- Data immutability
- Lists as the primary data structure
- [Homoiconicity](http://en.wikipedia.org/wiki/Homoiconicity) - that is,
similar representations of code and data
- Metaprogramming in the form of simple macrosCurrently, 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 linePrinting 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
DescriptionInteger
5
,-9
A standard integer (long
)Floating point
-5.
,3.14
A standard floating point (double
)Boolean
true
,false
A standard... booleanString
"\"escapes\" OK"
,'foobar'
A string type - either single or double quotesQ-Symbol
:like-in-ruby
,:'foo'
A quoted symbol (identifier), can also be written similar to stringsQ-Expr
{1 'b' (+ 1 2) x y}
A quoted expression. The basic data structure - acts like a listDictionary
[:x 42 :y 'yes' :z {c}]
A key-value store. Keys are Q-Symbols, values can be anythingFunction
(fn (x) (/ 1 x))
An anonymous function. The basic mechanism of function definitionError
(error 'somebody set up us the bomb')
An error. Stops evaluation### Expressions
Function calls in Awl are defined as
[S-Expressions](http://en.wikipedia.org/wiki/S_expression) (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](http://en.wikipedia.org/wiki/Polish_notation)" of Lisp).awl> (+ 5 6)
11
awl> (println 'foo')
fooWhen 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)
11The 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)
12There 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.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
//
(// [args...])
Truncating division. Removes decimal remainder. Takes 2 or more arguments
%
(% [args...])
Modulo. Takes 2 or more arguments
^
(^ [args...])
Power operator. Takes 2 or more arguments
>
(> [arg1] [arg2])
Greater than. Takes 2 arguments
>=
(>= [arg1] [arg2])
Greater than or equal to. Takes 2 arguments
<
(< [arg1] [arg2])
Less than. Takes 2 arguments
<=
(<= [arg1] [arg2])
Less than or equal to. Takes 2 arguments
==
(== [arg1] [arg2])
Equal to. Tests deep equality. Takes 2 arguments
!=
(!= [arg1] [arg2])
Unequal to. Tests deep equality. Takes 2 arguments
and
(and [arg1] [arg2])
Logical 'and'. Short circuiting. Takes 2 arguments
or
(or [arg1] [arg2])
Logical 'or'. Short circuiting. Takes 2 arguments
not
(not [arg1])
Logical 'not'. Takes 1 argument
head
(head [arg1])
Returns the extracted first element (head) of a list
qhead
(qhead [arg1])
Likehead
, except quotes symbols and S-Exprs
tail
(tail [arg1])
Returns the tail of a list, excluding the first element
first
(first [arg1])
Similar tohead
, but doesn't extract
last
(last [arg1])
Returns the last element of a list, unextracted
except-last
(except-last [arg1])
Returns the first section of a list, excluding the last element
list
(list [args...])
Returns a list containing the evaluated arguments
eval
(eval [arg1])
Evaluates a list as if it were an S-Expression
append
(append [args...])
Concatenates two or more lists
cons
(cons [arg1] [arg2])
Attaches a primitive type to the head of a list
dict-get
(dict-get [dict] [key])
Retrieves a value from a dict using a key
dict-set
(dict-set [dict] [key] [val])
Returns a new dict with a value set
dict-del
(dict-del [dict] [key])
Returns a new dict with a value deleted
dict-haskey?
(dict-haskey? [dict] [key])
Checks if a dict has a key set
dict-keys
(dict-keys [dict])
Returns a list of keys in the dictionary
dict-vals
(dict-vals [dict])
Returns a list of values in the dictionary
len
(len [arg1])
Returns the length of a collection
reverse
(reverse [arg1])
Reverses a collection
slice
(slice [c] [start] [end] [step])
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 totype
, which is represented by a qsym,
as returned bytypeof
import
(import [path])
Attempts to import theawl
file at the given path
(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 forqexpr?
nil?
(nil? [arg1])
Checks that argument isnil
to-str
(to-str [arg1])
Converts argument to a string
to-qsym
(to-qsym [arg1])
Converts argument to a Q-Symbol
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])
Likereduce
, 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 listl
satisfiesf
all
(all [f] [l])
Checks whether all values in listl
satisfyf
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 thenth
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 firstn
elements of a list
drop
(drop [n] [l])
Drops the firstn
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 withs
and going up toe
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 betweens
ande
## 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 TerminalAlso, thanks goes to the creator of the free "Build Your Own Lisp" online book,
which is what Awl was inspired from.