From bb2ae861b37912a97a9c3a0e6dc2dfb5651f133c Mon Sep 17 00:00:00 2001 From: Steve Dunham Date: Tue, 15 Jul 2025 19:58:58 -0400 Subject: [PATCH] Playground enhancements --- playground/TODO.md | 7 + playground/samples/aoc2024/Aoc.newt | 1 - playground/samples/aoc2024/Day1.newt | 1 - playground/samples/aoc2024/Day10.newt | 1 - playground/samples/aoc2024/Day11.newt | 1 - playground/samples/aoc2024/Day11b.newt | 1 - playground/samples/aoc2024/Day12.newt | 1 - playground/samples/aoc2024/Day13.newt | 1 - playground/samples/aoc2024/Day14.newt | 1 - playground/samples/aoc2024/Day15.newt | 1 - playground/samples/aoc2024/Day16.newt | 1 - playground/samples/aoc2024/Day17.newt | 1 - playground/samples/aoc2024/Day18.newt | 1 - playground/samples/aoc2024/Day19.newt | 1 - playground/samples/aoc2024/Day2.newt | 1 - playground/samples/aoc2024/Day20.newt | 1 - playground/samples/aoc2024/Day21.newt | 1 - playground/samples/aoc2024/Day22.newt | 1 - playground/samples/aoc2024/Day23.newt | 1 - playground/samples/aoc2024/Day24.newt | 1 - playground/samples/aoc2024/Day25.newt | 1 - playground/samples/aoc2024/Day3.newt | 1 - playground/samples/aoc2024/Day4.newt | 1 - playground/samples/aoc2024/Day5.newt | 1 - playground/samples/aoc2024/Day6.newt | 1 - playground/samples/aoc2024/Day7.newt | 1 - playground/samples/aoc2024/Day8.newt | 1 - playground/samples/aoc2024/Day9.newt | 1 - playground/samples/aoc2024/DayXX.newt | 1 - playground/samples/aoc2024/Node.newt | 1 - playground/samples/aoc2024/Parser.newt | 1 - playground/samples/aoc2024/Prelude.newt | 1 - playground/samples/aoc2024/SortedMap.newt | 1 - playground/samples/aoc2024/day1 | 1 - playground/samples/aoc2024/day10 | 1 - playground/samples/aoc2024/day11 | 1 - playground/samples/aoc2024/day12 | 1 - playground/samples/aoc2024/day13 | 1 - playground/samples/aoc2024/day14 | 1 - playground/samples/aoc2024/day15 | 1 - playground/samples/aoc2024/day16 | 1 - playground/samples/aoc2024/day17 | 1 - playground/samples/aoc2024/day18 | 1 - playground/samples/aoc2024/day19 | 1 - playground/samples/aoc2024/day2 | 1 - playground/samples/aoc2024/day20 | 1 - playground/samples/aoc2024/day21 | 1 - playground/samples/aoc2024/day22 | 1 - playground/samples/aoc2024/day23 | 1 - playground/samples/aoc2024/day24 | 1 - playground/samples/aoc2024/day25 | 1 - playground/samples/aoc2024/day3 | 1 - playground/samples/aoc2024/day4 | 1 - playground/samples/aoc2024/day5 | 1 - playground/samples/aoc2024/day6 | 1 - playground/samples/aoc2024/day7 | 1 - playground/samples/aoc2024/day8 | 1 - playground/samples/aoc2024/day9 | 1 - playground/samples/newt | 1 - playground/src/base64.test.ts | 14 + playground/src/base64.ts | 45 ++++ playground/src/cmeditor.ts | 3 +- playground/src/deflate.ts | 131 ++++++++++ playground/src/emul.ts | 7 + playground/src/frame.ts | 2 +- playground/src/inflate.test.ts | 16 ++ playground/src/inflate.ts | 304 ++++++++++++++++++++++ playground/src/ipc.ts | 74 ++++++ playground/src/main.ts | 267 +++++++++++-------- playground/src/types.ts | 2 + playground/src/worker.ts | 61 +++-- playground/src/zipfile.ts | 265 +------------------ playground/style.css | 29 +++ 73 files changed, 834 insertions(+), 451 deletions(-) delete mode 120000 playground/samples/aoc2024/Aoc.newt delete mode 120000 playground/samples/aoc2024/Day1.newt delete mode 120000 playground/samples/aoc2024/Day10.newt delete mode 120000 playground/samples/aoc2024/Day11.newt delete mode 120000 playground/samples/aoc2024/Day11b.newt delete mode 120000 playground/samples/aoc2024/Day12.newt delete mode 120000 playground/samples/aoc2024/Day13.newt delete mode 120000 playground/samples/aoc2024/Day14.newt delete mode 120000 playground/samples/aoc2024/Day15.newt delete mode 120000 playground/samples/aoc2024/Day16.newt delete mode 120000 playground/samples/aoc2024/Day17.newt delete mode 120000 playground/samples/aoc2024/Day18.newt delete mode 120000 playground/samples/aoc2024/Day19.newt delete mode 120000 playground/samples/aoc2024/Day2.newt delete mode 120000 playground/samples/aoc2024/Day20.newt delete mode 120000 playground/samples/aoc2024/Day21.newt delete mode 120000 playground/samples/aoc2024/Day22.newt delete mode 120000 playground/samples/aoc2024/Day23.newt delete mode 120000 playground/samples/aoc2024/Day24.newt delete mode 120000 playground/samples/aoc2024/Day25.newt delete mode 120000 playground/samples/aoc2024/Day3.newt delete mode 120000 playground/samples/aoc2024/Day4.newt delete mode 120000 playground/samples/aoc2024/Day5.newt delete mode 120000 playground/samples/aoc2024/Day6.newt delete mode 120000 playground/samples/aoc2024/Day7.newt delete mode 120000 playground/samples/aoc2024/Day8.newt delete mode 120000 playground/samples/aoc2024/Day9.newt delete mode 120000 playground/samples/aoc2024/DayXX.newt delete mode 120000 playground/samples/aoc2024/Node.newt delete mode 120000 playground/samples/aoc2024/Parser.newt delete mode 120000 playground/samples/aoc2024/Prelude.newt delete mode 120000 playground/samples/aoc2024/SortedMap.newt delete mode 120000 playground/samples/aoc2024/day1 delete mode 120000 playground/samples/aoc2024/day10 delete mode 120000 playground/samples/aoc2024/day11 delete mode 120000 playground/samples/aoc2024/day12 delete mode 120000 playground/samples/aoc2024/day13 delete mode 120000 playground/samples/aoc2024/day14 delete mode 120000 playground/samples/aoc2024/day15 delete mode 120000 playground/samples/aoc2024/day16 delete mode 120000 playground/samples/aoc2024/day17 delete mode 120000 playground/samples/aoc2024/day18 delete mode 120000 playground/samples/aoc2024/day19 delete mode 120000 playground/samples/aoc2024/day2 delete mode 120000 playground/samples/aoc2024/day20 delete mode 120000 playground/samples/aoc2024/day21 delete mode 120000 playground/samples/aoc2024/day22 delete mode 120000 playground/samples/aoc2024/day23 delete mode 120000 playground/samples/aoc2024/day24 delete mode 120000 playground/samples/aoc2024/day25 delete mode 120000 playground/samples/aoc2024/day3 delete mode 120000 playground/samples/aoc2024/day4 delete mode 120000 playground/samples/aoc2024/day5 delete mode 120000 playground/samples/aoc2024/day6 delete mode 120000 playground/samples/aoc2024/day7 delete mode 120000 playground/samples/aoc2024/day8 delete mode 120000 playground/samples/aoc2024/day9 delete mode 120000 playground/samples/newt create mode 100644 playground/src/base64.test.ts create mode 100644 playground/src/base64.ts create mode 100644 playground/src/deflate.ts create mode 100644 playground/src/inflate.test.ts create mode 100644 playground/src/inflate.ts create mode 100644 playground/src/ipc.ts diff --git a/playground/TODO.md b/playground/TODO.md index e83ce45..31d61b9 100644 --- a/playground/TODO.md +++ b/playground/TODO.md @@ -2,8 +2,11 @@ ## Todo items for the playground - [x] sample files +- [x] codemirror migration - [ ] make sample files available for import - 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] tabs for source, compiler output - [x] Show errors in editor @@ -13,3 +16,7 @@ - [x] publish / host on github - [ ] multiple persistent files - [x] kill return for autocomplete +- [x] save to url (copy from idris2-playground) +- [ ] click on url +- [ ] settings + diff --git a/playground/samples/aoc2024/Aoc.newt b/playground/samples/aoc2024/Aoc.newt deleted file mode 120000 index 61f3831..0000000 --- a/playground/samples/aoc2024/Aoc.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2023/Aoc.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day1.newt b/playground/samples/aoc2024/Day1.newt deleted file mode 120000 index 984888a..0000000 --- a/playground/samples/aoc2024/Day1.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day1.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day10.newt b/playground/samples/aoc2024/Day10.newt deleted file mode 120000 index 8b38a66..0000000 --- a/playground/samples/aoc2024/Day10.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day10.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day11.newt b/playground/samples/aoc2024/Day11.newt deleted file mode 120000 index 86589a0..0000000 --- a/playground/samples/aoc2024/Day11.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day11.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day11b.newt b/playground/samples/aoc2024/Day11b.newt deleted file mode 120000 index f9c395f..0000000 --- a/playground/samples/aoc2024/Day11b.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day11b.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day12.newt b/playground/samples/aoc2024/Day12.newt deleted file mode 120000 index da889f7..0000000 --- a/playground/samples/aoc2024/Day12.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day12.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day13.newt b/playground/samples/aoc2024/Day13.newt deleted file mode 120000 index 3dab26b..0000000 --- a/playground/samples/aoc2024/Day13.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day13.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day14.newt b/playground/samples/aoc2024/Day14.newt deleted file mode 120000 index 1bfd2d0..0000000 --- a/playground/samples/aoc2024/Day14.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day14.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day15.newt b/playground/samples/aoc2024/Day15.newt deleted file mode 120000 index c25d325..0000000 --- a/playground/samples/aoc2024/Day15.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day15.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day16.newt b/playground/samples/aoc2024/Day16.newt deleted file mode 120000 index 218863b..0000000 --- a/playground/samples/aoc2024/Day16.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day16.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day17.newt b/playground/samples/aoc2024/Day17.newt deleted file mode 120000 index a935098..0000000 --- a/playground/samples/aoc2024/Day17.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day17.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day18.newt b/playground/samples/aoc2024/Day18.newt deleted file mode 120000 index 95aec99..0000000 --- a/playground/samples/aoc2024/Day18.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day18.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day19.newt b/playground/samples/aoc2024/Day19.newt deleted file mode 120000 index 75c3ce2..0000000 --- a/playground/samples/aoc2024/Day19.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day19.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day2.newt b/playground/samples/aoc2024/Day2.newt deleted file mode 120000 index fbe220e..0000000 --- a/playground/samples/aoc2024/Day2.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day2.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day20.newt b/playground/samples/aoc2024/Day20.newt deleted file mode 120000 index 2dd7a7c..0000000 --- a/playground/samples/aoc2024/Day20.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day20.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day21.newt b/playground/samples/aoc2024/Day21.newt deleted file mode 120000 index ead4a42..0000000 --- a/playground/samples/aoc2024/Day21.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day21.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day22.newt b/playground/samples/aoc2024/Day22.newt deleted file mode 120000 index 4236e61..0000000 --- a/playground/samples/aoc2024/Day22.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day22.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day23.newt b/playground/samples/aoc2024/Day23.newt deleted file mode 120000 index d234e7f..0000000 --- a/playground/samples/aoc2024/Day23.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day23.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day24.newt b/playground/samples/aoc2024/Day24.newt deleted file mode 120000 index 6376d83..0000000 --- a/playground/samples/aoc2024/Day24.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day24.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day25.newt b/playground/samples/aoc2024/Day25.newt deleted file mode 120000 index 022657f..0000000 --- a/playground/samples/aoc2024/Day25.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day25.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day3.newt b/playground/samples/aoc2024/Day3.newt deleted file mode 120000 index 6aeebee..0000000 --- a/playground/samples/aoc2024/Day3.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day3.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day4.newt b/playground/samples/aoc2024/Day4.newt deleted file mode 120000 index 46fd6cf..0000000 --- a/playground/samples/aoc2024/Day4.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day4.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day5.newt b/playground/samples/aoc2024/Day5.newt deleted file mode 120000 index 609fcbf..0000000 --- a/playground/samples/aoc2024/Day5.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day5.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day6.newt b/playground/samples/aoc2024/Day6.newt deleted file mode 120000 index aab08c0..0000000 --- a/playground/samples/aoc2024/Day6.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day6.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day7.newt b/playground/samples/aoc2024/Day7.newt deleted file mode 120000 index f4ec895..0000000 --- a/playground/samples/aoc2024/Day7.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day7.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day8.newt b/playground/samples/aoc2024/Day8.newt deleted file mode 120000 index b6520de..0000000 --- a/playground/samples/aoc2024/Day8.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day8.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Day9.newt b/playground/samples/aoc2024/Day9.newt deleted file mode 120000 index 0563fc0..0000000 --- a/playground/samples/aoc2024/Day9.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Day9.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/DayXX.newt b/playground/samples/aoc2024/DayXX.newt deleted file mode 120000 index 2837f25..0000000 --- a/playground/samples/aoc2024/DayXX.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/DayXX.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Node.newt b/playground/samples/aoc2024/Node.newt deleted file mode 120000 index 1e033da..0000000 --- a/playground/samples/aoc2024/Node.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Node.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Parser.newt b/playground/samples/aoc2024/Parser.newt deleted file mode 120000 index 33e3852..0000000 --- a/playground/samples/aoc2024/Parser.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Parser.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/Prelude.newt b/playground/samples/aoc2024/Prelude.newt deleted file mode 120000 index 2fa97af..0000000 --- a/playground/samples/aoc2024/Prelude.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/Prelude.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/SortedMap.newt b/playground/samples/aoc2024/SortedMap.newt deleted file mode 120000 index 9cea5d0..0000000 --- a/playground/samples/aoc2024/SortedMap.newt +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/SortedMap.newt \ No newline at end of file diff --git a/playground/samples/aoc2024/day1 b/playground/samples/aoc2024/day1 deleted file mode 120000 index 91e1bf3..0000000 --- a/playground/samples/aoc2024/day1 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day1 \ No newline at end of file diff --git a/playground/samples/aoc2024/day10 b/playground/samples/aoc2024/day10 deleted file mode 120000 index 26a37c8..0000000 --- a/playground/samples/aoc2024/day10 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day10 \ No newline at end of file diff --git a/playground/samples/aoc2024/day11 b/playground/samples/aoc2024/day11 deleted file mode 120000 index aee1604..0000000 --- a/playground/samples/aoc2024/day11 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day11 \ No newline at end of file diff --git a/playground/samples/aoc2024/day12 b/playground/samples/aoc2024/day12 deleted file mode 120000 index 6cf3270..0000000 --- a/playground/samples/aoc2024/day12 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day12 \ No newline at end of file diff --git a/playground/samples/aoc2024/day13 b/playground/samples/aoc2024/day13 deleted file mode 120000 index b033837..0000000 --- a/playground/samples/aoc2024/day13 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day13 \ No newline at end of file diff --git a/playground/samples/aoc2024/day14 b/playground/samples/aoc2024/day14 deleted file mode 120000 index 9c0aceb..0000000 --- a/playground/samples/aoc2024/day14 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day14 \ No newline at end of file diff --git a/playground/samples/aoc2024/day15 b/playground/samples/aoc2024/day15 deleted file mode 120000 index 1ca0701..0000000 --- a/playground/samples/aoc2024/day15 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day15 \ No newline at end of file diff --git a/playground/samples/aoc2024/day16 b/playground/samples/aoc2024/day16 deleted file mode 120000 index f91927f..0000000 --- a/playground/samples/aoc2024/day16 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day16 \ No newline at end of file diff --git a/playground/samples/aoc2024/day17 b/playground/samples/aoc2024/day17 deleted file mode 120000 index 6390349..0000000 --- a/playground/samples/aoc2024/day17 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day17 \ No newline at end of file diff --git a/playground/samples/aoc2024/day18 b/playground/samples/aoc2024/day18 deleted file mode 120000 index a8bd6f5..0000000 --- a/playground/samples/aoc2024/day18 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day18 \ No newline at end of file diff --git a/playground/samples/aoc2024/day19 b/playground/samples/aoc2024/day19 deleted file mode 120000 index 1a9b9a4..0000000 --- a/playground/samples/aoc2024/day19 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day19 \ No newline at end of file diff --git a/playground/samples/aoc2024/day2 b/playground/samples/aoc2024/day2 deleted file mode 120000 index aae6369..0000000 --- a/playground/samples/aoc2024/day2 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day2 \ No newline at end of file diff --git a/playground/samples/aoc2024/day20 b/playground/samples/aoc2024/day20 deleted file mode 120000 index 40daf2c..0000000 --- a/playground/samples/aoc2024/day20 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day20 \ No newline at end of file diff --git a/playground/samples/aoc2024/day21 b/playground/samples/aoc2024/day21 deleted file mode 120000 index 0693fca..0000000 --- a/playground/samples/aoc2024/day21 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day21 \ No newline at end of file diff --git a/playground/samples/aoc2024/day22 b/playground/samples/aoc2024/day22 deleted file mode 120000 index 3091d44..0000000 --- a/playground/samples/aoc2024/day22 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day22 \ No newline at end of file diff --git a/playground/samples/aoc2024/day23 b/playground/samples/aoc2024/day23 deleted file mode 120000 index e777259..0000000 --- a/playground/samples/aoc2024/day23 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day23 \ No newline at end of file diff --git a/playground/samples/aoc2024/day24 b/playground/samples/aoc2024/day24 deleted file mode 120000 index ea2c673..0000000 --- a/playground/samples/aoc2024/day24 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day24 \ No newline at end of file diff --git a/playground/samples/aoc2024/day25 b/playground/samples/aoc2024/day25 deleted file mode 120000 index 3fda4ae..0000000 --- a/playground/samples/aoc2024/day25 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day25 \ No newline at end of file diff --git a/playground/samples/aoc2024/day3 b/playground/samples/aoc2024/day3 deleted file mode 120000 index f693f06..0000000 --- a/playground/samples/aoc2024/day3 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day3 \ No newline at end of file diff --git a/playground/samples/aoc2024/day4 b/playground/samples/aoc2024/day4 deleted file mode 120000 index 5ad8175..0000000 --- a/playground/samples/aoc2024/day4 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day4 \ No newline at end of file diff --git a/playground/samples/aoc2024/day5 b/playground/samples/aoc2024/day5 deleted file mode 120000 index 8957624..0000000 --- a/playground/samples/aoc2024/day5 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day5 \ No newline at end of file diff --git a/playground/samples/aoc2024/day6 b/playground/samples/aoc2024/day6 deleted file mode 120000 index 0c90d86..0000000 --- a/playground/samples/aoc2024/day6 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day6 \ No newline at end of file diff --git a/playground/samples/aoc2024/day7 b/playground/samples/aoc2024/day7 deleted file mode 120000 index 882e750..0000000 --- a/playground/samples/aoc2024/day7 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day7 \ No newline at end of file diff --git a/playground/samples/aoc2024/day8 b/playground/samples/aoc2024/day8 deleted file mode 120000 index dce1280..0000000 --- a/playground/samples/aoc2024/day8 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day8 \ No newline at end of file diff --git a/playground/samples/aoc2024/day9 b/playground/samples/aoc2024/day9 deleted file mode 120000 index 4ae6efc..0000000 --- a/playground/samples/aoc2024/day9 +++ /dev/null @@ -1 +0,0 @@ -../../../aoc2024/day9 \ No newline at end of file diff --git a/playground/samples/newt b/playground/samples/newt deleted file mode 120000 index 929cb3d..0000000 --- a/playground/samples/newt +++ /dev/null @@ -1 +0,0 @@ -../../src \ No newline at end of file diff --git a/playground/src/base64.test.ts b/playground/src/base64.test.ts new file mode 100644 index 0000000..a05cf56 --- /dev/null +++ b/playground/src/base64.test.ts @@ -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("---"); + } +}); diff --git a/playground/src/base64.ts b/playground/src/base64.ts new file mode 100644 index 0000000..5dd1bc2 --- /dev/null +++ b/playground/src/base64.ts @@ -0,0 +1,45 @@ +// tables +const i2c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +const c2i: Record = {}; +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); +} diff --git a/playground/src/cmeditor.ts b/playground/src/cmeditor.ts index 24fdce8..efae07e 100644 --- a/playground/src/cmeditor.ts +++ b/playground/src/cmeditor.ts @@ -81,7 +81,6 @@ function tokenizer(stream: StringStream, state: State): string | null { let word = stream.current(); if (keywords.includes(word)) return "keyword"; if (word[0] >= "A" && word[0] <= "Z") return "typename"; - console.log('IDENT', ) return "identifier"; } // unhandled @@ -136,7 +135,7 @@ export class CMEditor implements AbstractEditor { // For indent on return indentService.of((ctx, pos) => { let line = ctx.lineAt(pos) - if (!line) return null + if (!line || !line.from) return null let prevLine = ctx.lineAt(line.from - 1); if (prevLine.text.trimEnd().match(/\b(of|where|do)\s*$/)) { let pindent = prevLine.text.match(/^\s*/)?.[0].length ?? 0 diff --git a/playground/src/deflate.ts b/playground/src/deflate.ts new file mode 100644 index 0000000..76ddac1 --- /dev/null +++ b/playground/src/deflate.ts @@ -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< dist code (0..29) + let dist = 0; + for (code = 0 ; code < 16; code++) { + distBase[code] = dist; + for (let n = 0; n < (1<>= 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>7)] + write(5,d_code) + write(distExtra[d_code],offset-1-distBase[d_code],true) + s += l + nextEmit = s + } else { + s++ + } + } + for (;nextEmit> 3) + if (raw) return out.slice(0,len) + new DataView(out.buffer).setInt32(len,adler,false) + return out.slice(0, len+4) +} diff --git a/playground/src/emul.ts b/playground/src/emul.ts index 0a04d5d..5879b6a 100644 --- a/playground/src/emul.ts +++ b/playground/src/emul.ts @@ -9,7 +9,14 @@ export interface Handle { interface Process { argv: string[]; + platform: string; exit(_: number): void; + stdout: { + write(s: string): unknown + }; + cwd(): string; + env: Record + __lasterr: {errno: number} } export interface NodeShim { stdout: string; diff --git a/playground/src/frame.ts b/playground/src/frame.ts index e08f964..2c1b0fe 100644 --- a/playground/src/frame.ts +++ b/playground/src/frame.ts @@ -62,7 +62,7 @@ window.addEventListener("message", (ev: MessageEvent) => { let { src } = ev.data; try { sendMessage({ type: "setConsole", messages: [] }); - eval(src); + (new Function(src))(); } catch (e) { console.log(e); } diff --git a/playground/src/inflate.test.ts b/playground/src/inflate.test.ts new file mode 100644 index 0000000..819fcbf --- /dev/null +++ b/playground/src/inflate.test.ts @@ -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) +}) diff --git a/playground/src/inflate.ts b/playground/src/inflate.ts new file mode 100644 index 0000000..6b6e145 --- /dev/null +++ b/playground/src/inflate.ts @@ -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; + 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))); + } +} + diff --git a/playground/src/ipc.ts b/playground/src/ipc.ts new file mode 100644 index 0000000..335f02f --- /dev/null +++ b/playground/src/ipc.ts @@ -0,0 +1,74 @@ +export type Result = + | { 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 { + id: number; + key: K; + args: Parameters; +} + +export interface ResponseMSG { + id: number; + result: string; +} + +type Suspense = { + resolve: (value: any | PromiseLike) => void; + reject: (reason?: any) => void; +}; + +export class IPC { + callbacks: Record = {}; + _postMessage: (msg: Message) => void; + lastID = 1; + constructor() { + const newtWorker = new Worker("worker.js"); + this._postMessage = (msg: Message) => + 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) => { + 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( + key: K, + args: Parameters + ): Promise> { + 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 {} diff --git a/playground/src/main.ts b/playground/src/main.ts index 7924259..3ba9850 100644 --- a/playground/src/main.ts +++ b/playground/src/main.ts @@ -1,32 +1,59 @@ import { effect, signal } from "@preact/signals"; import { Diagnostic } from "@codemirror/lint"; import { useEffect, useRef, useState } from "preact/hooks"; -import { h, render } from "preact"; +import { h, render, VNode } from "preact"; import { ChangeEvent } from "preact/compat"; import { archive, preload } from "./preload.ts"; +import { b64decode, b64encode } from "./base64"; import { AbstractEditor, EditorDelegate, - CompileReq, - CompileRes, - Message, TopData, Marker, } from "./types.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; -const newtWorker = new Worker("worker.js"); -// :FIXME use because Safari -let postMessage = (msg: CompileReq) => 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); - postMessage = (msg: any) => workerFrame.contentWindow?.postMessage(msg, "*"); +const ipc = new IPC(); + +function mdline2nodes(s: string) { + let cs: (VNode|string)[] = [] + let toks = s.matchAll(/(\*\*.*?\*\*)|(\*.*?\*)|(_.*?_)|[^*]+|\*/g) + for (let tok of toks) { + if (tok[1]) cs.push(h('b',{},tok[0].slice(2,-2))) + else if (tok[2]) cs.push(h('em',{},tok[0].slice(1,-1))) + else if (tok[3]) cs.push(h('em',{},tok[0].slice(1,-1))) + 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 @@ -35,13 +62,20 @@ iframe.src = "frame.html"; iframe.style.display = "none"; document.body.appendChild(iframe); -function run(src: string) { - console.log("SEND TO", iframe.contentWindow); - const fileName = state.currentFile.value; - // postMessage({ type: "compileRequest", fileName, src }); +async function refreshJS() { +if (!state.javascript.value) { + let src = state.editor.value!.getValue(); + 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; console.log("RUN", iframe.contentWindow); try { @@ -68,55 +102,47 @@ function setOutput(output: string) { state.output.value = output; } -let lastID = 0; -const nextID = () => "" + lastID++; - -window.onmessage = (ev: MessageEvent) => { +window.addEventListener("message", (ev) => { + if (ev.data.id) return; console.log("window got", ev.data); if ("messages" in ev.data) state.messages.value = ev.data.messages; if ("message" in ev.data) { 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 = { - resolve: (value: T | PromiseLike) => void; - reject: (reason?: any) => void; -}; +// We could push this into the editor +document.addEventListener("keydown", (ev) => { + if ((ev.metaKey || ev.ctrlKey) && ev.code == "KeyS") + copyToClipboard(ev); +}); -const callbacks: Record> = {}; - -newtWorker.onmessage = (ev: MessageEvent) => { - let suspense = callbacks[ev.data.id]; - if (suspense) { - suspense.resolve(ev.data.output); - delete callbacks[ev.data.id]; - } - 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( - (resolve, reject) => { - callbacks[req.id] = { resolve, reject } - postMessage(req); +function getSavedCode() { + let value: string = localStorage.idrisCode || LOADING; + let hash = window.location.hash; + if (hash.startsWith("#code/")) { + try { + value = new TextDecoder().decode(inflate(b64decode(hash.slice(6)))); + } catch (e) { + console.error(e); } - ); + } + return value; } const state = { output: signal(""), + toast: signal(""), javascript: signal(""), messages: signal([]), editor: signal(null), @@ -166,7 +192,7 @@ document.addEventListener("keydown", (ev) => { const LOADING = "module Loading\n"; -let value = localStorage.code || LOADING; +let value = getSavedCode() || LOADING; let initialVertical = localStorage.vertical == "true"; interface EditorProps { @@ -176,13 +202,8 @@ const language: EditorDelegate = { getEntry(word, _row, _col) { return topData?.context.find((entry) => entry.name === word); }, - onChange(value) { - // run via the linter now - // clearTimeout(timeout); - // timeout = setTimeout(() => { - // run(value); - // localStorage.code = value; - // }, 1000); + onChange(_value) { + // we're using lint() now }, getFileName() { if (!topData) return ""; @@ -192,39 +213,35 @@ const language: EditorDelegate = { async lint(view) { console.log("LINT"); let src = view.state.doc.toString(); - localStorage.code = src - let module = src.match(/module\s+([^\s]+)/)?.[1] + localStorage.code = src; + let module = src.match(/module\s+([^\s]+)/)?.[1]; if (module) { - // This causes problems with stuff like aoc/... that reference files in the same directory - // state.currentFile.value = module.replace('.','/')+'.newt' + // This causes problems with stuff like aoc/... + state.currentFile.value = module.replace(".", "/") + ".newt"; } + state.javascript.value = '' let fileName = state.currentFile.value; console.log("FN", fileName); try { - let out = await runCommand({ - id: nextID(), - type: "compileRequest", - fileName, - src, - compile: false, - }); - console.log("OUT", out); + await ipc.sendMessage("save", [fileName, src]); + let out = await ipc.sendMessage("typeCheck", [fileName]); + setOutput(out); let markers = processOutput(out); - let diags: Diagnostic[] = [] + let diags: Diagnostic[] = []; 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; - let word = view.state.wordAt(pos) + let word = view.state.wordAt(pos); diags.push({ from: word?.from ?? pos, - to: word?.to ?? pos+1, + to: word?.to ?? pos + 1, severity: marker.severity, message: marker.message, - }) + }); } - return diags + return diags; } catch (e) { console.log("ERR", e); } @@ -243,7 +260,6 @@ function Editor({ initialValue }: EditorProps) { state.editor.value = editor; editor.setDark(state.dark.value); if (initialValue === LOADING) loadFile("Tour.newt"); - else run(initialValue); }, []); return h("div", { id: "editor", ref }); @@ -260,6 +276,38 @@ function Result() { 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() { const messages = state.messages.value ?? []; return h( @@ -272,6 +320,7 @@ function Console() { const RESULTS = "Output"; const JAVASCRIPT = "JS"; const CONSOLE = "Console"; +const HELP = "Help"; function Tabs() { const [selected, setSelected] = useState(localStorage.tab ?? RESULTS); @@ -289,6 +338,10 @@ function Tabs() { if (state.messages.value.length) setSelected(CONSOLE); }, [state.messages.value]); + useEffect(() => { + if (selected === JAVASCRIPT && !state.javascript.value) refreshJS(); + }, [selected, state.javascript.value]); + let body; switch (selected) { case RESULTS: @@ -300,6 +353,9 @@ function Tabs() { case CONSOLE: body = h(Console, {}); break; + case HELP: + body = h(Help, {}); + break; default: body = h("div", {}); } @@ -312,7 +368,8 @@ function Tabs() { { className: "tabBar" }, Tab(RESULTS), Tab(JAVASCRIPT), - Tab(CONSOLE) + Tab(CONSOLE), + Tab(HELP), ), h("div", { className: "tabBody" }, body) ); @@ -348,12 +405,15 @@ function EditWrap({ loadFile(fn); } }; - let d = vertical - ? "M0 0 h20 v20 h-20 z M0 10 h20" - : "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 })); + + const char = vertical ? "↕" : "↔" + + // let d = vertical + // ? "M0 0 h20 v20 h-20 z M0 10 h20" + // : "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( "div", { className: "tabPanel left" }, @@ -366,8 +426,10 @@ function EditWrap({ options ), h("div", { style: { flex: "1 1" } }), - h("button", { onClick: runOutput }, svg(play)), - h("button", { onClick: toggle }, svg(d)) + h("button", { onClick: copyToClipboard, title: "copy url" }, "📋"), + 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 })) ); @@ -379,10 +441,15 @@ function App() { setVertical(!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"}`; return h( "div", { className }, + toast, h(EditWrap, { vertical, toggle }), h(Tabs, {}) ); @@ -390,16 +457,10 @@ function 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 = ( - // editor: AbstractEditor, output: string ) => { - // let model = editor.getModel()!; - console.log('process output', output) + console.log("process output", output); let markers: Marker[] = []; let lines = output.split("\n"); let m = lines[0].match(/.*Process (.*)/); @@ -407,7 +468,9 @@ const processOutput = ( if (m) fn = m[1]; for (let i = 0; i < lines.length; 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) { let [_full, kind, file, line, col, message] = match; let lineNumber = +line + 1; @@ -428,7 +491,7 @@ const processOutput = ( } if (kind === "ERROR" || lineNumber) markers.push({ - severity: kind === 'ERROR' ? 'error' : 'info', + severity: kind === "ERROR" ? "error" : "info", message, startLineNumber: lineNumber, endLineNumber: lineNumber, @@ -437,7 +500,7 @@ const processOutput = ( }); } } - console.log('markers', markers) + console.log("markers", markers); // editor.setMarkers(markers) return markers; }; diff --git a/playground/src/types.ts b/playground/src/types.ts index ea45e66..d581a2b 100644 --- a/playground/src/types.ts +++ b/playground/src/types.ts @@ -1,6 +1,8 @@ import { EditorView } from "codemirror"; import { linter, Diagnostic } from "@codemirror/lint"; + + export interface CompileReq { id: string type: "compileRequest"; diff --git a/playground/src/worker.ts b/playground/src/worker.ts index 575460a..4b1314e 100644 --- a/playground/src/worker.ts +++ b/playground/src/worker.ts @@ -1,41 +1,50 @@ import { shim } from "./emul"; +import { API, Message, ResponseMSG } from "./ipc"; import { archive, preload } from "./preload"; -import { CompileReq, CompileRes } from "./types"; +const LOG = console.log console.log = (m) => { shim.stdout += "\n" + m; }; -const handleMessage = async function (ev: { data: CompileReq }) { - console.log("message", ev.data); +const handleMessage = async function (ev: { data: Message }) { + LOG("HANDLE", ev.data); await preload; shim.archive = archive; - let { id, src, fileName } = ev.data; - const outfile = "out.js"; - if (ev.data.compile) - shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"]; - else - shim.process.argv = ["browser", "newt", fileName, "--top"]; - shim.files[fileName] = new TextEncoder().encode(src); - shim.files[outfile] = new TextEncoder().encode("No JS output"); - shim.stdout = ""; - const start = +new Date(); - try { - Main_main(); - } catch (e) { - // make it clickable in console - console.error(e); - // make it visable on page - shim.stdout += "\n" + String(e); + let key = ev.data.key + if (key === 'typeCheck' || key === 'compile') { + let {id, args: [fileName]} = ev.data + LOG(key, fileName) + const outfile = "out.js"; + const isCompile = key === 'compile'; + if (isCompile) + shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"]; + else + shim.process.argv = ["browser", "newt", fileName, "--top"]; + shim.stdout = ""; + shim.files[outfile] = new TextEncoder().encode("No JS output"); + + try { + Main_main(); + } catch (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 -let sendResponse: (_: CompileRes) => void = postMessage; +let sendResponse: (_: ResponseMSG) => void = postMessage; onmessage = handleMessage; importScripts("newt.js"); diff --git a/playground/src/zipfile.ts b/playground/src/zipfile.ts index 7f4fe2e..f75b08f 100644 --- a/playground/src/zipfile.ts +++ b/playground/src/zipfile.ts @@ -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 { size: number; start: number; end: number; + method: number; } export class ZipFile { @@ -292,13 +34,12 @@ export class ZipFile { if (size) { let start = pos + 30 + fnlen + eflen; let end = start + csize; - this.entries[fn] = { size, start, end }; + this.entries[fn] = { size, start, end, method }; } pos = pos + 30 + fnlen + eflen + csize; } } getData(name: string) { - if (!(name in this.entries)) return let { start, end, size } = this.entries[name]; return inflate(new Uint8Array(this.data.slice(start, end))); } diff --git a/playground/style.css b/playground/style.css index e5bdde2..3eabc7e 100644 --- a/playground/style.css +++ b/playground/style.css @@ -7,6 +7,31 @@ svg.icon path { 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) { body { color: white; @@ -60,6 +85,7 @@ svg.icon path { .tabPanel { display: flex; flex-direction: column; + font-family: 'Comic Code', monospace; } .tabBar { display:flex; @@ -93,3 +119,6 @@ svg.icon path { .tooltip { padding: 5px; } +.cm-editor .cm-content { + font-family: 'Comic Code', monospace; +}