Skip to content

Instantly share code, notes, and snippets.

@Enichan
Last active December 16, 2021 21:38
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Enichan/4a9fa87aef6405e13e1c072baa117beb to your computer and use it in GitHub Desktop.
Save Enichan/4a9fa87aef6405e13e1c072baa117beb to your computer and use it in GitHub Desktop.
Interpretoy: a toy lisp interpreter with REPL built in javascript
<html>
<head>
<title>Interpretoy REPL</title>
</head>
<body>
<script>
// based on lispy by Peter Norvig: http://norvig.com/lispy.html
function load(source) {
return parse(tokenize(source));
}
function tokenize(source) {
return source.replaceAll("'", " ' ").replaceAll("(", " ( ").replaceAll(")", " ) ").replaceAll("\n", "").replaceAll("\r", "").replaceAll("\t", "").split(" ").filter(i => i);
}
function parse(tokens) {
if (!tokens.length) {
throw new Error("Unexpected EOF");
}
let token = tokens.shift();
if (token === "(") {
let l = [];
while (tokens[0] !== ")") {
l.push(parse(tokens));
}
tokens.shift();
return l;
}
else if (token === "'") {
let next = parse(tokens);
return [ "quote", next ];
}
else if (token === ")") {
throw new Error("Unexpected ')'");
}
else {
return atom(token);
}
}
function atom(token) {
let num = token * 1;
if (num || num === 0) {
return num;
}
return token;
}
function toLispString(l, s) {
return [l].map(function join(v) {
return Array.isArray(v) ? "(" + v.map(join).join(" ") + ")" : v;
}).join(" ");
}
function makeEnv() {
var env = new Env();
Object.getOwnPropertyNames(Math).forEach(property => {
if(typeof Math[property] === 'function') {
env[property] = Math[property];
}
});
[ ["+"], ["-"], ["*"], ["/"], ["<"], ["<="], [">"], [">="], [ "=", "===" ], [ "|" ], [ "&" ], [ "<<" ], [ ">>" ]].forEach(op => {
env[op[0]] = new Function("lhs", "rhs", `return lhs ${op[1] || op[0]} rhs;`);
});
Object.assign(env, {
append: (x, y, ...args) => x.concat(y).concat(...args),
apply: (f, x) => f.apply(f, x),
begin: (...args) => Array.isArray(args) ? args[args.length - 1] : undefined,
car: (x) => x[0],
cdr: (x) => x.slice(1),
cons: (x, y) => [x].concat(y),
display: (...args) => console.log(...args),
"eq?": (x, y) => x === y,
//"equal?" list contents equality D:
length: (x) => x.length,
list: (...args) => args,
"list?": (x) => Array.isArray(x),
map: (f, x) => x.map(f),
not: (x) => x === false ? true : false,
"null?": (x) => (Array.isArray(x) && x.length == 0) || (typeof x === "undefined") || (x === null),
"number?": (x) => typeof x === "number",
"procedure?": (x) => typeof x === "function",
reverse: (x) => x.reverse(),
"symbol?": (x) => typeof x === "string",
});
return env;
};
class Env {
constructor(parms, args, outer) {
if (typeof parms === "undefined") parms = []
if (typeof args === "undefined") args = []
parms.map((k, i) => this[k] = args[i]);
this.outer = outer;
}
find(symbol) {
if (typeof this[symbol] !== "undefined") {
return this;
}
if (this.outer) {
return this.outer.find(symbol);
}
throw new Error(`Symbol ${symbol} undefined`);
}
}
var g = makeEnv();
function makeProc(parms, body, env) {
return function(...args) {
return interp(body, new Env(parms, args, env));
};
}
function interp(x, env) {
env = env || g;
if (typeof x === "string") { // symbol
return env.find(x)[x];
}
else if (!Array.isArray(x)) { // constant literal
return x;
}
else if (x[0] === "quote") { // (quote exp)
let exp = x[1];
return exp;
}
else if (x[0] === "if") { // (if test conseq alt)
let test = x[1], conseq = x[2], alt = x[3];
let exp = interp(test, env) ? conseq : alt;
return interp(exp, env);
}
else if (x[0] === "define") { // (define symbol exp)
let symbol = x[1], exp = x[2];
env[symbol] = interp(exp, env);
}
else if (x[0] === "set!") { // (set! symbol exp)
let symbol = x[1], exp = x[2];
return (env.find(symbol)[symbol] = interp(exp, env));
}
else if (x[0] === "eval") { // custom shenanigans
let exp = interp(x[1], env);
return interp(exp, env);
}
else if (x[0] === "lambda") { // (lambda (symbol...) body)
let parms = x[1], body = x[2];
return makeProc(parms, body, env);
}
else {
let proc = interp(x[0], env);
let args = x.slice(1).map(exp => interp(exp, env));
if (typeof proc !== "function") {
throw new Error("Expected function, got " + (proc || "").toString());
}
return proc.apply(proc, args);
}
}
function repl(source, out) {
out.innerHTML += "<br>";
out.innerHTML += "> " + source;
let val;
try {
val = interp(load(source));
}
catch (e) {
val = "ERROR: " + e.message;
}
if (typeof val !== "undefined") {
out.innerHTML += "<br>";
out.innerHTML += toLispString(val);
}
}
function replSubmit(e) {
e = e || window.event;
if (e.keyCode == 13) {
let elem = e.srcElement || e.target;
repl(elem.value, document.getElementById("replOut"));
elem.value = "";
elem.scrollIntoView();
}
}
</script>
<div id="replOut">REPL:<br></div>
<br>
<input type="text" size="50" onkeydown="replSubmit()"/>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment