Well, after a couple false starts, I finally got a Nulan interpreter written in Racket working. Oh, and, ignore the README, it's super duper outdated and I haven't updated it yet.
You can use "./nulan" to get to a REPL. Near the bottom of "01 nulan.rkt" you can see all the stuff that's currently supported:
* An awful lot of functionality is customizable via objects. That includes custom pattern matching. I plan to make more things customizable as time goes on.
---
I found that writing it as an interpreter was an awful lot easier than trying to write it as a compiler, because I discovered that the way I was doing pattern matching made it impossible to determine which variables in a vau are free.
How it works is... it's passed three arguments: the environment, the pattern, and the value. It returns a new environment which is the result of matching the pattern to the value.
But it doesn't do an awful lot: it handles symbols, the wildcard ~, lists use the %pattern-match function of an object, and everything else just uses "eq?".
So, for instance, the "list" pattern matching is implemented here:
It's actually a lot simpler than it looks. It just recurses down the pattern and value, calling "pattern-match" on each element of the lists. Then, at the end, if either the pattern or value is not null, there was a mismatch, so it throws an error.
The argument list of a vau obviously uses the "pattern-match" function, but another place it's used is "def":
See how simple that is? It simply unboxes the dynamic environment, runs it through the pattern matcher, then assigns it to the dynamic environment[1].
With that small amount of code, the following now works:
(def (list a b) (list 1 2))
Which will bind "a" to 1 and "b" to 2. And as said, it's completely customizable: just slap a %pattern-match property onto any object.
And because Nulan is a vau-based language with first-class environments, you can use "def" inside vaus to create local bindings:
(fn ()
(def a 5)
a)
This is like "var" in JavaScript only much better.
---
* [1]: The reason the Racket version is a bit funky is because the "pattern-match" function is written in Nulan style. Here's how "def" would be written in Nulan:
(def def
(vau e {n v}
(let v (eval e v)
(set! e (pattern-match (unbox e) n v))
v)))