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...