450 lines language implementation

Note that 150 of these lines are blank or comments, so it's actually more like 300 lines.

HTML

<div id=examples>
<textarea class=ex>
</textarea>
<textarea class=ex>
match [1, 2, [3, 4]] with
    [a, b] -> a + b
    [a] -> a
    [a, b, [c, d]] -> a + b + c + d
    [a, b, c] -> a + b + c
    [1234, xyz, "hello"] -> xyz
end
</textarea>
<textarea class=ex>
# Arithmetic precedence
10 + 5 * 7 - 2^3^2
</textarea>
<textarea class=ex>
let
    odd(x) = if x == 0 then false else even(x - 1) end
    even(x) = if x == 0 then true else odd(x - 1) end
in
    1..10 each i -> print(
        i + " is " + if even(i) then "even" else "odd" end
    )
    true
end
</textarea>
<textarea class=ex>
let square(x) = x * x in
    [1, 2, 3, 4, 5] each x -> begin
        square(x)
    end
end
</textarea>
<textarea class=ex>
# You can define arbitrary operators. They have high priority by default,
# but you can specify a different priority in the config object.
let x *** y = (x*x + y*y)^(1/2) in
    3 *** 4
end
</textarea>
<textarea class=ex>
# This is a test for closures
let x = 1, f(y) = x + y in
    let x = 2 in
        [f(x), x]
    end
end
</textarea>
</div>
<textarea id=expr>
</textarea>
<div><button id=evaluate>Evaluate</button></div>
<h3>Result</h3>
<div id=result>???</div>
<h3>AST</h3>
<div id=ast></div>

CSS

#expr {
    width: 100%;
    height: 300px;
}

body, textarea, button {
  font-size: 12pt;
  font-family: monospace;
}
.hover {
  background-color: #eee;
}
.hover .inner {
  background-color: #fff;
}

h3 {
  margin-bottom: 0px;
}

.inner {
  display: inline-block;
  border: 1px solid #888;
  border-radius: 8px;
  padding: 3px;
  margin: 3px;
}
.id {
  color: #a55;
  font-weight: bold;
  padding: 2px;
}
.num {
  color: #008;
  font-weight: bold;
  padding: 2px;
}
.str {
  color: #080;
  font-weight: bold;
  padding: 2px;
}
.op {
  padding: 3px;
}

JavaScript

///////////////////
/// PARSER CORE ///
///////////////////

// There is an annotated configuration object in the GRAMMAR section below
function Parser(config) {
    var prio = this.priorities = config.priorities;
    prio["boundary:$"] = [-1, -1];
    this.re = config.re;
    this.toktypes = config.toktypes;
    config.blocks.forEach(function (defs) {
        var parts = defs.split(" ");
        prio[parts.shift()] = [10000, 0];
        prio[parts.pop()] = [0, 10001, true];
        parts.forEach(function (part) {prio[part] = [0, 0];});
    });
    var level = 5;
    config.tower.forEach(function (ops) {
        ops[1].split(" ").forEach(function (op) {
            var pfx = op.substring(0, 2) === "P:";
            prio[op] = [pfx ? 10000 : level, level - ops[0]];
            if (pfx && !prio[op.substring(2)])
                prio[op.substring(2)] = prio[op];
        });
        level += 5;
    });
}

// The return value of tokenize("a + 6 * 10") is:
//   [{token: "$", type: "boundary"},
//    {token: "a", type: "id"},
//    {token: "+", type: "op"},
//    {token: "6", type: "num"},
//    {token: "*", type: "op"},
//    {token: "10", type: "num"},
//    {token: "$", type: "boundary"}]
// In everything that follows, tok(a) will stand for {token: "a", type: "id"},
// tok(*) for {token: "*", type: "op"}, and so on.
Parser.prototype.tokenize = function (text) {
    var m; var last = "op";
    var results = [{token: "$", type: "boundary"}];
    while (m = this.re.exec(text)) {
        var type = this.toktypes[m.slice(1).indexOf(m[0])];
        if (type === "comment") continue;
        var tok = {token: m[0], type: type};
        // An "op" token followed by an "op" token makes the second prefix
        // unless the first is marked as suffix.
        if (last.type === "op" && type === "op" && !this.getPrio(last)[2])
            tok.prefix = true;
        results.push(tok);
        last = tok;
    }
    results.push({token: "$", type: "boundary"});
    return...