How to install
git clone --recursive https://github.com/Pauan/nulan.git nulan
How to run
Browser
Load doc/tutorial.html
in a modern web browser (I've only tested with Google Chrome 23)
Node.js
Use ./nulan
to get to a REPL (I've only tested with version 0.8.5)
Features
- A full-blown Lisp, which means programs are parsed as S-expressions, and there's a very heavy emphasis on "code is data is code"
- Pattern matching
- Hyper-static scope at both the global and function level
- Hygienic macros which are even easier to use than Arc/Common Lisp macros
- Customizable syntax to make common idioms shorter and easier to read
- The compiler is written in JavaScript and is capable of running in a browser: you can incrementally compile/eval Nulan programs at runtime
- Compiles ahead-of-time to extremely fast JavaScript: it should be just as fast or faster than handwritten JS code
- Includes an awesome IDE (built using CodeMirror) that runs right in the browser: just open
doc/tutorial.html
Examples
# This is a dictionary var foo = { bar 5 qux 10 } # Key lookup foo.bar foo.qux # Key assignment foo.bar <= 20 foo.qux <= 30 # This is a function def set -> a | var x = foo[a] # Lookup by expression | foo[a] <= 50 # Assign by expression | x set "bar" set "qux" foo.bar foo.qux
# Simulating a `for` loop using a `while` loop $mac for -> init test incr body 'w/new-scope | init | while test | body | incr for (var i = 0) (i < 10) (++ i) prn i
# Simulating a `do..while` loop using a `while` loop $mac do -> body [('while) test] 'while true | body | if ~ test break; var i = 0 do | prn i | ++ i while (i < 10)
# Infinite loop; be careful, the only way to stop it is to shut down the terminal! $mac 5ever -> body 'while true body 5ever | prn 1 | prn 2 | prn 3 | prn 4 | prn "das mor den 4ever"
# Macro to iterate over the elements of any list or string $mac w/each -> [('=) x y] body w/uniq i len w/complex y 'w/var len = y.length for (var i = 0) (i ~= len) (++ i) w/var x = y[i] body w/each x = [1 2 3] | prn x | prn x + 5 | prn;
# Macro to iterate over the elements of any list or string in reverse order $mac w/each-rev -> [('=) x y} bod] w/uniq i w/complex y 'w/var i = y.length while i w/var x = y[-- i] body w/each-rev x = [1 2 3] | prn x | prn x + 5 | prn;
# The built-in Array methods work very nicely with Nulan's -> syntax [1 2 3].forEach -> x | prn x | prn x + 5 | prn; [1 2 3].map -> x x + 5 [1 2 3].reduce -> x y "(@x @y)"
# An example of an unhygienic macro # Just like in Arc, it binds the symbol `it` to the test condition $mac aif -> test @rest w/sym it 'w/var it = test if it ,@:if rest.length >= 2 w/var [x @rest] = rest 'x (aif ,@rest) rest aif 1 + 2 it it aif false it it
def foo -> x y x + y $syntax-infix foo 1 foo 2 # Custom infix syntax \foo 1 2 # Use \ to disable syntax
# Array comprehensions var in $mac for -> x [('in) n y] 'y.map -> n x $syntax-infix for 0 { order "right" } $syntax-infix in 0 { order "right" } (x + 2) for x in [1 2 3]
#! /usr/bin/env nulan # A shell script that creates a simple HTTP server # Taken from http://nodejs.org/ var net = require "net" var server = net.create-server -> o | o.write "Echo server\r\n" | o.pipe o server.listen 1337 "127.0.0.1"
FAQ
Q: Why doesn't this work?!
def foo -> x bar x + 1 def bar -> x x + 5 foo 20
A: Nulan uses hyper-static scope, so you need to rearrange it so
foo
is defined afterbar
:def bar -> x x + 5 def foo -> x bar x + 1 foo 20
Q: Well, okay, but what about this?
$mac foo -> '1 + 2 prn foo
A: Nulan has a very strict separation between compile-time and run-time: things that exist at compile-time cannot be used at run-time in any way, shape, or form. And vice versa: things that exist at run-time cannot be used at compile-time.
Certain macros like
$mac
are prefixed with$
which indicates that they are evaluated at compile-time. To make the above example work, you have to evaluate the expression at compile-time by using$run
:$mac foo -> '1 + 2 $run prn foo
Q: If there's such a strict separation between the two, why does this work?
def foo -> x x + 1 $mac bar -> x 'foo x bar 10
A: Nulan replaces symbols with boxes. The value of the symbol
foo
is not available, but the box is.The
'
macro returns boxes, which means that thebar
macro returns the box forfoo
, not the value forfoo
. This is the only way that you can use run-time stuff at compile-time.However, this would not work...
$mac bar -> x foo x
...because it's trying to use the value of the
foo
symbol, which doesn't exist at compile-time.In addition, if a macro is the first element of a list, it is evaluated at compile-time, which is why
bar 10
works. Butprn bar 10
would not work, because the macrobar
isn't the first element of the list