Posted to tcl by suchenwi at Sun Nov 10 02:12:53 GMT 2013view pretty

/* =================================================== -*- C++ -*-
 * tcl.js "A Tcl implementation in Javascript"
 *
 * Released under the same terms as Tcl itself.
 * (BSD license found at <http://www.tcl.tk/software/tcltk/license.html>)
 *
 * Based on Picol by Salvatore Sanfilippo (<http://antirez.com/page/picol>)
 * (c) Stéphane Arnold 2007
 * Richard Suchenwirth 2007, 2013: cleanup, additions
 * vim: syntax=javascript autoindent softtabwidth=4
 */
_step = 0; // set to 1 for debugging
var fs = require('fs');
puts = console.log;

function TclInterp () {
  this.patchlevel = 0.4;
  this.callframe = new Array(new Object());
  this.level     = 0;
  this.commands  = new Object();
  this.procs     = new Array();
  this.script    = "";
  this.OK  = 0;
  this.RET = 1;
  this.BRK = 2;
  this.CNT = 3;
  this.getVar = function(name) {
    var nm = name.toString();
    if (nm.match("^::env[(]")) nm=nm.substr(2);
    if (nm.match("^env[(]")) {
      var key = nm.substr(4,nm.length-5);
      var val = process.env[key];
    } else if (nm.match("^::")) {
      var val = this.callframe[0][nm.substr(2)]; // global
    } else {
      var val = this.callframe[this.level][name];
    }
    if (val == null) throw 'can\'t read "'+name+'": no such variable';
    return val;
  }
  this.setVar = function(name, val) {
    var nm = name.toString();
    if (nm.match("^::")) {
      this.callframe[0][nm.substr(2)] = val;
    } else {this.callframe[this.level][name] = val;}
    return val;
  }
  this.setVar("argc",  process.argv.length-2);
  this.setVar("argv0", process.argv[1]);
  this.setVar("argv",  process.argv.slice(2));
  this.setVar("errorInfo", "");

  this.incrLevel = function() {
    this.callframe[++this.level] = new Object();
    return this.level;
  }
  this.decrLevel = function() {
    this.callframe[this.level] = null;
    this.level--;
    if (this.level<0) throw "Exit application";
    this.result = null;
  }
  this.getCommand = function(name) {
    try {
      return this.commands[name];
    } catch (e) {throw "No such command '"+name+"'";}
  }
  this.registerCommand = function(name, func, privdata) {
    if (func == null) throw "No such function: "+name;
    this.commands[name] = new TclCommand(func, privdata);
  }
  this.renameCommand = function (name, newname) {
    this.commands[newname] = this.commands[name];
    if (this.procs[name]) {
      this.procs[name] = null;
      this.procs[newname] = true;
    }
    this.commands[name] = null;
  }
  this.registerSubCommand = function(name, subcmd, func, privdata) {
    if (func == null) throw "No such subcommand: "+ name +" " + subcmd;
    var path = name.split(" ");
    var ens;
    name = path.shift();
    var cmd = this.commands[name];
    if (cmd == null) {
      ens = new Object();
      ens["subcommands"]  = new TclCommand(Tcl.InfoSubcommands, null);
      this.commands[name] = new TclCommand(Tcl.EnsembleCommand, null, ens);
    }
    ens = this.commands[name].ensemble;
    if (ens == null) throw "Not an ensemble command: '"+name+"'";
    // walks deeply into the subcommands tree
    while (path.length > 0) {
      name = path.shift();
      cmd = ens[name];
      if (cmd == null) {
        cmd = new TclCommand(Tcl.EnsembleCommand, null, new Object());
        ens[name] = cmd;
        ens = cmd.ensemble;
        ens["subcommands"] = new TclCommand(Tcl.InfoSubcommands, null);
      }
    }
    ens[subcmd] = new TclCommand(func, privdata);
  }
  this.eval = function (code) {
    try {
      return this.eval2(code);
    } catch (e) {
      var msg = code.substr(0,128);
      if(msg.length >= 125) msg += "...";
      puts(e);
      this.setVar("::errorInfo",e+'\n    while executing\n"'+msg+'"');
    }
  }
  this.eval2 = function(code) {
    this.code = this.OK;
    var parser = new TclParser(code);
    var args = new Array(0);
    var first = true;
    var text, prevtype, result;
    result = "";
    while (true) {
      prevtype = parser.type;
      try {
        parser.getToken();
      } catch (e) {break;}
      if (parser.type == (parser.EOF)) break;
      text = parser.getText();
      if (parser.type == (parser.VAR)) {
        try {
          text = this.getVar(text);
        } catch (e) {throw "No such variable '" + text + "'";}
      } else if (parser.type == (parser.CMD)) {
        try {
          text = this.eval2(text);
        } catch (e) {throw (e + "\nwhile parsing \"" + text + "\"");}
      } else if (parser.type == (parser.ESC)) {
        // escape handling missing!
      } else if (parser.type == (parser.SEP)) {
        prevtype = parser.type;
        continue;
      }
      text = this.objectify(text);
      if (parser.type ==parser.EOL || parser.type == parser.EOF) {
        prevtype = parser.type;
        if (args.length > 0) {
	  try {
	    result = this.call(args);
	  } catch(e) {
	    if(e.toString().match("Cannot call method")) 
	      throw 'invalid command name "'+args[0].toString()+'"';
	    throw e;
	  }
          if (this.code != this.OK) return this.objectify(result);
        }
        args = new Array();
        continue;
      }
      if (prevtype == parser.SEP || prevtype == parser.EOL) {
        args.push(text);
      } else {
        args[args.length-1] = args[args.length-1].toString() + text.toString();
      }
    }
    if (args.length > 0) result = this.call(args);
    return this.objectify(result);
  }
  //---------------------------------- Commands in alphabetical order
  this.registerCommand("append", function (interp, args) {
    this.requireMinArgc(args, 2);
    var vname = args[1].toString();
    if (interp.callframe[interp.level][vname] != null) {
      var str = interp.getVar(vname);
    } else var str = "";
    for (var i = 2; i < args.length; i++) str += args[i].toString();
    interp.setVar(vname, str);
    return str;
  });
  this.registerCommand("break", function (interp, args) {
    interp.code = interp.BRK;
    return;
  });
  this.registerCommand("cd", function (interp, args) {
      this.requireArgcRange(args, 1, 2);
      var dir = process.env.HOME;
      if (args.length == 2) dir = args[1].toString();
      process.chdir(dir);
      return;
    });
  this.registerCommand("continue", function (interp, args) {
    interp.code = interp.CNT;
    return;
  });
  this.registerSubCommand("clock", "format", function (interp, args) {
    var now = new Date();
    now.setTime(args[1]);
    return now.toString();
  });
  this.registerSubCommand("clock", "milliseconds", function (interp, args) {
      var t = new Date();
      return t.valueOf();
  });
  this.registerSubCommand("clock", "scan", function (interp, args) {
    return Date.parse(args[1]);
   });
  this.registerSubCommand("clock", "seconds", function (interp, args) {
      return Math.floor((new Date()).valueOf()/1000);
  });
  
  this.registerSubCommand("dict", "create", function (interp, args) {
      if(args.length % 2 == 0) 
	throw 'wrong # args: should be "dict create ?key value ...?';
      return new TclObject(args.slice(1));
  });
  this.registerSubCommand("dict", "get", function (interp, args) {
      if(args.length < 2) 
	throw 'wrong # args: should be "dict get ?key ...?';
      var dict = args[1].toList();
      var key  = args[2].toString();
      for (var i=0;i < dict.length;i+=2) {
	if(dict[i].toString() == key) return dict[i+1];
      }
      throw 'key "'+key+'" not known in dictionary';
    });
  this.registerSubCommand("dict", "set", function (interp, args) {
      this.requireExactArgc(args, 4);
      var name = args[1];
      var dict = interp.getVar(name);
      var key   = args[2].toString();
      var val   = args[3].toString();
      var found = false;
      puts("dict: "+dict+" dict.length: "+dict.length);
      dict.toList();
      for (var i=0;i < dict.length;i+=2) {
	puts("trying "+key+" at "+i+": "+dict[i].toString());
	if(dict[i].toString() == key) {
	  dict[i+1] = val;
	  found = true;
	  break;
	}
      }
      if (!found) {
	dict.content.push(interp.objectify(key)); 
	dict.content.push(interp.objectify(val));
      } 
      interp.setVar(name, dict);
      return dict;
    });
  /*
  if(typeof(jQuery) != 'undefined') {
    this.registerCommand("dom", function (interp, args) {
      var selector = args[1].toString();
      var fn = args[2].toString();
      args = args.slice(3);
      for (var i in args) args[i] = args[i].toString();
      var q = $(selector);
      q[fn].apply(q,args);
      return "dom  " + selector;
    });
    }*/
  this.registerCommand("eval",function (interp, args) {
    this.requireMinArgc(args, 2);
    for (var i = 1; i < args.length; i++) args[i] = args[i].toString();
    if (args.length == 2) var code = args[1];
    else                  var code = args.slice(1).join(" ");
    return interp.eval(code);
  });
  /*
  this.registerCommand("exec",function (interp, args) {
      this.requireMinArgc(args, 2);
      var exec = require('child_process').exec,
	child;
      puts("exec "+args.slice(1).join(" "));
      child = exec(args.slice(1).join(" "),
		   function (error, stdout, stderr) {
		     var res = stdout.toString();
		     //console.log('stdout: ' + stdout.toString());
		     if (error !== null) {
		       throw('exec error: ' + error);
		     } 
		     return res;
		   });
      return this.execres;
    });
  */
  this.registerCommand("exit",function (interp, args) {
      this.requireMinArgc(args, 1);
      var rc = 0;
      if (args.length == 2) rc = args[1];
      process.exit(rc);
    });
  acos = Math.acos;
  exp  = Math.exp;
  sqrt = Math.sqrt; // "publish" other Math.* functions as needed

  this.registerCommand("expr", function (interp, args) {
      var expression = args.slice(1).join(" ");
      return interp.expr(interp, expression);
  });
  this.expr =  function ($interp, $expression) {
    var $mx = $expression.match(/(\$[A-Za-z0-9_:]+)/g);
    for ($i in $mx)
      eval("var "+$mx[$i]+" = "+$interp.getVar($mx[$i].slice(1)));
    var res = eval($expression);
    if(res == false) res = 0; else if(res == true) res = 1;
    return res;
  };
  this.registerCommand("for", function (interp, args) {
    this.requireExactArgc(args, 5);
    interp.eval(args[1].toString());
    if(interp.code != interp.OK) return;
    var cond = args[2].toString();
    var step = args[3].toString();
    var body = args[4].toString();
    interp.inLoop = true;
    interp.code = interp.OK;
    while (true) {
      test = interp.objectify(interp.expr(interp, cond));
      if (!test.toBoolean()) break;
      interp.eval(body);
      var ic = interp.code; // tested after step command
      interp.eval(step);
      if(ic == interp.BRK) break;
      if(ic == interp.CNT) continue;
    }
    interp.inLoop = false;
    if(interp.code == interp.BRK || interp.code == interp.CNT)
      interp.code = interp.OK;
    return "";
  });
  this.registerCommand("foreach", function (interp, args) {
    this.requireExactArgc(args, 4);
    var list = args[2].toList();
    var body = args[3].toString();
    var res  = "";
    interp.inLoop = true;
    interp.code = interp.OK;
    for(i in list) {
       interp.setVar(args[1],interp.objectify(list[i]));
       interp.eval(body);
       if(interp.code == interp.BRK) break;
       if(interp.code == interp.CNT) continue;
    }
    interp.inLoop = false;
    if(interp.code == interp.BRK || interp.code == interp.CNT)
      interp.code=interp.OK;
    return "";
  });
  /*  this.registerCommand("gets", function (interp, args) {
    this.requireArgcRange(args, 2, 3);
    var reply; // = prompt(args[1],"");
    process.stdin.resume();
    process.stdin.on('data', function(str) {
	reply = str;
      });
    if(args[2] != null) {
      interp.setVar(args[2],interp.objectify(reply));
      return reply.length;
    } else return reply;
    }); */
  this.registerCommand("if", function (interp, args) {
    this.requireMinArgc(args, 3);
    var cond = args[1].toString();
    var test = interp.objectify(interp.expr(interp, cond));
    if (test.toBoolean()) return interp.eval(args[2].toString());
    if (args.length == 3) return;
    for (var i = 3; i < args.length; ) {
      switch (args[i].toString()) {
      case "else":
        this.requireExactArgc(args, i + 2);
        return interp.eval(args[i+1].toString());
      case "elseif":
        this.requireMinArgc(args, i + 3);
        test = interp.objectify(interp.expr(interp, args[i+1].toString()));
        if (test.toBoolean())
          return interp.eval(args[i+2].toString());
        i += 3;
        break;
      default:
        throw "Expected 'else' or 'elseif', got "+ args[i];
      }
    }
  });
  this.registerCommand("incr", function (interp, args) {
    this.requireArgcRange(args, 2, 3);
    var name = args[1];
    if (args.length == 2) var incr = 1;
    else var incr = interp.objectify(args[2]).toInteger();
    incr += interp.getVar(name).toInteger();
    return interp.setVar(name, new TclObject(incr, "INTEGER"));
  });
  this.registerSubCommand("info", "args", function (interp, args) {
    this.requireExactArgc(args, 2);
    var name = args[1].toString();
    if (!interp.procs[name]) throw '"'+name+'" isn\'t a procedure';
    return interp.getCommand(name).privdata[0];
  });
  this.registerSubCommand("info", "body", function (interp, args) {
    this.requireExactArgc(args, 2);
    var name = args[1].toString();
    if (!interp.procs[name]) throw '"'+name+'" isn\'t a procedure';
    return interp.getCommand(name).privdata[1];
  });
  this.registerSubCommand("info", "commands", function (interp, args) {
    return interp.mkList(interp.commands);
  });
  this.registerSubCommand("info", "globals", function (interp, args) {
    return interp.mkList(interp.callframe[0]);
  });
  this.registerSubCommand("info", "isensemble", function (interp, args) {
    this.requireExactArgc(args, 2);
    var name = args[1].toString();
    return (interp.getCommand(name).ensemble != null);
  });
  this.registerSubCommand("info", "patchlevel", function (interp, args) {
      return interp.patchlevel.toString();
  });
  this.registerSubCommand("info", "na", function (interp, args) {
      return process.execPath;
  });
  this.registerSubCommand("info", "procs", function (interp, args) {
    return interp.mkList(interp.procs);
  });
  this.registerSubCommand("info", "exists", function (interp, args) {
      var name = args[1];
      try {interp.getVar(name); return 1;} catch(e) {return 0;}
  });
  this.registerSubCommand("info", "script", function (interp, args) {
    return interp.script;
  });
  this.registerSubCommand("info", "vars", function (interp, args) {
    return interp.mkList(interp.callframe[interp.level]);
  });
  this.registerCommand("join", function (interp, args) {
      this.requireArgcRange(args, 2, 3);
      var lst = args[1].toList();
      var sep = " ";
      if(args.length == 3) sep = args[2].toString();
      return lst.join(sep);
    });
  this.registerCommand("jseval", function (interp, args) {
    return eval(args[1].toString());
  });
  this.registerCommand("lappend", function (interp, args) {
    this.requireMinArgc(args, 2);
    var vname = args[1].toString();
    if (interp.callframe[interp.level][vname] != null) {
      var list = interp.getVar(vname);
    } else var list = new TclObject([]);
    list.toList();
    for (var i = 2; i < args.length; i++) {
      list.content.push(interp.objectify(args[i]));
    }
    interp.setVar(vname, list);
    return list;
  });
  this.registerCommand("lindex", function (interp, args) {
    this.requireMinArgc(args, 3);
    var list = interp.objectify(args[1]);
    for (var i = 2; i < args.length; i++) {
      try {
        var index = list.listIndex(args[i]);
      } catch (e) {
        if (e == "Index out of bounds") return "";
        throw e;
      }
      list = list.content[index];
    }
    return interp.objectify(list);
  });
  this.registerCommand("list", function (interp, args) {
      args.shift();
      return new TclObject(args);
  });
  this.registerCommand("llength", function (interp, args) {
    this.requireExactArgc(args, 2);
    return args[1].toList().length;
  });
  this.registerCommand("lrange", function (interp, args) {
    this.requireExactArgc(args, 4);
    var list  = interp.objectify(args[1]);
    var start = list.listIndex(args[2]);
    var end   = list.listIndex(args[3])+1;
    try {
      return list.content.slice(start, end);
    } catch (e) {return new Array();}
  });
  this.registerCommand("lset", function (interp, args) {
    this.requireMinArgc(args, 4);
    var list = interp.getVar(args[1].toString());
    var elt = list;
    for (var i = 2; i < args.length-2; i++) {
      elt.toList();
      elt = interp.objectify(elt.content[elt.listIndex(args[i])]);
    }
    elt.toList();
    i = args.length - 2;
    elt.content[elt.listIndex(args[i])] = interp.objectify(args[i+1]);
    return list;
  });
  this.registerCommand("lsearch", function (interp, args) {
    this.requireExactArgc(args, 3);
    var lst = args[1].toList();
    for(i in lst) if(lst[i] == args[2].toString()) return i;
    return -1;
  });
  this.registerCommand("lsort", function (interp, args) {
    this.requireExactArgc(args, 2);
    return args[1].toList().sort();
  });
  this.registerCommand("pid", function (interp, args) {
      return process.pid;
  });
  this.registerCommand("puts", function (interp, args) {
      this.requireExactArgc(args, 2);
      puts(args[1].toString());
    });
  this.registerCommand("pwd", function (interp, args) {
      return process.cwd();
  });
  this.registerCommand("proc", function (interp, args) {
    this.requireExactArgc(args, 4);
    var name = args[1].toString();
    var argl = interp.parseList(args[2]);
    var body = args[3].toString();
    var priv = new Array(argl,body);
    interp.commands[name] = new TclCommand(Tcl.Proc, priv);
    interp.procs[name] = true;
  });
  this.registerCommand("regexp", function (interp, args) {
    this.requireExactArgc(args, 3);
    var re  = new RegExp(args[1].toString());
    var str = args[2].toString();
    return (str.search(re) > -1? "1":"0");
  });
  this.registerCommand("regsub", function (interp, args) {
    this.requireExactArgc(args, 4);
    var re  = new RegExp(args[1].toString());
    var str = args[2].toString();
    var trg = args[3].toString();
    return (str.replace(re,trg));
  });
  this.registerCommand("rename", function (interp, args) {
    this.requireExactArgc(args, 3);
    interp.renameCommand(args[1], args[2]);
  });
  this.registerCommand("return", function (interp, args) {
    this.requireArgcRange(args, 1, 2);
    var r = args[1];
    interp.code = interp.RET;
    return r;
  });
  this.registerCommand("set", function (interp, args) {
    this.requireArgcRange(args, 2, 3);
    var name = args[1];
    if (args.length == 3) interp.setVar(name, args[2]);
    return interp.getVar(name);
  });
  this.registerCommand("source", function (interp, args) {
      this.requireExactArgc(args, 2);
      interp.script = args[1].toString();
      try {
	var data = fs.readFileSync(interp.script).toString();
      } catch(e) {throw 'couldn\' read file "'+interp.script+
	  '": no such file or directory';}
      var res  = interp.eval(data);
      interp.script = "";
      return res;
    });
  this.registerCommand("split", function (interp, args) {
    this.requireArgcRange(args, 2, 3);
    var str = args[1].toString();
    var sep = " ";
    if (args.length == 3) sep = args[2].toString();
    return str.split(sep);
  });
  this.registerSubCommand("string", "equal", function (interp, args) {
    this.requireExactArgc(args, 3);
    return (args[1].toString() == args[2].toString())? "1": "0";
  });
  this.registerSubCommand("string", "index", function (interp, args) {
    this.requireExactArgc(args, 3);
    var s = args[1].toString();
    try {
      return s.charAt(args[1].stringIndex(args[2]));
    } catch (e) {return "";}
  });
  this.registerSubCommand("string", "length", function (interp, args) {
    this.requireExactArgc(args, 2);
    return args[1].toString().length;
  });
  this.registerSubCommand("string", "range", function (interp, args) {
    this.requireExactArgc(args, 4);
    var s = args[1];
    try {
      var b = s.stringIndex(args[2]);
      var e = s.stringIndex(args[3]);
      if (b > e) return "";
      return s.toString().substring(b, e + 1);
    } catch (e) {return "";}
  });
  this.registerSubCommand("string", "tolower", function (interp, args) {
    this.requireExactArgc(args, 2);
    return args[1].toString().toLowerCase();
  });
  this.registerSubCommand("string", "toupper", function (interp, args) {
    this.requireExactArgc(args, 2);
    return args[1].toString().toUpperCase();
  });
  this.registerSubCommand("string", "trim", function (interp, args) {
    this.requireExactArgc(args, 2);
    return args[1].toString().trim();
  });
  function sec_msec () {
    var t = new Date();
    return t.getSeconds()*1000 + t.getMilliseconds();
  }
  this.registerCommand("time", function (interp, args) {
    this.requireArgcRange(args, 2, 3);
    var body = args[1].toString();
    var n    = (args.length == 3)? args[2] : 1;
    var t0   = sec_msec();
    for(var i = 0; i < n; i++) interp.eval(body);
    return (sec_msec()-t0)*1000/n + " microseconds per iteration";
  });
  this.registerCommand("unset", function (interp, args) {
    this.requireMinArgc(args, 2);
    for (var i = 2; i < args.length; i++)
      interp.setVar(args[i], null);
  });
  this.registerCommand("uplevel",function (interp, args) {
    this.requireMinArgc(args, 3);
    var delta = args[1].toInteger();
    interp.level -= delta;
    for (var i = 2; i < args.length; i++) args[i] = args[i].toString();
    if (args.length == 3) var code = args[2];
    else                  var code = args.slice(2).join(" ");
    var res = interp.eval(code);
    interp.level += delta;
    return res;
  });
  this.registerCommand("while", function (interp, args) {
    this.requireExactArgc(args, 3);
    var cond = args[1].toString();
    var body = args[2].toString();
    var res  = "";
    interp.inLoop = true;
    interp.code = interp.OK;
    while (true) {
      test = interp.objectify(interp.eval(cond));
      if (!test.toBoolean()) break;
      res = interp.eval(body);
      if(interp.code == interp.CNT) continue;
      if(interp.code != interp.OK)  break;
    }
    interp.inLoop = false;
    if(interp.code == interp.BRK || interp.code == interp.CNT)
      interp.code=interp.OK;
    return interp.objectify(res);
  });
  // native cmdname {function(interp, args) {...}}
  this.registerCommand("native", function (interp, args) {
    this.requireExactArgc(args, 3);
    var cmd = args[1].toList();
    var func = eval(args[2].toString());
    //alert("in: "+args[2].toString()+", func: "+ func);
    if (cmd.length == 1) {
      interp.registerCommand(cmd[0].toString(), func);
      return;
    }
    base = cmd[0].toString();
    cmd.shift();
    interp.registerSubCommand(base, cmd.join(" "), eval(args[2].toString()));
    return;
  });
  this.math = function (name, a, b) {
    switch (name) {
    case "+": return a + b;
    case "-": return a - b;
    case "*": return a * b;
    case "/": return a / b;
    case "%": return a % b;
    case "<": return a < b? "1":"0";
    case ">": return a > b? "1":"0";
    case "==": return a == b? "1":"0";
    case "!=": return a != b? "1":"0";
    default: throw "Unknown operator: '"+name+"'";
    }
  }
  var ops = ["+","-","*","/","%","<",">","==","!="];
  for (i in ops)
    this.registerCommand(ops[i],function (interp, args) {
      this.requireExactArgc(args, 3);
      var name = args[0].toString();
      var a = interp.objectify(args[1]);
      var b = interp.objectify(args[2]);
      if (name == '==') 
	return new TclObject(a.toString() == b.toString()?"1":"0","BOOL");
      if (name == '!=') 
	return new TclObject(a.toString() != b.toString()?"1":"0","BOOL");

      var x = a.getNumber();
      var y = b.getNumber();
      if (a.isInteger() && b.isInteger())
        return new TclObject(interp.math(name, x, y),"INTEGER");
      if (a.isReal() && b.isReal())
        return new TclObject(interp.math(name, x, y),"REAL");
      return new TclObject(interp.math(name, args[1].toString(),
				       args[2].toString()));
    });
  this.mkList = function(x) {
    var list = new Array();
    for (var name in x) {list.push(name);}
    return list;
  }
  this.objectify = function (text) {
    if (text == null) text = "";
    else if (text instanceof TclObject) return text;
    return new TclObject(text);
  }
  this.parseString = function (text) {
    text = text.toString();
    switch (text.charAt(0)+text.substr(text.length-1)) {
    case "{}":
    case "\"\"":
      text = text.substr(1,text.length-2);
      break;
    }
    return this.objectify(text);
  }
  this.parseList = function (text) {
    text = text.toString();
    switch (text.charAt(0)+text.substr(text.length-1)) {
    case "{}":
    case "\"\"":
      text = new Array(text);
      break;
    }
    return this.objectify(text);
  }
  this.call = function(args) {
    if(_step) console.log("this.call "+args);
    var func = this.getCommand(args[0].toString());
    var r = func.call(this,args);
    switch (this.code) {
    case this.OK: case this.RET: return r;
    case this.BRK:
      if (!this.inLoop) throw "Invoked break outside of a loop";
      break;
    case this.CNT:
      if (!this.inLoop) throw "Invoked continue outside of a loop";
      break;
    default: throw "Unknown return code " + this.code;
    }
    return r;
  }
 }
 var Tcl = new Object();
 Tcl.isReal     = new RegExp("^[+\\-]?[0-9]+\\.[0-9]*([eE][+\\-]?[0-9]+)?$");
 Tcl.isDecimal  = new RegExp("^[+\\-]?[1-9][0-9]*$");
 Tcl.isHex      = new RegExp("^0x[0-9a-fA-F]+$");
 Tcl.isOctal    = new RegExp("^[+\\-]?0[0-7]*$");
 Tcl.isHexSeq   = new RegExp("[0-9a-fA-F]*");
 Tcl.isOctalSeq = new RegExp("[0-7]*");
 Tcl.isList     = new RegExp("[\\{\\} ]");
 Tcl.isNested   = new RegExp("^\\{.*\\}$");
 Tcl.getVar     = new RegExp("^[a-zA-Z0-9_]+", "g");

Tcl.Proc = function (interp, args) {
  var priv = this.privdata;
  interp.incrLevel();
  var arglist = priv[0].toList();
  var body    = priv[1];
  args.shift();
  for (var i = 0; i < arglist.length; i++) {
    var name = arglist[i].toString();
    if (i >= args.length) {
      if (name == "args") {
        interp.setVar("args", Tcl.empty);
        break;
      }
    }
    if (Tcl.isList.test(name)) {
      name = interp.parseString(name).toList();
      if (name[0] == "args") throw "'args' defaults to the empty string";
      if (i >= args.length)
        interp.setVar(name.shift(), interp.parseString(name.join(" ")));
      else interp.setVar(name[0], interp.objectify(args[i]));
    } else if (name == "args") {
      interp.setVar("args", new TclObject(args.slice(i, args.length)));
      break;
    }
    interp.setVar(name, interp.objectify(args[i]));
  }
  if (name == "args" && i+1 < arglist.length)
    throw "'args' should be the last argument";
  try {
    var r = interp.eval(body);
    interp.code = interp.OK;
    interp.decrLevel();
    return r;
  } catch (e) {
    interp.decrLevel();
    throw "Tcl.Proc exception "+e;
  }
}
/** Manage subcommands */
  Tcl.EnsembleCommand = function (interp, args) {
    var sub  = args[1].toString();
    var main = args.shift().toString()+" "+sub;
    args[0] = main;
    var ens = this.ensemble;
    if (ens == null) {
      throw "Not an ensemble command: "+main;
    } else if( ens[sub] == null) {
      throw 'unknown or ambiguous subcommand "'+sub+'": must be '+
      ens["subcommands"].valueOf();
    }
    return ens[sub].call(interp, args);
  }
 /** Get subcommands of the current ensemble command. */
 Tcl.InfoSubcommands = function(interp, args) {
  var r = new Array();
  for (var i in this.ensemble) r.push(i);
  return interp.objectify(r);
 }
 function TclObject(text) {
  this.TEXT    = 0;
  this.LIST    = 1;
  this.INTEGER = 2;
  this.REAL    = 3;
  this.BOOL    = 4;
  switch (arguments[0]) {
  case "LIST":
  case "INTEGER":
  case "REAL":
  case "BOOL":
    this.type = this[arguments[0]];
    break;
  default:
    this.type = this.TEXT;
    if (text instanceof Array) this.type = this.LIST;
    else text = text.toString();
    break;
  }
  this.content = text;
  this.stringIndex = function (i) {
    this.toString();
    return this.index(i, this.content.length);
  }
  this.listIndex = function (i) {
    this.toList();
    return this.index(i, this.content.length);
  }
  this.index = function (i, len) {
    var index = i.toString();
    if (index.substring(0,4) == "end-")
      index = len - parseInt(index.substring(4)) - 1;
    else if (index == "end") index = len-1;
    else index = parseInt(index);
    if (isNaN(index)) throw "Bad index "+i;
    if (index < 0 || index >= len) throw "Index out of bounds";
    return index;
  }
  this.isInteger = function () {return (this.type == this.INTEGER);}
  this.isReal    = function () {return (this.type == this.REAL);}
  this.getString = function (list, nested) {
    var res = new Array();
    for (var i in list) {
      res[i] = list[i].toString();
      if (Tcl.isList.test(res[i]) && !Tcl.isNested.test(res[i]))
        res[i] = "{" + res[i] + "}";
    }
    if (res.length == 1) return res[0];
    return res.join(" ");
  }
  this.toString = function () {
    if (this.type != this.TEXT) {
      if (this.type == this.LIST)
        this.content = this.getString(this.content);
      else this.content = this.content.toString();
      this.type = this.TEXT;
    }
    return this.content;
  }
  this.getList = function (text) {
    if (text.charAt(0) == "{" && text.charAt(text.length-1) == "}")
      text = text.substring(1, text.length-1);
    if (text == "") return [];
    var parser = new TclParser(text.toString());
    var content = new Array();
    for (var i = 0; ; i++) {
      parser.parseList();
      content[i] = new TclObject(parser.getText());
      if (parser.type == parser.EOL || parser.type == parser.ESC)
        break;
    }
    return content;
  }
  this.toList = function () {
    if (this.type != this.LIST) {
      if (this.type != this.TEXT) this.content[0] = this.content;
      else this.content = this.getList(this.content);
      this.type = this.LIST;
    }
    return this.content;
  }
  this.toInteger = function () {
    if (this.type == this.INTEGER) return this.content;
    this.toString();
    if (this.content.match(Tcl.isHex))
      this.content = parseInt(this.content.substring(2), 16);
    else if (this.content.match(Tcl.isOctal))
      this.content = parseInt(this.content, 8);
    else if (this.content.match(Tcl.isDecimal))
      this.content = parseInt(this.content);
    else throw "Not an integer: '"+this.content+"'";
    if (isNaN(this.content)) throw "Not an integer: '"+this.content+"'";
    this.type = this.INTEGER;
    return this.content;
  }
  this.getFloat = function (text) {
    if (!text.toString().match(Tcl.isReal))
    throw "Not a real: '"+text+"'";
    return parseFloat(text);
  }
  this.toReal = function () {
    if (this.type == this.REAL)
    return this.content;
    this.toString();
    // parseFloat doesn't control all the string, so need to check it
    this.content = this.getFloat(this.content);
    if (isNaN(this.content)) throw "Not a real: '"+this.content+"'";
    this.type = this.REAL;
    return this.content;
  }
  this.getNumber = function () {
    try {
      return this.toInteger();
    } catch (e) {return this.toReal();}
  }
  this.toBoolean = function () {
    if (this.type == this.BOOL) return this.content;
    try {
      this.content = (this.toInteger() != 0);
    } catch (e) {
      var t = this.content;
      if (t instanceof Boolean) return t;
      switch (t.toString().toLowerCase()) {
      case "yes":case "true":case "on":
        this.content = true;
        break;
      case "false":case "off":case "no":
        this.content = false;
        break;
      default:
        throw "Boolean expected, got: '"+this.content+"'";
      }
    }
    this.type = this.BOOL;
    return this.content;
  }
 }
 function TclCommand(func, privdata) {
  if (func == null) throw "No such function";
  this.func = func;
  this.privdata = privdata;
  this.ensemble = arguments[2];

  this.call = function(interp, args) {
    var r = (this.func)(interp, args);
    r = interp.objectify(r);
    return r;
  }
  this.requireExactArgc = function (args, argc) {
    if (args.length != argc) {
      throw argc + " words expected, got "+args.length;
    }
  }
  this.requireMinArgc = function (args, argc) {
    if (args.length < argc) {
      throw argc + " words expected at least, got "+args.length;
    }
  }
  this.requireArgcRange = function (args, min, max) {
    if (args.length < min || args.length > max) {
      throw min + " to "+max + " words expected, got "+args.length;
    }
  }
 }
 function TclParser(text) {
  this.OK  = 0;
  this.SEP = 0;
  this.STR = 1;
  this.EOL = 2;
  this.EOF = 3;
  this.ESC = 4;
  this.CMD = 5;
  this.VAR = 6;
  this.text  = text;
  this.start = 0;
  this.end   = 0;
  this.insidequote = false;
  this.index = 0;
  this.len = text.length;
  this.type = this.EOL;
  this.cur = this.text.charAt(0);
  this.getText = function () {
    return this.text.substring(this.start,this.end+1);
  }
  this.parseString = function () {
    var newword = (this.type==this.SEP ||
                   this.type == this.EOL || this.type == this.STR);
    if (newword && this.cur == "{") return this.parseBrace();
    else if (newword && this.cur == '"') {
      this.insidequote = true;
      this.feedchar();
    }
    this.start = this.index;
    while (true) {
      if (this.len == 0) {
        this.end = this.index-1;
        this.type = this.ESC;
        return this.OK;
      }
      if (this.cur == "\\") {
        if (this.len >= 2) this.feedSequence();
      }
      else if ("$[ \t\n\r;".indexOf(this.cur)>=0) {
        if ("$[".indexOf(this.cur)>=0 || !this.insidequote) {
          this.end = this.index-1;
          this.type = this.ESC;
          return this.OK;
        }
      }
      else if (this.cur == '"' && this.insidequote) {
        this.end  = this.index-1;
        this.type = this.ESC;
        this.feedchar();
        this.insidequote = false;
        return this.OK;
      }
      this.feedchar();
    }
    return this.OK;
  }
  this.parseList = function () {
    level = 0;
    this.start = this.index;
    while (true) {
      if (this.len == 0) {
        this.end = this.index;
        this.type = this.EOL;
        return;
      }
      switch (this.cur) {
      case "\\":
        if (this.len >= 2) this.feedSequence();
        break;
      case " ": case "\t": case "\n": case "\r":
        if (level > 0) break;
        this.end  = this.index - 1;
        this.type = this.SEP;
        this.feedchar();
        return;
      case '{': level++; break;
      case '}': level--; break;
      }
      this.feedchar();
    }
    if (level != 0) throw "Not a list";
    this.end = this.index;
    return;
  }
  this.parseSep = function () {
    this.start = this.index;
    while (" \t\r\n".indexOf(this.cur)>=0) this.feedchar();
    this.end  = this.index - 1;
    this.type = this.SEP;
    return this.OK;
  }
  this.parseEol = function () {
    this.start = this.index;
    while(" \t\n\r;".indexOf(this.cur)>=0) this.feedchar();
    this.end  = this.index - 1;
    this.type = this.EOL;
    return this.OK;
  }
  this.parseCommand = function () {
    var level = 1;
    var blevel = 0;
    this.feedcharstart();
    while (true) {
      if (this.len == 0) break;
      if (this.cur == "[" && blevel == 0)
        level++;
      else if (this.cur == "]" && blevel == 0) {
        level--;
        if (level == 0) break;
      } else if (this.cur == "\\") {
        this.feedSequence();
      } else if (this.cur == "{") {
        blevel++;
      } else if (this.cur == "}") {
        if (blevel != 0) blevel--;
      }
      this.feedchar();
    }
    this.end  = this.index-1;
    this.type = this.CMD;
    if (this.cur == "]") this.feedchar();
    return this.OK;
  }
  this.parseVar = function () {
    this.feedcharstart();
    this.end = this.index
    + this.text.substring(this.index).match(Tcl.getVar).toString().length-1;
    if (this.end == this.index-1) {
      this.end = --this.index;
      this.type = this.STR;
    } else this.type = this.VAR;
    this.setPos(this.end+1);
    return this.OK;
  }
  this.parseBrace = function () {
    var level = 1;
    this.feedcharstart();
    while (true) {
      if (this.len > 1 && this.cur == "\\") {
        this.feedSequence();
      } else if (this.len == 0 || this.cur == "}") {
        level--;
        if (level == 0 || this.len == 0) {
          this.end = this.index-1;
          if (this.len > 0) this.feedchar();
          this.type = this.STR;
          return this.OK;
        }
      } else if (this.cur == "{") level++;
      this.feedchar();
    }
    return this.OK; // unreached
  }
  this.parseComment = function () {
    while (this.cur != "\n" && this.cur != "\r") this.feedchar();
  }
  this.getToken = function () {
    while (true) {
      if (this.len == 0) {
        if (this.type == this.EOL) this.type = this.EOF;
        if (this.type != this.EOF) this.type = this.EOL;
        return this.OK;
      }
      switch (this.cur) {
        case ' ':
        case '\t':
        if (this.insidequote) return this.parseString();
        return this.parseSep();
        case '\n':
        case '\r':
        case ';':
        if (this.insidequote) return this.parseString();
        return this.parseEol();
        case '[': return this.parseCommand();
        case '$': return this.parseVar();
      }
      if (this.cur == "#" && this.type == this.EOL) {
        this.parseComment();
        continue;
      }
      return this.parseString();
    }
    return this.OK; // unreached
  }
  this.feedSequence = function () {
    if (this.cur != "\\") throw "Invalid escape sequence";
    var cur = this.steal(1);
    var specials = new Object();
    specials.a = "\a";
    specials.b = "\b";
    specials.f = "\f";
    specials.n = "\n";
    specials.r = "\r";
    specials.t = "\t";
    specials.v = "\v";
    switch (cur) {
    case 'u':
      var hex = this.steal(4);
      if (hex != Tcl.isHexSeq.exec(hex))
        throw "Invalid unicode escape sequence: "+hex;
      cur = String.fromCharCode(parseInt(hex,16));
      break;
    case 'x':
      var hex = this.steal(2);
      if (hex != Tcl.isHexSeq.exec(hex))
        throw "Invalid unicode escape sequence: "+hex;
      cur = String.fromCharCode(parseInt(hex,16));
      break;
    case "a": case "b": case "f": case "n":
    case "r": case "t": case "v":
      cur = specials[cur];
      break;
    default:
      if ("0123456789".indexOf(cur) >= 0) {
        cur = cur + this.steal(2);
        if (cur != Tcl.isOctalSeq.exec(cur))
          throw "Invalid octal escape sequence: "+cur;
        cur = String.fromCharCode(parseInt(cur, 8));
      }
      break;
    }
    this.text[index] = cur;
    this.feedchar();
  }
  this.steal = function (n) {
    var tail = this.text.substring(this.index+1);
    var word = tail.substr(0, n);
    this.text = this.text.substring(0, this.index-1) + tail.substring(n);
    return word;
  }
  this.feedcharstart = function () {
    this.feedchar();
    this.start = this.index;
  }
  this.setPos = function (index) {
    var d = index-this.index;
    this.index = index;
    this.len -= d;
    this.cur = this.text.charAt(this.index);
  }
  this.feedchar = function () {
    this.index++;
    this.len--;
    if (this.len < 0) throw "End of file reached";
    this.cur = this.text.charAt(this.index);
  }
 }
//------------------------------------- main Read-Eval-Print loop
var itp = new TclInterp();
var res;
process.argv.slice(2).forEach(function(cmd,index,array) {
    itp.eval(cmd);
  });
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('% ');
rl.prompt();
rl.on('line', function(line) {
    try {
	res = itp.eval(line.trim());
    } catch(e) {res = e;}
    if(res != null && res.toString() != "" && res.toString().length) 
      console.log(res.toString());
    rl.prompt();
}).on('close',function() {
    process.exit(0);
});