UNPKG

glk

Version:

Glk protocol for JavaScript

296 lines (294 loc) 9.49 kB
"use strict"; const Glk=module.exports; const fs=require("fs"); const smallEndian=(require("os").endianness=="LE"); const disp=require("./dispatch.js"); const reg={ a: new WeakMap(), b: new WeakMap(), c: new WeakMap(), d: new WeakMap(), }; const nreg={[-1]:null}; const arrays=[]; const bRefOut=Buffer.from("<"); Glk.Window=function(){}; Glk.Window.prototype.toString=()=>"[Glk Window]"; Glk.Stream=function(){}; Glk.Stream.prototype.toString=()=>"[Glk Stream]"; Glk.File=function(){}; Glk.File.prototype.toString=()=>"[Glk File]"; Glk.Channel=function(){}; Glk.Channel.prototype.toString=()=>"[Glk Channel]"; Glk._classes={a:Glk.Window,b:Glk.Stream,c:Glk.File,d:Glk.Channel}; let oclass; const objreg=id=>{ let obj=new Glk._classes[oclass](); reg[oclass].set(obj,id); nreg[id]=obj; }; const copyOutArray=(arr,buf)=>{ let n=buf.length-1; let i; switch(buf.toString("ascii",n)) { case "C": if(arr instanceof Uint8Array) { buf.copy(arr,0,0,n); } else { for(i=0;i<n;i++) arr[i]=buf[i]; } break; case "I": n>>=2; for(i=0;i<n;i++) arr[i]=buf.readUInt32BE(i<<2); break; case "Q": n>>=2; for(i=0;i<n;i++) arr[i]=nreg[buf.readInt32BE(i<<2)]; break; } }; Glk._write=x=>{ let n=fs.writeSync(3,x,0,x.length); if(n!=x.length) throw new Error("Cannot write Glk dispatch data"); } Glk._read=x=>{ let n=fs.readSync(4,x,0,x.length); if(n!=x.length) throw new Error("Cannot read Glk dispatch data"); } Glk._read1=x=>{ let n=fs.readSync(4,x,0,1); if(n!=1) throw new Error("Cannot read Glk dispatch data"); } Glk._dispatch=(id,proto)=>{ let p=[]; let r=false; proto.replace(/([&<>]\+?||:)(#?!?[A-Z][a-z]?|\[[0-9]+[A-Za-z]*\])/g,(x,y,z)=>{ let o={type:null,ref:0,required:false,array:false,retain:false}; x=(y==":"?"<+":y); if(x) o.ref=" <>&".indexOf(x[0]); if(x[1]=="+") o.required=true; if(z[0]=="#") o.array=true,z=z.slice(1); if(z[0]=="!") o.retain=true,z=z.slice(1); if(z[0]=="[") { o.type=[]; z.replace(/[A-Z][a-z]?/g,q=>o.type.push(q[0]=="Q"?q:q[0])); } else { o.type=z[0]=="Q"?z:z[0]; if(o.ref && !o.array) o.type=[o.type]; } if(y==":") r=true; p.push(o); return ""; }); let f=(...args)=>{ let b=[Buffer.allocUnsafe(2)]; let ri=0; const add=(x,y)=>{ switch(x[0]) { case "C": if(typeof(y)!=="number") throw new TypeError("Argument must be a number"); b.push(Buffer.from([67,y&255])); break; case "I": if(typeof(y)!=="number") throw new TypeError("Argument must be a number"); b.push(Buffer.from([73,(y>>24)&255,(y>>16)&255,(y>>8)&255,y&255])); break; case "Q": if(y===null) { b.push(Buffer.from("Z")); } else if(reg[x[1]].has(y)) { y=reg[x[1]].get(y); b.push(Buffer.from([81,(y>>24)&255,(y>>16)&255,(y>>8)&255,y&255])); } else { throw new TypeError("Argument must be a Glk "+{a:"Window",b:"Stream",c:"File",d:"Channel"}[x[1]]); } break; case "S": if(typeof(y)!=="string") throw new TypeError("Argument must be a string"); if(y.indexOf("\0")!=-1) throw new RangeError("String contains embedded null characters"); b.push(Buffer.from("S"+y+"\0","binary")); break; case "U": if(typeof(y)!=="string") throw new TypeError("Argument must be a string"); if(y.indexOf("\0")!=-1) throw new RangeError("String contains embedded null characters"); let z=[...y]; let z1=Buffer.allocUnsafe((z.length+1)*4); b.push(Buffer.from([85,(z.length>>24)&255,(z.length>>16)&255,(z.length>>8)&255,z.length&255])); z.push("\0"); z.forEach((x1,y1)=>z1.writeInt32BE(x1.charCodeAt(),y1<<2)); b.push(z1); break; } }; let a=0; let r1; let ry=[]; let ay={}; if(r) args.push(r1=[]); b[0].writeUInt16BE(id,0); for(let o of p) { if(a>=args.length) throw new TypeError("Too few arguments"); let v=args[a]; if(v===undefined) v=null; if(o.ref) { if(typeof(v)!=="object") throw new TypeError("Argument must be an object"); if(v===null) { if(o.required) throw new TypeError("Argument not optional"); b.push(Buffer.from("0")); a++; continue; } else { b.push(Buffer.from("1")); } } if(Array.isArray(o.type)) { // Structure type if(o.ref==1) { // Pass out only b.push(Buffer.from(o.type.map(x=>({I:"I\0\0\0\0<",C:"C\0<",Q:"Z"+x[1]}[x[0]])).join(""))); o.type.forEach((x,y)=>ry.push(v,y)); } else { // Pass in o.type.forEach((x,y)=>{ add(x,v[y]); if(o.ref&1) b.push(x[0]=="Q"?Buffer.from(x[1]):bRefOut),ry.push(v,y); }); } } else if(o.array) { // Array type if(o.type=="C" && (v instanceof Int8Array || v instanceof Uint8Array || v instanceof Uint8ClampedArray)) { let x=Buffer.from("#\0\0\0\0C","binary"); x.writeUInt32BE(v.length,1); b.push(x,Buffer.from(v.buffer,v.byteOffset,v.length)); } else if(o.type=="C" && Array.isArray(v)) { let x=Buffer.from("#\0\0\0\0C","binary"); x.writeUInt32BE(v.length,1); b.push(x,Buffer.from(v.map(x=>x&255))); } else if(o.type=="I" && (v instanceof Int32Array || v instanceof Uint32Array)) { let x=Buffer.from("#\0\0\0\0I","binary"); let y=Buffer.from(Buffer.from(v.buffer,v.byteOffset,v.byteLength)); if(smallEndian) y.swap32(); x.writeUInt32BE(v.length,1); b.push(x,y); } else if(o.type=="I" && Array.isArray(v)) { let x=Buffer.from("#\0\0\0\0I","binary"); let y=Buffer.allocUnsafe(v.length*4); x.writeUInt32BE(v.length,1); v.forEach((x1,y1)=>y.writeInt32BE(x1|0,y1<<2)); b.push(x,y); } else if(o.type[0]=="Q" && Array.isArray(v)) { if(v.some(x1=>!reg[o.type[1]].has(x1))) throw new TypeError("Invalid object reference in array"); let x=Buffer.from("#\0\0\0\0Q","binary"); let y=Buffer.allocUnsafe(v.length*4); x.writeUInt32BE(y.length,1); v.forEach((x1,y1)=>y.writeInt32BE(reg[o.type[1]].get(x1),y1<<2)); b.push(x,y); } else { throw new TypeError("Invalid array type"); } if(o.retain) { let i=arrays.indexOf(null,ri); if(i==-1) i=arrays.length; let x=Buffer.from("!\0\0\0\0"); x.writeUInt32BE(i,1); arrays[i]=null; ay[i]=v; b.push(x); ri=i+1; } else if(o.ref&1) { b.push(bRefOut); ry.push(v); } } else { // Scalar type add(o.type,o.ref?v[0]:v); } a++; } b.push(Buffer.from(".")); Glk._write(Buffer.concat(b)); b=Buffer.allocUnsafe(1); a=0; if(id==1) process.exit(0); // Glk is finished for(;;) { let i; Glk._read1(b); switch(String.fromCharCode(b[0])) { case "C": Glk._read1(b); ry[a][ry[a+1]]=b[0]; a+=2; break; case "I": Glk._read(b=Buffer.allocUnsafe(4)); ry[a][ry[a+1]]=b.readUInt32BE(0); a+=2; break; case "Q": Glk._read(b=Buffer.allocUnsafe(4)); ry[a][ry[a+1]]=nreg[b.readUInt32BE(0)]; a+=2; break; case "Z": ry[a][ry[a+1]]=null; a+=2; break; case "a": case "b": case "c": case "d": oclass=String.fromCharCode(b[0]); Glk._read(b=Buffer.allocUnsafe(4)); objreg(b.readInt32BE(0)); break; case "$": Glk._read(b=Buffer.allocUnsafe(4)); i=b.readInt32BE(0); reg.a.delete(nreg[i]); reg.b.delete(nreg[i]); reg.c.delete(nreg[i]); reg.d.delete(nreg[i]); delete nreg[i]; break; case "#": Glk._read(b=Buffer.allocUnsafe(4)); Glk._read(b=Buffer.allocUnsafe(b.readUInt32BE(0)+1)); copyOutArray(ry[a++],b) break; case "_": a++; break; case ".": return r?r1[0]:undefined; case "+": Glk._read(b=Buffer.allocUnsafe(4)); i=b.readUInt32BE(0); arrays[i]=ay[i]; break; case "-": Glk._read(b=Buffer.allocUnsafe(8)); i=b.readUInt32BE(0); Glk._read(b=Buffer.allocUnsafe(b.readUInt32BE(4)+1)); copyOutArray(arrays[i],b); arrays[i]=null; break; } } } Object.defineProperty(f,"length",{configurable:true,value:parseInt(proto)-(proto.slice(-1)==":"?0:1)}); return f; }; disp.constants.forEach(([x,y])=>{ Glk[x]=y; }); disp.functions(Glk); delete disp.constants; delete disp.functions; Glk.bufToString=(b,n)=>{ if(typeof(n)!=="number") throw new TypeError("Second argument must be a number"); if(b instanceof Uint32Array) { return String.fromCodePoint(...b.slice(0,n)); } else if(b instanceof Uint8Array) { return String.fromCharCode(...b.slice(0,n)); } else { throw new TypeError("First argument must be a Uint8Array or Uint32Array"); } };