goscript 0.0.25 → 0.0.28

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 (190) hide show
  1. package/README.md +4 -4
  2. package/cmd/goscript/cmd_compile.go +0 -3
  3. package/cmd/goscript/deps.go +11 -0
  4. package/compiler/analysis.go +259 -55
  5. package/compiler/assignment.go +2 -2
  6. package/compiler/builtin_test.go +1 -1
  7. package/compiler/compiler.go +201 -49
  8. package/compiler/compiler_test.go +53 -0
  9. package/compiler/composite-lit.go +32 -8
  10. package/compiler/decl.go +6 -6
  11. package/compiler/expr-call.go +83 -0
  12. package/compiler/expr.go +1 -1
  13. package/compiler/protobuf.go +557 -0
  14. package/compiler/spec-struct.go +4 -0
  15. package/compiler/spec-value.go +11 -3
  16. package/compiler/spec.go +18 -1
  17. package/compiler/stmt-assign.go +35 -0
  18. package/compiler/type-assert.go +87 -0
  19. package/compiler/type.go +5 -2
  20. package/dist/gs/builtin/builtin.d.ts +19 -1
  21. package/dist/gs/builtin/builtin.js +85 -5
  22. package/dist/gs/builtin/builtin.js.map +1 -1
  23. package/dist/gs/builtin/channel.js.map +1 -1
  24. package/dist/gs/builtin/slice.d.ts +1 -1
  25. package/dist/gs/builtin/slice.js +59 -26
  26. package/dist/gs/builtin/slice.js.map +1 -1
  27. package/dist/gs/cmp/index.js.map +1 -1
  28. package/dist/gs/context/context.d.ts +1 -1
  29. package/dist/gs/context/context.js +20 -11
  30. package/dist/gs/context/context.js.map +1 -1
  31. package/dist/gs/errors/errors.d.ts +7 -0
  32. package/dist/gs/errors/errors.js +190 -0
  33. package/dist/gs/errors/errors.js.map +1 -0
  34. package/dist/gs/errors/index.d.ts +1 -0
  35. package/dist/gs/errors/index.js +2 -0
  36. package/dist/gs/errors/index.js.map +1 -0
  37. package/dist/gs/internal/goarch/index.js +1 -1
  38. package/dist/gs/internal/goarch/index.js.map +1 -1
  39. package/dist/gs/io/index.d.ts +1 -0
  40. package/dist/gs/io/index.js +2 -0
  41. package/dist/gs/io/index.js.map +1 -0
  42. package/dist/gs/io/io.d.ts +107 -0
  43. package/dist/gs/io/io.js +385 -0
  44. package/dist/gs/io/io.js.map +1 -0
  45. package/dist/gs/iter/iter.js.map +1 -1
  46. package/dist/gs/math/bits/index.js +34 -32
  47. package/dist/gs/math/bits/index.js.map +1 -1
  48. package/dist/gs/runtime/runtime.d.ts +1 -0
  49. package/dist/gs/runtime/runtime.js +15 -18
  50. package/dist/gs/runtime/runtime.js.map +1 -1
  51. package/dist/gs/slices/slices.d.ts +1 -1
  52. package/dist/gs/slices/slices.js +1 -1
  53. package/dist/gs/slices/slices.js.map +1 -1
  54. package/dist/gs/strings/builder.d.ts +18 -0
  55. package/dist/gs/strings/builder.js +205 -0
  56. package/dist/gs/strings/builder.js.map +1 -0
  57. package/dist/gs/strings/clone.d.ts +1 -0
  58. package/dist/gs/strings/clone.js +16 -0
  59. package/dist/gs/strings/clone.js.map +1 -0
  60. package/dist/gs/strings/compare.d.ts +1 -0
  61. package/dist/gs/strings/compare.js +14 -0
  62. package/dist/gs/strings/compare.js.map +1 -0
  63. package/dist/gs/strings/index.d.ts +2 -0
  64. package/dist/gs/strings/index.js +3 -0
  65. package/dist/gs/strings/index.js.map +1 -0
  66. package/dist/gs/strings/iter.d.ts +8 -0
  67. package/dist/gs/strings/iter.js +160 -0
  68. package/dist/gs/strings/iter.js.map +1 -0
  69. package/dist/gs/strings/reader.d.ts +34 -0
  70. package/dist/gs/strings/reader.js +418 -0
  71. package/dist/gs/strings/reader.js.map +1 -0
  72. package/dist/gs/strings/replace.d.ts +106 -0
  73. package/dist/gs/strings/replace.js +1136 -0
  74. package/dist/gs/strings/replace.js.map +1 -0
  75. package/dist/gs/strings/search.d.ts +24 -0
  76. package/dist/gs/strings/search.js +169 -0
  77. package/dist/gs/strings/search.js.map +1 -0
  78. package/dist/gs/strings/strings.d.ts +47 -0
  79. package/dist/gs/strings/strings.js +418 -0
  80. package/dist/gs/strings/strings.js.map +1 -0
  81. package/dist/gs/stringslite/index.d.ts +1 -0
  82. package/dist/gs/stringslite/index.js +2 -0
  83. package/dist/gs/stringslite/index.js.map +1 -0
  84. package/dist/gs/stringslite/strings.d.ts +11 -0
  85. package/dist/gs/stringslite/strings.js +67 -0
  86. package/dist/gs/stringslite/strings.js.map +1 -0
  87. package/dist/gs/sync/index.d.ts +1 -0
  88. package/dist/gs/sync/index.js +2 -0
  89. package/dist/gs/sync/index.js.map +1 -0
  90. package/dist/gs/sync/sync.d.ts +79 -0
  91. package/dist/gs/sync/sync.js +392 -0
  92. package/dist/gs/sync/sync.js.map +1 -0
  93. package/dist/gs/time/time.js +7 -7
  94. package/dist/gs/time/time.js.map +1 -1
  95. package/dist/gs/unicode/index.d.ts +1 -0
  96. package/dist/gs/unicode/index.js +2 -0
  97. package/dist/gs/unicode/index.js.map +1 -0
  98. package/dist/gs/unicode/unicode.d.ts +105 -0
  99. package/dist/gs/unicode/unicode.js +332 -0
  100. package/dist/gs/unicode/unicode.js.map +1 -0
  101. package/dist/gs/unicode/utf8/index.d.ts +1 -0
  102. package/dist/gs/unicode/utf8/index.js +3 -0
  103. package/dist/gs/unicode/utf8/index.js.map +1 -0
  104. package/dist/gs/unicode/utf8/utf8.d.ts +20 -0
  105. package/dist/gs/unicode/utf8/utf8.js +196 -0
  106. package/dist/gs/unicode/utf8/utf8.js.map +1 -0
  107. package/dist/gs/unsafe/index.d.ts +1 -0
  108. package/dist/gs/unsafe/index.js +2 -0
  109. package/dist/gs/unsafe/index.js.map +1 -0
  110. package/dist/gs/unsafe/unsafe.d.ts +11 -0
  111. package/dist/gs/unsafe/unsafe.js +44 -0
  112. package/dist/gs/unsafe/unsafe.js.map +1 -0
  113. package/go.mod +2 -1
  114. package/go.sum +6 -2
  115. package/gs/README.md +6 -0
  116. package/gs/builtin/builtin.ts +158 -0
  117. package/gs/builtin/channel.ts +683 -0
  118. package/gs/builtin/defer.ts +58 -0
  119. package/gs/builtin/index.ts +1 -0
  120. package/gs/builtin/io.ts +22 -0
  121. package/gs/builtin/map.ts +50 -0
  122. package/gs/builtin/slice.ts +1030 -0
  123. package/gs/builtin/type.ts +1106 -0
  124. package/gs/builtin/varRef.ts +25 -0
  125. package/gs/cmp/godoc.txt +8 -0
  126. package/gs/cmp/index.ts +29 -0
  127. package/gs/context/context.ts +401 -0
  128. package/gs/context/godoc.txt +69 -0
  129. package/gs/context/index.ts +1 -0
  130. package/gs/errors/errors.ts +223 -0
  131. package/gs/errors/godoc.txt +63 -0
  132. package/gs/errors/index.ts +1 -0
  133. package/gs/internal/goarch/godoc.txt +39 -0
  134. package/gs/internal/goarch/index.ts +18 -0
  135. package/gs/io/godoc.txt +61 -0
  136. package/gs/io/index.ts +1 -0
  137. package/gs/io/io.go +75 -0
  138. package/gs/io/io.ts +546 -0
  139. package/gs/iter/godoc.txt +203 -0
  140. package/gs/iter/index.ts +1 -0
  141. package/gs/iter/iter.ts +117 -0
  142. package/gs/math/bits/index.ts +356 -0
  143. package/gs/math/godoc.txt +76 -0
  144. package/gs/runtime/godoc.txt +331 -0
  145. package/gs/runtime/index.ts +1 -0
  146. package/gs/runtime/runtime.ts +178 -0
  147. package/gs/slices/godoc.txt +44 -0
  148. package/gs/slices/index.ts +1 -0
  149. package/gs/slices/slices.ts +22 -0
  150. package/gs/strings/builder.test.ts +121 -0
  151. package/gs/strings/builder.ts +223 -0
  152. package/gs/strings/clone.test.ts +43 -0
  153. package/gs/strings/clone.ts +17 -0
  154. package/gs/strings/compare.test.ts +84 -0
  155. package/gs/strings/compare.ts +13 -0
  156. package/gs/strings/godoc.txt +66 -0
  157. package/gs/strings/index.ts +2 -0
  158. package/gs/strings/iter.test.ts +343 -0
  159. package/gs/strings/iter.ts +171 -0
  160. package/gs/strings/reader.test.ts +243 -0
  161. package/gs/strings/reader.ts +451 -0
  162. package/gs/strings/replace.test.ts +181 -0
  163. package/gs/strings/replace.ts +1310 -0
  164. package/gs/strings/search.test.ts +214 -0
  165. package/gs/strings/search.ts +213 -0
  166. package/gs/strings/strings.test.ts +477 -0
  167. package/gs/strings/strings.ts +510 -0
  168. package/gs/stringslite/godoc.txt +17 -0
  169. package/gs/stringslite/index.ts +1 -0
  170. package/gs/stringslite/strings.ts +82 -0
  171. package/gs/sync/godoc.txt +21 -0
  172. package/gs/sync/index.ts +1 -0
  173. package/gs/sync/sync.go +64 -0
  174. package/gs/sync/sync.ts +449 -0
  175. package/gs/time/godoc.md +116 -0
  176. package/gs/time/godoc.txt +116 -0
  177. package/gs/time/index.ts +1 -0
  178. package/gs/time/time.ts +272 -0
  179. package/gs/unicode/godoc.txt +52 -0
  180. package/gs/unicode/index.ts +1 -0
  181. package/gs/unicode/unicode.go +38 -0
  182. package/gs/unicode/unicode.ts +418 -0
  183. package/gs/unicode/utf8/godoc.txt +22 -0
  184. package/gs/unicode/utf8/index.ts +2 -0
  185. package/gs/unicode/utf8/utf8.ts +227 -0
  186. package/gs/unsafe/godoc.txt +19 -0
  187. package/gs/unsafe/index.ts +1 -0
  188. package/gs/unsafe/unsafe.test.ts +68 -0
  189. package/gs/unsafe/unsafe.ts +77 -0
  190. package/package.json +6 -4
@@ -0,0 +1,214 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import {
3
+ stringFinder,
4
+ makeStringFinder,
5
+ longestCommonSuffix,
6
+ } from './search.js'
7
+
8
+ describe('strings/search', () => {
9
+ describe('stringFinder', () => {
10
+ it('should create a stringFinder instance', () => {
11
+ const finder = new stringFinder({
12
+ pattern: 'test',
13
+ badCharSkip: new Array(256).fill(4),
14
+ goodSuffixSkip: [0, 0, 0, 0],
15
+ })
16
+
17
+ expect(finder.pattern).toBe('test')
18
+ expect(finder.badCharSkip).toHaveLength(256)
19
+ expect(finder.goodSuffixSkip).toHaveLength(4)
20
+ })
21
+
22
+ it('should clone a stringFinder instance', () => {
23
+ const original = new stringFinder({
24
+ pattern: 'hello',
25
+ badCharSkip: new Array(256).fill(5),
26
+ goodSuffixSkip: [1, 2, 3, 4, 5],
27
+ })
28
+
29
+ const cloned = original.clone()
30
+
31
+ expect(cloned.pattern).toBe(original.pattern)
32
+ expect(cloned.badCharSkip).toEqual(original.badCharSkip)
33
+ expect(cloned.goodSuffixSkip).toEqual(original.goodSuffixSkip)
34
+
35
+ // Ensure it's a deep clone
36
+ cloned.pattern = 'world'
37
+ expect(original.pattern).toBe('hello')
38
+ })
39
+
40
+ it('should find pattern in text using next method', () => {
41
+ const finder = makeStringFinder('abc')
42
+ expect(finder).not.toBeNull()
43
+
44
+ if (finder) {
45
+ expect(finder.next('abcdef')).toBe(0)
46
+ expect(finder.next('xyzabc')).toBe(3)
47
+ expect(finder.next('abcabc')).toBe(0)
48
+ expect(finder.next('xyz')).toBe(-1)
49
+ }
50
+ })
51
+
52
+ it('should handle empty pattern', () => {
53
+ const finder = makeStringFinder('')
54
+ expect(finder).not.toBeNull()
55
+
56
+ if (finder) {
57
+ expect(finder.next('anything')).toBe(0)
58
+ expect(finder.next('')).toBe(0)
59
+ }
60
+ })
61
+
62
+ it('should handle pattern not found', () => {
63
+ const finder = makeStringFinder('xyz')
64
+ expect(finder).not.toBeNull()
65
+
66
+ if (finder) {
67
+ expect(finder.next('abc')).toBe(-1)
68
+ expect(finder.next('abcdef')).toBe(-1)
69
+ expect(finder.next('')).toBe(-1)
70
+ }
71
+ })
72
+
73
+ it('should handle pattern longer than text', () => {
74
+ const finder = makeStringFinder('abcdef')
75
+ expect(finder).not.toBeNull()
76
+
77
+ if (finder) {
78
+ expect(finder.next('abc')).toBe(-1)
79
+ expect(finder.next('abcde')).toBe(-1)
80
+ expect(finder.next('abcdef')).toBe(0)
81
+ }
82
+ })
83
+
84
+ it('should find multiple occurrences correctly', () => {
85
+ const finder = makeStringFinder('ab')
86
+ expect(finder).not.toBeNull()
87
+
88
+ if (finder) {
89
+ const text = 'ababab'
90
+ expect(finder.next(text)).toBe(0)
91
+ expect(finder.next(text.slice(2))).toBe(0) // Should find at position 2 in original
92
+ expect(finder.next(text.slice(4))).toBe(0) // Should find at position 4 in original
93
+ }
94
+ })
95
+
96
+ it('should handle single character patterns', () => {
97
+ const finder = makeStringFinder('a')
98
+ expect(finder).not.toBeNull()
99
+
100
+ if (finder) {
101
+ expect(finder.next('abc')).toBe(0)
102
+ expect(finder.next('bac')).toBe(1)
103
+ expect(finder.next('bca')).toBe(2)
104
+ expect(finder.next('xyz')).toBe(-1)
105
+ }
106
+ })
107
+
108
+ it('should handle repeated character patterns', () => {
109
+ const finder = makeStringFinder('aaa')
110
+ expect(finder).not.toBeNull()
111
+
112
+ if (finder) {
113
+ expect(finder.next('aaaa')).toBe(0)
114
+ expect(finder.next('baaaa')).toBe(1)
115
+ expect(finder.next('aa')).toBe(-1)
116
+ expect(finder.next('ababab')).toBe(-1)
117
+ }
118
+ })
119
+ })
120
+
121
+ describe('makeStringFinder', () => {
122
+ it('should create a properly initialized stringFinder', () => {
123
+ const pattern = 'hello'
124
+ const finder = makeStringFinder(pattern)
125
+
126
+ expect(finder).not.toBeNull()
127
+ if (finder) {
128
+ expect(finder.pattern).toBe(pattern)
129
+ expect(finder.badCharSkip).toHaveLength(256)
130
+ expect(finder.goodSuffixSkip).toHaveLength(pattern.length)
131
+
132
+ // Test that bad character skip table is properly initialized
133
+ // Characters not in pattern should skip the full pattern length
134
+ expect(finder.badCharSkip[0]).toBe(pattern.length) // null character
135
+ expect(finder.badCharSkip[255]).toBe(pattern.length) // high ASCII
136
+
137
+ // Characters in pattern should have appropriate skip values
138
+ const hIndex = 'h'.charCodeAt(0)
139
+ const eIndex = 'e'.charCodeAt(0)
140
+ const lIndex = 'l'.charCodeAt(0)
141
+ const oIndex = 'o'.charCodeAt(0)
142
+
143
+ expect(finder.badCharSkip[hIndex]).toBeLessThan(pattern.length)
144
+ expect(finder.badCharSkip[eIndex]).toBeLessThan(pattern.length)
145
+ expect(finder.badCharSkip[lIndex]).toBeLessThan(pattern.length)
146
+ // 'o' is the last character, so it gets the full pattern length as skip value
147
+ expect(finder.badCharSkip[oIndex]).toBe(pattern.length)
148
+ }
149
+ })
150
+
151
+ it('should handle single character patterns', () => {
152
+ const finder = makeStringFinder('x')
153
+
154
+ expect(finder).not.toBeNull()
155
+ if (finder) {
156
+ expect(finder.pattern).toBe('x')
157
+ expect(finder.goodSuffixSkip).toHaveLength(1)
158
+
159
+ const xIndex = 'x'.charCodeAt(0)
160
+ // For single character patterns, the character gets the full pattern length as skip value
161
+ expect(finder.badCharSkip[xIndex]).toBe(1)
162
+ }
163
+ })
164
+
165
+ it('should handle empty pattern', () => {
166
+ const finder = makeStringFinder('')
167
+
168
+ expect(finder).not.toBeNull()
169
+ if (finder) {
170
+ expect(finder.pattern).toBe('')
171
+ expect(finder.goodSuffixSkip).toHaveLength(0)
172
+ }
173
+ })
174
+ })
175
+
176
+ describe('longestCommonSuffix', () => {
177
+ it('should find longest common suffix', () => {
178
+ expect(longestCommonSuffix('abc', 'bc')).toBe(2)
179
+ expect(longestCommonSuffix('hello', 'llo')).toBe(3)
180
+ expect(longestCommonSuffix('test', 'st')).toBe(2)
181
+ expect(longestCommonSuffix('abcd', 'cd')).toBe(2)
182
+ })
183
+
184
+ it('should handle no common suffix', () => {
185
+ expect(longestCommonSuffix('abc', 'def')).toBe(0)
186
+ expect(longestCommonSuffix('hello', 'world')).toBe(0)
187
+ expect(longestCommonSuffix('test', 'xyz')).toBe(0)
188
+ })
189
+
190
+ it('should handle identical strings', () => {
191
+ expect(longestCommonSuffix('abc', 'abc')).toBe(3)
192
+ expect(longestCommonSuffix('hello', 'hello')).toBe(5)
193
+ expect(longestCommonSuffix('', '')).toBe(0)
194
+ })
195
+
196
+ it('should handle empty strings', () => {
197
+ expect(longestCommonSuffix('', 'abc')).toBe(0)
198
+ expect(longestCommonSuffix('abc', '')).toBe(0)
199
+ expect(longestCommonSuffix('', '')).toBe(0)
200
+ })
201
+
202
+ it('should handle one string being suffix of another', () => {
203
+ expect(longestCommonSuffix('hello', 'lo')).toBe(2)
204
+ expect(longestCommonSuffix('testing', 'ing')).toBe(3)
205
+ expect(longestCommonSuffix('abc', 'c')).toBe(1)
206
+ })
207
+
208
+ it('should handle single character strings', () => {
209
+ expect(longestCommonSuffix('a', 'a')).toBe(1)
210
+ expect(longestCommonSuffix('a', 'b')).toBe(0)
211
+ expect(longestCommonSuffix('x', 'x')).toBe(1)
212
+ })
213
+ })
214
+ })
@@ -0,0 +1,213 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import { HasPrefix } from './strings.js'
3
+
4
+ // Helper function for max of two numbers
5
+ function max(a: number, b: number): number {
6
+ return a > b ? a : b
7
+ }
8
+
9
+ export class stringFinder {
10
+ // pattern is the string that we are searching for in the text.
11
+ public get pattern(): string {
12
+ return this._fields.pattern.value
13
+ }
14
+ public set pattern(value: string) {
15
+ this._fields.pattern.value = value
16
+ }
17
+
18
+ // badCharSkip[b] contains the distance between the last byte of pattern
19
+ // and the rightmost occurrence of b in pattern. If b is not in pattern,
20
+ // badCharSkip[b] is len(pattern).
21
+ //
22
+ // Whenever a mismatch is found with byte b in the text, we can safely
23
+ // shift the matching frame at least badCharSkip[b] until the next time
24
+ // the matching char could be in alignment.
25
+ public get badCharSkip(): number[] {
26
+ return this._fields.badCharSkip.value
27
+ }
28
+ public set badCharSkip(value: number[]) {
29
+ this._fields.badCharSkip.value = value
30
+ }
31
+
32
+ // goodSuffixSkip[i] defines how far we can shift the matching frame given
33
+ // that the suffix pattern[i+1:] matches, but the byte pattern[i] does
34
+ // not. There are two cases to consider:
35
+ //
36
+ // 1. The matched suffix occurs elsewhere in pattern (with a different
37
+ // byte preceding it that we might possibly match). In this case, we can
38
+ // shift the matching frame to align with the next suffix chunk. For
39
+ // example, the pattern "mississi" has the suffix "issi" next occurring
40
+ // (in right-to-left order) at index 1, so goodSuffixSkip[3] ==
41
+ // shift+len(suffix) == 3+4 == 7.
42
+ //
43
+ // 2. If the matched suffix does not occur elsewhere in pattern, then the
44
+ // matching frame may share part of its prefix with the end of the
45
+ // matching suffix. In this case, goodSuffixSkip[i] will contain how far
46
+ // to shift the frame to align this portion of the prefix to the
47
+ // suffix. For example, in the pattern "abcxxxabc", when the first
48
+ // mismatch from the back is found to be in position 3, the matching
49
+ // suffix "xxabc" is not found elsewhere in the pattern. However, its
50
+ // rightmost "abc" (at position 6) is a prefix of the whole pattern, so
51
+ // goodSuffixSkip[3] == shift+len(suffix) == 6+5 == 11.
52
+ public get goodSuffixSkip(): $.Slice<number> {
53
+ return this._fields.goodSuffixSkip.value
54
+ }
55
+ public set goodSuffixSkip(value: $.Slice<number>) {
56
+ this._fields.goodSuffixSkip.value = value
57
+ }
58
+
59
+ public _fields: {
60
+ pattern: $.VarRef<string>
61
+ badCharSkip: $.VarRef<number[]>
62
+ goodSuffixSkip: $.VarRef<$.Slice<number>>
63
+ }
64
+
65
+ constructor(
66
+ init?: Partial<{
67
+ badCharSkip?: number[]
68
+ goodSuffixSkip?: $.Slice<number>
69
+ pattern?: string
70
+ }>,
71
+ ) {
72
+ this._fields = {
73
+ pattern: $.varRef(init?.pattern ?? ''),
74
+ badCharSkip: $.varRef(init?.badCharSkip ?? new Array(256).fill(0)),
75
+ goodSuffixSkip: $.varRef(init?.goodSuffixSkip ?? null),
76
+ }
77
+ }
78
+
79
+ public clone(): stringFinder {
80
+ const cloned = new stringFinder()
81
+ cloned._fields = {
82
+ pattern: $.varRef(this._fields.pattern.value),
83
+ badCharSkip: $.varRef(this._fields.badCharSkip.value),
84
+ goodSuffixSkip: $.varRef(this._fields.goodSuffixSkip.value),
85
+ }
86
+ return cloned
87
+ }
88
+
89
+ // next returns the index in text of the first occurrence of the pattern. If
90
+ // the pattern is not found, it returns -1.
91
+ public next(text: string): number {
92
+ const f = this
93
+ let i = $.len(f!.pattern) - 1
94
+ for (; i < $.len(text); ) {
95
+ // Compare backwards from the end until the first unmatching character.
96
+ let j = $.len(f!.pattern) - 1
97
+ for (
98
+ ;
99
+ j >= 0 && $.indexString(text, i) == $.indexString(f!.pattern, j);
100
+
101
+ ) {
102
+ i--
103
+ j--
104
+ }
105
+
106
+ // match
107
+ if (j < 0) {
108
+ return i + 1
109
+ }
110
+ i += max(f!.badCharSkip![$.indexString(text, i)], f!.goodSuffixSkip![j])
111
+ }
112
+ return -1
113
+ }
114
+
115
+ // Register this type with the runtime type system
116
+ static __typeInfo = $.registerStructType(
117
+ 'stringFinder',
118
+ new stringFinder(),
119
+ [
120
+ {
121
+ name: 'next',
122
+ args: [
123
+ { name: 'text', type: { kind: $.TypeKind.Basic, name: 'string' } },
124
+ ],
125
+ returns: [{ type: { kind: $.TypeKind.Basic, name: 'number' } }],
126
+ },
127
+ ],
128
+ stringFinder,
129
+ {
130
+ pattern: { kind: $.TypeKind.Basic, name: 'string' },
131
+ badCharSkip: {
132
+ kind: $.TypeKind.Array,
133
+ length: 256,
134
+ elemType: { kind: $.TypeKind.Basic, name: 'number' },
135
+ },
136
+ goodSuffixSkip: {
137
+ kind: $.TypeKind.Slice,
138
+ elemType: { kind: $.TypeKind.Basic, name: 'number' },
139
+ },
140
+ },
141
+ )
142
+ }
143
+
144
+ export function makeStringFinder(pattern: string): stringFinder | null {
145
+ let f = new stringFinder({
146
+ goodSuffixSkip: $.makeSlice<number>($.len(pattern)),
147
+ pattern: pattern,
148
+ })
149
+ // last is the index of the last character in the pattern.
150
+ let last = $.len(pattern) - 1
151
+
152
+ // Build bad character table.
153
+ // Bytes not in the pattern can skip one pattern's length.
154
+ for (let i = 0; i < $.len(f!.badCharSkip); i++) {
155
+ {
156
+ f!.badCharSkip![i] = $.len(pattern)
157
+ }
158
+ }
159
+ // The loop condition is < instead of <= so that the last byte does not
160
+ // have a zero distance to itself. Finding this byte out of place implies
161
+ // that it is not in the last position.
162
+ for (let i = 0; i < last; i++) {
163
+ f!.badCharSkip![$.indexString(pattern, i)] = last - i
164
+ }
165
+
166
+ // Build good suffix table.
167
+ // First pass: set each value to the next index which starts a prefix of
168
+ // pattern.
169
+ let lastPrefix = last
170
+
171
+ // lastPrefix is the shift, and (last-i) is len(suffix).
172
+ for (let i = last; i >= 0; i--) {
173
+ if (HasPrefix(pattern, $.sliceString(pattern, i + 1, undefined))) {
174
+ lastPrefix = i + 1
175
+ }
176
+ // lastPrefix is the shift, and (last-i) is len(suffix).
177
+ f!.goodSuffixSkip![i] = lastPrefix + last - i
178
+ }
179
+ // Second pass: find repeats of pattern's suffix starting from the front.
180
+
181
+ // (last-i) is the shift, and lenSuffix is len(suffix).
182
+ for (let i = 0; i < last; i++) {
183
+ let lenSuffix = longestCommonSuffix(
184
+ pattern,
185
+ $.sliceString(pattern, 1, i + 1),
186
+ )
187
+
188
+ // (last-i) is the shift, and lenSuffix is len(suffix).
189
+ if (
190
+ $.indexString(pattern, i - lenSuffix) !=
191
+ $.indexString(pattern, last - lenSuffix)
192
+ ) {
193
+ // (last-i) is the shift, and lenSuffix is len(suffix).
194
+ f!.goodSuffixSkip![last - lenSuffix] = lenSuffix + last - i
195
+ }
196
+ }
197
+
198
+ return f
199
+ }
200
+
201
+ export function longestCommonSuffix(a: string, b: string): number {
202
+ let i: number = 0
203
+ {
204
+ for (; i < $.len(a) && i < $.len(b); i++) {
205
+ if (
206
+ $.indexString(a, $.len(a) - 1 - i) != $.indexString(b, $.len(b) - 1 - i)
207
+ ) {
208
+ break
209
+ }
210
+ }
211
+ return i
212
+ }
213
+ }