modularize playground (prep for persistent/modular file handling)
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -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.
|
||||||
|
|||||||
@@ -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
254
playground/src/emul.ts
Normal 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;
|
||||||
13
playground/src/global.d.ts
vendored
13
playground/src/global.d.ts
vendored
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
21
playground/src/preload.ts
Normal 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
9
playground/src/types.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface CompileReq {
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompileRes {
|
||||||
|
output: string
|
||||||
|
javascript: string
|
||||||
|
duration: number
|
||||||
|
}
|
||||||
@@ -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
305
playground/src/zipfile.ts
Normal 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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user