diff options
Diffstat (limited to 'treetree.js')
-rwxr-xr-x | treetree.js | 384 |
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; +} |