Posted to tcl by st3ve at Sun Nov 10 06:46:02 GMT 2013view raw

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

Comments

Posted by suchenwi at Sun Nov 10 11:17:19 GMT 2013 [text] [code]

Thank you! The test suite passes indeed: % suchenwi@suchenwi-NC10:~$ nodejs tcl053.js "source test_tcljs.tcl" ------------------------ test_tcljs.tcl total 63 tests, passed 63, failed 0