walley-language
Version: 
walley language (toy) is a simple script language ;)
1,447 lines (1,400 loc) • 78.8 kB
JavaScript
/*
    Toy Language Interpreter
*/
/*
=========================================================
=========================================================
=========================================================
==== The following functions require nodejs support =====
=========================================================
=========================================================
=========================================================
*/
var readStringFromFile; // read string from file function, return string
var writeStringToFile; // write string to file
var getCurrentDirectory; // get current directory
var RunningFileDirectory = "";
var resolveDirectory = function(param)
{
    /*
        eg
        toy /a/test.toy
        then dir is /a/
        toy ./test.toy
        dir is process.cwd()+"/"
    */
    for(var i = param.length-1; i>=0; i--)
    {
        if(param[i] === "/")
            break;
    }
    if(param[0] === "/")
    {
        RunningFileDirectory = param.slice(0, i+1);
    }
    else
    {
        RunningFileDirectory = process.cwd() + param.slice(1, i+1);
    }
    // console.log("RunningFileDirectory: " + RunningFileDirectory);
}
// var systemCommand; // call system command
// check node
if(typeof(require) === 'function')
{
    var fs = require("fs");
    var path = require("path");
    var sys = require('sys')
    var exec = require('child_process').exec;
    var out_;
    readStringFromFile = function(file_name)
    {
        return fs.readFileSync(path.resolve(/*__dirname*//*process.cwd()*/RunningFileDirectory, file_name),"utf8");
    };
    writeStringToFile = function(file_name, data)
    {
        fs.writeFile(path.resolve(__dirname, file_name), data);
        return "undefined";
    };
    getCurrentDirectory = function()
    {
        return __dirname;
    };
    /*
    function puts(error, stdout, stderr) { if(error!=null){console.log("EXEC ERROR: \n" + error); return;}; console.log(stdout); console.log(stderr); out_= stdout;};
    systemCommand = function(cmd)
    {
        exec(cmd, puts);
        return out_;
    };
    */
}
/*
=========================================================
=========================================================
=========================================================
=========================================================
=========================================================
=========================================================
=========================================================
=========================================================
*/
var LIST = 2;
var MACRO = 4;
var PROCEDURE = 6;
var BUILTIN_PRIMITIVE_PROCEDURE = 8;
var RATIO = 10;
var FLOAT = 12;
// build List
var Cons = function(x, y)
{
    this.car = x;
    this.cdr = y;
    this.set_car = function(value)
    {
        this.car = value;
    }
    this.set_cdr = function(value)
    {
        this.cdr = value;
    }
    this.TYPE = LIST  // for virtual machien check
}
// build procedure
var Procedure = function(args, body, closure_env, docstring)
{
    this.args = args;
    this.body = body;
    this.closure_env = closure_env;
    this.TYPE = PROCEDURE;
    this.docstring = docstring;
}
// build macro
var Macro = function(args, body, closure_env)
{
    this.args = args;
    this.body = body;
    this.closure_env = closure_env;
    this.TYPE = MACRO;
}
// build primitive builtin procedure
var Builtin_Primitive_Procedure = function(func)
{
    this.func = func;
    this.TYPE = BUILTIN_PRIMITIVE_PROCEDURE;
}
var Toy_Number = function(numer, denom, type)
{
    this.TYPE = type;
    this.numer = numer;
    this.denom = denom;
}
var cons = function(x, y)
{
    return new Cons(x,y);
}
var build_nil = function()
{
   return null;
}
/*
    check whether string is number
*/
function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}
/*
    check whether string is integer
*/
var isInteger = function(n)
{ 
    if(n.length==0)return false; 
    if(n[0]=="-") n = n.slice(1);
    return (n==="0" || /^[1-9][0-9]*$/.test(n) || /^0x[0-9A-F]{1,4}$/i.test(n) || /^0[1-9][0-9]*$/.test(n)) }
var isFloat = function(n){return isNumber(n) && !(isInteger(n))}
var isRatio = function(n) // can only check string
{
    if(typeof(n)!=="string") return false;
    var index_of_slash = n.indexOf("/");
    if(index_of_slash === -1) return false;
    var numer = n.slice(0, index_of_slash);
    var denom = n.slice(index_of_slash+1);
    // if(numer.length === 0 || denom.length == 0) return false;
    if(isInteger(numer) && isInteger(denom)) // didn't consider the case denominator is 0
    {
        if(parseInt(denom) === 0)
        {
            console.log("Invalid ratio --- " + n + " with denominator 0");
        }
        return true;
    }
    return false;
}
var number$ = function(v)
{
        return v instanceof Toy_Number;
}
// The Below 2 functions can be used
// when "n" has been proved to be ratio
var getNumerator = function(n)
{
    return n.slice(0, n.indexOf("/"));
}
var getDenominator = function(n)
{
    return n.slice(n.indexOf("/")+1);
}
var car = function(l){if(l===null){console.log("ERROR: Cannot get car of ()");return null;}return l.car};
var cdr = function(l){return l.cdr};
var caar = function(obj){return car(car(obj))}
var cadr = function(obj){return car(cdr(obj))}
var cddr = function(obj){return cdr(cdr(obj))}
var cdddr = function(obj){return cdr(cdr(cdr(obj)))}
var caddr = function(obj){return car(cdr(cdr(obj)))}
var cadddr = function(obj){return car(cdr(cdr(cdr(obj))))}
var cadar = function(obj){return car(cdr(car(obj)))}
/* ================================= */
/* tokenize string to list */
var lexer = function(input_str)
{   
    var find_final_comment_index = function(input_str, i) // find end index of comment ; comment
    {
        if(i == input_str.length) return i;
        if(input_str[i]=="\n") return i+1;
        else return find_final_comment_index(input_str, i + 1);
    }
    var find_final_long_annotation_index = function(input_str, i) // find end index of long comment ;;; comment ;;;
    {
        if(i == input_str.length) return i;
        if(i + 3 <= input_str.length && input_str.slice(i, i+3) === ";;;")
            return i+3;
        return find_final_long_annotation_index(input_str, i+1);
    }
    var find_final_string_index = function(input_str, i)  // find final index of string 
    {
        if(i == input_str.length)
        {
            console.log("ERROR: Incomplete String");
            return i;
        }
        else if(input_str[i]=="\\")
            return find_final_string_index(input_str, i+2);
        else if(input_str[i]==='"')
            return i+1;
        else 
            return find_final_string_index(input_str, i+1)
    }
    var find_final_number_of_atom_index = function(input_str, i)
    {
        if(i == input_str.length)
            return i;
        if(input_str[i]=="(" || input_str[i]==")"
            || input_str[i]=="[" || input_str[i]=="]"
            || input_str[i]=="{" || input_str[i]=="}"
            || input_str[i]==" " || input_str[i]=="\t"
            || input_str[i]=="\n" || input_str[i]==";"
            || input_str[i]==",")
            return i;
        else
            return find_final_number_of_atom_index(input_str, i+1);
    }
    var lexer_iter = function(input_str, i)
    {
        if(i>=input_str.length)
            return null; // finish
        else if(input_str[i]===" " || input_str[i]=="\n" || input_str[i]=="\t" || input_str[i]===",") // remove space tab newline ,
            return lexer_iter(input_str, i + 1);
        else if(input_str[i] === "(" || input_str[i] === "[")
            return cons( "(", lexer_iter(input_str, i + 1));
        // vector
        else if (input_str[i] === "#" && (input_str[i+1]==="(" || input_str[i+1]==="["))
            return cons( "(", cons("vector", lexer_iter(input_str, i+2)));
       // else if(input_str[i]==="[") remove vector support
       //     return cons( "(", cons( "vector", lexer_iter(input_str, i + 1)));
        else if(input_str[i]==="{")
            return cons( "(", cons( "dictionary", lexer_iter(input_str, i + 1)));
        else if(input_str[i]===")" || input_str[i]=="]" || input_str[i]=="}")
            return cons( ")", lexer_iter(input_str, i + 1));
        else if(input_str[i]==="~" && input_str[i+1]==="@")
            return cons("~@", lexer_iter(input_str, i+2));
        else if(input_str[i]==="'" || input_str[i]=="`" || input_str[i]=="~")
            return cons( input_str[i], lexer_iter(input_str, i + 1));
        else if(input_str[i]==='"')
        {
            var end = find_final_string_index(input_str, i+1);
            return cons(input_str.slice(i, end), lexer_iter(input_str, end))
            // return cons("(", cons("quote", cons(input_str.slice(i, end), cons(")", lexer_iter(input_str, end)))))
        }
        // long annotation
        else if (i + 3 <= input_str.length && input_str.slice(i, i+3) === ";;;") // ;;; comment ;;;
            return lexer_iter(input_str, find_final_long_annotation_index(input_str, i+3));
        else if(input_str[i]===";") // comment
            return lexer_iter(input_str, find_final_comment_index(input_str, i+1));
        else
        {
            // atom or number
            var end = find_final_number_of_atom_index(input_str, i+1);
            var __obj = input_str.slice(i, end);
            if(isRatio(__obj)) // is ratio number
            {
                return cons("(", cons("/", cons(parseFloat(getNumerator(__obj)), cons(parseFloat(getDenominator(__obj)), cons(")", lexer_iter(input_str, end))))));
            }
            else
            {
                return cons(__obj, lexer_iter(input_str, end))
            }
            // return cons( input_str.slice(i, end) , lexer_iter(input_str, end));
        }
    }
    return lexer_iter(input_str, 0);
}
/* parse list to list */
var parser = function(l)
{
    var rest = l; // keep track of rest
    var parse_list = function(l)
    {
        if(l === null)
        {
            console.log("ERROR: Incomplete Statement. Missing )");
            rest = null;
            return null;
        }
        if(car(l) === ")") // finish
        {
            rest = cdr(l);
            return build_nil();
        }
        else if (car(l) === "(") // list
        {
            return cons(parse_list(cdr(l)), parse_list(rest));
        }
        else if (car(l) === "'" || car(l) === "~" || car(l) === "`" || car(l) === "~@")  // quote unquote quasiquote unquote-splice
        {
            return cons(parse_special(l), parse_list(rest));
        }
        else  // symbol or number
        {
            return cons(parse_symbol_or_number( car(l) ), parse_list(cdr(l)));
        }
    }
    var parse_special = function(l)
    {
        var tag ;
        if(car(l) === "'")
            tag = "quote"
        else if (car(l) === "~")
            tag = "unquote"
        else if (car(l) === "~@")
            tag = "unquote-splice"
        else tag = 'quasiquote'
        l = cdr(l);
        if (car(l) === "(") // list
        {
            return cons(tag, cons(parse_list(cdr(l)), build_nil()));
        }
        else if (car(l) === "'" || car(l) === "~" || car(l) === "`")  // quote unquote quasiquote
        {   // here my be some errors
            return cons(tag, cons(parse_special(l), build_nil()));
        }
        else  // symbol or number
        {
            rest = cdr(l);
            return cons(tag, cons(parse_symbol_or_number(car(l)), build_nil()));
        }
    }
    var formatQuickAccess = function(ns, keys)
    {
        var formatQuickAccess_iter = function(keys, output, count)
        {
            if(count === keys.length)
                return output;
            return formatQuickAccess_iter(keys, cons(output, cons(":"+keys[count], null)), count + 1);
        }
        return formatQuickAccess_iter(keys, cons(ns, cons(":"+keys[0], null)), 1);
    }
    var parse_symbol_or_number = function(l)
    {
        /* keyword
        if(l[0]==":")
            return cons("keyword", cons('"'+l.slice(1)+'"', build_nil()))
        */
        if(l[0]==="\"") return l; // string
        else if (isNumber(l))
        {
            if(isInteger(l))
            {
                // octal
                if((l.length>2 && l[0] === "-" && l[1]==="0") || (l.length>=2 && l[0]==="0" && l[1]!=="x"))
                    return new Toy_Number(parseInt(l, 8), 1, RATIO);
                // hex or decimal
                return new Toy_Number(parseInt(l), 1, RATIO);
            }
            else return new Toy_Number(parseFloat(l), 1 ,FLOAT);
        }
        else if (isRatio(l))
            return new Toy_Number(parseFloat(getNumerator(l)), parseFloat(getDenominator(l)), RATIO)
        var splitted_ = l.split(":");
        // console.log(l);
        // console.log(splitted_);
        if(l === ":"  || splitted_.length == 1 || l[0] === ":" || l[l.length-1] === ":") //  : :abc abc: 
            return l;
        var ns = splitted_[0]; // eg x:a => ns 'x'  keys ['a']
        var keys = splitted_.slice(1);
        var formatted_ = formatQuickAccess(ns, keys); // eg x:a => (ref x :a) or (x :a)
        // console.log(formatted_);
        return formatted_;
    }
    // done
    if(l == null)
        return build_nil();
    // list
    else if (car(l) === "(")
    {
        return cons(parse_list(cdr(l)), parser(rest));
    }
    // quote // unquote // quasiquote // unquote-splice
    else if (car(l) === "'" || car(l) === "~" || car(l) === "`" || car(l) === "~@")
    {
        return cons(parse_special(l), rest);
    }
    // symbol or number
    else
    {
        return cons(parse_symbol_or_number( car(l) ), parser(cdr(l)));
    }
}
/*
    Numeric Calculation
*/
// GCD
/* // use  resursion
var gcd = function(a,b)
{
    if (b==0)
        return a
    return gcd(b,a%b)
}
*/
var gcd = function(a,b)
{
    while(b!=0)
    {
        var temp = a;
        a = b;
        b = temp%b;
    }
    return a;
}
var numer = function(rat){return rat.numer}
var denom = function(rat){return rat.denom}
var make_rat = function(numer, denom)
{
    var g = gcd(numer, denom)
    var numer = numer/g;
    var denom = denom/g;
    return new Toy_Number(numer, denom, RATIO);
}
/* convert Toy_Number to string */
var number_to_string = function(num)
{
    if(num.TYPE === FLOAT) return ""+num.numer;
    else if (num.denom === 1) return "" + num.numer;
    return num.numer+"/"+num.denom;
}
// fraction arithematic
var add_rat = function(x,y){
   return make_rat( numer(x)*denom(y)+numer(y)*denom(x) , denom(x)*denom(y))
}
var sub_rat = function(x,y){
    return make_rat( numer(x)*denom(y)-numer(y)*denom(x) , denom(x)*denom(y))
}
var mul_rat = function(x,y){
    return make_rat(numer(x)*numer(y), denom(x)*denom(y))
}
var div_rat = function (x,y){
    return make_rat(numer(x)*denom(y),denom(x)*numer(y))
}
/*
    format values to string
*/
var formatString = function(str)
{
    if(str.indexOf(" ")===-1)
        return str 
    return "#str{"+str+"}#"; // means it is string, but with space, so used #{}# to indicate it
}
var formatNumber = function(num)
{
    if(num.TYPE === FLOAT) return ""+num.numer.toFixed(10);
    else if (num.denom === 1) return "" + num.numer;
    return num.numer+"/"+num.denom;
}
    ;
var formatList = function(l) // format list object to javascript string
{
    if(l === null) // it is null
    {
        return '()';
    }
    else
    {
        var output = "(";
        var p = l; // pointer
        while(1)
        {
            if(l === null) // finish
            {
                output = output.slice(0, output.length - 1) + ")";
                break;
            }
            if(!(l instanceof Cons)) // pair
            {
                var c = l;
                output = output + ". ";
                if (c === null) output = output + "())";
                else if(number$(c))
                    output = output + formatNumber(c) + ")";
                else if (typeof(c) === "string")
                    output = output + formatString(c) + ")";
                else if (c  instanceof Cons)
                    output = output + formatList(c) + ")";
                else if (c instanceof Array)
                    output = output + formatVector(c) + ")";
                else if (c.TYPE === PROCEDURE)
                    output = output + "< user-defined-procedure >)" ;
                else if (typeof(c) === 'function')
                // else if (c.TYPE === BUILTIN_PRIMITIVE_PROCEDURE)
                    output = output + "< builtin-primitive-procedure >)"      ;
                else if (c.TYPE === MACRO)
                    output = output + "< macro >";
                else if (c instanceof Object)
                    output = output + formatDictionary(c) + ")";
                break;
            }
            var c = l.car;
            if (c === null) output = output + "() ";
            else if(number$(c))
                output = output + formatNumber(c) + " ";
            else if (typeof(c) === "string")
                output = output + formatString(c) + " ";
            else if (c  instanceof Cons)
                output = output + formatList(c) + " ";
            else if (c instanceof Array)
                output = output + formatVector(c) + " ";
            else if (typeof(c) === 'function')
            // else if (c.TYPE === BUILTIN_PRIMITIVE_PROCEDURE)
                output = output + "< builtin-procedure > "      ;
            else if (c.TYPE === PROCEDURE)
                output = output + "< user-defined-procedure > " ;
            else if (c.TYPE === MACRO)
                output = output + "< macro > "
            else if (c instanceof Object)
                output = output + formatDictionary(c) + " ";
            l = l.cdr; 
        }
        return output;
    }
}
var formatVector = function(v)
{
    if (v.length == 0) return "#[]";
    var output = "#[";
    var p = v; // pointer
    for(var i = 0; i < p.length; i++)
    {
        var c = p[i];
        if(c === null)
            output = output + "() "
        else if(number$(c))
            output = output + formatNumber(c) + " ";
        else if (typeof(c) === "string")
            output = output + formatString(c) + " ";
        else if (c instanceof Cons)
            output = output + formatList(c) + " ";
        else if (c instanceof Array)
            output = output + formatVector(c) + " ";
        else if (c.TYPE === PROCEDURE)
            output = output + "< user-defined-procedure > " ;
        else if (typeof(c) === 'function')
        // else if (c.TYPE === BUILTIN_PRIMITIVE_PROCEDURE)
            output = output + "< builtin-procedure > "      ;
        else if (c.TYPE === MACRO)
            output = output + "< macro > "
        else if (c instanceof Object)
            output = output + formatDictionary(c) + " ";
    }
    output = output.slice(0, output.length - 1) + "]"
    return output;
}
var formatDictionary = function(d)
{
    if(Object.keys(d).length === 0) return "{}";
    var output = "{";
    var p = d;  // pointer
    for(var key in p)
    {
        output = output + ":" + key + " "
        var c = p[key];
        if(c === null)
            output = output + "()" + ", ";
        else if(number$(c))
            output = output + formatNumber(c) + ", ";
        else if (typeof(c) === 'string')
            output = output + formatString(c) + ", ";
        else if (c instanceof Cons)
            output = output + formatList(c) + ", ";
        else if (c instanceof Array)
            output = output + formatVector(c) + ",";
        else if (c.TYPE === PROCEDURE)
            output = output + "< user-defined-procedure >, " ;
        else if (typeof(c) === 'function')
        // else if (c.TYPE === BUILTIN_PRIMITIVE_PROCEDURE)
            output = output + "< builtin-procedure >, "      ;
        else if (c.TYPE === MACRO)
            output = output + "< macro > "
        else if (c instanceof Object)
            output = output + formatDictionary(c) + ", ";
    }
    output = output.slice(0, output.length - 2) + "}"
    return output;
}
/* to string*/
var to_string = function(v)
{
    if(number$(v))
            return (formatNumber(v));
    else if (typeof(v) === "string" )
        return v;
    else if (v instanceof Cons)
        return (formatList(v));
    else if (v instanceof Array)
        return (formatVector(v));
    else if (v.TYPE === PROCEDURE)
        return ("< user-defined-procedure >");
    else if (v.TYPE === BUILTIN_PRIMITIVE_PROCEDURE)
        return ('undefined');
    else if (v instanceof Object)
        return (formatDictionary(v));
    else
    {
        console.log("Function display: Invalid Parameters Type");
        return 'undefined';
    }
}
var lookup_env = function(var_name, env)
{
    for(var i = env.length-1; i>=0; i--)
    {
        if(var_name in env[i])
            return env[i][var_name]
    }
    console.log("ERROR: unbound variable: " + var_name);
    return "undefined"
}
/* arg0: (add a b)
   arg1: ((+ a b) ...) */
/*
    (define (add a b) (+ a b))
    =>
    (define add (lambda (a b) (+ a b)))
*/
var make_lambda = function(arg0, arg1)
{
    var var_name = car(arg0);  // add
    var args = cdr(arg0);      // (a b), [a b]
    var body = arg1           // ((+ a b))
    var lambda_body = cons('lambda', cons(args, body))
    return cons("def", cons(var_name, cons(lambda_body, build_nil())))
}
var eval_set = function(var_name, var_value, env)
{
    for(var i = env.length-1; i>=0; i--)
    {
        if(var_name in env[i])
        {
            env[i][var_name] = var_value;
            return var_value;
        }
    }
    console.log("ERROR: Function set!, var name "+var_name+" does not exist");
    return "undefined";
}
var eval_quasiquote = function(list, env)
{
    if(list === null) return null;
    var v = car(list);
    if(typeof(v)==="string") 
    {
        if(v === ".") // pair
        {
            v = cadr(list);
            if(typeof(v) === "string") return v;
            else
            {
                if(car(v) === "unquote") return toy_eval(cadr(v), env);
                return eval_quasiquote(v, env);
            }
        }
        return cons(v, eval_quasiquote(cdr(list), env))
    }
    else if (v === null)
    {
        return cons(v, eval_quasiquote(cdr(list), env))
    }
    else if (v.TYPE === LIST)
    {
        if(car(v) === "unquote")
            return cons(toy_eval(cadr(v), env),
                        eval_quasiquote(cdr(list), env));
        if(car(v) === "unquote-splice")
        {
            var append = function(a, b)
            {
                if(a == null) return b;
                return cons(car(a), append(cdr(a), b))
            }
            var value = toy_eval(cadr(v), env);
            if(!(value instanceof Cons))
            {
                console.log("ERROR: ~@ only support list type value")
                return null;
            }
            return append(value, eval_quasiquote(cdr(list), env));
        }
        return cons(eval_quasiquote(v, env),
                    eval_quasiquote(cdr(list), env))
    }
    return cons(v, eval_quasiquote(cdr(list), env)); 
}
var eval_lambda = function(/*lambda_args, lambda_body */ lambda__ ,env)
{
    var lambda_args = car(lambda__);
    var lambda_body = cdr(lambda__);
    var docstring = "This function has no information provided";
    if(typeof(lambda_args) === "string" && lambda_args[0] === '"')
    {
        // docstring
        docstring = lambda_args.slice(1, lambda_args.length-1);
        lambda_args = car(lambda_body);
        lambda_body = cdr(lambda_body);
    }
   
    /* clean args */
    var arg = {}
    arg.arg_name_list = [];
    arg.arg_val_list  = [];
    while(lambda_args!=null)
    {
        var v = car(lambda_args);
        if(typeof(v) === 'string') /* (lambda (a) a) a is string */
        {
            if(v[0] === ":") /* (lambda (:a 12) a) */
            {
                var var_name = v.slice(1);          
                lambda_args = cdr(lambda_args);
                var var_val = toy_eval(car(lambda_args), env);
                arg.arg_name_list.push(var_name);  // add arg name
                arg.arg_val_list.push(var_val);   // add default arg value
            }
            else if (v === ".") // rest
            {
                arg.arg_name_list.push("."); arg.arg_val_list.push("undefined"); // save .
                lambda_args = cdr(lambda_args);
                v = car(lambda_args);
                if(v[0]==":")
                {
                    //
                    //    (def (add a . :b 123) b)  the default value of b is 123
                    //
                    var var_name = v.slice(1);
                    lambda_args = cdr(lambda_args);
                    var var_val = toy_eval(car(lambda_args), env);
                    arg.arg_name_list.push(var_name);  // add arg name
                    arg.arg_val_list.push(var_val);   // add default arg value
                    break;
                }
                else
                {
                    //
                    //    (def (add . a) a) => the default value of a is null
                    //
                    arg.arg_name_list.push(v); 
                    arg.arg_val_list.push(null);
                    break;
                }
            }
            else
            {
                arg.arg_name_list.push(v); // add arg name
                arg.arg_val_list.push("undefined"); // add default arg value
            }
        }
        else
        {
            console.log("ERROR: Function definition error.")
            return "undefined";
        }
        lambda_args = cdr(lambda_args);
    }
    /*
        extend lambda_body
        展开 宏.
        eg 
        (def x 0)
        (defmacro t [] (if (= x 0) "x is 0" "x is 1"))
        ((lambda [] 
            (def x 1) ;; change x to 1
            (t)       ;; => "x is 0" . even though above (def x 1) but it is not runned, so x is still 0 所以展开为 "x is 0"
            ))
    */
    var extend_macro_for_lambda = function(lambda_body, is_head)
    {
        if(lambda_body === null)
            return null;
        // is cons
        if(lambda_body instanceof Cons)
        {
            var c = car(lambda_body)
            if(c instanceof Cons) // is cons
            {  
                return cons(extend_macro_for_lambda(c, true), 
                            extend_macro_for_lambda(cdr(lambda_body, false)));
            }
            // is not cons
            // is head, so check whether it's macro
            if(is_head === true && typeof(c) === "string")
            {
                for(var i = env.length-1; i>=0; i--)
                {
                    // find it and it's macro
                    if(c in env[i])
                    {
                        if(env[i][c] instanceof Macro)
                        {
                            var expanded_statement = macro_expand(env[i][c], cdr(lambda_body), env);
                            var macro_stm = expanded_statement[0];
                            var macro_env = expanded_statement[1];
                            var formatted_stm = format_stm_for_macro(macro_stm, macro_env, true);
                            return extend_macro_for_lambda(formatted_stm, true)
                        }
                        else // it is not macro
                        {
                             // it is not macro
                             return cons(c, 
                                 extend_macro_for_lambda(cdr(lambda_body), false));
                        }
                    }
                }
                // it is not macro
                return cons(c, 
                            extend_macro_for_lambda(cdr(lambda_body), false));
            }
            // c is macro
            else if(is_head === true && c instanceof Macro)
            {
                var expanded_statement = macro_expand(c, cdr(lambda_body), env);
                var macro_stm = expanded_statement[0];
                var macro_env = expanded_statement[1];
                var formatted_stm = format_stm_for_macro(macro_stm, macro_env, true);
                return extend_macro_for_lambda(formatted_stm, true)
            }
            // is not head
            else
            {
                return cons(c, 
                            extend_macro_for_lambda(cdr(lambda_body), false));
            }
        }
        // is not cons
        else
        {
            return lambda_body;
        }
    }
    /* Debug 这个函数用。 现实展开后的 lambda_body */
    // primitive_builtin_functions["display"]([extend_macro_for_lambda(lambda_body, false)])
    lambda_body = extend_macro_for_lambda(lambda_body, false)
    return new Procedure(arg, lambda_body/*new_lambda_body*/, env.slice(0), docstring);   
}
var eval_macro = function(macro_args, macro_body, env)
{
    return new Macro(macro_args, macro_body, env.slice(0));
}
var eval_list = function(list, env)
{
    if(list === null) return null;
    else return cons(toy_eval(car(list), env),
                     eval_list(cdr(list), env));
}
var macro_expand = function(macro, params, env)
{
    var add_parameter = function(new_frame, args, params)
    {
        while(args!==null)
        {   
            var var_name = car(args); 
            if(var_name instanceof Cons)
            {
                add_parameter(new_frame, args.car, params.car);
            }
            else if(var_name === ".")
            {
                new_frame[cadr(args)] = params;
                break;
            }
            else
            {
                if(params === null) /* Error */
                {
                   console.log("ERROR: Invalid macro. Pattern doesn't match");
                    return;
                }
                var var_value = car(params); // does not calculate
                new_frame[var_name] = var_value;
            }
            args = cdr(args); params = cdr(params);
        }
    }
    var closure_env = macro.closure_env.slice(0);
    var args = macro.args;
    var body = macro.body;
    var new_frame = {};
    add_parameter(new_frame, args, params); // add parameters
    closure_env.push(new_frame);
    return [eval_begin(body, closure_env), macro.closure_env];
}
/*
    2014 / 1 / 18
    solve hygienic macro problem
*/
var format_stm_for_macro = function(macro_stm, macro_env, is_head)
{
    if(macro_stm === null)
        return null;
    else if (! (macro_stm instanceof Cons))
        return macro_stm;
    var o = car(macro_stm);
    if(o instanceof Cons)
    {
        return cons(format_stm_for_macro(o, macro_env, true), 
                    format_stm_for_macro(cdr(macro_stm), macro_env, false));
    }
    /*
        solve (def add (lambda [a b] (+ a b))) problem
        prevent "add" from being replaced
    */
    else if (is_head)
    {
        if (o === "def" || o === "set!" || o === "lambda" || o === "let")
        {
            return cons(o, cons(cadr(macro_stm), 
                                format_stm_for_macro(cddr(macro_stm), macro_env, false)));
        }
        else if (o === "quote" || o === "quasiquote" || o === "unquote")
        {
            return macro_stm;
        }
        else if (o === "defmacro")
        {
            return cons(o, cons(cadr(macro_stm), cons(caddr(macro_stm), format_stm_for_macro(cdddr(macro_stm), macro_env, false))));
        }
        /*
        if cond begin 是都要替换掉的
        */
        else
        {
            /*
                和小面那个else里面的内容一样
            */
            for(var i = macro_env.length-1; i>=0; i--)
            {
                if(o in macro_env[i])
                {
                    return cons(macro_env[i][o], 
                                format_stm_for_macro(cdr(macro_stm), macro_env, false));
                }
            }
            return cons(o, 
                        format_stm_for_macro(cdr(macro_stm), macro_env, false));
        }
    }
    /*
    else if (o === "def")
    {
        return cons(o, cons(cadr(macro_stm), 
                            format_stm_for_macro(cddr(macro_stm), macro_env)))
    }*/
    else
    {
        for(var i = macro_env.length-1; i>=0; i--)
        {
            if(o in macro_env[i])
            {
                return cons(macro_env[i][o], 
                            format_stm_for_macro(cdr(macro_stm), macro_env, false));
            }
        }
        return cons(o, 
                    format_stm_for_macro(cdr(macro_stm), macro_env, false))
    }
}
var eval_begin = function(body, env)
{
    var return_v = null;
    while(body!==null)
    {
        return_v = toy_eval(car(body), env);
        body = cdr(body);
    }
    return return_v;
}
/*
    extremely simple interpreter
    ;(
*/
var toy_eval = function(exp, env)
{
    while (true)
    {
        if(exp === null) return null;
        else if(typeof(exp) === "string"){
            if(exp[0]==='"')return eval(exp); // string
            if(exp[0]===":")return exp.slice(1);               // keyword
            return lookup_env(exp, env);
        }
        else if (exp instanceof Toy_Number)
            return exp;
        else if (exp.TYPE === LIST)
        {
            var tag = car(exp);
            if(tag === "quote")
            {
                var quote_list = function(l)
                {
                    if(l == null) return null;
                    var v = car(l);
                    if(typeof(v) === "string" && v[0] === '"') v = eval(v);
                    if(v instanceof Cons) return cons(quote_list(v), quote_list(cdr(l)));
                    else if (v === ".") return cadr(l);
                    return cons(v, quote_list(cdr(l)));
                }
                if(cadr(exp) instanceof Cons)
                    return quote_list(cadr(exp));
                return cadr(exp);
            }
            else if (tag === "quasiquote")
            {
                var value = cadr(exp);
                if(typeof(value)!=="string" && value.TYPE === LIST)
                {
                    return eval_quasiquote(value, env);
                }
                return value
            }
            else if(tag === "def")
            {
                var var_name = cadr(exp);
                var var_value = caddr(exp);
                /* Redefinition Error */
                if(typeof(var_name) === "string" && var_name in env[env.length - 1])
                {
                	console.log("\nERROR: It is not recommended or allowed to redefine an existed variable: " + var_name
                                 + "\nTo change the value of a variable. Use (set! var-name var-value)"
                                 + "\nIn this case: (set! " + var_name + " " + to_string(var_value) + ")");
					return "undefined";
                }
                /* lambda */
                if(typeof(var_name)!=="string" && var_name.TYPE === LIST)
                {
                    return toy_eval(make_lambda(var_name, cddr(exp)), env)
                }
                else
                {
                    if(typeof(var_name)!=="string")
                    {
                        console.log("ERROR: Invalid variable name : " + primitive_builtin_functions["->str"]([var_name]))
                        return "undefined"
                    }
                    var_value = toy_eval(var_value, env);
                    env[env.length - 1][var_name] = var_value;
                    return var_value;
                }
            }
            else if (tag === "set!")
            {
                var var_name = cadr(exp);
                var var_value = toy_eval(caddr(exp), env);
                return eval_set(var_name, var_value, env);
            }
            /*
            else if (tag === "macro")
            {
                return eval_macro(cadr(exp), cddr(exp), env);
            }
            */
            /*
                (let [x 1 y 2] (+ x y))
            */
            else if (tag === "let")
            {
                var var_val_vector = cadr(exp);
                
                var new_frame = {};
                env.push(new_frame); // this env has to be here... don't change it
                while(var_val_vector!==null)
                {
                    var var_name = car(var_val_vector);
                    var var_val = toy_eval(car(cdr(var_val_vector)), env);
                    new_frame[var_name] = var_val;
                    var_val_vector = cddr(var_val_vector);
                }
                var return_val = eval_begin(cddr(exp), env);
                env.pop(new_frame);
                return return_val;
            }
            else if (tag === "lambda")
            {
                return eval_lambda(/*cadr(exp), cddr(exp)*/ cdr(exp), env);
            }
            else if (tag === "if")
            {
                var test = cadr(exp);
                var conseq = caddr(exp);
                var alter = cadddr(exp);
                test = toy_eval(test, env);
                if(test == null){
                    exp = alter; continue;
                }
                exp = conseq; continue;
            }
            /* cond native support is removed on 01/25/2014
            else if (tag === "cond")
            {
                var clauses = cdr(exp);
                var run_body = null;
                while(clauses!==null)
                {
                    var clause = car(clauses)
                    var predicate = car(clause);
                    var body = cdr(clause);
                    if(predicate == "else" || toy_eval(predicate, env)!==null)
                    {
                        run_body = body;
                        env = env;
                        break;
                    }
                    clauses = cdr(clauses);
                }
                exp = cons("begin", run_body); // for tail call optimization
                continue;  
            }*/
            else if (tag === "begin")
            {
                var body = cdr(exp);
                if(body == null) return "undefined";
                while(body.cdr!==null)
                {
                    toy_eval(car(body), env);
                    body = body.cdr;
                }
                exp = body.car;
                continue;
            }
            else if (tag === "eval")
            {
                return toy_eval(toy_eval(cadr(exp), env), env)
            }
            else if (tag === "apply")
            {
                return toy_eval(cons(toy_eval(cadr(exp), env), toy_eval(caddr(exp), env) ) ,env)
            }
            else if (typeof(tag) === 'function') // javascript function
            {
                var eval_list_to_array = function(list, env)
                {
                    var output = []; while(list!==null)
                    {
                        output.push(toy_eval(car(list), env));
                        list = cdr(list)
                    }
                    return output;
                }
               return tag.call(null, eval_list_to_array(cdr(exp), env));
            }
            else if (tag === "macroexpand-1")
            {
                var v = toy_eval(cadr(exp), env);
                return macro_expand(toy_eval(car(v), env),
                                    cdr(v),
                                    env)[0];
            }
            else if (tag === "defmacro")
            {
                var macro = eval_macro(caddr(exp), cdddr(exp), env);
                // env[1][cadr(exp)] = macro;
                env[0][cadr(exp)] = macro;
                return macro
            }
            /*
            else if (tag === "while")
            {
                var test = cadr(exp);
                var body = cddr(exp);
                while(toy_eval(test, env))
                {
                    eval_begin(body, env);
                }
                return "undefined";
            }*/
            else if (tag === "get-env") // return current env
            {
                return env.slice(0);
            }
            else if (tag.TYPE === PROCEDURE)
            {  
                var proc = tag;var params = cdr(exp);
                var closure_env = proc.closure_env.slice(0);
                var args = proc.args;
                var body = proc.body;
                var new_frame = {}
                var args_val_list  = args.arg_val_list;  // arg default value list
                var args_name_list = args.arg_name_list; // arg name list
               
                for(var i = 0; i < args_name_list.length; i++) // add parameters
                {
                    if(args_name_list[i] === ".") // rest 
                    {
                        if(params == null)
                        {
                            var arg_name = args_name_list[i+1];
                            new_frame[arg_name] = args_val_list[i+1];
                        }
                        else
                        {
                            var arg_name = args_name_list[i+1];
                            var param_value = eval_list(params, env);
                            new_frame[arg_name] = param_value;
                        }
                        break;
                    }
                    if(params == null) 
                    {
                        if(args_name_list[i] in new_frame) continue;
                        new_frame[args_name_list[i]] = args_val_list[i];
                        continue;
                    }
                    var param = car(params);
                    if(typeof(param) === "string" && param[0] === ":") // :a => param name a 
                    {
                        var param_name = param.slice(1);
                        params = cdr(params);
                        var param_value = toy_eval(car(params), env);
                        new_frame[param_name] = param_value;
                        if(param_name!==args_name_list[i] && !(args_name_list[i] in new_frame))
                            new_frame[args_name_list[i]] = args_val_list[i];
                    }
                    else
                    {
                        var arg_name = args_name_list[i]; // default name
                        var param_val = toy_eval(car(params), env); // calculate given param
                        new_frame[arg_name] = param_val;
                    }
                    params = cdr(params);
                }
                closure_env.push(new_frame); // add new frame
                exp = cons("begin", body); env = closure_env; continue; // tail call optimization                 
            }
            else if (tag.TYPE === MACRO)
            {
            	var expanded_statement = macro_expand(tag, cdr(exp), env);
                var macro_stm = expanded_statement[0];
                var macro_env = expanded_statement[1];
                var formatted_stm = format_stm_for_macro(macro_stm, macro_env, true);
                
                // primitive_builtin_functions["display"]([formatted_stm]);
                return toy_eval(formatted_stm, env);
                // return toy_eval(expanded_statement[0], expanded_statement[1]);
            }
            else if (tag instanceof Array)
            {
                var index = toy_eval(cadr(exp), env);
                if(!index instanceof Toy_Number){console.log("ERROR: invalid index");return "undefined"}
                if( index >= tag.length || index < 0)
                {
                    console.log("ERROR: Index out of boundary")
                    return "undefined"
                }
                return tag[index.numer];
            }
            else if (tag instanceof Object && !(tag instanceof Toy_Number)  && !(tag instanceof Cons))
            {
                var key = toy_eval(cadr(exp), env);
                if( key in tag)
                    return tag[key];
                else
                    return "undefined"
            }
            else
            {
                var proc = toy_eval(car(exp), env);
                var application =   cons(proc, cdr(exp));
                if(proc === "undefined" || proc instanceof Toy_Number || typeof(proc) === "string")
                {
                    console.log("ERROR: Invalid Function: " + (proc instanceof Toy_Number? ("number type: " + formatNumber(proc)) 
                                                                                            : 
                                                                                            "string type: " + proc));
                    console.log("      WITH EXP: "+to_string(exp))
                    return "undefined"
                }
                /* continue */
                exp = application;
                env = env;
                continue;
                /* return toy_eval(
                                   application
                                    , env
                                    ) */
            }
        }
        else
            return exp;
    }
}
var primitive_builtin_functions = 
{
    "+":function(stack_param)
        {
            var arg0 = stack_param[0]; var arg1 = stack_param[1];
            var arg0_number = arg0 instanceof Toy_Number;
            var arg1_number = arg1 instanceof Toy_Number;
            if(arg0_number && arg1_number)
            {
                if(arg0.TYPE === FLOAT || arg1.TYPE === FLOAT) return new Toy_Number(arg0.numer/arg0.denom + arg1.numer/arg1.denom, 1,  FLOAT);
                return add_rat(arg0, arg1);
            }
            if(arg0_number) arg0 = formatNumber(arg0);
            if(arg1_number) arg1 = formatNumber(arg1);
            return arg0 + arg1;
        }, 
    "-":function(stack_param)
        {
            var arg0 = stack_param[0]; var arg1 = stack_param[1];
            var arg0_number = arg0 instanceof Toy_Number;
            var arg1_number = arg1 instanceof Toy_Number;
            if(arg0_number && arg1_number)
            {
                if(arg0.TYPE === FLOAT || arg1.TYPE === FLOAT) return new Toy_Number(arg0.numer/arg0.denom - arg1.numer/arg1.denom, 1, FLOAT);
                return sub_rat(arg0, arg1);
            }
            if(arg0_number) arg0 = formatNumber(arg0);
            if(arg1_number) arg1 = formatNumber(arg1);
            return arg0 - arg1;
        }, 
    "*":function(stack_param)
        {
            var arg0 = stack_param[0]; var arg1 = stack_param[1];
            var arg0_number = arg0 instanceof Toy_Number;
            var arg1_number = arg1 instanceof Toy_Number;
            if(arg0_number && arg1_number)
            {
                if(arg0.TYPE === FLOAT || arg1.TYPE === FLOAT) return new Toy_Number((arg0.numer/arg0.denom) * (arg1.numer/arg1.denom), 1, FLOAT);
                return mul_rat(arg0, arg1);
            }
            if(arg0_number) arg0 = formatNumber(arg0);
            if(arg1_number) arg1 = formatNumber(arg1);
            return arg0 * arg1;
        }, 
    "/":function(stack_param)
        {
            var arg0 = stack_param[0]; var arg1 = stack_param[1];
            var arg0_number = arg0 instanceof Toy_Number;
            var arg1_number = arg1 instanceof Toy_Number;
            if(arg0_number && arg1_number)
            {
                if(arg0.TYPE === FLOAT || arg1.TYPE === FLOAT) return new Toy_Number((arg0.numer/arg0.denom) / (arg1.numer/arg1.denom), 1, FLOAT);
                if(arg1.numer === 0){
                    console.log("ERROR: Cannot divide by 0")
                    return "false"
                }
                return  div_rat(arg0, arg1);
            }
            if(arg0_number) arg0 = formatNumber(arg0);
            if(arg1_number) arg1 = formatNumber(arg1);
            return arg0 / arg1;
        }, 
    "<":function(stack_param)
    {
        var arg0 = stack_param[0]; var arg1 = stack_param[1];
        var arg0_number = arg0 instanceof Toy_Number;
        var arg1_number = arg1 instanceof Toy_Number;
        if (arg0_number) arg0 = arg0.numer/arg0.denom;
        if (arg1_number) arg1 = arg1.numer/arg1.denom;
        return arg0 < arg1  ? 'true':null;
    },
    "eq?":function(stack_param)
    {
        var arg0 = stack_param[0]; var arg1 = stack_param[1];
        var arg0_number = arg0 instanceof Toy_Number;
        var arg1_number = arg1 instanceof Toy_Number;
        if (arg0_number) arg0 = arg0.numer/arg0.denom;
        if (arg1_number) arg1 = arg1.numer/arg1.denom;
        return arg0 === arg1  ? 'true':null;
    },
    "number?":function(param_array){return param_array[0] instanceof Toy_Number ? 'true':null;},
    "ratio?":function(param_array){return param_array[0] instanceof Toy_Number && param_array[0].TYPE === RATIO && param_array[0].denom !== 1 ? 'true':null;},
    "float?":function(param_array){return param_array[0] instanceof Toy_Number && param_array[0].TYPE === FLOAT ? 'true':null;},
    "integer?":function(param_array){return param_array[0] instanceof Toy_Number && param_array[0].TYPE === RATIO && param_array[0].denom == 1 ? 'true':null;},
    /* get numerator of number */
    "numerator": function(stack_param){ 
        if(stack_param[0] instanceof Toy_Number)
            return new Toy_Number(stack_param[0].numer, 1, RATIO);
        else
            console.log("ERROR: Function numerator wrong type parameters")
        return "undefined"
        },
    "denominator": function(stack_param){ 
        if(stack_param[0] instanceof Toy_Number)
            return new Toy_Number(stack_param[0].denom, 1, RATIO);
        else
            console.log("ERROR: Function numerator wrong type parameters")
        return "undefined"
        },
    "null?":function(stack_param){return stack_param[0]===null?'true':null},
    "cons":function(stack_param){retur