Skip to content

Instantly share code, notes, and snippets.

@StanAngeloff
Created August 1, 2011 18:41
Show Gist options
  • Save StanAngeloff/1118731 to your computer and use it in GitHub Desktop.
Save StanAngeloff/1118731 to your computer and use it in GitHub Desktop.
A very tiny Lisp-like implementation in JavaScript
// Based on http://www.flownet.com/ron/lisp/l.py
//
this.TinyLisp = (function(code, env) {
function reduce(array, block) {
var previous = array[0];
for (var i = 1, length = array.length - 1; i < length; i ++) {
previous = block(array[i], previous);
}
return previous;
};
var globalenv = {
'+': function() { return reduce(arguments, function(value, previous) { return previous + value; }); },
'-': function() { return reduce(arguments, function(value, previous) { return previous - value; }); },
'*': function() { return reduce(arguments, function(value, previous) { return previous * value; }); },
'/': function() { return reduce(arguments, function(value, previous) { return previous / value; }); },
'>': function(left, right) { return left > right; },
'>=': function(left, right) { return left >= right; },
'<=': function(left, right) { return left <= right; },
'<': function(left, right) { return left < right; },
'if': function(cond, left, right) { return (cond ? left : right); },
'->': function() {
var args = Array.prototype.slice.call(arguments), env = args.pop();
for (var i = 0, length = args.length; i < length; i ++) {
env = env[args[i]];
}
return env;
}
};
function evaluate(tokens, env) {
if (Object.prototype.toString.apply(tokens) === "[object Array]" && tokens.length > 0) {
for (var i = 0, length = tokens.length, token, apply; token = tokens[i], i < length; i ++) {
tokens[i] = evaluate(token, env);
}
apply = (env[tokens[0]] || tokens[0]);
if (typeof (apply) !== 'function') {
apply = function() { return tokens[0]; };
}
tokens = apply.apply(this, tokens.slice(1).concat([env]));
}
return tokens;
};
function transform(token) {
if (token.indexOf('"') === 0) {
return token.substring(1, token.length - 1);
}
var number = parseFloat(token);
if (isNaN(number)) {
return token;
}
return number;
};
function tokenize(code) {
var tokens = [], queue = '';
for (var i = 0, length = code.length, char; char = code.charAt(i), i < length; i ++) {
if (char === '(' || char === ')' || /\s/.exec(char)) {
if (queue.length) {
tokens.push(queue);
}
if (char.replace(/\s+/g, '').length) {
tokens.push(char);
}
queue = '';
} else {
queue = queue + char;
}
}
return tokens;
};
function parse(code) {
var tokens = tokenize(code),
result = [], stack = [result], tmp;
for (var i = 0, length = tokens.length, token; token = tokens[i], i < length; i ++) {
if (token === '(') {
tmp = [];
stack[0].push(tmp);
stack = [tmp].concat(stack);
} else if (token === ')') {
if (stack.length) {
stack = stack.slice(1);
}
} else {
stack[0].push(transform(token));
}
}
return result;
};
function run(code, env) {
env || (env = {});
for (var key in globalenv) {
env[key] = globalenv[key];
}
return evaluate(parse(code), env);
};
return run(code, env);
});
@StanAngeloff
Copy link
Author

Usage

console.log('result: %s', TinyLisp('(+ 1 (* 2 4 (/ 8 16)))'));
// ==> result: 5

console.log('result: %s', TinyLisp('(if (+ 0 1) "true" "false")'));
// ==> result: true

console.log('result: %s', TinyLisp('(if (- 1 1) "true" "false")'));
// ==> result: false

console.log('result: %s', TinyLisp('(if (-> external fn) "true" "false")', {
  external: {
    fn: function() { return true; }
  }
}));
// ==> result: true

TinyLisp('((-> external alert) "Hello" "World!")', {
  external: {
    alert: function() { alert(Array.prototype.slice.call(arguments, 0, -1).join(' ')); }
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment