2024-01-28 15:45:09 +00:00
|
|
|
import { Bert } from "./bert.js";
|
2024-01-28 11:32:24 +00:00
|
|
|
import { Duplex } from "node:stream";
|
2024-01-29 13:59:30 +00:00
|
|
|
const log = (msg) => process.stderr.write(`${msg}\r\n`);
|
2024-02-03 02:54:58 +00:00
|
|
|
/**
|
|
|
|
* Only handles Buffers.
|
|
|
|
*/
|
|
|
|
export class DumbPort extends Duplex {
|
|
|
|
static LEN_LEN = 2;
|
|
|
|
buf;
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.buf = Buffer.alloc(65_535 + DumbPort.LEN_LEN);
|
|
|
|
process.stdin.on("readable", () => {
|
|
|
|
while (this._read())
|
|
|
|
;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
_read() {
|
|
|
|
const lenBuf = process.stdin.read(DumbPort.LEN_LEN);
|
|
|
|
if (lenBuf) {
|
|
|
|
// Update this if you update LEN_LEN
|
|
|
|
const len = lenBuf.readUInt16BE(0);
|
|
|
|
const buf = process.stdin.read(len);
|
|
|
|
this.push(buf);
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.push(null);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_write(obj, encodingOrCallback, callback) {
|
|
|
|
const actualCallback = callback || typeof encodingOrCallback === "function" ? encodingOrCallback : undefined;
|
|
|
|
if (Buffer.isBuffer(obj)) {
|
2024-02-03 05:55:55 +00:00
|
|
|
this.buf.writeUInt16BE(obj.byteLength);
|
2024-02-03 02:54:58 +00:00
|
|
|
obj.copy(this.buf, DumbPort.LEN_LEN);
|
|
|
|
process.stdout.write(this.buf.subarray(0, obj.byteLength + DumbPort.LEN_LEN), actualCallback);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw new Error("write was passed non-Buffer");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Handles full Erlang/Elixir terms.
|
|
|
|
*/
|
2024-01-28 11:32:24 +00:00
|
|
|
export class Port extends Duplex {
|
2024-01-28 15:39:03 +00:00
|
|
|
bert;
|
2024-02-02 19:34:02 +00:00
|
|
|
lenBuffer = Buffer.alloc(4);
|
2024-01-28 15:45:09 +00:00
|
|
|
constructor(allBinariesAsString, mapKeyAsAtom, decodeUndefinedValues) {
|
2024-01-28 11:32:24 +00:00
|
|
|
super({ objectMode: true });
|
2024-01-28 15:45:09 +00:00
|
|
|
this.bert = new Bert(allBinariesAsString, mapKeyAsAtom, decodeUndefinedValues);
|
2024-01-28 16:31:37 +00:00
|
|
|
process.stdin.on("readable", () => {
|
|
|
|
while (this._read()) { }
|
|
|
|
});
|
2024-01-28 11:32:24 +00:00
|
|
|
}
|
2024-01-28 15:12:30 +00:00
|
|
|
_read() {
|
2024-01-28 15:01:33 +00:00
|
|
|
const lenBytes = process.stdin.read(4);
|
|
|
|
if (lenBytes) {
|
2024-01-28 15:39:03 +00:00
|
|
|
const termLen = this.bert.bytesToInt(lenBytes, 4, true);
|
2024-01-29 13:59:30 +00:00
|
|
|
log(`Got incoming term length: ${termLen} (bytes: <<${lenBytes.toString('hex').match(/../g).map((hex) => parseInt(`0x${hex}`).toString()).join(', ')}>>).`);
|
2024-01-28 15:01:33 +00:00
|
|
|
const termBytes = process.stdin.read(termLen);
|
|
|
|
if (termBytes) {
|
2024-01-28 16:31:37 +00:00
|
|
|
const decoded = this.bert.decode(termBytes);
|
|
|
|
this.push(decoded);
|
|
|
|
return decoded;
|
2024-01-28 11:32:24 +00:00
|
|
|
}
|
2024-01-28 15:01:33 +00:00
|
|
|
else {
|
2024-01-29 13:59:30 +00:00
|
|
|
log("Term read got erroneous null.");
|
2024-01-28 16:31:37 +00:00
|
|
|
return null;
|
2024-01-28 11:32:24 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-02 19:34:02 +00:00
|
|
|
else {
|
|
|
|
this.push(null);
|
2024-01-28 16:31:37 +00:00
|
|
|
return null;
|
2024-02-02 19:34:02 +00:00
|
|
|
}
|
2024-01-28 11:32:24 +00:00
|
|
|
}
|
2024-01-28 15:12:30 +00:00
|
|
|
_write(obj, encodingOrCallback, callback) {
|
2024-01-28 15:01:33 +00:00
|
|
|
const actualCallback = callback || typeof encodingOrCallback === "function" ? encodingOrCallback : undefined;
|
2024-02-02 19:34:02 +00:00
|
|
|
log(`Port was written to.`);
|
2024-01-28 11:32:24 +00:00
|
|
|
try {
|
2024-02-02 19:34:02 +00:00
|
|
|
const term = this.bert.encode(obj, false);
|
|
|
|
this.lenBuffer.writeUInt32BE(term.length);
|
|
|
|
process.stdout.write(this.lenBuffer);
|
2024-01-30 07:38:17 +00:00
|
|
|
process.stdout.write(term, actualCallback);
|
2024-01-28 16:31:37 +00:00
|
|
|
return true;
|
2024-01-28 11:32:24 +00:00
|
|
|
}
|
|
|
|
catch (error) {
|
2024-01-29 13:59:30 +00:00
|
|
|
log(`Error writing: ${error}`);
|
2024-01-28 15:01:33 +00:00
|
|
|
return false;
|
2024-01-28 11:32:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-01-30 07:17:34 +00:00
|
|
|
export class Server {
|
|
|
|
port;
|
|
|
|
handler;
|
|
|
|
state = undefined;
|
|
|
|
handleTerm = (term) => {
|
|
|
|
if (this.state === undefined) {
|
|
|
|
this.state = term;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.handler(term, (t) => this.port.write(t), this.state, (reply, ...extraArgs) => {
|
|
|
|
if (reply === "reply")
|
|
|
|
this.port.write(term);
|
|
|
|
if (reply === "reply" && extraArgs.length === 2) {
|
|
|
|
this.state = extraArgs[1];
|
|
|
|
}
|
|
|
|
else if (reply === "noreply" && extraArgs.length === 1) {
|
|
|
|
this.state = extraArgs[0];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
constructor(port, handler) {
|
|
|
|
this.port = port;
|
|
|
|
this.handler = handler;
|
|
|
|
port.on("readable", () => {
|
|
|
|
let term;
|
|
|
|
while (term = port.read()) {
|
|
|
|
this.handleTerm(term);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|