@grain/stdlib 0.4.0 → 0.4.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.
package/string.gr CHANGED
@@ -1,24 +1,65 @@
1
+ /**
2
+ * @module String: Utilities for working with strings.
3
+ * @example import String from "string"
4
+ *
5
+ * @since v0.2.0
6
+ * @history v0.1.0: Originally named `strings`
7
+ * @history v0.2.0: Renamed to `string`
8
+ */
1
9
  import WasmI32 from "runtime/unsafe/wasmi32"
2
10
  import Memory from "runtime/unsafe/memory"
3
11
  import Exception from "runtime/exception"
4
12
  import { tagSimpleNumber, allocateArray, allocateChar, allocateString, allocateBytes } from "runtime/dataStructures"
5
13
 
6
- // Concatenate two strings.
7
- // @param str1: String - The first string
8
- // @param str2: String - The second string
9
- // @returns String
14
+ /**
15
+ * @section Types: Type declarations included in the String module.
16
+ */
17
+
18
+ /**
19
+ * Byte encodings
20
+ */
21
+ export enum Encoding {
22
+ UTF8,
23
+ UTF16_BE,
24
+ UTF16_LE,
25
+ UTF32_BE,
26
+ UTF32_LE,
27
+ }
28
+
29
+ /**
30
+ * @section Values: Functions for working with the String data type.
31
+ */
32
+
33
+ /**
34
+ * Concatenate two strings.
35
+ *
36
+ * @param str1: The beginning string
37
+ * @param str2: The ending string
38
+ * @returns The combined string
39
+ *
40
+ * @example String.concat("Foo", " Bar") == "FooBar"
41
+ *
42
+ * @since v0.1.0
43
+ */
10
44
  export let concat = (++)
11
45
 
12
- // Get the character length of a string.
13
- // @param input: String - The string to check
14
- // @returns Number
46
+ /**
47
+ * Returns the character length of the input string.
48
+ *
49
+ * @param string: The string to inspect
50
+ * @returns The number of characters in the string
51
+ *
52
+ * @example String.length("Hello world") == 11
53
+ *
54
+ * @since v0.1.0
55
+ */
15
56
  @disableGC
16
- export let rec length = (s: String) => {
17
- let s = WasmI32.fromGrain(s)
18
- let size = WasmI32.load(s, 4n)
57
+ export let rec length = (string: String) => {
58
+ let string = WasmI32.fromGrain(string)
59
+ let size = WasmI32.load(string, 4n)
19
60
 
20
61
  let mut len = 0n
21
- let mut ptr = WasmI32.add(s, 8n)
62
+ let mut ptr = WasmI32.add(string, 8n)
22
63
  let end = WasmI32.add(ptr, size)
23
64
 
24
65
  while (WasmI32.ltU(ptr, end)) {
@@ -31,7 +72,7 @@ export let rec length = (s: String) => {
31
72
 
32
73
  let ret = tagSimpleNumber(len)
33
74
  Memory.decRef(WasmI32.fromGrain(length))
34
- Memory.decRef(WasmI32.fromGrain(s))
75
+ Memory.decRef(WasmI32.fromGrain(string))
35
76
  ret
36
77
  }
37
78
 
@@ -43,38 +84,51 @@ let wasmSafeLength = (s: String) => {
43
84
  length(s)
44
85
  }
45
86
 
46
- // Get the byte length of a string.
47
- // @param input: String - The string to check
48
- // @returns Number
87
+ /**
88
+ * Returns the byte length of the input string.
89
+ *
90
+ * @param string: The string to inspect
91
+ * @returns The number of bytes in the string
92
+ *
93
+ * @example String.byteLength("🌾") == 4
94
+ *
95
+ * @since v0.1.0
96
+ */
49
97
  @disableGC
50
- export let rec byteLength = (s: String) => {
51
- let s = WasmI32.fromGrain(s)
52
- let ret = tagSimpleNumber(WasmI32.load(s, 4n))
98
+ export let rec byteLength = (string: String) => {
99
+ let string = WasmI32.fromGrain(string)
100
+ let ret = tagSimpleNumber(WasmI32.load(string, 4n))
53
101
  Memory.decRef(WasmI32.fromGrain(byteLength))
54
- Memory.decRef(WasmI32.fromGrain(s))
102
+ Memory.decRef(WasmI32.fromGrain(string))
55
103
  ret
56
104
  }
57
105
 
58
-
59
106
  // @disableGC-safe wrapper
60
107
  @disableGC
61
- let wasmSafeByteLength = (s: String) => {
108
+ let wasmSafeByteLength = (string: String) => {
62
109
  Memory.incRef(WasmI32.fromGrain(byteLength))
63
- Memory.incRef(WasmI32.fromGrain(s))
64
- byteLength(s)
110
+ Memory.incRef(WasmI32.fromGrain(string))
111
+ byteLength(string)
65
112
  }
66
113
 
67
- // Find the start index of a substring.
68
- // @param sub: String - The substring to find
69
- // @param input: String - The string to check
70
- // @returns Option<Number>
114
+ /**
115
+ * Finds the position of a substring in the input string.
116
+ *
117
+ * @param search: The substring to find
118
+ * @param string: The string to inspect
119
+ * @returns `Some(position)` containing the starting position of the substring if found or `None` otherwise
120
+ *
121
+ * @example String.indexOf("world", "Hello world") == Some(6)
122
+ *
123
+ * @since v0.3.0
124
+ */
71
125
  @disableGC
72
- export let rec indexOf = (p: String, s: String) => {
73
- let p = WasmI32.fromGrain(p)
74
- let s = WasmI32.fromGrain(s)
126
+ export let rec indexOf = (search: String, string: String) => {
127
+ let search = WasmI32.fromGrain(search)
128
+ let string = WasmI32.fromGrain(string)
75
129
 
76
- let size = WasmI32.load(s, 4n)
77
- let psize = WasmI32.load(p, 4n)
130
+ let size = WasmI32.load(string, 4n)
131
+ let psize = WasmI32.load(search, 4n)
78
132
 
79
133
  let (>) = WasmI32.gtU
80
134
  let (<) = WasmI32.ltU
@@ -89,8 +143,8 @@ export let rec indexOf = (p: String, s: String) => {
89
143
  none
90
144
  } else {
91
145
  let mut idx = 0n
92
- let mut ptr = s + 8n
93
- let mut pptr = p + 8n
146
+ let mut ptr = string + 8n
147
+ let mut pptr = search + 8n
94
148
  let end = ptr + size - psize + 1n
95
149
 
96
150
  let mut result = -1n
@@ -122,19 +176,30 @@ export let rec indexOf = (p: String, s: String) => {
122
176
  Some(tagSimpleNumber(result))
123
177
  }
124
178
  }
125
- Memory.decRef(WasmI32.fromGrain(p))
126
- Memory.decRef(WasmI32.fromGrain(s))
179
+ Memory.decRef(WasmI32.fromGrain(search))
180
+ Memory.decRef(WasmI32.fromGrain(string))
127
181
  Memory.decRef(WasmI32.fromGrain(indexOf))
128
182
  ret
129
183
  }
130
184
 
185
+ /**
186
+ * Get the character at the position in the input string.
187
+ *
188
+ * @param position: The position to check
189
+ * @param string: The string to search
190
+ * @returns The character at the provided position
191
+ *
192
+ * @example String.charAt(5, "Hello world") == ' '
193
+ *
194
+ * @since v0.4.0
195
+ */
131
196
  @disableGC
132
- export let rec charAt = (idx, s: String) => {
197
+ export let rec charAt = (position, string: String) => {
133
198
  Memory.incRef(WasmI32.fromGrain((<=)))
134
- if (wasmSafeLength(s) <= idx || {Memory.incRef(WasmI32.fromGrain((<))); idx < 0}) {
199
+ if (wasmSafeLength(string) <= position || {Memory.incRef(WasmI32.fromGrain((<))); position < 0}) {
135
200
  Memory.incRef(WasmI32.fromGrain((++)))
136
201
  Memory.incRef(WasmI32.fromGrain(toString))
137
- fail ("Invalid offset: " ++ toString(idx))
202
+ fail ("Invalid offset: " ++ toString(position))
138
203
  }
139
204
  // Implementation is similar to explodeHelp, but doesn't perform unneeded memory allocations
140
205
  let (>>>) = WasmI32.shrU
@@ -142,11 +207,11 @@ export let rec charAt = (idx, s: String) => {
142
207
  let (&) = WasmI32.and
143
208
  let (<) = WasmI32.ltU
144
209
  let (==) = WasmI32.eq
145
- let size = WasmI32.fromGrain(wasmSafeByteLength(s)) >>> 1n
146
- let len = WasmI32.fromGrain(wasmSafeLength(s)) >>> 1n
147
- let idx = WasmI32.fromGrain(idx) >>> 1n
148
- let s = WasmI32.fromGrain(s)
149
- let mut ptr = s + 8n;
210
+ let size = WasmI32.fromGrain(wasmSafeByteLength(string)) >>> 1n
211
+ let len = WasmI32.fromGrain(wasmSafeLength(string)) >>> 1n
212
+ let position = WasmI32.fromGrain(position) >>> 1n
213
+ let string = WasmI32.fromGrain(string)
214
+ let mut ptr = string + 8n;
150
215
  let end = ptr + size;
151
216
  let mut counter = 0n;
152
217
  let mut result = 0n
@@ -161,7 +226,7 @@ export let rec charAt = (idx, s: String) => {
161
226
  } else {
162
227
  2n
163
228
  }
164
- if (counter == idx) {
229
+ if (counter == position) {
165
230
  let c = allocateChar()
166
231
  Memory.copy(c + 4n, ptr, n)
167
232
  result = c
@@ -174,7 +239,7 @@ export let rec charAt = (idx, s: String) => {
174
239
  fail "charAt: should be impossible (please report)"
175
240
  }
176
241
  let ret = WasmI32.toGrain(result): Char
177
- Memory.decRef(WasmI32.fromGrain(s))
242
+ Memory.decRef(WasmI32.fromGrain(string))
178
243
  Memory.decRef(WasmI32.fromGrain(charAt))
179
244
  ret
180
245
  }
@@ -228,17 +293,37 @@ let explodeHelp = (s: String, chars) => {
228
293
  arr
229
294
  }
230
295
 
231
- // Split a string into its UTF-8 characters
232
- // @param input: String - The string to split
233
- // @returns Array<Char>
296
+ /**
297
+ * Split a string into its Unicode characters.
298
+ *
299
+ * @param string: The string to split
300
+ * @returns An array containing all characters in the string
301
+ *
302
+ * @example String.explode("Hello") == [> 'H', 'e', 'l', 'l', 'o']
303
+ *
304
+ * @since v0.3.0
305
+ */
234
306
  @disableGC
235
- export let explode = (str) => {
236
- WasmI32.toGrain(explodeHelp(str, true)): Array<Char>
307
+ export let rec explode = (string) => {
308
+ // `explodeHelp` and `string` do not need to be incRef'd as they are not
309
+ // decRef'd in `explodeHelp`
310
+ let ret = WasmI32.toGrain(explodeHelp(string, true)) : (Array<Char>)
311
+
312
+ Memory.decRef(WasmI32.fromGrain(string))
313
+ Memory.decRef(WasmI32.fromGrain(explode))
314
+ ret
237
315
  }
238
316
 
239
- // Create a string from an array of characters
240
- // @param input: Array<Char> - The array to implode
241
- // @returns String
317
+ /**
318
+ * Create a string from an array of characters.
319
+ *
320
+ * @param arr: The array to combine
321
+ * @returns A string representation of the array of characters
322
+ *
323
+ * @example String.implode([> 'H', 'e', 'l', 'l', 'o']) == "Hello"
324
+ *
325
+ * @since v0.3.0
326
+ */
242
327
  @disableGC
243
328
  export let rec implode = (arr: Array<Char>) => {
244
329
  let (+) = WasmI32.add
@@ -297,12 +382,44 @@ export let rec implode = (arr: Array<Char>) => {
297
382
  ret
298
383
  }
299
384
 
300
- // Split a string by the given sequence
301
- // @param sequence: String - The sequence to split on
302
- // @param input: String - The string to split
303
- // @returns Array<String>
385
+ // Helper to get the length in constant time without depending on Array
386
+ primitive arrayLength : Array<a> -> Number = "@array.length"
387
+
388
+ /**
389
+ * Create a string that is the given string reversed.
390
+ *
391
+ * @param string: The string to reverse
392
+ * @returns A string whose characters are in the reverse order of the given string
393
+ *
394
+ * @example String.reverse("olleH") == "Hello"
395
+ *
396
+ * @since v0.4.5
397
+ */
398
+ export let reverse = (string) => {
399
+ let mut arr = explode(string)
400
+ let len = arrayLength(arr)
401
+ let halfLen = len / 2
402
+ for (let mut i = 0; i < halfLen; i += 1) {
403
+ let lastIdx = len - i - 1
404
+ let last = arr[lastIdx]
405
+ let first = arr[i]
406
+ arr[i] = last
407
+ arr[lastIdx] = first
408
+ }
409
+ implode(arr)
410
+ }
411
+
412
+ /**
413
+ * Split a string by the given separator.
414
+ *
415
+ * @param separator: The separator to split on
416
+ * @param string: The string to split
417
+ * @returns An array of substrings from the initial string
418
+ *
419
+ * @example String.split(" ", "Hello world") == [> "Hello", "world"]
420
+ */
304
421
  @disableGC
305
- export let rec split = (p: String, s: String) => {
422
+ export let rec split = (separator: String, string: String) => {
306
423
  let (+) = WasmI32.add
307
424
  let (-) = WasmI32.sub
308
425
  let (==) = WasmI32.eq
@@ -312,22 +429,22 @@ export let rec split = (p: String, s: String) => {
312
429
  let (>>) = WasmI32.shrS
313
430
  let (&) = WasmI32.and
314
431
 
315
- let size = WasmI32.fromGrain(wasmSafeByteLength(s)) >> 1n
316
- let psize = WasmI32.fromGrain(wasmSafeByteLength(p)) >> 1n
432
+ let size = WasmI32.fromGrain(wasmSafeByteLength(string)) >> 1n
433
+ let psize = WasmI32.fromGrain(wasmSafeByteLength(separator)) >> 1n
317
434
 
318
435
  let ret = if (psize == 0n) {
319
- WasmI32.toGrain(explodeHelp(s, false)): Array<String>
436
+ WasmI32.toGrain(explodeHelp(string, false)): Array<String>
320
437
  } else if (psize > size) {
321
- let s = WasmI32.fromGrain(s)
438
+ let string = WasmI32.fromGrain(string)
322
439
  let ptr = allocateArray(1n)
323
- WasmI32.store(ptr, Memory.incRef(s), 8n)
440
+ WasmI32.store(ptr, Memory.incRef(string), 8n)
324
441
  WasmI32.toGrain(ptr): Array<String>
325
442
  } else {
326
- let s = WasmI32.fromGrain(s)
327
- let p = WasmI32.fromGrain(p)
443
+ let string = WasmI32.fromGrain(string)
444
+ let separator = WasmI32.fromGrain(separator)
328
445
 
329
- let mut ptr = s + 8n
330
- let mut pptr = p + 8n
446
+ let mut ptr = string + 8n
447
+ let mut pptr = separator + 8n
331
448
  let end = ptr + size - psize + 1n
332
449
 
333
450
  let mut numStrings = 1n
@@ -348,7 +465,7 @@ export let rec split = (p: String, s: String) => {
348
465
  }
349
466
  }
350
467
 
351
- ptr = s + 8n
468
+ ptr = string + 8n
352
469
  let mut last = ptr
353
470
  let arr = allocateArray(numStrings)
354
471
  let mut arrIdx = 0n
@@ -377,26 +494,33 @@ export let rec split = (p: String, s: String) => {
377
494
  }
378
495
 
379
496
  // Grab last string
380
- let strSize = s + 8n + size - last
497
+ let strSize = string + 8n + size - last
381
498
  let lastStr = allocateString(strSize)
382
499
  Memory.copy(lastStr + 8n, last, strSize)
383
500
  WasmI32.store(arr + arrIdx, lastStr, 8n)
384
501
 
385
502
  WasmI32.toGrain(arr): Array<String>
386
503
  }
387
- Memory.decRef(WasmI32.fromGrain(p))
388
- Memory.decRef(WasmI32.fromGrain(s))
504
+ Memory.decRef(WasmI32.fromGrain(separator))
505
+ Memory.decRef(WasmI32.fromGrain(string))
389
506
  Memory.decRef(WasmI32.fromGrain(split))
390
507
  ret
391
508
  }
392
509
 
393
- // Get a portion of a string.
394
- // @param from: Number - The start index of the substring
395
- // @param to: Number - The end index of the substring
396
- // @param input: String - The input string
397
- // @returns String
510
+ /**
511
+ * Get a portion of a string.
512
+ *
513
+ * @param start: The start position of the substring
514
+ * @param to: The end position of the substring, exclusive
515
+ * @param string: The input string
516
+ * @returns The substring from the initial string
517
+ *
518
+ * @example String.slice(0, 5, "Hello world") == "Hello"
519
+ *
520
+ * @since v0.1.0
521
+ */
398
522
  @disableGC
399
- export let rec slice = (start: Number, to: Number, s: String) => {
523
+ export let rec slice = (start: Number, to: Number, string: String) => {
400
524
  let (+) = WasmI32.add
401
525
  let (-) = WasmI32.sub
402
526
  let (==) = WasmI32.eq
@@ -409,10 +533,10 @@ export let rec slice = (start: Number, to: Number, s: String) => {
409
533
  let startOrig = start
410
534
  let toOrig = to
411
535
 
412
- let len = WasmI32.fromGrain(wasmSafeLength(s)) >> 1n
413
- let size = WasmI32.fromGrain(wasmSafeByteLength(s)) >> 1n
536
+ let len = WasmI32.fromGrain(wasmSafeLength(string)) >> 1n
537
+ let size = WasmI32.fromGrain(wasmSafeByteLength(string)) >> 1n
414
538
 
415
- let s = WasmI32.fromGrain(s)
539
+ let string = WasmI32.fromGrain(string)
416
540
 
417
541
  let mut start = WasmI32.fromGrain(start)
418
542
  if ((start & 1n) != 1n) {
@@ -441,7 +565,7 @@ export let rec slice = (start: Number, to: Number, s: String) => {
441
565
  throw InvalidArgument("Start index exceeds end index")
442
566
  }
443
567
 
444
- let mut ptr = s + 8n
568
+ let mut ptr = string + 8n
445
569
  let mut begin = ptr
446
570
  let mut end = ptr
447
571
  let stop = ptr + size
@@ -462,7 +586,7 @@ export let rec slice = (start: Number, to: Number, s: String) => {
462
586
  ptr += 1n
463
587
  }
464
588
  if (to == len) {
465
- end = s + 8n + size
589
+ end = string + 8n + size
466
590
  }
467
591
  if (start == to) {
468
592
  begin = end
@@ -476,17 +600,24 @@ export let rec slice = (start: Number, to: Number, s: String) => {
476
600
  let ret = WasmI32.toGrain(newString): String
477
601
  Memory.decRef(WasmI32.fromGrain(startOrig))
478
602
  Memory.decRef(WasmI32.fromGrain(toOrig))
479
- Memory.decRef(WasmI32.fromGrain(s))
603
+ Memory.decRef(WasmI32.fromGrain(string))
480
604
  Memory.decRef(WasmI32.fromGrain(slice))
481
605
  ret
482
606
  }
483
607
 
484
- // Check if a string contains a substring.
485
- // @param pattern: String - The substring to check
486
- // @param input: String - The input string
487
- // @returns Bool
608
+ /**
609
+ * Check if a string contains a substring.
610
+ *
611
+ * @param search: The substring to check
612
+ * @param string: The string to search
613
+ * @returns `true` if the input string contains the search value or `false` otherwise
614
+ *
615
+ * @example String.contains("world", "Hello world") == true
616
+ *
617
+ * @since v0.1.0
618
+ */
488
619
  @disableGC
489
- export let rec contains = (p: String, s: String) => {
620
+ export let rec contains = (search: String, string: String) => {
490
621
  // "Not So Naive" string search algorithm
491
622
  // searching phase in O(nm) time complexity
492
623
  // slightly (by coefficient) sub-linear in the average case
@@ -500,17 +631,17 @@ export let rec contains = (p: String, s: String) => {
500
631
  let (<=) = WasmI32.leU
501
632
  let (>) = WasmI32.gtU
502
633
  let (>>) = WasmI32.shrS
503
- let pOrig = p
504
- let sOrig = s
634
+ let pOrig = search
635
+ let sOrig = string
505
636
 
506
- let n = WasmI32.fromGrain(wasmSafeByteLength(s)) >> 1n
507
- let m = WasmI32.fromGrain(wasmSafeByteLength(p)) >> 1n
637
+ let n = WasmI32.fromGrain(wasmSafeByteLength(string)) >> 1n
638
+ let m = WasmI32.fromGrain(wasmSafeByteLength(search)) >> 1n
508
639
 
509
- let mut s = WasmI32.fromGrain(s)
510
- let mut p = WasmI32.fromGrain(p)
640
+ let mut string = WasmI32.fromGrain(string)
641
+ let mut search = WasmI32.fromGrain(search)
511
642
 
512
- s += 8n
513
- p += 8n
643
+ string += 8n
644
+ search += 8n
514
645
 
515
646
  let mut j = 0n, k = 0n, ell = 0n
516
647
 
@@ -522,10 +653,10 @@ export let rec contains = (p: String, s: String) => {
522
653
  if (m == 0n) {
523
654
  true
524
655
  } else {
525
- let pat = WasmI32.load8U(p, 0n)
656
+ let pat = WasmI32.load8U(search, 0n)
526
657
  let mut result = false
527
658
  while (j < n) {
528
- if (pat == WasmI32.load8U(s + j, 0n)) {
659
+ if (pat == WasmI32.load8U(string + j, 0n)) {
529
660
  result = true
530
661
  break
531
662
  } else {
@@ -536,7 +667,7 @@ export let rec contains = (p: String, s: String) => {
536
667
  }
537
668
  } else {
538
669
  // NSM preprocessing
539
- if (WasmI32.load8U(p, 0n) == WasmI32.load8U(p, 1n)) {
670
+ if (WasmI32.load8U(search, 0n) == WasmI32.load8U(search, 1n)) {
540
671
  k = 2n
541
672
  ell = 1n
542
673
  } else {
@@ -547,10 +678,10 @@ export let rec contains = (p: String, s: String) => {
547
678
  let mut result = false
548
679
  // NSM searching
549
680
  while (j <= n - m) {
550
- if (WasmI32.load8U(p, 1n) != WasmI32.load8U(s + j, 1n)) {
681
+ if (WasmI32.load8U(search, 1n) != WasmI32.load8U(string + j, 1n)) {
551
682
  j += k
552
683
  } else {
553
- if (Memory.compare(p + 2n, s + j + 2n, m - 2n) == 0n && WasmI32.load8U(p, 0n) == WasmI32.load8U(s + j, 0n)) {
684
+ if (Memory.compare(search + 2n, string + j + 2n, m - 2n) == 0n && WasmI32.load8U(search, 0n) == WasmI32.load8U(string + j, 0n)) {
554
685
  result = true
555
686
  break
556
687
  }
@@ -565,32 +696,39 @@ export let rec contains = (p: String, s: String) => {
565
696
  ret
566
697
  }
567
698
 
568
- // Check if a string begins with another string.
569
- // @param pattern: String - The substring to check
570
- // @param input: String - The input string
571
- // @returns Bool
699
+ /**
700
+ * Check if a string begins with another string.
701
+ *
702
+ * @param search: The string to compare to the start
703
+ * @param string: The string to search
704
+ * @returns `true` if the input string starts with the search value or `false` otherwise
705
+ *
706
+ * @example String.startsWith("Hello", "Hello world") == true
707
+ *
708
+ * @since v0.1.0
709
+ */
572
710
  @disableGC
573
- export let rec startsWith = (p: String, s: String) => {
711
+ export let rec startsWith = (search: String, string: String) => {
574
712
  let (+) = WasmI32.add
575
713
  let (>) = WasmI32.gtU
576
714
  let (==) = WasmI32.eq
577
- let pOrig = p
578
- let sOrig = s
715
+ let pOrig = search
716
+ let sOrig = string
579
717
 
580
- let mut p = WasmI32.fromGrain(p)
581
- let mut s = WasmI32.fromGrain(s)
718
+ let mut search = WasmI32.fromGrain(search)
719
+ let mut string = WasmI32.fromGrain(string)
582
720
 
583
- let n = WasmI32.load(s, 4n)
584
- let m = WasmI32.load(p, 4n)
721
+ let n = WasmI32.load(string, 4n)
722
+ let m = WasmI32.load(search, 4n)
585
723
 
586
- s += 8n
587
- p += 8n
724
+ string += 8n
725
+ search += 8n
588
726
 
589
727
  // Bail if pattern length is longer than input length
590
728
  let ret = if (m > n) {
591
729
  false
592
730
  } else {
593
- Memory.compare(p, s, m) == 0n
731
+ Memory.compare(search, string, m) == 0n
594
732
  }
595
733
  Memory.decRef(WasmI32.fromGrain(pOrig))
596
734
  Memory.decRef(WasmI32.fromGrain(sOrig))
@@ -598,33 +736,40 @@ export let rec startsWith = (p: String, s: String) => {
598
736
  ret
599
737
  }
600
738
 
601
- // Check if a string ends with another string.
602
- // @param pattern: String - The substring to check
603
- // @param input: String - The input string
604
- // @returns Bool
739
+ /**
740
+ * Check if a string ends with another string.
741
+ *
742
+ * @param search: The string to compare to the end
743
+ * @param string: The string to search
744
+ * @returns `true` if the input string ends with the search value or `false` otherwise
745
+ *
746
+ * @example String.endsWith("world", "Hello world") == true
747
+ *
748
+ * @since v0.1.0
749
+ */
605
750
  @disableGC
606
- export let rec endsWith = (p: String, s: String) => {
751
+ export let rec endsWith = (search: String, string: String) => {
607
752
  let (+) = WasmI32.add
608
753
  let (-) = WasmI32.sub
609
754
  let (>) = WasmI32.gtU
610
755
  let (==) = WasmI32.eq
611
- let pOrig = p
612
- let sOrig = s
756
+ let pOrig = search
757
+ let sOrig = string
613
758
 
614
- let mut p = WasmI32.fromGrain(p)
615
- let mut s = WasmI32.fromGrain(s)
759
+ let mut search = WasmI32.fromGrain(search)
760
+ let mut string = WasmI32.fromGrain(string)
616
761
 
617
- let n = WasmI32.load(s, 4n)
618
- let m = WasmI32.load(p, 4n)
762
+ let n = WasmI32.load(string, 4n)
763
+ let m = WasmI32.load(search, 4n)
619
764
 
620
- s += 8n
621
- p += 8n
765
+ string += 8n
766
+ search += 8n
622
767
 
623
768
  // Bail if pattern length is longer than input length
624
769
  let ret = if (m > n) {
625
770
  false
626
771
  } else {
627
- Memory.compare(p, s + n - m, m) == 0n
772
+ Memory.compare(search, string + n - m, m) == 0n
628
773
  }
629
774
  Memory.decRef(WasmI32.fromGrain(pOrig))
630
775
  Memory.decRef(WasmI32.fromGrain(sOrig))
@@ -632,14 +777,6 @@ export let rec endsWith = (p: String, s: String) => {
632
777
  ret
633
778
  }
634
779
 
635
- export enum Encoding {
636
- UTF8,
637
- UTF16_BE,
638
- UTF16_LE,
639
- UTF32_BE,
640
- UTF32_LE,
641
- }
642
-
643
780
  // String->Byte encoding and helper functions:
644
781
 
645
782
  // these are globals to avoid memory leaks
@@ -775,7 +912,6 @@ let getCodePoint = (ptr: WasmI32) => {
775
912
  }
776
913
  continue
777
914
  }
778
- Memory.incRef(WasmI32.fromGrain((!)))
779
915
  if (!(lowerBoundary <= byte && byte <= upperBoundary)) {
780
916
  throw MalformedUnicode
781
917
  }
@@ -804,15 +940,8 @@ let initPtr = () => {
804
940
  }
805
941
  initPtr();
806
942
 
807
- // Encodes the given string using the given encoding scheme
808
- // @param s: String - The input string
809
- // @param encoding: Encoding - The encoding to use
810
- // @param includeBom: Bool - Whether to include the byte-order marker in the encoded output
811
- // @param dest: Bytes - The bytes object to write the encoded output into
812
- // @param destPos: Number - The location in the byte array to write the output
813
- // @returns Bytes - Returns `dest`
814
943
  @disableGC
815
- let rec encodeAtHelp = (s: String, encoding: Encoding, includeBom: Bool, dest: Bytes, destPos: Number) => {
944
+ let rec encodeAtHelp = (string: String, encoding: Encoding, includeBom: Bool, dest: Bytes, destPos: Number) => {
816
945
  let (>>>) = WasmI32.shrU
817
946
  let (-) = WasmI32.sub
818
947
  let (&) = WasmI32.and
@@ -821,12 +950,12 @@ let rec encodeAtHelp = (s: String, encoding: Encoding, includeBom: Bool, dest: B
821
950
  let (<=) = WasmI32.leU
822
951
  let (==) = WasmI32.eq
823
952
  let (+) = WasmI32.add
824
- let byteSize = WasmI32.fromGrain(wasmSafeByteLength(s)) >>> 1n
825
- let len = WasmI32.fromGrain(wasmSafeLength(s)) >>> 1n
953
+ let byteSize = WasmI32.fromGrain(wasmSafeByteLength(string)) >>> 1n
954
+ let len = WasmI32.fromGrain(wasmSafeLength(string)) >>> 1n
826
955
 
827
- let s = WasmI32.fromGrain(s)
956
+ let string = WasmI32.fromGrain(string)
828
957
 
829
- let mut ptr = s + 8n
958
+ let mut ptr = string + 8n
830
959
  let end = ptr + byteSize
831
960
 
832
961
  let bytes = WasmI32.fromGrain(dest)
@@ -995,45 +1124,50 @@ let rec encodeAtHelp = (s: String, encoding: Encoding, includeBom: Bool, dest: B
995
1124
  }
996
1125
  }
997
1126
 
998
- let ret = WasmI32.toGrain(bytes): Bytes
999
- Memory.decRef(WasmI32.fromGrain(s))
1127
+ Memory.decRef(WasmI32.fromGrain(string))
1000
1128
  Memory.decRef(WasmI32.fromGrain(encoding))
1001
1129
  Memory.decRef(WasmI32.fromGrain(includeBom))
1002
- Memory.decRef(WasmI32.fromGrain(dest))
1003
1130
  Memory.decRef(WasmI32.fromGrain(destPos))
1004
1131
  Memory.decRef(WasmI32.fromGrain(encodeAtHelp))
1005
- ret
1132
+
1133
+ // We don't decRef `dest` because we're returning it
1134
+ dest
1006
1135
  }
1007
1136
 
1008
- // Encodes the given string using the given encoding scheme
1009
- // @param s: String - The input string
1010
- // @param encoding: Encoding - The encoding to use
1011
- // @param dest: Bytes - The bytes object to write the encoded output into
1012
- // @param destPos: Number - The location in the byte array to write the output
1013
- // @returns Bytes - Returns `dest`
1014
- export let encodeAt = (s, encoding, dest, destPos) => {
1015
- encodeAtHelp(s, encoding, false, dest, destPos)
1137
+ /**
1138
+ * Encodes the given string into a byte sequence at the supplied position, excluding any byte-order marker, using the encoding scheme provided.
1139
+ *
1140
+ * @param string: The input string
1141
+ * @param encoding: The encoding to use
1142
+ * @param dest: The byte sequence that will be copied
1143
+ * @param destPos: The location in the byte sequence to write the output
1144
+ * @returns A copy of the input bytes with the encoded string replaced at the given position
1145
+ *
1146
+ * @since v0.4.0
1147
+ */
1148
+ export let encodeAt = (string, encoding, dest, destPos) => {
1149
+ encodeAtHelp(string, encoding, false, dest, destPos)
1016
1150
  }
1017
1151
 
1018
- // Encodes the given string using the given encoding scheme
1019
- // @param s: String - The input string
1020
- // @param encoding: Encoding - The encoding to use
1021
- // @param dest: Bytes - The bytes object to write the encoded output into
1022
- // @param destPos: Number - The location in the byte array to write the output
1023
- // @returns Bytes - Returns `dest`
1024
- export let encodeAtWithBom = (s, encoding, dest, destPos) => {
1025
- encodeAtHelp(s, encoding, true, dest, destPos)
1152
+ /**
1153
+ * Encodes the given string into a byte sequence at the supplied position, including any byte-order marker, using the encoding scheme provided.
1154
+ *
1155
+ * @param string: The input string
1156
+ * @param encoding: The encoding to use
1157
+ * @param dest: The byte sequence that will be copied
1158
+ * @param destPos: The location in the byte sequence to write the output
1159
+ * @returns A copy of the input bytes with the encoded string replaced at the given position
1160
+ *
1161
+ * @since v0.4.0
1162
+ */
1163
+ export let encodeAtWithBom = (string, encoding, dest, destPos) => {
1164
+ encodeAtHelp(string, encoding, true, dest, destPos)
1026
1165
  }
1027
1166
 
1028
- // Encodes the given string using the given encoding scheme
1029
- // @param s: String - The input string
1030
- // @param encoding: Encoding - The encoding to use
1031
- // @param includeBom: Bool - Whether to include the byte-order marker in the encoded output
1032
- // @returns Bytes
1033
1167
  @disableGC
1034
- let rec encodeHelp = (s: String, encoding: Encoding, includeBom: Bool) => {
1168
+ let rec encodeHelp = (string: String, encoding: Encoding, includeBom: Bool) => {
1035
1169
  Memory.incRef(WasmI32.fromGrain((+)))
1036
- let size = encodedLength(s, encoding) + if (includeBom) {
1170
+ let size = encodedLength(string, encoding) + if (includeBom) {
1037
1171
  match(encoding) {
1038
1172
  UTF8 => 3,
1039
1173
  UTF16_LE => 2,
@@ -1045,33 +1179,42 @@ let rec encodeHelp = (s: String, encoding: Encoding, includeBom: Bool) => {
1045
1179
  let (>>>) = WasmI32.shrU
1046
1180
  let bytes = WasmI32.toGrain(allocateBytes(WasmI32.fromGrain(size) >>> 1n))
1047
1181
  Memory.incRef(WasmI32.fromGrain(encodeAtHelp))
1048
- Memory.incRef(WasmI32.fromGrain(s))
1182
+ Memory.incRef(WasmI32.fromGrain(string))
1049
1183
  Memory.incRef(WasmI32.fromGrain(encoding))
1050
1184
  Memory.incRef(WasmI32.fromGrain(includeBom))
1051
1185
  Memory.incRef(WasmI32.fromGrain(bytes))
1052
- let ret = encodeAtHelp(s, encoding, includeBom, bytes, 0)
1053
- Memory.decRef(WasmI32.fromGrain(s))
1186
+ let ret = encodeAtHelp(string, encoding, includeBom, bytes, 0)
1187
+ Memory.decRef(WasmI32.fromGrain(string))
1054
1188
  Memory.decRef(WasmI32.fromGrain(encoding))
1055
1189
  Memory.decRef(WasmI32.fromGrain(includeBom))
1056
1190
  Memory.decRef(WasmI32.fromGrain(encodeHelp))
1057
1191
  ret
1058
1192
  }
1059
1193
 
1060
- // Encodes the given string using the given encoding scheme. A byte-order marker
1061
- // will not be included in the output.
1062
- // @param s: String - The input string
1063
- // @param encoding: Encoding - The encoding to use
1064
- // @returns Bytes
1065
- export let encode = (s: String, encoding: Encoding) => {
1066
- encodeHelp(s, encoding, false)
1194
+ /**
1195
+ * Encodes the given string using the given encoding scheme, excluding any byte-order marker.
1196
+ *
1197
+ * @param string: The input string
1198
+ * @param encoding: The encoding to use
1199
+ * @returns The byte representation of the string in the given encoding
1200
+ *
1201
+ * @since v0.4.0
1202
+ */
1203
+ export let encode = (string: String, encoding: Encoding) => {
1204
+ encodeHelp(string, encoding, false)
1067
1205
  }
1068
1206
 
1069
- // Encodes the given string using the given encoding scheme, including a byte-order marker
1070
- // @param s: String - The input string
1071
- // @param encoding: Encoding - The encoding to use
1072
- // @returns Bytes
1073
- export let encodeWithBom = (s: String, encoding: Encoding) => {
1074
- encodeHelp(s, encoding, true)
1207
+ /**
1208
+ * Encodes the given string using the given encoding scheme, including any byte-order marker.
1209
+ *
1210
+ * @param string: The input string
1211
+ * @param encoding: The encoding to use
1212
+ * @returns The byte representation of the string in the given encoding
1213
+ *
1214
+ * @since v0.4.0
1215
+ */
1216
+ export let encodeWithBom = (string: String, encoding: Encoding) => {
1217
+ encodeHelp(string, encoding, true)
1075
1218
  }
1076
1219
 
1077
1220
  // Byte->String decoding and helper functions:
@@ -1315,13 +1458,6 @@ let decodedLength = (bytes: Bytes, encoding: Encoding, start: WasmI32, size: Was
1315
1458
  }
1316
1459
  }
1317
1460
 
1318
- // Decodes the given byte sequence into a string using the given encoding scheme
1319
- // @param bytes: Bytes - The input bytes
1320
- // @param encoding: Encoding - The encoding to use
1321
- // @param skipBom: Bool - Whether to include the byte-order marker (if present) in the decoded output
1322
- // @param start: Number - The byte offset to begin decoding from
1323
- // @param size: Number - The maximum number of bytes to decode
1324
- // @returns String
1325
1461
  @disableGC
1326
1462
  let rec decodeRangeHelp = (bytes: Bytes, encoding: Encoding, skipBom: Bool, start: Number, size: Number) => {
1327
1463
  let (+) = WasmI32.add
@@ -1438,33 +1574,36 @@ let rec decodeRangeHelp = (bytes: Bytes, encoding: Encoding, skipBom: Bool, star
1438
1574
  ret
1439
1575
  }
1440
1576
 
1441
- // Decodes the given byte sequence into a string using the given encoding scheme, skipping
1442
- // the byte-order marker, if it's present.
1443
- // @param bytes: Bytes - The input bytes
1444
- // @param encoding: Encoding - The encoding to use
1445
- // @param start: Number - The byte offset to begin decoding from
1446
- // @param size: Number - The maximum number of bytes to decode
1447
- // @returns String
1577
+ /**
1578
+ * Decodes the given byte sequence of the specified range into a string, excluding any byte-order marker, using encoding scheme provided.
1579
+ *
1580
+ * @param bytes: The input bytes
1581
+ * @param encoding: The encoding to use
1582
+ * @param start: The byte offset to begin decoding from
1583
+ * @param size: The maximum number of bytes to decode
1584
+ * @returns The decoded string
1585
+ *
1586
+ * @since v0.4.0
1587
+ */
1448
1588
  export let decodeRange = (bytes: Bytes, encoding: Encoding, start: Number, size: Number) => {
1449
1589
  decodeRangeHelp(bytes, encoding, true, start, size)
1450
1590
  }
1451
1591
 
1452
- // Decodes the given byte sequence into a string using the given encoding scheme, including
1453
- // the byte-order marker, if it's present.
1454
- // @param bytes: Bytes - The input bytes
1455
- // @param encoding: Encoding - The encoding to use
1456
- // @param start: Number - The byte offset to begin decoding from
1457
- // @param size: Number - The maximum number of bytes to decode
1458
- // @returns String
1592
+ /**
1593
+ * Decodes the given byte sequence of the specified range into a string, including any byte-order marker, using encoding scheme provided.
1594
+ *
1595
+ * @param bytes: The input bytes
1596
+ * @param encoding: The encoding to use
1597
+ * @param start: The byte offset to begin decoding from
1598
+ * @param size: The maximum number of bytes to decode
1599
+ * @returns The decoded string
1600
+ *
1601
+ * @since v0.4.0
1602
+ */
1459
1603
  export let decodeRangeKeepBom = (bytes: Bytes, encoding: Encoding, start: Number, size: Number) => {
1460
1604
  decodeRangeHelp(bytes, encoding, false, start, size)
1461
1605
  }
1462
1606
 
1463
- // Decodes the given byte sequence into a string using the given encoding scheme
1464
- // @param bytes: Bytes - The input bytes
1465
- // @param encoding: Encoding - The encoding to use
1466
- // @param skipBom: Bool - Whether to include the byte-order marker (if present) in the decoded output
1467
- // @returns String
1468
1607
  @disableGC
1469
1608
  let rec decodeHelp = (bytes: Bytes, encoding: Encoding, skipBom: Bool) => {
1470
1609
  let bytesPtr = WasmI32.fromGrain(bytes)
@@ -1479,20 +1618,28 @@ let rec decodeHelp = (bytes: Bytes, encoding: Encoding, skipBom: Bool) => {
1479
1618
  ret
1480
1619
  }
1481
1620
 
1482
- // Decodes the given byte sequence into a string using the given encoding scheme,
1483
- // skipping the byte-order marker, if it's present.
1484
- // @param bytes: Bytes - The input bytes
1485
- // @param encoding: Encoding - The encoding to use
1486
- // @returns String
1621
+ /**
1622
+ * Decodes the given byte sequence into a string using the given encoding scheme, excluding any byte-order marker.
1623
+ *
1624
+ * @param bytes: The input bytes
1625
+ * @param encoding: The encoding to use
1626
+ * @returns The decoded string
1627
+ *
1628
+ * @since v0.4.0
1629
+ */
1487
1630
  export let decode = (bytes: Bytes, encoding: Encoding) => {
1488
1631
  decodeHelp(bytes, encoding, true)
1489
1632
  }
1490
1633
 
1491
- // Decodes the given byte sequence into a string using the given encoding scheme,
1492
- // including the byte-order marker, if it's present
1493
- // @param bytes: Bytes - The input bytes
1494
- // @param encoding: Encoding - The encoding to use
1495
- // @returns String
1634
+ /**
1635
+ * Decodes the given byte sequence into a string using the given encoding scheme, including any byte-order marker.
1636
+ *
1637
+ * @param bytes: The input bytes
1638
+ * @param encoding: The encoding to use
1639
+ * @returns The decoded string
1640
+ *
1641
+ * @since v0.4.0
1642
+ */
1496
1643
  export let decodeKeepBom = (bytes: Bytes, encoding: Encoding) => {
1497
1644
  decodeHelp(bytes, encoding, false)
1498
1645
  }
@@ -1502,6 +1649,10 @@ export let decodeKeepBom = (bytes: Bytes, encoding: Encoding) => {
1502
1649
  *
1503
1650
  * @param fn: The iterator function
1504
1651
  * @param str: The string to iterate
1652
+ *
1653
+ * @example String.forEachCodePoint(print, "Hello world")
1654
+ *
1655
+ * @since v0.4.0
1505
1656
  */
1506
1657
  @disableGC
1507
1658
  export let rec forEachCodePoint = (fn: (Number) -> Void, str: String) => {
@@ -1560,6 +1711,10 @@ export let rec forEachCodePoint = (fn: (Number) -> Void, str: String) => {
1560
1711
  *
1561
1712
  * @param fn: The iterator function
1562
1713
  * @param str: The string to iterate
1714
+ *
1715
+ * @example String.forEachCodePointi((codepoint, index) => print((codepoint, index)), "Hello world")
1716
+ *
1717
+ * @since v0.4.0
1563
1718
  */
1564
1719
  @disableGC
1565
1720
  export let rec forEachCodePointi = (fn: (Number, Number) -> Void, str: String) => {
@@ -1610,3 +1765,62 @@ export let rec forEachCodePointi = (fn: (Number, Number) -> Void, str: String) =
1610
1765
  Memory.decRef(WasmI32.fromGrain(forEachCodePointi))
1611
1766
  void
1612
1767
  }
1768
+ let trimString = (str: String, end: Bool) => {
1769
+ let chars = explode(str), charsLength = length(str)
1770
+ let mut i = 0, offset = 1
1771
+ if (end) {
1772
+ i = charsLength-1
1773
+ offset = -1
1774
+ }
1775
+ for (; i < charsLength && i > -1; i += offset) {
1776
+ let currentChar = chars[i];
1777
+ // TODO: Use unicode whitespace property and unicode line terminator once github issue #661 is completed
1778
+ if (
1779
+ // Spacing
1780
+ currentChar != '\u{0009}' && // Tab
1781
+ currentChar != '\u{000B}' && // LINE TABULATION
1782
+ currentChar != '\u{000C}' && // FORM FEED (FF)
1783
+ currentChar != '\u{0020}' && // Space
1784
+ currentChar != '\u{00A0}' && // No Break Space
1785
+ currentChar != '\u{FEFF}' && // ZERO WIDTH NO-BREAK SPACE
1786
+ // Line Terminators
1787
+ currentChar != '\n' && // LF
1788
+ currentChar != '\r' // CR
1789
+ ) break
1790
+ }
1791
+ if (end) slice(0, i+1, str)
1792
+ else slice(i, charsLength, str)
1793
+ }
1794
+ /**
1795
+ * Trims the beginning of a string—removing any leading whitespace characters.
1796
+ *
1797
+ * @param string: The string to be trimmed
1798
+ * @returns The trimmed string
1799
+ *
1800
+ * @example String.trimStart(" Hello World") == "Hello World"
1801
+ *
1802
+ * @since v0.4.2
1803
+ */
1804
+ export let trimStart = (string: String) => trimString(string, false)
1805
+ /**
1806
+ * Trims the end of a string—removing any trailing whitespace characters.
1807
+ *
1808
+ * @param string: The string to be trimmed
1809
+ * @returns The trimmed string
1810
+ *
1811
+ * @example String.trimEnd("Hello World ") == "Hello World"
1812
+ *
1813
+ * @since v0.4.2
1814
+ */
1815
+ export let trimEnd = (string: String) => trimString(string, true)
1816
+ /**
1817
+ * Trims a string—removing all leading and trailing whitespace characters.
1818
+ *
1819
+ * @param string: The string to be trimmed
1820
+ * @returns The trimmed string
1821
+ *
1822
+ * @example String.trim(" Hello World ") == "Hello World"
1823
+ *
1824
+ * @since v0.4.2
1825
+ */
1826
+ export let trim = (string: String) => trimEnd(trimStart(string))