Files
newt/playground/src/emul.ts

254 lines
6.6 KiB
TypeScript

import { ZipFile } from "./zipfile";
class Buffer extends DataView {
static alloc(n: number) {
return new Buffer(new Uint8Array(n).buffer);
}
indexOf(n: number) {
return new Uint8Array(this.buffer).indexOf(n);
}
get length() {
return this.byteLength;
}
slice(start: number, end: number) {
return new Buffer(this.buffer.slice(start, end));
}
readUInt8(i: number) {
return this.getUint8(i);
}
writeUInt8(val: number, i: number) {
this.setUint8(i, val);
}
write(value: string, start: number, len: number, enc: string) {
// console.log("write", value, start, len, enc);
let buf = new TextEncoder().encode(value);
let ss = 0;
let se = Math.min(len, buf.length);
let ts = start;
for (; ss < se; ss++, ts++) this.setInt8(ts, buf[ss]);
shim.process.__lasterr.errno = 0;
return se;
}
readDoubleLE(i: number) {
return this.getFloat64(i, true);
}
readInt32LE(i: number) {
return this.getInt32(i, true);
}
writeInt32LE(val: number, i: number) {
return this.setInt32(i, val, true);
}
copy(target: Buffer, ts: number, ss: number, se: number) {
for (; ss < se; ss++, ts++) target.setInt8(ts, this.getInt8(ss));
}
static concat(bufs: Buffer[]) {
let size = bufs.reduce((a, b) => (a += b.byteLength), 0);
let rval = Buffer.alloc(size);
let off = 0;
for (let buf of bufs) {
const view = new Int8Array(rval.buffer);
view.set(new Uint8Array(buf.buffer), off);
off += buf.byteLength;
}
return rval;
}
toString() {
return new TextDecoder().decode(this);
}
}
export interface Handle {
name: string;
mode: string;
pos: number;
buf: Uint8Array;
}
interface Process {
platform: string;
stdout: {
write(s: string): void;
};
argv: string[];
exit(_: number): void;
cwd(): string;
env: Record<string, string>;
__lasterr: { errno: number };
}
export interface NodeShim {
stdout: string;
archive?: ZipFile;
process: Process;
files: Record<string, Uint8Array>;
fds: Handle[];
tty: {
isatty(): number;
};
os: {
platform(): string;
};
fs: any;
}
export let shim: NodeShim = {
// these three and process are poked at externally
archive: undefined,
stdout: "",
files: {},
fds: [],
tty: {
isatty() {
return 0;
},
},
os: {
platform() {
return "linux";
},
},
fs: {
// TODO - Idris is doing readdir, we should implement that
opendirSync(name: string) {
let fd = shim.fds.findIndex((x) => !x);
if (fd < 0) fd = shim.fds.length;
console.log("openDir", name);
shim.process.__lasterr.errno = 0;
return fd;
},
mkdirSync(name: string) {
console.log("mkdir", name);
shim.process.__lasterr.errno = 0;
return 0;
},
openSync(name: string, mode: string) {
console.log("open", name, mode);
if (name.startsWith('./')) name = name.slice(2)
let te = new TextEncoder();
let fd = shim.fds.findIndex((x) => !x);
if (fd < 0) fd = shim.fds.length;
let buf: Uint8Array;
let pos = 0;
if (mode == "w") {
buf = new Uint8Array(0);
} else {
// TODO, we need to involve localStorage when the window does multiple files and persists
if (shim.files[name]) {
buf = shim.files[name];
} else if (shim.archive?.entries[name]) {
// keep a copy of the uncompressed version for speed
buf = shim.files[name] = shim.archive.getData(name)!;
} else {
shim.process.__lasterr.errno = 1;
throw new Error(`${name} not found`);
}
}
shim.process.__lasterr.errno = 0;
shim.fds[fd] = { buf, pos, mode, name };
// we'll mutate the pointer as stuff is read
return fd;
},
writeSync(fd: number, line: string | Buffer) {
try {
let handle = shim.fds[fd];
if (!handle) throw new Error(`bad fd ${fd}`);
let buf2: ArrayBuffer;
if (typeof line === "string") {
buf2 = new TextEncoder().encode(line);
let newbuf = new Uint8Array(handle.buf.byteLength + buf2.byteLength);
newbuf.set(new Uint8Array(handle.buf));
newbuf.set(new Uint8Array(buf2), handle.buf.byteLength);
handle.buf = newbuf;
shim.process.__lasterr.errno = 0;
} else if (line instanceof Buffer) {
let start = arguments[2];
let len = arguments[3];
buf2 = line.buffer.slice(start, start + len);
let newbuf = new Uint8Array(handle.buf.byteLength + buf2.byteLength);
newbuf.set(new Uint8Array(handle.buf));
newbuf.set(new Uint8Array(buf2), handle.buf.byteLength);
handle.buf = newbuf;
shim.process.__lasterr.errno = 0;
return len;
} else {
debugger;
throw new Error(`write ${typeof line} not implemented`);
}
} catch (e) {
debugger;
throw e;
}
},
chmodSync(fn: string, mode: number) {},
fstatSync(fd: number) {
let hand = shim.fds[fd];
return { size: hand.buf.byteLength };
},
readSync(fd: number, buf: Buffer, start: number, len: number) {
let hand = shim.fds[fd];
let avail = hand.buf.length - hand.pos;
let rd = Math.min(avail, len);
let src = hand.buf;
let dest = new Uint8Array(buf.buffer);
for (let i = 0; i < rd; i++) dest[start + i] = src[hand.pos++];
return rd;
},
closeSync(fd: number) {
let handle = shim.fds[fd];
// console.log("close", handle.name);
if (handle.mode == "w") {
shim.files[handle.name] = handle.buf;
}
delete shim.fds[fd];
},
},
process: {
platform: "linux",
argv: ["", ""],
stdout: {
write(s) {
shim.stdout += s;
},
},
exit(v: number) {
console.log("exit", v);
},
cwd() {
return "";
},
env: {
NO_COLOR: "true",
IDRIS2_CG: "javascript",
IDRIS2_PREFIX: "",
},
__lasterr: {
errno: 0,
},
// stdin: { fd: 0 },
},
};
// Spy on Idris' calls to see what we need to fill in
shim.fs = new Proxy(shim.fs, {
get(target, prop, receiver) {
if (prop in target) {
return (target as any)[prop];
}
let err = new Error(`IMPLEMENT fs.${String(prop)}`);
// idris support eats the exception
console.error(err);
throw err;
},
});
// we intercept require to return our fake node modules
declare global {
interface Window {
require: (x: string) => any;
process: Process;
}
}
const requireStub: any = (x: string) => (shim as any)[x];
self.require = requireStub;
self.process = shim.process;