@grain/stdlib 0.5.3 → 0.5.5

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 (77) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/array.gr +65 -57
  3. package/array.md +54 -6
  4. package/buffer.gr +71 -1
  5. package/buffer.md +142 -0
  6. package/bytes.gr +52 -3
  7. package/bytes.md +117 -0
  8. package/char.gr +23 -20
  9. package/char.md +18 -3
  10. package/immutablemap.gr +493 -0
  11. package/immutablemap.md +479 -0
  12. package/immutablepriorityqueue.gr +44 -16
  13. package/immutablepriorityqueue.md +44 -1
  14. package/immutableset.gr +498 -0
  15. package/immutableset.md +449 -0
  16. package/int32.gr +39 -37
  17. package/int32.md +6 -0
  18. package/int64.gr +39 -37
  19. package/int64.md +6 -0
  20. package/list.gr +33 -24
  21. package/list.md +39 -10
  22. package/map.gr +19 -28
  23. package/marshal.gr +4 -4
  24. package/number.gr +727 -26
  25. package/number.md +345 -23
  26. package/option.gr +30 -26
  27. package/option.md +12 -0
  28. package/package.json +1 -1
  29. package/path.gr +787 -0
  30. package/path.md +727 -0
  31. package/pervasives.gr +3 -4
  32. package/pervasives.md +6 -1
  33. package/priorityqueue.gr +25 -5
  34. package/priorityqueue.md +30 -0
  35. package/queue.gr +22 -7
  36. package/queue.md +18 -1
  37. package/regex.gr +161 -65
  38. package/regex.md +70 -0
  39. package/result.gr +24 -20
  40. package/result.md +12 -0
  41. package/runtime/atof/common.gr +198 -0
  42. package/runtime/atof/common.md +243 -0
  43. package/runtime/atof/decimal.gr +663 -0
  44. package/runtime/atof/decimal.md +59 -0
  45. package/runtime/atof/lemire.gr +264 -0
  46. package/runtime/atof/lemire.md +6 -0
  47. package/runtime/atof/parse.gr +615 -0
  48. package/runtime/atof/parse.md +12 -0
  49. package/runtime/atof/slow.gr +238 -0
  50. package/runtime/atof/slow.md +6 -0
  51. package/runtime/atof/table.gr +2016 -0
  52. package/runtime/atof/table.md +12 -0
  53. package/runtime/{stringUtils.gr → atoi/parse.gr} +1 -1
  54. package/runtime/{stringUtils.md → atoi/parse.md} +1 -1
  55. package/runtime/bigint.gr +7 -7
  56. package/runtime/compare.gr +2 -1
  57. package/runtime/equal.gr +3 -2
  58. package/runtime/exception.gr +9 -5
  59. package/runtime/exception.md +8 -2
  60. package/runtime/gc.gr +2 -1
  61. package/runtime/malloc.gr +1 -3
  62. package/runtime/numberUtils.gr +13 -13
  63. package/runtime/numberUtils.md +6 -0
  64. package/runtime/numbers.gr +123 -39
  65. package/runtime/numbers.md +26 -0
  66. package/runtime/string.gr +4 -2
  67. package/runtime/unsafe/conv.gr +21 -41
  68. package/runtime/unsafe/conv.md +0 -3
  69. package/runtime/unsafe/printWasm.gr +4 -40
  70. package/runtime/utils/printing.gr +3 -3
  71. package/set.gr +25 -25
  72. package/stack.gr +14 -0
  73. package/stack.md +17 -0
  74. package/string.gr +313 -39
  75. package/string.md +99 -0
  76. package/sys/file.gr +1 -1
  77. package/sys/time.gr +4 -4
package/set.gr CHANGED
@@ -2,7 +2,7 @@
2
2
  * @module Set: A Set is an unordered collection of unique values. Operations on a Set mutate the internal state, so it never needs to be re-assigned.
3
3
  * @example import Set from "set"
4
4
  *
5
- * @since 0.3.0
5
+ * @since v0.3.0
6
6
  */
7
7
  import List from "list"
8
8
  import Array from "array"
@@ -33,7 +33,7 @@ record Set<k> {
33
33
  * @param size: The initial storage size of the set
34
34
  * @returns An empty set with the given initial storage size
35
35
  *
36
- * @since 0.3.0
36
+ * @since v0.3.0
37
37
  */
38
38
  export let makeSized = size => {
39
39
  let buckets = Array.make(size, None)
@@ -44,7 +44,7 @@ export let makeSized = size => {
44
44
  *
45
45
  * @returns An empty set
46
46
  *
47
- * @since 0.3.0
47
+ * @since v0.3.0
48
48
  */
49
49
  export let make = () => {
50
50
  makeSized(16)
@@ -121,7 +121,7 @@ let rec nodeInBucket = (key, node) => {
121
121
  * @param key: The value to add
122
122
  * @param set: The set to update
123
123
  *
124
- * @since 0.3.0
124
+ * @since v0.3.0
125
125
  */
126
126
  export let add = (key, set) => {
127
127
  let buckets = set.buckets
@@ -154,7 +154,7 @@ export let add = (key, set) => {
154
154
  * @param set: The set to search
155
155
  * @returns `true` if the set contains the given value or `false` otherwise
156
156
  *
157
- * @since 0.3.0
157
+ * @since v0.3.0
158
158
  */
159
159
  export let contains = (key, set) => {
160
160
  let buckets = set.buckets
@@ -186,7 +186,7 @@ let rec removeInBucket = (key, node) => {
186
186
  * @param key: The value to remove
187
187
  * @param set: The set to update
188
188
  *
189
- * @since 0.3.0
189
+ * @since v0.3.0
190
190
  */
191
191
  export let remove = (key, set) => {
192
192
  let buckets = set.buckets
@@ -214,7 +214,7 @@ export let remove = (key, set) => {
214
214
  * @param set: The set to inspect
215
215
  * @returns The count of elements in the set
216
216
  *
217
- * @since 0.3.0
217
+ * @since v0.3.0
218
218
  */
219
219
  export let size = set => {
220
220
  set.size
@@ -226,7 +226,7 @@ export let size = set => {
226
226
  * @param set: The set to inspect
227
227
  * @returns `true` if the given set is empty or `false` otherwise
228
228
  *
229
- * @since 0.3.0
229
+ * @since v0.3.0
230
230
  */
231
231
  export let isEmpty = set => {
232
232
  size(set) == 0
@@ -237,7 +237,7 @@ export let isEmpty = set => {
237
237
  *
238
238
  * @param set: The set to reset
239
239
  *
240
- * @since 0.3.0
240
+ * @since v0.3.0
241
241
  */
242
242
  export let clear = set => {
243
243
  set.size = 0
@@ -263,7 +263,7 @@ let rec forEachBucket = (fn, node) => {
263
263
  * @param fn: The iterator function to call with each element
264
264
  * @param set: The set to iterate
265
265
  *
266
- * @since 0.3.0
266
+ * @since v0.3.0
267
267
  * @history v0.5.0: Ensured the iterator function return type is always `Void`
268
268
  */
269
269
  export let forEach = (fn, set) => {
@@ -288,7 +288,7 @@ let rec reduceEachBucket = (fn, node, acc) => {
288
288
  * @param set: The set to iterate
289
289
  * @returns The final accumulator returned from `fn`
290
290
  *
291
- * @since 0.3.0
291
+ * @since v0.3.0
292
292
  */
293
293
  export let reduce = (fn, init, set) => {
294
294
  let buckets = set.buckets
@@ -305,10 +305,10 @@ export let reduce = (fn, init, set) => {
305
305
  * @param fn: The predicate function to indicate which elements to remove from the set, where returning `false` indicates the value should be removed
306
306
  * @param set: The set to iterate
307
307
  *
308
- * @since 0.3.0
308
+ * @since v0.3.0
309
309
  */
310
- export let filter = (predicate, set) => {
311
- let keysToRemove = reduce((list, key) => if (!predicate(key)) {
310
+ export let filter = (fn, set) => {
311
+ let keysToRemove = reduce((list, key) => if (!fn(key)) {
312
312
  [key, ...list]
313
313
  } else {
314
314
  list
@@ -324,10 +324,10 @@ export let filter = (predicate, set) => {
324
324
  * @param fn: The predicate function to indicate which elements to remove from the set, where returning `true` indicates the value should be removed
325
325
  * @param set: The set to iterate
326
326
  *
327
- * @since 0.3.0
327
+ * @since v0.3.0
328
328
  */
329
- export let reject = (predicate, set) => {
330
- filter(key => !predicate(key), set)
329
+ export let reject = (fn, set) => {
330
+ filter(key => !fn(key), set)
331
331
  }
332
332
 
333
333
  /**
@@ -336,7 +336,7 @@ export let reject = (predicate, set) => {
336
336
  * @param set: The set to convert
337
337
  * @returns A list containing all set values
338
338
  *
339
- * @since 0.3.0
339
+ * @since v0.3.0
340
340
  */
341
341
  export let toList = set => {
342
342
  reduce((list, key) => [key, ...list], [], set)
@@ -348,7 +348,7 @@ export let toList = set => {
348
348
  * @param list: The list to convert
349
349
  * @returns A set containing all list values
350
350
  *
351
- * @since 0.3.0
351
+ * @since v0.3.0
352
352
  */
353
353
  export let fromList = list => {
354
354
  let set = make()
@@ -364,7 +364,7 @@ export let fromList = list => {
364
364
  * @param set: The set to convert
365
365
  * @returns An array containing all set values
366
366
  *
367
- * @since 0.3.0
367
+ * @since v0.3.0
368
368
  */
369
369
  export let toArray = set => {
370
370
  Array.fromList(toList(set))
@@ -376,7 +376,7 @@ export let toArray = set => {
376
376
  * @param array: The array to convert
377
377
  * @returns A set containing all array values
378
378
  *
379
- * @since 0.3.0
379
+ * @since v0.3.0
380
380
  */
381
381
  export let fromArray = array => {
382
382
  let set = make()
@@ -393,7 +393,7 @@ export let fromArray = array => {
393
393
  * @param set2: The second set to combine
394
394
  * @returns A set containing all elements of both sets
395
395
  *
396
- * @since 0.3.0
396
+ * @since v0.3.0
397
397
  */
398
398
  export let union = (set1, set2) => {
399
399
  let set = make()
@@ -413,7 +413,7 @@ export let union = (set1, set2) => {
413
413
  * @param set2: The second set to combine
414
414
  * @returns A set containing only unshared elements from both sets
415
415
  *
416
- * @since 0.3.0
416
+ * @since v0.3.0
417
417
  */
418
418
  export let diff = (set1, set2) => {
419
419
  let set = make()
@@ -437,7 +437,7 @@ export let diff = (set1, set2) => {
437
437
  * @param set2: The second set to combine
438
438
  * @returns A set containing only shared elements from both sets
439
439
  *
440
- * @since 0.3.0
440
+ * @since v0.3.0
441
441
  */
442
442
  export let intersect = (set1, set2) => {
443
443
  let set = make()
@@ -461,7 +461,7 @@ export let intersect = (set1, set2) => {
461
461
  * @param set: The set to inspect
462
462
  * @returns The internal state of the set
463
463
  *
464
- * @since 0.3.0
464
+ * @since v0.3.0
465
465
  */
466
466
  export let getInternalStats = set => {
467
467
  (set.size, Array.length(set.buckets))
package/stack.gr CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * @module Stack: An immutable stack implementation. A stack is a LIFO (last-in-first-out) data structure where new values are added, retrieved, and removed from the end.
3
3
  * @example import Stack from "stack"
4
+ *
5
+ * @deprecated This module will be renamed to ImmutableStack in the v0.6.0 release of Grain.
4
6
  */
5
7
 
6
8
  import List from "list"
@@ -20,10 +22,22 @@ record Stack<a> {
20
22
  * @section Values: Functions and constants included in the Stack module.
21
23
  */
22
24
 
25
+ /**
26
+ * An empty stack.
27
+ *
28
+ * @since v0.5.4
29
+ */
30
+ export let empty = {
31
+ let empty = { data: [], }
32
+ empty
33
+ }
34
+
23
35
  /**
24
36
  * Creates a new stack.
25
37
  *
26
38
  * @returns An empty stack
39
+ *
40
+ * @deprecated This will be removed in the v0.6.0 release of Grain.
27
41
  */
28
42
  export let make = () => {
29
43
  { data: [], }
package/stack.md CHANGED
@@ -2,6 +2,8 @@
2
2
  title: Stack
3
3
  ---
4
4
 
5
+ > **Deprecated:** This module will be renamed to ImmutableStack in the v0.6.0 release of Grain.
6
+
5
7
  An immutable stack implementation. A stack is a LIFO (last-in-first-out) data structure where new values are added, retrieved, and removed from the end.
6
8
 
7
9
  ```grain
@@ -24,8 +26,23 @@ Stacks are immutable data structures that store their data in a List.
24
26
 
25
27
  Functions and constants included in the Stack module.
26
28
 
29
+ ### Stack.**empty**
30
+
31
+ <details disabled>
32
+ <summary tabindex="-1">Added in <code>0.5.4</code></summary>
33
+ No other changes yet.
34
+ </details>
35
+
36
+ ```grain
37
+ empty : Stack<a>
38
+ ```
39
+
40
+ An empty stack.
41
+
27
42
  ### Stack.**make**
28
43
 
44
+ > **Deprecated:** This will be removed in the v0.6.0 release of Grain.
45
+
29
46
  ```grain
30
47
  make : () -> Stack<a>
31
48
  ```
package/string.gr CHANGED
@@ -221,7 +221,7 @@ export let lastIndexOf = (search: String, string: String) => {
221
221
  }
222
222
  }
223
223
 
224
- @disableGC
224
+ @unsafe
225
225
  let getCodePoint = (ptr: WasmI32) => {
226
226
  // Algorithm from https://encoding.spec.whatwg.org/#utf-8-decoder
227
227
  let (+) = WasmI32.add
@@ -872,6 +872,239 @@ export let endsWith = (search: String, string: String) => {
872
872
  }
873
873
  }
874
874
 
875
+ /**
876
+ * Replaces the first appearance of the search pattern in the string with the replacement value.
877
+ *
878
+ * @param searchPattern: The string to replace
879
+ * @param replacement: The replacement
880
+ * @param string: The string to change
881
+ * @returns A new string with the first occurrence of the search pattern replaced
882
+ *
883
+ * @example String.replaceFirst("🌾", "🌎", "Hello 🌾🌾") == "Hello 🌎🌾"
884
+ *
885
+ * @since v0.5.4
886
+ */
887
+ @unsafe
888
+ export let replaceFirst =
889
+ (
890
+ searchPattern: String,
891
+ replacement: String,
892
+ string: String,
893
+ ) => {
894
+ let (+) = WasmI32.add
895
+ let (-) = WasmI32.sub
896
+ let (>) = WasmI32.gtU
897
+ let (<) = WasmI32.ltU
898
+ let (==) = WasmI32.eq
899
+
900
+ let mut patternPtr = WasmI32.fromGrain(searchPattern)
901
+ let mut stringPtr = WasmI32.fromGrain(string)
902
+ let mut replacementPtr = WasmI32.fromGrain(replacement)
903
+
904
+ let patternLen = WasmI32.load(patternPtr, 4n)
905
+ let stringLen = WasmI32.load(stringPtr, 4n)
906
+ let replacementLen = WasmI32.load(replacementPtr, 4n)
907
+ // Bail if search str is longer than the string
908
+ if (stringLen < patternLen) {
909
+ string
910
+ } else {
911
+ patternPtr += 8n
912
+ stringPtr += 8n
913
+ replacementPtr += 8n
914
+
915
+ let mut found = false
916
+ // Search for an instance of the string
917
+ let mut foundIndex = -1n
918
+ let stringEndPtr = stringPtr + stringLen - patternLen + 1n
919
+ for (let mut i = stringPtr; i < stringEndPtr; i += 1n) {
920
+ // check for match
921
+ foundIndex += 1n
922
+ if (Memory.compare(i, patternPtr, patternLen) == 0n) {
923
+ found = true
924
+ break
925
+ }
926
+ }
927
+ if (found) {
928
+ // Create the new string
929
+ let str = allocateString(stringLen - patternLen + replacementLen)
930
+ let strPtr = str + 8n
931
+ Memory.copy(strPtr, stringPtr, foundIndex)
932
+ Memory.copy(strPtr + foundIndex, replacementPtr, replacementLen)
933
+ Memory.copy(
934
+ strPtr + foundIndex + replacementLen,
935
+ stringPtr + foundIndex + patternLen,
936
+ stringLen - foundIndex
937
+ )
938
+ // Copy over the str
939
+ WasmI32.toGrain(str): String
940
+ } else {
941
+ string
942
+ }
943
+ }
944
+ }
945
+
946
+ /**
947
+ * Replaces the last appearance of the search pattern in the string with the replacement value.
948
+ *
949
+ * @param searchPattern: The string to replace
950
+ * @param replacement: The replacement
951
+ * @param string: The string to change
952
+ * @returns A new string with the last occurrence of the search pattern replaced
953
+ *
954
+ * @example String.replaceLast("🌾", "🌎", "Hello 🌾🌾") == "Hello 🌾🌎"
955
+ *
956
+ * @since v0.5.4
957
+ */
958
+ @unsafe
959
+ export let replaceLast =
960
+ (
961
+ searchPattern: String,
962
+ replacement: String,
963
+ string: String,
964
+ ) => {
965
+ let (+) = WasmI32.add
966
+ let (-) = WasmI32.sub
967
+ let (>) = WasmI32.gtU
968
+ let (<) = WasmI32.ltU
969
+ let (==) = WasmI32.eq
970
+
971
+ let mut patternPtr = WasmI32.fromGrain(searchPattern)
972
+ let mut stringPtr = WasmI32.fromGrain(string)
973
+ let mut replacementPtr = WasmI32.fromGrain(replacement)
974
+
975
+ let patternLen = WasmI32.load(patternPtr, 4n)
976
+ let stringLen = WasmI32.load(stringPtr, 4n)
977
+ let replacementLen = WasmI32.load(replacementPtr, 4n)
978
+
979
+ // Bail if search str is longer than the string
980
+ if (stringLen < patternLen) {
981
+ string
982
+ } else {
983
+ patternPtr += 8n
984
+ stringPtr += 8n
985
+ replacementPtr += 8n
986
+
987
+ let mut found = false
988
+ // Search for an instance of the string
989
+ let stringEndPtr = stringPtr + stringLen - patternLen
990
+ let mut foundIndex = stringLen - patternLen + 1n
991
+ for (let mut i = stringEndPtr; i > stringPtr - 1n; i -= 1n) {
992
+ // check for match
993
+ foundIndex -= 1n
994
+ if (Memory.compare(i, patternPtr, patternLen) == 0n) {
995
+ found = true
996
+ break
997
+ }
998
+ }
999
+ if (found) {
1000
+ // Create the new string
1001
+ let str = allocateString(stringLen - patternLen + replacementLen)
1002
+ let strPtr = str + 8n
1003
+ Memory.copy(strPtr, stringPtr, foundIndex)
1004
+ Memory.copy(strPtr + foundIndex, replacementPtr, replacementLen)
1005
+ Memory.copy(
1006
+ strPtr + foundIndex + replacementLen,
1007
+ stringPtr + foundIndex + patternLen,
1008
+ stringLen - foundIndex
1009
+ )
1010
+ // Copy over the str
1011
+ WasmI32.toGrain(str): String
1012
+ } else {
1013
+ string
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+ /**
1019
+ * Replaces every appearance of the search pattern in the string with the replacement value.
1020
+ *
1021
+ * @param searchPattern: The string to replace
1022
+ * @param replacement: The replacement
1023
+ * @param string: The string to change
1024
+ * @returns A new string with each occurrence of the search pattern replaced
1025
+ *
1026
+ * @example String.replaceAll("🌾", "🌎", "Hello 🌾🌾") == "Hello 🌎🌎"
1027
+ *
1028
+ * @since v0.5.4
1029
+ */
1030
+ @unsafe
1031
+ export let replaceAll =
1032
+ (
1033
+ searchPattern: String,
1034
+ replacement: String,
1035
+ string: String,
1036
+ ) => {
1037
+ let (*) = WasmI32.mul
1038
+ let (+) = WasmI32.add
1039
+ let (-) = WasmI32.sub
1040
+ let (>) = WasmI32.gtU
1041
+ let (<) = WasmI32.ltU
1042
+ let (==) = WasmI32.eq
1043
+ let (>>) = WasmI32.shrS
1044
+
1045
+ let mut patternPtr = WasmI32.fromGrain(searchPattern)
1046
+ let mut stringPtr = WasmI32.fromGrain(string)
1047
+ let mut replacementPtr = WasmI32.fromGrain(replacement)
1048
+
1049
+ let patternLen = WasmI32.load(patternPtr, 4n)
1050
+ let stringLen = WasmI32.load(stringPtr, 4n)
1051
+ let replacementLen = WasmI32.load(replacementPtr, 4n)
1052
+
1053
+ // Bail if search str is longer than the string
1054
+ if (stringLen < patternLen) {
1055
+ string
1056
+ } else {
1057
+ patternPtr += 8n
1058
+ stringPtr += 8n
1059
+ replacementPtr += 8n
1060
+
1061
+ let mut found = false
1062
+ // Search for an instance of the string
1063
+ let stringEndPtr = stringPtr + stringLen - patternLen
1064
+ let mut foundIndex = stringLen - patternLen + 1n
1065
+ let mut foundIndexes = []
1066
+ let mut foundCount = 0n
1067
+ for (let mut i = stringEndPtr; i > stringPtr - 1n; i -= 1n) {
1068
+ // check for match
1069
+ foundIndex -= 1n
1070
+ if (Memory.compare(i, patternPtr, patternLen) == 0n) {
1071
+ found = true
1072
+ foundCount += 1n
1073
+ foundIndexes = [tagSimpleNumber(foundIndex), ...foundIndexes]
1074
+ }
1075
+ }
1076
+ if (found) {
1077
+ // Create the new string
1078
+ let str = allocateString(
1079
+ stringLen - (patternLen - replacementLen) * foundCount
1080
+ )
1081
+ let mut strPtr = str + 8n
1082
+ let mut lastIndex = 0n
1083
+ while (true) {
1084
+ match (foundIndexes) {
1085
+ [idx, ...rest] => {
1086
+ let index = untagSimpleNumber(idx)
1087
+ // Copy the part before
1088
+ Memory.copy(strPtr, stringPtr + lastIndex, index - lastIndex)
1089
+ strPtr += index - lastIndex
1090
+ // Copy the part after
1091
+ Memory.copy(strPtr, replacementPtr, replacementLen)
1092
+ strPtr += replacementLen
1093
+ lastIndex = index + patternLen
1094
+ foundIndexes = rest
1095
+ },
1096
+ [] => break,
1097
+ }
1098
+ }
1099
+ // Copy remaining string
1100
+ Memory.copy(strPtr, stringPtr + lastIndex, stringLen - lastIndex)
1101
+ WasmI32.toGrain(str): String
1102
+ } else {
1103
+ string
1104
+ }
1105
+ }
1106
+ }
1107
+
875
1108
  // String->Byte encoding and helper functions:
876
1109
 
877
1110
  // these are globals to avoid memory leaks
@@ -1349,33 +1582,33 @@ let bytesHaveBom = (bytes: Bytes, encoding: Encoding, start: WasmI32) => {
1349
1582
  match (encoding) {
1350
1583
  UTF8 => {
1351
1584
  bytesSize >= 3n &&
1352
- WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xEFn &&
1353
- WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xBBn &&
1354
- WasmI32.load8U(ptr + 2n, _BYTES_OFFSET) == 0xBFn
1585
+ WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xEFn &&
1586
+ WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xBBn &&
1587
+ WasmI32.load8U(ptr + 2n, _BYTES_OFFSET) == 0xBFn
1355
1588
  },
1356
1589
  UTF16_BE => {
1357
1590
  bytesSize >= 2n &&
1358
- WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xFEn &&
1359
- WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xFFn
1591
+ WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xFEn &&
1592
+ WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xFFn
1360
1593
  },
1361
1594
  UTF16_LE => {
1362
1595
  bytesSize >= 2n &&
1363
- WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xFFn &&
1364
- WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xFEn
1596
+ WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xFFn &&
1597
+ WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xFEn
1365
1598
  },
1366
1599
  UTF32_BE => {
1367
1600
  bytesSize >= 4n &&
1368
- WasmI32.load8U(ptr, _BYTES_OFFSET) == 0x00n &&
1369
- WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0x00n &&
1370
- WasmI32.load8U(ptr + 2n, _BYTES_OFFSET) == 0xFEn &&
1371
- WasmI32.load8U(ptr + 3n, _BYTES_OFFSET) == 0xFFn
1601
+ WasmI32.load8U(ptr, _BYTES_OFFSET) == 0x00n &&
1602
+ WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0x00n &&
1603
+ WasmI32.load8U(ptr + 2n, _BYTES_OFFSET) == 0xFEn &&
1604
+ WasmI32.load8U(ptr + 3n, _BYTES_OFFSET) == 0xFFn
1372
1605
  },
1373
1606
  UTF32_LE => {
1374
1607
  bytesSize >= 4n &&
1375
- WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xFFn &&
1376
- WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xFEn &&
1377
- WasmI32.load8U(ptr + 2n, _BYTES_OFFSET) == 0x00n &&
1378
- WasmI32.load8U(ptr + 3n, _BYTES_OFFSET) == 0x00n
1608
+ WasmI32.load8U(ptr, _BYTES_OFFSET) == 0xFFn &&
1609
+ WasmI32.load8U(ptr + 1n, _BYTES_OFFSET) == 0xFEn &&
1610
+ WasmI32.load8U(ptr + 2n, _BYTES_OFFSET) == 0x00n &&
1611
+ WasmI32.load8U(ptr + 3n, _BYTES_OFFSET) == 0x00n
1379
1612
  },
1380
1613
  }
1381
1614
  }
@@ -1612,7 +1845,7 @@ let decodeRangeHelp =
1612
1845
  // high surrogate. next character is low srurrogate
1613
1846
  let w1 = (w1 & 0x03FFn) << 10n
1614
1847
  let w2 = (WasmI32.load8U(bytesPtr, 2n) << 8n |
1615
- WasmI32.load8U(bytesPtr, 3n)) &
1848
+ WasmI32.load8U(bytesPtr, 3n)) &
1616
1849
  0x03FFn
1617
1850
  let codeWord = w1 + w2 + 0x10000n
1618
1851
  // no problems, so go past both code words
@@ -1635,7 +1868,7 @@ let decodeRangeHelp =
1635
1868
  // high surrogate. next character is low srurrogate
1636
1869
  let w1 = (w1 & 0x03FFn) << 10n
1637
1870
  let w2 = (WasmI32.load8U(bytesPtr, 3n) << 8n |
1638
- WasmI32.load8U(bytesPtr, 2n)) &
1871
+ WasmI32.load8U(bytesPtr, 2n)) &
1639
1872
  0x03FFn
1640
1873
  //let uPrime = codePoint - 0x10000n
1641
1874
  //let w1 = ((uPrime & 0b11111111110000000000n) >>> 10n) + 0xD800n // High surrogate
@@ -1867,30 +2100,71 @@ export let forEachCodePointi = (fn: (Number, Number) -> Void, str: String) => {
1867
2100
  void
1868
2101
  }
1869
2102
 
1870
- let trimString = (str: String, end: Bool) => {
1871
- let chars = explode(str), charsLength = length(str)
1872
- let mut i = 0, offset = 1
1873
- if (end) {
1874
- i = charsLength - 1
1875
- offset = -1
2103
+ @unsafe
2104
+ let trimString = (str: String, fromEnd: Bool) => {
2105
+ let (>>>) = WasmI32.shrU
2106
+ let (+) = WasmI32.add
2107
+ let (*) = WasmI32.mul
2108
+ let (-) = WasmI32.sub
2109
+ let (<) = WasmI32.ltU
2110
+ let (==) = WasmI32.eq
2111
+ let (!=) = WasmI32.ne
2112
+
2113
+ let mut stringPtr = WasmI32.fromGrain(str)
2114
+ let byteLength = WasmI32.load(stringPtr, 4n)
2115
+ stringPtr += 8n
2116
+ let mut i = 0n, offset = 1n
2117
+ if (fromEnd) {
2118
+ i = byteLength - 1n
2119
+ offset = -1n
1876
2120
  }
1877
- for (; i < charsLength && i > -1; i += offset) {
1878
- let currentChar = chars[i]
1879
- // TODO: Use unicode whitespace property and unicode line terminator once github issue #661 is completed
2121
+ let mut count = 0n
2122
+ for (; i < byteLength; i += offset) {
2123
+ // Get the byte, not necessarily a full UTF-8 codepoint
2124
+ let byte = WasmI32.load8U(stringPtr, i)
2125
+ // TODO(#661): Use unicode whitespace property and unicode line terminator
2126
+ if (!fromEnd) {
2127
+ if (
2128
+ byte == 0xEFn && // Check for the first BOM byte
2129
+ // Check for the full BOM codepoint, 0xEFBBBF. Bytes are reversed because wasm is little-endian
2130
+ WasmI32.load(stringPtr, i - 1n) >>> 8n == 0xBFBBEFn
2131
+ ) {
2132
+ i += 2n
2133
+ count += 3n
2134
+ continue
2135
+ }
2136
+ } else {
2137
+ if (
2138
+ byte == 0xBFn && // Check for the last BOM byte
2139
+ // Check for the full BOM codepoint, 0xEFBBBF. Bytes are reversed because wasm is little-endian
2140
+ WasmI32.load(stringPtr, i - 3n) >>> 8n == 0xBFBBEFn
2141
+ ) {
2142
+ i -= 2n
2143
+ count += 3n
2144
+ continue
2145
+ }
2146
+ }
1880
2147
  if (
1881
- // Spacing
1882
- currentChar != '\u{0009}' && // Tab
1883
- currentChar != '\u{000B}' && // LINE TABULATION
1884
- currentChar != '\u{000C}' && // FORM FEED (FF)
1885
- currentChar != '\u{0020}' && // Space
1886
- currentChar != '\u{00A0}' && // No Break Space
1887
- currentChar != '\u{FEFF}' && // ZERO WIDTH NO-BREAK SPACE
1888
- // Line Terminators
1889
- currentChar != '\n' && // LF
1890
- currentChar != '\r' // CR
1891
- ) break
2148
+ byte != 0x20n && // Space
2149
+ byte != 0x0Dn && // LF
2150
+ byte != 0x0An && // CR
2151
+ byte != 0x09n && // Tab
2152
+ byte != 0x0Bn && // LINE TABULATION
2153
+ byte != 0x0Cn && // FORM FEED (FF)
2154
+ byte != 0xA0n // No Break Space
2155
+ ) {
2156
+ break
2157
+ }
2158
+ count += 1n
1892
2159
  }
1893
- if (end) slice(0, i + 1, str) else slice(i, charsLength, str)
2160
+ let str = allocateString(byteLength - count)
2161
+ // Copy the string
2162
+ if (fromEnd) {
2163
+ Memory.copy(str + 8n, stringPtr, byteLength - count)
2164
+ } else {
2165
+ Memory.copy(str + 8n, stringPtr + count, byteLength - count)
2166
+ }
2167
+ WasmI32.toGrain(str): String
1894
2168
  }
1895
2169
  /**
1896
2170
  * Trims the beginning of a string—removing any leading whitespace characters.