@grain/stdlib 0.5.3 β†’ 0.5.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
@@ -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.
package/string.md CHANGED
@@ -514,6 +514,105 @@ Examples:
514
514
  String.endsWith("world", "Hello world") == true
515
515
  ```
516
516
 
517
+ ### String.**replaceFirst**
518
+
519
+ <details disabled>
520
+ <summary tabindex="-1">Added in <code>0.5.4</code></summary>
521
+ No other changes yet.
522
+ </details>
523
+
524
+ ```grain
525
+ replaceFirst : (String, String, String) -> String
526
+ ```
527
+
528
+ Replaces the first appearance of the search pattern in the string with the replacement value.
529
+
530
+ Parameters:
531
+
532
+ |param|type|description|
533
+ |-----|----|-----------|
534
+ |`searchPattern`|`String`|The string to replace|
535
+ |`replacement`|`String`|The replacement|
536
+ |`string`|`String`|The string to change|
537
+
538
+ Returns:
539
+
540
+ |type|description|
541
+ |----|-----------|
542
+ |`String`|A new string with the first occurrence of the search pattern replaced|
543
+
544
+ Examples:
545
+
546
+ ```grain
547
+ String.replaceFirst("🌾", "🌎", "Hello 🌾🌾") == "Hello 🌎🌾"
548
+ ```
549
+
550
+ ### String.**replaceLast**
551
+
552
+ <details disabled>
553
+ <summary tabindex="-1">Added in <code>0.5.4</code></summary>
554
+ No other changes yet.
555
+ </details>
556
+
557
+ ```grain
558
+ replaceLast : (String, String, String) -> String
559
+ ```
560
+
561
+ Replaces the last appearance of the search pattern in the string with the replacement value.
562
+
563
+ Parameters:
564
+
565
+ |param|type|description|
566
+ |-----|----|-----------|
567
+ |`searchPattern`|`String`|The string to replace|
568
+ |`replacement`|`String`|The replacement|
569
+ |`string`|`String`|The string to change|
570
+
571
+ Returns:
572
+
573
+ |type|description|
574
+ |----|-----------|
575
+ |`String`|A new string with the last occurrence of the search pattern replaced|
576
+
577
+ Examples:
578
+
579
+ ```grain
580
+ String.replaceLast("🌾", "🌎", "Hello 🌾🌾") == "Hello 🌾🌎"
581
+ ```
582
+
583
+ ### String.**replaceAll**
584
+
585
+ <details disabled>
586
+ <summary tabindex="-1">Added in <code>0.5.4</code></summary>
587
+ No other changes yet.
588
+ </details>
589
+
590
+ ```grain
591
+ replaceAll : (String, String, String) -> String
592
+ ```
593
+
594
+ Replaces every appearance of the search pattern in the string with the replacement value.
595
+
596
+ Parameters:
597
+
598
+ |param|type|description|
599
+ |-----|----|-----------|
600
+ |`searchPattern`|`String`|The string to replace|
601
+ |`replacement`|`String`|The replacement|
602
+ |`string`|`String`|The string to change|
603
+
604
+ Returns:
605
+
606
+ |type|description|
607
+ |----|-----------|
608
+ |`String`|A new string with each occurrence of the search pattern replaced|
609
+
610
+ Examples:
611
+
612
+ ```grain
613
+ String.replaceAll("🌾", "🌎", "Hello 🌾🌾") == "Hello 🌎🌎"
614
+ ```
615
+
517
616
  ### String.**encodeAt**
518
617
 
519
618
  <details disabled>
package/sys/file.gr CHANGED
@@ -816,7 +816,7 @@ export let fdStats = (fd: FileDescriptor) => {
816
816
  let rightsInheriting = WasmI64.load(structPtr, 16n)
817
817
  (rightsInheriting &
818
818
  1N << WasmI64.extendI32U(WasmI32.fromGrain(i) >> 1n)) >
819
- 0N
819
+ 0N
820
820
  }
821
821
  let rightsInheritingList = List.filteri(flagsToWasmVal, orderedRights)
822
822