glk
Version:
Glk protocol for JavaScript
296 lines (294 loc) • 9.49 kB
JavaScript
;
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");
}
};