UNPKG

walley-language

Version:

walley language (toy) is a simple script language ;)

1,447 lines (1,400 loc) 78.8 kB
/* 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