450 lines language implementation
Note that 150 of these lines are blank or comments, so it's actually more like 300 lines.
by rajeshpillai
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
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...