@amritk/nish-x86_64-linux 0.12.0 → 0.14.0
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/INSTALL.md +13 -13
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +85 -1
- package/runtime/nish.h +62 -11
- package/runtime/nish.mjs +38 -3
- package/runtime/runtime-host.c +178 -0
- package/runtime/{runtime_os.c → runtime-os.c} +16 -1
- package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
- package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
- package/runtime/runtime.c +5 -5
- package/runtime/shim.mjs +128 -6
- package/scripts/build.sh +19 -13
- package/std/README.md +42 -2
- package/std/collections.ts +194 -188
- package/std/crypto/base64url.ts +145 -0
- package/std/crypto/ct.ts +64 -0
- package/std/crypto/hkdf.ts +118 -0
- package/std/crypto/hmac.ts +155 -0
- package/std/crypto/sha256.ts +444 -0
- package/std/crypto/sha512.ts +510 -0
- package/std/crypto/x25519.ts +494 -0
- package/std/json.ts +136 -136
- package/std/map.ts +9 -7
- package/std/pair.ts +2 -2
- package/std/testing.ts +67 -67
- package/std/text.ts +54 -54
- package/std/threads.ts +126 -38
package/std/testing.ts
CHANGED
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
* of generated text, and a detail line forty thousand bytes long is not a
|
|
71
71
|
* diagnostic — it is the output the reader was already searching by hand.
|
|
72
72
|
*/
|
|
73
|
-
const TESTING_EXCERPT: i32 = 200
|
|
73
|
+
const TESTING_EXCERPT: i32 = 200
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* `text` when it is short enough to read, and its first `TESTING_EXCERPT` bytes
|
|
@@ -88,12 +88,12 @@ const TESTING_EXCERPT: i32 = 200;
|
|
|
88
88
|
* exported").
|
|
89
89
|
*/
|
|
90
90
|
const testingExcerpt = (text: string): string => {
|
|
91
|
-
const length: i32 = toI32(text.length)
|
|
91
|
+
const length: i32 = toI32(text.length)
|
|
92
92
|
if (length <= TESTING_EXCERPT) {
|
|
93
|
-
return text
|
|
93
|
+
return text
|
|
94
94
|
}
|
|
95
|
-
return `${text.substring(0, TESTING_EXCERPT)}... (${length} bytes)
|
|
96
|
-
}
|
|
95
|
+
return `${text.substring(0, TESTING_EXCERPT)}... (${length} bytes)`
|
|
96
|
+
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* The index of the first line at which `actual` and `expected` differ, the
|
|
@@ -107,21 +107,21 @@ const testingExcerpt = (text: string): string => {
|
|
|
107
107
|
* Ten lines is the cheaper half of that trade.
|
|
108
108
|
*/
|
|
109
109
|
const testingFirstDifferentLine = (actual: string[], expected: string[]): i32 => {
|
|
110
|
-
const actualLength: i32 = toI32(actual.length)
|
|
111
|
-
const expectedLength: i32 = toI32(expected.length)
|
|
112
|
-
const shorter: i32 = actualLength < expectedLength ? actualLength : expectedLength
|
|
113
|
-
let i: i32 = 0
|
|
110
|
+
const actualLength: i32 = toI32(actual.length)
|
|
111
|
+
const expectedLength: i32 = toI32(expected.length)
|
|
112
|
+
const shorter: i32 = actualLength < expectedLength ? actualLength : expectedLength
|
|
113
|
+
let i: i32 = 0
|
|
114
114
|
// Two bounds rather than `i < shorter`: they stop at the same index, and each
|
|
115
115
|
// is what proves one of the two reads below in range, where the minimum of
|
|
116
116
|
// the pair proves neither.
|
|
117
117
|
while (i < actualLength && i < expectedLength) {
|
|
118
118
|
if (actual[i] !== expected[i]) {
|
|
119
|
-
return i
|
|
119
|
+
return i
|
|
120
120
|
}
|
|
121
|
-
i += 1
|
|
121
|
+
i += 1
|
|
122
122
|
}
|
|
123
|
-
return actualLength === expectedLength ? -1 : shorter
|
|
124
|
-
}
|
|
123
|
+
return actualLength === expectedLength ? -1 : shorter
|
|
124
|
+
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* One run of checks, and the tally it reports.
|
|
@@ -132,30 +132,30 @@ const testingFirstDifferentLine = (actual: string[], expected: string[]): i32 =>
|
|
|
132
132
|
*/
|
|
133
133
|
export class Suite {
|
|
134
134
|
/** Names the summary line, so several suites in one program stay apart. */
|
|
135
|
-
name: string
|
|
135
|
+
name: string
|
|
136
136
|
// These three have initializers because the constructor does not assign them,
|
|
137
137
|
// and every field holds a value once it returns. `name` does not, and an
|
|
138
138
|
// initializer there would only be a store the constructor overwrites.
|
|
139
|
-
passed: i32 = 0
|
|
140
|
-
failed: i32 = 0
|
|
141
|
-
skipped: i32 = 0
|
|
139
|
+
passed: i32 = 0
|
|
140
|
+
failed: i32 = 0
|
|
141
|
+
skipped: i32 = 0
|
|
142
142
|
/**
|
|
143
143
|
* The names of the checks that failed, recapped by `done`. The detail of
|
|
144
144
|
* each was printed when it happened; this is the list a reader wants after
|
|
145
145
|
* a hundred lines of output have scrolled the failures off the screen.
|
|
146
146
|
*/
|
|
147
|
-
failures: string[]
|
|
147
|
+
failures: string[]
|
|
148
148
|
|
|
149
149
|
constructor(name: string) {
|
|
150
|
-
this.name = name
|
|
151
|
-
this.failures = []
|
|
150
|
+
this.name = name
|
|
151
|
+
this.failures = []
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/** Records a pass. Public because a program with a check of its own shape still wants the tally. */
|
|
155
155
|
pass(name: string): boolean {
|
|
156
|
-
this.passed += 1
|
|
157
|
-
console.log(`PASS ${name}`)
|
|
158
|
-
return true
|
|
156
|
+
this.passed += 1
|
|
157
|
+
console.log(`PASS ${name}`)
|
|
158
|
+
return true
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/**
|
|
@@ -164,13 +164,13 @@ export class Suite {
|
|
|
164
164
|
* name already says everything wants.
|
|
165
165
|
*/
|
|
166
166
|
fail(name: string, detail: string): boolean {
|
|
167
|
-
this.failed += 1
|
|
168
|
-
this.failures.push(name)
|
|
169
|
-
console.log(`FAIL ${name}`)
|
|
167
|
+
this.failed += 1
|
|
168
|
+
this.failures.push(name)
|
|
169
|
+
console.log(`FAIL ${name}`)
|
|
170
170
|
if (toI32(detail.length) > 0) {
|
|
171
|
-
console.log(` ${detail}`)
|
|
171
|
+
console.log(` ${detail}`)
|
|
172
172
|
}
|
|
173
|
-
return false
|
|
173
|
+
return false
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
@@ -179,36 +179,36 @@ export class Suite {
|
|
|
179
179
|
* decided to skip, the way `tests/run.js` decides when `llvm-as` is missing.
|
|
180
180
|
*/
|
|
181
181
|
skip(name: string, reason: string): void {
|
|
182
|
-
this.skipped += 1
|
|
183
|
-
console.log(`SKIP ${name} (${reason})`)
|
|
182
|
+
this.skipped += 1
|
|
183
|
+
console.log(`SKIP ${name} (${reason})`)
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
ok(name: string, condition: boolean): boolean {
|
|
187
187
|
if (condition) {
|
|
188
|
-
return this.pass(name)
|
|
188
|
+
return this.pass(name)
|
|
189
189
|
}
|
|
190
|
-
return this.fail(name, "expected true, got false")
|
|
190
|
+
return this.fail(name, "expected true, got false")
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
eqBool(name: string, actual: boolean, expected: boolean): boolean {
|
|
194
194
|
if (actual === expected) {
|
|
195
|
-
return this.pass(name)
|
|
195
|
+
return this.pass(name)
|
|
196
196
|
}
|
|
197
|
-
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
197
|
+
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
eqI32(name: string, actual: i32, expected: i32): boolean {
|
|
201
201
|
if (actual === expected) {
|
|
202
|
-
return this.pass(name)
|
|
202
|
+
return this.pass(name)
|
|
203
203
|
}
|
|
204
|
-
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
204
|
+
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
eqI64(name: string, actual: i64, expected: i64): boolean {
|
|
208
208
|
if (actual === expected) {
|
|
209
|
-
return this.pass(name)
|
|
209
|
+
return this.pass(name)
|
|
210
210
|
}
|
|
211
|
-
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
211
|
+
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
/**
|
|
@@ -218,9 +218,9 @@ export class Suite {
|
|
|
218
218
|
*/
|
|
219
219
|
eqF64(name: string, actual: f64, expected: f64): boolean {
|
|
220
220
|
if (actual === expected) {
|
|
221
|
-
return this.pass(name)
|
|
221
|
+
return this.pass(name)
|
|
222
222
|
}
|
|
223
|
-
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
223
|
+
return this.fail(name, `expected ${expected}, got ${actual}`)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
@@ -229,14 +229,14 @@ export class Suite {
|
|
|
229
229
|
* belongs to the computation, not to the harness.
|
|
230
230
|
*/
|
|
231
231
|
nearF64(name: string, actual: f64, expected: f64, tolerance: f64): boolean {
|
|
232
|
-
let delta: f64 = actual - expected
|
|
232
|
+
let delta: f64 = actual - expected
|
|
233
233
|
if (delta < 0) {
|
|
234
|
-
delta = -delta
|
|
234
|
+
delta = -delta
|
|
235
235
|
}
|
|
236
236
|
if (delta <= tolerance) {
|
|
237
|
-
return this.pass(name)
|
|
237
|
+
return this.pass(name)
|
|
238
238
|
}
|
|
239
|
-
return this.fail(name, `expected ${expected} +/- ${tolerance}, got ${actual}`)
|
|
239
|
+
return this.fail(name, `expected ${expected} +/- ${tolerance}, got ${actual}`)
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/**
|
|
@@ -246,9 +246,9 @@ export class Suite {
|
|
|
246
246
|
*/
|
|
247
247
|
eqStr(name: string, actual: string, expected: string): boolean {
|
|
248
248
|
if (actual === expected) {
|
|
249
|
-
return this.pass(name)
|
|
249
|
+
return this.pass(name)
|
|
250
250
|
}
|
|
251
|
-
return this.fail(name, `expected "${expected}", got "${actual}"`)
|
|
251
|
+
return this.fail(name, `expected "${expected}", got "${actual}"`)
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
/**
|
|
@@ -264,9 +264,9 @@ export class Suite {
|
|
|
264
264
|
*/
|
|
265
265
|
contains(name: string, haystack: string, needle: string): boolean {
|
|
266
266
|
if (toI32(haystack.indexOf(needle)) >= 0) {
|
|
267
|
-
return this.pass(name)
|
|
267
|
+
return this.pass(name)
|
|
268
268
|
}
|
|
269
|
-
return this.fail(name, `expected to contain "${needle}", got "${testingExcerpt(haystack)}"`)
|
|
269
|
+
return this.fail(name, `expected to contain "${needle}", got "${testingExcerpt(haystack)}"`)
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/**
|
|
@@ -281,30 +281,30 @@ export class Suite {
|
|
|
281
281
|
* the one a reader fixes first.
|
|
282
282
|
*/
|
|
283
283
|
containsAll(name: string, haystack: string, needles: string[]): boolean {
|
|
284
|
-
let looked: i32 = 0
|
|
285
|
-
let missing: i32 = 0
|
|
286
|
-
let first = ""
|
|
284
|
+
let looked: i32 = 0
|
|
285
|
+
let missing: i32 = 0
|
|
286
|
+
let first = ""
|
|
287
287
|
for (const needle of needles) {
|
|
288
288
|
if (toI32(needle.length) === 0) {
|
|
289
|
-
continue
|
|
289
|
+
continue
|
|
290
290
|
}
|
|
291
|
-
looked += 1
|
|
291
|
+
looked += 1
|
|
292
292
|
if (toI32(haystack.indexOf(needle)) < 0) {
|
|
293
|
-
missing += 1
|
|
293
|
+
missing += 1
|
|
294
294
|
if (toI32(first.length) === 0) {
|
|
295
|
-
first = needle
|
|
295
|
+
first = needle
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
if (missing === 0) {
|
|
300
|
-
return this.pass(name)
|
|
300
|
+
return this.pass(name)
|
|
301
301
|
}
|
|
302
302
|
if (missing === 1) {
|
|
303
|
-
return this.fail(name, `missing "${first}"`)
|
|
303
|
+
return this.fail(name, `missing "${first}"`)
|
|
304
304
|
}
|
|
305
305
|
// `looked` and not `needles.length`, because the blank lines this ignored are
|
|
306
306
|
// not fragments anybody expected to find.
|
|
307
|
-
return this.fail(name, `missing ${missing} of ${looked}, first "${first}"`)
|
|
307
|
+
return this.fail(name, `missing ${missing} of ${looked}, first "${first}"`)
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
/**
|
|
@@ -319,13 +319,13 @@ export class Suite {
|
|
|
319
319
|
* one an editor takes.
|
|
320
320
|
*/
|
|
321
321
|
eqLines(name: string, actual: string[], expected: string[]): boolean {
|
|
322
|
-
const at: i32 = testingFirstDifferentLine(actual, expected)
|
|
322
|
+
const at: i32 = testingFirstDifferentLine(actual, expected)
|
|
323
323
|
if (at < 0) {
|
|
324
|
-
return this.pass(name)
|
|
324
|
+
return this.pass(name)
|
|
325
325
|
}
|
|
326
|
-
const wanted = at < toI32(expected.length) ? `"${expected[at]}"` : "<end of input>"
|
|
327
|
-
const saw = at < toI32(actual.length) ? `"${actual[at]}"` : "<end of input>"
|
|
328
|
-
return this.fail(name, `line ${at + 1}: expected ${wanted}, got ${saw}`)
|
|
326
|
+
const wanted = at < toI32(expected.length) ? `"${expected[at]}"` : "<end of input>"
|
|
327
|
+
const saw = at < toI32(actual.length) ? `"${actual[at]}"` : "<end of input>"
|
|
328
|
+
return this.fail(name, `line ${at + 1}: expected ${wanted}, got ${saw}`)
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
/**
|
|
@@ -336,12 +336,12 @@ export class Suite {
|
|
|
336
336
|
*/
|
|
337
337
|
done(): i32 {
|
|
338
338
|
if (toI32(this.failures.length) > 0) {
|
|
339
|
-
console.log(`failed: ${this.failures.join(", ")}`)
|
|
339
|
+
console.log(`failed: ${this.failures.join(", ")}`)
|
|
340
340
|
}
|
|
341
|
-
console.log(`${this.name}: ${this.passed} passed, ${this.failed} failed, ${this.skipped} skipped`)
|
|
341
|
+
console.log(`${this.name}: ${this.passed} passed, ${this.failed} failed, ${this.skipped} skipped`)
|
|
342
342
|
if (this.failed > 0) {
|
|
343
|
-
return 1
|
|
343
|
+
return 1
|
|
344
344
|
}
|
|
345
|
-
return 0
|
|
345
|
+
return 0
|
|
346
346
|
}
|
|
347
347
|
}
|
package/std/text.ts
CHANGED
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
* immutable, so its length cannot change underneath the local.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
const NEWLINE: i32 = 10
|
|
31
|
-
const CARRIAGE_RETURN: i32 = 13
|
|
32
|
-
const SPACE: i32 = 32
|
|
33
|
-
const TAB: i32 = 9
|
|
30
|
+
const NEWLINE: i32 = 10
|
|
31
|
+
const CARRIAGE_RETURN: i32 = 13
|
|
32
|
+
const SPACE: i32 = 32
|
|
33
|
+
const TAB: i32 = 9
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Whether `code` is one of the four ASCII bytes this module treats as blank.
|
|
@@ -42,7 +42,7 @@ const TAB: i32 = 9;
|
|
|
42
42
|
* makes the collision unlikely instead.
|
|
43
43
|
*/
|
|
44
44
|
const isTextBlankByte = (code: i32): boolean =>
|
|
45
|
-
code === SPACE || code === TAB || code === NEWLINE || code === CARRIAGE_RETURN
|
|
45
|
+
code === SPACE || code === TAB || code === NEWLINE || code === CARRIAGE_RETURN
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* The lines of `text`, without their terminators.
|
|
@@ -55,10 +55,10 @@ const isTextBlankByte = (code: i32): boolean =>
|
|
|
55
55
|
* `trimEnd` each line.
|
|
56
56
|
*/
|
|
57
57
|
export const splitLines = (text: string): string[] => {
|
|
58
|
-
const length: i32 = toI32(text.length)
|
|
59
|
-
const lines: string[] = []
|
|
60
|
-
let start: i32 = 0
|
|
61
|
-
let i: i32 = 0
|
|
58
|
+
const length: i32 = toI32(text.length)
|
|
59
|
+
const lines: string[] = []
|
|
60
|
+
let start: i32 = 0
|
|
61
|
+
let i: i32 = 0
|
|
62
62
|
while (i < length) {
|
|
63
63
|
if (toI32(text.charCodeAt(i)) === NEWLINE) {
|
|
64
64
|
// `start` is never negative and never passes `i`, which is below
|
|
@@ -66,17 +66,17 @@ export const splitLines = (text: string): string[] => {
|
|
|
66
66
|
// proves `start` within `text`: the clamp on that bound is dead once it
|
|
67
67
|
// is proven, and the emitter drops it.
|
|
68
68
|
if (start >= 0 && start < length) {
|
|
69
|
-
lines.push(text.substring(start, i))
|
|
69
|
+
lines.push(text.substring(start, i))
|
|
70
70
|
}
|
|
71
|
-
start = i + 1
|
|
71
|
+
start = i + 1
|
|
72
72
|
}
|
|
73
|
-
i += 1
|
|
73
|
+
i += 1
|
|
74
74
|
}
|
|
75
75
|
if (start < length) {
|
|
76
|
-
lines.push(text.substring(start, length))
|
|
76
|
+
lines.push(text.substring(start, length))
|
|
77
77
|
}
|
|
78
|
-
return lines
|
|
79
|
-
}
|
|
78
|
+
return lines
|
|
79
|
+
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* The runs of non-blank bytes in `text`, with every blank run as the separator.
|
|
@@ -85,57 +85,57 @@ export const splitLines = (text: string): string[] => {
|
|
|
85
85
|
* `split(text, " ")`, which would.
|
|
86
86
|
*/
|
|
87
87
|
export const splitWhitespace = (text: string): string[] => {
|
|
88
|
-
const length: i32 = toI32(text.length)
|
|
89
|
-
const parts: string[] = []
|
|
90
|
-
let start: i32 = -1
|
|
91
|
-
let i: i32 = 0
|
|
88
|
+
const length: i32 = toI32(text.length)
|
|
89
|
+
const parts: string[] = []
|
|
90
|
+
let start: i32 = -1
|
|
91
|
+
let i: i32 = 0
|
|
92
92
|
while (i < length) {
|
|
93
93
|
if (isTextBlankByte(toI32(text.charCodeAt(i)))) {
|
|
94
94
|
// `start` is `-1` or a byte already passed, so the second half always
|
|
95
95
|
// holds when the first does; it proves the bound, as in `splitLines`.
|
|
96
96
|
if (start >= 0 && start < length) {
|
|
97
|
-
parts.push(text.substring(start, i))
|
|
98
|
-
start = -1
|
|
97
|
+
parts.push(text.substring(start, i))
|
|
98
|
+
start = -1
|
|
99
99
|
}
|
|
100
100
|
} else if (start < 0) {
|
|
101
|
-
start = i
|
|
101
|
+
start = i
|
|
102
102
|
}
|
|
103
|
-
i += 1
|
|
103
|
+
i += 1
|
|
104
104
|
}
|
|
105
105
|
if (start >= 0) {
|
|
106
|
-
parts.push(text.substring(start, length))
|
|
106
|
+
parts.push(text.substring(start, length))
|
|
107
107
|
}
|
|
108
|
-
return parts
|
|
109
|
-
}
|
|
108
|
+
return parts
|
|
109
|
+
}
|
|
110
110
|
|
|
111
111
|
/** `text` without its leading blank bytes. */
|
|
112
112
|
export const trimStart = (text: string): string => {
|
|
113
|
-
const length: i32 = toI32(text.length)
|
|
114
|
-
let i: i32 = 0
|
|
113
|
+
const length: i32 = toI32(text.length)
|
|
114
|
+
let i: i32 = 0
|
|
115
115
|
while (i < length && isTextBlankByte(toI32(text.charCodeAt(i)))) {
|
|
116
|
-
i += 1
|
|
116
|
+
i += 1
|
|
117
117
|
}
|
|
118
|
-
return text.substring(i, length)
|
|
119
|
-
}
|
|
118
|
+
return text.substring(i, length)
|
|
119
|
+
}
|
|
120
120
|
|
|
121
121
|
/** `text` without its trailing blank bytes. */
|
|
122
122
|
export const trimEnd = (text: string): string => {
|
|
123
|
-
let end: i32 = toI32(text.length)
|
|
123
|
+
let end: i32 = toI32(text.length)
|
|
124
124
|
while (end > 0 && isTextBlankByte(toI32(text.charCodeAt(end - 1)))) {
|
|
125
|
-
end -= 1
|
|
125
|
+
end -= 1
|
|
126
126
|
}
|
|
127
|
-
return text.substring(0, end)
|
|
128
|
-
}
|
|
127
|
+
return text.substring(0, end)
|
|
128
|
+
}
|
|
129
129
|
|
|
130
130
|
/** `text` without blank bytes at either end. */
|
|
131
|
-
export const trim = (text: string): string => trimEnd(trimStart(text))
|
|
131
|
+
export const trim = (text: string): string => trimEnd(trimStart(text))
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Whether `needle` occurs anywhere in `haystack`. One `indexOf`, named, because
|
|
135
135
|
* `indexOf(...) >= 0` at a call site reads as arithmetic where the question is a
|
|
136
136
|
* yes or a no. `contains(s, "")` is `true`, as `indexOf("")` is `0`.
|
|
137
137
|
*/
|
|
138
|
-
export const contains = (haystack: string, needle: string): boolean => toI32(haystack.indexOf(needle)) >= 0
|
|
138
|
+
export const contains = (haystack: string, needle: string): boolean => toI32(haystack.indexOf(needle)) >= 0
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* `text` with every occurrence of `needle` replaced by `replacement`.
|
|
@@ -149,25 +149,25 @@ export const contains = (haystack: string, needle: string): boolean => toI32(hay
|
|
|
149
149
|
* choice about how many empty matches a string contains.
|
|
150
150
|
*/
|
|
151
151
|
export const replaceAll = (text: string, needle: string, replacement: string): string => {
|
|
152
|
-
const needleLength: i32 = toI32(needle.length)
|
|
152
|
+
const needleLength: i32 = toI32(needle.length)
|
|
153
153
|
if (needleLength === 0) {
|
|
154
|
-
return text
|
|
154
|
+
return text
|
|
155
155
|
}
|
|
156
|
-
const parts: string[] = []
|
|
157
|
-
let rest = text
|
|
156
|
+
const parts: string[] = []
|
|
157
|
+
let rest = text
|
|
158
158
|
while (true) {
|
|
159
|
-
const at: i32 = toI32(rest.indexOf(needle))
|
|
159
|
+
const at: i32 = toI32(rest.indexOf(needle))
|
|
160
160
|
// `indexOf` never answers past `rest.length`, so the second half never
|
|
161
161
|
// holds; it is written because its negation is what proves `at` within
|
|
162
162
|
// `rest` for the `substring` below, as in `splitLines`.
|
|
163
163
|
if (at < 0 || at > toI32(rest.length)) {
|
|
164
|
-
parts.push(rest)
|
|
165
|
-
return parts.join(replacement)
|
|
164
|
+
parts.push(rest)
|
|
165
|
+
return parts.join(replacement)
|
|
166
166
|
}
|
|
167
|
-
parts.push(rest.substring(0, at))
|
|
168
|
-
rest = rest.substring(at + needleLength, toI32(rest.length))
|
|
167
|
+
parts.push(rest.substring(0, at))
|
|
168
|
+
rest = rest.substring(at + needleLength, toI32(rest.length))
|
|
169
169
|
}
|
|
170
|
-
}
|
|
170
|
+
}
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
173
|
* The index of the first line at which `left` and `right` differ, or `-1` when
|
|
@@ -177,17 +177,17 @@ export const replaceAll = (text: string, needle: string, replacement: string): s
|
|
|
177
177
|
* not a diagnostic.
|
|
178
178
|
*/
|
|
179
179
|
export const firstDifference = (left: string[], right: string[]): i32 => {
|
|
180
|
-
const leftLength: i32 = toI32(left.length)
|
|
181
|
-
const rightLength: i32 = toI32(right.length)
|
|
182
|
-
let i: i32 = 0
|
|
180
|
+
const leftLength: i32 = toI32(left.length)
|
|
181
|
+
const rightLength: i32 = toI32(right.length)
|
|
182
|
+
let i: i32 = 0
|
|
183
183
|
// Two tests rather than one against the shorter length: each proves `i`
|
|
184
184
|
// within one of the arrays, and a minimum taken with a ternary proves
|
|
185
185
|
// neither. The loop ends with `i` at the shorter length.
|
|
186
186
|
while (i < leftLength && i < rightLength) {
|
|
187
187
|
if (left[i] !== right[i]) {
|
|
188
|
-
return i
|
|
188
|
+
return i
|
|
189
189
|
}
|
|
190
|
-
i += 1
|
|
190
|
+
i += 1
|
|
191
191
|
}
|
|
192
|
-
return leftLength === rightLength ? -1 : i
|
|
193
|
-
}
|
|
192
|
+
return leftLength === rightLength ? -1 : i
|
|
193
|
+
}
|