aboutsummaryrefslogtreecommitdiff
path: root/treetree.js
diff options
context:
space:
mode:
Diffstat (limited to 'treetree.js')
-rwxr-xr-xtreetree.js384
1 files changed, 384 insertions, 0 deletions
diff --git a/treetree.js b/treetree.js
new file mode 100755
index 0000000..1c0e0ff
--- /dev/null
+++ b/treetree.js
@@ -0,0 +1,384 @@
+#!/usr/bin/env node
+
+var fs=require("fs"),util=require("util"),kbd=require("kbd");
+
+var flags={"debug":false},TRpreload=undefined;
+
+if(process.argv.length<3){
+ console.log("Please supply a treetree source file on the command line.");
+ console.log("\t-d Debug stuff.");
+ console.log("\t-s Preload stack in internal JSON array format in next parameter.");
+ process.exit(1);
+} else {
+ var i,j,skipArg=false;
+ for(i=2;i<process.argv.length-1;i++){
+ if(skipArg){
+ skipArg=false;
+ continue;
+ }
+ if(process.argv[i][0]!="-"){
+ console.log("Invalid argument '"+process.argv[i]+"'!");
+ process.exit(1);
+ }
+ for(j=1;j<process.argv[i].length;j++){
+ switch(process.argv[i][j]){
+ case "d":flags.debug=true;break;
+ case "s":TRpreload=JSON.parse(process.argv[i+1]);skipArg=true;break;
+ default:
+ console.log("Invalid flag '-"+process.argv[i][j]+"'!");
+ process.exit(1);
+ }
+ }
+ }
+}
+
+function TTerror(msg){
+ TTerror.super_.apply(this,arguments);
+ this.message=msg.toString();
+}
+util.inherits(TTerror,Error);
+
+function objcpy(obj){return JSON.parse(JSON.stringify(obj));}
+
+var cmd_list=["INP","OUT","IFI","OFI","IUS","OUS","ICH","OCH","PSH","PLL","SWP","POP","DUP","GRB","REL","INT","FLT","STR","NAN","LEN","SPL","SUM","DIF","PRO","QUO","POW","ROO","LOG","MOD","SIG","PI_","OR_","AND","XOR","INV","NOT","EQU","NEQ","SML","GRT","TAU","E__","RND","JMP","EXJ","IFJ","STP","STK"];
+
+//returns [command term,index after end of command]
+function extractCmd(code,startat){
+ var i;
+ while((code[startat]==" "||code[startat]=="\t"||code[startat]=="\n")&&startat<code.length)startat++;
+ if(startat==code.length)return false;
+ if(code[startat]=='"'||code[startat]=="'"||code[startat]=="{"){
+ var term;
+ term="";
+ for(i=startat+1;i<code.length;i++){
+ if(code[i]==code[startat]||(code[startat]=="{"&&code[i]=="}"))break;
+ term+=code[i];
+ }
+ if(code[startat]=="'"||code[startat]=="{")return extractCmd(code,i+1); //'this is a comment,' {and this too.}
+ return [["str",term],i+1];
+ } else if(code[startat].match(/[0-9]|-|\./)){
+ var neg,dot;
+ i=startat;
+ neg=code[startat]=="-";
+ i+=neg;
+ dot=false;
+ for(;i<code.length;i++){
+ if(code[i]=="."){
+ if(!dot)dot=true;
+ else break;
+ }
+ if(!code[i].match(/[0-9]/))break;
+ }
+ return [[dot?"flt":"int",parseFloat(code.slice(startat,i))],i];
+ } else if(code[startat]=="#"){
+ var term;
+ for(i=startat+1;i<code.length;i++)
+ if(code[i]==" "||code[i]=="\t"||code[i]=="\n")break;
+ return [["lbl",code.slice(startat+1,i)],i];
+ } else if(code[startat]=="@"){
+ var term;
+ for(i=startat+1;i<code.length;i++)
+ if(code[i]==" "||code[i]=="\t"||code[i]=="\n")break;
+ return [["ref",code.slice(startat+1,i)],i];
+ } else {
+ var term;
+ if(code.length-startat<3)throw new Error("Invalid command '"+code.slice(startat)+"' at end of code with less than 3 characters!");
+ term=code.slice(startat,startat+3).toUpperCase();
+ if(cmd_list.indexOf(term)!=-1)return [["cmd",term],startat+3];
+ else throw new Error("Unrecognised command '"+term+"'!");
+ }
+}
+
+var runCmds_state={"in":undefined,"out":undefined,"interm":"\n","outterm":"\n","stdinbuf":""};
+//Pass command array and the current tree. The tree is edited in-place.
+function runCmds(cmds,TR){
+ var cmdidx;
+ for(cmdidx=0;cmdidx<cmds.length;cmdidx++){
+ if(cmds[cmdidx][0]=="str"||cmds[cmdidx][0]=="int"||cmds[cmdidx][0]=="flt")TR=[cmds[cmdidx][1],TR,false];
+ else if(cmds[cmdidx][0]=="ref")TR=[labels[cmds[cmdidx][1]],TR,false]; //yes, a label reference is just the position integer.
+ else if(cmds[cmdidx][0]=="cmd"){
+ switch(cmds[cmdidx][1]){
+ case "INP":
+ if(runCmds_state["in"]==undefined){
+ var idx,str;
+ str=kbd.getLineSync()+"\n";
+ idx=str.indexOf(runCmds_state["interm"]);
+ while(idx==-1){
+ runCmds_state["stdinbuf"]+=str;
+ str=kbd.getLineSync()+"\n";
+ idx=str.indexOf(runCmds_state["interm"]);
+ }
+ idx=runCmds_state["stdinbuf"].length+idx;
+ runCmds_state["stdinbuf"]+=str;
+ TR=[runCmds_state["stdinbuf"].slice(0,idx),TR,false];
+ runCmds_state["stdinbuf"]=runCmds_state["stdinbuf"].slice(idx+1,runCmds_state["stdinbuf"].length);
+ } else {
+ var buf,str,c;
+ buf=new Buffer(1);
+ str="";
+ do{
+ fs.readSync(runCmds_state["in"],buf,0,1,null);
+ c=String.fromCharCode(buf[0])
+ str+=c;
+ }while(c!=runCmds_state["interm"]);
+ TR=[str.slice(0,-1),TR,false];
+ }
+ break;
+ case "OUT":
+ var f,stdout,printPrimaryChildTree;
+ if(runCmds_state["out"]==undefined)stdout=true;
+ else stdout=false;
+ printPrimaryChildTree=function(e){
+ while(e!=false){
+ if(typeof e[0]!="string")throw new TTerror("Value to be printed, "+e[0].toString()+", is not a string!");
+ if(stdout)process.stdout.write(e[0]);
+ else fs.writeSync(runCmds_state["out"],new Buffer(e[0]),0,e[0].length,null);
+ if(e[2]!==false)printPrimaryChildTree(e[2]);
+ e=e[1];
+ }
+ }
+ if(TR===false)throw new TTerror("Tree is empty while processing OUT!");
+ if(typeof TR[0]!="string")throw new TTerror("Value to be printed, "+TR[0].toString()+", is not a string!");
+ if(stdout)process.stdout.write(TR[0]+runCmds_state["outterm"]);
+ else fs.writeSync(runCmds_state["out"],new Buffer(TR[0]+runCmds_state["outterm"]),0,TR[0].length+1,null);
+ printPrimaryChildTree(TR[2]);
+ TR=TR[1];
+ break;
+ case "IFI":
+ var fname;
+ if(typeof TR[0]!="string")throw new TTerror("File name (in IFI) is not a string (namely "+TR[0].toString()+")!");
+ if(TR[2]!=false)throw new TTerror("Cannot open multiple files (in IFI), but the root node ('"+TR[0]+"') has secondary children!");
+ fname=TR[0];
+ TR=[1,TR[1],false]; //"return" 1
+ try{
+ runCmds_state["in"]=fs.openSync(fname,"r");
+ } catch(err){
+ TR[0]=0; //"return" 0
+ }
+ break;
+ case "OFI":
+ var fname;
+ if(typeof TR[0]!="string")throw new TTerror("File name (in OFI) is not a string (namely "+TR[0].toString()+")!");
+ if(TR[2]!=false)throw new TTerror("Cannot open multiple files (in OFI), but the root node ('"+TR[0]+"') has secondary children!");
+ fname=TR[0];
+ TR=[1,TR[1],false]; //"return" 1
+ try{
+ runCmds_state["out"]=fs.openSync(fname,"w");
+ } catch(err){
+ TR[0]=0; //"return" 0
+ }
+ break;
+ case "IUS":
+ if(runCmds_state["in"]!=undefined){
+ fs.closeSync(runCmds_state["in"]);
+ runCmds_state["in"]=undefined;
+ }
+ break;
+ case "OUS":
+ if(runCmds_state["out"]!=undefined){
+ fs.closeSync(runCmds_state["out"]);
+ runCmds_state["out"]=undefined;
+ }
+ break;
+ case "ICH":
+ if(TR[2]!=false)throw new TTerror("Cannot open multiple files (in ICH), but the root node ('"+TR[0]+"') has secondary children!");
+ if(typeof TR[0]!="string")throw new TTerror("String expected (in ICH), got '"+TR[0].toString()+"'!");
+ if(TR[0].length!=1)throw new TTerror("String is not one character while processing ICH!");
+ runCmds_state["interm"]=TR[0];
+ TR=TR[1];
+ break;
+ case "OCH":
+ if(TR[2]!=false)throw new TTerror("Cannot open multiple files (in OCH), but the root node ('"+TR[0]+"') has secondary children!");
+ if(typeof TR[0]!="string")throw new TTerror("String expected (in OCH), got '"+TR[0].toString()+"'!");
+ if(TR[0].length!=1)throw new TTerror("String is not one character while processing OCH!");
+ runCmds_state["outterm"]=TR[0];
+ TR=TR[1];
+ break;
+ case "PSH":case "PLL":
+ var n;
+ if(TR[2]!=false)throw new TTerror("Cannot operate multiple times (in "+cmds[cmdidx][1]+"), but the root node ('"+TR[0]+"') has secondary children!");
+ if(typeof TR[0]!="number"||TR[0]%1!=0)throw new TTerror("Integer expected (in "+cmds[cmdidx][1]+"), got '"+TR[0].toString()+"'!");
+ n=TR[0];
+ TR=TR[1];
+ if(cmds[cmdidx][1]=="PLL")n=-n;
+ if(n>0){ //PSH
+ var next_part,next_part_parent,moved_node,i;
+ next_part_parent=TR;
+ for(i=0;i<n;i++){
+ next_part_parent=next_part_parent[1];
+ if(next_part_parent==false)throw new TTerror("Trying to PSH beyond end of tree (in "+cmds[cmdidx][1]+")!");
+ }
+ next_part=objcpy(next_part_parent[1]);
+ moved_node=[TR[0],next_part,objcpy(TR[2])];
+ TR=TR[1];
+ next_part_parent[1]=moved_node;
+ } else if(n<-1){ //PLL; if n==1 then it pulls the root node onto the root node. Which is useless.
+ n=-n;
+ var moved_node_parent,i;
+ moved_node_parent=TR;
+ for(i=0;i<n-1;i++){
+ moved_node_parent=moved_node_parent[1];
+ if(moved_node_parent==false)throw new TTerror("Trying to PLL beyond end of tree (in "+cmds[cmdidx][1]+")!");
+ }
+ moved_node=moved_node_parent[1];
+ if(moved_node==false)throw new TTerror("Trying to PLL just beyond end of tree (in "+cmds[cmdidx][1]+")!");
+ moved_node_parent[1]=objcpy(moved_node[1]);
+ moved_node=[moved_node[0],TR,moved_node[2]];
+ TR=objcpy(moved_node); //Sorry, garbage colector.
+ }
+ break;
+ case "SWP":
+ var temp;
+ temp=objcpy(TR[1]);
+ TR[1]=objcpy(TR[2]);
+ TR[2]=temp;
+ break;
+ case "POP":break;
+ case "DUP":break;
+ case "GRB":break;
+ case "REL":break;
+ case "INT":break;
+ case "FLT":break;
+ case "STR":break;
+ case "NAN":break;
+ case "LEN":break;
+ case "SPL":break;
+ case "SUM":break;
+ case "DIF":break;
+ case "PRO":break;
+ case "QUO":break;
+ case "POW":break;
+ case "ROO":break;
+ case "LOG":break;
+ case "MOD":break;
+ case "SIG":break;
+ case "PI_":break;
+ case "OR_":break;
+ case "AND":break;
+ case "XOR":break;
+ case "INV":break;
+ case "NOT":break;
+ case "EQU":break;
+ case "NEQ":break;
+ case "SML":break;
+ case "GRT":break;
+ case "TAU":break;
+ case "E__":break;
+ case "RND":break;
+ case "JMP":break;
+ case "EXJ":break;
+ case "IFJ":break;
+ case "STP":
+ process.exit();
+ break;
+ case "STK":
+ var str;
+ str=util.inspect(TR,{"depth":null});
+ if(runCmds_state["out"]==undefined)process.stdout.write(str);
+ else fs.writeSync(runCmds_state["out"],new Buffer(str),0,str.length,null);
+ break;
+ default:
+ throw new Error("Internal error: Unrecognised command in internal command array! Please report the command '"+cmds[cmdidx][1]+"'!");
+ }
+ } else throw new Error("Internal error: Invalid command type! Please report the type '"+cmds[cmdidx][0]+"'!");
+ if(flags.debug){
+ process.stdout.write("\x1B[36m");
+ console.log("%j",TR);
+ process.stdout.write("\x1B[0m");
+ }
+ }
+}
+
+//Edits `labels` in-place.
+function parseLabels(cmds,labels){
+ var i;
+ for(i=0;i<cmds.length;i++){
+ if(cmds[i][0]=="lbl"){
+ labels[cmds[i][1]]=i;
+ cmds.splice(i,1);
+ i--;
+ }
+ }
+}
+
+/*
+`TR` stores the main tree in treetree. `false` indicates non-existance. It
+contains a root node with a primary child and a secondary child. Every node is
+represented by an array with three items, the first containing the contents of
+the node, the second containing the primary child (which is also an array like
+this node) and the third containing the second child. An absent child is of
+course indicated with the literal value `false`.
+For example, the following program:
+1 2 3 SWP 4
+makes the tree (*'s indicating absence of children, / is a primary child, --
+is a secondary child):
+ 4 *
+ /
+ 3--2 *
+ * /
+ 1 *
+ *
+and is represented in `TR` like:
+[4,
+ [3,
+ false,
+ [2,
+ [1,false,false],
+ false
+ ]
+ ],
+ false
+]
+
+Commands, in `cmds`, are each an array with two items, the first being the type
+and the second being the value. The types are:
+"str", "flt", "int", "lbl", "ref" and "cmd".
+"str" contains a literal string, "flt" a literal floating-point and "int" a
+literal integer. "lbl" and "ref" contain the names of the labels and "irf"
+contains the integer mapping of the label after the first pass. "cmd" indicates
+a string with the specific command.
+
+The first pass over the code removes all the "lbl" commands and adds those
+labels to `labels`. The second pass interprets the code.
+*/
+
+var fname,infile,offset,code,cmds,TR,labels;
+
+fname=process.argv[process.argv.length-1];
+try{
+ infile=fs.openSync(fname,"r");
+ fs.closeSync(infile);
+}catch(e){
+ console.log("Could not open file '"+fname+"'!");
+ process.exit(1);
+}
+code=fs.readFileSync(fname,{"encoding":"utf8"});
+cmds=[]; //command list in array form
+offset=0;
+while(true){
+ offset=extractCmd(code,offset);
+ if(offset===false)break;
+ cmds.push(offset[0]);
+ offset=offset[1];
+}
+
+if(TRpreload)TR=TRpreload;
+else TR=false; //The Big TRee starts non-existant. Yeah. Well, it is true, sort-of.
+labels=[]; //label index
+parseLabels(cmds,labels);
+if(flags.debug){
+ process.stdout.write("\x1B[36m");
+ process.stdout.write("Commands: ");
+ console.log(cmds);
+ process.stdout.write("Labels: ");
+ console.log(labels);
+ process.stdout.write("\x1B[0m");
+}
+try{
+ runCmds(cmds,TR);
+} catch(err) {
+ if(err instanceof TTerror){
+ console.log("\x1B[31;1mTreetree error:\x1B[0m \x1B[31m"+err.message+"\x1B[0m");
+ process.exit(1);
+ } else throw err;
+}