Lispy.js with ZEN style.

//Remove all white spaces at the beginning and end of the string
//From Douglas Crockford's 'Javascript: The Good Parts'
String.prototype.trim = function () {
    return this.replace(/^s+|s+$/g, '');
};

//This is the equivalent of Python's str.split(None) method
//which has a different splitting algorithm when None is passed as a parameter.
//http://docs.python.org/library/stdtypes.html#str.split
String.prototype.pysplit = function () {
    return this.replace(/s+/g, ' ').trim().split(' ');
};


Math.add = function (a, b) {
    return a + b;
};

Math.sub = function (a, b) {
    return a - b;
};

Math.mul = function (a, b) {
    return a * b;
};

Math.div = function (a, b) {
    return a / b;
};

Math.gt = function (a, b) {
    return a > b;
};

Math.lt = function (a, b) {
    return a < b;
};

Math.ge = function (a, b) {
    return a >= b;
};

Math.le = function (a, b) {
    return a <= b;
};

Math.eq = function (a, b) {
    return a === b;
};

Math.mod = function (a, b) {
    return a % b;
};

//################ Symbol, Procedure, Env classes
var Symbol = String;

var environment = function (spec) {
    var i, env = {}, outer = spec.outer || {};
	
    var get_outer = function () {
		return outer;
    };
	
    var find = function (variable) {
		if (env.hasOwnProperty(variable)) {
			return env;
		} else {
            return outer.find(variable);
        }
    };
    
    if (0 !== spec.params.length) {
        for (i = 0; i < spec.params.length; i += 1) {
            env[spec.params[i]] = spec.args[i];
        }
    }

    env.get_outer = get_outer;
    env.find = find;
    
    return env;
};


var add_globals = function (env) {
    //Cannot use for..in on built-in objects like Math in JS.
    //So need to include all methods manually
    var mathMethods = ['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan'], i;

    for (i = 0; i < mathMethods.length; i += 1) {
        env[mathMethods[i]] = Math[mathMethods[i]];
    }
    env['+'] = Math.add;
    env['-'] = Math.sub;
    env['*'] = Math.mul;
    env['/'] = Math.div;
    env['>'] = Math.gt;
    env['<'] = Math.lt;
    env['>='] = Math.ge;
    env['<='] = Math.le;
    env['='] = Math.eq;
	env['remainder'] = Math.mod;
    env['equal?'] = Math.eq;
    env['eq?'] = Math.eq; //'eq?':op.is_ ;Need to find Object Equality operator in JS
	env['length'] = function (x) { return x.length; };
	env['cons'] = function (x, y) { var arr = [x]; return arr.concat(y); };
    env['car'] = function (x) { return (x.length !== 0) ? x[0] : null; };
    env['cdr'] = function (x) { return (x.length > 1) ? x.slice(1) : null; }; 
	env['append'] = function (x, y) { return x.concat(y); };
    env['list'] = function () { return Array.prototype.slice.call(arguments); }; //'list':lambda *x:list(x)
	env['list?'] = function (x) { return x && typeof x === 'object' && x.constructor === Array ; }; //'list?': lambda x:isa(x,list)
	env['null?'] = function (x) { return (!x || x.length === 0); };
	env['symbol?'] = function (x) { return typeof x === 'string'; };
    return env;
};

var global_env = add_globals(environment({params: [], args: [], outer: undefined}));

//################ eval
var eval = function (x, env) {
	env = env || global_env;
	return ((analyze(x)) (env));
};

//################ analyze
var analyze = function (x) {
    if (typeof x === 'string') {	//variable reference
        return function (env) { return env.find(x.valueOf())[x.valueOf()];};
    } else if (typeof x === 'number') {	//constant literal
        return function (env) { return x; };
    } else if (x[0] === 'quote') {	//(quote exp)
		var qval = x[1];
        return function (env) { return  qval; };
    } else if (x[0] === 'if') {		//(if test conseq alt)
		return function (pproc, cproc, aproc) {
			return function (env) { 
						if (pproc(env)) {
							return cproc(env);
						} else {
							return aproc(env);
						}
					};		
		}(analyze(x[1]), analyze(x[2]), analyze(x[3]));
    } else if (x[0] === 'set!') {			//(set! var exp)
		return function (vvar, vproc) {
			return function (env) { env.find(vvar)[vvar] = vproc(env); };
		}(x[1], analyze(x[2]));
    } else if (x[0] === 'define') {	//(define var exp)
		return function (vvar, vproc) {
			return function (env) { env[vvar] = vproc(env); };
		}(x[1], analyze(x[2]));
    } else if (x[0] === 'lambda') {	//(lambda (var*) exp)
		return analyze_lambda(x);
    } else if (x[0] === 'begin') {	//(begin exp*)
		x.shift();
		return analyze_sequence(x);
    } else {				//(proc exp*)
		var aprocs = x.map(analyze);
		var fproc = aprocs.shift();	
		return function (env) {
			var opprocs = aprocs.map(function (aproc) {return aproc(env);});
			return fproc(env).apply(env, opprocs);
		};
    }
};

var analyze_lambda = function (x) {
	var vars = x[1];
	var bproc = analyze_sequence([x[2]]);
	return function (env) {
        return function () {
	        return bproc(environment({params: vars, args: arguments, outer: env }));
        };		
	};
};

var analyze_sequence = function (x) {
	var procs = x.map(analyze);
	return function (env) {
		var result;
		var i;
		for (i = 0; i < procs.length; i += 1) {
			result = procs[i](env);
		}
		return result;
	};
};

//################ parse, read, and user interaction
var atom = function (token) {
    if (isNaN(token)) {
		return token;
    } else {
		return +token; //Cast to number. Nice trick from Douglas Crockford's Javascript: The Good Parts
    }
};

var tokenize = function (s) {
    return s.replace(/(/g, ' ( ').replace(/)/g, ' ) ').pysplit();
};

var read_from = function (tokens) {
    if (0 === tokens.length) {
		throw {
			name: 'SyntaxError',
			message: 'unexpected EOF while reading'
		};
	}
    var token = tokens.shift();
    if ('(' === token) {
		var L = [];
        while (')' !== tokens[0]) {
            L.push(read_from(tokens));
        }
        tokens.shift(); // pop off ')'
        return L;
    } else {
		if (')' === token) {
			throw {
				name: 'SyntaxError',
				message: 'unexpected )'
			};
		} else {
			return atom(token);
		}
    }
};

var read = function (s) {
    return read_from(tokenize(s));
};

var parse = read;

var to_string = function (exp) {
};

var debug = function (s) {
    try {
		document.getElementById('debugdiv').innerHTML = eval(parse(s));
    } catch (e) {
		document.getElementById('debugdiv').innerHTML = e.name + ': ' + e.message;
    }
};

Posted on 2011-12-27 13:53 with js in 1.345 sec.