Teacup

A short implementation of a simple programming language.

HTML

<div id="examples">
<textarea class="ex"></textarea>
<textarea class="ex">
# Fibonacci
let
    fib(n) = begin
        if n == 0 then
            0
        elif n == 1 then
            1
        else
            fib(n - 1) + fib(n - 2)
        end
    end
in
    fib(10)
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
    for i in 1..10 do
        log(i + " is " + if even(i) then "even" else "odd" end)
    end
    "done"
end
</textarea>
<textarea class="ex">
let square(x) = x * x in
    for x in [1, 2, 3, 4, 5] do
        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>
<textarea class="ex">
# Test closures on iteration variable
let fns = for i in 1..10 do
              x -> x + i
          end
in
    for fn in fns do
        fn(3)
    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;
}

h3 {
  margin-bottom: 0px;
}

.inner {
  display: inline-block;
  border: 1px solid #888;
  border-radius: 8px;
  padding: 3px;
  margin: 3px;
}

.word {
  color: #a55;
  font-weight: bold;
  padding: 2px;
}

.number {
  color: #008;
  font-weight: bold;
  padding: 2px;
}

.string {
  color: #080;
  font-weight: bold;
  padding: 2px;
}

.op {
  padding: 3px;
}

JavaScript

//////////////////////////////////////////
// CORE LANGUAGE-BUILDING FUNCTIONALITY //
//////////////////////////////////////////

// PIPELINE

function Pipeline(...steps) {
    this.steps = steps;
}

Pipeline.prototype.process = function (x) {
    for (var step of this.steps) {
        if (typeof(step) === "function")
            x = step(x);
        else
            x = step.process(x);
    }
    return x;
}

// LEXER

function Lexer(tokenDefinitions) {
    // Build a big ass regular expression
    var keys = Object.keys(tokenDefinitions);
    var regexps = keys.map(k => tokenDefinitions[k]);
    this.re = new RegExp("(" + regexps.join(")|(") + ")");
    // this.types associates each group in the regular expression
    // to a token type (group 0 => not matched => null)
    this.types = [null].concat(keys);
}

Lexer.prototype.process = function(text) {
    var pos = 0;
    // Splitting with a regular expression inserts the matching
    // groups between the splits.
    return text.split(this.re)
        .map((token, i) => ({
            type: this.types[i % this.types.length], // magic!
            text: token,
            start: pos,
            end: pos += (token || "").length
        }))
        .filter(t => t.type && t.type !== "comment" && t.text) // remove empty tokens
}


// PARSER

function Parser(priorities, finalize) {
    this.priorities = Object.assign(Object.create(null), priorities);
    this.finalize = finalize;
}

Parser.prototype.getPrio = function (t) {
    var x = this.priorities[t.type + ":" + t.text]
         || this.priorities[t.text]
         || this.priorities["type:" + t.type]
    if (x) return x;
    else throw SyntaxError("Unknown operator: " + t.text);
}

Parser.prototype.order = function (a, b) {
    if (!a && !b) return "done";
    if (!a) return 1;
    if (!b) return -1;
    var pa = this.getPrio(a).left;
    var pb = this.getPrio(b).right;
    return Math.sign(pb - pa);
}

Parser.prototype.process = function (tokens) {
    tokens...