Playground enhancements
This commit is contained in:
@@ -2,8 +2,11 @@
|
|||||||
## Todo items for the playground
|
## Todo items for the playground
|
||||||
|
|
||||||
- [x] sample files
|
- [x] sample files
|
||||||
|
- [x] codemirror migration
|
||||||
- [ ] make sample files available for import
|
- [ ] make sample files available for import
|
||||||
- workaround is to visit the file first
|
- workaround is to visit the file first
|
||||||
|
- we can put them in the zip file and pull them over the IPC
|
||||||
|
- [ ] make phone layout automatic
|
||||||
- [x] move newt to a worker (shim + newt + listener)
|
- [x] move newt to a worker (shim + newt + listener)
|
||||||
- [x] tabs for source, compiler output
|
- [x] tabs for source, compiler output
|
||||||
- [x] Show errors in editor
|
- [x] Show errors in editor
|
||||||
@@ -13,3 +16,7 @@
|
|||||||
- [x] publish / host on github
|
- [x] publish / host on github
|
||||||
- [ ] multiple persistent files
|
- [ ] multiple persistent files
|
||||||
- [x] kill return for autocomplete
|
- [x] kill return for autocomplete
|
||||||
|
- [x] save to url (copy from idris2-playground)
|
||||||
|
- [ ] click on url
|
||||||
|
- [ ] settings
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2023/Aoc.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day1.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day10.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day11.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day11b.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day12.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day13.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day14.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day15.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day16.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day17.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day18.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day19.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day2.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day20.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day21.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day22.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day23.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day24.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day25.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day3.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day4.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day5.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day6.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day7.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day8.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Day9.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/DayXX.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Node.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Parser.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/Prelude.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/SortedMap.newt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day10
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day11
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day12
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day13
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day14
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day15
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day16
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day17
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day18
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day19
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day2
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day20
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day21
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day22
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day23
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day24
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day25
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day3
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day4
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day5
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day6
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day7
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day8
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../aoc2024/day9
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../src
|
|
||||||
14
playground/src/base64.test.ts
Normal file
14
playground/src/base64.test.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { b64decode, b64encode } from "./base64.ts";
|
||||||
|
|
||||||
|
test("round trip", () => {
|
||||||
|
for (let s of ["", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"]) {
|
||||||
|
let t = new TextEncoder().encode(s);
|
||||||
|
console.log(t, t + "");
|
||||||
|
let enc = b64encode(t);
|
||||||
|
assert.equal(enc.length, Math.ceil((t.length * 8) / 6));
|
||||||
|
assert.equal(b64decode(b64encode(t)) + "", t + "");
|
||||||
|
console.log("---");
|
||||||
|
}
|
||||||
|
});
|
||||||
45
playground/src/base64.ts
Normal file
45
playground/src/base64.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// tables
|
||||||
|
const i2c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||||
|
const c2i: Record<string, number> = {};
|
||||||
|
i2c.split("").forEach((c, i) => (c2i[c] = i));
|
||||||
|
|
||||||
|
export function b64encode(data: Uint8Array): string {
|
||||||
|
let rval = "";
|
||||||
|
let i = 0;
|
||||||
|
while (i < data.length) {
|
||||||
|
let v = data[i++];
|
||||||
|
// aaaaaa aa
|
||||||
|
rval += i2c[v >> 2];
|
||||||
|
// aabbbb bbbb
|
||||||
|
v = ((v & 3) << 8) | data[i++];
|
||||||
|
rval += i2c[v >> 4];
|
||||||
|
if (i > data.length) break;
|
||||||
|
// bbbbcc cccccc
|
||||||
|
v = ((v & 15) << 8) | data[i++];
|
||||||
|
rval += i2c[v >> 6];
|
||||||
|
if (i > data.length) break;
|
||||||
|
// cccccc
|
||||||
|
rval += i2c[v & 63];
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function b64decode(s: string) {
|
||||||
|
let i = 0;
|
||||||
|
let arr: number[] = [];
|
||||||
|
while (i < s.length) {
|
||||||
|
// aaaaaabb bbbb
|
||||||
|
let acc = (c2i[s[i++]] << 6) | c2i[s[i++]];
|
||||||
|
arr.push(acc >> 4);
|
||||||
|
if (i >= s.length) break;
|
||||||
|
// bbbbcccc cc
|
||||||
|
acc = ((acc & 15) << 6) | c2i[s[i++]];
|
||||||
|
arr.push(acc >> 2);
|
||||||
|
if (i >= s.length) break;
|
||||||
|
// ccdddddd
|
||||||
|
acc = ((acc & 3) << 6) | c2i[s[i++]];
|
||||||
|
arr.push(acc);
|
||||||
|
if (i >= s.length) break;
|
||||||
|
}
|
||||||
|
return Uint8Array.from(arr);
|
||||||
|
}
|
||||||
@@ -81,7 +81,6 @@ function tokenizer(stream: StringStream, state: State): string | null {
|
|||||||
let word = stream.current();
|
let word = stream.current();
|
||||||
if (keywords.includes(word)) return "keyword";
|
if (keywords.includes(word)) return "keyword";
|
||||||
if (word[0] >= "A" && word[0] <= "Z") return "typename";
|
if (word[0] >= "A" && word[0] <= "Z") return "typename";
|
||||||
console.log('IDENT', )
|
|
||||||
return "identifier";
|
return "identifier";
|
||||||
}
|
}
|
||||||
// unhandled
|
// unhandled
|
||||||
@@ -136,7 +135,7 @@ export class CMEditor implements AbstractEditor {
|
|||||||
// For indent on return
|
// For indent on return
|
||||||
indentService.of((ctx, pos) => {
|
indentService.of((ctx, pos) => {
|
||||||
let line = ctx.lineAt(pos)
|
let line = ctx.lineAt(pos)
|
||||||
if (!line) return null
|
if (!line || !line.from) return null
|
||||||
let prevLine = ctx.lineAt(line.from - 1);
|
let prevLine = ctx.lineAt(line.from - 1);
|
||||||
if (prevLine.text.trimEnd().match(/\b(of|where|do)\s*$/)) {
|
if (prevLine.text.trimEnd().match(/\b(of|where|do)\s*$/)) {
|
||||||
let pindent = prevLine.text.match(/^\s*/)?.[0].length ?? 0
|
let pindent = prevLine.text.match(/^\s*/)?.[0].length ?? 0
|
||||||
|
|||||||
131
playground/src/deflate.ts
Normal file
131
playground/src/deflate.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// This is a minimal deflate implementation.
|
||||||
|
//
|
||||||
|
// It writes data that zlib can decompress, but sticks to the built-in huffman tables
|
||||||
|
// and a simple search heuristic to keep the code size down.
|
||||||
|
//
|
||||||
|
// TODO
|
||||||
|
// - initialize offsets to something other than zero (MAXINT32)
|
||||||
|
// - offsets are all wrong.
|
||||||
|
|
||||||
|
const maxMatchOffset = 1 << 14
|
||||||
|
const tableSize = 1 << 14
|
||||||
|
const lengthExtra = [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
|
||||||
|
const distExtra = [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
|
||||||
|
const lengthCode : number[] = []
|
||||||
|
const lengthBase : number[] = []
|
||||||
|
const distCode : number[] = [];
|
||||||
|
const distBase : number[] = [];
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
// see trees.c
|
||||||
|
let code
|
||||||
|
let length = 0
|
||||||
|
for (code = 0; code < 28; code++) {
|
||||||
|
lengthBase[code] = length;
|
||||||
|
for (let n = 0; n < (1<<lengthExtra[code]); n++) {
|
||||||
|
lengthCode[length++] = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lengthCode[length-1] = code
|
||||||
|
|
||||||
|
// Initialize the mapping dist (0..32K) -> dist code (0..29)
|
||||||
|
let dist = 0;
|
||||||
|
for (code = 0 ; code < 16; code++) {
|
||||||
|
distBase[code] = dist;
|
||||||
|
for (let n = 0; n < (1<<distExtra[code]); n++) {
|
||||||
|
distCode[dist++] = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dist >>= 7; // from now on, all distances are divided by 128
|
||||||
|
for ( ; code < 30; code++) {
|
||||||
|
distBase[code] = dist << 7;
|
||||||
|
for (let n = 0; n < (1<<(distExtra[code]-7)); n++) {
|
||||||
|
distCode[256 + dist] = code;
|
||||||
|
dist++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
export function deflate(buf: Uint8Array, raw = true) {
|
||||||
|
let bitpos = 0
|
||||||
|
let out = new Uint8Array(65536)
|
||||||
|
function write(bits: number,value:number,backwards?: boolean) {
|
||||||
|
for (let i = 0;i < bits;i++) {
|
||||||
|
const bytepos = bitpos >> 3
|
||||||
|
const mask = 1 << (bitpos&7)
|
||||||
|
if (bytepos + 10 > out.length) {
|
||||||
|
const tmp = new Uint8Array(out.length*1.5)
|
||||||
|
tmp.set(out)
|
||||||
|
out = tmp
|
||||||
|
}
|
||||||
|
let j = backwards ? i : bits - i - 1
|
||||||
|
if (value & (1 << j)) out[bytepos] |= mask
|
||||||
|
bitpos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emit(value: number) {
|
||||||
|
if (value < 144) write(8, 48+value)
|
||||||
|
else if (value < 256) write(9, value-144+400)
|
||||||
|
else if (value < 280) write(7, value-256)
|
||||||
|
else write(8, value-280+192)
|
||||||
|
}
|
||||||
|
if (!raw) {
|
||||||
|
write(8,0x78,true)
|
||||||
|
write(8,0x9c,true)
|
||||||
|
}
|
||||||
|
write(1,1)
|
||||||
|
write(2,2)
|
||||||
|
|
||||||
|
const src = new DataView(buf.buffer)
|
||||||
|
|
||||||
|
const hash = (u:number) => (u * 0x1e35a7bd) >>> 18 // top 14 bits
|
||||||
|
const values = new Int32Array(tableSize)
|
||||||
|
const offsets = new Int32Array(tableSize)
|
||||||
|
const sLimit = buf.length - 3
|
||||||
|
|
||||||
|
let s = 0
|
||||||
|
let nextEmit = 0
|
||||||
|
while ( s < sLimit) {
|
||||||
|
let cur_val = src.getUint32(s, true)
|
||||||
|
let cur_hash = hash(cur_val)
|
||||||
|
const cand_val = values[cur_hash]
|
||||||
|
const cand_off = offsets[cur_hash]
|
||||||
|
offsets[cur_hash] = s
|
||||||
|
values[cur_hash] = cur_val
|
||||||
|
|
||||||
|
let offset = s - cand_off
|
||||||
|
if (0 < offset && offset < maxMatchOffset && cur_val == cand_val) {
|
||||||
|
for (;nextEmit<s;nextEmit++) emit(buf[nextEmit])
|
||||||
|
s += 4
|
||||||
|
let t = cand_off + 4
|
||||||
|
let l = 0
|
||||||
|
while (s+l < buf.length && buf[s+l] === buf[t+l] && l<255) l++
|
||||||
|
const l_code = lengthCode[l+1]
|
||||||
|
emit(l_code+257)
|
||||||
|
write(lengthExtra[l_code],l + 1 - lengthBase[l_code],true)
|
||||||
|
|
||||||
|
const d_code = (offset<257) ? distCode[offset-1] : distCode[256+((offset-1)>>7)]
|
||||||
|
write(5,d_code)
|
||||||
|
write(distExtra[d_code],offset-1-distBase[d_code],true)
|
||||||
|
s += l
|
||||||
|
nextEmit = s
|
||||||
|
} else {
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (;nextEmit<buf.length;nextEmit++) emit(buf[nextEmit])
|
||||||
|
emit(256)
|
||||||
|
|
||||||
|
let adler = 1
|
||||||
|
let s2 = 0
|
||||||
|
for (let i=0;i<buf.length;i++) {
|
||||||
|
adler = (adler + buf[i] % 65521)
|
||||||
|
s2 = (s2 + adler) % 65521
|
||||||
|
}
|
||||||
|
adler |= (s2 << 16)
|
||||||
|
let len = 1 + (bitpos >> 3)
|
||||||
|
if (raw) return out.slice(0,len)
|
||||||
|
new DataView(out.buffer).setInt32(len,adler,false)
|
||||||
|
return out.slice(0, len+4)
|
||||||
|
}
|
||||||
@@ -9,7 +9,14 @@ export interface Handle {
|
|||||||
|
|
||||||
interface Process {
|
interface Process {
|
||||||
argv: string[];
|
argv: string[];
|
||||||
|
platform: string;
|
||||||
exit(_: number): void;
|
exit(_: number): void;
|
||||||
|
stdout: {
|
||||||
|
write(s: string): unknown
|
||||||
|
};
|
||||||
|
cwd(): string;
|
||||||
|
env: Record<string,string>
|
||||||
|
__lasterr: {errno: number}
|
||||||
}
|
}
|
||||||
export interface NodeShim {
|
export interface NodeShim {
|
||||||
stdout: string;
|
stdout: string;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ window.addEventListener("message", (ev: MessageEvent<Message>) => {
|
|||||||
let { src } = ev.data;
|
let { src } = ev.data;
|
||||||
try {
|
try {
|
||||||
sendMessage({ type: "setConsole", messages: [] });
|
sendMessage({ type: "setConsole", messages: [] });
|
||||||
eval(src);
|
(new Function(src))();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|||||||
16
playground/src/inflate.test.ts
Normal file
16
playground/src/inflate.test.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { deflate } from "./deflate.ts";
|
||||||
|
import { inflate } from "./inflate.ts";
|
||||||
|
import { b64encode } from "./base64.ts";
|
||||||
|
|
||||||
|
|
||||||
|
test('round trip', ()=>{
|
||||||
|
let src = readFileSync('src/inflate.ts','utf8')
|
||||||
|
let smol = deflate(new TextEncoder().encode(src))
|
||||||
|
let big = inflate(smol)
|
||||||
|
assert.equal(src, new TextDecoder().decode(big))
|
||||||
|
console.log(src.length, smol.length, b64encode(smol).length)
|
||||||
|
})
|
||||||
304
playground/src/inflate.ts
Normal file
304
playground/src/inflate.ts
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
|
||||||
|
// 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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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);
|
||||||
|
if (method !== 8) throw new Error(`method ${method} not handled`)
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
74
playground/src/ipc.ts
Normal file
74
playground/src/ipc.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
export type Result<A> =
|
||||||
|
| { status: "ok"; value: A }
|
||||||
|
| { status: "err"; error: string };
|
||||||
|
|
||||||
|
export interface API {
|
||||||
|
save(fileName: string, content: string): string;
|
||||||
|
typeCheck(fileName: string): string;
|
||||||
|
compile(fileName: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Message<K extends keyof API> {
|
||||||
|
id: number;
|
||||||
|
key: K;
|
||||||
|
args: Parameters<API[K]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseMSG {
|
||||||
|
id: number;
|
||||||
|
result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Suspense = {
|
||||||
|
resolve: (value: any | PromiseLike<any>) => void;
|
||||||
|
reject: (reason?: any) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class IPC {
|
||||||
|
callbacks: Record<number, Suspense> = {};
|
||||||
|
_postMessage: <K extends keyof API>(msg: Message<K>) => void;
|
||||||
|
lastID = 1;
|
||||||
|
constructor() {
|
||||||
|
const newtWorker = new Worker("worker.js");
|
||||||
|
this._postMessage = <K extends keyof API>(msg: Message<K>) =>
|
||||||
|
newtWorker.postMessage(msg);
|
||||||
|
// Safari/MobileSafari have small stacks in webworkers.
|
||||||
|
if (navigator.vendor.includes("Apple")) {
|
||||||
|
const workerFrame = document.createElement("iframe");
|
||||||
|
workerFrame.src = "worker.html";
|
||||||
|
workerFrame.style.display = "none";
|
||||||
|
document.body.appendChild(workerFrame);
|
||||||
|
this._postMessage = (msg: any) =>
|
||||||
|
workerFrame.contentWindow?.postMessage(msg, "*");
|
||||||
|
window.addEventListener("message", (ev) => this.onmessage(ev));
|
||||||
|
} else {
|
||||||
|
newtWorker.onmessage = this.onmessage
|
||||||
|
}
|
||||||
|
// Need to handle messages from the other iframe too? Or at least ignore them.
|
||||||
|
}
|
||||||
|
onmessage = (ev: MessageEvent<ResponseMSG>) => {
|
||||||
|
console.log("GET", ev.data);
|
||||||
|
// Maybe key off of type
|
||||||
|
if (ev.data.id) {
|
||||||
|
let suspense = this.callbacks[ev.data.id];
|
||||||
|
if (suspense) {
|
||||||
|
suspense.resolve(ev.data.result);
|
||||||
|
delete this.callbacks[ev.data.id];
|
||||||
|
}
|
||||||
|
console.log("result", ev.data, "suspense", suspense);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async sendMessage<K extends keyof API>(
|
||||||
|
key: K,
|
||||||
|
args: Parameters<API[K]>
|
||||||
|
): Promise<ReturnType<API[K]>> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let id = this.lastID++;
|
||||||
|
this.callbacks[id] = { resolve, reject };
|
||||||
|
console.log("POST", { id, key, args });
|
||||||
|
this._postMessage({ id, key, args });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPCClient {}
|
||||||
@@ -1,32 +1,59 @@
|
|||||||
import { effect, signal } from "@preact/signals";
|
import { effect, signal } from "@preact/signals";
|
||||||
import { Diagnostic } from "@codemirror/lint";
|
import { Diagnostic } from "@codemirror/lint";
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { h, render } from "preact";
|
import { h, render, VNode } from "preact";
|
||||||
import { ChangeEvent } from "preact/compat";
|
import { ChangeEvent } from "preact/compat";
|
||||||
import { archive, preload } from "./preload.ts";
|
import { archive, preload } from "./preload.ts";
|
||||||
|
import { b64decode, b64encode } from "./base64";
|
||||||
import {
|
import {
|
||||||
AbstractEditor,
|
AbstractEditor,
|
||||||
EditorDelegate,
|
EditorDelegate,
|
||||||
CompileReq,
|
|
||||||
CompileRes,
|
|
||||||
Message,
|
|
||||||
TopData,
|
TopData,
|
||||||
Marker,
|
Marker,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { CMEditor } from "./cmeditor.ts";
|
import { CMEditor } from "./cmeditor.ts";
|
||||||
|
import { deflate } from "./deflate.ts";
|
||||||
|
import { inflate } from "./inflate.ts";
|
||||||
|
import { IPC } from "./ipc.ts";
|
||||||
|
|
||||||
let topData: undefined | TopData;
|
let topData: undefined | TopData;
|
||||||
|
|
||||||
const newtWorker = new Worker("worker.js");
|
const ipc = new IPC();
|
||||||
// :FIXME use because Safari
|
|
||||||
let postMessage = (msg: CompileReq) => newtWorker.postMessage(msg);
|
function mdline2nodes(s: string) {
|
||||||
// Safari/MobileSafari have small stacks in webworkers.
|
let cs: (VNode|string)[] = []
|
||||||
if (navigator.vendor.includes("Apple")) {
|
let toks = s.matchAll(/(\*\*.*?\*\*)|(\*.*?\*)|(_.*?_)|[^*]+|\*/g)
|
||||||
const workerFrame = document.createElement("iframe");
|
for (let tok of toks) {
|
||||||
workerFrame.src = "worker.html";
|
if (tok[1]) cs.push(h('b',{},tok[0].slice(2,-2)))
|
||||||
workerFrame.style.display = "none";
|
else if (tok[2]) cs.push(h('em',{},tok[0].slice(1,-1)))
|
||||||
document.body.appendChild(workerFrame);
|
else if (tok[3]) cs.push(h('em',{},tok[0].slice(1,-1)))
|
||||||
postMessage = (msg: any) => workerFrame.contentWindow?.postMessage(msg, "*");
|
else cs.push(tok[0])
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
|
function md2nodes(md: string) {
|
||||||
|
let rval: VNode[] = []
|
||||||
|
let list: VNode[] | undefined
|
||||||
|
for (let line of md.split("\n")) {
|
||||||
|
if (line.startsWith('- ')) {
|
||||||
|
list = list ?? []
|
||||||
|
list.push(h('li', {}, mdline2nodes(line.slice(2))))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (list) {
|
||||||
|
rval.push(h('ul', {}, list))
|
||||||
|
list = undefined
|
||||||
|
}
|
||||||
|
if (line.startsWith('# ')) {
|
||||||
|
rval.push(h('h2', {}, mdline2nodes(line.slice(2))))
|
||||||
|
} else if (line.startsWith('## ')) {
|
||||||
|
rval.push(h('h3', {}, mdline2nodes(line.slice(3))))
|
||||||
|
} else {
|
||||||
|
rval.push(h('div', {}, mdline2nodes(line)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rval
|
||||||
}
|
}
|
||||||
|
|
||||||
// iframe for running newt output
|
// iframe for running newt output
|
||||||
@@ -35,13 +62,20 @@ iframe.src = "frame.html";
|
|||||||
iframe.style.display = "none";
|
iframe.style.display = "none";
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
function run(src: string) {
|
async function refreshJS() {
|
||||||
console.log("SEND TO", iframe.contentWindow);
|
if (!state.javascript.value) {
|
||||||
const fileName = state.currentFile.value;
|
let src = state.editor.value!.getValue();
|
||||||
// postMessage({ type: "compileRequest", fileName, src });
|
console.log("SEND TO", iframe.contentWindow);
|
||||||
|
const fileName = state.currentFile.value;
|
||||||
|
// maybe send fileName, src?
|
||||||
|
await ipc.sendMessage("save", [fileName, src]);
|
||||||
|
let js = await ipc.sendMessage("compile", [fileName]);
|
||||||
|
state.javascript.value = js;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runOutput() {
|
async function runOutput() {
|
||||||
|
await refreshJS()
|
||||||
const src = state.javascript.value;
|
const src = state.javascript.value;
|
||||||
console.log("RUN", iframe.contentWindow);
|
console.log("RUN", iframe.contentWindow);
|
||||||
try {
|
try {
|
||||||
@@ -68,55 +102,47 @@ function setOutput(output: string) {
|
|||||||
state.output.value = output;
|
state.output.value = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastID = 0;
|
window.addEventListener("message", (ev) => {
|
||||||
const nextID = () => "" + lastID++;
|
if (ev.data.id) return;
|
||||||
|
|
||||||
window.onmessage = (ev: MessageEvent<Message>) => {
|
|
||||||
console.log("window got", ev.data);
|
console.log("window got", ev.data);
|
||||||
if ("messages" in ev.data) state.messages.value = ev.data.messages;
|
if ("messages" in ev.data) state.messages.value = ev.data.messages;
|
||||||
if ("message" in ev.data) {
|
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
|
});
|
||||||
if ("output" in ev.data) {
|
|
||||||
newtWorker.onmessage?.(ev)
|
|
||||||
setOutput(ev.data.output);
|
|
||||||
state.javascript.value = ev.data.javascript;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO wrap up IPC
|
async function copyToClipboard(ev: Event) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let src = state.editor.value!.getValue();
|
||||||
|
let hash = `#code/${b64encode(deflate(new TextEncoder().encode(src)))}`;
|
||||||
|
window.location.hash = hash;
|
||||||
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
|
state.toast.value = "URL copied to clipboard";
|
||||||
|
setTimeout(() => (state.toast.value = ""), 2_000);
|
||||||
|
}
|
||||||
|
|
||||||
type Suspense<T> = {
|
// We could push this into the editor
|
||||||
resolve: (value: T | PromiseLike<T>) => void;
|
document.addEventListener("keydown", (ev) => {
|
||||||
reject: (reason?: any) => void;
|
if ((ev.metaKey || ev.ctrlKey) && ev.code == "KeyS")
|
||||||
};
|
copyToClipboard(ev);
|
||||||
|
});
|
||||||
|
|
||||||
const callbacks: Record<string, Suspense<string>> = {};
|
function getSavedCode() {
|
||||||
|
let value: string = localStorage.idrisCode || LOADING;
|
||||||
newtWorker.onmessage = (ev: MessageEvent<CompileRes>) => {
|
let hash = window.location.hash;
|
||||||
let suspense = callbacks[ev.data.id];
|
if (hash.startsWith("#code/")) {
|
||||||
if (suspense) {
|
try {
|
||||||
suspense.resolve(ev.data.output);
|
value = new TextDecoder().decode(inflate(b64decode(hash.slice(6))));
|
||||||
delete callbacks[ev.data.id];
|
} catch (e) {
|
||||||
}
|
console.error(e);
|
||||||
console.log('result', ev.data, 'suspense', suspense)
|
|
||||||
// FIXME - we want to have the callback take a response for its command
|
|
||||||
setOutput(ev.data.output);
|
|
||||||
state.javascript.value = ev.data.javascript;
|
|
||||||
};
|
|
||||||
|
|
||||||
function runCommand(req: CompileReq) {
|
|
||||||
return new Promise<string>(
|
|
||||||
(resolve, reject) => {
|
|
||||||
callbacks[req.id] = { resolve, reject }
|
|
||||||
postMessage(req);
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
output: signal(""),
|
output: signal(""),
|
||||||
|
toast: signal(""),
|
||||||
javascript: signal(""),
|
javascript: signal(""),
|
||||||
messages: signal<string[]>([]),
|
messages: signal<string[]>([]),
|
||||||
editor: signal<AbstractEditor | null>(null),
|
editor: signal<AbstractEditor | null>(null),
|
||||||
@@ -166,7 +192,7 @@ document.addEventListener("keydown", (ev) => {
|
|||||||
|
|
||||||
const LOADING = "module Loading\n";
|
const LOADING = "module Loading\n";
|
||||||
|
|
||||||
let value = localStorage.code || LOADING;
|
let value = getSavedCode() || LOADING;
|
||||||
let initialVertical = localStorage.vertical == "true";
|
let initialVertical = localStorage.vertical == "true";
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
@@ -176,13 +202,8 @@ const language: EditorDelegate = {
|
|||||||
getEntry(word, _row, _col) {
|
getEntry(word, _row, _col) {
|
||||||
return topData?.context.find((entry) => entry.name === word);
|
return topData?.context.find((entry) => entry.name === word);
|
||||||
},
|
},
|
||||||
onChange(value) {
|
onChange(_value) {
|
||||||
// run via the linter now
|
// we're using lint() now
|
||||||
// clearTimeout(timeout);
|
|
||||||
// timeout = setTimeout(() => {
|
|
||||||
// run(value);
|
|
||||||
// localStorage.code = value;
|
|
||||||
// }, 1000);
|
|
||||||
},
|
},
|
||||||
getFileName() {
|
getFileName() {
|
||||||
if (!topData) return "";
|
if (!topData) return "";
|
||||||
@@ -192,39 +213,35 @@ const language: EditorDelegate = {
|
|||||||
async lint(view) {
|
async lint(view) {
|
||||||
console.log("LINT");
|
console.log("LINT");
|
||||||
let src = view.state.doc.toString();
|
let src = view.state.doc.toString();
|
||||||
localStorage.code = src
|
localStorage.code = src;
|
||||||
let module = src.match(/module\s+([^\s]+)/)?.[1]
|
let module = src.match(/module\s+([^\s]+)/)?.[1];
|
||||||
if (module) {
|
if (module) {
|
||||||
// This causes problems with stuff like aoc/... that reference files in the same directory
|
// This causes problems with stuff like aoc/...
|
||||||
// state.currentFile.value = module.replace('.','/')+'.newt'
|
state.currentFile.value = module.replace(".", "/") + ".newt";
|
||||||
}
|
}
|
||||||
|
state.javascript.value = ''
|
||||||
let fileName = state.currentFile.value;
|
let fileName = state.currentFile.value;
|
||||||
console.log("FN", fileName);
|
console.log("FN", fileName);
|
||||||
try {
|
try {
|
||||||
let out = await runCommand({
|
await ipc.sendMessage("save", [fileName, src]);
|
||||||
id: nextID(),
|
let out = await ipc.sendMessage("typeCheck", [fileName]);
|
||||||
type: "compileRequest",
|
setOutput(out);
|
||||||
fileName,
|
|
||||||
src,
|
|
||||||
compile: false,
|
|
||||||
});
|
|
||||||
console.log("OUT", out);
|
|
||||||
let markers = processOutput(out);
|
let markers = processOutput(out);
|
||||||
let diags: Diagnostic[] = []
|
let diags: Diagnostic[] = [];
|
||||||
for (let marker of markers) {
|
for (let marker of markers) {
|
||||||
let col = marker.startColumn
|
let col = marker.startColumn;
|
||||||
|
|
||||||
let line = view.state.doc.line(marker.startLineNumber)
|
let line = view.state.doc.line(marker.startLineNumber);
|
||||||
const pos = line.from + col - 1;
|
const pos = line.from + col - 1;
|
||||||
let word = view.state.wordAt(pos)
|
let word = view.state.wordAt(pos);
|
||||||
diags.push({
|
diags.push({
|
||||||
from: word?.from ?? pos,
|
from: word?.from ?? pos,
|
||||||
to: word?.to ?? pos+1,
|
to: word?.to ?? pos + 1,
|
||||||
severity: marker.severity,
|
severity: marker.severity,
|
||||||
message: marker.message,
|
message: marker.message,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return diags
|
return diags;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("ERR", e);
|
console.log("ERR", e);
|
||||||
}
|
}
|
||||||
@@ -243,7 +260,6 @@ function Editor({ initialValue }: EditorProps) {
|
|||||||
state.editor.value = editor;
|
state.editor.value = editor;
|
||||||
editor.setDark(state.dark.value);
|
editor.setDark(state.dark.value);
|
||||||
if (initialValue === LOADING) loadFile("Tour.newt");
|
if (initialValue === LOADING) loadFile("Tour.newt");
|
||||||
else run(initialValue);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return h("div", { id: "editor", ref });
|
return h("div", { id: "editor", ref });
|
||||||
@@ -260,6 +276,38 @@ function Result() {
|
|||||||
return h("div", { id: "result" }, text);
|
return h("div", { id: "result" }, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Help() {
|
||||||
|
return h("div", { id: "help" },
|
||||||
|
md2nodes(`
|
||||||
|
# Newt Playground
|
||||||
|
|
||||||
|
The editor will typecheck the file with newt and render errors as the file is changed. The current file is saved to localStorage and will be restored if there is no data in the URL. Cmd-s / Ctrl-s will create a url embedding the file contents. There is a layout toggle for phone use.
|
||||||
|
|
||||||
|
## Tabs
|
||||||
|
|
||||||
|
**Output** - Displays the compiler output, which is also used to render errors and info annotations in the editor.
|
||||||
|
|
||||||
|
**JS** - Displays the javascript translation of the file
|
||||||
|
|
||||||
|
**Console** - Displays the console output from running the javascript
|
||||||
|
|
||||||
|
**Help** - Displays this help file
|
||||||
|
|
||||||
|
## Buttons
|
||||||
|
|
||||||
|
▶ Compile and run the current file in an iframe, console output is collected to the console tab.
|
||||||
|
|
||||||
|
📋 Embed the current file in the URL and copy to clipboard
|
||||||
|
|
||||||
|
↕ or ↔ Toggle vertical or horziontal layout (for mobile)
|
||||||
|
|
||||||
|
## Keyboard
|
||||||
|
|
||||||
|
*C-s or M-s* - Embed the current file in the URL and copy to clipboard
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function Console() {
|
function Console() {
|
||||||
const messages = state.messages.value ?? [];
|
const messages = state.messages.value ?? [];
|
||||||
return h(
|
return h(
|
||||||
@@ -272,6 +320,7 @@ function Console() {
|
|||||||
const RESULTS = "Output";
|
const RESULTS = "Output";
|
||||||
const JAVASCRIPT = "JS";
|
const JAVASCRIPT = "JS";
|
||||||
const CONSOLE = "Console";
|
const CONSOLE = "Console";
|
||||||
|
const HELP = "Help";
|
||||||
|
|
||||||
function Tabs() {
|
function Tabs() {
|
||||||
const [selected, setSelected] = useState(localStorage.tab ?? RESULTS);
|
const [selected, setSelected] = useState(localStorage.tab ?? RESULTS);
|
||||||
@@ -289,6 +338,10 @@ function Tabs() {
|
|||||||
if (state.messages.value.length) setSelected(CONSOLE);
|
if (state.messages.value.length) setSelected(CONSOLE);
|
||||||
}, [state.messages.value]);
|
}, [state.messages.value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selected === JAVASCRIPT && !state.javascript.value) refreshJS();
|
||||||
|
}, [selected, state.javascript.value]);
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
switch (selected) {
|
switch (selected) {
|
||||||
case RESULTS:
|
case RESULTS:
|
||||||
@@ -300,6 +353,9 @@ function Tabs() {
|
|||||||
case CONSOLE:
|
case CONSOLE:
|
||||||
body = h(Console, {});
|
body = h(Console, {});
|
||||||
break;
|
break;
|
||||||
|
case HELP:
|
||||||
|
body = h(Help, {});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
body = h("div", {});
|
body = h("div", {});
|
||||||
}
|
}
|
||||||
@@ -312,7 +368,8 @@ function Tabs() {
|
|||||||
{ className: "tabBar" },
|
{ className: "tabBar" },
|
||||||
Tab(RESULTS),
|
Tab(RESULTS),
|
||||||
Tab(JAVASCRIPT),
|
Tab(JAVASCRIPT),
|
||||||
Tab(CONSOLE)
|
Tab(CONSOLE),
|
||||||
|
Tab(HELP),
|
||||||
),
|
),
|
||||||
h("div", { className: "tabBody" }, body)
|
h("div", { className: "tabBody" }, body)
|
||||||
);
|
);
|
||||||
@@ -348,12 +405,15 @@ function EditWrap({
|
|||||||
loadFile(fn);
|
loadFile(fn);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let d = vertical
|
|
||||||
? "M0 0 h20 v20 h-20 z M0 10 h20"
|
const char = vertical ? "↕" : "↔"
|
||||||
: "M0 0 h20 v20 h-20 z M10 0 v20";
|
|
||||||
let play = "M0 0 L20 10 L0 20 z";
|
// let d = vertical
|
||||||
let svg = (d: string) =>
|
// ? "M0 0 h20 v20 h-20 z M0 10 h20"
|
||||||
h("svg", { width: 20, height: 20, className: "icon" }, h("path", { d }));
|
// : "M0 0 h20 v20 h-20 z M10 0 v20";
|
||||||
|
// let play = "M0 0 L20 10 L0 20 z";
|
||||||
|
// let svg = (d: string) =>
|
||||||
|
// h("svg", { width: 20, height: 20, className: "icon" }, h("path", { d }));
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "tabPanel left" },
|
{ className: "tabPanel left" },
|
||||||
@@ -366,8 +426,10 @@ function EditWrap({
|
|||||||
options
|
options
|
||||||
),
|
),
|
||||||
h("div", { style: { flex: "1 1" } }),
|
h("div", { style: { flex: "1 1" } }),
|
||||||
h("button", { onClick: runOutput }, svg(play)),
|
h("button", { onClick: copyToClipboard, title: "copy url" }, "📋"),
|
||||||
h("button", { onClick: toggle }, svg(d))
|
h("button", { onClick: runOutput, title: "run program" }, "▶"),
|
||||||
|
h("button", { onClick: toggle, title: "change layout" }, char),
|
||||||
|
// h("button", { onClick: toggle }, svg(d))
|
||||||
),
|
),
|
||||||
h("div", { className: "tabBody" }, h(Editor, { initialValue: value }))
|
h("div", { className: "tabBody" }, h(Editor, { initialValue: value }))
|
||||||
);
|
);
|
||||||
@@ -379,10 +441,15 @@ function App() {
|
|||||||
setVertical(!vertical);
|
setVertical(!vertical);
|
||||||
localStorage.vertical = !vertical;
|
localStorage.vertical = !vertical;
|
||||||
};
|
};
|
||||||
|
let toast;
|
||||||
|
if (state.toast.value) {
|
||||||
|
toast = h("p", { className: "toast" }, h("div", {}, state.toast.value));
|
||||||
|
}
|
||||||
let className = `wrapper ${vertical ? "vertical" : "horizontal"}`;
|
let className = `wrapper ${vertical ? "vertical" : "horizontal"}`;
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className },
|
{ className },
|
||||||
|
toast,
|
||||||
h(EditWrap, { vertical, toggle }),
|
h(EditWrap, { vertical, toggle }),
|
||||||
h(Tabs, {})
|
h(Tabs, {})
|
||||||
);
|
);
|
||||||
@@ -390,16 +457,10 @@ function App() {
|
|||||||
|
|
||||||
render(h(App, {}), document.getElementById("app")!);
|
render(h(App, {}), document.getElementById("app")!);
|
||||||
|
|
||||||
let timeout: number | undefined;
|
|
||||||
|
|
||||||
// Adapted from the vscode extension, but types are slightly different
|
|
||||||
// and positions are 1-based.
|
|
||||||
const processOutput = (
|
const processOutput = (
|
||||||
// editor: AbstractEditor,
|
|
||||||
output: string
|
output: string
|
||||||
) => {
|
) => {
|
||||||
// let model = editor.getModel()!;
|
console.log("process output", output);
|
||||||
console.log('process output', output)
|
|
||||||
let markers: Marker[] = [];
|
let markers: Marker[] = [];
|
||||||
let lines = output.split("\n");
|
let lines = output.split("\n");
|
||||||
let m = lines[0].match(/.*Process (.*)/);
|
let m = lines[0].match(/.*Process (.*)/);
|
||||||
@@ -407,7 +468,9 @@ const processOutput = (
|
|||||||
if (m) fn = m[1];
|
if (m) fn = m[1];
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
const match = line.match(/(INFO|ERROR) at ([^:]+):\((\d+), (\d+)\):\s*(.*)/);
|
const match = line.match(
|
||||||
|
/(INFO|ERROR) at ([^:]+):\((\d+), (\d+)\):\s*(.*)/
|
||||||
|
);
|
||||||
if (match) {
|
if (match) {
|
||||||
let [_full, kind, file, line, col, message] = match;
|
let [_full, kind, file, line, col, message] = match;
|
||||||
let lineNumber = +line + 1;
|
let lineNumber = +line + 1;
|
||||||
@@ -428,7 +491,7 @@ const processOutput = (
|
|||||||
}
|
}
|
||||||
if (kind === "ERROR" || lineNumber)
|
if (kind === "ERROR" || lineNumber)
|
||||||
markers.push({
|
markers.push({
|
||||||
severity: kind === 'ERROR' ? 'error' : 'info',
|
severity: kind === "ERROR" ? "error" : "info",
|
||||||
message,
|
message,
|
||||||
startLineNumber: lineNumber,
|
startLineNumber: lineNumber,
|
||||||
endLineNumber: lineNumber,
|
endLineNumber: lineNumber,
|
||||||
@@ -437,7 +500,7 @@ const processOutput = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('markers', markers)
|
console.log("markers", markers);
|
||||||
// editor.setMarkers(markers)
|
// editor.setMarkers(markers)
|
||||||
return markers;
|
return markers;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { EditorView } from "codemirror";
|
import { EditorView } from "codemirror";
|
||||||
import { linter, Diagnostic } from "@codemirror/lint";
|
import { linter, Diagnostic } from "@codemirror/lint";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface CompileReq {
|
export interface CompileReq {
|
||||||
id: string
|
id: string
|
||||||
type: "compileRequest";
|
type: "compileRequest";
|
||||||
|
|||||||
@@ -1,41 +1,50 @@
|
|||||||
import { shim } from "./emul";
|
import { shim } from "./emul";
|
||||||
|
import { API, Message, ResponseMSG } from "./ipc";
|
||||||
import { archive, preload } from "./preload";
|
import { archive, preload } from "./preload";
|
||||||
import { CompileReq, CompileRes } from "./types";
|
|
||||||
|
|
||||||
|
const LOG = console.log
|
||||||
console.log = (m) => {
|
console.log = (m) => {
|
||||||
shim.stdout += "\n" + m;
|
shim.stdout += "\n" + m;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMessage = async function (ev: { data: CompileReq }) {
|
const handleMessage = async function <K extends keyof API>(ev: { data: Message<K> }) {
|
||||||
console.log("message", ev.data);
|
LOG("HANDLE", ev.data);
|
||||||
await preload;
|
await preload;
|
||||||
shim.archive = archive;
|
shim.archive = archive;
|
||||||
let { id, src, fileName } = ev.data;
|
let key = ev.data.key
|
||||||
const outfile = "out.js";
|
if (key === 'typeCheck' || key === 'compile') {
|
||||||
if (ev.data.compile)
|
let {id, args: [fileName]} = ev.data
|
||||||
shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"];
|
LOG(key, fileName)
|
||||||
else
|
const outfile = "out.js";
|
||||||
shim.process.argv = ["browser", "newt", fileName, "--top"];
|
const isCompile = key === 'compile';
|
||||||
shim.files[fileName] = new TextEncoder().encode(src);
|
if (isCompile)
|
||||||
shim.files[outfile] = new TextEncoder().encode("No JS output");
|
shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"];
|
||||||
shim.stdout = "";
|
else
|
||||||
const start = +new Date();
|
shim.process.argv = ["browser", "newt", fileName, "--top"];
|
||||||
try {
|
shim.stdout = "";
|
||||||
Main_main();
|
shim.files[outfile] = new TextEncoder().encode("No JS output");
|
||||||
} catch (e) {
|
|
||||||
// make it clickable in console
|
try {
|
||||||
console.error(e);
|
Main_main();
|
||||||
// make it visable on page
|
} catch (e) {
|
||||||
shim.stdout += "\n" + String(e);
|
// make it clickable in console
|
||||||
|
console.error(e);
|
||||||
|
// make it visable on page
|
||||||
|
shim.stdout += "\n" + String(e);
|
||||||
|
}
|
||||||
|
let result = isCompile ? new TextDecoder().decode(shim.files[outfile]) : shim.stdout
|
||||||
|
sendResponse({id, result})
|
||||||
|
} else if (key === 'save') {
|
||||||
|
let {id, args: [fileName, content]} = ev.data
|
||||||
|
LOG(`SAVE ${content?.length} to ${fileName}`)
|
||||||
|
shim.files[fileName] = new TextEncoder().encode(content)
|
||||||
|
LOG('send', {id, result: ''})
|
||||||
|
sendResponse({id, result: ''})
|
||||||
}
|
}
|
||||||
let duration = +new Date() - start;
|
|
||||||
console.log(`process ${fileName} in ${duration} ms`);
|
|
||||||
let javascript = new TextDecoder().decode(shim.files[outfile]);
|
|
||||||
let output = shim.stdout;
|
|
||||||
sendResponse({ id, type: "compileResult", javascript, output, duration });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// hooks for worker.html to override
|
// hooks for worker.html to override
|
||||||
let sendResponse: (_: CompileRes) => void = postMessage;
|
let sendResponse: <K extends keyof API>(_: ResponseMSG) => void = postMessage;
|
||||||
onmessage = handleMessage;
|
onmessage = handleMessage;
|
||||||
importScripts("newt.js");
|
importScripts("newt.js");
|
||||||
|
|||||||
@@ -1,269 +1,11 @@
|
|||||||
|
import { inflate } from "./inflate";
|
||||||
|
|
||||||
// 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 {
|
interface Entry {
|
||||||
size: number;
|
size: number;
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
|
method: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ZipFile {
|
export class ZipFile {
|
||||||
@@ -292,13 +34,12 @@ export class ZipFile {
|
|||||||
if (size) {
|
if (size) {
|
||||||
let start = pos + 30 + fnlen + eflen;
|
let start = pos + 30 + fnlen + eflen;
|
||||||
let end = start + csize;
|
let end = start + csize;
|
||||||
this.entries[fn] = { size, start, end };
|
this.entries[fn] = { size, start, end, method };
|
||||||
}
|
}
|
||||||
pos = pos + 30 + fnlen + eflen + csize;
|
pos = pos + 30 + fnlen + eflen + csize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getData(name: string) {
|
getData(name: string) {
|
||||||
if (!(name in this.entries)) return
|
|
||||||
let { start, end, size } = this.entries[name];
|
let { start, end, size } = this.entries[name];
|
||||||
return inflate(new Uint8Array(this.data.slice(start, end)));
|
return inflate(new Uint8Array(this.data.slice(start, end)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,31 @@ svg.icon path {
|
|||||||
fill: none;
|
fill: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#help {
|
||||||
|
padding: 5px;
|
||||||
|
font-family: 'Comic Code', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help>div {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50px;
|
||||||
|
width: auto;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.toast div {
|
||||||
|
margin-left: -50%;
|
||||||
|
border: solid 2px darkgrey;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
color: white;
|
color: white;
|
||||||
@@ -60,6 +85,7 @@ svg.icon path {
|
|||||||
.tabPanel {
|
.tabPanel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
font-family: 'Comic Code', monospace;
|
||||||
}
|
}
|
||||||
.tabBar {
|
.tabBar {
|
||||||
display:flex;
|
display:flex;
|
||||||
@@ -93,3 +119,6 @@ svg.icon path {
|
|||||||
.tooltip {
|
.tooltip {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
.cm-editor .cm-content {
|
||||||
|
font-family: 'Comic Code', monospace;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user