Check-in [2079bac7f3]
Overview
SHA1:2079bac7f37c0981aeff75f5a4b945f56aac0254
Date: 2013-11-13 22:44:01
User: suchenwi
Comment:first
Timelines: family | ancestors | descendants | both | trunk
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2013-11-19
20:21
[c9965b6d9d] now passing all 105 tests (user: suchenwi, tags: trunk)
2013-11-13
22:44
[2079bac7f3] first (user: suchenwi, tags: trunk)
22:18
[e519a8a5c8] initial empty check-in (user: suchenwi, tags: trunk)
Changes

Added tcl053.js version [44d4ee318f].

            1  +/* =================================================== -*- C++ -*-
            2  + * tcl.js "A Tcl implementation in Javascript"
            3  + *
            4  + * Released under the same terms as Tcl itself.
            5  + * (BSD license found at <http://www.tcl.tk/software/tcltk/license.html>)
            6  + *
            7  + * Based on Picol by Salvatore Sanfilippo (<http://antirez.com/page/picol>)
            8  + * (c) St├ęphane Arnold 2007
            9  + * Richard Suchenwirth 2007, 2013: cleanup, additions
           10  + * vim: syntax=javascript autoindent softtabwidth=4
           11  + */
           12  +_step = 0; // set to 1 for debugging
           13  +var fs = require('fs');
           14  +puts = console.log;
           15  +
           16  +function TclInterp () {
           17  +    this.patchlevel = "0.5.3";
           18  +    this.callframe  = [{}];
           19  +    this.level      = 0;
           20  +    this.levelcall  = [];
           21  +    this.commands   = {};
           22  +    this.procs      = [];
           23  +    this.script     = "";
           24  +    this.OK  = 0;
           25  +    this.RET = 1;
           26  +    this.BRK = 2;
           27  +    this.CNT = 3;
           28  +    this.getVar = function(name) {
           29  +        var nm = name.toString();
           30  +        if (nm.match("^::env[(]")) nm=nm.substr(2);
           31  +        if (nm.match("^env[(]")) {
           32  +            var key = nm.substr(4,nm.length-5);
           33  +            var val = process.env[key];
           34  +        } else if (nm.match("^::")) {
           35  +            var val = this.callframe[0][nm.substr(2)]; // global
           36  +        } else {
           37  +            var val = this.callframe[this.level][name];
           38  +        }
           39  +        if (val == null) throw 'can\'t read "'+name+'": no such variable';
           40  +        return val;
           41  +    }
           42  +    this.setVar = function(name, val) {
           43  +        var nm = name.toString();
           44  +        if (nm.match("^::")) {
           45  +            this.callframe[0][nm.substr(2)] = val;
           46  +        } else {this.callframe[this.level][name] = val;}
           47  +        return val;
           48  +    }
           49  +    this.setVar("argc",  process.argv.length-2);
           50  +    this.setVar("argv0", process.argv[1]);
           51  +    this.setVar("argv",  process.argv.slice(2));
           52  +    this.setVar("errorInfo", "");
           53  +
           54  +    this.incrLevel = function() {
           55  +        this.callframe[++this.level] = {};
           56  +        return this.level;
           57  +    }
           58  +    this.decrLevel = function() {
           59  +        this.callframe[this.level] = null;
           60  +        this.level--;
           61  +        if (this.level<0) throw "Exit application";
           62  +        this.result = null;
           63  +    }
           64  +    this.getCommand = function(name) {
           65  +        try {
           66  +            return this.commands[name];
           67  +        } catch (e) {throw "No such command '"+name+"'";}
           68  +    }
           69  +    this.registerCommand = function(name, func, privdata) {
           70  +        if (func == null) throw "No such function: "+name;
           71  +        this.commands[name] = new TclCommand(func, privdata);
           72  +    }
           73  +    this.registerSubCommand = function(name, subcmd, func, privdata) {
           74  +      if (func == null) throw "No such subcommand: "+ name +" " + subcmd;
           75  +      var path = name.split(" ");
           76  +      var ens;
           77  +      name = path.shift();
           78  +      var cmd = this.commands[name];
           79  +      if (cmd == null) {
           80  +	ens = {};
           81  +	// ens["subcommands"]  = new TclCommand(Tcl.InfoSubcommands, null);
           82  +	this.commands[name] = new TclCommand(Tcl.EnsembleCommand, null, ens);
           83  +      }
           84  +      ens = this.commands[name].ensemble;
           85  +      if (ens == null) throw "Not an ensemble command: '"+name+"'";
           86  +      // walks deeply into the subcommands tree
           87  +      while (path.length > 0) {
           88  +	name = path.shift();
           89  +	cmd  = ens[name];
           90  +	if (cmd == null) {
           91  +	  cmd = new TclCommand(Tcl.EnsembleCommand, null, {});
           92  +	  ens[name] = cmd;
           93  +	  ens = cmd.ensemble;
           94  +	  // ens["subcommands"] = new TclCommand(Tcl.InfoSubcommands, null);
           95  +	}
           96  +      }
           97  +      ens[subcmd] = new TclCommand(func, privdata);
           98  +    }
           99  +    this.eval = function (code) {
          100  +      try {
          101  +	return this.eval2(code);
          102  +      } catch (e) {
          103  +	var msg = code.substr(0,128);
          104  +	if(msg.length >= 125) msg += "...";
          105  +	puts(e);
          106  +	var msg = e+'\n        while executing\n"'+msg+'"';
          107  +	for(var i = this.level; i > 0; i--)
          108  +	  msg += '\n        invoked from within\n"'+this.levelcall[i]+'"'
          109  +	this.setVar("::errorInfo", msg);
          110  +      }
          111  +    }
          112  +    this.eval2 = function(code) {
          113  +      this.code  = this.OK;
          114  +      var parser = new TclParser(code);
          115  +      var args   = [];
          116  +      var first  = true;
          117  +      var text, prevtype, result;
          118  +      result     = "";
          119  +      while (true) {
          120  +	prevtype = parser.type;
          121  +	try {
          122  +	  parser.getToken();
          123  +	} catch (e) {break;}
          124  +	if (parser.type == (parser.EOF)) break;
          125  +	text = parser.getText();
          126  +	if (parser.type == (parser.VAR)) {
          127  +	  text = this.getVar(text);
          128  +	} else if (parser.type == (parser.CMD)) {
          129  +	  try {
          130  +	    text = this.eval2(text);
          131  +	  } catch (e) {throw (e + "\nwhile parsing \"" + text + "\"");}
          132  +	} else if (parser.type == (parser.ESC)) {
          133  +	  // escape handling missing!
          134  +	} else if (parser.type == (parser.SEP)) {
          135  +	  prevtype = parser.type;
          136  +	  continue;
          137  +	}
          138  +	text = this.objectify(text);
          139  +	if (parser.type ==parser.EOL || parser.type == parser.EOF) {
          140  +	  prevtype = parser.type;
          141  +	  if (args.length > 0) {
          142  +	    try {
          143  +	      result = this.call(args);
          144  +	    } catch(e) {
          145  +	      if(e.toString().match("Cannot call method")) 
          146  +		throw 'invalid command name "'+args[0].toString()+'"';
          147  +	      throw e;
          148  +	    }
          149  +	    if (this.code != this.OK) return this.objectify(result);
          150  +	  }
          151  +	  args = [];
          152  +	  continue;
          153  +	}
          154  +	if (prevtype == parser.SEP || prevtype == parser.EOL) {
          155  +	  args.push(text);
          156  +	} else {
          157  +	  args[args.length-1] = args[args.length-1].toString() + text.toString();
          158  +	}
          159  +      }
          160  +      if (args.length > 0) result = this.call(args);
          161  +      return this.objectify(result);
          162  +    }
          163  +    //---------------------------------- Commands in alphabetical order
          164  +    this.registerCommand("append", function (interp, args) {
          165  +        this.arity(args, 2, Infinity);
          166  +        var vname = args[1].toString();
          167  +	try {var str = interp.getVar(vname);} catch(e) {var str = "";}
          168  +	for (var i = 2; i < args.length; i++) str += args[i].toString();
          169  +        interp.setVar(vname, str);
          170  +        return str;
          171  +      });
          172  +    this.registerCommand("break", function (interp, args) {
          173  +        interp.code = interp.BRK;
          174  +        return;
          175  +      });
          176  +    this.registerCommand("cd", function (interp, args) {
          177  +	this.arity(args, 1, 2);
          178  +	var dir = process.env.HOME;
          179  +	if (args.length == 2) dir = args[1].toString();
          180  +	process.chdir(dir);
          181  +	return;
          182  +      });
          183  +    this.registerCommand("continue", function (interp, args) {
          184  +        interp.code = interp.CNT;
          185  +        return;
          186  +      });
          187  +    this.registerSubCommand("clock", "format", function (interp, args) {
          188  +        var now = new Date();
          189  +        now.setTime(args[1]);
          190  +        return now.toString();
          191  +      });
          192  +    this.registerSubCommand("clock", "milliseconds", function (interp, args) {
          193  +	var t = new Date();
          194  +	return t.valueOf();
          195  +      });
          196  +    this.registerSubCommand("clock", "scan", function (interp, args) {
          197  +        return Date.parse(args[1]);
          198  +      });
          199  +    this.registerSubCommand("clock", "seconds", function (interp, args) {
          200  +	return Math.floor((new Date()).valueOf()/1000);
          201  +      });
          202  +    
          203  +    this.registerSubCommand("dict", "create", function (interp, args) {
          204  +	if(args.length % 2 == 0) 
          205  +	  throw 'wrong # args: should be "dict create ?key value ...?"';
          206  +	return new TclObject(args.slice(1));
          207  +      });
          208  +    this.registerSubCommand("dict", "get", function (interp, args) {
          209  +	if(args.length < 2) 
          210  +	  throw 'wrong # args: should be "dict get ?key ...?"';
          211  +	var dict = args[1].toList();
          212  +	var key  = args[2].toString();
          213  +	for (var i=0;i < dict.length;i+=2) {
          214  +	  if(dict[i].toString() == key) return dict[i+1];
          215  +	}
          216  +	throw 'key "'+key+'" not known in dictionary';
          217  +      });
          218  +    this.registerSubCommand("dict", "set", function (interp, args) {
          219  +	this.arity(args, 4);
          220  +	var name  = args[1];
          221  +	var dict  = interp.getVar(name);
          222  +	var key   = args[2].toString();
          223  +	var val   = args[3].toString();
          224  +	var found = false;
          225  +	var list  = dict.toList();
          226  +	for (var i=0;i < list.length;i+=2) {
          227  +	  if(list[i].toString() == key) {
          228  +	    list[i+1] = val;
          229  +	    found = true;
          230  +	    break;
          231  +	  }
          232  +	}
          233  +	if (!found) {
          234  +	  list.push(interp.objectify(key)); 
          235  +	  list.push(interp.objectify(val));
          236  +	} 
          237  +	interp.setVar(name, dict);
          238  +	return dict;
          239  +      });
          240  +    /*
          241  +      if(typeof(jQuery) != 'undefined') {
          242  +      this.registerCommand("dom", function (interp, args) {
          243  +      var selector = args[1].toString();
          244  +      var fn = args[2].toString();
          245  +      args = args.slice(3);
          246  +      for (var i in args) args[i] = args[i].toString();
          247  +      var q = $(selector);
          248  +      q[fn].apply(q,args);
          249  +      return "dom    " + selector;
          250  +      });
          251  +      }*/
          252  +    this.registerCommand("eval",function (interp, args) {
          253  +        this.arity(args, 2,Infinity);
          254  +        for (var i = 1; i < args.length; i++) args[i] = args[i].toString();
          255  +        if (args.length == 2) var code = args[1];
          256  +        else                  var code = args.slice(1).join(" ");
          257  +        return interp.eval(code);
          258  +      });
          259  +    /*
          260  +      this.registerCommand("exec",function (interp, args) {
          261  +      this.arity(args, 2, Infinity);
          262  +      var exec = require('child_process').exec,
          263  +      child;
          264  +      puts("exec "+args.slice(1).join(" "));
          265  +      child = exec(args.slice(1).join(" "),
          266  +      function (error, stdout, stderr) {
          267  +      var res = stdout.toString();
          268  +      //console.log('stdout: ' + stdout.toString());
          269  +      if (error !== null) {
          270  +      throw('exec error: ' + error);
          271  +      } 
          272  +      return res;
          273  +      });
          274  +      return this.execres;
          275  +      });
          276  +    */
          277  +    this.registerCommand("exit",function (interp, args) {
          278  +	this.arity(args, 1,2);
          279  +	var rc = 0;
          280  +	if (args.length == 2) rc = args[1];
          281  +	process.exit(rc);
          282  +      });
          283  +    acos = Math.acos;
          284  +    exp  = Math.exp;
          285  +    sqrt = Math.sqrt; // "publish" other Math.* functions as needed
          286  +
          287  +    this.registerCommand("expr", function (interp, args) {
          288  +	var expression = args.slice(1).join(" ");
          289  +	return interp.expr(interp, expression);
          290  +      });
          291  +    this.expr = function ($interp, $expression) { // also used in for, if, while
          292  +      try {
          293  +	var $mx = $expression.match(/(\[.*\])/g);
          294  +	for ($i in $mx)
          295  +	  puts("have to deal with "+$mx[i].toString());
          296  +      } catch(e) {puts(i+". exception: "+e);}
          297  +      $mx = $expression.match(/(\$[A-Za-z0-9_:]+)/g);
          298  +      for ($i in $mx)
          299  +	eval("var "+$mx[$i]+" = "+$interp.getVar($mx[$i].slice(1)));
          300  +      var res = eval($expression);
          301  +      if(res == false) res = 0; else if(res == true) res = 1;
          302  +      return res;
          303  +    };
          304  +    this.registerSubCommand("file", "dirname", function (interp, args) {
          305  +        this.arity(args, 2);
          306  +	var path = args[1].toString().split("/");
          307  +	path.pop();
          308  +	return path.join("/");
          309  +      });
          310  +    this.registerCommand("for", function (interp, args) {
          311  +        this.arity(args, 5);
          312  +        interp.eval(args[1].toString());
          313  +        if(interp.code != interp.OK) return;
          314  +        var cond = args[2].toString();
          315  +        var step = args[3].toString();
          316  +        var body = args[4].toString();
          317  +        interp.inLoop = true;
          318  +        interp.code = interp.OK;
          319  +        while (true) {
          320  +            test = interp.objectify(interp.expr(interp, cond));
          321  +            if (!test.toBoolean()) break;
          322  +            interp.eval(body);
          323  +            var ic = interp.code; // tested after step command
          324  +            interp.eval(step);
          325  +            if(ic == interp.BRK) break;
          326  +            if(ic == interp.CNT) continue;
          327  +        }
          328  +        interp.inLoop = false;
          329  +        if(interp.code == interp.BRK || interp.code == interp.CNT)
          330  +            interp.code = interp.OK;
          331  +        return "";
          332  +    });
          333  +    this.registerCommand("foreach", function (interp, args) {
          334  +        this.arity(args, 4);
          335  +        var list = args[2].toList();
          336  +        var body = args[3].toString();
          337  +        var res    = "";
          338  +        interp.inLoop = true;
          339  +        interp.code = interp.OK;
          340  +        for(i in list) {
          341  +             interp.setVar(args[1],interp.objectify(list[i]));
          342  +             interp.eval(body);
          343  +             if(interp.code == interp.BRK) break;
          344  +             if(interp.code == interp.CNT) continue;
          345  +        }
          346  +        interp.inLoop = false;
          347  +        if(interp.code == interp.BRK || interp.code == interp.CNT)
          348  +            interp.code=interp.OK;
          349  +        return "";
          350  +    });
          351  +    /*    this.registerCommand("gets", function (interp, args) {
          352  +        this.arity(args, 2, 3);
          353  +        var reply; // = prompt(args[1],"");
          354  +        process.stdin.resume();
          355  +        process.stdin.on('data', function(str) {
          356  +        reply = str;
          357  +            });
          358  +        if(args[2] != null) {
          359  +            interp.setVar(args[2],interp.objectify(reply));
          360  +            return reply.length;
          361  +        } else return reply;
          362  +        }); */
          363  +    this.registerCommand("if", function (interp, args) {
          364  +        this.arity(args, 3, Infinity);
          365  +        var cond = args[1].toString();
          366  +        var test = interp.objectify(interp.expr(interp, cond));
          367  +        if (test.toBoolean()) return interp.eval(args[2].toString());
          368  +        if (args.length == 3) return;
          369  +        for (var i = 3; i < args.length; ) {
          370  +            switch (args[i].toString()) {
          371  +            case "else":
          372  +                this.arity(args, i + 2);
          373  +                return interp.eval(args[i+1].toString());
          374  +            case "elseif":
          375  +                this.arity(args, i + 3);
          376  +                test = interp.objectify(interp.expr(interp, args[i+1].toString()));
          377  +                if (test.toBoolean())
          378  +                    return interp.eval(args[i+2].toString());
          379  +                i += 3;
          380  +                break;
          381  +            default:
          382  +                throw "Expected 'else' or 'elseif', got "+ args[i];
          383  +            }
          384  +        }
          385  +    });
          386  +    this.registerCommand("incr", function (interp, args) {
          387  +        this.arity(args, 2, 3);
          388  +        var name = args[1];
          389  +        if (args.length == 2) var incr = 1;
          390  +        else var incr = interp.objectify(args[2]).toInteger();
          391  +        incr += interp.getVar(name).toInteger();
          392  +        return interp.setVar(name, new TclObject(incr, "INTEGER"));
          393  +    });
          394  +    this.registerSubCommand("info", "args", function (interp, args) {
          395  +        this.arity(args, 2);
          396  +        var name = args[1].toString();
          397  +        if (!interp.procs[name]) throw '"'+name+'" isn\'t a procedure';
          398  +        return interp.getCommand(name).privdata[0];
          399  +    });
          400  +    this.registerSubCommand("info", "body", function (interp, args) {
          401  +        this.arity(args, 2);
          402  +        var name = args[1].toString();
          403  +        if (!interp.procs[name]) throw '"'+name+'" isn\'t a procedure';
          404  +        return interp.getCommand(name).privdata[1];
          405  +    });
          406  +    this.registerSubCommand("info", "commands", function (interp, args) {
          407  +        return interp.mkList(interp.commands);
          408  +    });
          409  +    this.registerSubCommand("info", "exists", function (interp, args) {
          410  +            var name = args[1];
          411  +            try {interp.getVar(name); return 1;} catch(e) {return 0;}
          412  +    });
          413  +    this.registerSubCommand("info", "globals", function (interp, args) {
          414  +        return interp.mkList(interp.callframe[0]);
          415  +    });
          416  +    this.registerSubCommand("info", "isensemble", function (interp, args) {
          417  +        this.arity(args, 2);
          418  +        var name = args[1].toString();
          419  +        return (interp.getCommand(name).ensemble != null);
          420  +    });
          421  +    this.registerSubCommand("info", "level", function (interp, args) {
          422  +	if(args.length == 1)
          423  +	  return interp.level;
          424  +	var delta = args[1];
          425  +	return interp.levelcall[interp.level - delta];
          426  +    });
          427  +    this.registerSubCommand("info", "na", function (interp, args) {
          428  +            return process.execPath;
          429  +    });
          430  +    this.registerSubCommand("info", "patchlevel", function (interp, args) {
          431  +            return interp.patchlevel.toString();
          432  +    });
          433  +    this.registerSubCommand("info", "procs", function (interp, args) {
          434  +        return interp.mkList(interp.procs);
          435  +    });
          436  +    this.registerSubCommand("info", "script", function (interp, args) {
          437  +        return interp.script;
          438  +    });
          439  +    this.registerSubCommand("info", "vars", function (interp, args) {
          440  +        return interp.mkList(interp.callframe[interp.level]);
          441  +    });
          442  +    this.registerCommand("join", function (interp, args) {
          443  +	this.arity(args, 2, 3);
          444  +	var lst = args[1].toList();
          445  +	var sep = " ";
          446  +	if(args.length == 3) sep = args[2].toString();
          447  +	var res = [];
          448  +	var re  = /^{.*}$/;
          449  +	for (i in lst) {
          450  +	  var word = lst[i].toString();
          451  +	  if (re.test(word)) word = word.substring(1,word.length-1);
          452  +	  res.push(word);
          453  +	}
          454  +	return res.join(sep);
          455  +      });
          456  +    this.registerCommand("jseval", function (interp, args) {
          457  +        return eval(args[1].toString());
          458  +      });
          459  +    this.registerCommand("lappend", function (interp, args) {
          460  +        this.arity(args, 2, Infinity);
          461  +        var vname = args[1].toString();
          462  +	try {
          463  +	  var list = interp.getVar(vname);
          464  +	} catch(e) {var list = new TclObject([]);}
          465  +        list.toList();
          466  +        for (var i = 2; i < args.length; i++) {
          467  +	  list.content.push(interp.objectify(args[i]));
          468  +        }
          469  +        interp.setVar(vname, list);
          470  +        return list;
          471  +      });
          472  +    this.registerCommand("lindex", function (interp, args) {
          473  +        this.arity(args, 3, Infinity);
          474  +        var list = interp.objectify(args[1]);
          475  +        for (var i = 2; i < args.length; i++) {
          476  +	  try {
          477  +	    var index = list.listIndex(args[i]);
          478  +	  } catch (e) {
          479  +	    if (e == "Index out of bounds") return "";
          480  +	    throw e;
          481  +	  }
          482  +	  list = list.content[index];
          483  +        }
          484  +        return interp.objectify(list);
          485  +      });
          486  +    this.registerCommand("list", function (interp, args) {
          487  +	args.shift();
          488  +	return new TclObject(args);
          489  +      });
          490  +    this.registerCommand("llength", function (interp, args) {
          491  +        this.arity(args, 2);
          492  +        return args[1].toList().length;
          493  +      });
          494  +    this.registerCommand("lrange", function (interp, args) {
          495  +        this.arity(args, 4);
          496  +        var list    = interp.objectify(args[1]);
          497  +        var start = list.listIndex(args[2]);
          498  +        var end     = list.listIndex(args[3])+1;
          499  +        try {
          500  +	  return list.content.slice(start, end);
          501  +        } catch (e) {return [];}
          502  +      });
          503  +    this.registerCommand("lset", function (interp, args) {
          504  +        this.arity(args, 4, Infinity);
          505  +        var list = interp.getVar(args[1].toString());
          506  +        var elt = list;
          507  +        for (var i = 2; i < args.length-2; i++) {
          508  +	  elt.toList();
          509  +	  elt = interp.objectify(elt.content[elt.listIndex(args[i])]);
          510  +        }
          511  +        elt.toList();
          512  +        i = args.length - 2;
          513  +        elt.content[elt.listIndex(args[i])] = interp.objectify(args[i+1]);
          514  +        return list;
          515  +      });
          516  +    this.registerCommand("lsearch", function (interp, args) {
          517  +        this.arity(args, 3);
          518  +        var lst = args[1].toList();
          519  +        for(i in lst) if(lst[i] == args[2].toString()) return i;
          520  +        return -1;
          521  +      });
          522  +    this.registerCommand("lsort", function (interp, args) {
          523  +        this.arity(args, 2);
          524  +        return args[1].toList().sort();
          525  +      });
          526  +    this.registerCommand("pid", function (interp, args) {
          527  +	return process.pid;
          528  +      });
          529  +    this.registerCommand("puts", function (interp, args) {
          530  +	this.arity(args, 2);
          531  +	puts(args[1].toString());
          532  +      });
          533  +    this.registerCommand("pwd", function (interp, args) {
          534  +	return process.cwd();
          535  +      });
          536  +    this.registerCommand("proc", function (interp, args) {
          537  +        this.arity(args, 4);
          538  +        var name = args[1].toString();
          539  +        var argl = interp.parseList(args[2]);
          540  +        var body = args[3].toString();
          541  +        var priv = [argl, body];
          542  +        interp.commands[name] = new TclCommand(Tcl.Proc, priv);
          543  +        interp.procs[name]    = true;
          544  +      });
          545  +    this.registerCommand("regexp", function (interp, args) {
          546  +        this.arity(args, 3);
          547  +        var re    = new RegExp(args[1].toString());
          548  +        var str = args[2].toString();
          549  +        return (str.search(re) > -1? "1":"0");
          550  +      });
          551  +    this.registerCommand("regsub", function (interp, args) {
          552  +        this.arity(args, 4);
          553  +        var re    = new RegExp(args[1].toString());
          554  +        var str = args[2].toString();
          555  +        var trg = args[3].toString();
          556  +        return (str.replace(re,trg));
          557  +      });
          558  +    this.registerCommand("rename", function (interp, args) {
          559  +        this.arity(args, 3);
          560  +	var name    = args[1];
          561  +	var newname = args[2];
          562  +	interp.commands[newname] = interp.commands[name];
          563  +        if (interp.procs[name]) {
          564  +	  interp.procs[name] = null;
          565  +	  interp.procs[newname] = true;
          566  +        }
          567  +        interp.commands[name] = null;
          568  +      });
          569  +    this.registerCommand("return", function (interp, args) {
          570  +        this.arity(args, 1, 2);
          571  +        var r = args[1];
          572  +        interp.code = interp.RET;
          573  +        return r;
          574  +    });
          575  +    this.registerCommand("set", function (interp, args) {
          576  +        this.arity(args, 2, 3);
          577  +        var name = args[1];
          578  +        if (args.length == 3) interp.setVar(name, args[2]);
          579  +        return interp.getVar(name);
          580  +    });
          581  +    this.registerCommand("source", function (interp, args) {
          582  +        this.arity(args, 2);
          583  +        interp.script = args[1].toString();
          584  +        try {
          585  +	  var data = fs.readFileSync(interp.script).toString();
          586  +        } catch(e) {
          587  +	  throw 'couldn\' read file "'+interp.script+'": no such file or directory';}
          588  +	var res    = interp.eval(data);
          589  +	interp.script = "";
          590  +	return res;
          591  +      });
          592  +    this.registerCommand("split", function (interp, args) {
          593  +        this.arity(args, 2, 3);
          594  +        var str = args[1].toString();
          595  +        var sep = " ";
          596  +        if (args.length == 3) sep = args[2].toString();
          597  +	var res = [], e;
          598  +        var tmp = str.split(sep);
          599  +	for(i in tmp) {
          600  +	  e = tmp[i];
          601  +	  if(e == "") e = "{}";
          602  +	  res.push(e);
          603  +	}
          604  +	return res.join(" ");
          605  +      });
          606  +    this.registerSubCommand("string", "equal", function (interp, args) {
          607  +        this.arity(args, 3);
          608  +        return (args[1].toString() == args[2].toString())? "1": "0";
          609  +      });
          610  +    this.registerSubCommand("string", "index", function (interp, args) {
          611  +        this.arity(args, 3);
          612  +        var s = args[1].toString();
          613  +        try {
          614  +	  return s.charAt(args[1].stringIndex(args[2]));
          615  +        } catch (e) {return "";}
          616  +      });
          617  +    this.registerSubCommand("string", "length", function (interp, args) {
          618  +        this.arity(args, 2);
          619  +        return args[1].toString().length;
          620  +      });
          621  +    this.registerSubCommand("string", "range", function (interp, args) {
          622  +        this.arity(args, 4);
          623  +        var s = args[1];
          624  +        try {
          625  +            var b = s.stringIndex(args[2]);
          626  +            var e = s.stringIndex(args[3]);
          627  +            if (b > e) return "";
          628  +            return s.toString().substring(b, e + 1);
          629  +        } catch (e) {return "";}
          630  +    });
          631  +    this.registerSubCommand("string", "tolower", function (interp, args) {
          632  +        this.arity(args, 2);
          633  +        return args[1].toString().toLowerCase();
          634  +    });
          635  +    this.registerSubCommand("string", "toupper", function (interp, args) {
          636  +        this.arity(args, 2);
          637  +        return args[1].toString().toUpperCase();
          638  +    });
          639  +    this.registerSubCommand("string", "trim", function (interp, args) {
          640  +        this.arity(args, 2);
          641  +        return args[1].toString().trim();
          642  +    });
          643  +    function sec_msec () {
          644  +        var t = new Date();
          645  +        return t.getSeconds()*1000 + t.getMilliseconds();
          646  +    }
          647  +    this.registerCommand("time", function (interp, args) {
          648  +        this.arity(args, 2, 3);
          649  +        var body = args[1].toString();
          650  +        var n    = (args.length == 3)? args[2] : 1;
          651  +        var t0   = sec_msec();
          652  +        for(var i = 0; i < n; i++) interp.eval(body);
          653  +        return (sec_msec()-t0)*1000/n + " microseconds per iteration";
          654  +    });
          655  +    this.registerCommand("unset", function (interp, args) {
          656  +        this.arity(args, 2, Infinity);
          657  +        for (var i = 2; i < args.length; i++)
          658  +            interp.setVar(args[i], null);
          659  +    });
          660  +    this.registerCommand("uplevel",function (interp, args) {
          661  +        this.arity(args, 3, Infinity);
          662  +        var delta = args[1].toInteger();
          663  +        interp.level -= delta;
          664  +	if(interp.level < 0) {
          665  +	  interp.level += delta;
          666  +	  throw 'bad level "'+delta+'"';
          667  +	}
          668  +        for (var i = 2; i < args.length; i++) args[i] = args[i].toString();
          669  +        if (args.length == 3) {
          670  +	  var code = args[2];
          671  +        } else var code = args.slice(2).join(" ");
          672  +        var res = interp.eval(code);
          673  +        interp.level += delta;
          674  +        return res;
          675  +      });
          676  +    this.registerCommand("while", function (interp, args) {
          677  +        this.arity(args, 3);
          678  +        var cond = args[1].toString();
          679  +        var body = args[2].toString();
          680  +        var res  = "";
          681  +        interp.inLoop = true;
          682  +        interp.code = interp.OK;
          683  +        while (true) {
          684  +	  test = interp.objectify(interp.expr(interp, cond));
          685  +	  if (!test.toBoolean()) break;
          686  +	  res = interp.eval(body);
          687  +	  if(interp.code == interp.CNT) continue;
          688  +	  if(interp.code != interp.OK)    break;
          689  +        }
          690  +        interp.inLoop = false;
          691  +        if(interp.code == interp.BRK || interp.code == interp.CNT)
          692  +	  interp.code=interp.OK;
          693  +        return interp.objectify(res);
          694  +      });
          695  +    // native cmdname {function(interp, args) {...}}
          696  +    this.registerCommand("native", function (interp, args) {
          697  +        this.arity(args, 3);
          698  +        var cmd = args[1].toList();
          699  +        var func = eval(args[2].toString());
          700  +        //alert("in: "+args[2].toString()+", func: "+ func);
          701  +        if (cmd.length == 1) {
          702  +	  interp.registerCommand(cmd[0].toString(), func);
          703  +	  return;
          704  +        }
          705  +        base = cmd[0].toString();
          706  +        cmd.shift();
          707  +        interp.registerSubCommand(base, cmd.join(" "), eval(args[2].toString()));
          708  +        return;
          709  +      });
          710  +    this.math = function (name, a, b) {
          711  +      switch (name) {
          712  +      case "+":  return a + b;
          713  +      case "-":  return a - b;
          714  +      case "*":  return a * b;
          715  +      case "/":  return a / b;
          716  +      case "%":  return a % b;
          717  +      case "<":  return a < b? "1":"0";
          718  +      case ">":  return a > b? "1":"0";
          719  +      case "==": return a == b? "1":"0";
          720  +      case "!=": return a != b? "1":"0";
          721  +      default:   throw "Unknown operator: '"+name+"'";
          722  +      }
          723  +    }
          724  +    var ops = ["+","-","*","/","%","<",">","==","!="];
          725  +    for (i in ops)
          726  +      this.registerCommand(ops[i],function (interp, args) {
          727  +	  this.arity(args, 3);
          728  +	  var name = args[0].toString();
          729  +	  var a    = interp.objectify(args[1]);
          730  +	  var b    = interp.objectify(args[2]);
          731  +	  if (name == '==') 
          732  +	    return new TclObject(a.toString() == b.toString()?"1":"0","BOOL");
          733  +	  if (name == '!=') 
          734  +	    return new TclObject(a.toString() != b.toString()?"1":"0","BOOL");
          735  +	  
          736  +	  var x = a.getNumber();
          737  +	  var y = b.getNumber();
          738  +	  if (a.isInteger() && b.isInteger())
          739  +	    return new TclObject(interp.math(name, x, y),"INTEGER");
          740  +	  if (a.isReal() && b.isReal())
          741  +	    return new TclObject(interp.math(name, x, y),"REAL");
          742  +	  return new TclObject(interp.math(name, args[1].toString(), 
          743  +					   args[2].toString()));
          744  +        });
          745  +    this.mkList = function(x) {
          746  +      var list = [];
          747  +      for (var name in x) {list.push(name);}
          748  +      return list;
          749  +    }
          750  +    this.objectify = function (text) {
          751  +      if (text == null) text = "";
          752  +      else if (text instanceof TclObject) return text;
          753  +      return new TclObject(text);
          754  +    }
          755  +    this.parseString = function (text) {
          756  +      text = text.toString();
          757  +      switch (text.charAt(0)+text.substr(text.length-1)) {
          758  +      case "{}":
          759  +      case '""':
          760  +	text = text.substr(1,text.length-2);
          761  +      break;
          762  +      }
          763  +      return this.objectify(text);
          764  +    }
          765  +    this.parseList = function (text) {
          766  +      text = text.toString();
          767  +      switch (text.charAt(0)+text.substr(text.length-1)) {
          768  +      case "{}":
          769  +      case '""':
          770  +	text = [text];
          771  +      break;
          772  +      }
          773  +      return this.objectify(text);
          774  +    }
          775  +    this.call = function(args) {
          776  +      if(_step) puts("this.call "+args);
          777  +      var func = this.getCommand(args[0].toString());
          778  +      var res  = func.call(this,args);
          779  +      switch (this.code) {
          780  +      case this.OK: case this.RET: return res;
          781  +      case this.BRK:
          782  +	if (!this.inLoop) throw "Invoked break outside of a loop";
          783  +	break;
          784  +      case this.CNT:
          785  +	if (!this.inLoop) throw "Invoked continue outside of a loop";
          786  +	break;
          787  +      default: throw "Unknown return code " + this.code;
          788  +      }
          789  +      return res;
          790  +    }
          791  +}
          792  +
          793  +var Tcl = {};
          794  +Tcl.isReal     = new RegExp("^[+\\-]?[0-9]+\\.[0-9]*([eE][+\\-]?[0-9]+)?$");
          795  +Tcl.isDecimal  = new RegExp("^[+\\-]?[1-9][0-9]*$");
          796  +Tcl.isHex      = new RegExp("^0x[0-9a-fA-F]+$");
          797  +Tcl.isOctal    = new RegExp("^[+\\-]?0[0-7]*$");
          798  +Tcl.isHexSeq   = new RegExp("[0-9a-fA-F]*");
          799  +Tcl.isOctalSeq = new RegExp("[0-7]*");
          800  +Tcl.isList     = new RegExp("[\\{\\} ]");
          801  +Tcl.isNested   = new RegExp("^\\{.*\\}$");
          802  +Tcl.getVar     = new RegExp("^[a-zA-Z0-9_]+", "g");
          803  +
          804  +Tcl.Proc = function (interp, args) {
          805  +   var priv = this.privdata;
          806  +   interp.incrLevel();
          807  +   var arglist = priv[0].toList();
          808  +   var body    = priv[1];
          809  +   var call    = []; 
          810  +   for(var i in args) {
          811  +     var elt = args[i].toString();
          812  +     if(elt.match(/ /)) elt = "{"+elt+"}";
          813  +     call.push(elt);
          814  +   }
          815  +   interp.levelcall[interp.level] = call.join(" ");
          816  +   args.shift();
          817  +   for (var i = 0; i < arglist.length; i++) {
          818  +       var name = arglist[i].toString();
          819  +       if (i >= args.length) {
          820  +           if (name == "args") {
          821  +               interp.setVar("args", Tcl.empty);
          822  +               break;
          823  +           }
          824  +       }
          825  +       if (Tcl.isList.test(name)) {
          826  +           name = interp.parseString(name).toList();
          827  +           if (name[0] == "args") throw "'args' defaults to the empty string";
          828  +           if (i >= args.length)
          829  +               interp.setVar(name.shift(), interp.parseString(name.join(" ")));
          830  +           else interp.setVar(name[0], interp.objectify(args[i]));
          831  +       } else if (name == "args") {
          832  +           interp.setVar("args", new TclObject(args.slice(i, args.length)));
          833  +           break;
          834  +       }
          835  +       interp.setVar(name, interp.objectify(args[i]));
          836  +   }
          837  +   if (name == "args" && i+1 < arglist.length)
          838  +       throw "'args' should be the last argument";
          839  +   try {
          840  +       var res = interp.eval(body);
          841  +       interp.code = interp.OK;
          842  +       interp.decrLevel();
          843  +       return res;
          844  +   } catch (e) {
          845  +       interp.decrLevel();
          846  +       throw "Tcl.Proc exception "+e;
          847  +   }
          848  +}
          849  +/** Manage subcommands */
          850  +Tcl.EnsembleCommand = function (interp, args) {
          851  +   var sub    = args[1].toString();
          852  +   var main = args.shift().toString()+" "+sub;
          853  +   args[0] = main;
          854  +   var ens = this.ensemble;
          855  +   if (ens == null) {
          856  +        throw "Not an ensemble command: "+main;
          857  +    } else if( ens[sub] == null) {
          858  +     var r = [];
          859  +     for (var i in ens) r.push(i);
          860  +     r[r.length-1] = "or "+r[r.length-1];
          861  +     throw 'unknown or ambiguous subcommand "'+sub+'": must be '+
          862  +     r.join(", ");
          863  +    }
          864  +    return ens[sub].call(interp, args);
          865  +}
          866  +/** Get subcommands of the current ensemble command. */
          867  +/*
          868  +Tcl.InfoSubcommands = function(interp, args) {
          869  +    var r = [];
          870  +    for (var i in this.ensemble) r.push(i);
          871  +    return interp.objectify(r);
          872  +}
          873  +*/
          874  +function TclObject(text) {
          875  +    this.TEXT    = 0;
          876  +    this.LIST    = 1;
          877  +    this.INTEGER = 2;
          878  +    this.REAL    = 3;
          879  +    this.BOOL    = 4;
          880  +    switch (arguments[0]) {
          881  +    case "LIST":
          882  +    case "INTEGER":
          883  +    case "REAL":
          884  +    case "BOOL":
          885  +        this.type = this[arguments[0]];
          886  +        break;
          887  +    default:
          888  +        this.type = this.TEXT;
          889  +        if (text instanceof Array) this.type = this.LIST;
          890  +        else text = text.toString();
          891  +        break;
          892  +    }
          893  +    this.content = text;
          894  +    this.stringIndex = function (i) {
          895  +        this.toString();
          896  +        return this.index(i, this.content.length);
          897  +    }
          898  +    this.listIndex = function (i) {
          899  +        this.toList();
          900  +        return this.index(i, this.content.length);
          901  +    }
          902  +    this.index = function (i, len) {
          903  +        var index = i.toString();
          904  +        if (index.substring(0,4) == "end-")
          905  +            index = len - parseInt(index.substring(4)) - 1;
          906  +        else if (index == "end") index = len-1;
          907  +        else index = parseInt(index);
          908  +        if (isNaN(index)) throw "Bad index "+i;
          909  +        if (index < 0 || index >= len) throw "Index out of bounds";
          910  +        return index;
          911  +    }
          912  +    this.isInteger = function () {return (this.type == this.INTEGER);}
          913  +    this.isReal    = function () {return (this.type == this.REAL);}
          914  +    this.getString = function (list, nested) {
          915  +        var res = [];
          916  +        for (var i in list) {
          917  +            res[i] = list[i].toString();
          918  +            if (Tcl.isList.test(res[i]) && !Tcl.isNested.test(res[i]))
          919  +                res[i] = "{" + res[i] + "}";
          920  +        }
          921  +        if (res.length == 1) return res[0];
          922  +        return res.join(" ");
          923  +    }
          924  +    this.toString = function () {
          925  +        if (this.type != this.TEXT) {
          926  +            if (this.type == this.LIST)
          927  +                this.content = this.getString(this.content);
          928  +            else this.content = this.content.toString();
          929  +            this.type = this.TEXT;
          930  +        }
          931  +        return this.content;
          932  +    }
          933  +    this.toList = function () {
          934  +        if (this.type != this.LIST) {
          935  +            if (this.type != this.TEXT)
          936  +                this.content[0] = this.content;
          937  +            else {
          938  +
          939  +                var text = this.content;
          940  +                if (text.charAt(0) == "{" && text.charAt(text.length-1) == "}")
          941  +                    text = text.substring(1, text.length-1);
          942  +                if (text == "")
          943  +                    return [];
          944  +
          945  +                var parser = new TclParser(text.toString());
          946  +                this.content = [];
          947  +                for(;;) {
          948  +                    parser.parseList();
          949  +                    this.content.push(new TclObject(parser.getText()));
          950  +                    if (parser.type == parser.EOL || parser.type == parser.ESC)
          951  +                        break;
          952  +                }
          953  +            }
          954  +
          955  +            this.type = this.LIST;
          956  +        }
          957  +        return this.content;
          958  +    }
          959  +    this.toInteger = function () {
          960  +        if (this.type == this.INTEGER) return this.content;
          961  +        this.toString();
          962  +        if (this.content.match(Tcl.isHex))
          963  +            this.content = parseInt(this.content.substring(2), 16);
          964  +        else if (this.content.match(Tcl.isOctal))
          965  +            this.content = parseInt(this.content, 8);
          966  +        else if (this.content.match(Tcl.isDecimal))
          967  +            this.content = parseInt(this.content);
          968  +        else throw "Not an integer: '"+this.content+"'";
          969  +        if (isNaN(this.content)) throw "Not an integer: '"+this.content+"'";
          970  +        this.type = this.INTEGER;
          971  +        return this.content;
          972  +    }
          973  +    this.getFloat = function (text) {
          974  +        if (!text.toString().match(Tcl.isReal))
          975  +        throw "Not a real: '"+text+"'";
          976  +        return parseFloat(text);
          977  +    }
          978  +    this.toReal = function () {
          979  +        if (this.type == this.REAL)
          980  +        return this.content;
          981  +        this.toString();
          982  +        // parseFloat doesn't control all the string, so need to check it
          983  +        this.content = this.getFloat(this.content);
          984  +        if (isNaN(this.content)) throw "Not a real: '"+this.content+"'";
          985  +        this.type = this.REAL;
          986  +        return this.content;
          987  +    }
          988  +    this.getNumber = function () {
          989  +        try {
          990  +            return this.toInteger();
          991  +        } catch (e) {return this.toReal();}
          992  +    }
          993  +    this.toBoolean = function () {
          994  +        if (this.type == this.BOOL) return this.content;
          995  +        try {
          996  +            this.content = (this.toInteger() != 0);
          997  +        } catch (e) {
          998  +            var t = this.content;
          999  +            if (t instanceof Boolean) return t;
         1000  +            switch (t.toString().toLowerCase()) {
         1001  +            case "yes":case "true":case "on":
         1002  +                this.content = true;
         1003  +                break;
         1004  +            case "false":case "off":case "no":
         1005  +                this.content = false;
         1006  +                break;
         1007  +            default:
         1008  +                throw "Boolean expected, got: '"+this.content+"'";
         1009  +            }
         1010  +        }
         1011  +        this.type = this.BOOL;
         1012  +        return this.content;
         1013  +    }
         1014  +}
         1015  +function TclCommand(func, privdata) {
         1016  +  if (func == null) throw "No such function";
         1017  +  this.func     = func;
         1018  +  this.privdata = privdata;
         1019  +  this.ensemble = arguments[2];
         1020  +  
         1021  +  this.call = function(interp, args) {
         1022  +    var r = (this.func)(interp, args);
         1023  +    r = interp.objectify(r);
         1024  +    return r;
         1025  +  }
         1026  +  this.arity = function (args, min, max) {
         1027  +    if(max == undefined) max = min;
         1028  +    if (args.length < min || args.length > max) {
         1029  +      throw min + ".."+max + " words expected, got "+args.length;
         1030  +    }
         1031  +  } 
         1032  +}
         1033  +function TclParser(text) {
         1034  +  this.OK  = 0;
         1035  +  this.SEP = 0;
         1036  +  this.STR = 1;
         1037  +  this.EOL = 2;
         1038  +  this.EOF = 3;
         1039  +  this.ESC = 4;
         1040  +  this.CMD = 5;
         1041  +  this.VAR = 6;
         1042  +  this.text  = text;
         1043  +  this.start = 0;
         1044  +  this.end   = 0;
         1045  +  this.insidequote = false;
         1046  +  this.index = 0;
         1047  +  this.len = text.length;
         1048  +  this.type = this.EOL;
         1049  +  this.cur = this.text.charAt(0);
         1050  +  this.getText = function () {
         1051  +    return this.text.substring(this.start,this.end+1);
         1052  +  }
         1053  +  this.parseString = function () {
         1054  +    var newword = (this.type==this.SEP ||
         1055  +		   this.type == this.EOL || this.type == this.STR);
         1056  +    if (newword && this.cur == "{") return this.parseBrace();
         1057  +    else if (newword && this.cur == '"') {
         1058  +      this.insidequote = true;
         1059  +      this.feedchar();
         1060  +    }
         1061  +    this.start = this.index;
         1062  +    while (true) {
         1063  +      if (this.len == 0) {
         1064  +	this.end = this.index-1;
         1065  +	this.type = this.ESC;
         1066  +	return this.OK;
         1067  +      }
         1068  +      if (this.cur == "\\") {
         1069  +	if (this.len >= 2) this.feedSequence();
         1070  +      }
         1071  +      else if ("$[ \t\n\r;".indexOf(this.cur)>=0) {
         1072  +	if ("$[".indexOf(this.cur)>=0 || !this.insidequote) {
         1073  +	  this.end = this.index-1;
         1074  +	  this.type = this.ESC;
         1075  +	  return this.OK;
         1076  +	}
         1077  +      }
         1078  +      else if (this.cur == '"' && this.insidequote) {
         1079  +	this.end    = this.index-1;
         1080  +	this.type = this.ESC;
         1081  +	this.feedchar();
         1082  +	this.insidequote = false;
         1083  +	return this.OK;
         1084  +      }
         1085  +      this.feedchar();
         1086  +    }
         1087  +    return this.OK;
         1088  +  }
         1089  +  this.parseList = function () {
         1090  +    level = 0;
         1091  +    this.start = this.index;
         1092  +    while (true) {
         1093  +      if (this.len == 0) {
         1094  +	this.end = this.index;
         1095  +	this.type = this.EOL;
         1096  +	return;
         1097  +      }
         1098  +      switch (this.cur) {
         1099  +      case "\\":
         1100  +	if (this.len >= 2) this.feedSequence();
         1101  +	break;
         1102  +      case " ": case "\t": case "\n": case "\r":
         1103  +	if (level > 0) break;
         1104  +	this.end    = this.index - 1;
         1105  +	this.type = this.SEP;
         1106  +	this.feedchar();
         1107  +	return;
         1108  +      case '{': level++; break;
         1109  +      case '}': level--; break;
         1110  +      }
         1111  +      this.feedchar();
         1112  +    }
         1113  +    if (level != 0) throw "Not a list";
         1114  +    this.end = this.index;
         1115  +    return;
         1116  +  }
         1117  +  this.parseSep = function () {
         1118  +    this.start = this.index;
         1119  +    while (" \t\r\n".indexOf(this.cur)>=0) this.feedchar();
         1120  +    this.end    = this.index - 1;
         1121  +    this.type = this.SEP;
         1122  +    return this.OK;
         1123  +  }
         1124  +  this.parseEol = function () {
         1125  +    this.start = this.index;
         1126  +    while(" \t\n\r;".indexOf(this.cur)>=0) this.feedchar();
         1127  +    this.end    = this.index - 1;
         1128  +    this.type = this.EOL;
         1129  +    return this.OK;
         1130  +  }
         1131  +  this.parseCommand = function () {
         1132  +    var level = 1;
         1133  +    var blevel = 0;
         1134  +    this.feedcharstart();
         1135  +    while (true) {
         1136  +      if (this.len == 0) break;
         1137  +      if (this.cur == "[" && blevel == 0)
         1138  +	level++;
         1139  +      else if (this.cur == "]" && blevel == 0) {
         1140  +	level--;
         1141  +	if (level == 0) break;
         1142  +      } else if (this.cur == "\\") {
         1143  +	this.feedSequence();
         1144  +      } else if (this.cur == "{") {
         1145  +	blevel++;
         1146  +      } else if (this.cur == "}") {
         1147  +	if (blevel != 0) blevel--;
         1148  +      }
         1149  +      this.feedchar();
         1150  +    }
         1151  +    this.end    = this.index-1;
         1152  +    this.type = this.CMD;
         1153  +    if (this.cur == "]") this.feedchar();
         1154  +    return this.OK;
         1155  +  }
         1156  +  this.parseVar = function () {
         1157  +    this.feedcharstart();
         1158  +    this.end = this.index
         1159  +    + this.text.substring(this.index).match(Tcl.getVar).toString().length-1;
         1160  +    if (this.end == this.index-1) {
         1161  +      this.end = --this.index;
         1162  +      this.type = this.STR;
         1163  +    } else this.type = this.VAR;
         1164  +    this.setPos(this.end+1);
         1165  +    return this.OK;
         1166  +  }
         1167  +  this.parseBrace = function () {
         1168  +    var level = 1;
         1169  +    this.feedcharstart();
         1170  +    while (true) {
         1171  +      if (this.len > 1 && this.cur == "\\") {
         1172  +	this.feedSequence();
         1173  +      } else if (this.len == 0 || this.cur == "}") {
         1174  +	level--;
         1175  +	if (level == 0 || this.len == 0) {
         1176  +	  this.end = this.index-1;
         1177  +	  if (this.len > 0) this.feedchar();
         1178  +	  this.type = this.STR;
         1179  +	  return this.OK;
         1180  +	}
         1181  +      } else if (this.cur == "{") level++;
         1182  +      this.feedchar();
         1183  +    }
         1184  +    return this.OK; // unreached
         1185  +  }
         1186  +  this.parseComment = function () {
         1187  +    while (this.cur != "\n" && this.cur != "\r") this.feedchar();
         1188  +  }
         1189  +  this.getToken = function () {
         1190  +    while (true) {
         1191  +      if (this.len == 0) {
         1192  +	if (this.type == this.EOL) this.type = this.EOF;
         1193  +	if (this.type != this.EOF) this.type = this.EOL;
         1194  +	return this.OK;
         1195  +      }
         1196  +      switch (this.cur) {
         1197  +      case ' ':
         1198  +      case '\t':
         1199  +      if (this.insidequote) return this.parseString();
         1200  +      return this.parseSep();
         1201  +      case '\n':
         1202  +      case '\r':
         1203  +      case ';':
         1204  +      if (this.insidequote) return this.parseString();
         1205  +      return this.parseEol();
         1206  +      case '[': return this.parseCommand();
         1207  +      case '$': return this.parseVar();
         1208  +      }
         1209  +      if (this.cur == "#" && this.type == this.EOL) {
         1210  +	this.parseComment();
         1211  +	continue;
         1212  +      }
         1213  +      return this.parseString();
         1214  +    }
         1215  +    return this.OK; // unreached
         1216  +  }
         1217  +  this.feedSequence = function () {
         1218  +    if (this.cur != "\\") throw "Invalid escape sequence";
         1219  +    var cur = this.steal(1);
         1220  +    var specials = {};
         1221  +    specials.a = "\a";
         1222  +    specials.b = "\b";
         1223  +    specials.f = "\f";
         1224  +    specials.n = "\n";
         1225  +    specials.r = "\r";
         1226  +    specials.t = "\t";
         1227  +    specials.v = "\v";
         1228  +    switch (cur) {
         1229  +    case 'u':
         1230  +      var hex = this.steal(4);
         1231  +      if (hex != Tcl.isHexSeq.exec(hex))
         1232  +	throw "Invalid unicode escape sequence: "+hex;
         1233  +      cur = String.fromCharCode(parseInt(hex,16));
         1234  +      break;
         1235  +    case 'x':
         1236  +      var hex = this.steal(2);
         1237  +      if (hex != Tcl.isHexSeq.exec(hex))
         1238  +	throw "Invalid unicode escape sequence: "+hex;
         1239  +      cur = String.fromCharCode(parseInt(hex,16));
         1240  +      break;
         1241  +    case "a": case "b": case "f": case "n":
         1242  +    case "r": case "t": case "v":
         1243  +      cur = specials[cur];
         1244  +      break;
         1245  +    default:
         1246  +      if ("0123456789".indexOf(cur) >= 0) {
         1247  +	cur = cur + this.steal(2);
         1248  +	if (cur != Tcl.isOctalSeq.exec(cur))
         1249  +	  throw "Invalid octal escape sequence: "+cur;
         1250  +	cur = String.fromCharCode(parseInt(cur, 8));
         1251  +      }
         1252  +      break;
         1253  +    }
         1254  +    this.text[index] = cur;
         1255  +    this.feedchar();
         1256  +  }
         1257  +  this.steal = function (n) {
         1258  +    var tail = this.text.substring(this.index+1);
         1259  +    var word = tail.substr(0, n);
         1260  +    this.text = this.text.substring(0, this.index-1) + tail.substring(n);
         1261  +    return word;
         1262  +  }
         1263  +  this.feedcharstart = function () {
         1264  +    this.feedchar();
         1265  +    this.start = this.index;
         1266  +  }
         1267  +  this.setPos = function (index) {
         1268  +    var d = index-this.index;
         1269  +    this.index = index;
         1270  +    this.len -= d;
         1271  +    this.cur = this.text.charAt(this.index);
         1272  +  }
         1273  +  this.feedchar = function () {
         1274  +    this.index++;
         1275  +    this.len--;
         1276  +    if (this.len < 0)
         1277  +      throw "End of file reached";
         1278  +    this.cur = this.text.charAt(this.index);
         1279  +  }
         1280  +}
         1281  +//------------------------------------- main Read-Eval-Print loop
         1282  +var itp = new TclInterp();
         1283  +var res;
         1284  +process.argv.slice(2).forEach(function(cmd,index,array) {
         1285  +       itp.eval(cmd);
         1286  +     });
         1287  +var readline = require('readline');
         1288  +var rl = readline.createInterface(process.stdin, process.stdout);
         1289  +rl.setPrompt('% ');
         1290  +rl.prompt();
         1291  +rl.on('line', function(line) {
         1292  +    try {
         1293  +      res = itp.eval(line.trim());
         1294  +    } catch(e) {res = e;}
         1295  +    if(res != null && res.toString() != "" && res.toString().length) 
         1296  +      console.log(res.toString());
         1297  +    rl.prompt();
         1298  +  }).on('close',function() {
         1299  +    process.exit(0);
         1300  +  });

Added test_tcljs.tcl version [9b4e7f495f].

            1  +# test suite for TclJS
            2  +# This file is designed so it can also run in a tclsh. Some JavaScript goodies,
            3  +# like 1/0, sqrt(-1) were excluded from the tests.
            4  +# [clock format 0] was excluded because the timezone string differed.
            5  +
            6  +set version 0.5.3
            7  +set total  0
            8  +set passed 0
            9  +set fail   0
           10  +puts "------------------------ [info script]"
           11  +
           12  +proc e.g. {cmd -> expected} {
           13  +    incr ::total
           14  +    incr ::fail ;# to also count exceptions
           15  +    set mres [uplevel 1 $cmd]
           16  +    if [!= $mres $expected] {
           17  +	puts "**** $cmd -> $mres, expected $expected"
           18  +    } else {incr ::passed; incr ::fail -1}
           19  +}
           20  +#------------------------------- commands not in real Tcl
           21  +if [info exists auto_path] {
           22  +    proc func {name argl body} {proc $name $argl [list expr $body]}
           23  +    func +   {a b} {$a +  $b}
           24  +    func !=  {a b} {$a != $b}
           25  +    func *   {a b} {$a *  $b}
           26  +    func ==  {a b} {$a == $b}
           27  +    func <   {a b} {$a <  $b}
           28  +    set noTcl 0
           29  +} else {set noTcl 1}
           30  +
           31  +
           32  +# e.g. {exec echo hello} -> hello
           33  +
           34  +e.g. {append new hello} -> hello
           35  +e.g. {set x foo}        -> foo
           36  +e.g. {append x bar}     -> foobar
           37  +
           38  +e.g. {set d [dict create a 1 b 2 c 3]} -> {a 1 b 2 c 3}
           39  +e.g. {dict get $d b} -> 2
           40  +e.g. {dict set d b 5} -> {a 1 b 5 c 3}
           41  +e.g. {dict set d x 7} -> {a 1 b 5 c 3 x 7}
           42  +
           43  +e.g. {set home [file dirname [pwd]]; list} -> {}
           44  +e.g. {string equal [set env(HOME)] $home}   -> 1
           45  +e.g. {string equal [set ::env(HOME)] $home} -> 1
           46  +
           47  +e.g. {expr 6*7}         -> 42
           48  +e.g. {expr {6 * 7 + 1}} -> 43
           49  +e.g. {set x 43}         -> 43
           50  +e.g. {expr {$x-1}}      -> 42
           51  +e.g. {expr $x-1}        -> 42
           52  +if $noTcl {
           53  +    e.g. {clock format 0} -> {Thu Jan 01 1970 01:00:00 GMT+0100 (CET)}
           54  +    e.g. {set i [expr 1/0]} -> Infinity
           55  +    e.g. {expr $i==$i+42}   -> 1
           56  +    e.g. {set n [expr sqrt(-1)]} -> NaN
           57  +    e.g. {expr $n == $n} -> 0
           58  +    e.g. {expr $n==$n}   -> 0
           59  +    e.g. {expr $n!=$n}   -> 1
           60  +}
           61  +e.g. {expr 0xFF}   -> 255
           62  +e.g. {expr 0376}   -> 254
           63  +e.g. {expr 6 * 7}  -> 42
           64  +
           65  +e.g. {expr 1 == 2} -> 0
           66  +e.g. {expr 1 < 2}  -> 1
           67  +
           68  +set forres ""
           69  +e.g. {for {set i 0} {$i < 5} {incr i} {append forres $i}; set forres} -> 01234
           70  +
           71  +e.g. {set x 41}  -> 41
           72  +e.g. {incr x}    -> 42
           73  +e.g. {incr x 2}  -> 44
           74  +e.g. {incr x -3} -> 41
           75  +
           76  +e.g. {info args e.g.} -> {cmd -> expected}
           77  +e.g. {unset -nocomplain foo} -> {}
           78  +e.g. {info exists foo} -> 0
           79  +e.g. {set foo 42}      -> 42
           80  +e.g. {info exists foo} -> 1
           81  +e.g. {info level}      -> 0 ;# e.g. runs the command one level up
           82  +e.g. {info patchlevel} -> $version
           83  +
           84  +e.g. {join {a b c}}     -> {a b c}
           85  +e.g. {join {a b c} +}   -> {a+b+c}
           86  +e.g. {join {a {b c} d}} -> {a b c d}
           87  +
           88  +e.g. {expr !0}  -> 1
           89  +e.g. {expr !42} -> 0
           90  +
           91  +e.g. {regexp {X[ABC]Y} XAY}    -> 1
           92  +e.g. {regexp {X[ABC]Y} XDY}    -> 0
           93  +e.g. {regsub {[A-C]+} uBAAD x} -> uxD 
           94  +
           95  +e.g. {split "a b  c d"}     -> {a b {} c d}
           96  +e.g. {split " a b  c d"}     -> {{} a b {} c d}
           97  +e.g. {split "a b  c d "}     -> {a b {} c d {}}
           98  +e.g. {split usr/local/bin /} -> {usr local bin}
           99  +
          100  +e.g. {string equal foo foo}   -> 1
          101  +e.g. {string equal foo bar}   -> 0
          102  +e.g. {string index abcde 2}   -> c
          103  +e.g. {string length ""}       -> 0
          104  +e.g. {string length foo}      -> 3
          105  +e.g. {string range hello 1 3} -> ell
          106  +e.g. {string tolower TCL}     -> tcl
          107  +e.g. {string toupper tcl}     -> TCL
          108  +e.g. {string trim " foo "}    -> foo
          109  +
          110  +e.g. {set x {a b c}} -> {a b c}
          111  +e.g. {lappend x d}   -> {a b c d}
          112  +e.g. {set x}         -> {a b c d}
          113  +e.g. {lset x 3 e}    -> {a b c e}
          114  +e.g. {llength $x}    -> 4
          115  +e.g. {lindex $x 2}   -> c
          116  +e.g. {lrange $x 1 2} -> {b c}
          117  +e.g. {lsearch $x b}  -> 1
          118  +e.g. {lsearch $x y}  -> -1
          119  +e.g. {lsort {z x y}} -> {x y z}
          120  +
          121  +e.g. {proc f x {set y 0; info vars}} -> ""
          122  +e.g. {f 41} -> {x y} ;# must fix proc call in uplevel issue
          123  +set tmp [f 41]; e.g. {set tmp} -> {x y}
          124  +e.g. {info args f} -> x
          125  +e.g. {info body f} -> {set y 0; info vars}
          126  +#e.g. {f 42} -> {x y} ;# must fix proc call in uplevel issue
          127  +
          128  +puts "total $total tests, passed $passed, failed $fail"