goscript 0.1.2 → 0.1.4

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 (138) hide show
  1. package/cmd/goscript/cmd_compile.go +28 -8
  2. package/cmd/goscript/cmd_compile_test.go +105 -6
  3. package/compiler/build-flags.go +9 -10
  4. package/compiler/gotest/runner_test.go +127 -0
  5. package/compiler/lowered-program.go +1 -0
  6. package/compiler/lowering.go +1325 -194
  7. package/compiler/lowering_bench_test.go +350 -0
  8. package/compiler/override-registry_test.go +43 -0
  9. package/compiler/package-graph.go +61 -4
  10. package/compiler/package-graph_test.go +30 -0
  11. package/compiler/semantic-model-types.go +8 -0
  12. package/compiler/semantic-model.go +447 -22
  13. package/compiler/semantic-model_test.go +138 -0
  14. package/compiler/skeleton_test.go +1436 -50
  15. package/compiler/typescript-emitter.go +47 -4
  16. package/dist/gs/builtin/builtin.d.ts +2 -2
  17. package/dist/gs/builtin/builtin.js +20 -0
  18. package/dist/gs/builtin/builtin.js.map +1 -1
  19. package/dist/gs/builtin/channel.js +36 -9
  20. package/dist/gs/builtin/channel.js.map +1 -1
  21. package/dist/gs/builtin/slice.js +5 -0
  22. package/dist/gs/builtin/slice.js.map +1 -1
  23. package/dist/gs/builtin/type.d.ts +1 -1
  24. package/dist/gs/builtin/type.js +80 -8
  25. package/dist/gs/builtin/type.js.map +1 -1
  26. package/dist/gs/bytes/bytes.gs.d.ts +7 -5
  27. package/dist/gs/bytes/bytes.gs.js +10 -4
  28. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  29. package/dist/gs/compress/zlib/index.d.ts +3 -3
  30. package/dist/gs/compress/zlib/index.js +88 -26
  31. package/dist/gs/compress/zlib/index.js.map +1 -1
  32. package/dist/gs/crypto/sha1/index.d.ts +5 -0
  33. package/dist/gs/crypto/sha1/index.js +103 -0
  34. package/dist/gs/crypto/sha1/index.js.map +1 -0
  35. package/dist/gs/crypto/sha256/index.js +2 -5
  36. package/dist/gs/crypto/sha256/index.js.map +1 -1
  37. package/dist/gs/crypto/sha512/index.js +2 -5
  38. package/dist/gs/crypto/sha512/index.js.map +1 -1
  39. package/dist/gs/embed/index.d.ts +6 -0
  40. package/dist/gs/embed/index.js +210 -5
  41. package/dist/gs/embed/index.js.map +1 -1
  42. package/dist/gs/fmt/fmt.d.ts +4 -4
  43. package/dist/gs/fmt/fmt.js +93 -19
  44. package/dist/gs/fmt/fmt.js.map +1 -1
  45. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +118 -6
  46. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +1 -1
  47. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.d.ts +45 -0
  48. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js +229 -0
  49. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js.map +1 -0
  50. package/dist/gs/io/fs/readdir.js +5 -3
  51. package/dist/gs/io/fs/readdir.js.map +1 -1
  52. package/dist/gs/io/io.d.ts +18 -11
  53. package/dist/gs/io/io.js +107 -44
  54. package/dist/gs/io/io.js.map +1 -1
  55. package/dist/gs/math/bits/index.d.ts +26 -5
  56. package/dist/gs/math/bits/index.js +13 -24
  57. package/dist/gs/math/bits/index.js.map +1 -1
  58. package/dist/gs/net/http/httptest/index.js +7 -5
  59. package/dist/gs/net/http/httptest/index.js.map +1 -1
  60. package/dist/gs/net/http/index.d.ts +11 -1
  61. package/dist/gs/net/http/index.js +157 -11
  62. package/dist/gs/net/http/index.js.map +1 -1
  63. package/dist/gs/os/types_js.gs.d.ts +6 -2
  64. package/dist/gs/os/types_js.gs.js +169 -8
  65. package/dist/gs/os/types_js.gs.js.map +1 -1
  66. package/dist/gs/os/zero_copy_posix.gs.js +1 -1
  67. package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
  68. package/dist/gs/reflect/type.d.ts +1 -0
  69. package/dist/gs/reflect/type.js +80 -51
  70. package/dist/gs/reflect/type.js.map +1 -1
  71. package/dist/gs/strings/reader.d.ts +1 -1
  72. package/dist/gs/strings/reader.js +2 -2
  73. package/dist/gs/strings/reader.js.map +1 -1
  74. package/dist/gs/sync/sync.d.ts +2 -1
  75. package/dist/gs/sync/sync.js +37 -16
  76. package/dist/gs/sync/sync.js.map +1 -1
  77. package/dist/gs/syscall/js/index.js +9 -0
  78. package/dist/gs/syscall/js/index.js.map +1 -1
  79. package/dist/gs/testing/testing.js +8 -6
  80. package/dist/gs/testing/testing.js.map +1 -1
  81. package/gs/builtin/builtin.ts +25 -2
  82. package/gs/builtin/channel.ts +47 -9
  83. package/gs/builtin/runtime-contract.test.ts +78 -0
  84. package/gs/builtin/slice.ts +7 -0
  85. package/gs/builtin/type.ts +97 -8
  86. package/gs/bytes/bytes.gs.ts +19 -10
  87. package/gs/bytes/bytes.test.ts +17 -0
  88. package/gs/compress/zlib/index.test.ts +97 -0
  89. package/gs/compress/zlib/index.ts +117 -27
  90. package/gs/compress/zlib/meta.json +4 -1
  91. package/gs/context/context.test.ts +5 -1
  92. package/gs/crypto/sha1/index.test.ts +45 -0
  93. package/gs/crypto/sha1/index.ts +127 -0
  94. package/gs/crypto/sha1/meta.json +8 -0
  95. package/gs/crypto/sha256/index.test.ts +14 -2
  96. package/gs/crypto/sha256/index.ts +3 -6
  97. package/gs/crypto/sha512/index.test.ts +17 -2
  98. package/gs/crypto/sha512/index.ts +3 -6
  99. package/gs/embed/index.test.ts +87 -0
  100. package/gs/embed/index.ts +229 -5
  101. package/gs/fmt/fmt.test.ts +61 -3
  102. package/gs/fmt/fmt.ts +115 -22
  103. package/gs/fmt/meta.json +6 -1
  104. package/gs/github.com/aperturerobotics/starpc/srpc/index.test.ts +8 -1
  105. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +139 -11
  106. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +1 -1
  107. package/gs/github.com/go-git/go-billy/v6/osfs/index.test.ts +110 -0
  108. package/gs/github.com/go-git/go-billy/v6/osfs/index.ts +280 -0
  109. package/gs/github.com/go-git/go-billy/v6/osfs/meta.json +8 -0
  110. package/gs/io/fs/readdir.test.ts +38 -0
  111. package/gs/io/fs/readdir.ts +7 -3
  112. package/gs/io/io.test.ts +135 -0
  113. package/gs/io/io.ts +143 -63
  114. package/gs/io/meta.json +7 -1
  115. package/gs/math/bits/index.ts +52 -28
  116. package/gs/net/http/httptest/index.test.ts +34 -2
  117. package/gs/net/http/httptest/index.ts +23 -8
  118. package/gs/net/http/index.test.ts +46 -0
  119. package/gs/net/http/index.ts +178 -12
  120. package/gs/os/file_unix_js.test.ts +52 -0
  121. package/gs/os/meta.json +4 -0
  122. package/gs/os/readdir.test.ts +56 -0
  123. package/gs/os/types_js.gs.ts +169 -8
  124. package/gs/os/zero_copy_posix.gs.ts +1 -2
  125. package/gs/reflect/deepequal.test.ts +10 -1
  126. package/gs/reflect/type.ts +91 -56
  127. package/gs/reflect/typefor.test.ts +31 -1
  128. package/gs/strings/meta.json +5 -2
  129. package/gs/strings/reader.test.ts +2 -2
  130. package/gs/strings/reader.ts +2 -2
  131. package/gs/sync/meta.json +2 -0
  132. package/gs/sync/sync.test.ts +41 -1
  133. package/gs/sync/sync.ts +41 -16
  134. package/gs/syscall/js/index.test.ts +18 -0
  135. package/gs/syscall/js/index.ts +12 -0
  136. package/gs/testing/testing.test.ts +32 -3
  137. package/gs/testing/testing.ts +13 -10
  138. package/package.json +1 -1
@@ -8,6 +8,9 @@ import * as utf8 from "@goscript/unicode/utf8/index.js"
8
8
  // for linkname
9
9
  import * as _ from "@goscript/unsafe/index.js"
10
10
 
11
+ type SyncCallbackResult<T> = T | globalThis.Promise<T>
12
+ type PredicateCallback = ((r: number) => SyncCallbackResult<boolean>) | null
13
+
11
14
  // Equal reports whether a and b
12
15
  // are the same length and contain the same bytes.
13
16
  // A nil argument is equivalent to an empty slice.
@@ -90,7 +93,7 @@ export function ContainsRune(b: $.Bytes, r: number): boolean {
90
93
  }
91
94
 
92
95
  // ContainsFunc reports whether any of the UTF-8-encoded code points r within b satisfy f(r).
93
- export function ContainsFunc(b: $.Bytes, f: ((p0: number) => boolean) | null): boolean {
96
+ export function ContainsFunc(b: $.Bytes, f: PredicateCallback): boolean {
94
97
  return IndexFunc(b, f) >= 0
95
98
  }
96
99
 
@@ -794,21 +797,21 @@ export function TrimSuffix(s: $.Bytes, suffix: $.Bytes): $.Bytes {
794
797
  // IndexFunc interprets s as a sequence of UTF-8-encoded code points.
795
798
  // It returns the byte index in s of the first Unicode
796
799
  // code point satisfying f(c), or -1 if none do.
797
- export function IndexFunc(s: $.Bytes, f: ((r: number) => boolean) | null): number {
800
+ export function IndexFunc(s: $.Bytes, f: PredicateCallback): number {
798
801
  return indexFunc(s, f, true)
799
802
  }
800
803
 
801
804
  // LastIndexFunc interprets s as a sequence of UTF-8-encoded code points.
802
805
  // It returns the byte index in s of the last Unicode
803
806
  // code point satisfying f(c), or -1 if none do.
804
- export function LastIndexFunc(s: $.Bytes, f: ((r: number) => boolean) | null): number {
807
+ export function LastIndexFunc(s: $.Bytes, f: PredicateCallback): number {
805
808
  return lastIndexFunc(s, f, true)
806
809
  }
807
810
 
808
811
  // indexFunc is the same as IndexFunc except that if
809
812
  // truth==false, the sense of the predicate function is
810
813
  // inverted.
811
- export function indexFunc(s: $.Bytes, f: ((r: number) => boolean) | null, truth: boolean): number {
814
+ export function indexFunc(s: $.Bytes, f: PredicateCallback, truth: boolean): number {
812
815
  if (s === null || f === null) {
813
816
  return -1
814
817
  }
@@ -817,12 +820,12 @@ export function indexFunc(s: $.Bytes, f: ((r: number) => boolean) | null, truth:
817
820
  const [r, size] = utf8.DecodeRune($.goSlice(s, i, undefined))
818
821
  if (size <= 0) {
819
822
  // Invalid UTF-8
820
- if (f(utf8.RuneError) === truth) {
823
+ if (syncBoolean(f(utf8.RuneError)) === truth) {
821
824
  return i
822
825
  }
823
826
  i++
824
827
  } else {
825
- if (f(r) === truth) {
828
+ if (syncBoolean(f(r)) === truth) {
826
829
  return i
827
830
  }
828
831
  i += size
@@ -835,7 +838,7 @@ export function indexFunc(s: $.Bytes, f: ((r: number) => boolean) | null, truth:
835
838
  // lastIndexFunc is the same as LastIndexFunc except that if
836
839
  // truth==false, the sense of the predicate function is
837
840
  // inverted.
838
- export function lastIndexFunc(s: $.Bytes, f: ((r: number) => boolean) | null, truth: boolean): number {
841
+ export function lastIndexFunc(s: $.Bytes, f: PredicateCallback, truth: boolean): number {
839
842
  if (s === null || f === null) {
840
843
  return -1
841
844
  }
@@ -846,12 +849,12 @@ export function lastIndexFunc(s: $.Bytes, f: ((r: number) => boolean) | null, tr
846
849
  const [r, size] = utf8.DecodeRune($.goSlice(s, i, undefined))
847
850
  if (size <= 0) {
848
851
  // Invalid UTF-8
849
- if (f(utf8.RuneError) === truth) {
852
+ if (syncBoolean(f(utf8.RuneError)) === truth) {
850
853
  lastIndex = i
851
854
  }
852
855
  i++
853
856
  } else {
854
- if (f(r) === truth) {
857
+ if (syncBoolean(f(r)) === truth) {
855
858
  lastIndex = i
856
859
  }
857
860
  i += size
@@ -861,6 +864,13 @@ export function lastIndexFunc(s: $.Bytes, f: ((r: number) => boolean) | null, tr
861
864
  return lastIndex
862
865
  }
863
866
 
867
+ function syncBoolean(value: SyncCallbackResult<boolean>): boolean {
868
+ if (value instanceof Promise) {
869
+ throw new Error("bytes: asynchronous callback result is not supported")
870
+ }
871
+ return value
872
+ }
873
+
864
874
  class asciiSet {
865
875
  constructor(public _value: number[]) {}
866
876
 
@@ -1285,4 +1295,3 @@ export function CutSuffix(s: $.Bytes, suffix: $.Bytes): [$.Bytes, boolean] {
1285
1295
  }
1286
1296
  return [$.goSlice(s, undefined, $.len(s) - $.len(suffix)), true]
1287
1297
  }
1288
-
@@ -0,0 +1,17 @@
1
+ import * as $ from "@goscript/builtin/index.js"
2
+ import { IndexFunc } from "./index.js"
3
+ import { describe, expect, test } from "vitest"
4
+
5
+ describe("bytes", () => {
6
+ test("IndexFunc accepts generated async-shaped callbacks", () => {
7
+ const predicate: (r: number) => boolean | Promise<boolean> = (r) => r === 0x62
8
+
9
+ expect(IndexFunc($.stringToBytes("abc"), predicate)).toBe(1)
10
+ })
11
+
12
+ test("IndexFunc rejects actual async callback results", () => {
13
+ expect(() => IndexFunc($.stringToBytes("abc"), async (r) => r === 0x62)).toThrow(
14
+ "bytes: asynchronous callback result is not supported",
15
+ )
16
+ })
17
+ })
@@ -25,4 +25,101 @@ describe('compress/zlib override', () => {
25
25
  expect(readErr).toBeNull()
26
26
  expect($.bytesToString(out)).toBe('hello compressed world')
27
27
  })
28
+
29
+ test('reader implements resettable zlib reader contract', async () => {
30
+ const first = $.markAsStructValue(new bytes.Buffer())
31
+ const firstWriter = NewWriter(first)
32
+ expect(firstWriter.Write($.stringToBytes('first stream'))[1]).toBeNull()
33
+ expect(await firstWriter.Close()).toBeNull()
34
+
35
+ const second = $.markAsStructValue(new bytes.Buffer())
36
+ const secondWriter = NewWriter(second)
37
+ expect(secondWriter.Write($.stringToBytes('second stream'))[1]).toBeNull()
38
+ expect(await secondWriter.Close()).toBeNull()
39
+
40
+ const readerInterface = $.registerInterfaceType(
41
+ 'compress/zlib.testReader',
42
+ null,
43
+ [
44
+ { name: 'Close', args: [], returns: [{ type: 'error' }] },
45
+ {
46
+ name: 'Read',
47
+ args: [
48
+ {
49
+ name: 'p',
50
+ type: {
51
+ kind: $.TypeKind.Slice,
52
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
53
+ },
54
+ },
55
+ ],
56
+ returns: [{ type: 'int' }, { type: 'error' }],
57
+ },
58
+ {
59
+ name: 'Reset',
60
+ args: [{ type: 'io.Reader' }, { type: '[]byte' }],
61
+ returns: [{ type: 'error' }],
62
+ },
63
+ ],
64
+ )
65
+
66
+ const [reader, readerErr] = NewReader(bytes.NewReader(first.Bytes()))
67
+ expect(readerErr).toBeNull()
68
+ const [zlibReader, ok] = $.typeAssertTuple<
69
+ io.ReadCloser & {
70
+ Reset(r: io.Reader | null, dict: $.Bytes | null): $.GoError
71
+ }
72
+ >(reader, readerInterface)
73
+ expect(ok).toBe(true)
74
+
75
+ const [firstOut, firstReadErr] = await io.ReadAll(zlibReader)
76
+ expect(firstReadErr).toBeNull()
77
+ expect($.bytesToString(firstOut)).toBe('first stream')
78
+
79
+ expect(zlibReader.Reset(bytes.NewReader(second.Bytes()), null)).toBeNull()
80
+ const [secondOut, secondReadErr] = await io.ReadAll(zlibReader)
81
+ expect(secondReadErr).toBeNull()
82
+ expect($.bytesToString(secondOut)).toBe('second stream')
83
+ })
84
+
85
+ test('reader reset accepts async generated readers', async () => {
86
+ const compressed = $.markAsStructValue(new bytes.Buffer())
87
+ const writer = NewWriter(compressed)
88
+ expect(writer.Write($.stringToBytes('async source stream'))[1]).toBeNull()
89
+ expect(await writer.Close()).toBeNull()
90
+
91
+ const source = bytes.NewReader(compressed.Bytes())
92
+ const asyncReader = {
93
+ async Read(p: $.Bytes): Promise<[number, $.GoError]> {
94
+ await Promise.resolve()
95
+ return source.Read(p)
96
+ },
97
+ }
98
+
99
+ const [reader, readerErr] = NewReader(asyncReader as io.Reader)
100
+ expect(readerErr).toBeNull()
101
+ const [out, readErr] = await io.ReadAll(reader!)
102
+ expect(readErr).toBeNull()
103
+ expect($.bytesToString(out)).toBe('async source stream')
104
+ })
105
+
106
+ test('Close awaits pointer-wrapped generated writers', async () => {
107
+ const chunks: number[] = []
108
+ const sink = {
109
+ async Write(p: $.Bytes): Promise<[number, $.GoError]> {
110
+ await Promise.resolve()
111
+ chunks.push(...Array.from(p ?? []))
112
+ return [$.len(p), null]
113
+ },
114
+ }
115
+ const writer = NewWriter(
116
+ $.interfaceValue($.varRef(sink), '*zlib.asyncWriter'),
117
+ )
118
+
119
+ const [written, writeErr] = writer.Write($.stringToBytes('async zlib sink'))
120
+ expect(writeErr).toBeNull()
121
+ expect(written).toBe('async zlib sink'.length)
122
+ expect(await writer.Close()).toBeNull()
123
+ expect(chunks.length).toBeGreaterThan(0)
124
+ })
28
125
  })
@@ -3,7 +3,11 @@ import * as errors from '@goscript/errors/index.js'
3
3
  import * as io from '@goscript/io/index.js'
4
4
 
5
5
  export type Resetter = {
6
- Reset(r: io.Reader | null, dict: $.Bytes | null): Promise<$.GoError>
6
+ Reset(r: io.Reader | null, dict: $.Bytes | null): $.GoError
7
+ }
8
+
9
+ type maybeAsyncWriter = {
10
+ Write(p: $.Bytes): [number, $.GoError] | Promise<[number, $.GoError]>
7
11
  }
8
12
 
9
13
  export let ErrChecksum = errors.New('zlib: invalid checksum')
@@ -22,12 +26,30 @@ export function __goscript_set_ErrHeader(value: $.GoError): void {
22
26
  ErrHeader = value
23
27
  }
24
28
 
25
- class zlibReader implements io.ReadCloser {
29
+ class zlibReader implements Resetter {
30
+ private data: Uint8Array = new Uint8Array(0)
26
31
  private offset = 0
32
+ private pending:
33
+ | Promise<{ data: Uint8Array | null; err: $.GoError }>
34
+ | null = null
27
35
 
28
- constructor(private data: Uint8Array) {}
36
+ constructor(data?: Uint8Array) {
37
+ if (data != null) {
38
+ this.data = data
39
+ }
40
+ }
29
41
 
30
- Read(p: $.Bytes): [number, $.GoError] {
42
+ async Read(p: $.Bytes): Promise<[number, $.GoError]> {
43
+ const pending = this.pending
44
+ if (pending != null) {
45
+ this.pending = null
46
+ const result = await pending
47
+ if (result.err != null) {
48
+ return [0, result.err]
49
+ }
50
+ this.data = result.data ?? new Uint8Array(0)
51
+ this.offset = 0
52
+ }
31
53
  if (this.offset >= this.data.length) {
32
54
  return [0, io.EOF]
33
55
  }
@@ -41,6 +63,26 @@ class zlibReader implements io.ReadCloser {
41
63
  Close(): $.GoError {
42
64
  return null
43
65
  }
66
+
67
+ Reset(r: io.Reader | null, dict: $.Bytes | null): $.GoError {
68
+ if (r == null) {
69
+ return errors.New('zlib: nil reader')
70
+ }
71
+ const result = readInflated(r, dict)
72
+ if (result instanceof Promise) {
73
+ this.data = new Uint8Array(0)
74
+ this.offset = 0
75
+ this.pending = result
76
+ return null
77
+ }
78
+ if (result.err != null) {
79
+ return result.err
80
+ }
81
+ this.data = result.data ?? new Uint8Array(0)
82
+ this.offset = 0
83
+ this.pending = null
84
+ return null
85
+ }
44
86
  }
45
87
 
46
88
  export class Writer {
@@ -58,7 +100,7 @@ export class Writer {
58
100
  return [data.length, null]
59
101
  }
60
102
 
61
- Close(): $.GoError {
103
+ async Close(): Promise<$.GoError> {
62
104
  if (this.closed) {
63
105
  return null
64
106
  }
@@ -67,7 +109,8 @@ export class Writer {
67
109
  return errors.New('zlib: nil writer')
68
110
  }
69
111
  const compressed = deflate(concat(this.chunks))
70
- const [, err] = this.w.Write(compressed)
112
+ const writer = $.pointerValue<maybeAsyncWriter>(this.w)
113
+ const [, err] = await writer.Write(compressed)
71
114
  return err
72
115
  }
73
116
 
@@ -109,21 +152,14 @@ export function NewReader(
109
152
 
110
153
  export function NewReaderDict(
111
154
  r: io.Reader | null,
112
- _dict: $.Bytes | null,
155
+ dict: $.Bytes | null,
113
156
  ): [io.ReadCloser | null, $.GoError] {
114
- if (r == null) {
115
- return [null, errors.New('zlib: nil reader')]
116
- }
117
- const [data, readErr] = readAll(r)
118
- if (readErr != null) {
119
- return [null, readErr]
120
- }
121
- try {
122
- const out = inflate($.bytesToUint8Array(data))
123
- return [new zlibReader(out), null]
124
- } catch {
125
- return [null, ErrHeader]
157
+ const reader = new zlibReader()
158
+ const err = reader.Reset(r, dict)
159
+ if (err != null) {
160
+ return [null, err]
126
161
  }
162
+ return [reader as any, null]
127
163
  }
128
164
 
129
165
  function deflate(data: Uint8Array): Uint8Array {
@@ -168,20 +204,74 @@ function nodeZlib(): any {
168
204
  throw new Error('compress/zlib: node zlib module unavailable')
169
205
  }
170
206
 
171
- function readAll(r: io.Reader): [Uint8Array, $.GoError] {
172
- const chunks: Uint8Array[] = []
173
- const buf = $.makeSlice<number>(32 * 1024, undefined, 'byte')
207
+ function readInflated(
208
+ r: io.Reader,
209
+ dict: $.Bytes | null,
210
+ ):
211
+ | { data: Uint8Array | null; err: $.GoError }
212
+ | Promise<{ data: Uint8Array | null; err: $.GoError }> {
213
+ const chunks: number[] = []
214
+ const buf = $.makeSlice<number>(1, undefined, 'byte')
215
+ while (true) {
216
+ const read = r.Read(buf)
217
+ if (read instanceof Promise) {
218
+ return readInflatedAsync(read, r, buf, chunks, dict)
219
+ }
220
+ const [n, err] = read
221
+ const result = recordCompressedBytes(chunks, buf, n, dict)
222
+ if (result.err == null && result.data != null) {
223
+ return result
224
+ }
225
+ if (err != null) {
226
+ if (err === io.EOF) {
227
+ return result
228
+ }
229
+ return { data: null, err }
230
+ }
231
+ }
232
+ }
233
+
234
+ async function readInflatedAsync(
235
+ first: Promise<[number, $.GoError]>,
236
+ r: io.Reader,
237
+ buf: $.Bytes,
238
+ chunks: number[],
239
+ dict: $.Bytes | null,
240
+ ): Promise<{ data: Uint8Array | null; err: $.GoError }> {
241
+ let read = await first
174
242
  while (true) {
175
- const [n, err] = r.Read(buf)
176
- if (n > 0) {
177
- chunks.push($.bytesToUint8Array($.goSlice(buf, 0, n)).slice())
243
+ const [n, err] = read
244
+ const result = recordCompressedBytes(chunks, buf, n, dict)
245
+ if (result.err == null && result.data != null) {
246
+ return result
178
247
  }
179
248
  if (err != null) {
180
249
  if (err === io.EOF) {
181
- return [concat(chunks), null]
250
+ return result
182
251
  }
183
- return [new Uint8Array(0), err]
252
+ return { data: null, err }
253
+ }
254
+ read = await r.Read(buf)
255
+ }
256
+ }
257
+
258
+ function recordCompressedBytes(
259
+ chunks: number[],
260
+ buf: $.Bytes,
261
+ n: number,
262
+ dict: $.Bytes | null,
263
+ ): { data: Uint8Array | null; err: $.GoError } {
264
+ if (n > 0) {
265
+ chunks.push(...$.bytesToUint8Array($.goSlice(buf, 0, n)))
266
+ }
267
+ const compressed = new Uint8Array(chunks)
268
+ try {
269
+ return { data: inflate(compressed), err: null }
270
+ } catch {
271
+ if (dict != null && $.len(dict) > 0) {
272
+ return { data: null, err: ErrDictionary }
184
273
  }
274
+ return { data: null, err: ErrHeader }
185
275
  }
186
276
  }
187
277
 
@@ -1,3 +1,6 @@
1
1
  {
2
- "dependencies": []
2
+ "dependencies": [],
3
+ "asyncMethods": {
4
+ "Writer.Close": true
5
+ }
3
6
  }
@@ -8,6 +8,10 @@ async function nextMicrotask(): Promise<void> {
8
8
  await new Promise<void>((resolve) => queueMicrotask(resolve))
9
9
  }
10
10
 
11
+ async function nextTask(): Promise<void> {
12
+ await new Promise<void>((resolve) => setTimeout(resolve, 0))
13
+ }
14
+
11
15
  describe('context override', () => {
12
16
  it('matches generated struct keys by Go comparable value', () => {
13
17
  class Key {
@@ -39,7 +43,7 @@ describe('context override', () => {
39
43
 
40
44
  cancel?.()
41
45
  await nextMicrotask()
42
- await nextMicrotask()
46
+ await nextTask()
43
47
 
44
48
  expect(called).toBe(true)
45
49
  expect(stop()).toBe(false)
@@ -0,0 +1,45 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { describe, expect, it } from 'vitest'
3
+ import * as $ from '@goscript/builtin/index.js'
4
+
5
+ import { New, Size, Sum } from './index.js'
6
+
7
+ describe('crypto/sha1 override', () => {
8
+ it('matches Node digest', async () => {
9
+ const data = new TextEncoder().encode('goscript sha1')
10
+
11
+ expect(toHex(await Sum(data))).toBe(nodeHash(data))
12
+ })
13
+
14
+ it('supports incremental hash.Hash use', async () => {
15
+ const h = New()
16
+ h.Write(new TextEncoder().encode('go'))
17
+ h.Write(new TextEncoder().encode('script'))
18
+
19
+ expect(toHex(await h.Sum(null))).toBe(
20
+ nodeHash(new TextEncoder().encode('goscript')),
21
+ )
22
+ })
23
+
24
+ it('appends into spare byte-slice backing', async () => {
25
+ const h = New()
26
+ h.Write(new TextEncoder().encode('abc'))
27
+
28
+ const backing = $.makeSlice<number>(Size, undefined, 'byte')
29
+ const out = await h.Sum($.goSlice(backing, 0, 0))
30
+ expect(out.length).toBe(Size)
31
+ expect(backing[0]).toBe(out[0])
32
+ expect(backing[Size - 1]).toBe(out[Size - 1])
33
+ expect(toHex($.bytesToUint8Array(backing))).toBe(
34
+ nodeHash(new TextEncoder().encode('abc')),
35
+ )
36
+ })
37
+ })
38
+
39
+ function nodeHash(data: Uint8Array): string {
40
+ return createHash('sha1').update(data).digest('hex')
41
+ }
42
+
43
+ function toHex(value: Uint8Array): string {
44
+ return Buffer.from(value).toString('hex')
45
+ }
@@ -0,0 +1,127 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import {
3
+ getHostRuntime,
4
+ type NodeCryptoHash,
5
+ } from '@goscript/builtin/hostio.js'
6
+
7
+ export const Size = 20
8
+ export const BlockSize = 64
9
+
10
+ class Sha1Error {
11
+ constructor(private readonly message: string) {}
12
+
13
+ Error(): string {
14
+ return this.message
15
+ }
16
+ }
17
+
18
+ class Digest {
19
+ private chunks: Uint8Array[] = []
20
+ private dataLength = 0
21
+ private hash: NodeCryptoHash | null
22
+ private canCopyHash: boolean
23
+
24
+ constructor() {
25
+ this.hash = createNodeHash()
26
+ this.canCopyHash = typeof this.hash?.copy === 'function'
27
+ }
28
+
29
+ Write(p: $.Bytes): [number, $.GoError] {
30
+ const bytes = $.bytesToUint8Array(p)
31
+ this.hash?.update(bytes)
32
+ if (!this.canCopyHash) {
33
+ this.chunks.push(bytes.slice())
34
+ this.dataLength += bytes.length
35
+ }
36
+ return [bytes.length, null]
37
+ }
38
+
39
+ async Sum(b: $.Bytes): Promise<$.Bytes> {
40
+ const digest =
41
+ this.canCopyHash ?
42
+ new Uint8Array(this.hash!.copy!().digest())
43
+ : await sum(this.snapshotBytes())
44
+ return appendDigest(b, digest)
45
+ }
46
+
47
+ Reset(): void {
48
+ this.chunks = []
49
+ this.dataLength = 0
50
+ this.hash = createNodeHash()
51
+ this.canCopyHash = typeof this.hash?.copy === 'function'
52
+ }
53
+
54
+ Size(): number {
55
+ return Size
56
+ }
57
+
58
+ BlockSize(): number {
59
+ return BlockSize
60
+ }
61
+
62
+ private snapshotBytes(): Uint8Array {
63
+ return concatChunks(this.chunks, this.dataLength)
64
+ }
65
+ }
66
+
67
+ export function New(): any {
68
+ return new Digest()
69
+ }
70
+
71
+ export async function Sum(data: $.Bytes): Promise<Uint8Array> {
72
+ return sum(data)
73
+ }
74
+
75
+ async function sum(data: $.Bytes): Promise<Uint8Array> {
76
+ const hash = createNodeHash()
77
+ if (hash != null) {
78
+ return new Uint8Array(hash.update($.bytesToUint8Array(data)).digest())
79
+ }
80
+
81
+ const subtle = subtleCrypto()
82
+ if (subtle == null) {
83
+ throw new Error(
84
+ new Sha1Error('crypto/sha1: WebCrypto digest is unavailable').Error(),
85
+ )
86
+ }
87
+
88
+ const digest = await subtle.digest(
89
+ 'SHA-1',
90
+ $.bytesToUint8Array(data) as unknown as BufferSource,
91
+ )
92
+ return new Uint8Array(digest)
93
+ }
94
+
95
+ function appendDigest(prefix: $.Bytes, digest: Uint8Array): $.Bytes {
96
+ return $.append(prefix as any, ...digest) as $.Bytes
97
+ }
98
+
99
+ function createNodeHash(): NodeCryptoHash | null {
100
+ const nodeCrypto = getHostRuntime().nodeCrypto
101
+ if (!nodeCrypto?.createHash) {
102
+ return null
103
+ }
104
+ try {
105
+ return nodeCrypto.createHash('sha1')
106
+ } catch {
107
+ return null
108
+ }
109
+ }
110
+
111
+ function concatChunks(chunks: Uint8Array[], length: number): Uint8Array {
112
+ const out = new Uint8Array(length)
113
+ let offset = 0
114
+ for (const chunk of chunks) {
115
+ out.set(chunk, offset)
116
+ offset += chunk.length
117
+ }
118
+ return out
119
+ }
120
+
121
+ function subtleCrypto(): SubtleCrypto | null {
122
+ const crypto = globalThis.crypto
123
+ if (crypto?.subtle && typeof crypto.subtle.digest === 'function') {
124
+ return crypto.subtle
125
+ }
126
+ return null
127
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "asyncFunctions": {
3
+ "Sum": true
4
+ },
5
+ "asyncMethods": {
6
+ "Digest.Sum": true
7
+ }
8
+ }
@@ -25,11 +25,23 @@ describe('crypto/sha256 override', () => {
25
25
  )
26
26
  })
27
27
 
28
+ test('streaming digest appends into spare byte-slice backing', async () => {
29
+ const digest = New()
30
+ expect(digest.Write($.stringToBytes('abc'))).toEqual([3, null])
31
+
32
+ const backing = $.makeSlice<number>(Size, undefined, 'byte')
33
+ const out = await digest.Sum($.goSlice(backing, 0, 0))
34
+ expect(out.length).toBe(Size)
35
+ expect(Array.from($.bytesToUint8Array(backing))).toEqual(
36
+ Array.from(await Sum256($.stringToBytes('abc'))),
37
+ )
38
+ })
39
+
28
40
  test('sums SHA-224 with host crypto', async () => {
29
41
  const sum = await Sum224($.stringToBytes('abc'))
30
42
  expect(Array.from(sum)).toEqual([
31
- 35, 9, 125, 34, 52, 5, 216, 34, 134, 66, 164, 119, 189, 162, 85, 179,
32
- 42, 173, 188, 228, 189, 160, 179, 247, 227, 108, 157, 167,
43
+ 35, 9, 125, 34, 52, 5, 216, 34, 134, 66, 164, 119, 189, 162, 85, 179, 42,
44
+ 173, 188, 228, 189, 160, 179, 247, 227, 108, 157, 167,
33
45
  ])
34
46
  })
35
47
 
@@ -44,7 +44,7 @@ class Digest {
44
44
  this.canCopyHash ?
45
45
  new Uint8Array(this.hash!.copy!().digest())
46
46
  : await sum(this.algorithm, this.snapshotBytes())
47
- return appendDigest($.bytesToUint8Array(b), digest)
47
+ return appendDigest(b, digest)
48
48
  }
49
49
 
50
50
  Reset(): void {
@@ -112,11 +112,8 @@ async function sum(
112
112
  return new Uint8Array(digest)
113
113
  }
114
114
 
115
- function appendDigest(prefix: Uint8Array, digest: Uint8Array): Uint8Array {
116
- const out = new Uint8Array(prefix.length + digest.length)
117
- out.set(prefix)
118
- out.set(digest, prefix.length)
119
- return out
115
+ function appendDigest(prefix: $.Bytes, digest: Uint8Array): $.Bytes {
116
+ return $.append(prefix as any, ...digest) as $.Bytes
120
117
  }
121
118
 
122
119
  function createNodeHash(algorithm: ShaAlgorithm): NodeCryptoHash | null {