goscript 0.0.26 → 0.0.29
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/README.md +4 -4
- package/cmd/goscript/cmd_compile.go +0 -3
- package/cmd/goscript/deps.go +11 -0
- package/compiler/analysis.go +298 -55
- package/compiler/assignment.go +2 -2
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +200 -68
- package/compiler/compiler_test.go +17 -24
- package/compiler/composite-lit.go +32 -8
- package/compiler/decl.go +6 -6
- package/compiler/expr-call.go +170 -15
- package/compiler/expr-selector.go +100 -0
- package/compiler/expr.go +1 -1
- package/compiler/protobuf.go +557 -0
- package/compiler/spec-struct.go +4 -0
- package/compiler/spec-value.go +89 -10
- package/compiler/spec.go +254 -1
- package/compiler/stmt-assign.go +35 -0
- package/compiler/type-assert.go +87 -0
- package/compiler/type.go +4 -1
- package/dist/gs/builtin/builtin.d.ts +20 -1
- package/dist/gs/builtin/builtin.js +95 -4
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js +21 -2
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/errors/errors.d.ts +5 -6
- package/dist/gs/errors/errors.js.map +1 -1
- package/dist/gs/internal/oserror/errors.d.ts +6 -0
- package/dist/gs/internal/oserror/errors.js +7 -0
- package/dist/gs/internal/oserror/errors.js.map +1 -0
- package/dist/gs/internal/oserror/index.d.ts +1 -0
- package/dist/gs/internal/oserror/index.js +2 -0
- package/dist/gs/internal/oserror/index.js.map +1 -0
- package/dist/gs/io/fs/format.d.ts +3 -0
- package/dist/gs/io/fs/format.js +56 -0
- package/dist/gs/io/fs/format.js.map +1 -0
- package/dist/gs/io/fs/fs.d.ts +79 -0
- package/dist/gs/io/fs/fs.js +200 -0
- package/dist/gs/io/fs/fs.js.map +1 -0
- package/dist/gs/io/fs/glob.d.ts +10 -0
- package/dist/gs/io/fs/glob.js +141 -0
- package/dist/gs/io/fs/glob.js.map +1 -0
- package/dist/gs/io/fs/index.d.ts +8 -0
- package/dist/gs/io/fs/index.js +9 -0
- package/dist/gs/io/fs/index.js.map +1 -0
- package/dist/gs/io/fs/readdir.d.ts +7 -0
- package/dist/gs/io/fs/readdir.js +152 -0
- package/dist/gs/io/fs/readdir.js.map +1 -0
- package/dist/gs/io/fs/readfile.d.ts +6 -0
- package/dist/gs/io/fs/readfile.js +118 -0
- package/dist/gs/io/fs/readfile.js.map +1 -0
- package/dist/gs/io/fs/stat.d.ts +6 -0
- package/dist/gs/io/fs/stat.js +87 -0
- package/dist/gs/io/fs/stat.js.map +1 -0
- package/dist/gs/io/fs/sub.d.ts +6 -0
- package/dist/gs/io/fs/sub.js +172 -0
- package/dist/gs/io/fs/sub.js.map +1 -0
- package/dist/gs/io/fs/walk.d.ts +7 -0
- package/dist/gs/io/fs/walk.js +76 -0
- package/dist/gs/io/fs/walk.js.map +1 -0
- package/dist/gs/io/index.d.ts +1 -0
- package/dist/gs/io/index.js +2 -0
- package/dist/gs/io/index.js.map +1 -0
- package/dist/gs/io/io.d.ts +107 -0
- package/dist/gs/io/io.js +385 -0
- package/dist/gs/io/io.js.map +1 -0
- package/dist/gs/path/index.d.ts +2 -0
- package/dist/gs/path/index.js +3 -0
- package/dist/gs/path/index.js.map +1 -0
- package/dist/gs/path/match.d.ts +6 -0
- package/dist/gs/path/match.js +281 -0
- package/dist/gs/path/match.js.map +1 -0
- package/dist/gs/path/path.d.ts +7 -0
- package/dist/gs/path/path.js +256 -0
- package/dist/gs/path/path.js.map +1 -0
- package/dist/gs/strings/builder.d.ts +18 -0
- package/dist/gs/strings/builder.js +205 -0
- package/dist/gs/strings/builder.js.map +1 -0
- package/dist/gs/strings/clone.d.ts +1 -0
- package/dist/gs/strings/clone.js +16 -0
- package/dist/gs/strings/clone.js.map +1 -0
- package/dist/gs/strings/compare.d.ts +1 -0
- package/dist/gs/strings/compare.js +14 -0
- package/dist/gs/strings/compare.js.map +1 -0
- package/dist/gs/strings/index.d.ts +2 -0
- package/dist/gs/strings/index.js +3 -0
- package/dist/gs/strings/index.js.map +1 -0
- package/dist/gs/strings/iter.d.ts +8 -0
- package/dist/gs/strings/iter.js +160 -0
- package/dist/gs/strings/iter.js.map +1 -0
- package/dist/gs/strings/reader.d.ts +34 -0
- package/dist/gs/strings/reader.js +418 -0
- package/dist/gs/strings/reader.js.map +1 -0
- package/dist/gs/strings/replace.d.ts +106 -0
- package/dist/gs/strings/replace.js +1136 -0
- package/dist/gs/strings/replace.js.map +1 -0
- package/dist/gs/strings/search.d.ts +24 -0
- package/dist/gs/strings/search.js +169 -0
- package/dist/gs/strings/search.js.map +1 -0
- package/dist/gs/strings/strings.d.ts +47 -0
- package/dist/gs/strings/strings.js +418 -0
- package/dist/gs/strings/strings.js.map +1 -0
- package/dist/gs/stringslite/index.d.ts +1 -0
- package/dist/gs/stringslite/index.js +2 -0
- package/dist/gs/stringslite/index.js.map +1 -0
- package/dist/gs/stringslite/strings.d.ts +11 -0
- package/dist/gs/stringslite/strings.js +67 -0
- package/dist/gs/stringslite/strings.js.map +1 -0
- package/dist/gs/sync/index.d.ts +1 -0
- package/dist/gs/sync/index.js +2 -0
- package/dist/gs/sync/index.js.map +1 -0
- package/dist/gs/sync/sync.d.ts +79 -0
- package/dist/gs/sync/sync.js +392 -0
- package/dist/gs/sync/sync.js.map +1 -0
- package/dist/gs/time/time.d.ts +11 -2
- package/dist/gs/time/time.js +337 -12
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/index.d.ts +1 -0
- package/dist/gs/unicode/index.js +2 -0
- package/dist/gs/unicode/index.js.map +1 -0
- package/dist/gs/unicode/unicode.d.ts +105 -0
- package/dist/gs/unicode/unicode.js +332 -0
- package/dist/gs/unicode/unicode.js.map +1 -0
- package/dist/gs/unicode/utf8/index.d.ts +1 -0
- package/dist/gs/unicode/utf8/index.js +3 -0
- package/dist/gs/unicode/utf8/index.js.map +1 -0
- package/dist/gs/unicode/utf8/utf8.d.ts +20 -0
- package/dist/gs/unicode/utf8/utf8.js +196 -0
- package/dist/gs/unicode/utf8/utf8.js.map +1 -0
- package/dist/gs/unsafe/index.d.ts +1 -0
- package/dist/gs/unsafe/index.js +2 -0
- package/dist/gs/unsafe/index.js.map +1 -0
- package/dist/gs/unsafe/unsafe.d.ts +11 -0
- package/dist/gs/unsafe/unsafe.js +44 -0
- package/dist/gs/unsafe/unsafe.js.map +1 -0
- package/go.mod +2 -1
- package/go.sum +6 -2
- package/gs/README.md +6 -0
- package/gs/builtin/builtin.ts +171 -0
- package/gs/builtin/channel.ts +683 -0
- package/gs/builtin/defer.ts +58 -0
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/io.ts +22 -0
- package/gs/builtin/map.ts +50 -0
- package/gs/builtin/slice.ts +1030 -0
- package/gs/builtin/type.ts +1106 -0
- package/gs/builtin/varRef.ts +25 -0
- package/gs/cmp/godoc.txt +8 -0
- package/gs/cmp/index.ts +29 -0
- package/gs/context/context.ts +401 -0
- package/gs/context/godoc.txt +69 -0
- package/gs/context/index.ts +1 -0
- package/gs/errors/errors.ts +223 -0
- package/gs/errors/godoc.txt +63 -0
- package/gs/errors/index.ts +1 -0
- package/gs/internal/goarch/godoc.txt +39 -0
- package/gs/internal/goarch/index.ts +18 -0
- package/gs/internal/oserror/errors.ts +14 -0
- package/gs/internal/oserror/index.ts +1 -0
- package/gs/io/fs/format.ts +65 -0
- package/gs/io/fs/fs.ts +359 -0
- package/gs/io/fs/glob.ts +167 -0
- package/gs/io/fs/godoc.txt +35 -0
- package/gs/io/fs/index.ts +8 -0
- package/gs/io/fs/readdir.ts +126 -0
- package/gs/io/fs/readfile.ts +77 -0
- package/gs/io/fs/stat.ts +38 -0
- package/gs/io/fs/sub.ts +208 -0
- package/gs/io/fs/walk.ts +89 -0
- package/gs/io/godoc.txt +61 -0
- package/gs/io/index.ts +1 -0
- package/gs/io/io.go +75 -0
- package/gs/io/io.ts +546 -0
- package/gs/iter/godoc.txt +203 -0
- package/gs/iter/index.ts +1 -0
- package/gs/iter/iter.ts +117 -0
- package/gs/math/bits/index.ts +356 -0
- package/gs/math/godoc.txt +76 -0
- package/gs/path/index.ts +2 -0
- package/gs/path/match.ts +307 -0
- package/gs/path/path.ts +301 -0
- package/gs/runtime/godoc.txt +331 -0
- package/gs/runtime/index.ts +1 -0
- package/gs/runtime/runtime.ts +178 -0
- package/gs/slices/godoc.txt +44 -0
- package/gs/slices/index.ts +1 -0
- package/gs/slices/slices.ts +22 -0
- package/gs/strings/builder.test.ts +121 -0
- package/gs/strings/builder.ts +223 -0
- package/gs/strings/clone.test.ts +43 -0
- package/gs/strings/clone.ts +17 -0
- package/gs/strings/compare.test.ts +84 -0
- package/gs/strings/compare.ts +13 -0
- package/gs/strings/godoc.txt +66 -0
- package/gs/strings/index.ts +2 -0
- package/gs/strings/iter.test.ts +343 -0
- package/gs/strings/iter.ts +171 -0
- package/gs/strings/reader.test.ts +242 -0
- package/gs/strings/reader.ts +451 -0
- package/gs/strings/replace.test.ts +181 -0
- package/gs/strings/replace.ts +1310 -0
- package/gs/strings/search.test.ts +214 -0
- package/gs/strings/search.ts +213 -0
- package/gs/strings/strings.test.ts +477 -0
- package/gs/strings/strings.ts +510 -0
- package/gs/stringslite/godoc.txt +17 -0
- package/gs/stringslite/index.ts +1 -0
- package/gs/stringslite/strings.ts +82 -0
- package/gs/sync/godoc.txt +21 -0
- package/gs/sync/index.ts +1 -0
- package/gs/sync/sync.go +64 -0
- package/gs/sync/sync.ts +449 -0
- package/gs/time/godoc.txt +116 -0
- package/gs/time/index.ts +1 -0
- package/gs/time/time.ts +585 -0
- package/gs/unicode/godoc.txt +52 -0
- package/gs/unicode/index.ts +1 -0
- package/gs/unicode/unicode.go +38 -0
- package/gs/unicode/unicode.ts +418 -0
- package/gs/unicode/utf8/godoc.txt +22 -0
- package/gs/unicode/utf8/index.ts +2 -0
- package/gs/unicode/utf8/utf8.ts +227 -0
- package/gs/unsafe/godoc.txt +19 -0
- package/gs/unsafe/index.ts +1 -0
- package/gs/unsafe/unsafe.test.ts +68 -0
- package/gs/unsafe/unsafe.ts +77 -0
- package/package.json +4 -3
|
@@ -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
|
+
}
|