modularize playground (prep for persistent/modular file handling)

This commit is contained in:
2024-12-07 16:58:10 -08:00
parent 421f5ea208
commit ba70845c09
11 changed files with 673 additions and 193 deletions

View File

@@ -4,7 +4,7 @@
- [x] SortedMap.newt issue in `where` - [x] SortedMap.newt issue in `where`
- [x] fix "insufficient patterns", wire in M or Either String - [x] fix "insufficient patterns", wire in M or Either String
- [ ] Matching _,_ when Maybe is expected should be an error - [ ] Matching _,_ when Maybe is expected should be an error
- [ ] error for repeated names on LHS - [ ] error for non-linear pattern
- [ ] typeclass dependencies - [ ] typeclass dependencies
- need to flag internal functions to not search (or flag functions for search). I need to decide on syntax for this. - need to flag internal functions to not search (or flag functions for search). I need to decide on syntax for this.
- don't search functions that are currently being defined. This is subtle... We do want to recurse in bind, we don't want to do that for the isEq function. Maybe something idris like. - don't search functions that are currently being defined. This is subtle... We do want to recurse in bind, we don't want to do that for the isEq function. Maybe something idris like.

View File

@@ -3,9 +3,8 @@ mkdir -p public
echo build monaco worker echo build monaco worker
esbuild --bundle node_modules/monaco-editor/esm/vs/editor/editor.worker.js > public/workerMain.js esbuild --bundle node_modules/monaco-editor/esm/vs/editor/editor.worker.js > public/workerMain.js
echo build newt worker echo build newt worker
esbuild src/worker.ts > public/worker.js esbuild src/worker.ts --bundle --format=esm > public/worker.js
echo copy newt echo copy newt
cp ../build/exec/newt.js public cp ../build/exec/newt.js public
# cat ../build/exec/newt.js |grep -v '^#'>> public/worker.js cp -r static/* public
cp -r samples/* public (cd samples && zip -r ../public/files.zip .)
# esbuild --minify ../build/exec/newt.min.js > public/newt.js

254
playground/src/emul.ts Normal file
View File

@@ -0,0 +1,254 @@
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);
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: {
// We'll want to replace this one
write(s) {
console.log("*", 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;

View File

@@ -3,15 +3,8 @@ export {};
declare global { declare global {
// typescript doesn't know worker.ts is a worker // typescript doesn't know worker.ts is a worker
function importScripts(...scripts: string[]): void; function importScripts(...scripts: string[]): void;
interface Process {
platform: string; // let files: Record<string, string>;
stdout: { // let process: Process;
write(s: string): void;
};
argv: string[];
exit(_: number): void;
}
let files: Record<string, string>;
let process: Process;
let newtMain: () => unknown; let newtMain: () => unknown;
} }

View File

@@ -2,8 +2,18 @@ import { effect, signal } from "@preact/signals";
import { newtConfig, newtTokens } from "./monarch.ts"; import { newtConfig, newtTokens } from "./monarch.ts";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
import { h, render, VNode } from "preact"; import { h, render } from "preact";
import { ChangeEvent } from "preact/compat"; import { ChangeEvent } from "preact/compat";
import { archive, preload } from "./preload.ts";
import { CompileReq, CompileRes } from "./types.ts";
// editor.(createModel / setModel / getModels) to switch files
// TODO - remember files and allow switching?
// static zip filesystem with user changes overlaid via localStorage
// download individual files (we could use the cheap compression from the pdflib or no compression to make zip download)
// would need way to reset/delete
interface FC { interface FC {
file: string; file: string;
@@ -31,22 +41,27 @@ monaco.languages.registerDefinitionProvider("newt", {
if (!topData) return; if (!topData) return;
// HACK - we don't know our filename which was generated from `module` decl, so // HACK - we don't know our filename which was generated from `module` decl, so
// assume the last context entry is in our file. // assume the last context entry is in our file.
let last = topData.context[topData.context.length-1] let last = topData.context[topData.context.length - 1];
let file = last.fc.file let file = last.fc.file;
const info = model.getWordAtPosition(position); const info = model.getWordAtPosition(position);
if (!info) return; if (!info) return;
let entry = topData.context.find((entry) => entry.name === info.word); let entry = topData.context.find((entry) => entry.name === info.word);
// we can't switch files at the moment // we can't switch files at the moment
if (!entry || entry.fc.file !== file) return if (!entry || entry.fc.file !== file) return;
let lineNumber = entry.fc.line + 1 let lineNumber = entry.fc.line + 1;
let column = entry.fc.col + 1 let column = entry.fc.col + 1;
let word = model.getWordAtPosition({lineNumber, column}) let word = model.getWordAtPosition({ lineNumber, column });
let range = new monaco.Range(lineNumber, column, lineNumber, column) let range = new monaco.Range(lineNumber, column, lineNumber, column);
if (word) { if (word) {
range = new monaco.Range(lineNumber, word.startColumn, lineNumber, word.endColumn) range = new monaco.Range(
lineNumber,
word.startColumn,
lineNumber,
word.endColumn
);
} }
return { uri: model.uri,range} return { uri: model.uri, range };
}, },
}); });
monaco.languages.registerHoverProvider("newt", { monaco.languages.registerHoverProvider("newt", {
@@ -68,7 +83,7 @@ monaco.languages.registerHoverProvider("newt", {
}, },
}); });
const newtWorker = new Worker("worker.js"); const newtWorker = new Worker("worker.js");
let postMessage = (msg: any) => newtWorker.postMessage(msg); let postMessage = (msg: CompileReq) => newtWorker.postMessage(msg);
// Safari/MobileSafari have small stacks in webworkers. // Safari/MobileSafari have small stacks in webworkers.
if (navigator.vendor.includes("Apple")) { if (navigator.vendor.includes("Apple")) {
@@ -117,20 +132,29 @@ function setOutput(output: string) {
state.output.value = output; state.output.value = output;
} }
window.onmessage = (ev) => { interface ConsoleList {
messages: string[]
}
interface ConsoleItem {
message: string
}
type WinMessage = CompileRes | ConsoleList | ConsoleItem
window.onmessage = (ev: MessageEvent<WinMessage>) => {
console.log("window got", ev.data); console.log("window got", ev.data);
if (ev.data.messages) state.messages.value = ev.data.messages; if ('messages' in ev.data) state.messages.value = ev.data.messages;
if (ev.data.message) { if ('message' in ev.data) {
state.messages.value = [...state.messages.value, ev.data.message]; state.messages.value = [...state.messages.value, ev.data.message];
} }
// safari callback // safari callback
if (ev.data.output !== undefined) { if ('output' in ev.data) {
setOutput(ev.data.output); setOutput(ev.data.output);
state.javascript.value = ev.data.javascript; state.javascript.value = ev.data.javascript;
} }
}; };
newtWorker.onmessage = (ev) => { newtWorker.onmessage = (ev: MessageEvent<CompileRes>) => {
setOutput(ev.data.output); setOutput(ev.data.output);
state.javascript.value = ev.data.javascript; state.javascript.value = ev.data.javascript;
}; };
@@ -169,9 +193,10 @@ if (window.matchMedia) {
} }
async function loadFile(fn: string) { async function loadFile(fn: string) {
if (fn) { await preload;
const res = await fetch(fn); let data = archive?.getData(fn);
const text = await res.text(); if (data) {
let text = new TextDecoder().decode(data);
state.editor.value!.setValue(text); state.editor.value!.setValue(text);
} else { } else {
state.editor.value!.setValue("module Main\n"); state.editor.value!.setValue("module Main\n");

21
playground/src/preload.ts Normal file
View File

@@ -0,0 +1,21 @@
import {ZipFile} from './zipfile'
export let archive: ZipFile | undefined;
export let preload = (async function () {
// We pull down an archive of .ttc and support shim.files
try {
let res = await self.fetch("files.zip");
if (res.status === 200) {
let data = await res.arrayBuffer();
archive = new ZipFile(new Uint8Array(data));
let entries = archive.entries;
let count = Object.keys(entries).length;
console.log(`preloaded ${count} files`);
} else {
console.error(
`fetch of files.zip got status ${res.status}: ${res.statusText}`
);
}
} catch (e) {
console.error("preload failed", e);
}
})();

9
playground/src/types.ts Normal file
View File

@@ -0,0 +1,9 @@
export interface CompileReq {
src: string;
}
export interface CompileRes {
output: string
javascript: string
duration: number
}

View File

@@ -1,165 +1,39 @@
class Buffer extends ArrayBuffer { import { shim } from "./emul";
static alloc(n: number) { import { archive, preload } from "./preload";
return new Buffer(n); import { CompileReq, CompileRes } from "./types";
}
indexOf(n: number) {
let view = new Uint8Array(this);
return view.indexOf(n);
}
static concat(bufs: Buffer[]) { const handleMessage = async function (ev: {data: CompileReq}) {
let size = bufs.reduce((a, b) => (a += b.byteLength), 0); console.log("message", ev.data);
let rval = new Buffer(size); await preload;
let view = new Uint8Array(rval); shim.archive = archive;
let off = 0; let { src } = ev.data;
for (let buf of bufs) { let module = "Main";
view.set(new Uint8Array(buf), off); let m = src.match(/module (\w+)/);
off += buf.byteLength; if (m) module = m[1];
} let fn = `${module}.newt`;
return rval; const outfile = "out.js";
} shim.process.argv = ["", "", fn, "-o", outfile, "--top"];
toString() { console.log("Using args", shim.process.argv);
return new TextDecoder().decode(this); shim.files[fn] = new TextEncoder().encode(src);
} shim.files[outfile] = new TextEncoder().encode("No JS output");
} shim.stdout = "";
const start = +new Date();
let files: Record<string, string> = {};
interface Handle {
name: string;
mode: string;
pos: number;
buf: Buffer;
}
let fds: Handle[] = [];
let shim: any = {
os: {
platform() {
return "linux";
},
},
fs: {
openSync(name: string, mode: string) {
console.log("open", name, arguments);
let te = new TextEncoder();
let fd = fds.findIndex((x) => !x);
if (fd < 0) fd = fds.length;
let buf;
let pos = 0;
if (mode == "w") {
buf = new Buffer(0);
} else {
if (!files[name]) throw new Error(`${name} not found`);
buf = te.encode(files[name]);
}
fds[fd] = { buf, pos, mode, name };
// we'll mutate the pointer as stuff is read
return fd;
},
writeSync(fd: number, line: string) {
if (typeof line !== "string") throw new Error("not implemented");
let handle = fds[fd];
if (!handle) throw new Error(`bad fd ${fd}`)
let buf2 = new TextEncoder().encode(line);
handle.buf = Buffer.concat([handle.buf, buf2])
},
chmodSync(fn: string, mode: number) { },
readSync(fd: number, buf: Buffer, start: number, len: number) {
let hand = fds[fd];
let avail = hand.buf.byteLength - hand.pos;
let rd = Math.min(avail, len);
let src = new Uint8Array(hand.buf);
let dest = new Uint8Array(buf);
for (let i = 0; i < rd; i++) dest[start + i] = src[hand.pos++];
return rd;
},
closeSync(fd: number) {
let handle = fds[fd];
if (handle.mode == "w") {
files[handle.name] = new TextDecoder().decode(handle.buf);
}
delete fds[fd];
},
},
};
// 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];
}
throw new Error(`implement fs.${String(prop)}`)
},
});
const process: Process = {
platform: "linux",
argv: ["", ""],
stdout: {
// We'll want to replace this one
write: console.log,
},
exit(v: number) {
console.log("exit", v);
},
// stdin: { fd: 0 },
};
const require = (x: string) => shim[x];
// Maybe the shim goes here and we append newt...
let stdout = ''
// We'll want to collect and put info in the monaco
process.stdout.write = (s) => {
stdout += s
};
// hack for now
const preload = [
"Prelude.newt",
"Web.newt",
"aoc2023/day1/eg.txt",
"aoc2023/day1/eg2.txt",
]
const handleMessage = async function (e) {
console.log('message for you sir', e.data)
for (let fn of preload) {
if (!files[fn]) {
console.log('preload', fn)
let res = await fetch(fn)
let text = await res.text()
files[fn] = text
}
}
let {src} = e.data
let module = 'Main'
let m = src.match(/module (\w+)/)
if (m) module = m[1]
let fn = `${module}.newt`
process.argv = ["", "", fn, "-o", "out.js", "--top"];
console.log("args", process.argv);
files[fn] = src;
files['out.js'] = 'No JS output';
stdout = ''
const start = +new Date()
try { try {
newtMain(); newtMain();
} catch (e) { } catch (e) {
// make it clickable // make it clickable in console
console.error(e) console.error(e);
// make it visable // make it visable on page
stdout += '\n' + String(e) shim.stdout += "\n" + String(e);
} }
let duration = +new Date() - start let duration = +new Date() - start;
console.log(`process ${fn} in ${duration} ms`) console.log(`shim.process ${fn} in ${duration} ms`);
let javascript = files['out.js'] let javascript = new TextDecoder().decode(shim.files[outfile]);
let output = stdout let output = shim.stdout;
sendResponse({javascript, output, duration}) sendResponse({ javascript, output, duration });
} };
let sendResponse = postMessage
onmessage = handleMessage
importScripts('newt.js') // hooks for worker.html to override
let sendResponse : (_: CompileRes) => void = postMessage;
onmessage = handleMessage;
importScripts("newt.js");

305
playground/src/zipfile.ts Normal file
View File

@@ -0,0 +1,305 @@
// I wrote this inflate years ago, seems to work for zip
class BitReader {
pos = 0;
bits = 0;
acc = 0;
len: number;
data: Uint8Array;
constructor(data: Uint8Array) {
this.data = data;
this.len = data.length;
}
read(bits: number) {
while (this.bits < bits) {
if (this.pos >= this.len) throw "EOF";
this.acc |= this.data[this.pos++] << this.bits;
this.bits += 8;
}
let rval = this.acc & ((1 << bits) - 1);
this.acc >>= bits;
this.bits -= bits;
return rval;
}
read8() {
if (this.pos > this.len) throw "EOF";
// flush
if (this.bits > 0) {
this.bits = 0;
this.acc = 0;
}
return this.data[this.pos++];
}
read16() {
let rval = this.read8() * 256 + this.read8();
return rval;
}
}
class HuffDic {
limit: number[];
codes: number[];
base: number[];
constructor(lengths: number[]) {
let counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let min = 0;
let max = 0;
for (let i = 0; i < lengths.length; i++) {
let len = lengths[i];
if (len != 0) {
if (len < min || min == 0) min = len;
if (len > max) max = len;
counts[len]++;
}
}
this.base = [];
this.limit = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
];
let code = 0;
let seq = 0;
let next_code = [];
for (let i = min; i <= max; i++) {
let n = counts[i];
next_code[i] = code;
this.base[i] = code - seq;
code += n;
seq += n;
this.limit[i] = code - 1;
code <<= 1;
}
this.codes = [];
for (let i = 0; i < lengths.length; i++) {
let n = lengths[i];
if (n != 0) {
code = next_code[n];
next_code[n]++;
if (!this.base[n]) this.base[n] = 0;
seq = code - this.base[n];
this.codes[seq] = i;
}
}
}
readSymbol(r: BitReader) {
let v = 0;
let l = 0;
let offset = 0;
for (let i = 1; i < this.limit.length; i++) {
v <<= 1;
v |= r.read(1);
let limit = this.limit[i];
if (v <= limit) {
return this.codes[v - this.base[i]];
}
}
throw "eHUFF";
}
}
let staticHuff: HuffDic;
let distHuff: HuffDic;
{
let tmp = [];
for (let i = 0; i < 144; i++) tmp[i] = 8;
for (let i = 144; i < 256; i++) tmp[i] = 9;
for (let i = 256; i < 280; i++) tmp[i] = 7;
for (let i = 280; i < 288; i++) tmp[i] = 8;
staticHuff = new HuffDic(tmp);
tmp = [];
for (let i = 0; i < 30; i++) {
tmp[i] = 5;
}
distHuff = new HuffDic(tmp);
}
function inflate(input: Uint8Array) {
let r = new BitReader(input);
let out = new Uint8Array(65536);
let pos = 0;
const push = (b: number) => {
if (pos + 10 > out.length) {
const tmp = new Uint8Array(out.length * 1.5);
tmp.set(out);
out = tmp;
}
out[pos++] = b;
};
let fin = 0;
while (!fin) {
fin = r.read(1);
let btype = r.read(2);
let huff2;
let huff3;
if (btype == 0) {
let len = r.read16();
let nlen = r.read16();
for (let i = 0; i < len; i++) push(r.read8());
} else if (btype == 1) {
// fixed huffman
huff2 = staticHuff;
huff3 = distHuff;
} else if (btype == 2) {
// dynamic huffman
let hlit = r.read(5) + 257;
let hdist = r.read(5) + 1;
let hclen = r.read(4) + 4;
let lengths: number[] = [];
for (let i = 0; i < 19; i++) lengths[i] = 0;
let xx = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
];
for (let i = 0; i < hclen; i++) {
let t = r.read(3);
lengths[xx[i]] = t;
}
let huff = new HuffDic(lengths);
lengths = [];
while (true) {
let k = huff.readSymbol(r);
if (k < 16) {
lengths.push(k);
} else if (k == 16) {
let count = r.read(2) + 3;
if (lengths.length == 0) throw new Error("no lengths?");
for (; count > 0; count--) lengths.push(lengths[lengths.length - 1]);
} else if (k == 17) {
let count = r.read(3) + 3;
for (; count > 0; count--) lengths.push(0);
} else if (k == 18) {
let count = r.read(7) + 11;
for (; count > 0; count--) lengths.push(0);
}
if (lengths.length >= hlit + hdist) break;
}
huff2 = new HuffDic(lengths.slice(0, hlit));
huff3 = new HuffDic(lengths.slice(hlit));
} else {
throw new Error("btype " + btype);
}
if (huff2) {
while (true) {
let k = huff2.readSymbol(r);
let len = 0;
let n = 0; // extra bits
if (k < 256) {
push(k);
continue;
} else if (k == 256) {
// End of block
break;
} else if (k < 265) {
len = k - 257 + 3;
n = 0;
} else if (k < 269) {
len = (k - 265) * 2 + 11;
n = 1;
} else if (k < 273) {
len = (k - 269) * 4 + 19;
n = 2;
} else if (k < 277) {
len = (k - 273) * 8 + 35;
n = 3;
} else if (k < 281) {
len = (k - 277) * 16 + 67;
n = 4;
} else if (k < 285) {
len = (k - 281) * 32 + 131;
n = 5;
} else {
len = 258;
n = 0;
}
if (n > 0) len += r.read(n);
// distance
if (r.pos > r.len) throw new Error("EOF");
let dist;
if (huff3) dist = huff3.readSymbol(r);
else dist = r.read(5);
if (dist < 4) {
dist++;
} else if (dist < 30) {
let db = (dist - 2) >> 1;
let extra = (dist & 1) << db;
extra |= r.read(db);
dist = (1 << (db + 1)) + 1 + extra;
} else {
throw new Error(`dist ${dist}`);
}
if (dist > pos) throw new Error(`dist ${dist} > pos ${pos}`);
let s: number = pos - dist;
for (let i = 0; i < len; i++) push(out[s + i]);
}
}
}
return out.slice(0, pos);
}
interface Entry {
size: number;
start: number;
end: number;
}
export class ZipFile {
data: Uint8Array;
entries: Record<string, Entry>;
constructor(data: Uint8Array) {
this.data = data;
this.entries = {};
let td = new TextDecoder();
let error = (msg: string) => {
throw new Error(`${msg} at ${pos}`);
};
let view = new DataView(data.buffer);
let pos = 0;
while (pos < view.byteLength) {
let sig = view.getUint32(pos, true);
if (sig == 0x02014b50) break;
if (sig != 0x04034b50) error(`bad sig ${sig.toString(16)}`);
let method = view.getUint16(pos + 8, true);
let csize = view.getUint32(pos + 18, true);
let size = view.getUint32(pos + 22, true);
let fnlen = view.getUint16(pos + 26, true);
let eflen = view.getUint16(pos + 28, true);
let fn = td.decode(data.slice(pos + 30, pos + 30 + fnlen));
if (size) {
let start = pos + 30 + fnlen + eflen;
let end = start + csize;
this.entries[fn] = { size, start, end };
}
pos = pos + 30 + fnlen + eflen + csize;
}
}
getData(name: string) {
let { start, end, size } = this.entries[name];
return inflate(new Uint8Array(this.data.slice(start, end)));
}
}