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.
Files changed (61) hide show
  1. package/build/compiler/{checker.4dd4556dd421@0+0.wasm → checker.1d67e1d4e91a@0+0.wasm} +0 -0
  2. package/build/compiler/{expander.967ce443d379@0+0.wasm → expander.34110a31a949@0+0.wasm} +0 -0
  3. package/build/compiler/{main.4fc52f204d54@0+0.wasm → main.ca9b5089c24d@0+0.wasm} +0 -0
  4. package/build/compiler/program +4 -4
  5. package/build/compiler/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
  6. package/build/sub/{build.459c5b9412bd@0+0.wasm → build.eef7dfa467ec@0+0.wasm} +0 -0
  7. package/build/sub/{common.1edc613607a0@0+0.wasm → common.7617940db2dc@0+0.wasm} +0 -0
  8. package/build/sub/deflate.23f7c4679260@0+0.wasm +0 -0
  9. package/build/sub/{doc.0775df5de6fe@0+0.wasm → doc.92d6d82d99c1@0+0.wasm} +0 -0
  10. package/build/sub/{doc.ef9965d5d402@0+0.wasm → doc.dab689b811ca@0+0.wasm} +0 -0
  11. package/build/sub/{fetch.96b2d10f025e@0+0.wasm → fetch.3367ff4f88c0@0+0.wasm} +0 -0
  12. package/build/sub/{files.ad0469fdf6fa@0+0.wasm → files.5ae9d3cebeca@0+0.wasm} +0 -0
  13. package/build/sub/gzip.16e372eda1cf@0+0.wasm +0 -0
  14. package/build/sub/{hash.660682e4f491@0+0.wasm → hash.c57c141b4862@0+0.wasm} +0 -0
  15. package/build/sub/{html.a23126eb949a@0+0.wasm → html.803f73c35c4a@0+0.wasm} +0 -0
  16. package/build/sub/{manifest.ef317480deb0@0+0.wasm → manifest.da52a88c7af7@0+0.wasm} +0 -0
  17. package/build/sub/{map.f455cca6a42a@0+0.wasm → map.5c8084999550@0+0.wasm} +0 -0
  18. package/build/sub/program +22 -22
  19. package/build/sub/{root.f10a4906f460@0+0.wasm → root.78253db36643@0+0.wasm} +0 -0
  20. package/build/sub/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
  21. package/build/sub/sha256.a6c32b04d799@0+0.wasm +0 -0
  22. package/build/sub/site.47fd989be8fb@0+0.wasm +0 -0
  23. package/build/sub/site.c908b1f6db12@0+0.wasm +0 -0
  24. package/build/sub/{store.a24d7a25daa6@0+0.wasm → store.048c77bb124d@0+0.wasm} +0 -0
  25. package/build/sub/sub.a618f89f2ec8@0+0.wasm +0 -0
  26. package/build/sub/{test.9bd59c73f65e@0+0.wasm → test.b529bf52ba3e@0+0.wasm} +0 -0
  27. package/build/sub/{text.1bc90482bd43@0+0.wasm → text.b214933a4984@0+0.wasm} +0 -0
  28. package/build/sub/{upgrade.39ee449b00d5@0+0.wasm → upgrade.7ec5651ed2e9@0+0.wasm} +0 -0
  29. package/package.json +1 -1
  30. package/std/http/server.sub +7 -7
  31. package/std/http/static.sub +66 -30
  32. package/std/package/manifest.sub +9 -4
  33. package/std/package/site.sub +209 -15
  34. package/std/package.sub +2 -2
  35. package/std/test/browser.sub +20 -14
  36. package/std/test/build.sub +8 -6
  37. package/std/test/page/agent.mjs +8 -2
  38. package/std/web/canvas.sub +1 -1
  39. package/std/web/dom.sub +5 -5
  40. package/std/web/patches.sub +8 -8
  41. package/substrate/VERSION +1 -1
  42. package/substrate/compiler/expander.sub +2 -3
  43. package/substrate/compiler/roots.sub +12 -5
  44. package/substrate/host/grants.mjs +3 -3
  45. package/substrate/host/node/net.mjs +17 -2
  46. package/substrate/host/node/worker.mjs +3 -1
  47. package/substrate/host/subhost.mjs +4 -1
  48. package/substrate/host/web/audio.mjs +1 -1
  49. package/substrate/host/web/dom.mjs +3 -3
  50. package/substrate/host/web/dom.sub +4 -4
  51. package/substrate/host/web/fs.mjs +3 -3
  52. package/substrate/host/web/gpu.mjs +2 -2
  53. package/substrate/host/web/location.sub +1 -1
  54. package/substrate/host/web/start.mjs +11 -7
  55. package/substrate/host/web/subhost-web.mjs +54 -33
  56. package/substrate/host/web/worker-script.mjs +4 -4
  57. package/substrate/host/web/worker.mjs +4 -4
  58. package/substrate/host/worker.mjs +1 -1
  59. package/build/sub/site.1732b58ec0e8@0+0.wasm +0 -0
  60. package/build/sub/site.ae3913b0b0f4@0+0.wasm +0 -0
  61. package/build/sub/sub.2e3092140706@0+0.wasm +0 -0
@@ -4,11 +4,11 @@ syntax.32f3e40b61c7@0+0
4
4
  path.5a6286472cd4@0+0
5
5
  fs.01b8aab4a9a8@0+0
6
6
  reader.694b03770bf0@0+0
7
- roots.cefd10e93684@0+0
7
+ roots.2f1ad1a79459@0+0
8
8
  running.0d25dbaec018@0+0
9
9
  core.f2defb75e00b@0+0
10
10
  ops.515b5e05f646@0+0
11
- checker.4dd4556dd421@0+0
11
+ checker.1d67e1d4e91a@0+0
12
12
  leb128.15d814b7b4f2@0+0
13
13
  binary.2d43b43d1600@0+0
14
14
  serialize.b0d82f3d63d2@0+0
@@ -16,6 +16,6 @@ compile.5d8f6407af2a@0+0
16
16
  sha256.a6c32b04d799@0+0
17
17
  link.b826ea7c8f17@0+0
18
18
  macros.b269e0c42784@0+0
19
- expander.967ce443d379@0+0
19
+ expander.34110a31a949@0+0
20
20
  prune.406215c3e4f8@0+0
21
- main.4fc52f204d54@0+0
21
+ main.ca9b5089c24d@0+0
package/build/sub/program CHANGED
@@ -3,46 +3,46 @@ process.7106571fa723@0+0
3
3
  path.5a6286472cd4@0+0
4
4
  path.6891ffea73ca@0+0
5
5
  fs.01b8aab4a9a8@0+0
6
- common.1edc613607a0@0+0
6
+ common.7617940db2dc@0+0
7
7
  grants.c54776c0d7e3@0+0
8
8
  writer.00b7802cbe39@0+0
9
9
  syntax.32f3e40b61c7@0+0
10
10
  reader.694b03770bf0@0+0
11
- roots.cefd10e93684@0+0
11
+ roots.2f1ad1a79459@0+0
12
12
  read.0b93933d62a2@0+0
13
- manifest.ef317480deb0@0+0
13
+ manifest.da52a88c7af7@0+0
14
14
  git.dc4c0e757ebe@0+0
15
- files.ad0469fdf6fa@0+0
15
+ files.5ae9d3cebeca@0+0
16
16
  sha256.a6c32b04d799@0+0
17
- hash.660682e4f491@0+0
18
- store.a24d7a25daa6@0+0
19
- map.f455cca6a42a@0+0
17
+ hash.c57c141b4862@0+0
18
+ store.048c77bb124d@0+0
19
+ map.5c8084999550@0+0
20
20
  env.e5451da6e747@0+0
21
21
  events.bf5473ebf220@0+0
22
- build.459c5b9412bd@0+0
22
+ build.eef7dfa467ec@0+0
23
23
  pack.e02836264f6a@0+0
24
- site.ae3913b0b0f4@0+0
25
- site.1732b58ec0e8@0+0
26
- fetch.7790d4943758@0+0
27
- stream.126e3bd5d626@0+0
28
- worker.762a9d642af0@0+0
29
24
  deflate.23f7c4679260@0+0
30
25
  inflate.59bb61f14001@0+0
31
26
  gzip.16e372eda1cf@0+0
27
+ site.47fd989be8fb@0+0
28
+ site.c908b1f6db12@0+0
29
+ fetch.7790d4943758@0+0
30
+ stream.126e3bd5d626@0+0
31
+ worker.762a9d642af0@0+0
32
32
  tar.2a633861e654@0+0
33
- fetch.96b2d10f025e@0+0
34
- doc.ef9965d5d402@0+0
33
+ fetch.3367ff4f88c0@0+0
34
+ doc.dab689b811ca@0+0
35
35
  html.e9ed37f34977@0+0
36
- html.a23126eb949a@0+0
37
- text.1bc90482bd43@0+0
38
- root.f10a4906f460@0+0
39
- doc.0775df5de6fe@0+0
36
+ html.803f73c35c4a@0+0
37
+ text.b214933a4984@0+0
38
+ root.78253db36643@0+0
39
+ doc.92d6d82d99c1@0+0
40
40
  timer.f35eb62ae446@0+0
41
41
  chromium.3d5818f2707d@0+0
42
- test.9bd59c73f65e@0+0
42
+ test.b529bf52ba3e@0+0
43
43
  utf8.e7216bd054e1@0+0
44
44
  json.55590d2f518a@0+0
45
45
  sha512.c24a792d7891@0+0
46
46
  base64.a26bdcf60a2d@0+0
47
- upgrade.39ee449b00d5@0+0
48
- sub.2e3092140706@0+0
47
+ upgrade.7ec5651ed2e9@0+0
48
+ sub.a618f89f2ec8@0+0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-lang",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "A Lisp for writing WebAssembly, and the substrate command that builds, runs and tests it",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,10 +22,10 @@
22
22
 
23
23
  (provide http-server http-request http-check http-hook http-serve! http-server-close! http-server-shutdown!
24
24
  http-stop-on-signals! http-hook! http-unhook! http-log-access http-request-header http-request-server
25
- http-request-gone? http-request-address http-request-https? http-request-deadline! http-add-header!
26
- http-respond! http-header! http-length! http-write! http-send! http-send-text! http-send-stream! http-finish!
27
- http-take! http-read-body! http-answer-text! http-redirect! http-trapped http-connect! http-connection-feed!
28
- http-connection-close!)
25
+ http-request-gone? http-request-address http-request-https? http-accepts-gzip? http-request-deadline!
26
+ http-add-header! http-respond! http-header! http-length! http-write! http-send! http-send-text!
27
+ http-send-stream! http-finish! http-take! http-read-body! http-answer-text! http-redirect! http-trapped
28
+ http-connect! http-connection-feed! http-connection-close!)
29
29
 
30
30
  (const default-body-limit u32 1048576)
31
31
  (const most-dropped u64 262144)
@@ -630,8 +630,8 @@
630
630
  (unless (or (== (@ q i) #\.) (== (@ q i) #\0)) (return #f)))
631
631
  #t)
632
632
 
633
- ;; accepts-gzip? reads Accept-Encoding: gzip, or else *, with a weight above 0.
634
- (define (accepts-gzip? [r (ptr http-request)]) -> bool
633
+ ;; http-accepts-gzip? reads Accept-Encoding: gzip, or else *, with a weight above 0.
634
+ (define (http-accepts-gzip? [r (ptr http-request)]) -> bool
635
635
  (let ([v (http-request-header r "accept-encoding")] [i u32 0] [gzip i32 -1] [any i32 -1])
636
636
  (while (< i (-> v length))
637
637
  (let ([end i] [k i])
@@ -653,7 +653,7 @@
653
653
 
654
654
  (define (zip? [r (ptr http-request)]) -> bool
655
655
  (and (> (-> (http-request-server r) compress) 0) (-> r compressible) (not (-> r encoded)) (has-body? r)
656
- (!= (-> r status) 206) (accepts-gzip? r)))
656
+ (!= (-> r status) 206) (http-accepts-gzip? r)))
657
657
 
658
658
  ;; One deflater for every whole answer and event, each done before the next begins; zipped holds its output.
659
659
  (define shared deflater)
@@ -1,15 +1,17 @@
1
1
  ;; Files: http-send-file! answers with a file under the root, as Flask's send_from_directory does, and http-static! with
2
2
  ;; the file at the request's path, a directory's index.html, or a redirect adding a final slash. Answers carry
3
3
  ;; Last-Modified and an ETag, are 304 or 412 as preconditions require, hold one range when asked, and have Cache-Control
4
- ;; no-cache unless cache gives another value for the path. A part that is . or .., or that starts with a dot other than
5
- ;; .well-known, is not found. An empty root is the current directory.
4
+ ;; no-cache unless cache gives another value for the path. When a file has a gzipped copy beside it, named name.gz, a
5
+ ;; request that takes gzip gets the copy. A part that is . or .., or that starts with a dot other than .well-known, is
6
+ ;; not found. An empty root is the current directory. http-hashed-cache caches a path for a year when one of its
7
+ ;; directories is named by 16 hex digits, as in the sites that substrate site --release writes.
6
8
  (require-unqualified host/fs)
7
9
  (require-unqualified "http.sub")
8
10
  (require-unqualified "server.sub")
9
11
  (require-unqualified "conditional.sub")
10
12
  (require-unqualified "url.sub")
11
13
 
12
- (provide http-files http-static! http-send-file!)
14
+ (provide http-files http-static! http-send-file! http-hashed-cache)
13
15
 
14
16
  (struct http-files [root string] [cache (fn (string) -> string)])
15
17
 
@@ -185,53 +187,87 @@
185
187
  (let ([value (if (== (-> files cache) 0) "" ((-> files cache) path))])
186
188
  (if (== (-> value length) 0) "no-cache" value)))
187
189
 
188
- (define (head-of! [r (ptr http-request)] [name string] [partial bool] [s (ptr span)] [size i64])
190
+ (define (hex-digit? [c u8]) -> bool (or (and (>= c #\0) (<= c #\9)) (and (>= c #\a) (<= c #\f))))
191
+
192
+ (define (hash-part? [part string]) -> bool
193
+ (when (!= (-> part length) 16) (return #f))
194
+ (for ([i u32 0]) (< i 16) (+= i 1) (unless (hex-digit? (@ part i)) (return #f)))
195
+ #t)
196
+
197
+ (define (http-hashed-cache [path string]) -> string
198
+ (let ([start u32 0])
199
+ (for ([i u32 0]) (< i (-> path length)) (+= i 1)
200
+ (when (== (@ path i) #\/)
201
+ (when (hash-part? (slice path start i)) (return "public, max-age=31536000, immutable"))
202
+ (set! start (+ i 1))))
203
+ ""))
204
+
205
+ (struct zipped [name string] [info fs-info] [found bool])
206
+
207
+ (define (zipped! [z (ptr zipped)] [name string])
208
+ (let ([b (make-buffer)])
209
+ (buffer-add-string! (& b) name)
210
+ (buffer-add-string! (& b) ".gz")
211
+ (set! (-> z name) (buffer-string (& b)))
212
+ (set! (-> z found) (and (fs-stat (-> z name) (& (-> z info)) 0) (== (-> z info kind) fs-file)))))
213
+
214
+ (define (head-of! [r (ptr http-request)] [name string] [encoded bool] [partial bool] [s (ptr span)] [size i64])
189
215
  (http-respond! r (if partial 206 200))
190
216
  (when partial (content-range! r s size))
191
- (http-header! r "content-type" (content-type name)))
217
+ (http-header! r "content-type" (content-type name))
218
+ (when encoded (http-header! r "content-encoding" "gzip")))
192
219
 
193
220
  (define (failed! [r (ptr http-request)]) (http-answer-text! r 500 "cannot read the file"))
194
221
 
195
- (define (body! [r (ptr http-request)] [name string] [partial bool] [s (ptr span)] [size i64])
222
+ (define (body! [r (ptr http-request)] [name string] [file string] [encoded bool] [partial bool] [s (ptr span)]
223
+ [size i64])
196
224
  (let ([n (- (-> s end) (-> s start))])
197
225
  (cond [(== (-> r method) "HEAD")
198
- (head-of! r name partial s size)
226
+ (head-of! r name encoded partial s size)
199
227
  (http-length! r n)
200
228
  (http-finish! r)]
201
229
  [(<= size whole)
202
230
  (let ([f (slice u8)])
203
- (if (not (fs-read-file name (& f) 0))
231
+ (if (not (fs-read-file file (& f) 0))
204
232
  (failed! r)
205
233
  (begin
206
- (head-of! r name partial s size)
234
+ (head-of! r name encoded partial s size)
207
235
  (http-send! r (slice f (cast u32 (-> s start)) (cast u32 (-> s end))))
208
236
  (free (-> f items)))))]
209
237
  [else
210
- (let ([h u32 0] [file u32 0])
211
- (if (not (and (fs-open-file name fs-read-flag (& h) 0) (fs-read-stream h (-> s start) 0 (& file) 0)))
238
+ (let ([h u32 0] [stream u32 0])
239
+ (if (not (and (fs-open-file file fs-read-flag (& h) 0) (fs-read-stream h (-> s start) 0 (& stream) 0)))
212
240
  (failed! r)
213
241
  (begin
214
- (head-of! r name partial s size)
215
- (http-send-stream! r file n))))])))
242
+ (head-of! r name encoded partial s size)
243
+ (http-send-stream! r stream n))))])))
216
244
 
217
- ;; send! answers with the file at name, whose path under the root is path.
245
+ ;; send! answers with the file at name, whose path under the root is path, or with its gzipped copy. When there is a
246
+ ;; copy, send! adds Vary itself and marks the answer encoded. The server then neither compresses it nor adds Vary again.
218
247
  (define (send! [r (ptr http-request)] [files (ptr http-files)] [name string] [path string] [info (ptr fs-info)])
219
- (let ([size (-> info size)] [tag (make-buffer)] [s span] [partial i32 0])
220
- (span! (& s) 0 size)
221
- (etag! (& tag) info)
222
- (http-add-header! r "cache-control" (cache-of files path))
223
- (unless (http-conditional! r (buffer-string (& tag)) (-> info modified))
224
- (set! partial (ranges! r size (buffer-string (& tag)) (-> info modified) (& s)))
225
- (if (< partial 0)
226
- (let ([all span])
227
- (span! (& all) -1 0)
228
- (http-respond! r 416)
229
- (content-range! r (& all) size)
230
- (http-header! r "content-type" "text/plain; charset=utf-8")
231
- (http-send-text! r "range not satisfiable"))
232
- (begin
233
- (set! (-> r ranges) #t)
234
- (body! r name (> partial 0) (& s) size))))
248
+ (let* ([z zipped] [tag (make-buffer)] [s span] [partial i32 0])
249
+ (zipped! (& z) name)
250
+ (let* ([encoded (and (-> z found) (http-accepts-gzip? r))] [sent (if encoded (& (-> z info)) info)]
251
+ [size (-> sent size)])
252
+ (span! (& s) 0 size)
253
+ (etag! (& tag) sent)
254
+ (http-add-header! r "cache-control" (cache-of files path))
255
+ (when (-> z found)
256
+ (http-add-header! r "vary" "accept-encoding")
257
+ (set! (-> r encoded) #t))
258
+ (unless (http-conditional! r (buffer-string (& tag)) (-> sent modified))
259
+ (set! partial (if encoded 0 (ranges! r size (buffer-string (& tag)) (-> sent modified) (& s))))
260
+ (if (< partial 0)
261
+ (let ([all span])
262
+ (span! (& all) -1 0)
263
+ (http-respond! r 416)
264
+ (content-range! r (& all) size)
265
+ (http-header! r "content-type" "text/plain; charset=utf-8")
266
+ (http-send-text! r "range not satisfiable"))
267
+ (begin
268
+ (set! (-> r ranges) (not encoded))
269
+ (body! r name (if encoded (-> z name) name) encoded (> partial 0) (& s) size)))))
270
+ (free (-> (-> z name) bytes))
235
271
  (buffer-free! (& tag))))
236
272
 
237
273
  (define (not-found! [r (ptr http-request)]) (http-answer-text! r 404 "not found"))
@@ -6,7 +6,8 @@
6
6
  ;; The substrate clause gives the oldest substrate the package builds with. (substrate "0.10.0") also takes later 0.10
7
7
  ;; releases, and (substrate "1.2.0") takes later 1.x releases. Every clause but version is optional, and so are a path
8
8
  ;; dependency's hash and a site's html and files. A files part gives the program the files matching its paths, from its
9
- ;; from directory, under the directory it names; * matches any text in a name and ** any directories. (browser) says
9
+ ;; from directory, under the directory it names; * matches any text in a name and ** any directories. (from name)
10
+ ;; takes them from the directory that name stands for in a require: a dependency, std, compiler or host. (browser) says
10
11
  ;; that the package's tests open browsers with std/test/browser.sub.
11
12
  (require-unqualified "read.sub")
12
13
  (require-unqualified host/fs)
@@ -16,7 +17,7 @@
16
17
  package-name? full-hash? version-before? version-compatible?)
17
18
 
18
19
  (struct dependency [name string] [path string] [url string] [hash string] [line u32] [column u32])
19
- (struct site-files [dir string] [from string] [patterns (slice string)])
20
+ (struct site-files [dir string] [from string] [root string] [patterns (slice string)])
20
21
  (struct declared-site [name string] [program string] [html string] [files (slice site-files)] [line u32] [column u32])
21
22
  (struct manifest [name string] [version string] [major u32] [substrate string] [exports (slice string)]
22
23
  [dependencies (slice dependency)] [tests (slice string)]
@@ -267,10 +268,14 @@
267
268
  (define (files-of [file string] [c (ptr data)] [a (ptr arena)] [f (ptr site-files)] [err (ptr buffer)]) -> bool
268
269
  (let* ([items (items-of c)] [k u32 2] [n u32 0] [ps (new a string (-> items length))])
269
270
  (set! (-> f from) "")
271
+ (set! (-> f root) "")
270
272
  (unless (and (>= (-> items length) 3) (string-of (& (@ items 1)) (& (-> f dir))) (starts? (-> f dir) "/"))
271
273
  (return (failed-at! err file c files-form)))
272
274
  (when (list-of? (& (@ items 2)) "from" 2)
273
- (unless (string-of (& (@ (items-of (& (@ items 2))) 1)) (& (-> f from))) (return (failed-at! err file c files-form)))
275
+ (let ([given (& (@ (items-of (& (@ items 2))) 1))])
276
+ (set! (-> f root) (symbol-of given))
277
+ (unless (or (> (-> f root length) 0) (string-of given (& (-> f from))))
278
+ (return (failed-at! err file c files-form))))
274
279
  (set! k 3))
275
280
  (for ([i u32 k]) (< i (-> items length)) (+= i 1)
276
281
  (unless (and (string-of (& (@ items i)) (& (@ ps n))) (> (-> (@ ps n) length) 0))
@@ -284,7 +289,7 @@
284
289
  "a site is (name \"program.sub\" (html \"file.html\") (files \"/dir\" \"path\" ...) ...), and the parts after the program are optional")
285
290
 
286
291
  (define files-form string
287
- "a site's files are (files \"/dir\" (from \"directory\") \"path\" ...), where from is optional, and in a path * matches any text in a name and ** matches any directories")
292
+ "a site's files are (files \"/dir\" (from \"directory\") \"path\" ...) or (files \"/dir\" (from name) \"path\" ...), where from is optional, and in a path * matches any text in a name and ** matches any directories")
288
293
 
289
294
  (define (module-name? [s string]) -> bool
290
295
  (let ([start u32 0])
@@ -1,13 +1,20 @@
1
- ;; Sites: directories that a static server can serve. A site holds index.html, the web host, the program, the assets
2
- ;; listed in assets.list, and files.pack with the files the site declares. site-write! writes all of it except a
3
- ;; program given as a file, which the caller builds into the site's program directory, as cmd/sub/site.sub and
4
- ;; std/test/browser.sub do.
1
+ ;; Sites: directories that a static server can serve. A site holds index.html, the web host, the program, and
2
+ ;; files.pack with the files the site declares. It also holds the assets from beside the program, and assets.list lists
3
+ ;; them. The program reads the declared files as files and loads the assets by URL. site-write! writes all of it except
4
+ ;; a program given as a file. The caller builds that program into the site's program directory, as cmd/sub/site.sub and
5
+ ;; std/test/browser.sub do. Files from a name need the program's module map in compiler/roots, so both callers load it
6
+ ;; first. site-release! makes a written site a release (below).
7
+ (require-unqualified compiler/macros)
8
+ (require-unqualified compiler/roots)
5
9
  (require-unqualified host/fs)
10
+ (require-unqualified std/crypto/sha256)
11
+ (require-unqualified std/compress/deflate)
12
+ (require-unqualified std/compress/gzip)
6
13
  (require-unqualified "manifest.sub")
7
14
  (require-unqualified "pack.sub")
8
15
  (require-unqualified "path.sub")
9
16
 
10
- (provide site-plan site-named? site-declared site-write!)
17
+ (provide site-plan site-named? site-declared site-write! site-release!)
11
18
 
12
19
  (struct site-plan [name string] [program string] [html string] [files (slice site-files)] [base string])
13
20
 
@@ -36,6 +43,19 @@
36
43
  (define (written! [path string] [b (slice u8)])
37
44
  (let ([why error]) (unless (fs-write-file path b (& why)) (throw site-failed (error-text (& why))))))
38
45
 
46
+ (define (strings-of [b (ptr buffer)]) -> (slice string)
47
+ (slice (cast (ptr string) (-> b bytes)) (/ (-> b length) (sizeof string))))
48
+
49
+ (define (added! [b (ptr buffer)] [s string]) (buffer-add! b (slice (cast (ptr u8) (& s)) (sizeof string))))
50
+
51
+ (define (sorted! [ps (slice string)])
52
+ (for ([i u32 1]) (< i (-> ps length)) (+= i 1)
53
+ (let ([s (@ ps i)] [j i])
54
+ (while (and (> j 0) (< s (@ ps (- j 1))))
55
+ (set! (@ ps j) (@ ps (- j 1)))
56
+ (-= j 1))
57
+ (set! (@ ps j) s))))
58
+
39
59
  (define (copy-tree! [from string] [to string] [base string] [files (ptr buffer)])
40
60
  (let ([names (names-in from)])
41
61
  (made! to)
@@ -110,9 +130,19 @@
110
130
  (set! start (+ i 1))))
111
131
  (slice (cast (ptr string) (-> out bytes)) (/ (-> out length) (sizeof string)))))
112
132
 
133
+ (define (root-directory [p (ptr site-plan)] [name string]) -> string
134
+ (let ([dir datum '#f])
135
+ (try (set! dir (named-directory (string->datum (-> p program)) (string->datum name)))
136
+ [catch compile-error (message blamed loc) (set! dir '#f)])
137
+ (unless (string? dir)
138
+ (throw site-failed (joined (joined "site: (from " name) ") names nothing that the site's program can require")))
139
+ (datum->string dir)))
140
+
113
141
  (define (packed! [p (ptr site-plan)] [g (ptr site-files)] [files (ptr buffer)])
114
- (let ([base (if (== (-> g from length) 0) (-> p base) (resolved-path (-> p base) (-> g from)))] [found (make-buffer)]
115
- [err (make-buffer)])
142
+ (let ([base (cond [(> (-> g root length) 0) (root-directory p (-> g root))]
143
+ [(== (-> g from length) 0) (-> p base)]
144
+ [else (resolved-path (-> p base) (-> g from))])]
145
+ [found (make-buffer)] [err (make-buffer)])
116
146
  (for ([i u32 0]) (< i (-> g patterns length)) (+= i 1)
117
147
  (let ([before (-> found length)])
118
148
  (matched! base "" (parts-of (@ (-> g patterns) i)) 0 (& found))
@@ -143,14 +173,9 @@
143
173
  (let ([assets (joined-path (parent-of (-> p program)) "assets")] [found (make-buffer)])
144
174
  (when (== (kind-of assets) fs-directory)
145
175
  (copy-tree! assets (joined-path out "assets") "assets" (& found))
146
- (let ([ps (cast (ptr string) (-> found bytes))] [n (/ (-> found length) (sizeof string))])
147
- (for ([i u32 1]) (< i n) (+= i 1)
148
- (let ([s (@ ps i)] [j i])
149
- (while (and (> j 0) (< s (@ ps (- j 1))))
150
- (set! (@ ps j) (@ ps (- j 1)))
151
- (-= j 1))
152
- (set! (@ ps j) s)))
153
- (for ([i u32 0]) (< i n) (+= i 1)
176
+ (let ([ps (strings-of (& found))])
177
+ (sorted! ps)
178
+ (for ([i u32 0]) (< i (-> ps length)) (+= i 1)
154
179
  (buffer-add-string! (& list) (@ ps i))
155
180
  (buffer-add-byte! (& list) 10)))))
156
181
  (written! (joined-path out "assets.list") (buffer-bytes (& list)))
@@ -163,3 +188,172 @@
163
188
  (joined-path out "index.html"))
164
189
  (when (== (kind-of (-> p program)) fs-directory)
165
190
  (copy-tree! (-> p program) (joined-path out "program") "" (& files)))))
191
+
192
+ ;; ---- releases ----
193
+
194
+ ;; A release keeps index.html and the assets where they are. It moves the host, the program, files.pack and assets.list
195
+ ;; into a directory named by a hash of their paths and bytes, the first 16 hex digits of a SHA-256. Each asset also gets
196
+ ;; a copy in a directory named by its own hash, and assets.list gives each copy's path after a tab. A server can cache
197
+ ;; all of these for good, as http-hashed-cache in std/http/static.sub does. index.html loads the program from its new
198
+ ;; directory and preloads the program's files. A file gets a gzipped copy, name.gz, when gzip shrinks it to nine tenths
199
+ ;; of its size or less.
200
+
201
+ ;; site-release! makes the site written in out a release, or returns #f with the reason in err.
202
+ (define (site-release! [out string] [err (ptr buffer)]) -> bool
203
+ (try (begin (site-released! out) #t)
204
+ [catch site-failed (why)
205
+ (buffer-add-string! err why)
206
+ #f]))
207
+
208
+ (define (read! [path string]) -> (slice u8)
209
+ (let ([b (slice u8)] [why error])
210
+ (unless (fs-read-file path (& b) (& why)) (throw site-failed (error-text (& why))))
211
+ b))
212
+
213
+ (define (renamed! [from string] [to string])
214
+ (let ([why error]) (unless (fs-rename from to (& why)) (throw site-failed (error-text (& why))))))
215
+
216
+ (define (slashed [a string] [b string]) -> string (if (== (-> a length) 0) b (joined (joined a "/") b)))
217
+
218
+ (define (walked! [dir string] [rel string] [into (ptr buffer)])
219
+ (let ([names (names-in dir)])
220
+ (for ([i u32 0]) (< i (-> names length)) (+= i 1)
221
+ (let ([f (joined-path dir (@ names i))] [r (slashed rel (@ names i))])
222
+ (if (== (kind-of f) fs-directory) (walked! f r into) (added! into r))))))
223
+
224
+ (define (hex! [b (ptr buffer)] [bytes (slice u8)])
225
+ (let ([digits "0123456789abcdef"])
226
+ (for ([i u32 0]) (< i (-> bytes length)) (+= i 1)
227
+ (buffer-add-byte! b (@ digits (>> (cast u32 (@ bytes i)) 4)))
228
+ (buffer-add-byte! b (@ digits (bit-and (cast u32 (@ bytes i)) 15))))))
229
+
230
+ (define (release-name [out string] [paths (slice string)]) -> string
231
+ (let ([s sha256] [digest (array u8 32)] [name (make-buffer)] [size (make-buffer)])
232
+ (sha256-init! (& s))
233
+ (for ([i u32 0]) (< i (-> paths length)) (+= i 1)
234
+ (let ([b (read! (joined-path out (@ paths i)))])
235
+ (set! (-> size length) 0)
236
+ (buffer-add-byte! (& size) 0)
237
+ (buffer-add-u32! (& size) (-> b length))
238
+ (sha256-add! (& s) (cast (slice u8) (@ paths i)))
239
+ (sha256-add! (& s) (buffer-bytes (& size)))
240
+ (sha256-add! (& s) b)
241
+ (free (-> b items))))
242
+ (sha256-end! (& s) (slice digest))
243
+ (hex! (& name) (slice (slice digest) 0 8))
244
+ (buffer-free! (& size))
245
+ (buffer-string (& name))))
246
+
247
+ (define (found [s string] [part string]) -> i64
248
+ (for ([i u32 0]) (<= (+ i (-> part length)) (-> s length)) (+= i 1)
249
+ (when (== (slice s i (+ i (-> part length))) part) (return (cast i64 i))))
250
+ -1)
251
+
252
+ (define (found-before [s string] [part string] [before u32]) -> i64
253
+ (let ([i before])
254
+ (while (> i 0)
255
+ (-= i 1)
256
+ (when (and (<= (+ i (-> part length)) before) (== (slice s i (+ i (-> part length))) part))
257
+ (return (cast i64 i))))
258
+ -1))
259
+
260
+ (define (url-safe? [c u8]) -> bool
261
+ (or (and (>= c #\a) (<= c #\z)) (and (>= c #\A) (<= c #\Z)) (and (>= c #\0) (<= c #\9))
262
+ (== c 45) (== c 95) (== c 46) (== c 33) (== c 126) (== c 42) (== c 39) (== c 40) (== c 41)))
263
+
264
+ ;; url! escapes each part of a path as encodeURIComponent does in the web host (subhost-web.mjs).
265
+ (define (url! [b (ptr buffer)] [path string])
266
+ (let ([digits "0123456789ABCDEF"])
267
+ (for ([i u32 0]) (< i (-> path length)) (+= i 1)
268
+ (let ([c (@ path i)])
269
+ (if (or (url-safe? c) (== c #\/))
270
+ (buffer-add-byte! b c)
271
+ (begin
272
+ (buffer-add-byte! b 37)
273
+ (buffer-add-byte! b (@ digits (>> (cast u32 c) 4)))
274
+ (buffer-add-byte! b (@ digits (bit-and (cast u32 c) 15)))))))))
275
+
276
+ (define (link! [b (ptr buffer)] [name string] [path string])
277
+ (buffer-add-string! b "<link rel=\"preload\" href=\"")
278
+ (buffer-add-string! b name)
279
+ (buffer-add-byte! b #\/)
280
+ (url! b path)
281
+ (buffer-add-string! b "\" as=\"fetch\" crossorigin>\n"))
282
+
283
+ (define (released-html! [out string] [name string] [fetched (slice string)])
284
+ (let* ([path (joined-path out "index.html")] [html (cast string (read! path))] [at (found html "host/web/start.mjs")]
285
+ [tag (if (< at 0) -1 (found-before html "<script" (cast u32 at)))] [b (make-buffer)])
286
+ (when (< tag 0)
287
+ (throw site-failed "site: index.html has no script that loads host/web/start.mjs, so it cannot be released"))
288
+ (buffer-add-string! (& b) (slice html 0 (cast u32 tag)))
289
+ (for ([i u32 0]) (< i (-> fetched length)) (+= i 1) (link! (& b) name (@ fetched i)))
290
+ (buffer-add-string! (& b) (slice html (cast u32 tag) (cast u32 at)))
291
+ (buffer-add-string! (& b) name)
292
+ (buffer-add-byte! (& b) #\/)
293
+ (buffer-add-string! (& b) (slice html (cast u32 at) (-> html length)))
294
+ (written! path (buffer-bytes (& b)))
295
+ (buffer-free! (& b))
296
+ (free (-> html bytes))))
297
+
298
+ (define (zipped! [dir string] [d (ptr deflater)])
299
+ (let ([names (names-in dir)])
300
+ (for ([i u32 0]) (< i (-> names length)) (+= i 1)
301
+ (let ([name (@ names i)] [f (joined-path dir (@ names i))])
302
+ (cond [(== (kind-of f) fs-directory) (zipped! f d)]
303
+ [(or (== name ".site") (ends? name ".gz")) (begin)]
304
+ [else
305
+ (let ([b (read! f)] [z (make-buffer)])
306
+ (gzip! d (& z) b "")
307
+ (when (<= (* (cast u64 (-> z length)) 10) (* (cast u64 (-> b length)) 9))
308
+ (written! (joined f ".gz") (buffer-bytes (& z))))
309
+ (free (-> b items))
310
+ (buffer-free! (& z)))])))))
311
+
312
+ (define (hash-of [b (slice u8)]) -> string
313
+ (let ([digest (array u8 32)] [name (make-buffer)])
314
+ (sha256-of b (slice digest))
315
+ (hex! (& name) (slice (slice digest) 0 8))
316
+ (buffer-string (& name))))
317
+
318
+ (define (hashed-assets! [out string])
319
+ (let ([listed (cast string (read! (joined-path out "assets.list")))] [b (make-buffer)] [start u32 0])
320
+ (for ([i u32 0]) (<= i (-> listed length)) (+= i 1)
321
+ (when (or (== i (-> listed length)) (== (@ listed i) 10))
322
+ (when (> i start)
323
+ (let* ([path (slice listed start i)] [bytes (read! (joined-path out path))] [hash (hash-of bytes)] [k i])
324
+ (while (and (> k start) (!= (@ listed (- k 1)) #\/)) (-= k 1))
325
+ (let ([dir (joined-path (joined-path out "assets") hash)] [name (slice listed k i)])
326
+ (made! dir)
327
+ (written! (joined-path dir name) bytes)
328
+ (buffer-add-string! (& b) path)
329
+ (buffer-add-byte! (& b) 9)
330
+ (buffer-add-string! (& b) (slashed "assets" (slashed hash name)))
331
+ (buffer-add-byte! (& b) 10))
332
+ (free (-> bytes items))))
333
+ (set! start (+ i 1))))
334
+ (written! (joined-path out "assets.list") (buffer-bytes (& b)))
335
+ (buffer-free! (& b))
336
+ (free (-> listed bytes))))
337
+
338
+ (define (site-released! [out string])
339
+ (let ([paths (make-buffer)] [fetched (make-buffer)] [d deflater])
340
+ (hashed-assets! out)
341
+ (walked! (joined-path out "host") "host" (& paths))
342
+ (walked! (joined-path out "program") "program" (& paths))
343
+ (added! (& paths) "files.pack")
344
+ (added! (& paths) "assets.list")
345
+ (sorted! (strings-of (& paths)))
346
+ (let* ([name (release-name out (strings-of (& paths)))] [code (joined-path out name)])
347
+ (made! code)
348
+ (renamed! (joined-path out "host") (joined-path code "host"))
349
+ (renamed! (joined-path out "program") (joined-path code "program"))
350
+ (renamed! (joined-path out "files.pack") (joined-path code "files.pack"))
351
+ (renamed! (joined-path out "assets.list") (joined-path code "assets.list"))
352
+ (walked! (joined-path code "program") "program" (& fetched))
353
+ (added! (& fetched) "files.pack")
354
+ (added! (& fetched) "assets.list")
355
+ (sorted! (strings-of (& fetched)))
356
+ (released-html! out name (strings-of (& fetched)))
357
+ (deflater-init! (& d) 9)
358
+ (zipped! out (& d))
359
+ (deflater-free! (& d)))))