@grain/stdlib 0.6.6 → 0.7.1

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 (137) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/LICENSE +1 -1
  3. package/README.md +2 -2
  4. package/array.gr +55 -7
  5. package/array.md +606 -560
  6. package/bigint.md +228 -228
  7. package/buffer.gr +85 -53
  8. package/buffer.md +442 -319
  9. package/bytes.gr +112 -35
  10. package/bytes.md +299 -219
  11. package/char.gr +201 -99
  12. package/char.md +447 -120
  13. package/exception.gr +11 -11
  14. package/exception.md +29 -4
  15. package/float32.gr +327 -3
  16. package/float32.md +698 -111
  17. package/float64.gr +320 -3
  18. package/float64.md +698 -111
  19. package/fs.gr +1082 -0
  20. package/fs.md +630 -0
  21. package/hash.gr +142 -88
  22. package/hash.md +105 -17
  23. package/int16.md +178 -178
  24. package/int32.gr +26 -5
  25. package/int32.md +266 -231
  26. package/int64.gr +27 -2
  27. package/int64.md +266 -231
  28. package/int8.md +178 -178
  29. package/json.gr +366 -51
  30. package/json.md +431 -15
  31. package/list.gr +328 -31
  32. package/list.md +759 -336
  33. package/map.gr +20 -12
  34. package/map.md +266 -260
  35. package/marshal.gr +41 -40
  36. package/marshal.md +14 -14
  37. package/number.gr +278 -35
  38. package/number.md +688 -269
  39. package/option.md +162 -162
  40. package/package.json +5 -3
  41. package/path.gr +48 -0
  42. package/path.md +180 -89
  43. package/pervasives.gr +2 -2
  44. package/pervasives.md +275 -275
  45. package/priorityqueue.gr +7 -7
  46. package/priorityqueue.md +131 -131
  47. package/queue.gr +183 -29
  48. package/queue.md +404 -148
  49. package/random.md +43 -43
  50. package/range.gr +4 -4
  51. package/range.md +42 -42
  52. package/rational.md +123 -123
  53. package/regex.gr +52 -51
  54. package/regex.md +102 -102
  55. package/result.md +118 -118
  56. package/runtime/atof/common.md +39 -39
  57. package/runtime/atof/decimal.gr +6 -6
  58. package/runtime/atof/decimal.md +14 -14
  59. package/runtime/atof/lemire.gr +5 -5
  60. package/runtime/atof/lemire.md +1 -1
  61. package/runtime/atof/parse.gr +16 -16
  62. package/runtime/atof/parse.md +2 -2
  63. package/runtime/atof/slow.md +1 -1
  64. package/runtime/atof/table.md +2 -2
  65. package/runtime/atoi/parse.gr +3 -3
  66. package/runtime/atoi/parse.md +1 -1
  67. package/runtime/bigint.gr +15 -47
  68. package/runtime/bigint.md +54 -60
  69. package/runtime/compare.gr +2 -2
  70. package/runtime/compare.md +8 -8
  71. package/runtime/dataStructures.md +211 -211
  72. package/runtime/debugPrint.gr +4 -1
  73. package/runtime/debugPrint.md +9 -9
  74. package/runtime/equal.gr +99 -77
  75. package/runtime/equal.md +8 -8
  76. package/runtime/exception.gr +62 -82
  77. package/runtime/exception.md +62 -11
  78. package/runtime/gc.gr +39 -45
  79. package/runtime/gc.md +4 -4
  80. package/runtime/malloc.gr +7 -7
  81. package/runtime/malloc.md +13 -13
  82. package/runtime/math/kernel/cos.gr +70 -0
  83. package/runtime/math/kernel/cos.md +14 -0
  84. package/runtime/math/kernel/sin.gr +65 -0
  85. package/runtime/math/kernel/sin.md +14 -0
  86. package/runtime/math/kernel/tan.gr +136 -0
  87. package/runtime/math/kernel/tan.md +14 -0
  88. package/runtime/math/rempio2.gr +244 -0
  89. package/runtime/math/rempio2.md +14 -0
  90. package/runtime/math/trig.gr +130 -0
  91. package/runtime/math/trig.md +28 -0
  92. package/runtime/math/umuldi.gr +26 -0
  93. package/runtime/math/umuldi.md +14 -0
  94. package/runtime/numberUtils.gr +29 -29
  95. package/runtime/numberUtils.md +12 -12
  96. package/runtime/numbers.gr +373 -381
  97. package/runtime/numbers.md +348 -342
  98. package/runtime/string.gr +37 -105
  99. package/runtime/string.md +20 -26
  100. package/runtime/unsafe/constants.md +24 -24
  101. package/runtime/unsafe/conv.md +19 -19
  102. package/runtime/unsafe/memory.gr +24 -20
  103. package/runtime/unsafe/memory.md +27 -7
  104. package/runtime/unsafe/offsets.gr +36 -0
  105. package/runtime/unsafe/offsets.md +88 -0
  106. package/runtime/unsafe/panic.gr +28 -0
  107. package/runtime/unsafe/panic.md +14 -0
  108. package/runtime/unsafe/tags.md +32 -32
  109. package/runtime/unsafe/wasmf32.md +28 -28
  110. package/runtime/unsafe/wasmf64.md +28 -28
  111. package/runtime/unsafe/wasmi32.md +47 -47
  112. package/runtime/unsafe/wasmi64.md +50 -50
  113. package/runtime/utf8.gr +189 -0
  114. package/runtime/utf8.md +117 -0
  115. package/runtime/wasi.gr +4 -2
  116. package/runtime/wasi.md +147 -147
  117. package/set.gr +18 -11
  118. package/set.md +253 -247
  119. package/stack.gr +171 -2
  120. package/stack.md +371 -89
  121. package/string.gr +352 -557
  122. package/string.md +298 -255
  123. package/uint16.md +170 -170
  124. package/uint32.gr +25 -4
  125. package/uint32.md +249 -214
  126. package/uint64.gr +25 -5
  127. package/uint64.md +249 -214
  128. package/uint8.md +170 -170
  129. package/uri.gr +57 -53
  130. package/uri.md +88 -89
  131. package/wasi/file.gr +67 -59
  132. package/wasi/file.md +308 -308
  133. package/wasi/process.md +26 -26
  134. package/wasi/random.md +12 -12
  135. package/wasi/time.md +16 -16
  136. package/runtime/utils/printing.gr +0 -60
  137. package/runtime/utils/printing.md +0 -26
package/json.gr CHANGED
@@ -17,6 +17,8 @@ from "runtime/bigint" include Bigint as BI
17
17
  from "runtime/dataStructures" include DataStructures
18
18
  from "runtime/numbers" include Numbers
19
19
  from "runtime/numberUtils" include NumberUtils
20
+ from "runtime/utf8" include Utf8
21
+ use Utf8.{ getCodePoint }
20
22
  from "runtime/string" include String as RuntimeString
21
23
  from "runtime/unsafe/tags" include Tags
22
24
  from "runtime/unsafe/wasmi32" include WasmI32
@@ -28,7 +30,7 @@ from "char" include Char
28
30
  from "string" include String
29
31
  from "list" include List
30
32
  from "uint8" include Uint8
31
- use RuntimeString.{ toString as runtimeToString, getCodePoint }
33
+ use RuntimeString.{ toString as runtimeToString }
32
34
  use Numbers.{ coerceNumberToWasmI32 }
33
35
  use DataStructures.{ tagSimpleNumber, untagSimpleNumber }
34
36
 
@@ -1026,8 +1028,8 @@ let makeJsonWriter = (format: FormattingSettings, buffer: Buffer.Buffer) => {
1026
1028
  // escapes it when needed, but requires to keep track of the previous code
1027
1029
  // point in the iteration so it's more complicated and handled separately.
1028
1030
  let emitCodePoint = if (
1029
- !format.escapeAllControlPoints &&
1030
- !format.escapeNonASCII
1031
+ !format.escapeAllControlPoints
1032
+ && !format.escapeNonASCII
1031
1033
  ) {
1032
1034
  (codePoint: Number) => {
1033
1035
  if (codePoint > 31 && codePoint != 0x0022 && codePoint != 0x005C) {
@@ -1042,10 +1044,10 @@ let makeJsonWriter = (format: FormattingSettings, buffer: Buffer.Buffer) => {
1042
1044
  // from 31 to 127.
1043
1045
  (codePoint: Number) => {
1044
1046
  if (
1045
- codePoint > 31 &&
1046
- codePoint != 0x0022 &&
1047
- codePoint != 0x005C &&
1048
- codePoint < 128
1047
+ codePoint > 31
1048
+ && codePoint != 0x0022
1049
+ && codePoint != 0x005C
1050
+ && codePoint < 128
1049
1051
  ) {
1050
1052
  Buffer.addCharFromCodePoint(codePoint, buffer)
1051
1053
  } else {
@@ -1059,11 +1061,11 @@ let makeJsonWriter = (format: FormattingSettings, buffer: Buffer.Buffer) => {
1059
1061
  // codepoints, but covering that would be overkill.
1060
1062
  (codePoint: Number) => {
1061
1063
  if (
1062
- codePoint > 31 &&
1063
- codePoint != 0x0022 &&
1064
- codePoint != 0x005C &&
1065
- codePoint < 127 ||
1066
- codePoint > 159
1064
+ codePoint > 31
1065
+ && codePoint != 0x0022
1066
+ && codePoint != 0x005C
1067
+ && codePoint < 127
1068
+ || codePoint > 159
1067
1069
  ) {
1068
1070
  Buffer.addCharFromCodePoint(codePoint, buffer)
1069
1071
  } else {
@@ -1077,10 +1079,10 @@ let makeJsonWriter = (format: FormattingSettings, buffer: Buffer.Buffer) => {
1077
1079
  // 127 (Delete).
1078
1080
  (codePoint: Number) => {
1079
1081
  if (
1080
- codePoint > 31 &&
1081
- codePoint != 0x0022 &&
1082
- codePoint != 0x005C &&
1083
- codePoint < 127
1082
+ codePoint > 31
1083
+ && codePoint != 0x0022
1084
+ && codePoint != 0x005C
1085
+ && codePoint < 127
1084
1086
  ) {
1085
1087
  // fast path for chars that never need any escaping
1086
1088
  Buffer.addCharFromCodePoint(codePoint, buffer)
@@ -1321,6 +1323,7 @@ let rec readCodePoint = (bytePosition: Number, string: String) => {
1321
1323
 
1322
1324
  if (bytePositionW32 < byteSize) {
1323
1325
  let codePoint = getCodePoint(ptr)
1326
+
1324
1327
  tagSimpleNumber(codePoint)
1325
1328
  } else {
1326
1329
  _END_OF_INPUT
@@ -1364,8 +1367,8 @@ let skipWhiteSpace = (parserState: JsonParserState) => {
1364
1367
  // isAtEndOfInput is not strictly necessary here
1365
1368
  // could remove as an optimization
1366
1369
  while (
1367
- isInterTokenWhiteSpace(parserState.currentCodePoint) &&
1368
- !isAtEndOfInput(parserState)
1370
+ isInterTokenWhiteSpace(parserState.currentCodePoint)
1371
+ && !isAtEndOfInput(parserState)
1369
1372
  ) {
1370
1373
  next(parserState)
1371
1374
  void
@@ -1426,10 +1429,10 @@ let expectCodePointAndAdvance = (
1426
1429
  next(parserState)
1427
1430
  None
1428
1431
  } else {
1429
- let detail = "expected " ++
1430
- formatCodePointOrEOF(expectedCodePoint) ++
1431
- ", found " ++
1432
- formatCodePointOrEOF(c)
1432
+ let detail = "expected "
1433
+ ++ formatCodePointOrEOF(expectedCodePoint)
1434
+ ++ ", found "
1435
+ ++ formatCodePointOrEOF(c)
1433
1436
  Some(buildUnexpectedTokenError(parserState, detail))
1434
1437
  }
1435
1438
  }
@@ -1438,9 +1441,9 @@ let atoiFast = buffer => {
1438
1441
  let mut result = 0
1439
1442
  for (let mut i = 0; i < bufLen; i += 1) {
1440
1443
  use Uint8.{ (-) }
1441
- result = (result << 1) +
1442
- (result << 3) +
1443
- Uint8.toNumber(Buffer.getUint8(i, buffer) - 48us)
1444
+ result = (result << 1)
1445
+ + (result << 3)
1446
+ + Uint8.toNumber(Buffer.getUint8(i, buffer) - 48us)
1444
1447
  }
1445
1448
  result
1446
1449
  }
@@ -1467,8 +1470,8 @@ let rec parseValue = (parserState: JsonParserState) => {
1467
1470
  0x39 => parseNumberValue(parserState), // '9'
1468
1471
  0x2D => parseNumberValue(parserState), // '-'
1469
1472
  c => {
1470
- let detail = "expected start of a JSON value, found " ++
1471
- formatCodePointOrEOF(c)
1473
+ let detail = "expected start of a JSON value, found "
1474
+ ++ formatCodePointOrEOF(c)
1472
1475
  Err(buildUnexpectedTokenError(parserState, detail))
1473
1476
  },
1474
1477
  }
@@ -1655,20 +1658,20 @@ and parseString = (parserState: JsonParserState) => {
1655
1658
  if (hexDigitCodePoint >= 48 && hexDigitCodePoint <= 57) { // 0..9
1656
1659
  digit -= 48
1657
1660
  } else if (
1658
- hexDigitCodePoint >= 65 &&
1659
- hexDigitCodePoint <= 70
1661
+ hexDigitCodePoint >= 65
1662
+ && hexDigitCodePoint <= 70
1660
1663
  ) { // A..F
1661
1664
  digit -= 55 // (65 - 10)
1662
1665
  } else if (
1663
- hexDigitCodePoint >= 97 &&
1664
- hexDigitCodePoint <= 102
1666
+ hexDigitCodePoint >= 97
1667
+ && hexDigitCodePoint <= 102
1665
1668
  ) { // a..f
1666
1669
  digit -= 87 // (97 - 10)
1667
1670
  } else {
1668
1671
  let digitsSoFar = 3 - digitIndex
1669
1672
  let detail =
1670
- "expected exactly 4 hexadecimal digits in the UTF-16 escape sequence, found only " ++
1671
- runtimeToString(digitsSoFar)
1673
+ "expected exactly 4 hexadecimal digits in the UTF-16 escape sequence, found only "
1674
+ ++ runtimeToString(digitsSoFar)
1672
1675
  return Err(buildUnexpectedTokenError(parserState, detail))
1673
1676
  }
1674
1677
 
@@ -1700,19 +1703,19 @@ and parseString = (parserState: JsonParserState) => {
1700
1703
  // iteration of the loop.
1701
1704
  highSurrogate = codeUnit
1702
1705
  } else if (
1703
- isCodePointInBasicMultilingualPlane(codeUnit) &&
1704
- !isLowSurrogate(codeUnit)
1706
+ isCodePointInBasicMultilingualPlane(codeUnit)
1707
+ && !isLowSurrogate(codeUnit)
1705
1708
  ) {
1706
1709
  let codePoint = codeUnit
1707
1710
  Buffer.addCharFromCodePoint(codePoint, buffer)
1708
1711
  break
1709
1712
  } else {
1710
1713
  let message =
1711
- "Invalid character escape sequence at position " ++
1712
- runtimeToString(escapeStartPos) ++
1713
- ": expected a Unicode code point in Basic Multilingual Plane (U+0000..U+FFFF) or a high surrogate (0xD800..0xDBFF) of a UTF-16 surrogate pair, found " ++
1714
- "0x" ++
1715
- toHexWithZeroPadding(codeUnit, 4)
1714
+ "Invalid character escape sequence at position "
1715
+ ++ runtimeToString(escapeStartPos)
1716
+ ++ ": expected a Unicode code point in Basic Multilingual Plane (U+0000..U+FFFF) or a high surrogate (0xD800..0xDBFF) of a UTF-16 surrogate pair, found "
1717
+ ++ "0x"
1718
+ ++ toHexWithZeroPadding(codeUnit, 4)
1716
1719
  return Err(InvalidUTF16SurrogatePair(message))
1717
1720
  }
1718
1721
  } else {
@@ -1728,11 +1731,11 @@ and parseString = (parserState: JsonParserState) => {
1728
1731
  break
1729
1732
  } else {
1730
1733
  let message =
1731
- "Invalid character escape sequence at position " ++
1732
- runtimeToString(escapeStartPos) ++
1733
- ": expected a low surrogate (0xDC00..0xDFFF) in the second code unit of the UTF-16 sequence, found " ++
1734
- "0x" ++
1735
- toHexWithZeroPadding(codeUnit, 4)
1734
+ "Invalid character escape sequence at position "
1735
+ ++ runtimeToString(escapeStartPos)
1736
+ ++ ": expected a low surrogate (0xDC00..0xDFFF) in the second code unit of the UTF-16 sequence, found "
1737
+ ++ "0x"
1738
+ ++ toHexWithZeroPadding(codeUnit, 4)
1736
1739
  return Err(InvalidUTF16SurrogatePair(message))
1737
1740
  }
1738
1741
  }
@@ -1742,8 +1745,8 @@ and parseString = (parserState: JsonParserState) => {
1742
1745
  // JSON doesn't allow arbitrary characters to be preceded by backslash escape.
1743
1746
  // Only the ones above.
1744
1747
  let detail =
1745
- "expected a valid escape sequence or the end of string, found " ++
1746
- formatCodePointOrEOF(unexpectedCodePoint)
1748
+ "expected a valid escape sequence or the end of string, found "
1749
+ ++ formatCodePointOrEOF(unexpectedCodePoint)
1747
1750
  return Err(buildUnexpectedTokenError(parserState, detail))
1748
1751
  },
1749
1752
  }
@@ -1816,8 +1819,8 @@ and parseNumberValue = (parserState: JsonParserState) => {
1816
1819
  unexpectedCodePoint => {
1817
1820
  // The integer part of the number has to have at least one digit.
1818
1821
  // JSON doesn't allow numbers starting with decimal separator like ".1".
1819
- let detail = "expected a decimal digit, found " ++
1820
- formatCodePointOrEOF(unexpectedCodePoint)
1822
+ let detail = "expected a decimal digit, found "
1823
+ ++ formatCodePointOrEOF(unexpectedCodePoint)
1821
1824
  return Err(buildUnexpectedTokenError(parserState, detail))
1822
1825
  },
1823
1826
  }
@@ -2061,11 +2064,323 @@ provide let parse: (str: String) => Result<Json, JsonParseError> = (str: String)
2061
2064
  } else {
2062
2065
  match (root) {
2063
2066
  Ok(_) => {
2064
- let detail = "expected end of input, found " ++
2065
- formatCodePointOrEOF(parserState.currentCodePoint)
2067
+ let detail = "expected end of input, found "
2068
+ ++ formatCodePointOrEOF(parserState.currentCodePoint)
2066
2069
  Err(buildUnexpectedTokenError(parserState, detail))
2067
2070
  },
2068
2071
  e => e,
2069
2072
  }
2070
2073
  }
2071
2074
  }
2075
+
2076
+ /**
2077
+ * Utilities for accessing and updating JSON data.
2078
+ *
2079
+ * @example
2080
+ * let obj = JsonObject([("x", JsonNumber(123))])
2081
+ * assert get(property("x") ||> number, obj) == Some(123)
2082
+ * @example
2083
+ * let obj = JsonObject([("x", JsonNumber(123))])
2084
+ * assert set(property("x") ||> number, 321, obj) ==
2085
+ * Some(JsonObject([("x", JsonNumber(321))]))
2086
+ *
2087
+ * @since v0.7.0
2088
+ */
2089
+ provide module Lenses {
2090
+ /**
2091
+ * A structure which provides functionality for accessing and setting JSON
2092
+ * data.
2093
+ *
2094
+ * @since v0.7.0
2095
+ */
2096
+ provide record Lens<a, b> {
2097
+ /**
2098
+ * A function which reads a value from the subject.
2099
+ */
2100
+ get: (subject: a) => Option<b>,
2101
+ /**
2102
+ * A function which immutably updates a value in the subject.
2103
+ */
2104
+ set: (newValue: b, subject: a) => Option<a>,
2105
+ }
2106
+
2107
+ /**
2108
+ * Reads the value focused on by the given lens from the input data.
2109
+ *
2110
+ * @param lens: The lens to apply to the subject data
2111
+ * @param subject: The data which will have the lens applied to it
2112
+ * @returns `Some(data)` containing the data read by the lens if the lens matches the given data, or `None` if the data cannot be matched to the lens
2113
+ *
2114
+ * @example assert get(number, JsonNumber(123)) == Some(123)
2115
+ * @example assert get(string, JsonString("abc")) == Some("abc")
2116
+ * @example assert get(number, JsonString("abc")) == None
2117
+ */
2118
+ provide let get = (lens, subject) => lens.get(subject)
2119
+
2120
+ /**
2121
+ * Sets the value focused on by the given lens from the input data to the
2122
+ * desired new value.
2123
+ *
2124
+ * @param lens: The lens to apply to the subject data
2125
+ * @param newValue: The new value to set at the focus of the lens
2126
+ * @param subject: The data which will have the lens applied to it
2127
+ * @returns `Some(data)` containing the new data after the lens substitution if the lens matches the given data, or `None` if the data cannot be matched to the lens
2128
+ *
2129
+ * @example assert set(number, 123, JsonBoolean(true)) == Some(JsonNumber(123))
2130
+ * @example assert set(property("a"), JsonNumber(123), JsonObject([("a", JsonNull)])) == Some(JsonObject([("a", JsonNumber(123))]))
2131
+ * @example assert set(property("a"), JsonNumber(123), JsonBoolean(true)) == None
2132
+ */
2133
+ provide let set = (lens, newValue, subject) => lens.set(newValue, subject)
2134
+
2135
+ /**
2136
+ * Updates the value focused on by the given lens from the input data by
2137
+ * applying a function to it and setting the focus to the result of the function
2138
+ *
2139
+ * @param lens: The lens to apply to the subject data
2140
+ * @param fn: The function to apply to the matched data at the lens if matched
2141
+ * @param subject: The data which will have the lens applied to it
2142
+ * @returns `Some(data)` containing the new data after the lens mapping has been applied if the lens matches the given data, or `None` if the data cannot be matched to the lens
2143
+ *
2144
+ * @example assert map(number, x => x * 2, JsonNumber(5)) == Some(JsonNumber(10))
2145
+ * @example
2146
+ * assert map(property("x"), x => JsonArray([x, x]), JsonObject([("x", JsonNumber(1))])) ==
2147
+ * Some(JsonObject([("x", JsonArray([JsonNumber(1), JsonNumber(1)]))]))
2148
+ * @example assert map(number, x => x * 2, JsonString("abc")) == None
2149
+ */
2150
+ provide let map = (lens, fn, subject) => {
2151
+ match (lens.get(subject)) {
2152
+ Some(lensVal) => lens.set(fn(lensVal), subject),
2153
+ None => None,
2154
+ }
2155
+ }
2156
+
2157
+ /**
2158
+ * A lens whose focus is a JSON value.
2159
+ *
2160
+ * @example assert get(json, JsonString("abc")) == Some(JsonString("abc"))
2161
+ *
2162
+ * @since v0.7.0
2163
+ */
2164
+ provide let json = {
2165
+ get: json => Some(json),
2166
+ set: (newValue, _) => Some(newValue),
2167
+ }
2168
+
2169
+ /**
2170
+ * A lens whose focus is a JSON boolean value.
2171
+ *
2172
+ * @example assert get(boolean, JsonBoolean(true)) == Some(true)
2173
+ *
2174
+ * @since v0.7.0
2175
+ */
2176
+ provide let boolean = {
2177
+ get: json => {
2178
+ match (json) {
2179
+ JsonBoolean(val) => Some(val),
2180
+ _ => None,
2181
+ }
2182
+ },
2183
+ set: (newValue, _) => Some(JsonBoolean(newValue)),
2184
+ }
2185
+
2186
+ /**
2187
+ * A lens whose focus is a JSON string value.
2188
+ *
2189
+ * @example assert get(string, JsonString("abc")) == Some("abc")
2190
+ *
2191
+ * @since v0.7.0
2192
+ */
2193
+ provide let string = {
2194
+ get: json => {
2195
+ match (json) {
2196
+ JsonString(val) => Some(val),
2197
+ _ => None,
2198
+ }
2199
+ },
2200
+ set: (newValue, _) => Some(JsonString(newValue)),
2201
+ }
2202
+
2203
+ /**
2204
+ * A lens whose focus is a JSON number value.
2205
+ *
2206
+ * @example assert get(number, JsonNumber(123)) == Some(123)
2207
+ *
2208
+ * @since v0.7.0
2209
+ */
2210
+ provide let number = {
2211
+ get: json => {
2212
+ match (json) {
2213
+ JsonNumber(val) => Some(val),
2214
+ _ => None,
2215
+ }
2216
+ },
2217
+ set: (newValue, _) => Some(JsonNumber(newValue)),
2218
+ }
2219
+
2220
+ /**
2221
+ * A lens whose focus is a JSON array.
2222
+ *
2223
+ * @example assert get(array, JsonArray([JsonNumber(123)])) == Some([JsonNumber(123)])
2224
+ *
2225
+ * @since v0.7.0
2226
+ */
2227
+ provide let array = {
2228
+ get: json => {
2229
+ match (json) {
2230
+ JsonArray(val) => Some(val),
2231
+ _ => None,
2232
+ }
2233
+ },
2234
+ set: (newValue, _) => Some(JsonArray(newValue)),
2235
+ }
2236
+
2237
+ /**
2238
+ * A lens whose focus is the property pair list of a JSON object.
2239
+ *
2240
+ * @example assert get(objectProperties, JsonObject([("a", JsonNumber(123))])) == Some([("a", JsonNumber(123))])
2241
+ *
2242
+ * @since v0.7.0
2243
+ */
2244
+ provide let objectProperties = {
2245
+ get: json => {
2246
+ match (json) {
2247
+ JsonObject(val) => Some(val),
2248
+ _ => None,
2249
+ }
2250
+ },
2251
+ set: (newValue, _) => Some(JsonObject(newValue)),
2252
+ }
2253
+
2254
+ let rec replaceFirst = (acc, list, propertyName, newValue) => {
2255
+ match (list) {
2256
+ [first, ...rest] => {
2257
+ let (firstKey, firstVal) = first
2258
+ if (firstKey == propertyName) {
2259
+ List.append(List.reverse([(firstKey, newValue), ...acc]), rest)
2260
+ } else {
2261
+ replaceFirst([first, ...acc], rest, propertyName, newValue)
2262
+ }
2263
+ },
2264
+ [] => List.reverse([(propertyName, newValue), ...acc]),
2265
+ }
2266
+ }
2267
+
2268
+ /**
2269
+ * Creates a lens whose focus is a given property of a JSON object.
2270
+ *
2271
+ * @param propertyName: The property name of the JSON object to focus on
2272
+ * @returns A lens whose focus is the given property of a JSON object
2273
+ *
2274
+ * @example assert get(property("x"), JsonObject([("x", JsonNumber(123))])) == Some(JsonNumber(123))
2275
+ * @example
2276
+ * assert set(property("x"), JsonString("new"), JsonObject([("x", JsonNumber(123))])) ==
2277
+ * Some(JsonObject([("x", JsonString("new"))]))
2278
+ *
2279
+ * @since v0.7.0
2280
+ */
2281
+ provide let property = propertyName =>
2282
+ { get: json => match (json) {
2283
+ JsonObject(props) => {
2284
+ match (List.find(((k, _)) => k == propertyName, props)) {
2285
+ Some((_, v)) => Some(v),
2286
+ None => None,
2287
+ }
2288
+ },
2289
+ _ => None,
2290
+ }, set: (newValue, json) => {
2291
+ match (json) {
2292
+ JsonObject(props) =>
2293
+ Some(JsonObject(replaceFirst([], props, propertyName, newValue))),
2294
+ _ => None,
2295
+ }
2296
+ } }
2297
+
2298
+ /**
2299
+ * Wraps a lens to permit nullable values in addition to the original value
2300
+ * type of the given lens. During a `get` operation if the lens matches then
2301
+ * the result will be enclosed in `Some`; if the lens does not match but the
2302
+ * value focused is null, then the lens will still successfully match and
2303
+ * `None` will be returned.
2304
+ *
2305
+ * @example assert get(nullable(number), JsonNumber(123)) == Some(Some(123))
2306
+ * @example assert get(nullable(number), JsonNull) == Some(None)
2307
+ * @example assert get(nullable(number), JsonString("abc")) == None
2308
+ * @example assert set(nullable(number), Some(123), JsonString("abc")) == Some(JsonNumber(123))
2309
+ *
2310
+ * @since v0.7.0
2311
+ */
2312
+ provide let nullable = lens =>
2313
+ { get: json => {
2314
+ match (get(lens, json)) {
2315
+ Some(x) => Some(Some(x)),
2316
+ None => {
2317
+ match (json) {
2318
+ JsonNull => Some(None),
2319
+ _ => {
2320
+ match (get(lens, json)) {
2321
+ Some(x) => Some(Some(x)),
2322
+ None => None,
2323
+ }
2324
+ },
2325
+ }
2326
+ },
2327
+ }
2328
+ }, set: (newValue, json) => {
2329
+ match (newValue) {
2330
+ Some(x) => lens.set(x, json),
2331
+ None => Some(JsonNull),
2332
+ }
2333
+ } }
2334
+
2335
+ /**
2336
+ * Reverse lens composition.
2337
+ *
2338
+ * @param lens1: The lens which will be applied first
2339
+ * @param lens2: The lens which will be applied second
2340
+ * @returns A lens which combines the two given lenses, passing through the first and then the second
2341
+ *
2342
+ * @example assert get(property("x") ||> number, JsonObject([("x", JsonNumber(123))])) == Some(123)
2343
+ * @example
2344
+ * assert set(property("x") ||> string, "new", JsonObject([("x", JsonNumber(123))])) ==
2345
+ * Some(JsonObject([("x", JsonString("new"))]))
2346
+ *
2347
+ * @since v0.7.0
2348
+ */
2349
+ let (||>) = (lens1, lens2) =>
2350
+ { get: json => {
2351
+ match (lens1.get(json)) {
2352
+ Some(x) => lens2.get(x),
2353
+ None => None,
2354
+ }
2355
+ }, set: (newValue, target) => {
2356
+ match (lens1.get(target)) {
2357
+ Some(x) => match (lens2.set(newValue, x)) {
2358
+ Some(y) => lens1.set(y, target),
2359
+ None => None,
2360
+ },
2361
+ None => None,
2362
+ }
2363
+ } }
2364
+
2365
+ /**
2366
+ * Creates a lens whose focus is a given property path within a JSON object tree.
2367
+ *
2368
+ * @param propertyNames: The property path of the JSON object to create a focus on
2369
+ * @returns A lens whose focus is the given property path of a JSON object
2370
+ *
2371
+ * @example
2372
+ * let nestedObj = JsonObject([("a", JsonObject([("b", JsonNumber(123))]))])
2373
+ * assert get(propertyPath(["a", "b"]), nestedObj) == Some(JsonNumber(123))
2374
+ *
2375
+ * @since v0.7.0
2376
+ */
2377
+ provide let propertyPath = propertyNames => {
2378
+ List.reduce(
2379
+ (lensAcc, propertyName) => lensAcc ||> property(propertyName),
2380
+ json,
2381
+ propertyNames
2382
+ )
2383
+ }
2384
+
2385
+ provide { (||>) }
2386
+ }