substrate-lang 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/compiler/{checker.4dd4556dd421@0+0.wasm → checker.1d67e1d4e91a@0+0.wasm} +0 -0
- package/build/compiler/{expander.967ce443d379@0+0.wasm → expander.34110a31a949@0+0.wasm} +0 -0
- package/build/compiler/{main.4fc52f204d54@0+0.wasm → main.ca9b5089c24d@0+0.wasm} +0 -0
- package/build/compiler/program +4 -4
- package/build/compiler/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
- package/build/sub/{build.459c5b9412bd@0+0.wasm → build.eef7dfa467ec@0+0.wasm} +0 -0
- package/build/sub/{common.1edc613607a0@0+0.wasm → common.7617940db2dc@0+0.wasm} +0 -0
- package/build/sub/deflate.23f7c4679260@0+0.wasm +0 -0
- package/build/sub/{doc.0775df5de6fe@0+0.wasm → doc.92d6d82d99c1@0+0.wasm} +0 -0
- package/build/sub/{doc.ef9965d5d402@0+0.wasm → doc.dab689b811ca@0+0.wasm} +0 -0
- package/build/sub/{fetch.96b2d10f025e@0+0.wasm → fetch.3367ff4f88c0@0+0.wasm} +0 -0
- package/build/sub/{files.ad0469fdf6fa@0+0.wasm → files.5ae9d3cebeca@0+0.wasm} +0 -0
- package/build/sub/gzip.16e372eda1cf@0+0.wasm +0 -0
- package/build/sub/{hash.660682e4f491@0+0.wasm → hash.c57c141b4862@0+0.wasm} +0 -0
- package/build/sub/{html.a23126eb949a@0+0.wasm → html.803f73c35c4a@0+0.wasm} +0 -0
- package/build/sub/{manifest.ef317480deb0@0+0.wasm → manifest.da52a88c7af7@0+0.wasm} +0 -0
- package/build/sub/{map.f455cca6a42a@0+0.wasm → map.5c8084999550@0+0.wasm} +0 -0
- package/build/sub/program +22 -22
- package/build/sub/{root.f10a4906f460@0+0.wasm → root.78253db36643@0+0.wasm} +0 -0
- package/build/sub/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
- package/build/sub/sha256.a6c32b04d799@0+0.wasm +0 -0
- package/build/sub/site.47fd989be8fb@0+0.wasm +0 -0
- package/build/sub/site.c908b1f6db12@0+0.wasm +0 -0
- package/build/sub/{store.a24d7a25daa6@0+0.wasm → store.048c77bb124d@0+0.wasm} +0 -0
- package/build/sub/sub.a618f89f2ec8@0+0.wasm +0 -0
- package/build/sub/{test.9bd59c73f65e@0+0.wasm → test.b529bf52ba3e@0+0.wasm} +0 -0
- package/build/sub/{text.1bc90482bd43@0+0.wasm → text.b214933a4984@0+0.wasm} +0 -0
- package/build/sub/{upgrade.39ee449b00d5@0+0.wasm → upgrade.7ec5651ed2e9@0+0.wasm} +0 -0
- package/package.json +1 -1
- package/std/http/server.sub +7 -7
- package/std/http/static.sub +66 -30
- package/std/package/manifest.sub +9 -4
- package/std/package/site.sub +209 -15
- package/std/package.sub +2 -2
- package/std/test/browser.sub +20 -14
- package/std/test/build.sub +8 -6
- package/std/test/page/agent.mjs +8 -2
- package/std/web/canvas.sub +1 -1
- package/std/web/dom.sub +5 -5
- package/std/web/patches.sub +8 -8
- package/substrate/VERSION +1 -1
- package/substrate/compiler/expander.sub +2 -3
- package/substrate/compiler/roots.sub +12 -5
- package/substrate/host/grants.mjs +3 -3
- package/substrate/host/node/net.mjs +17 -2
- package/substrate/host/node/worker.mjs +3 -1
- package/substrate/host/subhost.mjs +4 -1
- package/substrate/host/web/audio.mjs +1 -1
- package/substrate/host/web/dom.mjs +3 -3
- package/substrate/host/web/dom.sub +4 -4
- package/substrate/host/web/fs.mjs +3 -3
- package/substrate/host/web/gpu.mjs +2 -2
- package/substrate/host/web/location.sub +1 -1
- package/substrate/host/web/start.mjs +11 -7
- package/substrate/host/web/subhost-web.mjs +54 -33
- package/substrate/host/web/worker-script.mjs +4 -4
- package/substrate/host/web/worker.mjs +4 -4
- package/substrate/host/worker.mjs +1 -1
- package/build/sub/site.1732b58ec0e8@0+0.wasm +0 -0
- package/build/sub/site.ae3913b0b0f4@0+0.wasm +0 -0
- package/build/sub/sub.2e3092140706@0+0.wasm +0 -0
package/std/package.sub
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(package std
|
|
2
|
-
(version "0.10.
|
|
3
|
-
(substrate "0.10.
|
|
2
|
+
(version "0.10.2")
|
|
3
|
+
(substrate "0.10.2")
|
|
4
4
|
(exports archive/tar browser/chromium browser/devtools compress/deflate compress/gzip compress/inflate crypto/hmac
|
|
5
5
|
crypto/password crypto/sha1
|
|
6
6
|
crypto/sha256 crypto/sha512 doc/doc doc/html doc/root doc/text encoding/base64 encoding/json encoding/utf8
|
package/std/test/browser.sub
CHANGED
|
@@ -105,13 +105,13 @@
|
|
|
105
105
|
|
|
106
106
|
(define (%browser-step! [s string]) (page-step! s))
|
|
107
107
|
|
|
108
|
-
(define (browser-opened [spec string] [html string] [url string] [args (slice string)] [server (fn ())]
|
|
109
|
-
-> browser
|
|
108
|
+
(define (browser-opened [spec string] [html string] [release bool] [url string] [args (slice string)] [server (fn ())]
|
|
109
|
+
[message string]) -> browser
|
|
110
110
|
(let ([address string])
|
|
111
111
|
(unless (env-get "SUBSTRATE_DEVTOOLS" (& address) 0)
|
|
112
112
|
(throw test-failed (lines-joined message "a test that opens a browser needs (browser) in the tests clause of its package's package.sub")))
|
|
113
113
|
(when (== (-> address length) 0) (throw test-skipped "need Chromium"))
|
|
114
|
-
(let ([dir (site-built spec html message)] [why error] [p (ptr page) 0])
|
|
114
|
+
(let ([dir (site-built spec html release message)] [why error] [p (ptr page) 0])
|
|
115
115
|
(unless (page-open dir (with-args url args) address server (& p) (& why))
|
|
116
116
|
(throw test-failed (lines-joined message (error-text (& why)))))
|
|
117
117
|
(browser #:page p))))
|
|
@@ -126,28 +126,31 @@
|
|
|
126
126
|
(url-add-form-value! (& b) (@ args i)))
|
|
127
127
|
(buffer-string (& b))))
|
|
128
128
|
|
|
129
|
-
(struct built [spec string] [html string] [dir string])
|
|
129
|
+
(struct built [spec string] [html string] [release bool] [dir string])
|
|
130
130
|
|
|
131
131
|
(define made buffer)
|
|
132
132
|
|
|
133
133
|
;; Builds spec's site in the test's scratch directory, once per test.
|
|
134
|
-
(define (site-built [spec string] [html string] [message string]) -> string
|
|
134
|
+
(define (site-built [spec string] [html string] [release bool] [message string]) -> string
|
|
135
135
|
(let* ([all (cast (ptr built) (-> made bytes))] [n (/ (-> made length) (sizeof built))]
|
|
136
136
|
[base (base-of spec)] [name (if (ends? base ".sub") (slice base 0 (- (-> base length) 4)) base)]
|
|
137
137
|
[dir (joined-path (joined-path (scratch-directory) "sites") name)] [info fs-info] [err (make-buffer)]
|
|
138
138
|
[b built])
|
|
139
139
|
(for ([i u32 0]) (< i n) (+= i 1)
|
|
140
140
|
(let ([m (& (@ all i))])
|
|
141
|
-
(when (and (== (-> m spec) spec) (== (-> m html) html)) (return (-> m dir)))
|
|
141
|
+
(when (and (== (-> m spec) spec) (== (-> m html) html) (== (-> m release) release)) (return (-> m dir)))
|
|
142
142
|
(when (== (-> m dir) dir) (set! dir (numbered dir n)))))
|
|
143
143
|
(let ([p (planned spec name message)])
|
|
144
144
|
(when (> (-> html length) 0) (set! (-> p html) html))
|
|
145
|
+
(build-ready!)
|
|
145
146
|
(unless (site-write! (& p) dir (toolchain) (& err))
|
|
146
147
|
(throw test-failed (lines-joined message (buffer-string (& err)))))
|
|
147
148
|
(unless (and (fs-stat (-> p program) (& info) 0) (== (-> info kind) fs-directory))
|
|
148
|
-
(unless (program-write! (-> p program) (joined-path dir "program") (& err))
|
|
149
|
-
(throw test-failed (lines-joined message (buffer-string (& err))))))
|
|
150
|
-
|
|
149
|
+
(unless (program-write! (-> p program) (joined-path dir "program") release (& err))
|
|
150
|
+
(throw test-failed (lines-joined message (buffer-string (& err))))))
|
|
151
|
+
(when (and release (not (site-release! dir (& err))))
|
|
152
|
+
(throw test-failed (lines-joined message (buffer-string (& err))))))
|
|
153
|
+
(set! b (built #:spec spec #:html html #:release release #:dir dir))
|
|
151
154
|
(buffer-add! (& made) (slice (cast (ptr u8) (& b)) (sizeof built)))
|
|
152
155
|
dir))
|
|
153
156
|
|
|
@@ -367,18 +370,21 @@
|
|
|
367
370
|
(define (with-browser-form [s datum]) -> datum
|
|
368
371
|
(syntax-match s
|
|
369
372
|
[(_ ((? identifier? b) (? text? file) options ...) body ...)
|
|
370
|
-
(let ([html (literal '"")] [url (literal '"")] [args (no-fields)] [server #'(cast (fn ()) 0)]
|
|
373
|
+
(let ([html (literal '"")] [release #'#f] [url (literal '"")] [args (no-fields)] [server #'(cast (fn ()) 0)]
|
|
374
|
+
[l options])
|
|
371
375
|
(while (pair? l)
|
|
372
376
|
(unless (and (keyword? (syntax-e (car l))) (pair? (cdr l)))
|
|
373
377
|
(raise-syntax-error 'with-browser "expected a keyword and its value" (car l)))
|
|
374
378
|
(let ([k (keyword->string (syntax-e (car l)))] [v (cadr l)])
|
|
375
379
|
(cond [(and (== k "html") (text? v)) (set! html (path-of s v))]
|
|
380
|
+
[(== k "release") (set! release v)]
|
|
376
381
|
[(and (== k "url") (text? v)) (set! url v)]
|
|
377
382
|
[(== k "args") (set! args (arguments v))]
|
|
378
383
|
[(== k "server") (set! server v)]
|
|
379
|
-
[else (raise-syntax-error 'with-browser "the options are #:html \"file\", #:url \"url\", #:args and #:server function" (car l))]))
|
|
384
|
+
[else (raise-syntax-error 'with-browser "the options are #:html \"file\", #:release #t, #:url \"url\", #:args and #:server function" (car l))]))
|
|
380
385
|
(set! l (cddr l)))
|
|
381
|
-
#`(let ([#,b (browser-opened #,(if (named? (syntax-e file)) file (path-of s file)) #,html #,url #,args
|
|
386
|
+
#`(let ([#,b (browser-opened #,(if (named? (syntax-e file)) file (path-of s file)) #,html #,release #,url #,args
|
|
387
|
+
#,server
|
|
382
388
|
#,(literal (format "~a:~a: (with-browser (~s ~s) ...)"
|
|
383
389
|
`(,(syntax-source s) ,(syntax-line s) ,(syntax->datum b)
|
|
384
390
|
,(syntax-e file)))))])
|
|
@@ -386,8 +392,8 @@
|
|
|
386
392
|
[catch-all (browser-closed! #,b) (rethrow)])
|
|
387
393
|
(%browser-step! "")
|
|
388
394
|
(browser-closed! #,b)))]
|
|
389
|
-
[_ (raise-syntax-error 'with-browser "expected (with-browser (name \"site or program.sub\" [#:html \"index.html\"] [#:
|
|
390
|
-
[#:server function]) body ...)" s)]))
|
|
395
|
+
[_ (raise-syntax-error 'with-browser "expected (with-browser (name \"site or program.sub\" [#:html \"index.html\"] [#:release #t] [#:url \"url\"]
|
|
396
|
+
[#:args (\"argument\" ...)] [#:server function]) body ...)" s)]))
|
|
391
397
|
|
|
392
398
|
;; The program's arguments from #:args: either a list of strings, or an expression that gives a slice of strings.
|
|
393
399
|
(define (arguments [v datum]) -> datum
|
package/std/test/build.sub
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
;; Building programs during a test: expect-compile-error, and program-write!, which with-browser uses to build sites.
|
|
2
|
+
;; build-ready! gives the compiler the test program's toolchain, module map and cache, once.
|
|
2
3
|
(require-unqualified compiler/macros)
|
|
3
4
|
(require-unqualified compiler/expander)
|
|
4
5
|
(require-unqualified compiler/roots)
|
|
@@ -9,12 +10,12 @@
|
|
|
9
10
|
(require-unqualified "test.sub")
|
|
10
11
|
(require-unqualified "compare.sub")
|
|
11
12
|
|
|
12
|
-
(provide expect-compile-error program-write!)
|
|
13
|
+
(provide expect-compile-error program-write! build-ready!)
|
|
13
14
|
|
|
14
15
|
(define ready bool #f)
|
|
15
16
|
(define directory string)
|
|
16
17
|
|
|
17
|
-
(define (ready!)
|
|
18
|
+
(define (build-ready!)
|
|
18
19
|
(unless ready
|
|
19
20
|
(set! ready #t)
|
|
20
21
|
(let ([why error] [toolchain string] [map-file string] [cache string])
|
|
@@ -29,19 +30,20 @@
|
|
|
29
30
|
|
|
30
31
|
;; Builds the program at path into out, both relative to the package's directory, with the test program's module map
|
|
31
32
|
;; and cache. Returns #f with the compile error in err if it does not compile.
|
|
32
|
-
(define (program-write! [path string] [out string] [err (ptr buffer)]) -> bool
|
|
33
|
-
(ready!)
|
|
33
|
+
(define (program-write! [path string] [out string] [strip bool] [err (ptr buffer)]) -> bool
|
|
34
|
+
(build-ready!)
|
|
34
35
|
(let ([full (resolved-path directory path)] [to (resolved-path directory out)])
|
|
35
36
|
(set-display-directory! (string->datum (parent-of full)))
|
|
36
37
|
(fs-remove to #t 0)
|
|
37
|
-
(try (begin (write-program! 0 (string->datum full) (string->datum to)
|
|
38
|
+
(try (begin (write-program! 0 (string->datum full) (string->datum to) strip) #t)
|
|
38
39
|
[catch compile-error (message blamed loc)
|
|
39
40
|
(write-error err message blamed loc)
|
|
40
41
|
#f])))
|
|
41
42
|
|
|
42
43
|
(define (compile-error-check [path string] [text string] [message string])
|
|
43
44
|
(let ([err (make-buffer)])
|
|
44
|
-
(when (program-write! path (joined-path (joined-path (scratch-directory) "programs") (program-name path))
|
|
45
|
+
(when (program-write! path (joined-path (joined-path (scratch-directory) "programs") (program-name path)) #f
|
|
46
|
+
(& err))
|
|
45
47
|
(failure! message "the file compiled"))
|
|
46
48
|
(unless (contains? (buffer-string (& err)) text) (failure! message (text-of "its compile error is\n" (buffer-string (& err)))))
|
|
47
49
|
(buffer-free! (& err))))
|
package/std/test/page/agent.mjs
CHANGED
|
@@ -74,8 +74,14 @@ const same = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
|
74
74
|
// nameOf names drawn images and sounds by the path of the site's asset with the same bytes.
|
|
75
75
|
async function nameOf(bytes, otherwise) {
|
|
76
76
|
if (!assets) {
|
|
77
|
-
const
|
|
78
|
-
|
|
77
|
+
const start = document.querySelector('script[src$="host/web/start.mjs"]');
|
|
78
|
+
const list = start ? new URL('../../assets.list', start.src) : new URL('/assets.list', location.href);
|
|
79
|
+
const lines = (await (await fetch(list)).text()).split('\n').filter((l) => l !== '');
|
|
80
|
+
assets = await Promise.all(lines.map(async (line) => {
|
|
81
|
+
const [path, url = path] = line.split('\t');
|
|
82
|
+
const from = new URL(url.split('/').map(encodeURIComponent).join('/'), location.href);
|
|
83
|
+
return [`/${path}`, new Uint8Array(await (await fetch(from)).arrayBuffer())];
|
|
84
|
+
}));
|
|
79
85
|
}
|
|
80
86
|
const found = assets.find(([, b]) => same(b, bytes));
|
|
81
87
|
return found ? found[0] : otherwise;
|
package/std/web/canvas.sub
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
;; Drawing on a <canvas> element: rectangles, paths, circles, text, images and transforms. The calls write into the
|
|
2
|
-
;;
|
|
2
|
+
;; patches (patches.sub) between canvas-begin! and canvas-end!, and a call made outside them draws nothing.
|
|
3
3
|
(require-unqualified std/web/patches)
|
|
4
4
|
|
|
5
5
|
(provide canvas-begin! canvas-end! canvas-drawing?
|
package/std/web/dom.sub
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
;; The
|
|
2
|
-
;; name that walks from a node, or from the current event, through the
|
|
3
|
-
;; joined by dots. It gives a number, with true as 1, or text. The text lasts until the patches are sent. dom-is
|
|
4
|
-
;; dom-event-is also take a node, and say whether the name leads to it. (dom-is node dom-document "activeElement")
|
|
5
|
-
;; whether the node has the focus. dom-event-call calls a method of the current event.
|
|
1
|
+
;; The document: the host module "dom" and the patches that patches.sub writes. dom-flush! sends the patches. A read
|
|
2
|
+
;; takes a name that walks from a node, or from the current event, through the document's objects: a property's name, or
|
|
3
|
+
;; names joined by dots. It gives a number, with true as 1, or text. The text lasts until the patches are sent. dom-is
|
|
4
|
+
;; and dom-event-is also take a node, and say whether the name leads to it. (dom-is node dom-document "activeElement")
|
|
5
|
+
;; says whether the node has the focus. dom-event-call calls a method of the current event.
|
|
6
6
|
(require-unqualified host-dom host/web/dom)
|
|
7
7
|
(require-unqualified patches "patches.sub")
|
|
8
8
|
|
package/std/web/patches.sub
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
;; Changes to the
|
|
2
|
-
;; the nodes a program will change are marked: elements with a data-n attribute, <!--t--> for a text node, and
|
|
3
|
-
;; for a place that other pieces can be put before. dom-add! puts a piece, a copy of a template,
|
|
4
|
-
;; the piece's number; its marked nodes are numbered after it, in document order. The strings these
|
|
5
|
-
;; copied, and the copies last until the patches are sent or dom-discard! drops them.
|
|
1
|
+
;; Changes to the document, written as patches for the host module "dom". dom.sub sends them. A template is HTML in
|
|
2
|
+
;; which the nodes a program will change are marked: elements with a data-n attribute, <!--t--> for a text node, and
|
|
3
|
+
;; <!--a--> for a place that other pieces can be put before. dom-add! puts a piece, a copy of a template, in the document
|
|
4
|
+
;; and returns the piece's number; its marked nodes are numbered after it, in document order. The strings these
|
|
5
|
+
;; functions take are copied, and the copies last until the patches are sent or dom-discard! drops them.
|
|
6
6
|
(provide dom-template dom-add! dom-remove! dom-forget! dom-move! dom-set-text! dom-set-attr! dom-remove-attr! dom-listen!
|
|
7
7
|
dom-listen-window! dom-value dom-number dom-text dom-write-number! dom-write-text! dom-call! dom-call-with!
|
|
8
8
|
dom-document dom-image! dom-stylesheet! dom-new-node!
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
(words! (slice (array-of u32 2 template first n parent before)))
|
|
50
50
|
first))
|
|
51
51
|
|
|
52
|
-
;; dom-remove! takes a piece
|
|
53
|
-
;; inside one taken
|
|
52
|
+
;; dom-remove! takes a piece out of the document and forgets its nodes. dom-forget! only forgets them, for a piece that
|
|
53
|
+
;; was inside one taken out.
|
|
54
54
|
(define (dom-remove! [first u32] [template u32]) (words! (slice (array-of u32 3 first (size-of template)))))
|
|
55
55
|
(define (dom-forget! [first u32] [template u32]) (words! (slice (array-of u32 4 first (size-of template)))))
|
|
56
56
|
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
(let ([n (kept name)]) (words! (slice (array-of u32 8 node (cast u32 (-> n bytes)) (-> n length))))))
|
|
111
111
|
|
|
112
112
|
;; dom-listen! sends the node's event to tag's handler, and dom-listen-window! sends the window's event there while the
|
|
113
|
-
;; node is
|
|
113
|
+
;; node is in the document.
|
|
114
114
|
(define (dom-listen! [node u32] [event string] [tag (ptr void)])
|
|
115
115
|
(let ([e (kept event)]) (words! (slice (array-of u32 9 node (cast u32 (-> e bytes)) (-> e length) (cast u32 tag))))))
|
|
116
116
|
|
package/substrate/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.10.
|
|
1
|
+
0.10.2
|
|
@@ -1069,10 +1069,9 @@
|
|
|
1069
1069
|
(equal? (section d 'format) (make-integer cache-format))
|
|
1070
1070
|
(equal? (section d 'compiler) compiler-hash)))
|
|
1071
1071
|
|
|
1072
|
-
;; load-cached loads
|
|
1073
|
-
;; from.
|
|
1072
|
+
;; load-cached loads the module at path from the program's compiled modules when they hold it, or else from the cache.
|
|
1074
1073
|
(define (load-cached [path datum]) -> datum
|
|
1075
|
-
(if (
|
|
1074
|
+
(if (and (== (compiled-module path) 0) (or (compiled-root? path) (== (cache-compiler) 0)))
|
|
1076
1075
|
0
|
|
1077
1076
|
(begin
|
|
1078
1077
|
(begin-module-arena)
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
(provide set-toolchain-directory! set-module-map! set-module-map-text! set-display-directory! toolchain-root? toolchain-roots
|
|
10
10
|
toolchain-holding root-directories root-under-test? identity identity-label host-file entry-identity program-identity
|
|
11
11
|
set-compiled-module-map! compiled-module-map compiled-module compiled-root? module-source-file module-path-from
|
|
12
|
-
module-identity named-file program-forms language-path compiler-file module-source?
|
|
13
|
-
write-error fail-at read-text file-text host-error module-refusal)
|
|
12
|
+
module-identity named-file named-directory program-forms language-path compiler-file module-source?
|
|
13
|
+
string-literal? displayed write-error fail-at read-text file-text host-error module-refusal)
|
|
14
14
|
|
|
15
15
|
(define (symbol-of [s datum]) -> datum (string->symbol (datum->string s)))
|
|
16
16
|
|
|
@@ -183,10 +183,12 @@
|
|
|
183
183
|
(define (set-module-map-text! [text datum] [source datum])
|
|
184
184
|
(set-module-map! (map (lambda ([f datum]) -> datum (syntax->datum f)) (read-text text (symbol-of source)))))
|
|
185
185
|
|
|
186
|
-
;; set-compiled-module-map!
|
|
186
|
+
;; set-compiled-module-map! gives sessions the program's compiled modules. When the program has given no roots with
|
|
187
|
+
;; directories, it also replaces the roots with those of the compiled modules, which have no directories.
|
|
187
188
|
(define (set-compiled-module-map! [forms datum] [modules datum])
|
|
188
|
-
(
|
|
189
|
-
|
|
189
|
+
(when (null? (root-directories))
|
|
190
|
+
(set! toolchain-directory 0)
|
|
191
|
+
(set-module-map! forms))
|
|
190
192
|
(set! compiled (keep modules)))
|
|
191
193
|
|
|
192
194
|
;; compiled-module-map returns the module map without directories, which a build writes next to the program's compiled
|
|
@@ -390,6 +392,11 @@
|
|
|
390
392
|
(let ([file (concat rest '".sub")] [inner (file-in rest (concat (last-part rest) '".sub"))])
|
|
391
393
|
(if (and (== (compiled-module (identity label file)) 0) (!= (compiled-module (identity label inner)) 0)) inner file)))
|
|
392
394
|
|
|
395
|
+
;; named-directory gives the directory that name stands for in a require from file, or #f for a root of compiled modules.
|
|
396
|
+
(define (named-directory [file datum] [name datum]) -> datum
|
|
397
|
+
(let ([r (root-holding (absolute file))])
|
|
398
|
+
(cadr (root-in (named-root 0 name (if (== r 0) program-label (car r)) roots) roots))))
|
|
399
|
+
|
|
393
400
|
;; naming-label returns the root used to resolve module names in code from a given module: that module's root, or the
|
|
394
401
|
;; program's root for code outside any root, such as session input.
|
|
395
402
|
(define (naming-label [from datum]) -> datum
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// What a program may do beyond computing: read and write files, reach hosts, read the environment and run
|
|
2
|
-
// programs. The grants match Deno's permissions wherever the two overlap, except that the host never prompts, a
|
|
3
|
-
// cannot check where fetch is redirected or where names resolve, and a child may keep the dynamic loader's
|
|
4
|
-
// the host started with.
|
|
2
|
+
// programs. The grants match Deno's permissions wherever the two overlap, except that the host never prompts, a web
|
|
3
|
+
// program cannot check where fetch is redirected or where names resolve, and a child may keep the dynamic loader's
|
|
4
|
+
// variables the host started with.
|
|
5
5
|
const kinds = ['read', 'write', 'net', 'env', 'run'];
|
|
6
6
|
export const flags = {
|
|
7
7
|
read: '--allow-read', write: '--allow-write', net: '--allow-net', env: '--allow-env', run: '--allow-run',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// The host module "net" (net.sub).
|
|
1
|
+
// The host module "net" (net.sub). A TLS server reloads its certificate and key when their files change.
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import net from 'node:net';
|
|
3
4
|
import tls from 'node:tls';
|
|
4
5
|
import { errorOf, reasonOf } from '../errors.mjs';
|
|
@@ -14,6 +15,19 @@ const ACCEPT = 2;
|
|
|
14
15
|
const CONNECTED = 3;
|
|
15
16
|
const FAILED = 4;
|
|
16
17
|
|
|
18
|
+
function renewed(server, certificate) {
|
|
19
|
+
const [certFile, keyFile] = certificate.files;
|
|
20
|
+
const reload = () => {
|
|
21
|
+
try {
|
|
22
|
+
server.setSecureContext({ cert: fs.readFileSync(certFile), key: fs.readFileSync(keyFile) });
|
|
23
|
+
} catch {}
|
|
24
|
+
};
|
|
25
|
+
for (const file of certificate.files) fs.watchFile(file, { interval: 1000, persistent: false }, reload);
|
|
26
|
+
server.once('close', () => {
|
|
27
|
+
for (const file of certificate.files) fs.unwatchFile(file, reload);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
export function netModule(grants, certificate) {
|
|
18
32
|
const listeners = new Map();
|
|
19
33
|
const ca = certificate ? [...tls.rootCertificates, certificate.cert] : undefined;
|
|
@@ -39,9 +53,10 @@ export function netModule(grants, certificate) {
|
|
|
39
53
|
throw new Error('net.listen: TLS needs the host started with --tls-cert and --tls-key');
|
|
40
54
|
}
|
|
41
55
|
server = secure
|
|
42
|
-
? tls.createServer({
|
|
56
|
+
? tls.createServer({ cert: certificate.cert, key: certificate.key, allowHalfOpen: true })
|
|
43
57
|
: net.createServer({ allowHalfOpen: true });
|
|
44
58
|
if (most > 0) server.maxConnections = most;
|
|
59
|
+
if (secure && certificate.files) renewed(server, certificate);
|
|
45
60
|
if (addressDenials(grants.net) && !net.isIP(host)) {
|
|
46
61
|
lookupWithin(grants, 'net.listen', port)(host, {}, (e, address) => {
|
|
47
62
|
if (listeners.get(id) !== server) return;
|
|
@@ -30,7 +30,9 @@ export function workers(setup) {
|
|
|
30
30
|
spawn(plan) {
|
|
31
31
|
const { port1, port2 } = new MessageChannel();
|
|
32
32
|
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
33
|
-
const tls = plan.tls
|
|
33
|
+
const tls = plan.tls
|
|
34
|
+
? { cert: read(plan.tls.cert), key: read(plan.tls.key), files: [plan.tls.cert, plan.tls.key] }
|
|
35
|
+
: setup.tls;
|
|
34
36
|
const thread = new Worker(new URL('./worker-thread.mjs', import.meta.url));
|
|
35
37
|
// The messages a thread posted before it ended, which can still wait on the port when its exit is heard.
|
|
36
38
|
let heard = null;
|
|
@@ -42,7 +42,10 @@ try {
|
|
|
42
42
|
if (request.inherit.size > 0) fail('inherit is only for workers');
|
|
43
43
|
setup.grants = joinedGrants(setup.grants, request.grants);
|
|
44
44
|
for (const [name, at] of request.fakes) setup.fakes.set(name, at);
|
|
45
|
-
if (request.tls)
|
|
45
|
+
if (request.tls) {
|
|
46
|
+
const files = [request.tls.cert, request.tls.key];
|
|
47
|
+
setup.tls = { cert: read(files[0]), key: read(files[1]), files };
|
|
48
|
+
}
|
|
46
49
|
} catch (e) {
|
|
47
50
|
fail(e.message);
|
|
48
51
|
}
|
|
@@ -74,7 +74,7 @@ export function audio() {
|
|
|
74
74
|
counted(tag, 1);
|
|
75
75
|
const data = bytes
|
|
76
76
|
? Promise.resolve(bytes.slice().buffer)
|
|
77
|
-
: fetch(name).then((response) => {
|
|
77
|
+
: fetch(app.url(name)).then((response) => {
|
|
78
78
|
if (!response.ok) throw new Error(`the server answered ${response.status}`);
|
|
79
79
|
return response.arrayBuffer();
|
|
80
80
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The host module "dom": applies a program's patches to the
|
|
1
|
+
// The host module "dom": applies a program's patches to the document, and sends it the document's events.
|
|
2
2
|
import { resolvePath } from './fs.mjs';
|
|
3
3
|
import { errors } from '../errors.mjs';
|
|
4
4
|
|
|
@@ -64,7 +64,7 @@ export function dom(root) {
|
|
|
64
64
|
x = step(x, parts[k], path);
|
|
65
65
|
if (x === null || x === undefined) return [null, last];
|
|
66
66
|
if (!reachable(x)) {
|
|
67
|
-
caller.trap(`dom: ${path}: ${parts.slice(0, k + 1).join('.')} is not one of the
|
|
67
|
+
caller.trap(`dom: ${path}: ${parts.slice(0, k + 1).join('.')} is not one of the document's objects`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
return [x, last];
|
|
@@ -372,7 +372,7 @@ export function dom(root) {
|
|
|
372
372
|
const image = new Image();
|
|
373
373
|
const bytes = app.files.get(resolvePath(app.cwd, name));
|
|
374
374
|
const type = imageTypes[name.slice(name.lastIndexOf('.') + 1).toLowerCase()] || '';
|
|
375
|
-
const url = bytes ? URL.createObjectURL(new Blob([bytes], { type })) : name;
|
|
375
|
+
const url = bytes ? URL.createObjectURL(new Blob([bytes], { type })) : app.url(name);
|
|
376
376
|
const id = w[i + 1];
|
|
377
377
|
const tag = w[i + 4];
|
|
378
378
|
const finished = () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
;; The
|
|
2
|
-
;; hears when its element comes into the viewport or leaves it. dom-bind-element gives the element with an id a
|
|
3
|
-
;; number, for dom-element (std/web/dom.sub). The reads of the
|
|
4
|
-
;; counts the images loading for a tag.
|
|
1
|
+
;; The document: the host module "dom". A heard event carries the id and tag its listener was given. A visibility
|
|
2
|
+
;; listener hears when its element comes into the viewport or leaves it. dom-bind-element gives the element with an id a
|
|
3
|
+
;; node number, for dom-element (std/web/dom.sub). The reads of the document are in std/web/dom.sub too.
|
|
4
|
+
;; dom-images-loading counts the images loading for a tag.
|
|
5
5
|
(provide dom-source dom-heard dom-image-loaded dom-image-failed dom-apply dom-bind-element dom-images-loading)
|
|
6
6
|
|
|
7
7
|
(const dom-source u32 1)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// A
|
|
2
|
-
// grants. A directory exists as long as it has a file in it, and there are no links or modes.
|
|
1
|
+
// A web program's files for fs.mjs, kept in memory under the program's working directory and checked against the
|
|
2
|
+
// program's grants. A directory exists as long as it has a file in it, and there are no links or modes.
|
|
3
3
|
import { mayPath, mayTree, pathDenied, unlimited } from '../grants.mjs';
|
|
4
4
|
import { sameFile, tempName } from '../fs.mjs';
|
|
5
5
|
|
|
@@ -17,7 +17,7 @@ const missing = () => Object.assign(new Error('No such file or directory'), { co
|
|
|
17
17
|
const unsupported = () => new Error('Operation not supported');
|
|
18
18
|
const under = (dir) => (dir === '/' ? '/' : `${dir}/`);
|
|
19
19
|
|
|
20
|
-
export function
|
|
20
|
+
export function memoryFiles(files, cwd, grants) {
|
|
21
21
|
const times = new Map();
|
|
22
22
|
const at = (k, p, tree = false) => {
|
|
23
23
|
const key = resolvePath(cwd, p);
|
|
@@ -144,7 +144,7 @@ export async function gpu(options = {}) {
|
|
|
144
144
|
case 1: {
|
|
145
145
|
const id = w[i + 1];
|
|
146
146
|
const node = theDom && theDom.nodeOf ? theDom.nodeOf(w[i + 2]) : null;
|
|
147
|
-
if (!node) caller.trap(`gpu: surface ${id}:
|
|
147
|
+
if (!node) caller.trap(`gpu: surface ${id}: this module has no dom, or node ${w[i + 2]} is not in the document`);
|
|
148
148
|
if (!(node instanceof HTMLCanvasElement)) caller.trap(`gpu: surface ${id}: node ${w[i + 2]} is not a canvas`);
|
|
149
149
|
if (options.offscreen) {
|
|
150
150
|
const texture = device.createTexture({
|
|
@@ -563,7 +563,7 @@ export async function gpu(options = {}) {
|
|
|
563
563
|
counted(to, 1);
|
|
564
564
|
try {
|
|
565
565
|
const bytes = app && app.files.get(resolvePath(app.cwd, name));
|
|
566
|
-
const blob = bytes ? new Blob([bytes]) : await fetch(name).then((r) => {
|
|
566
|
+
const blob = bytes ? new Blob([bytes]) : await fetch(app ? app.url(name) : name).then((r) => {
|
|
567
567
|
if (!r.ok) throw new Error(`the server answered ${r.status}`);
|
|
568
568
|
return r.blob();
|
|
569
569
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
;; The
|
|
1
|
+
;; The document's URL and history: the host module "location". When the user goes back or forward, each listener's tag
|
|
2
2
|
;; gets a changed event with the URL as its bytes. location-listen gives the listener's number, and location-unlisten
|
|
3
3
|
;; takes it.
|
|
4
4
|
(provide location-source location-changed location-get location-push location-replace location-back location-listen
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Starts a site's program with the files in
|
|
2
|
-
//
|
|
1
|
+
// Starts a site's program with the files in files.pack, the URL's arg parameters as its arguments, and the host modules
|
|
2
|
+
// it imports. The program, files.pack and assets.list sit beside the host directory. The assets load from the site
|
|
3
|
+
// when the program asks for them. It sets window.modules, sets window.ready once the program has started, and sets
|
|
3
4
|
// window.ended to the program's exit status and window.endReason to why it ended.
|
|
4
5
|
import { boot } from './subhost-web.mjs';
|
|
5
6
|
import { allGrants } from '../grants.mjs';
|
|
@@ -7,6 +8,7 @@ import { allGrants } from '../grants.mjs';
|
|
|
7
8
|
const query = new URLSearchParams(location.search);
|
|
8
9
|
const grants = allGrants();
|
|
9
10
|
const root = document.getElementById('root');
|
|
11
|
+
const built = new URL('../../', import.meta.url);
|
|
10
12
|
const made = {};
|
|
11
13
|
const makers = {
|
|
12
14
|
dom: async () => (await import('./dom.mjs')).dom(root),
|
|
@@ -26,9 +28,10 @@ const makers = {
|
|
|
26
28
|
const make = (name) => (made[name] ||= makers[name]());
|
|
27
29
|
|
|
28
30
|
window.app = await boot({
|
|
29
|
-
program: 'program/',
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
program: new URL('program/', built).href,
|
|
32
|
+
packs: [[new URL('files.pack', built).href, '/']],
|
|
33
|
+
assets: new URL('assets.list', built).href,
|
|
34
|
+
site: document.baseURI,
|
|
32
35
|
args: query.getAll('arg'),
|
|
33
36
|
grants,
|
|
34
37
|
onEnd: (code, reason) => {
|
|
@@ -36,8 +39,9 @@ window.app = await boot({
|
|
|
36
39
|
window.endReason = reason;
|
|
37
40
|
},
|
|
38
41
|
async modules(wanted) {
|
|
39
|
-
const
|
|
40
|
-
|
|
42
|
+
const names = [...wanted].filter((name) => Object.hasOwn(makers, name));
|
|
43
|
+
const given = await Promise.all(names.map(make));
|
|
44
|
+
const modules = Object.fromEntries(names.map((name, i) => [name, given[i]]));
|
|
41
45
|
window.modules = modules;
|
|
42
46
|
return modules;
|
|
43
47
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The web host: runs a program in a browser, with its files kept in memory. A web program has every grant and
|
|
2
2
|
// no fakes. Its workers can be given fewer grants and some fakes (worker-script.mjs).
|
|
3
3
|
import { createHost } from '../subhost-core.mjs';
|
|
4
4
|
import { fsModule } from '../fs.mjs';
|
|
5
5
|
import { allGrants, pairsFrom, requestOf } from '../grants.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { memoryFiles, resolvePath } from './fs.mjs';
|
|
7
7
|
|
|
8
8
|
const text = new TextDecoder();
|
|
9
9
|
const utf8 = new TextEncoder();
|
|
@@ -26,13 +26,13 @@ async function fetchBytes(url) {
|
|
|
26
26
|
return new Uint8Array(await response.arrayBuffer());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async function
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
async function fetchAssets(url) {
|
|
30
|
+
const urls = new Map();
|
|
31
|
+
for (const line of text.decode(await fetchBytes(url)).split('\n')) {
|
|
32
|
+
const tab = line.indexOf('\t');
|
|
33
|
+
if (tab >= 0) urls.set(resolvePath('/', line.slice(0, tab)), line.slice(tab + 1));
|
|
34
|
+
}
|
|
35
|
+
return urls;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function lines(line) {
|
|
@@ -92,36 +92,50 @@ export function importedModules(bytes) {
|
|
|
92
92
|
return names;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
async function fetchProgram(base, wasm) {
|
|
96
|
+
const names = text.decode(await fetchBytes(`${base}program`)).split('\n').filter((n) => n !== '');
|
|
97
|
+
await Promise.all(names.map(async (name) => {
|
|
98
|
+
wasm.set(name, await fetchBytes(`${base}${encodeURIComponent(name)}.wasm`));
|
|
99
|
+
}));
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The compiled modules for sessions: the compiled file lists them, each one is in <hash>.subc, and compiled.map holds
|
|
104
|
+
// their module map.
|
|
105
|
+
async function fetchCompiled(base) {
|
|
106
|
+
const compiled = new Map();
|
|
107
|
+
const listed = await fetch(`${base}compiled`);
|
|
108
|
+
if (!listed.ok) return compiled;
|
|
109
|
+
const list = new Uint8Array(await listed.arrayBuffer());
|
|
110
|
+
const hashes = text.decode(list).split('\n').filter((l) => l !== '').map((l) => l.slice(0, l.indexOf(' ')));
|
|
111
|
+
const fetched = async (name) => compiled.set(name, await fetchBytes(`${base}${name}`));
|
|
112
|
+
const names = hashes.map((h) => `${h}.subc`);
|
|
113
|
+
compiled.set('compiled', list);
|
|
114
|
+
await Promise.all((hashes.length > 0 ? ['compiled.map', ...names] : []).map(fetched));
|
|
115
|
+
return compiled;
|
|
116
|
+
}
|
|
117
|
+
|
|
95
118
|
// options.modules is the modules, or a function that takes the names the program imports and gives the modules.
|
|
119
|
+
// options.assets is the URL of the site's assets.list.
|
|
96
120
|
export async function boot(options) {
|
|
97
121
|
const base = options.program.endsWith('/') ? options.program : `${options.program}/`;
|
|
98
122
|
const files = options.files || new Map();
|
|
99
|
-
for (const [url, dir] of options.lists || []) await fetchListed(url, dir, files);
|
|
100
|
-
for (const [url, dir] of options.packs || []) unpack(await fetchBytes(url), dir, files);
|
|
101
|
-
|
|
102
|
-
const names = text.decode(await fetchBytes(`${base}program`)).split('\n').filter((n) => n !== '');
|
|
103
123
|
const wasm = new Map();
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let { modules } = options;
|
|
108
|
-
if (typeof modules === 'function') {
|
|
124
|
+
const names = fetchProgram(base, wasm);
|
|
125
|
+
const ask = options.modules;
|
|
126
|
+
const modules = typeof ask !== 'function' ? ask : names.then(() => {
|
|
109
127
|
const wanted = new Set();
|
|
110
128
|
for (const bytes of wasm.values()) for (const name of importedModules(bytes)) wanted.add(name);
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (hashes.length > 0) compiled.set('compiled.map', await fetchBytes(`${base}compiled.map`));
|
|
122
|
-
await Promise.all(hashes.map(async (h) => compiled.set(`${h}.subc`, await fetchBytes(`${base}${h}.subc`))));
|
|
123
|
-
}
|
|
124
|
-
return run({ ...options, files, modules, compiled }, names, wasm);
|
|
129
|
+
return ask(wanted);
|
|
130
|
+
});
|
|
131
|
+
const [listed, given, compiled, urls] = await Promise.all([
|
|
132
|
+
names,
|
|
133
|
+
modules,
|
|
134
|
+
fetchCompiled(base),
|
|
135
|
+
options.assets ? fetchAssets(options.assets) : new Map(),
|
|
136
|
+
...(options.packs || []).map(async ([url, dir]) => unpack(await fetchBytes(url), dir, files)),
|
|
137
|
+
]);
|
|
138
|
+
return run({ ...options, files, modules: given, compiled, urls }, listed, wasm);
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
export function run(options, names, wasm) {
|
|
@@ -136,9 +150,16 @@ export function run(options, names, wasm) {
|
|
|
136
150
|
const output = options.output || ((fd, bytes) => write(fd, decoders[fd].decode(bytes, { stream: true })));
|
|
137
151
|
|
|
138
152
|
const app = { files, cwd, entry: null, ended: false, realTime: [] };
|
|
153
|
+
// app.url gives the URL to load a file from when it is not in memory.
|
|
154
|
+
const site = options.site || (globalThis.document ? document.baseURI : globalThis.location.href);
|
|
155
|
+
const urls = options.urls || new Map();
|
|
156
|
+
app.url = (name) => {
|
|
157
|
+
const path = resolvePath(cwd, name);
|
|
158
|
+
return new URL((urls.get(path) || path.slice(1)).split('/').map(encodeURIComponent).join('/'), site).href;
|
|
159
|
+
};
|
|
139
160
|
const modules = new Map();
|
|
140
161
|
for (const [name, make] of Object.entries(options.modules || {})) modules.set(name, (caller) => make(caller, app));
|
|
141
|
-
if (!modules.has('fs')) modules.set('fs', fsModule(
|
|
162
|
+
if (!modules.has('fs')) modules.set('fs', fsModule(memoryFiles(files, cwd, grants)));
|
|
142
163
|
const report = (message) => output(2, utf8.encode(`error: ${message}\n`));
|
|
143
164
|
const ended = (code, reason) => {
|
|
144
165
|
app.ended = true;
|