﻿// TODO: Bug printing large cyclic objects

var code = [];
var data = [];

var steps = 0;
var trace = "";
//function traceStart() { trace = "<table class='trace' cellpadding='0' cellspacing='0'>"; }
//function traceStep(step) { trace += "<li>" + data.toString() + " <b>" + step + "</b> " + code.toString() + "</li>"; }
var traceLogging = false;
function traceStep(name, step) {
    if (!traceLogging) trace = "";
    trace += "<table class='trace' cellpadding='0' cellspacing='0' width='100%'>";
    trace += "<tr><td nowrap='nowrap' class='data' width='30%'><div class='words'>" + prettyPrint(data) + "</div></td>" +
                 "<td nowrap='nowrap' class='step' width='10%'><div class='words'>" + prettyPrintAtom(name, step) + (step && step._isUserFunction ? "→" : "") + "</div></td>" +
                 "<td nowrap='nowrap' class='code' width='60%'><div class='words'>" + prettyPrint(code) + "</div></td></tr>";
    trace += "</table>";
    document.getElementById("result").innerHTML = trace;
    document.getElementById("steps").innerHTML = "Steps: " + steps;
}
//function traceEnd() { trace += "</table>"; }
Function.prototype.toString = function () { return this.name; }
Object.prototype.toString = function () { return this.name; }
Array.prototype.toString = function () { var s = "["; for (var i in this) s += this[i] + ","; return s + "]"; }

function prettyPrintAtom(name, value) {
    function word(name, type, color) {
        var n = (name === undefined ? "UNDEF" : name.toString());
        if (n !== undefined && n.length > 0) // TODO: why in n undefined?
            return "<span class='" + type + (color ? "' style='background-color:" + color + "'>" : "'>") + n + "</span>";
        else return "";
    }
    var pretty = "";
    var t = typeof value;
    if (t == "string") value = "'" + value + "'";
    var isDef = (value != null && value._isUserFunction) === true; // avoiding undefined
    if (t == "function" && !isDef) t = "primitive";
    if (t == "object" && isDef) t = "function";
    if (value === _scope) {
        pretty += "<span class='scope'>•</span>";
    }
    else if (value === _sleep) {
        pretty += "<span class='sleep'>•</span>";
    }
    else if (value === _break) {
        pretty += "<span class='break'>•</span>";
    }
    else {
        if ((t == "object" || t == "array") && !isDef) {
            if (value !== null) {
                //var contents = (value.length == 0 ? "<span class='empty'>∅</span>" : (t == "object" ? "OBJ" : prettyPrint(value)));
                var contents = (value.length == 0 ? "<span class='empty'>∅</span>" : prettyPrint(value));
                pretty += "<span class='container'>" + contents + "</span>";
            }
        }
        else {
            if (value && value._nickname)
                pretty += word(value._nickname, t, value._color);
            else
                pretty += word(value, t, value._color);
        }
    }
    return pretty;
}

function prettyPrint(container) {
    var pretty = "";
    var max = 15;
    for (var x in container) {
        pretty += prettyPrintAtom(x, container[x]);
        if (--max <= 0) {
            pretty += "...";
            break;
        }
    }
    return pretty;
}

function updateInteractive(source) {
    var code = null;
    try {
        code = eval("[" + source + "]");
    }
    catch (ex) {
        try {
            code = eval("[" + source + "]]"); // try closing off trailing list
        }
        catch (ex) {
            try {
                code = eval("[" + source + "']"); // try closing off trailing quote
            }
            catch (ex) {
                try {
                    code = eval("[" + source + '"]'); // try closing off trailing quote
                }
                catch (ex) {
                    return; // just bail if source doesn't "compile"
                }
            }
        }
    }
    document.getElementById("interactive").innerHTML = "<div class='words'>" + prettyPrint(code) + "</div>";
}

// TODO: Works for arrays (test for objects, etc.)
function deepCopy(o) { if (o == null || typeof (o) != 'object') return o; var t = []; for (var k in o) t[k] = deepCopy(o[k]); return t; }

function push(a) { if (a !== undefined) data.push(a); } // undefined comes from unit functions
function pop() { return data.pop(); }
function unary(fn) { fn(pop()); }
function binary(fn) { fn(pop(), pop()); }
function tertiary(fn) { fn(pop(), pop(), pop()); }
function quaternary(fn) { fn(pop(), pop(), pop(), pop()); }
function quinary(fn) { fn(pop(), pop(), pop(), pop(), pop()); }
function senary(fn) { fn(pop(), pop(), pop(), pop(), pop(), pop()); }
function septenary(fn) { fn(pop(), pop(), pop(), pop(), pop(), pop(), pop()); }
function expand(source) { code = eval("[" + source + "]").concat(code); }
function rec() { unary(function (a) { code = [eval(a)].concat(code); }); } // used to expand names of recursive definitions
function jsobj() { unary(function (a) { push(eval(a)); }); }
function jspropget() { binary(function (p, o) { var v = o[p]; push(v ? v : false); }); } // note: no 'undefined'
function jspropset() { tertiary(function (v, p, o) { o[p] = v; }); }
function jscall0() { unary(function (f) { f(); }); } // TODO: is there a better way?
function jscall1() { binary(function (a1, f) { f(a1); }); }
function add() { binary(function (b, a) { push(a + b); }); } add._nickname = "+";
function sub() { binary(function (b, a) { push(a - b); }); } sub._nickname = "-";
function neg() { unary(function (a) { push(-a); }); }
function mul() { binary(function (b, a) { push(a * b); }); } mul._nickname = "×";
function div() { binary(function (b, a) { push(a / b); }); } div._nickname = "÷";
function mod() { binary(function (b, a) { push(a % b); }); } mod._nickname = "%";
function gt() { binary(function (b, a) { push(a > b); }); } gt._nickname = ">";
function lt() { binary(function (b, a) { push(a < b); }); } lt._nickname = "<";
function gte() { binary(function (b, a) { push(a >= b); }); } gte._nickname = "≥";
function lte() { binary(function (b, a) { push(a <= b); }); } lte._nickname = "≤";
function eq() { binary(function (b, a) { push(a == b); }); } eq._nickname = "=";
function neq() { binary(function (b, a) { push(a != b); }); } neq._nickname = "≠";
function seq() { binary(function (b, a) { push(a === b); }); }
function sneq() { binary(function (b, a) { push(a !== b); }); }
function lsh() { binary(function (b, a) { push(a << b); }); }
function rsh() { binary(function (b, a) { push(a >> b); }); }
function rshu() { binary(function (b, a) { push(a >>> b); }); }
function band() { binary(function (b, a) { push(a & b); }); }
function bor() { binary(function (b, a) { push(a | b); }); }
function bxor() { binary(function (b, a) { push(a ^ b); }); }
function bnot() { unary(function (a) { push(~a); }); }
function iif() { var f = pop(); var t = pop(); code = (pop() ? t : f).concat(code); }
function and() { binary(function (b, a) { push(a && b); }); }
function or() { binary(function (b, a) { push(a || b); }); }
function not() { unary(function (a) { push(!a); }); }
// stack manipulation
function clear() { data.length = 0; }
function drop() { pop(); }
function dup() { push(data[data.length - 1]); } // dup is more efficient and works with imperative things (e.g. graphics contexts, etc.)
function clone() { push(deepCopy(data[data.length - 1])); } // clone is required if you don't want "phantom updates" to copies as you cons/uncons, etc.
function swap() { binary(function (a, b) { push(a); push(b); }); }
function dip() { binary(function (quot, a) { code.unshift(a); code = quot.concat(code); }); }
function cons() { binary(function (list, a) { list.unshift(a); push(list); }); }
function uncons() { unary(function (list) { var a = list.shift(); push(a); push(list); }); }
function apply() { unary(function (list) { expand(list) }); }
function empty() { push([]); } // why doesn't this work? var empty = def("[]"); empty._nickname = "∅";
function isempty() { unary(function (list) { push(list.length == 0); }); } isempty._nickname = "∅?"; // note: aplies to strings too
function getelem() { unary(function (id) { push(document.getElementById(id)); }); }
function jsregexp() { binary(function (flags, pattern) { push(new RegExp(pattern, flags)); }); }
function jsobjcall0() { binary(function (m, o) { push(o[m]()); }); }
function jsobjcall1() { tertiary(function (a, m, o) { push(o[m](a)); }); }
function jsobjcall2() { quaternary(function (a2, a1, m, o) { push(o[m](a1, a2)); }); }
function jsobjcall3() { quinary(function (a3, a2, a1, m, o) { push(o[m](a1, a2, a3)); }); }
function jsobjcall4() { senary(function (a4, a3, a2, a1, m, o) { push(o[m](a1, a2, a3, a4)); }); }
function jsobjcall5() { septenary(function (a5, a4, a3, a2, a1, m, o) { push(o[m](a1, a2, a3, a4, a5)); }); }
function nan() { push(Number.NaN); }
function isfinite() { unary(function (a) { push(isFinite(a)); }); }
function isnan() { unary(function (a) { push(isNaN(a)); }); }
function gettype() { unary(function (x) { push(typeof (x)); }); }

// Math
function me() { push(Math.E); } me._nickname = "e";
function mln10() { push(Math.LN10); } mln10._nickname = "ln₁₀";
function mln2() { push(Math.LN2); } mln2._nickname = "ln₂";
function mlog10e() { push(Math.LOG10E); } mlog10e._nickname = "log₁₀e";
function mlog2e() { push(Math.LOG2E); } mlog2e._nickname = "log₂e";
function pi() { push(Math.PI); } pi._nickname = "π";
function msqrt1_2() { push(Math.SQRT1_2); }
function msqrt2() { push(Math.SQRT2); } msqrt2._nickname = "√2";
function mabs() { unary(function (x) { push(Math.abs(x)); }); } mabs._nickname = "abs";
function macos() { unary(function (x) { push(Math.acos(x)); }); } macos._nickname = "acos";
function masin() { unary(function (x) { push(Math.asin(x)); }); } masin._nickname = "asin";
function matan() { unary(function (x) { push(Math.atan(x)); }); } matan._nickname = "atan";
function matan2() { unary(function (x) { push(Math.atan2(x)); }); } matan2._nickname = "atan2";
function mceil() { unary(function (x) { push(Math.ceil(x)); }); } mceil._nickname = "ceil";
function mcos() { unary(function (x) { push(Math.cos(x)); }); } mcos._nickname = "cos";
function mexp() { unary(function (x) { push(Math.exp(x)); }); } mexp._nickname = "exp";
function mfloor() { unary(function (x) { push(Math.floor(x)); }); } mfloor._nickname = "floor";
function mlog() { unary(function (x) { push(Math.log(x)); }); } mlog._nickname = "log";
function mmax() { binary(function (y, x) { push(Math.max(x, y)); }); } mmax._nickname = "max";
function mmin() { binary(function (y, x) { push(Math.min(x, y)); }); } mmin._nickname = "min";
function mpow() { binary(function (y, x) { push(Math.pow(x, y)); }); } mpow._nickname = "xⁿ";
function mrand() { push(Math.random()); } mrand._nickname = "rand";
function mround() { unary(function (x) { push(Math.round(x)); }); } mround._nickname = "round";
function msin() { unary(function (x) { push(Math.sin(x)); }); } msin._nickname = "sin";
function msqrt() { unary(function (x) { push(Math.sqrt(x)); }); } msqrt._nickname = "√";
function mtan() { unary(function (x) { push(Math.tan(x)); }); } mtan._nickname = "tan";
function advmode() { advanced = true; document.getElementById("vocab").style.display = ""; document.getElementById("advcontrols").style.display = ""; document.getElementById("canvas").style.display = ""; }
function nav() { unary(function (x) { window.open(x); }); }

function showtrace() { unary(function (a) { trace = ""; traceLogging = a; }); }

function _scope() { }; // marker really
function _sleep() { }; // marker really
function _break() { }; // marker really

var vocab = new Array();

function def(name, source, nickname, color) {
    var body = eval("[" + source + "]");
    var fn = new function () {
        this.name = name;
        this._isUserFunction = true; // TODO: this name is now 'reserved' otherwise it confuses the pretty printer
        this.invoke = function () {
            code = body.concat(code);
        }
    }
    fn._nickname = nickname; // TODO: _nickname is a reserved word?
    fn._color = color; // TODO: _color is a reserved word?
    window[name] = vocab[name] = fn;
}

def("jsobjpropget", "[jsobj],dip,jspropget");
def("jsobjpropset", "[[jsobj],dip],dip,jspropset");
def("xor", "[not],empty,iif");
def("head", "uncons,drop");
def("tail", "uncons,swap,drop");
def("iszero", "0,eq");
def("isone", "1,eq");
def("swons", "swap,cons");
def("quote", "empty,cons");
def("prop", "quote,dip,jsobjpropget");
def("numprop", "'Number',prop");
def("minnum", "'MIN_VALUE',numprop");
def("maxnum", "'MAX_VALUE',numprop");
def("neginf", "'NEGATIVE_INFINITY',numprop");
def("posinf", "'POSITIVE_INFINITY',numprop");
def("quote1", "quote");
def("quote2", "quote1,cons");
def("quote3", "quote2,cons");
def("quote4", "quote3,cons");
def("quote5", "quote4,cons");
def("quote6", "quote5,cons");
def("quote7", "quote6,cons");
def("quote8", "quote7,cons");
def("quote9", "quote8,cons");
def("drop1", "drop");
def("drop2", "drop1,drop");
def("drop3", "drop2,drop");
def("drop4", "drop3,drop");
def("drop5", "drop4,drop");
def("drop6", "drop5,drop");
def("drop7", "drop6,drop");
def("drop8", "drop7,drop");
def("drop9", "drop8,drop");
def("dig1", "quote1,dip");
def("dig2", "quote2,dip");
def("dig3", "quote3,dip");
def("dig4", "quote4,dip");
def("dig5", "quote5,dip");
def("dig6", "quote6,dip");
def("dig7", "quote7,dip");
def("dig8", "quote8,dip");
def("dig9", "quote9,dip");
def("apply", "dup,dip,drop");
def("dwaply", "dip,swap,apply");
def("bury1", "[quote1],dwaply");
def("bury2", "[quote2],dwaply");
def("bury3", "[quote3],dwaply");
def("bury4", "[quote4],dwaply");
def("bury5", "[quote5],dwaply");
def("bury6", "[quote6],dwaply");
def("bury7", "[quote7],dwaply");
def("bury8", "[quote8],dwaply");
def("bury9", "[quote9],dwaply");
def("flip2", "dig1");
def("flip3", "dig1,dig2");
def("flip4", "dig1,dig2,dig3");
def("flip5", "dig1,dig2,dig3,dig4");
def("flip6", "dig1,dig2,dig3,dig4,dig5");
def("flip7", "dig1,dig2,dig3,dig4,dig5,dig6");
def("flip8", "dig1,dig2,dig3,dig4,dig5,dig6,dig7");
def("flip9", "dig1,dig2,dig3,dig4,dig5,dig6,dig7,dig8");
def("jswindowcall1", "'window',jsobj,bury2,bury1,jsobjcall1");
def("jsalert", "'alert',jswindowcall1");
def("jsparseint", "'parseInt',jswindowcall1");
def("jsparsefloat", "'parseFloat',jswindowcall1");
def("jstostring", "'toString',jsobjcall0");
def("jstolocstring", "'toLocaleString',jsobjcall0");
def("jsescape", "'escape',jswindowcall1");
def("jsunescape", "'unescape',jswindowcall1");
def("jsencodeuri", "'encodeURI',jswindowcall1");
def("jsencodeuricomponent", "'encodeURIComponent',jswindowcall1");
def("jsdecodeuri", "'decodeURI',jswindowcall1");
def("jsdecodeuricomponent", "'decodeURIComponent',jswindowcall1");
def("strlen", "'length',jspropget");
def("strcharat", "'charAt',swap,jsobjcall1");
def("strcharcodeat", "'charCodeAt',swap,jsobjcall1");
def("strfromcharcode", "'String',swap,[jsobj],dip,'fromCharCode',swap,jsobjcall1"); // tricky because String is a constructor function too
def("strsubstring", "'substring',dig2,dig2,jsobjcall2");
def("strsubstringstart", "'substring',swap,jsobjcall1");
def("strslice", "'slice',dig2,dig2,jsobjcall2");
def("strslicestart", "'slice',swap,jsobjcall1");
def("strsubstr", "'substr',dig2,dig2,jsobjcall2");
def("strsubstrstart", "'substr',swap,jsobjcall1");
def("strupper", "'toUpperCase',jsobjcall0");
def("strlower", "'toLowerCase',jsobjcall0");
def("strlocupper", "'toLocaleUpperCase',jsobjcall0");
def("strloclower", "'toLocaleLowerCase',jsobjcall0");
def("strsplit", "'split',swap,jsobjcall1");
def("strjoin", "'join',swap,jsobjcall1");
def("strconcat", "'concat',swap,jsobjcall1");
def("strindex", "'indexOf',swap,jsobjcall1");
def("strlastindex", "'lastIndexOf',swap,jsobjcall1");
def("strreplace", "'replace',dig2,dig2,jsobjcall2");
def("strtest", "'test',swap,jsobjcall1");
def("strmatch", "'match',swap,jsobjcall1");
def("strsearch", "'search',swap,jsobjcall1");
def("listrev", "'reverse',jsobjcall0");
def("gxctx", "'canvas',getelem,'getContext','2d',jsobjcall1");
def("gxclear", "gxctx,dup,'fillStyle','rgb(255,255,255)',jspropset,'fillRect',0,0,320,320,jsobjcall4");
def("gxbegin", "gxctx,'beginPath',jsobjcall0");
def("gxclose", "gxctx,'closePath',jsobjcall0");
def("gxmove", "[[gxctx,'moveTo'],dip],dip,jsobjcall2"); // TODO: Factor "double dip"
def("gxline", "[[gxctx,'lineTo'],dip],dip,jsobjcall2"); // TODO: Factor "double dip"
def("gxstroke", "gxctx,'stroke',jsobjcall0");
def("gxfillcolor", "gxctx,'fillStyle',dig2,jspropset");
def("gxfill", "gxctx,'fill',jsobjcall0");
def("htmlset", "'innerHTML',swap,jspropset");
def("htmlget", "'innerHTML',jspropget");
def("isdempty", "dup,isempty", "*∅?");
def("iszero", "0,eq", "0?");
def("isdzero", "dup,0,eq", "*0?");
def("isone", "1,eq", "1?");
def("isdone", "dup,1,eq", "*1?"); // TODO: looks like "is done"
def("iseven", "2,mod,0,eq")
def("isodd", "iseven,not")
def("unpair", "uncons,uncons,drop");
def("pair", "empty,cons,cons");

// palindrome
def("strrev", "'',strsplit,listrev,'',strjoin");
def("strremove", "'',strreplace");
def("nonalpha", "'[^a-z]','g',jsregexp");
def("normalize", "strlower,nonalpha,strremove");
def("ispalindrome", "normalize,dup,strrev,eq");

// geometry
def("sq", "dup,mul", "x²");
def("cube", "dup,sq,mul", "x³");
def("area", "sq,pi,mul");

// trig
def("deg2rad", "pi,180,div,mul");
def("rad2deg", "180,pi,div,mul");

// HOF
def("fold", "dup,dig3,isdempty,[drop,drop,drop],[uncons,swap,dig4,dig3,apply,dig2,'fold',rec],iif");
def("reverse", "empty,[cons],fold");
def("sum", "0,[add],fold");
def("prod", "1,[mul],fold");
def("map", "empty,swap,[bury1,bury2,apply,cons],cons,fold,reverse");
def("prepword", "uncons,drop,cons")
def("filter", "[],[apply,[cons],[drop],iif],dig2,cons,[dup],prepword,[swap],prepword,fold");
def("repeat", "1,sub,dup,0,lt,[drop,drop],[[clone,[apply],dip],dip,'repeat',rec],iif");

// graphics
def("tcenter", "160,160,empty,cons,cons"); // [160,160] causes issues with dup (vs. clone)
def("tline", "swap,unpair,gxmove,clone,unpair,gxline,gxstroke,_sleep");
def("tmove", "clone,unpair,gxmove,swap,drop");
def("treset", "gxclear,gxbegin,tcenter,clone,unpair,gxmove");

// logo
def("logostepsize", "10");
def("logoforward", "dup,bury2,logostepsize,dup,dig2,deg2rad,dup,mcos,swap,msin,dig2,mul,bury2,mul,dig2,unpair,bury2,add,bury2,add,pair,swap"); // TOOD: Deprecate
def("logofor", "swap,clone,dig2,logoforward,bury2,tline,swap"); // TODO: Deprecate
def("logoforward2", "[dup,bury2],dip,dup,dig2,deg2rad,dup,mcos,swap,msin,dig2,mul,bury2,mul,dig2,unpair,bury2,add,bury2,add,pair,swap");
def("logowalk", "[swap,clone,dig2],dip,logoforward2,bury2,tline,swap");
def("logoright", "add");
def("logoleft", "sub");
def("logostart", "treset,0");
def("logostop", "drop,drop");
def("logohardleft", "90,logoleft");
def("logohardright", "90,logoright");
def("logoturnaround", "180,logoleft");
def("logomoveto", "bury2,dig1,drop");
def("logopointat", "swap,drop");

def("drawarc", "[logofor,10,logoright],9,repeat");
def("drawcircle", "[drawarc],4,repeat");
def("drawline", "[logofor],8,repeat");
def("drawregpolygon", "dup,bury2,360,swap,div,[[logowalk],dip,logoright],cons,cons,dig1,repeat");
def("drawtriangle", "40,3,drawregpolygon");
def("drawsquare", "40,4,drawregpolygon");
def("drawpentagon", "40,5,drawregpolygon");
def("drawhexagon", "40,6,drawregpolygon");
def("drawheptagon", "40,7,drawregpolygon");
def("drawoctagon", "40,8,drawregpolygon");
def("drawspirograph", "[drawcircle,20,logoright],18,repeat");

// flower
//def("drawpeddle", "gxbegin,drawarc,90,logoright,drawarc,gxclose,'red',gxfillcolor,gxfill");
def("drawpeddle", "drawarc,90,logoright,drawarc");
def("drawflowerpeddles", "[drawpeddle,45,logoright],8,repeat");
def("drawflowerstem", "[logofor],6,repeat");
def("drawlongstem", "[drawflowerstem],2,repeat");
def("drawflower", "logostart,logohardleft,drawflowerstem,drawflowerpeddles,logoturnaround,drawlongstem,logohardright,drawpeddle,drawlongstem,logostop");

// koch
def("kochn", "dup,[logowalk],dip,[60,logoleft],dip,dup,[logowalk],dip,[120,logoright],dip,dup,[logowalk],dip,[60,logoleft],dip,logowalk");
def("kochSeg3", "3,logowalk");
//def("kochSeg9", "9,logowalk");
//def("kochSeg27", "27,logowalk");
def("kochBend", "60,logoleft");
def("kochTurn", "120,logoright");
def("koch3", "kochSeg3,kochBend,kochSeg3,kochTurn,kochSeg3,kochBend,kochSeg3");
def("koch9", "koch3,kochBend,koch3,kochTurn,koch3,kochBend,koch3");
def("koch27", "koch9,kochBend,koch9,kochTurn,koch9,kochBend,koch9");
def("koch81", "koch27,kochBend,koch27,kochTurn,koch27,kochBend,koch27");
def("snowflake", "[40,90],logomoveto,0,logopointat,[koch81,kochTurn],3,repeat");

// demo
def("facnontail", "dup,isone,empty,[dup,1,sub,'facnontail',rec,mul],iif"); // non-tail recursive
def("factail", "swap,dup,isone,empty,[dup,1,sub,dig2,dig2,mul,'factail',rec],iif");
def("fac", "1,factail,drop");
def("ispyth", "bury2,sq,swap,sq,add,msqrt,eq");

// simple logo subset
var c = "Green";
def("S", "logostart", "Start", c);
def("F", "logowalk", "Forward", c);
def("R", "logoright", "Right", c);
def("L", "logoleft", "Left", c);
def("E", "logostop", "End", c);
def("L90", "logohardleft", "Hard Left", c);
def("R90", "logohardright", "Hard Right", c);
def("T180", "logoturnaround", "Turn Around", c);
def("M", "logomoveto", "Move To", c);
def("P", "logopointat", "Point At", c);
def("X", "repeat", "Repeat", c);

def("wc", "'http://www.',swap,strconcat,'.com',strconcat");
def("wcgo", "wc,nav");
def("search", "[wc],dip,strconcat,swap,jsencodeuricomponent,strconcat,nav");
def("bing", "'bing','?q=',search");
def("wolf", "'wolframalpha','/input/?i=',search");
def("goog", "'google','/search?q=',search");
def("c9", "'http://channel9.msdn.com',nav");
def("fb", "'facebook',wcgo");
def("boa", "'bankofamerica',wcgo");
def("chase", "'chase',wcgo");
def("nz", "'msnbc',wcgo");
def("stock", "'http://www.bing.com/stocksandfunds/search?q=US:',swap,strconcat,nav");
def("msft", "'msft',stock");
def("aapl", "'aapl',stock");
def("blog", "'http://blogs.msdn.com/ashleyf',nav");
def("twitter", "'http://twitter.com/AshleyFen',nav");
def("xnav", "'http://lkjsdf.com/archive/xnav',nav");
def("turtle", "'http://lkjsdf.com/archive/turtle?(320dg0(1adf89r)720x1lc)1000x',nav");
def("gan", "'http://www.google.com/analytics/',nav");
def("amz", "'http://www.amazon.com',nav");



function lookup(word) { // TODO: chain namespaces
    return vocab[word] !== null ? word : vocab[word];
}

var lastWord = null;

function start(source, name) {
    steps = 0;
    clear();
    lastWord = null;
    code = eval("[" + source + "]");
    trace = "";
    traceStep(name, null);
}

var needCleanup = true;
var cleanupPending = false;

function cleanUpAfterSleep() { // TODO: Total hack!
    if (needCleanup) {
        traceStep("end", null);
        needCleanup = cleanupPending = false;
    }
}

function runToScope() {
    needCleanup = true;
    var word;
    while (code.length > 0) {
        var step = code.shift();
        word = lookup(step);
        if (step === _scope) break; // end of scope block
        steps++;
        if (word._isUserFunction == true) word.invoke(data);
        else if (typeof (word) == "function") word(data);
        else data.push(word); // literal
        if (step === _sleep) {
            window.setTimeout(runToScope, 0);
            if (!cleanupPending) {
                //window.setTimeout(cleanUpAfterSleep, 250);
                cleanupPending = true;
            }
            break;
        }
    }
    return word;
}

function step(into, over, out) {
    //alert("into: " + into + ", over: " + over + ", out: " + out);
    if (lastWord !== null) { // pending expansion
        //alert("expansion:" + lastWord);
        if (code.length > 0 && // no sense adding scope at very end
            code[0] != _scope) // and no reason to add multiple breaks in a row
            code = [_scope].concat(code);
        lastWord.invoke(data);
        lastWord = null;
        if (into) {
            //alert("into");
            traceStep("expand", null);
            return; // that's it for this step (artificial step for debugging)
        }
        if (over) runToScope();
    }
    else if (into) {
        //alert("Cannot expand atomic word.");
        return; // nothing to step into
    }
    else if (out) { // step out?
        //alert("OUT!");
        traceStep(runToScope(), null);
        return;
    }
    if (code.length > 0) {
        //alert("step");
        var step, word;
        do {
            step = code.shift();
            word = lookup(step);
            //if (step === _scope) alert("skipping scope");
        } while (step === _scope);  // end of scope block
        if (step === undefined) { // nothing after scope
            traceStep("end", null);
            return;
        }

        steps++;
        traceStep(word, step);
        if (word._isUserFunction == true) {
            lastWord = word;
        }
        else if (typeof (word) == "function") {
            word(data);
        }
        else {
            data.push(word); // literal
        }
    }
    else traceStep("end", null);
}

function run(source, name) {
    start(source, name);
//        while (code.length > 0)
//            step(true);
}

var advanced = false;
function go() {
    if (advanced) {
        document.getElementById("go").focus();
        //traceStart();
        eval(document.getElementById("vocab").value);
        var source = document.getElementById("source").value;
        run(source, "main");
        //traceStep("end", null);
        //traceEnd();
        //alert(trace);
    }
    else {
        eval(document.getElementById("vocab").value);
        var source = document.getElementById("source").value;
        document.getElementById("source").value = "";
        try {
            run(source, "main");
        }
        catch (ex) {
            // try again as a simple Bing query
            run("'" + source + "',bing", "main");
        }
        step(false, false, true);
        
    }
}

var tests = "";
var count = 0;

function test(source, expected) {
    count++;
    run(source);
    var expect = eval("[" + expected + "]");
    if (data != expect && data.toString() != expect.toString())
        tests += "FAILURE: " + source + " (EXPECTED: " + expect + ", ACTUAL: " + data + ")<br/>";
    //else tests += "SUCCESS: " + source + " (" + data + ")<br/>";
}

function runTests() {
    test("2,2,add", "4");
    test("-2,3,add", "1");
    test("3,-2,add", "1");
    test("1.23,9,add", "10.23");
    test("'hello','world',add", "'helloworld'");
    test("'hello',1,add", "'hello1'");
    test("true,3,add", "4"); // TODO: Allow? (type check?)
    test("false,3,add", "3"); // TODO: Allow? (type check?)
    test("5,2,sub", "3");
    test("-2,3,sub", "-5");
    test("3,-2,sub", "5");
    test("9.23,2,sub", "7.23");
    test("'hello',1,sub", "NaN"); // TODO: Type check?
    test("true,3,sub", "-2"); // TODO: Allow? (type check?)
    test("false,3,sub", "-3"); // TODO: Allow? (type check?)
    test("3,neg", "-3");
    test("-3,neg", "3");
    test("8,3,mul", "24");
    test("123,10,div", "12.3"); // note: not integer division
    test("1234,100,mod", "34");
    test("100,99,lt", "true");
    test("99,100,lt", "false");
    test("100,99,lte", "true");
    test("99,100,lte", "false");
    test("100,100,lte", "true");
    test("100,99,gt", "false");
    test("99,100,gt", "true");
    test("100,99,gte", "false");
    test("99,100,gte", "true");
    test("100,100,gte", "true");
    test("100,100,eq", "true");
    test("99,100,eq", "false");
    test("100,100,neq", "false");
    test("99,100,neq", "true");
    test("'123',123,eq", "true"); // strict equality
    test("'123',123,seq", "false");
    test("'123',123,neq", "false");
    test("'123',123,sneq", "true");
    test("true,1,eq", "true");
    test("true,1,seq", "false");
    test("true,1,neq", "false");
    test("true,1,sneq", "true");
    test("1000,3,lsh", "8000");
    test("1000,3,rsh", "125");
    test("0xffff0000,8,rshu", "0x00ffff00");
    test("0x55555555,0xff00ffff,band", "0x55005555");
    test("0x55555555,0x00ff0000,bor", "0x55ff5555");
    test("0x55555555,0x00ff0000,bxor", "0x55aa5555");
    test("0xaaaa,bnot", "-43691");
    test("0xaaaa,bnot,0xffff,band", "0x5555");
    test("true,2,3,iif", "2");
    test("false,2,3,iif", "3");
    test("true,[2,2,add],[3,3,mul],iif", "4");
    test("false,[2,2,add],[3,3,mul],iif", "9");
    test("true,true,and", "true");
    test("false,true,and", "false");
    test("true,false,or", "true");
    test("false,false,or", "false");
    test("true,not", "false");
    test("false,not", "true");
    test("true,true,xor", "false");
    test("false,true,xor", "true");
    test("true,false,xor", "true");
    test("false,false,xor", "false");
    test("minnum", "Number.MIN_VALUE");
    test("maxnum", "Number.MAX_VALUE");
    test("posinf", "Number.POSITIVE_INFINITY");
    test("1,0,div,posinf,eq", "true");
    test("1,0,div,isfinite", "false");
    test("neginf", "Number.NEGATIVE_INFINITY");
    test("-1,0,div,neginf,eq", "true");
    test("-1,0,div,isfinite", "false");
    test("nan", "Number.NaN");
    test("nan,isnan", "true");
    test("0,0,div,isnan", "true");
    test("'foo','bar',clear,10", "10");
    test("1,2,3,drop", "1,2");
    test("1,2,3,dup", "1,2,3,3");
    test("1,2,3,swap", "1,3,2");
    test("1,2,3,[4,add],dip", "1,6,3");
    test("empty", "[]");
    test("empty,2,swons,1,swons", "[1,2]");
    test("[2],1,swons", "[1,2]");
    test("[1,2,3],uncons", "1,[2,3]");
    test("3,[4,add],apply", "7");
    test("'',isempty", "true");
    test("empty,isempty", "true");
    test("3,isempty", "false");
    test("[1,2,3],head", "1");
    test("[1,2,3],tail", "[2,3]");
    test("0,iszero", "true");
    test("1,iszero", "false");
    test("0,isone", "false");
    test("1,isone", "true");
    test("1,quote", "[1]");
    test("1,quote1", "[1]");
    test("1,2,quote2", "[1,2]");
    test("1,2,3,quote3", "[1,2,3]");
    test("1,2,3,4,quote4", "[1,2,3,4]");
    test("1,2,3,4,5,quote5", "[1,2,3,4,5]");
    test("1,2,3,4,5,6,quote6", "[1,2,3,4,5,6]");
    test("1,2,3,4,5,6,7,quote7", "[1,2,3,4,5,6,7]");
    test("1,2,3,4,5,6,7,8,quote8", "[1,2,3,4,5,6,7,8]");
    test("1,2,3,4,5,6,7,8,9,quote9", "[1,2,3,4,5,6,7,8,9]");

    document.write("Tests: " + count + " (" + steps + " steps)");
    if (tests.length > 0) document.write(tests);
}

// TODO: Test NaN, null, undefined, "", 0 vs. false, etc. (http://www.mapbender.org/JavaScript_pitfalls:_null,_false,_undefined,_NaN)
// TODO: Test hex, octal, exponential notation, etc.
// CONSIDER: Implement assignment and +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>=, ++, --, etc.?
// TODO: Test implicit number conversions (e.g. "2560" === "256" + 0)
// TODO: Add boolean xor word
// TODO: Parse more Factor-like syntax?
// TODO: Compile literals to functions that push literals?
// TODO: Rewrite as pure functions? (now using stack effects)
// TODO: Make equality deep
// TODO: RegEx related string methods
// TODO: toFixed/Precision/Exponential

var myword =
{
    word: "isone",
    display: "1?",
    color: [255, 50, 50],
    documentation:
    {
        effect: [['n'], ['bool']],
        summary: "Number one?",
        description: "Consumes a number and compares with exactly 1."
    },
    tests:
    [
        ["1,isone", "true"],
        ["0,isone", "false", "In fact this works with non-numbers."]
    ]
};
