@kexhq/kex 0.4.0-beta.3 → 0.4.0-beta.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/dist/kex_repl_wasm.data +1101 -659
- package/dist/kex_repl_wasm.js +1 -1
- package/dist/kex_repl_wasm.wasm +0 -0
- package/package.json +1 -1
package/dist/kex_repl_wasm.data
CHANGED
|
@@ -171,6 +171,50 @@ make [A], implement: Monoid do
|
|
|
171
171
|
# groups.reduce([]) { |acc, g| acc.combine(g) }
|
|
172
172
|
let combine(other: This) -> This = this + other
|
|
173
173
|
end
|
|
174
|
+
# Atoms: the +:name+ values. An atom is its own name — two atoms with the same
|
|
175
|
+
# text are the same value, and comparing them is as cheap as comparing numbers.
|
|
176
|
+
#
|
|
177
|
+
# :ok
|
|
178
|
+
# :b@localhost # an interior `@` is part of a bare atom
|
|
179
|
+
# :"b@host.example.com" # the quoted form spells any text
|
|
180
|
+
#
|
|
181
|
+
# Most atoms are written in the source. From text:
|
|
182
|
+
#
|
|
183
|
+
# "hello".as(Atom) # at compile time, from a literal
|
|
184
|
+
# Atom.from(text) # at run time, making the atom — a node name, say
|
|
185
|
+
# text.to(Atom) # at run time, only an atom that already exists: Atom?
|
|
186
|
+
#
|
|
187
|
+
# and +string+ gives the text back.
|
|
188
|
+
module Atom do
|
|
189
|
+
# Returns the atom whose name is +text+.
|
|
190
|
+
#
|
|
191
|
+
# On the BEAM atoms are never freed, and a node holds at most about a
|
|
192
|
+
# million. Build atoms from a bounded set of names — node names, config
|
|
193
|
+
# keys — never from untrusted input; +text.to(Atom)+ is the safe form for
|
|
194
|
+
# that, since it only finds atoms that already exist.
|
|
195
|
+
#
|
|
196
|
+
# @param text [String] the atom's name
|
|
197
|
+
# @return [Atom] the atom
|
|
198
|
+
#
|
|
199
|
+
# @example
|
|
200
|
+
# Atom.from("ok") == :ok # => true
|
|
201
|
+
# Atom.from("app@${host}") # => :"app@myhost"
|
|
202
|
+
from : String -> Atom
|
|
203
|
+
let from(text) = Kex.Intrinsic.Atom.from(text)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
make Atom do
|
|
207
|
+
# Returns the atom's name as text, without the leading colon. Total, like
|
|
208
|
+
# +Char.string+: every atom has a name.
|
|
209
|
+
#
|
|
210
|
+
# @return [String] the name
|
|
211
|
+
#
|
|
212
|
+
# @example
|
|
213
|
+
# :ok.string # => "ok"
|
|
214
|
+
# :"b@host.example.com".string # => "b@host.example.com"
|
|
215
|
+
string :> String
|
|
216
|
+
let string = Kex.Intrinsic.Atom.name(this)
|
|
217
|
+
end
|
|
174
218
|
# An opaque, immutable sequence of bytes. A +Binary+ never implicitly becomes
|
|
175
219
|
# text.
|
|
176
220
|
#
|
|
@@ -312,6 +356,31 @@ make Binary, implement: Showable, Inspectable do
|
|
|
312
356
|
# data.at(-1) # => None
|
|
313
357
|
let at(index: Integer) -> Byte? = Kex.Intrinsic.Binary.at(this, index)
|
|
314
358
|
|
|
359
|
+
# The byte at +index+, counting from zero. The same as +at+, matching the
|
|
360
|
+
# +get+ that +List+ and +String+ carry.
|
|
361
|
+
#
|
|
362
|
+
# @param index [Integer] the position to read
|
|
363
|
+
# @return [Byte?] the byte, or +None+ when the index is outside the binary
|
|
364
|
+
#
|
|
365
|
+
# @example
|
|
366
|
+
# Binary.fromBytes([104, 105]).get(1) # => Just(105)
|
|
367
|
+
# Binary.fromBytes([104, 105]).get(9) # => None
|
|
368
|
+
get :> Integer -> Byte?
|
|
369
|
+
let get(index) = this.at(index)
|
|
370
|
+
|
|
371
|
+
# The byte at +index+, or +default+ when the index is outside the binary.
|
|
372
|
+
# The total form of +get+: the byte comes back unwrapped.
|
|
373
|
+
#
|
|
374
|
+
# @param index [Integer] the position to read
|
|
375
|
+
# @param default [Byte] the value to use when out of range
|
|
376
|
+
# @return [Byte] the byte, or +default+
|
|
377
|
+
#
|
|
378
|
+
# @example
|
|
379
|
+
# Binary.fromBytes([104, 105]).get(0, 0) # => 104
|
|
380
|
+
# Binary.fromBytes([104, 105]).get(9, 0) # => 0
|
|
381
|
+
get :> Integer -> Byte -> Byte
|
|
382
|
+
let get(index, default) = this.at(index).or(default)
|
|
383
|
+
|
|
315
384
|
# The first +count+ bytes.
|
|
316
385
|
#
|
|
317
386
|
# Clamped at both ends, like the list operations: a +count+ of zero or less
|
|
@@ -1108,11 +1177,7 @@ module Console do
|
|
|
1108
1177
|
enabled? : Bool
|
|
1109
1178
|
let enabled? = Kex.Intrinsic.Console.enabled?
|
|
1110
1179
|
end
|
|
1111
|
-
#
|
|
1112
|
-
#
|
|
1113
|
-
# +Retry.run+ executes the block immediately. An +Ok+ stops the run; an
|
|
1114
|
-
# +Error+ schedules another attempt. When attempts or total sleep run out,
|
|
1115
|
-
# the last error is returned unchanged. Runtime faults are not caught.
|
|
1180
|
+
# Retry an operation after it returns an error.
|
|
1116
1181
|
#
|
|
1117
1182
|
# @example Retry a request and handle the final result
|
|
1118
1183
|
# using Control.Retry
|
|
@@ -1126,22 +1191,116 @@ end
|
|
|
1126
1191
|
# Error(error) => IO.printLine(error.message)
|
|
1127
1192
|
# end
|
|
1128
1193
|
#
|
|
1129
|
-
#
|
|
1130
|
-
#
|
|
1131
|
-
#
|
|
1132
|
-
#
|
|
1133
|
-
#
|
|
1134
|
-
#
|
|
1194
|
+
# @example Fixed delays: three waits of 250 milliseconds
|
|
1195
|
+
# let schedule = Retry.Schedule {
|
|
1196
|
+
# attempts: 4, delay: 250.milliseconds, backoff: 1.0,
|
|
1197
|
+
# maximumDelay: 250.milliseconds, jitter: 0.0
|
|
1198
|
+
# }
|
|
1199
|
+
# Retry.run(schedule: schedule) do
|
|
1200
|
+
# Error("not ready")
|
|
1201
|
+
# end
|
|
1202
|
+
# # => Error("not ready"), after four calls and 750 ms of sleep
|
|
1203
|
+
#
|
|
1204
|
+
# @example Exponential delays: 200, 400, 800, 1600 milliseconds
|
|
1205
|
+
# let schedule = Retry.Schedule {
|
|
1206
|
+
# attempts: 5, delay: 200.milliseconds, backoff: 2.0,
|
|
1207
|
+
# maximumDelay: 5.seconds, jitter: 0.0
|
|
1208
|
+
# }
|
|
1209
|
+
# Retry.run(schedule: schedule) do
|
|
1210
|
+
# Error("busy")
|
|
1211
|
+
# end
|
|
1212
|
+
# # => Error("busy"), after five calls and three seconds of sleep
|
|
1213
|
+
#
|
|
1214
|
+
# @example Capped delays: 500 ms, 1 s, 2 s, 2 s, 2 s
|
|
1215
|
+
# Retry.Schedule {
|
|
1216
|
+
# attempts: 6, delay: 500.milliseconds, backoff: 2.0,
|
|
1217
|
+
# maximumDelay: 2.seconds, jitter: 0.0
|
|
1218
|
+
# }
|
|
1219
|
+
#
|
|
1220
|
+
# @example Use one schedule for two requests
|
|
1221
|
+
# using Net.HTTP
|
|
1222
|
+
# let schedule = Retry.Schedule { attempts: 5 }
|
|
1223
|
+
# let inventory = Retry.run(schedule: schedule) do
|
|
1224
|
+
# HTTP.get("https://api.example.com/inventory")
|
|
1225
|
+
# end
|
|
1226
|
+
# let orders = Retry.run(schedule: schedule) do
|
|
1227
|
+
# HTTP.get("https://api.example.com/orders")
|
|
1228
|
+
# end
|
|
1229
|
+
# # Each request gets up to five attempts, independently.
|
|
1230
|
+
#
|
|
1231
|
+
# @example Set retry options directly
|
|
1232
|
+
# Retry.run(attempts: 5, delay: 200.milliseconds, jitter: 0.0) do
|
|
1233
|
+
# Ok("ready")
|
|
1234
|
+
# end
|
|
1235
|
+
# # => Ok("ready")
|
|
1236
|
+
#
|
|
1237
|
+
# @example Limit the time spent sleeping
|
|
1238
|
+
# Retry.run(
|
|
1239
|
+
# attempts: 5, delay: 1.seconds, backoff: 2.0,
|
|
1240
|
+
# jitter: 0.0, maximumTotalDelay: Just(2.seconds)
|
|
1241
|
+
# ) do
|
|
1242
|
+
# Error("busy")
|
|
1243
|
+
# end
|
|
1244
|
+
# # => Error("busy"), two calls and one second of sleep
|
|
1245
|
+
#
|
|
1246
|
+
# @example Test jitter without waiting
|
|
1247
|
+
# Retry.run(
|
|
1248
|
+
# attempts: 2, delay: 4.seconds, jitter: 0.25,
|
|
1249
|
+
# sleeper: { |wait| Assert.equal(wait, 3.seconds) },
|
|
1250
|
+
# random: { 0.0 }
|
|
1251
|
+
# ) do
|
|
1252
|
+
# Error("temporary")
|
|
1253
|
+
# end
|
|
1254
|
+
# # => Error("temporary"), two calls and one fake sleep
|
|
1255
|
+
#
|
|
1256
|
+
# @example Report progress
|
|
1257
|
+
# using Net.HTTP
|
|
1258
|
+
# Retry.run(attempts: 5, onRetry: { |info|
|
|
1259
|
+
# let progress = "retrying ${info.nextAttempt}/${info.maximumAttempts}"
|
|
1260
|
+
# IO.printLine("${progress} in ${info.delay.seconds} seconds")
|
|
1261
|
+
# }) do
|
|
1262
|
+
# HTTP.get("https://api.example.com/inventory")
|
|
1263
|
+
# end
|
|
1264
|
+
#
|
|
1265
|
+
# @example Retry temporary HTTP failures and selected statuses
|
|
1266
|
+
# using Net
|
|
1267
|
+
# using Net.HTTP
|
|
1268
|
+
# let schedule = Retry.Schedule { attempts: 5 }
|
|
1269
|
+
# let outcome = Retry.run(schedule: schedule) do
|
|
1270
|
+
# let result = HTTP.get("https://api.example.com/inventory")
|
|
1271
|
+
# match result do
|
|
1272
|
+
# Error(error) => if error.kind == Timeout || error.kind == Connect
|
|
1273
|
+
# Retry.again(result)
|
|
1274
|
+
# else
|
|
1275
|
+
# Retry.done(result)
|
|
1276
|
+
# end
|
|
1277
|
+
# Ok(response) => if [429, 502, 503, 504].contains?(response.status.code)
|
|
1278
|
+
# Retry.again(result)
|
|
1279
|
+
# else
|
|
1280
|
+
# Retry.done(result)
|
|
1281
|
+
# end
|
|
1282
|
+
# end
|
|
1283
|
+
# end
|
|
1284
|
+
# match outcome do
|
|
1285
|
+
# Done(result) => IO.printLine(result)
|
|
1286
|
+
# Again(last) => IO.printLine("retry budget exhausted: ${last}")
|
|
1287
|
+
# end
|
|
1135
1288
|
module Control.Retry
|
|
1136
1289
|
|
|
1137
|
-
#
|
|
1290
|
+
# +run+ calls the block immediately. +Ok+ stops; +Error+ waits and retries.
|
|
1291
|
+
# When the schedule runs out, it returns the last error unchanged.
|
|
1292
|
+
# Use +again+ and +done+ when your application needs to choose what to retry.
|
|
1293
|
+
#
|
|
1294
|
+
# HTTP 429 and 503 responses are +Ok(response)+, so retrying those statuses
|
|
1295
|
+
# requires an explicit decision. For writes, make sure repeating the request
|
|
1296
|
+
# is safe, for example by using an idempotency key. Set request timeouts
|
|
1297
|
+
# separately: the schedule limits sleep, not time spent inside the operation.
|
|
1138
1298
|
module Retry do
|
|
1139
|
-
#
|
|
1299
|
+
# Timing and attempt limits for +run+.
|
|
1140
1300
|
#
|
|
1141
|
-
#
|
|
1142
|
-
#
|
|
1143
|
-
#
|
|
1144
|
-
# each run starts a fresh attempt count and delay sequence.
|
|
1301
|
+
# Defaults: three attempts, starting with a 100 ms wait, doubling up to
|
|
1302
|
+
# 5 seconds, with 20% jitter. All fields are optional.
|
|
1303
|
+
# A schedule holds settings. Reusing it starts each run from attempt one.
|
|
1145
1304
|
#
|
|
1146
1305
|
# * +attempts+: maximum executions, including the first; default 3.
|
|
1147
1306
|
# * +delay+: initial base wait; default 100 milliseconds.
|
|
@@ -1152,42 +1311,6 @@ module Retry do
|
|
|
1152
1311
|
# Operation execution time is excluded. A wait that exceeds the remaining
|
|
1153
1312
|
# allowance ends the run without sleeping or calling the operation again.
|
|
1154
1313
|
#
|
|
1155
|
-
# @example Fixed delays: three waits of 250 milliseconds
|
|
1156
|
-
# let schedule = Retry.Schedule {
|
|
1157
|
-
# attempts: 4, delay: 250.milliseconds, backoff: 1.0,
|
|
1158
|
-
# maximumDelay: 250.milliseconds, jitter: 0.0
|
|
1159
|
-
# }
|
|
1160
|
-
# Retry.run(schedule: schedule) do
|
|
1161
|
-
# Error("not ready")
|
|
1162
|
-
# end
|
|
1163
|
-
# # => Error("not ready"), after four calls and 750 ms of sleep
|
|
1164
|
-
#
|
|
1165
|
-
# @example Exponential delays: 200, 400, 800, 1600 milliseconds
|
|
1166
|
-
# let schedule = Retry.Schedule {
|
|
1167
|
-
# attempts: 5, delay: 200.milliseconds, backoff: 2.0,
|
|
1168
|
-
# maximumDelay: 5.seconds, jitter: 0.0
|
|
1169
|
-
# }
|
|
1170
|
-
# Retry.run(schedule: schedule) do
|
|
1171
|
-
# Ok("ready")
|
|
1172
|
-
# end
|
|
1173
|
-
# # => Ok("ready"), immediately, without sleeping
|
|
1174
|
-
#
|
|
1175
|
-
# @example Capped delays: 500 ms, 1 s, 2 s, 2 s, 2 s
|
|
1176
|
-
# Retry.Schedule {
|
|
1177
|
-
# attempts: 6, delay: 500.milliseconds, backoff: 2.0,
|
|
1178
|
-
# maximumDelay: 2.seconds, jitter: 0.0
|
|
1179
|
-
# }
|
|
1180
|
-
#
|
|
1181
|
-
# @example Share settings, not an attempt budget
|
|
1182
|
-
# using Net.HTTP
|
|
1183
|
-
# let schedule = Retry.Schedule { attempts: 5 }
|
|
1184
|
-
# let inventory = Retry.run(schedule: schedule) do
|
|
1185
|
-
# HTTP.get("https://api.example.com/inventory")
|
|
1186
|
-
# end
|
|
1187
|
-
# let orders = Retry.run(schedule: schedule) do
|
|
1188
|
-
# HTTP.get("https://api.example.com/orders")
|
|
1189
|
-
# end
|
|
1190
|
-
# # Each request gets up to five attempts, independently.
|
|
1191
1314
|
record Schedule do
|
|
1192
1315
|
# Maximum executions including the initial call. Default: 3.
|
|
1193
1316
|
# Values below 1 are treated as 1: the initial call always happens.
|
|
@@ -1211,36 +1334,31 @@ module Retry do
|
|
|
1211
1334
|
maximumTotalDelay : Duration? = None
|
|
1212
1335
|
end
|
|
1213
1336
|
|
|
1214
|
-
#
|
|
1337
|
+
# Whether to retry, together with the last result.
|
|
1215
1338
|
# +Done(value)+ means the block stopped deliberately. +Again(value)+
|
|
1216
1339
|
# returned by +run+ means the schedule was exhausted before completion.
|
|
1217
|
-
#
|
|
1340
|
+
# The value inside can be any application result, including an +Error+.
|
|
1218
1341
|
type Decision<X> = Again(X) | Done(X)
|
|
1219
1342
|
|
|
1220
|
-
#
|
|
1343
|
+
# Retry this operation if the schedule has room for another attempt.
|
|
1221
1344
|
#
|
|
1222
1345
|
# @param value [X] the last outcome, retained if the schedule is exhausted
|
|
1223
1346
|
# @return [Decision<X>] +Again(value)+
|
|
1224
|
-
# @example Polling an application job
|
|
1225
|
-
# Retry.again("pending")
|
|
1226
1347
|
again : X -> Decision<X>
|
|
1227
1348
|
let again(value) = Again(value)
|
|
1228
1349
|
|
|
1229
|
-
#
|
|
1350
|
+
# Stop now. The supplied value may be a success or a permanent failure.
|
|
1230
1351
|
#
|
|
1231
1352
|
# @param value [X] the final application outcome
|
|
1232
1353
|
# @return [Decision<X>] +Done(value)+
|
|
1233
|
-
# @example Stop on invalid credentials without retrying
|
|
1234
|
-
# Retry.done(Error("invalid credentials"))
|
|
1235
1354
|
done : X -> Decision<X>
|
|
1236
1355
|
let done(value) = Done(value)
|
|
1237
1356
|
|
|
1238
|
-
#
|
|
1357
|
+
# Progress passed to +onRetry+ before the next wait.
|
|
1239
1358
|
#
|
|
1240
|
-
#
|
|
1241
|
-
# the
|
|
1242
|
-
#
|
|
1243
|
-
# attempts. Durations describe scheduled sleep, not wall-clock elapsed time.
|
|
1359
|
+
# The callback runs only when another attempt is allowed. It does not run
|
|
1360
|
+
# for the first call, a successful result, +done+, or exhaustion.
|
|
1361
|
+
# Durations count scheduled sleep; they exclude time spent in the operation.
|
|
1244
1362
|
#
|
|
1245
1363
|
# * +attempt+: the just-completed attempt, starting at 1.
|
|
1246
1364
|
# * +nextAttempt+: the attempt that follows the upcoming wait.
|
|
@@ -1266,23 +1384,25 @@ module Retry do
|
|
|
1266
1384
|
result : X
|
|
1267
1385
|
end
|
|
1268
1386
|
|
|
1269
|
-
#
|
|
1387
|
+
# Call the block until it succeeds, returns +done+, or runs out of attempts or sleep time.
|
|
1270
1388
|
#
|
|
1271
1389
|
# The first attempt is immediate. Every later attempt follows one sleep.
|
|
1272
1390
|
# There is no sleep after success or the final error. Named timing options
|
|
1273
1391
|
# override the corresponding schedule field for this execution only.
|
|
1274
1392
|
#
|
|
1275
|
-
#
|
|
1276
|
-
#
|
|
1277
|
-
# returns +Decision<X>+: +Done+ stops, +Again+ retries, and exhaustion
|
|
1278
|
-
# returns the last +Again+. Match the returned decision to distinguish
|
|
1279
|
-
# completion from exhaustion. Use one form consistently within a block.
|
|
1393
|
+
# Return a +Result+ from the block for automatic retries: +Ok+ stops and
|
|
1394
|
+
# +Error+ retries. If no more retries are allowed, +run+ returns the last error.
|
|
1280
1395
|
#
|
|
1281
|
-
#
|
|
1396
|
+
# Return +done(value)+ or +again(value)+ to make the decision yourself.
|
|
1397
|
+
# The final +Done+ means the block chose to stop; +Again+ means it wanted
|
|
1398
|
+
# another try but the schedule ran out. Use one return form throughout the
|
|
1399
|
+
# block. Runtime faults propagate; they are not retried.
|
|
1400
|
+
#
|
|
1401
|
+
# Out-of-range settings are adjusted: attempts below one become one, negative
|
|
1282
1402
|
# durations become zero, backoff below one becomes one, jitter and random
|
|
1283
1403
|
# samples are clamped to 0..1. Non-finite Float settings are unsupported.
|
|
1284
1404
|
#
|
|
1285
|
-
# @param operation [Block<X>]
|
|
1405
|
+
# @param operation [Block<X>] called again for each attempt; returns Result or Decision
|
|
1286
1406
|
# @param schedule [Schedule] reusable settings; defaults to +Schedule {}+
|
|
1287
1407
|
# @param attempts [Integer] total attempts; defaults to the schedule field
|
|
1288
1408
|
# @param delay [Duration] initial wait; defaults to the schedule field
|
|
@@ -1293,65 +1413,7 @@ module Retry do
|
|
|
1293
1413
|
# @param sleeper [Duration -> Void] defaults to real +Task.sleep+
|
|
1294
1414
|
# @param random [Block<Float>] defaults to secure backend sampling in 0..1
|
|
1295
1415
|
# @param onRetry [Info<X> -> Void] reports an allowed retry; defaults to no action
|
|
1296
|
-
# @return [X] first Ok/Done or last Error/Again
|
|
1297
|
-
#
|
|
1298
|
-
# @example Named options without constructing a schedule
|
|
1299
|
-
# Retry.run(attempts: 5, delay: 200.milliseconds, jitter: 0.0) do
|
|
1300
|
-
# Ok("ready")
|
|
1301
|
-
# end
|
|
1302
|
-
# # => Ok("ready")
|
|
1303
|
-
#
|
|
1304
|
-
# @example Stop before a wait exceeds the total sleep budget
|
|
1305
|
-
# Retry.run(
|
|
1306
|
-
# attempts: 5, delay: 1.seconds, backoff: 2.0,
|
|
1307
|
-
# jitter: 0.0, maximumTotalDelay: Just(2.seconds)
|
|
1308
|
-
# ) do
|
|
1309
|
-
# Error("busy")
|
|
1310
|
-
# end
|
|
1311
|
-
# # => Error("busy"), two calls and one second of sleep
|
|
1312
|
-
#
|
|
1313
|
-
# @example Test jitter without waiting
|
|
1314
|
-
# Retry.run(
|
|
1315
|
-
# attempts: 2, delay: 4.seconds, jitter: 0.25,
|
|
1316
|
-
# sleeper: { |wait| Assert.equal(wait, 3.seconds) },
|
|
1317
|
-
# random: { 0.0 }
|
|
1318
|
-
# ) do
|
|
1319
|
-
# Error("temporary")
|
|
1320
|
-
# end
|
|
1321
|
-
# # => Error("temporary"), two calls and one fake sleep
|
|
1322
|
-
#
|
|
1323
|
-
# @example Report progress to a user or log
|
|
1324
|
-
# using Net.HTTP
|
|
1325
|
-
# Retry.run(attempts: 5, onRetry: { |info|
|
|
1326
|
-
# let progress = "retrying ${info.nextAttempt}/${info.maximumAttempts}"
|
|
1327
|
-
# IO.printLine("${progress} in ${info.delay.seconds} seconds")
|
|
1328
|
-
# }) do
|
|
1329
|
-
# HTTP.get("https://api.example.com/inventory")
|
|
1330
|
-
# end
|
|
1331
|
-
#
|
|
1332
|
-
# @example Retry temporary HTTP failures and selected statuses
|
|
1333
|
-
# using Net
|
|
1334
|
-
# using Net.HTTP
|
|
1335
|
-
# let schedule = Retry.Schedule { attempts: 5 }
|
|
1336
|
-
# let outcome = Retry.run(schedule: schedule) do
|
|
1337
|
-
# let result = HTTP.get("https://api.example.com/inventory")
|
|
1338
|
-
# match result do
|
|
1339
|
-
# Error(error) => if error.kind == Timeout || error.kind == Connect
|
|
1340
|
-
# Retry.again(result)
|
|
1341
|
-
# else
|
|
1342
|
-
# Retry.done(result)
|
|
1343
|
-
# end
|
|
1344
|
-
# Ok(response) => if [429, 502, 503, 504].contains?(response.status.code)
|
|
1345
|
-
# Retry.again(result)
|
|
1346
|
-
# else
|
|
1347
|
-
# Retry.done(result)
|
|
1348
|
-
# end
|
|
1349
|
-
# end
|
|
1350
|
-
# end
|
|
1351
|
-
# match outcome do
|
|
1352
|
-
# Done(result) => IO.printLine(result)
|
|
1353
|
-
# Again(last) => IO.printLine("retry budget exhausted: ${last}")
|
|
1354
|
-
# end
|
|
1416
|
+
# @return [X] first Ok/Done, or last Error/Again when retries run out
|
|
1355
1417
|
#
|
|
1356
1418
|
# +Done(Error(...))+ means the application stopped on a permanent error.
|
|
1357
1419
|
# +Again(Ok(response))+ means an unacceptable HTTP status persisted until
|
|
@@ -1422,12 +1484,12 @@ module Retry do
|
|
|
1422
1484
|
return result
|
|
1423
1485
|
end
|
|
1424
1486
|
let fraction = clamp(schedule.jitter, 0.0, 1.0)
|
|
1425
|
-
let sample =
|
|
1487
|
+
let sample = fraction == 0.0 then 0.5 else clamp(random(), 0.0, 1.0)
|
|
1426
1488
|
let sleepSeconds = boundedDelay(schedule, base * (1.0 + (sample * 2.0 - 1.0) * fraction))
|
|
1427
1489
|
let nextElapsed = elapsed + sleepSeconds
|
|
1428
1490
|
let allowed = match schedule.maximumTotalDelay do
|
|
1429
1491
|
None => true
|
|
1430
|
-
Just(limit) => nextElapsed <= (
|
|
1492
|
+
Just(limit) => nextElapsed <= (limit.seconds < 0.0 then 0.0 else limit.seconds)
|
|
1431
1493
|
end
|
|
1432
1494
|
if !allowed
|
|
1433
1495
|
return result
|
|
@@ -1442,7 +1504,7 @@ module Retry do
|
|
|
1442
1504
|
result: result
|
|
1443
1505
|
})
|
|
1444
1506
|
sleeper(Duration { seconds: sleepSeconds })
|
|
1445
|
-
let factor =
|
|
1507
|
+
let factor = schedule.backoff < 1.0 then 1.0 else schedule.backoff
|
|
1446
1508
|
let nextBase = boundedDelay(schedule, base * factor)
|
|
1447
1509
|
attempt(schedule, sleeper, random, onRetry, operation, number + 1, nextBase, nextElapsed)
|
|
1448
1510
|
end
|
|
@@ -3610,7 +3672,7 @@ make FileHandle<CanRead, W>, implement: Readable do
|
|
|
3610
3672
|
# condition. The same operation as +readLine+, under the name +IO.getLine+
|
|
3611
3673
|
# uses.
|
|
3612
3674
|
#
|
|
3613
|
-
# @return [String
|
|
3675
|
+
# @return [Result<String?, ReadError>] the next line, or +None+ at end of file
|
|
3614
3676
|
#
|
|
3615
3677
|
# @example Walking a file line by line
|
|
3616
3678
|
# foul echo(handle: FileHandle<CanRead, W>) -> Void do
|
|
@@ -3629,7 +3691,7 @@ make FileHandle<CanRead, W>, implement: Readable do
|
|
|
3629
3691
|
#
|
|
3630
3692
|
# Answers +None+ at end of file.
|
|
3631
3693
|
#
|
|
3632
|
-
# @return [String
|
|
3694
|
+
# @return [Result<String?, ReadError>] the next character, or +None+ at end of file
|
|
3633
3695
|
#
|
|
3634
3696
|
# @example
|
|
3635
3697
|
# let firstChar = handle.get.or("")
|
|
@@ -3639,7 +3701,7 @@ make FileHandle<CanRead, W>, implement: Readable do
|
|
|
3639
3701
|
# Reads the next line from the handle, without its newline. The same as
|
|
3640
3702
|
# +getLine+, named for reading from a file rather than from a console.
|
|
3641
3703
|
#
|
|
3642
|
-
# @return [String
|
|
3704
|
+
# @return [Result<String?, ReadError>] the next line, or +None+ at end of file
|
|
3643
3705
|
#
|
|
3644
3706
|
# @example
|
|
3645
3707
|
# let header = handle.readLine.or("")
|
|
@@ -3654,7 +3716,7 @@ make FileHandle<CanRead, W>, implement: Readable do
|
|
|
3654
3716
|
# Reads from the current position, so calling it after a +readLine+ gives
|
|
3655
3717
|
# the rest of the file rather than the whole of it.
|
|
3656
3718
|
#
|
|
3657
|
-
# @return [String
|
|
3719
|
+
# @return [Result<String, ReadError>] the remaining contents
|
|
3658
3720
|
#
|
|
3659
3721
|
# @example
|
|
3660
3722
|
# let body = handle.read.or("")
|
|
@@ -3670,7 +3732,7 @@ make FileHandle<CanRead, W>, implement: Readable do
|
|
|
3670
3732
|
# Unlike +read+, this accepts arbitrary binary data and cannot fail because
|
|
3671
3733
|
# the input is not valid UTF-8. It starts at the handle's current position.
|
|
3672
3734
|
#
|
|
3673
|
-
# @return [Binary] the remaining bytes
|
|
3735
|
+
# @return [Result<Binary, ReadError>] the remaining bytes
|
|
3674
3736
|
#
|
|
3675
3737
|
# @example Reading a file with an unknown encoding
|
|
3676
3738
|
# let payload = handle.readBytes.try
|
|
@@ -3884,6 +3946,24 @@ module FS do
|
|
|
3884
3946
|
| ReadFailed(FilePath)
|
|
3885
3947
|
| InvalidUtf8(FilePath, Integer)
|
|
3886
3948
|
|
|
3949
|
+
# What a path names, as +FS.File.info+ reports it: a regular +:file+, a
|
|
3950
|
+
# +:directory+, a +:symlink+ (the link itself, never what it points at), or
|
|
3951
|
+
# +:other+ for devices, sockets and pipes.
|
|
3952
|
+
type FileKind = :file | :directory | :symlink | :other
|
|
3953
|
+
|
|
3954
|
+
# What one +stat+ of a path says about it.
|
|
3955
|
+
record FileInfo do
|
|
3956
|
+
# Which kind of entry the path names.
|
|
3957
|
+
kind : FileKind
|
|
3958
|
+
|
|
3959
|
+
# The size in bytes. For a symlink, the length of the path it holds.
|
|
3960
|
+
size : Integer
|
|
3961
|
+
|
|
3962
|
+
# When the contents last changed, at UTC, to the whole second: the
|
|
3963
|
+
# precision the BEAM reports.
|
|
3964
|
+
modified : DateTime
|
|
3965
|
+
end
|
|
3966
|
+
|
|
3887
3967
|
# Reading and writing files.
|
|
3888
3968
|
#
|
|
3889
3969
|
# A capability: every member reaches the real filesystem, so a test can
|
|
@@ -3906,7 +3986,7 @@ module FS do
|
|
|
3906
3986
|
#
|
|
3907
3987
|
# @param path [FilePath] the file to open
|
|
3908
3988
|
# @param mode [FileModes] +Read+, +Write+, +Append+ or +ReadWrite+
|
|
3909
|
-
# @return [Result<FileHandle, FileError>] the handle, or +OpenFailed+
|
|
3989
|
+
# @return [Result<FileHandle<CanRead, CannotWrite>, FileError>] the handle, or +OpenFailed+
|
|
3910
3990
|
#
|
|
3911
3991
|
# @example Reading a file line by line
|
|
3912
3992
|
# match FS.File.open("log.txt", Read) do
|
|
@@ -4297,6 +4377,32 @@ module FS do
|
|
|
4297
4377
|
# FS.File.symlink?("current") # => true
|
|
4298
4378
|
symlink? : FilePath -> Bool
|
|
4299
4379
|
foul symlink?(path) = Kex.Intrinsic.File.symlink?(path)
|
|
4380
|
+
|
|
4381
|
+
# The kind, size and modification time of +path+, read in one +lstat+.
|
|
4382
|
+
#
|
|
4383
|
+
# One call answers what +file?+, +directory?+, +symlink?+ and +size+
|
|
4384
|
+
# would otherwise ask separately, which is what walking a tree or
|
|
4385
|
+
# fingerprinting it for changes wants. A symlink is described as itself,
|
|
4386
|
+
# not followed.
|
|
4387
|
+
#
|
|
4388
|
+
# @param path [FilePath] the path to inspect
|
|
4389
|
+
# @return [Result<FileInfo, FileError>] what the path names, or
|
|
4390
|
+
# +ReadFailed+ when it cannot be read
|
|
4391
|
+
#
|
|
4392
|
+
# @example
|
|
4393
|
+
# FS.File.info("hello.txt").try.size # => 6
|
|
4394
|
+
# FS.File.info("src").try.kind # => :directory
|
|
4395
|
+
# FS.File.info("nowhere") # => Error(ReadFailed("nowhere"))
|
|
4396
|
+
#
|
|
4397
|
+
# @example Noticing that a file changed
|
|
4398
|
+
# let before = FS.File.info(path).try
|
|
4399
|
+
# ...
|
|
4400
|
+
# let changed = FS.File.info(path).try != before
|
|
4401
|
+
info : FilePath -> Result<FileInfo, FileError>
|
|
4402
|
+
foul info(path) = match Kex.Intrinsic.File.info(path) do
|
|
4403
|
+
Just((kind, size, modified)) => Ok(FileInfo { kind: kind, size: size, modified: DateTime.fromEpochSeconds(modified) })
|
|
4404
|
+
None => Error(ReadFailed(path))
|
|
4405
|
+
end
|
|
4300
4406
|
end
|
|
4301
4407
|
|
|
4302
4408
|
# Path arithmetic: joining, splitting, normalising and comparing paths.
|
|
@@ -5144,12 +5250,12 @@ private do
|
|
|
5144
5250
|
|
|
5145
5251
|
let allowComments = Kex.Intrinsic.Map.getWithDefault(options, allowCommentsKey(), false)
|
|
5146
5252
|
# On BEAM, OTP's own decoder builds the same value in a fraction of the
|
|
5147
|
-
# time (kexhq/kex#333)
|
|
5148
|
-
#
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5253
|
+
# time (kexhq/kex#333), JSONC included once its comments are blanked out
|
|
5254
|
+
# (kexhq/kex#405). It answers None for anything it will not vouch for —
|
|
5255
|
+
# invalid input, the tree-walker — and this parser runs.
|
|
5256
|
+
let fast = allowComments then Kex.Intrinsic.Json.decodeCommented(text) else Kex.Intrinsic.Json.decode(text)
|
|
5257
|
+
if let Just(value) = fast
|
|
5258
|
+
return Ok(value)
|
|
5153
5259
|
end
|
|
5154
5260
|
let cursor = skipIgnored(Input { input: text }, allowComments).try
|
|
5155
5261
|
let (value, afterValue) = parseValue(cursor, allowComments).try
|
|
@@ -5633,20 +5739,58 @@ end
|
|
|
5633
5739
|
# The running toolchain, which backend, which version, which features.
|
|
5634
5740
|
#
|
|
5635
5741
|
# Kex.BACKEND # => Interpreter
|
|
5636
|
-
# Kex.
|
|
5637
|
-
# Kex.
|
|
5742
|
+
# Kex.BACKEND.compiled? # => false
|
|
5743
|
+
# Kex.VERSION.release # => "0.4.0"
|
|
5744
|
+
# Kex.Feature.has?(Kex.FileSystem) # => true
|
|
5638
5745
|
module Kex do
|
|
5639
5746
|
# Which backend is executing the program: the tree-walking +Interpreter+, or
|
|
5640
5747
|
# the +Beam+ virtual machine.
|
|
5641
5748
|
type Backend = Interpreter | Beam
|
|
5642
5749
|
|
|
5750
|
+
make Backend do
|
|
5751
|
+
# Whether this is the tree-walking interpreter.
|
|
5752
|
+
#
|
|
5753
|
+
# @return [Bool] +true+ for +Interpreter+
|
|
5754
|
+
#
|
|
5755
|
+
# @example
|
|
5756
|
+
# Kex.BACKEND.interpreted? # => true under `kex -R file.kex`
|
|
5757
|
+
interpreted? :> Bool
|
|
5758
|
+
let interpreted? = this == Interpreter
|
|
5759
|
+
|
|
5760
|
+
# Whether this backend runs compiled code: today, the BEAM.
|
|
5761
|
+
#
|
|
5762
|
+
# @return [Bool] +true+ for every backend but the interpreter
|
|
5763
|
+
#
|
|
5764
|
+
# @example
|
|
5765
|
+
# Kex.BACKEND.compiled? # => true under `kex file.kex`
|
|
5766
|
+
compiled? :> Bool
|
|
5767
|
+
let compiled? = this != Interpreter
|
|
5768
|
+
|
|
5769
|
+
# Whether this is the BEAM virtual machine. Processes, the web server
|
|
5770
|
+
# and clustering need it.
|
|
5771
|
+
#
|
|
5772
|
+
# @return [Bool] +true+ for +Beam+
|
|
5773
|
+
#
|
|
5774
|
+
# @example
|
|
5775
|
+
# Kex.BACKEND.beam? # => true under `kex file.kex`
|
|
5776
|
+
beam? :> Bool
|
|
5777
|
+
let beam? = this == Beam
|
|
5778
|
+
end
|
|
5779
|
+
|
|
5643
5780
|
# An optional capability a build may or may not include. Ask about one with
|
|
5644
5781
|
# +Kex.Feature.has?+ before relying on it.
|
|
5645
|
-
|
|
5782
|
+
#
|
|
5783
|
+
# +FileSystem+: the program can read and write the host's files (+FS+).
|
|
5784
|
+
# +ExternalPrograms+: it can run other programs (+Process.run+,
|
|
5785
|
+
# +Process.stream+), which the browser build cannot.
|
|
5786
|
+
#
|
|
5787
|
+
# Kex's own processes (+spawn+, +receive+) are not optional: every backend
|
|
5788
|
+
# has them. Networking has its finer-grained report, +Net.Support.current+.
|
|
5789
|
+
type Feature = FileSystem | ExternalPrograms
|
|
5646
5790
|
|
|
5647
5791
|
# Which backend is executing this program.
|
|
5648
5792
|
#
|
|
5649
|
-
# +interpreted?+ and +
|
|
5793
|
+
# Its +interpreted?+, +compiled?+ and +beam?+ are the readable way to ask.
|
|
5650
5794
|
#
|
|
5651
5795
|
# @return [Backend] the running backend
|
|
5652
5796
|
#
|
|
@@ -5655,131 +5799,194 @@ module Kex do
|
|
|
5655
5799
|
BACKEND : Backend
|
|
5656
5800
|
let BACKEND = Kex.Intrinsic.Kex.backend()
|
|
5657
5801
|
|
|
5658
|
-
#
|
|
5802
|
+
# Build identity for the compiler and runtime executing this program:
|
|
5803
|
+
# +Version+ and +VERSION+ below.
|
|
5659
5804
|
#
|
|
5660
|
-
#
|
|
5661
|
-
#
|
|
5662
|
-
#
|
|
5663
|
-
#
|
|
5664
|
-
let interpreted? = Kex.BACKEND == Kex.Interpreter
|
|
5665
|
-
|
|
5666
|
-
# Returns +true+ when running on the BEAM.
|
|
5667
|
-
#
|
|
5668
|
-
# The backend a program is on decides what is available: processes and the
|
|
5669
|
-
# web server need the BEAM (+kex -R file.kex+).
|
|
5805
|
+
# Useful in bug reports, generated artifacts, and compatibility checks where
|
|
5806
|
+
# +Kex.BACKEND+ alone is not enough to identify the toolchain.
|
|
5807
|
+
# The toolchain a program is running on. `kex --version` and the REPL
|
|
5808
|
+
# banner report the same numbers.
|
|
5670
5809
|
#
|
|
5671
|
-
#
|
|
5810
|
+
# `revision` is the git commit the compiler was built from: `None` when
|
|
5811
|
+
# it was built from a source archive rather than a checkout, which is why
|
|
5812
|
+
# it is an Optional rather than a String.
|
|
5672
5813
|
#
|
|
5673
5814
|
# @example
|
|
5674
|
-
# Kex.
|
|
5675
|
-
|
|
5815
|
+
# Kex.VERSION.major # => 0
|
|
5816
|
+
# Kex.VERSION.revision # => Just("a1b2c3d")
|
|
5817
|
+
# Kex.VERSION.to(String) # => "0.3.0 (a1b2c3d)"
|
|
5818
|
+
record Version do
|
|
5819
|
+
# The major version number.
|
|
5820
|
+
major : Integer
|
|
5676
5821
|
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
#
|
|
5822
|
+
# The minor version number.
|
|
5823
|
+
minor : Integer
|
|
5824
|
+
|
|
5825
|
+
# The patch version number.
|
|
5826
|
+
patch : Integer
|
|
5827
|
+
|
|
5828
|
+
# The git commit the compiler was built from, or +None+ when it was
|
|
5829
|
+
# built from a source archive rather than a checkout.
|
|
5830
|
+
revision : String?
|
|
5831
|
+
|
|
5832
|
+
# The release channel: "" for a stable build, otherwise "rc.1",
|
|
5833
|
+
# "beta.2", "prealpha.1": the part of VERSION after the dash.
|
|
5834
|
+
preRelease : String = ""
|
|
5835
|
+
end
|
|
5836
|
+
|
|
5837
|
+
make Version do
|
|
5838
|
+
# The version's four values as a tuple, for destructuring.
|
|
5839
|
+
#
|
|
5840
|
+
# A tuple cannot carry accessors of its own: there is no named type for
|
|
5841
|
+
# a `make` block to target, so the record is the value and this is the
|
|
5842
|
+
# view.
|
|
5684
5843
|
#
|
|
5685
|
-
#
|
|
5686
|
-
# it was built from a source archive rather than a checkout, which is why
|
|
5687
|
-
# it is an Optional rather than a String.
|
|
5844
|
+
# @return [(Integer, Integer, Integer, String?)] major, minor, patch, revision
|
|
5688
5845
|
#
|
|
5689
5846
|
# @example
|
|
5690
|
-
# Kex.
|
|
5691
|
-
#
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
# The major version number.
|
|
5695
|
-
major : Integer
|
|
5696
|
-
|
|
5697
|
-
# The minor version number.
|
|
5698
|
-
minor : Integer
|
|
5699
|
-
|
|
5700
|
-
# The patch version number.
|
|
5701
|
-
patch : Integer
|
|
5702
|
-
|
|
5703
|
-
# The git commit the compiler was built from, or +None+ when it was
|
|
5704
|
-
# built from a source archive rather than a checkout.
|
|
5705
|
-
revision : String?
|
|
5706
|
-
|
|
5707
|
-
# The release channel: "" for a stable build, otherwise "rc.1",
|
|
5708
|
-
# "beta.2", "prealpha.1": the part of VERSION after the dash.
|
|
5709
|
-
preRelease : String = ""
|
|
5710
|
-
end
|
|
5847
|
+
# let (major, minor, patch, revision) = Kex.VERSION.tuple
|
|
5848
|
+
# major # => 0
|
|
5849
|
+
tuple : (Integer, Integer, Integer, String?)
|
|
5850
|
+
let tuple = (this.major, this.minor, this.patch, this.revision)
|
|
5711
5851
|
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
# major # => 0
|
|
5724
|
-
tuple : (Integer, Integer, Integer, String?)
|
|
5725
|
-
let tuple = (this.major, this.minor, this.patch, this.revision)
|
|
5726
|
-
|
|
5727
|
-
# The three numbers, plus the pre-release channel when there is one.
|
|
5728
|
-
#
|
|
5729
|
-
# `0.4.0`, or `0.4.0-rc.1` on a pre-release build. What a version RANGE
|
|
5730
|
-
# is matched against, so the channel has to be in it.
|
|
5731
|
-
#
|
|
5732
|
-
# @return [String] the release string
|
|
5733
|
-
#
|
|
5734
|
-
# @example
|
|
5735
|
-
# Kex.Kernel.VERSION.release # => "0.4.0-alpha.2"
|
|
5736
|
-
release : String
|
|
5737
|
-
let release = @preRelease.empty? then "${this.major}.${this.minor}.${this.patch}" else "${this.major}.${this.minor}.${this.patch}-${@preRelease}"
|
|
5738
|
-
|
|
5739
|
-
# The release string with the build revision after it, when there is one.
|
|
5740
|
-
#
|
|
5741
|
-
# This is what +kex --version+ and the REPL banner print.
|
|
5742
|
-
#
|
|
5743
|
-
# `to(String)`: the language's conversion protocol, and what this
|
|
5744
|
-
# should really be: is deliberately NOT defined here: a second
|
|
5745
|
-
# `to(String)` implementation anywhere in the prelude breaks
|
|
5746
|
-
# type-directed `to` dispatch for every prelude type on BEAM, so adding
|
|
5747
|
-
# one here silently broke `3.kilo.watt.to(String)`. Pinned by
|
|
5748
|
-
# spec/prelude_to_string_dispatch.kex; restore this as `to(String)` once
|
|
5749
|
-
# that dispatcher is fixed.
|
|
5750
|
-
#
|
|
5751
|
-
# @return [String] the full version string
|
|
5752
|
-
#
|
|
5753
|
-
# @example
|
|
5754
|
-
# Kex.Kernel.VERSION.number # => "0.4.0-alpha.2 (219e625)"
|
|
5755
|
-
#
|
|
5756
|
-
# @example Reporting the toolchain in a tool's output
|
|
5757
|
-
# IO.printLine("built with Kex ${Kex.Kernel.VERSION.number}")
|
|
5758
|
-
number : String
|
|
5759
|
-
let number = match this.revision do
|
|
5760
|
-
Just(hash) => "${this.release} (${hash})"
|
|
5761
|
-
None => this.release
|
|
5762
|
-
end
|
|
5763
|
-
end
|
|
5852
|
+
# The three numbers, plus the pre-release channel when there is one.
|
|
5853
|
+
#
|
|
5854
|
+
# `0.4.0`, or `0.4.0-rc.1` on a pre-release build. What a version RANGE
|
|
5855
|
+
# is matched against, so the channel has to be in it.
|
|
5856
|
+
#
|
|
5857
|
+
# @return [String] the release string
|
|
5858
|
+
#
|
|
5859
|
+
# @example
|
|
5860
|
+
# Kex.VERSION.release # => "0.4.0-alpha.2"
|
|
5861
|
+
release : String
|
|
5862
|
+
let release = @preRelease.empty? then "${this.major}.${this.minor}.${this.patch}" else "${this.major}.${this.minor}.${this.patch}-${@preRelease}"
|
|
5764
5863
|
|
|
5765
|
-
#
|
|
5864
|
+
# The release string with the build revision after it, when there is one.
|
|
5865
|
+
#
|
|
5866
|
+
# This is what +kex --version+ and the REPL banner print.
|
|
5867
|
+
#
|
|
5868
|
+
# `to(String)`: the language's conversion protocol, and what this
|
|
5869
|
+
# should really be: is deliberately NOT defined here: a second
|
|
5870
|
+
# `to(String)` implementation anywhere in the prelude breaks
|
|
5871
|
+
# type-directed `to` dispatch for every prelude type on BEAM, so adding
|
|
5872
|
+
# one here silently broke `3.kilo.watt.to(String)`. Pinned by
|
|
5873
|
+
# spec/prelude_to_string_dispatch.kex; restore this as `to(String)` once
|
|
5874
|
+
# that dispatcher is fixed.
|
|
5766
5875
|
#
|
|
5767
|
-
# @return [
|
|
5876
|
+
# @return [String] the full version string
|
|
5768
5877
|
#
|
|
5769
5878
|
# @example
|
|
5770
|
-
# Kex.
|
|
5771
|
-
#
|
|
5772
|
-
#
|
|
5773
|
-
#
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
Version { major: major, minor: minor, patch: patch, revision: revision,
|
|
5779
|
-
preRelease: Kex.Intrinsic.Kex.versionPreRelease() }
|
|
5879
|
+
# Kex.VERSION.number # => "0.4.0-alpha.2 (219e625)"
|
|
5880
|
+
#
|
|
5881
|
+
# @example Reporting the toolchain in a tool's output
|
|
5882
|
+
# IO.printLine("built with Kex ${Kex.VERSION.number}")
|
|
5883
|
+
number : String
|
|
5884
|
+
let number = match this.revision do
|
|
5885
|
+
Just(hash) => "${this.release} (${hash})"
|
|
5886
|
+
None => this.release
|
|
5780
5887
|
end
|
|
5781
5888
|
end
|
|
5782
5889
|
|
|
5890
|
+
# This build's version.
|
|
5891
|
+
#
|
|
5892
|
+
# @return [Version] the running toolchain's version
|
|
5893
|
+
#
|
|
5894
|
+
# @example
|
|
5895
|
+
# Kex.VERSION.major # => 0
|
|
5896
|
+
# Kex.VERSION.release # => "0.4.0-alpha.2"
|
|
5897
|
+
#
|
|
5898
|
+
# @example Reporting the version in a tool's output
|
|
5899
|
+
# IO.printLine("built with Kex ${Kex.VERSION.number}")
|
|
5900
|
+
VERSION : Version
|
|
5901
|
+
let VERSION = match Kex.Intrinsic.Kex.version() do
|
|
5902
|
+
(major, minor, patch, revision) =>
|
|
5903
|
+
Version { major: major, minor: minor, patch: patch, revision: revision,
|
|
5904
|
+
preRelease: Kex.Intrinsic.Kex.versionPreRelease() }
|
|
5905
|
+
end
|
|
5906
|
+
|
|
5907
|
+
# A compiled module loaded into the running program by +Kex.load+.
|
|
5908
|
+
#
|
|
5909
|
+
# For a program that decides at run time what code it needs: a site
|
|
5910
|
+
# generator rendering a theme's templates, a plugin host. Compile the source
|
|
5911
|
+
# with `kex --compile -o <dir>` once, then load the `.beam` and call it as
|
|
5912
|
+
# often as needed, without starting another VM for every call
|
|
5913
|
+
# (kexhq/kex#399).
|
|
5914
|
+
#
|
|
5915
|
+
# Every entry module is named after its file (`kex_<stem>.beam` for
|
|
5916
|
+
# `<stem>.kex`), and so is everything declared at the top level of that
|
|
5917
|
+
# file. A `let render = Template.html(Kex.embed(path))` written outside any
|
|
5918
|
+
# `module` therefore lands in `kex_<stem>`, not in a module the file names;
|
|
5919
|
+
# give each generated file a distinct name, or put the declaration inside a
|
|
5920
|
+
# `module`, whose functions compile into `Kex.<Name>`.
|
|
5921
|
+
#
|
|
5922
|
+
# The BEAM backend only: under the interpreter, +Kex.load+ answers an error.
|
|
5923
|
+
#
|
|
5924
|
+
# let theme = Kex.load("cache/kex_theme_3f2a.beam").try
|
|
5925
|
+
# let html = theme.call(:render, [context]).try
|
|
5926
|
+
record LoadedModule do
|
|
5927
|
+
# The module's BEAM name, e.g. +:kex_theme+ or +:"Kex.Theme"+.
|
|
5928
|
+
name : Atom
|
|
5929
|
+
end
|
|
5930
|
+
|
|
5931
|
+
# Loads the compiled module at +path+, replacing an older version of it
|
|
5932
|
+
# that is already loaded. See +Kex.LoadedModule+.
|
|
5933
|
+
#
|
|
5934
|
+
# @param path [String] a `.beam` file written by `kex --compile`
|
|
5935
|
+
# @return [Result<LoadedModule, String>] the module, or why it could not be
|
|
5936
|
+
# loaded
|
|
5937
|
+
#
|
|
5938
|
+
# @example
|
|
5939
|
+
# let theme = Kex.load("build/kex_theme.beam").try
|
|
5940
|
+
load : String -> Result<LoadedModule, String>
|
|
5941
|
+
foul load(path) = Kex.Intrinsic.Code.load(path).map { |name| LoadedModule { name: name } }
|
|
5942
|
+
|
|
5943
|
+
# The module already loaded under +name+, or +None+ when there is none.
|
|
5944
|
+
#
|
|
5945
|
+
# @param name [Atom] the module's BEAM name
|
|
5946
|
+
# @return [LoadedModule?] the module
|
|
5947
|
+
#
|
|
5948
|
+
# @example Loading only once
|
|
5949
|
+
# let theme = Kex.loaded(:kex_theme).or(Kex.load(path).try)
|
|
5950
|
+
loaded : Atom -> LoadedModule?
|
|
5951
|
+
foul loaded(name) = Kex.Intrinsic.Code.loaded?(name) then Just(LoadedModule { name: name }) else None
|
|
5952
|
+
|
|
5953
|
+
make LoadedModule do
|
|
5954
|
+
# Calls the module's function +function+ with +arguments+.
|
|
5955
|
+
#
|
|
5956
|
+
# A failure inside the call is an +Error+ describing it, not a crash of
|
|
5957
|
+
# the caller, and so is a function the module does not export with that
|
|
5958
|
+
# many arguments. A `foul` function takes one argument more than it
|
|
5959
|
+
# declares, its capabilities, so call pure functions this way.
|
|
5960
|
+
#
|
|
5961
|
+
# @param function [Atom] the function's name
|
|
5962
|
+
# @param arguments [[Any]] its arguments, in order
|
|
5963
|
+
# @return [Result<Any, String>] what the function returned, or why the
|
|
5964
|
+
# call failed
|
|
5965
|
+
#
|
|
5966
|
+
# @example
|
|
5967
|
+
# theme.call(:render, [context]) # => Ok("<html>...")
|
|
5968
|
+
call :> Atom -> [Any] -> Result<Any, String>
|
|
5969
|
+
foul call(function, arguments) = Kex.Intrinsic.Code.call(@name, function, arguments)
|
|
5970
|
+
end
|
|
5971
|
+
|
|
5972
|
+
# A non-cryptographic hash of any value: a non-negative +Integer+ below
|
|
5973
|
+
# 2^32, the same for equal values.
|
|
5974
|
+
#
|
|
5975
|
+
# Cheap to compute over a whole structure, with no string built along the
|
|
5976
|
+
# way, which suits fingerprinting data to notice when it changed. It is
|
|
5977
|
+
# stable only within one running program: the number differs between the
|
|
5978
|
+
# interpreter and the BEAM, and may change between Kex versions, so never
|
|
5979
|
+
# store it or send it anywhere. Use +Digest+ for that, or for anything an
|
|
5980
|
+
# adversary could choose collisions for.
|
|
5981
|
+
#
|
|
5982
|
+
# @param value [Any] the value to hash
|
|
5983
|
+
# @return [Integer] the hash, from 0 up to 2^32 - 1
|
|
5984
|
+
#
|
|
5985
|
+
# @example Noticing that a project's files changed
|
|
5986
|
+
# let stamp = Kex.hash(paths.map { |path| (path, FS.File.info(path).try) })
|
|
5987
|
+
hash : Any -> Integer
|
|
5988
|
+
let hash(value) = Kex.Intrinsic.Kex.hash(value)
|
|
5989
|
+
|
|
5783
5990
|
# Which optional capabilities this build includes.
|
|
5784
5991
|
#
|
|
5785
5992
|
# Optional non-network capabilities in this build. Networking has its own
|
|
@@ -5791,7 +5998,7 @@ module Kex do
|
|
|
5791
5998
|
# @return [Bool] +true+ when it is available
|
|
5792
5999
|
#
|
|
5793
6000
|
# @example
|
|
5794
|
-
# Kex.Feature.has?(Kex.
|
|
6001
|
+
# Kex.Feature.has?(Kex.ExternalPrograms) # => true, except in a browser
|
|
5795
6002
|
#
|
|
5796
6003
|
has? : Feature -> Bool
|
|
5797
6004
|
let has?(f) = Kex.Intrinsic.Kex.featureHas?(f)
|
|
@@ -5801,7 +6008,7 @@ module Kex do
|
|
|
5801
6008
|
# @return [[Feature]] the available capabilities
|
|
5802
6009
|
#
|
|
5803
6010
|
# @example
|
|
5804
|
-
# Kex.Feature.list # => [
|
|
6011
|
+
# Kex.Feature.list # => [FileSystem, ExternalPrograms]
|
|
5805
6012
|
list : [Feature]
|
|
5806
6013
|
let list = Kex.Intrinsic.Kex.featureList()
|
|
5807
6014
|
end
|
|
@@ -5815,7 +6022,7 @@ module Kex.Interface do
|
|
|
5815
6022
|
#
|
|
5816
6023
|
# The term is an ordinary tree of tuples, lists, atoms, integers and
|
|
5817
6024
|
# strings, so it is walked with normal pattern matching and `Tuple.items`.
|
|
5818
|
-
# This exists so that reading it needs no `
|
|
6025
|
+
# This exists so that reading it needs no `BEAM.*` interop: it is the one
|
|
5819
6026
|
# intentional entry point rather than a general term decoder.
|
|
5820
6027
|
#
|
|
5821
6028
|
# @param path [FS.FilePath] the compiled .beam file to read
|
|
@@ -6877,9 +7084,9 @@ make [X], implement: Enumerable, Foldable do
|
|
|
6877
7084
|
# Folds the list from the left, starting with +acc+ and combining each
|
|
6878
7085
|
# element via +f+.
|
|
6879
7086
|
#
|
|
6880
|
-
# @param acc [
|
|
6881
|
-
# @param f [
|
|
6882
|
-
# @return [
|
|
7087
|
+
# @param acc [A] initial accumulator value
|
|
7088
|
+
# @param f [A -> X -> A]
|
|
7089
|
+
# @return [A]
|
|
6883
7090
|
#
|
|
6884
7091
|
# @example
|
|
6885
7092
|
# [1, 2, 3].reduce(0) { |acc, x| acc + x } # => 6
|
|
@@ -8286,6 +8493,10 @@ module Mock do
|
|
|
8286
8493
|
None => Error(ReadFailed(path))
|
|
8287
8494
|
end
|
|
8288
8495
|
foul symlink?(path) = false
|
|
8496
|
+
foul info(path) = match this.cannedRead(path) do
|
|
8497
|
+
Just(content) => Ok(FileInfo { kind: :file, size: content.bytes.count, modified: DateTime.fromEpochSeconds(0) })
|
|
8498
|
+
None => Error(ReadFailed(path))
|
|
8499
|
+
end
|
|
8289
8500
|
|
|
8290
8501
|
# A fake is a value, so there is nowhere for a write to go. Refusing is
|
|
8291
8502
|
# the honest answer and the useful one: a test that did not expect a
|
|
@@ -8900,6 +9111,7 @@ end
|
|
|
8900
9111
|
module Router do
|
|
8901
9112
|
# @return [Router] a router with no routes
|
|
8902
9113
|
# @example +Router.build.get("/health", { |request, context| Response.text(200, "ok") })+
|
|
9114
|
+
build : Router
|
|
8903
9115
|
let build = Router { routes: [] }
|
|
8904
9116
|
end
|
|
8905
9117
|
|
|
@@ -9933,6 +10145,146 @@ module TLS do
|
|
|
9933
10145
|
foul closed?(connection: TLSConnection) -> Bool = Kex.Intrinsic.NetTLS.closed?(connection)
|
|
9934
10146
|
|
|
9935
10147
|
end
|
|
10148
|
+
# Distributed Kex: this BEAM node, and the other nodes it is connected to.
|
|
10149
|
+
#
|
|
10150
|
+
# A node is one running BEAM VM with a name. Once two nodes share a cookie and
|
|
10151
|
+
# are connected, a +Pid+ from one works on the other: +send+, +link+ and
|
|
10152
|
+
# +monitor+ cross the network unchanged, and so do typed +Process+ and
|
|
10153
|
+
# +Server+ handles.
|
|
10154
|
+
#
|
|
10155
|
+
# Name a node when starting it (`kex --sname a --cookie secret app.kex`), or
|
|
10156
|
+
# from inside the program with +Node.start+:
|
|
10157
|
+
#
|
|
10158
|
+
# using Node
|
|
10159
|
+
#
|
|
10160
|
+
# main do
|
|
10161
|
+
# Node.connect(:b@myhost)
|
|
10162
|
+
# Node.send(:b@myhost, :logger, (:hello, Node.self))
|
|
10163
|
+
# end
|
|
10164
|
+
#
|
|
10165
|
+
# Node names are atoms: `:b@myhost` for a short name, the quoted
|
|
10166
|
+
# `:"b@host.example.com"` for one with dots, or `Atom.from(text)` for one
|
|
10167
|
+
# built at runtime.
|
|
10168
|
+
#
|
|
10169
|
+
# Both nodes need the same compiled code for anything that carries a function,
|
|
10170
|
+
# like a lambda or a record whose methods the receiver calls; +Node.spawn+
|
|
10171
|
+
# sends its block's code along. Plain data — numbers, strings, lists, tuples,
|
|
10172
|
+
# records — needs nothing.
|
|
10173
|
+
#
|
|
10174
|
+
# Backed by Kex.Intrinsic.Node on the BEAM. The tree-walk interpreter is always
|
|
10175
|
+
# a single, unnamed node.
|
|
10176
|
+
module Node do
|
|
10177
|
+
# This node's name, or +:nonode@nohost+ when it is not distributed.
|
|
10178
|
+
#
|
|
10179
|
+
# @return [Atom] the node name
|
|
10180
|
+
#
|
|
10181
|
+
# @example
|
|
10182
|
+
# Node.self # => :a@myhost
|
|
10183
|
+
self : Atom
|
|
10184
|
+
foul self = Kex.Intrinsic.Node.self
|
|
10185
|
+
|
|
10186
|
+
# Whether this node is distributed — started with a name, so other nodes
|
|
10187
|
+
# can connect to it.
|
|
10188
|
+
#
|
|
10189
|
+
# @return [Bool] true once the node has a name
|
|
10190
|
+
#
|
|
10191
|
+
# @example
|
|
10192
|
+
# Node.alive? # => false, unless started with --sname or Node.start
|
|
10193
|
+
alive? : Bool
|
|
10194
|
+
foul alive? = Kex.Intrinsic.Node.alive?
|
|
10195
|
+
|
|
10196
|
+
# The nodes this one is currently connected to, not including itself.
|
|
10197
|
+
#
|
|
10198
|
+
# @return [[Atom]] the connected node names
|
|
10199
|
+
#
|
|
10200
|
+
# @example
|
|
10201
|
+
# Node.list # => [:b@myhost]
|
|
10202
|
+
list : [Atom]
|
|
10203
|
+
foul list = Kex.Intrinsic.Node.list
|
|
10204
|
+
|
|
10205
|
+
# Makes this node distributed under +name+, as `--sname`/`--name` would at
|
|
10206
|
+
# startup. A +name+ whose host has a dot (`:"app@host.example.com"`) uses
|
|
10207
|
+
# long names; anything else uses short names.
|
|
10208
|
+
#
|
|
10209
|
+
# @param name [Atom] the node name, like +:app+ or +:app@myhost+
|
|
10210
|
+
# @return [Result<Atom, String>] the full node name, or why it could not start
|
|
10211
|
+
#
|
|
10212
|
+
# @example
|
|
10213
|
+
# Node.start(:app) # => Ok(:app@myhost)
|
|
10214
|
+
start : Atom -> Result<Atom, String>
|
|
10215
|
+
foul start(name) = Kex.Intrinsic.Node.start(name)
|
|
10216
|
+
|
|
10217
|
+
# Stops distribution: the node drops its name and every connection.
|
|
10218
|
+
#
|
|
10219
|
+
# @return [Bool] whether it was distributed and has now stopped
|
|
10220
|
+
stop : Bool
|
|
10221
|
+
foul stop = Kex.Intrinsic.Node.stop
|
|
10222
|
+
|
|
10223
|
+
# Connects to the node named +node+. Both nodes must share a cookie.
|
|
10224
|
+
#
|
|
10225
|
+
# @param node [Atom] the node to connect to
|
|
10226
|
+
# @return [Bool] whether the connection is up
|
|
10227
|
+
#
|
|
10228
|
+
# @example
|
|
10229
|
+
# Node.connect(:b@myhost) # => true
|
|
10230
|
+
connect : Atom -> Bool
|
|
10231
|
+
foul connect(node) = Kex.Intrinsic.Node.connect(node)
|
|
10232
|
+
|
|
10233
|
+
# Drops the connection to +node+.
|
|
10234
|
+
#
|
|
10235
|
+
# @param node [Atom] the node to disconnect from
|
|
10236
|
+
# @return [Bool] whether a connection was dropped
|
|
10237
|
+
disconnect : Atom -> Bool
|
|
10238
|
+
foul disconnect(node) = Kex.Intrinsic.Node.disconnect(node)
|
|
10239
|
+
|
|
10240
|
+
# Sets the cookie this node presents when connecting. Nodes connect only
|
|
10241
|
+
# when their cookies match.
|
|
10242
|
+
#
|
|
10243
|
+
# @param cookie [String] the shared secret
|
|
10244
|
+
# @return [Void]
|
|
10245
|
+
setCookie : String -> Void
|
|
10246
|
+
foul setCookie(cookie) = Kex.Intrinsic.Node.setCookie(cookie)
|
|
10247
|
+
|
|
10248
|
+
# Sends +message+ to the process registered as +name+ on +node+, unchanged.
|
|
10249
|
+
# Like +Pid.send+, it never blocks and never fails.
|
|
10250
|
+
#
|
|
10251
|
+
# @param node [Atom] the node the process runs on
|
|
10252
|
+
# @param name [Atom] the name it was registered under
|
|
10253
|
+
# @param message [X] the message
|
|
10254
|
+
# @return [Void]
|
|
10255
|
+
#
|
|
10256
|
+
# @example
|
|
10257
|
+
# Node.send(:b@myhost, :logger, (:info, "started"))
|
|
10258
|
+
send : Atom -> Atom -> X -> Void
|
|
10259
|
+
foul send(node, name, message) = Kex.Intrinsic.Node.send(node, name, message)
|
|
10260
|
+
|
|
10261
|
+
# The +Pid+ registered as +name+ on +node+, or +None+.
|
|
10262
|
+
#
|
|
10263
|
+
# @param node [Atom] the node to ask
|
|
10264
|
+
# @param name [Atom] the registered name
|
|
10265
|
+
# @return [Pid?] the process, or +None+
|
|
10266
|
+
#
|
|
10267
|
+
# @example
|
|
10268
|
+
# Node.whereIs(:b@myhost, :logger).map { |pid| pid.send(:flush) }
|
|
10269
|
+
whereIs : Atom -> Atom -> Pid?
|
|
10270
|
+
foul whereIs(node, name) = Kex.Intrinsic.Node.whereIs(node, name)
|
|
10271
|
+
|
|
10272
|
+
# Runs +block+ in a new process on +node+ and returns its +Pid+.
|
|
10273
|
+
#
|
|
10274
|
+
# The block is a function of THIS program. When +node+ has not loaded the
|
|
10275
|
+
# module it belongs to, that module's compiled code is sent over and loaded
|
|
10276
|
+
# first. A module +node+ already has is left as it is.
|
|
10277
|
+
#
|
|
10278
|
+
# @param node [Atom] where to run it
|
|
10279
|
+
# @param block [Block<X>] the work
|
|
10280
|
+
# @return [Pid] the new process
|
|
10281
|
+
#
|
|
10282
|
+
# @example
|
|
10283
|
+
# let me = Process.self
|
|
10284
|
+
# Node.spawn(:b@myhost) do me.send(Node.self) end
|
|
10285
|
+
spawn : Atom -> Block<X> -> Pid
|
|
10286
|
+
foul spawn(node, block) = Kex.Intrinsic.Node.spawn(node, block)
|
|
10287
|
+
end
|
|
9936
10288
|
# Whole numbers, of arbitrary size.
|
|
9937
10289
|
#
|
|
9938
10290
|
# +Integer+ has no width limit: factorials and cryptographic moduli are
|
|
@@ -10299,6 +10651,151 @@ module Float do
|
|
|
10299
10651
|
# Float.parsePrefix("12.5kg") # => Just((12.5, "kg"))
|
|
10300
10652
|
parsePrefix : String -> (Float, String)?
|
|
10301
10653
|
let parsePrefix(s) = Kex.Intrinsic.Float.parsePrefix(s)
|
|
10654
|
+
|
|
10655
|
+
# The largest finite +Float+.
|
|
10656
|
+
#
|
|
10657
|
+
# A Kex +Float+ is always finite, so there is no infinity to start from:
|
|
10658
|
+
# this is the bound to use instead, say as the first "smallest so far".
|
|
10659
|
+
#
|
|
10660
|
+
# @return [Float] 1.7976931348623157e308
|
|
10661
|
+
#
|
|
10662
|
+
# @example
|
|
10663
|
+
# let smallest = readings.reduce(Float.MAX) { |low, x| x < low then x else low }
|
|
10664
|
+
MAX : Float
|
|
10665
|
+
let MAX = 1.7976931348623157e308
|
|
10666
|
+
|
|
10667
|
+
# The most negative finite +Float+: +-Float.MAX+. Not the smallest positive
|
|
10668
|
+
# one, which some languages call MIN.
|
|
10669
|
+
#
|
|
10670
|
+
# @return [Float] -1.7976931348623157e308
|
|
10671
|
+
MIN : Float
|
|
10672
|
+
let MIN = -1.7976931348623157e308
|
|
10673
|
+
end
|
|
10674
|
+
|
|
10675
|
+
# The bounds of the sized numeric types: +Int8.MAX+, +UInt64.MAX+,
|
|
10676
|
+
# +Float32.MIN+ and so on. +Integer+ has none: it is arbitrary-precision.
|
|
10677
|
+
# For a float type, +MIN+ is the most negative finite value, not the smallest
|
|
10678
|
+
# positive one.
|
|
10679
|
+
|
|
10680
|
+
# The range of a 32-bit float.
|
|
10681
|
+
module Float32 do
|
|
10682
|
+
# The largest Float32: 3.4028234663852886e38.
|
|
10683
|
+
MAX : Float32
|
|
10684
|
+
let MAX = 3.4028234663852886e38
|
|
10685
|
+
|
|
10686
|
+
# The most negative finite Float32: -3.4028234663852886e38.
|
|
10687
|
+
MIN : Float32
|
|
10688
|
+
let MIN = -3.4028234663852886e38
|
|
10689
|
+
end
|
|
10690
|
+
|
|
10691
|
+
# The range of a 64-bit float, the same as a +Float+.
|
|
10692
|
+
module Float64 do
|
|
10693
|
+
# The largest Float64: 1.7976931348623157e308.
|
|
10694
|
+
MAX : Float64
|
|
10695
|
+
let MAX = 1.7976931348623157e308
|
|
10696
|
+
|
|
10697
|
+
# The most negative finite Float64: -1.7976931348623157e308.
|
|
10698
|
+
MIN : Float64
|
|
10699
|
+
let MIN = -1.7976931348623157e308
|
|
10700
|
+
end
|
|
10701
|
+
|
|
10702
|
+
# The range of a signed 8-bit integer.
|
|
10703
|
+
module Int8 do
|
|
10704
|
+
# The largest Int8: 127.
|
|
10705
|
+
MAX : Int8
|
|
10706
|
+
let MAX = 127
|
|
10707
|
+
|
|
10708
|
+
# The smallest Int8: -128.
|
|
10709
|
+
MIN : Int8
|
|
10710
|
+
let MIN = -128
|
|
10711
|
+
end
|
|
10712
|
+
|
|
10713
|
+
# The range of a signed 16-bit integer.
|
|
10714
|
+
module Int16 do
|
|
10715
|
+
# The largest Int16: 32767.
|
|
10716
|
+
MAX : Int16
|
|
10717
|
+
let MAX = 32767
|
|
10718
|
+
|
|
10719
|
+
# The smallest Int16: -32768.
|
|
10720
|
+
MIN : Int16
|
|
10721
|
+
let MIN = -32768
|
|
10722
|
+
end
|
|
10723
|
+
|
|
10724
|
+
# The range of a signed 32-bit integer.
|
|
10725
|
+
module Int32 do
|
|
10726
|
+
# The largest Int32: 2147483647.
|
|
10727
|
+
MAX : Int32
|
|
10728
|
+
let MAX = 2147483647
|
|
10729
|
+
|
|
10730
|
+
# The smallest Int32: -2147483648.
|
|
10731
|
+
MIN : Int32
|
|
10732
|
+
let MIN = -2147483648
|
|
10733
|
+
end
|
|
10734
|
+
|
|
10735
|
+
# The range of a signed 64-bit integer.
|
|
10736
|
+
module Int64 do
|
|
10737
|
+
# The largest Int64: 9223372036854775807.
|
|
10738
|
+
MAX : Int64
|
|
10739
|
+
let MAX = 9223372036854775807
|
|
10740
|
+
|
|
10741
|
+
# The smallest Int64: -9223372036854775808.
|
|
10742
|
+
MIN : Int64
|
|
10743
|
+
let MIN = -9223372036854775808
|
|
10744
|
+
end
|
|
10745
|
+
|
|
10746
|
+
# The range of an unsigned 8-bit integer: +UInt8+ is another name for +Byte+.
|
|
10747
|
+
module UInt8 do
|
|
10748
|
+
# The largest UInt8: 255.
|
|
10749
|
+
MAX : UInt8
|
|
10750
|
+
let MAX = 255
|
|
10751
|
+
|
|
10752
|
+
# The smallest UInt8: 0.
|
|
10753
|
+
MIN : UInt8
|
|
10754
|
+
let MIN = 0
|
|
10755
|
+
end
|
|
10756
|
+
|
|
10757
|
+
# The range of an unsigned 16-bit integer.
|
|
10758
|
+
module UInt16 do
|
|
10759
|
+
# The largest UInt16: 65535.
|
|
10760
|
+
MAX : UInt16
|
|
10761
|
+
let MAX = 65535
|
|
10762
|
+
|
|
10763
|
+
# The smallest UInt16: 0.
|
|
10764
|
+
MIN : UInt16
|
|
10765
|
+
let MIN = 0
|
|
10766
|
+
end
|
|
10767
|
+
|
|
10768
|
+
# The range of an unsigned 32-bit integer.
|
|
10769
|
+
module UInt32 do
|
|
10770
|
+
# The largest UInt32: 4294967295.
|
|
10771
|
+
MAX : UInt32
|
|
10772
|
+
let MAX = 4294967295
|
|
10773
|
+
|
|
10774
|
+
# The smallest UInt32: 0.
|
|
10775
|
+
MIN : UInt32
|
|
10776
|
+
let MIN = 0
|
|
10777
|
+
end
|
|
10778
|
+
|
|
10779
|
+
# The range of an unsigned 64-bit integer.
|
|
10780
|
+
module UInt64 do
|
|
10781
|
+
# The largest UInt64: 18446744073709551615.
|
|
10782
|
+
MAX : UInt64
|
|
10783
|
+
let MAX = 18446744073709551615
|
|
10784
|
+
|
|
10785
|
+
# The smallest UInt64: 0.
|
|
10786
|
+
MIN : UInt64
|
|
10787
|
+
let MIN = 0
|
|
10788
|
+
end
|
|
10789
|
+
|
|
10790
|
+
# The range of a +Byte+, Kex's unsigned 8-bit integer (also called +UInt8+).
|
|
10791
|
+
module Byte do
|
|
10792
|
+
# The largest Byte: 255.
|
|
10793
|
+
MAX : Byte
|
|
10794
|
+
let MAX = 255
|
|
10795
|
+
|
|
10796
|
+
# The smallest Byte: 0.
|
|
10797
|
+
MIN : Byte
|
|
10798
|
+
let MIN = 0
|
|
10302
10799
|
end
|
|
10303
10800
|
|
|
10304
10801
|
# Reading a number out of text without deciding in advance which half of the
|
|
@@ -11691,7 +12188,7 @@ make Input do
|
|
|
11691
12188
|
# use it where the grammar requires something to be there.
|
|
11692
12189
|
#
|
|
11693
12190
|
# @param f [Input -> Result<(T, Input), ParseError>] the parser to repeat
|
|
11694
|
-
# @return [([T], Input)] the results and the cursor, or the first failure
|
|
12191
|
+
# @return [Result<([T], Input), ParseError>] the results and the cursor, or the first failure
|
|
11695
12192
|
#
|
|
11696
12193
|
# @example
|
|
11697
12194
|
# Input { input: "abc" }.some { |p| p.charWhen(~alpha?) }
|
|
@@ -11712,7 +12209,7 @@ make Input do
|
|
|
11712
12209
|
# the caret.
|
|
11713
12210
|
#
|
|
11714
12211
|
# @param expected [String] the literal that must be next
|
|
11715
|
-
# @return [(String, Input)] the literal and the cursor, or an +Expected+ error
|
|
12212
|
+
# @return [Result<(String, Input), ParseError>] the literal and the cursor, or an +Expected+ error
|
|
11716
12213
|
#
|
|
11717
12214
|
# @example
|
|
11718
12215
|
# Input { input: "abc" }.string("abc") # => Ok(("abc", cursor at 3))
|
|
@@ -11768,7 +12265,7 @@ make Input do
|
|
|
11768
12265
|
# they all started from.
|
|
11769
12266
|
#
|
|
11770
12267
|
# @param alts [[Input -> Result<(T, Input), ParseError>]] the parsers to try, in order
|
|
11771
|
-
# @return [(T, Input)] the first success, or +NoMatch+
|
|
12268
|
+
# @return [Result<(T, Input), ParseError>] the first success, or +NoMatch+
|
|
11772
12269
|
#
|
|
11773
12270
|
# @example
|
|
11774
12271
|
# cursor.choice([
|
|
@@ -11822,6 +12319,7 @@ end
|
|
|
11822
12319
|
# declaration in that source file, not here.
|
|
11823
12320
|
|
|
11824
12321
|
using Algebra
|
|
12322
|
+
using Atom
|
|
11825
12323
|
using Binary
|
|
11826
12324
|
using Blankable
|
|
11827
12325
|
using Comparable
|
|
@@ -11887,7 +12385,7 @@ using Units
|
|
|
11887
12385
|
|
|
11888
12386
|
# An opaque BEAM process identifier.
|
|
11889
12387
|
#
|
|
11890
|
-
# Obtained from +Process.self+ or +Process.
|
|
12388
|
+
# Obtained from +Process.self+ or +Process.whereIs+. Send it messages, link to
|
|
11891
12389
|
# it, monitor it, or ask whether it is still alive.
|
|
11892
12390
|
type Pid
|
|
11893
12391
|
|
|
@@ -12116,19 +12614,91 @@ module Process do
|
|
|
12116
12614
|
register : Pid -> Atom -> Void
|
|
12117
12615
|
foul register(pid, name) = Kex.Intrinsic.Process.register(pid, name)
|
|
12118
12616
|
|
|
12617
|
+
# Registers the typed process +process+ under the atom +name+.
|
|
12618
|
+
#
|
|
12619
|
+
# @param process [Process<X>] the process to register
|
|
12620
|
+
# @param name [Atom] the name to register it under
|
|
12621
|
+
# @return [Void]
|
|
12622
|
+
#
|
|
12623
|
+
# @example
|
|
12624
|
+
# let logger = spawn do logLoop() end
|
|
12625
|
+
# Process.register(logger, :logger)
|
|
12626
|
+
register : Process<X> -> Atom -> Void
|
|
12627
|
+
foul register(process, name) = Kex.Intrinsic.Process.register(process, name)
|
|
12628
|
+
|
|
12119
12629
|
# Returns the +Pid+ registered under +name+, or +None+ when nothing is.
|
|
12120
12630
|
#
|
|
12121
12631
|
# @param name [Atom] the registered name
|
|
12122
12632
|
# @return [Pid?] the process, or +None+
|
|
12123
12633
|
#
|
|
12124
12634
|
# @example
|
|
12125
|
-
# Process.
|
|
12126
|
-
# Process.
|
|
12635
|
+
# Process.whereIs(:main) # => Just(pid)
|
|
12636
|
+
# Process.whereIs(:not_there) # => None
|
|
12127
12637
|
#
|
|
12128
12638
|
# @example Sending to a named process if it is there
|
|
12129
|
-
# Process.
|
|
12130
|
-
|
|
12131
|
-
foul
|
|
12639
|
+
# Process.whereIs(:logger).map { |pid| pid.send(message) }
|
|
12640
|
+
whereIs : Atom -> Pid?
|
|
12641
|
+
foul whereIs(name) = Kex.Intrinsic.Process.whereIs(name)
|
|
12642
|
+
|
|
12643
|
+
# A named, typed slot of state that every process can read without asking
|
|
12644
|
+
# another process for it.
|
|
12645
|
+
#
|
|
12646
|
+
# State kept in a +serving+ process is copied into the caller on every
|
|
12647
|
+
# read, which is costly for a large value read by every request. A
|
|
12648
|
+
# +Shared+ value is stored once for the whole node (on the BEAM, in
|
|
12649
|
+
# +persistent_term+): reading it copies nothing, however large it is.
|
|
12650
|
+
#
|
|
12651
|
+
# The price is on the other side: replacing a value makes the runtime scan
|
|
12652
|
+
# every process for references to the old one. So it suits state that is
|
|
12653
|
+
# read constantly and written rarely, such as configuration or a loaded
|
|
12654
|
+
# site, and not a counter.
|
|
12655
|
+
#
|
|
12656
|
+
# Get a handle with +Process.Shared.named+. The slot is identified by its
|
|
12657
|
+
# name alone, so two handles with the same name are the same slot; the type
|
|
12658
|
+
# parameter is what a handle promises to store and read back, so give every
|
|
12659
|
+
# handle on one name the same one.
|
|
12660
|
+
#
|
|
12661
|
+
# @example Loading once, reading from every request handler
|
|
12662
|
+
# let site : Process.Shared<Site> = Process.Shared.named("site")
|
|
12663
|
+
# site.put(loadSite(root))
|
|
12664
|
+
# ...
|
|
12665
|
+
# let current = site.get.or(emptySite)
|
|
12666
|
+
type Shared<X>
|
|
12667
|
+
|
|
12668
|
+
module Shared do
|
|
12669
|
+
# The handle on the slot called +name+. Nothing is stored or read until
|
|
12670
|
+
# +put+ or +get+.
|
|
12671
|
+
#
|
|
12672
|
+
# @param name [String] the slot's name
|
|
12673
|
+
# @return [Process.Shared<X>] a handle on the slot
|
|
12674
|
+
named : String -> Process.Shared<X>
|
|
12675
|
+
let named(name) = Kex.Intrinsic.Shared.named(name)
|
|
12676
|
+
end
|
|
12677
|
+
|
|
12678
|
+
make Shared<X> do
|
|
12679
|
+
# Stores +value+, replacing whatever the slot held.
|
|
12680
|
+
#
|
|
12681
|
+
# Expensive: see +Process.Shared+.
|
|
12682
|
+
#
|
|
12683
|
+
# @param value [X] the value to store
|
|
12684
|
+
# @return [Void]
|
|
12685
|
+
put :> X -> Void
|
|
12686
|
+
foul put(value) = Kex.Intrinsic.Shared.put(this, value)
|
|
12687
|
+
|
|
12688
|
+
# The stored value, or +None+ when nothing has been stored yet.
|
|
12689
|
+
#
|
|
12690
|
+
# Cheap, whatever the value's size.
|
|
12691
|
+
#
|
|
12692
|
+
# @return [X?] the stored value
|
|
12693
|
+
get :> X?
|
|
12694
|
+
foul get = Kex.Intrinsic.Shared.get(this)
|
|
12695
|
+
|
|
12696
|
+
# Empties the slot.
|
|
12697
|
+
#
|
|
12698
|
+
# @return [Bool] +true+ when it held a value
|
|
12699
|
+
delete :> Bool
|
|
12700
|
+
foul delete = Kex.Intrinsic.Shared.delete(this)
|
|
12701
|
+
end
|
|
12132
12702
|
end
|
|
12133
12703
|
|
|
12134
12704
|
# What an external command left behind: its exit status and its output.
|
|
@@ -12424,413 +12994,278 @@ make Reference do
|
|
|
12424
12994
|
demonitor :> Void
|
|
12425
12995
|
let demonitor = Kex.Intrinsic.Process.demonitor(this)
|
|
12426
12996
|
end
|
|
12427
|
-
#
|
|
12997
|
+
# Random values for simulations, sampling, games, and reproducible tests.
|
|
12428
12998
|
#
|
|
12429
|
-
#
|
|
12430
|
-
#
|
|
12999
|
+
# Import with +using Random+. Ordinary calls return a value directly;
|
|
13000
|
+
# a +Random.Generator+ returns +(value, nextGenerator)+. Reusing the same
|
|
13001
|
+
# generator replays the same draw. Pass its returned state to continue.
|
|
12431
13002
|
#
|
|
13003
|
+
# @example Roll a die or initialize a weight
|
|
12432
13004
|
# using Random
|
|
13005
|
+
# let roll = Random.integer(1..6)
|
|
13006
|
+
# let weight = Random.float(-1.0..1.0)
|
|
12433
13007
|
#
|
|
12434
|
-
#
|
|
12435
|
-
#
|
|
12436
|
-
#
|
|
12437
|
-
#
|
|
12438
|
-
#
|
|
12439
|
-
# Two layers, for two needs. `Rng` is a small deterministic generator with
|
|
12440
|
-
# explicit state: the same seed answers the same sequence on every run and
|
|
12441
|
-
# on both backends, which is what makes randomized code testable. `Random`
|
|
12442
|
-
# is the ambient convenience layer over it: each call draws fresh entropy
|
|
12443
|
-
# from the host, so answers differ between runs.
|
|
12444
|
-
#
|
|
12445
|
-
# let rng = Rng.seeded(42)
|
|
12446
|
-
# let (roll, next) = rng.nextBounded(6) # 0..5, then keep going with next
|
|
12447
|
-
# Random.integer(6) # 0..5, fresh entropy, foul
|
|
13008
|
+
# @example Reproduce a sequence
|
|
13009
|
+
# let generator = Random.seeded(42)
|
|
13010
|
+
# let (first, afterFirst) = generator.integer(1..6)
|
|
13011
|
+
# let (second, afterSecond) = afterFirst.integer(1..6)
|
|
12448
13012
|
#
|
|
12449
|
-
#
|
|
12450
|
-
#
|
|
12451
|
-
|
|
12452
|
-
#
|
|
12453
|
-
# seeds from the host's secure source, but one secure seed does not make
|
|
12454
|
-
# the stream that follows secure.
|
|
12455
|
-
|
|
12456
|
-
# The state of a deterministic generator: one 64-bit word.
|
|
12457
|
-
#
|
|
12458
|
-
# A value, not a handle: every step answers a new `Rng` alongside its
|
|
12459
|
-
# output, and the old one keeps answering what it always did. Thread the
|
|
12460
|
-
# answer forward and the sequence is reproducible from its seed.
|
|
12461
|
-
#
|
|
12462
|
-
# let rng = Rng.seeded(42)
|
|
12463
|
-
# let (a, rng) = rng.nextUint64
|
|
12464
|
-
# let (b, rng) = rng.nextUint64 # same `a` and `b` on every run
|
|
12465
|
-
#
|
|
12466
|
-
# Build one with +Rng.seeded+ rather than by hand: the record literal does
|
|
12467
|
-
# no range reduction, and every step here assumes a state inside
|
|
12468
|
-
# 0..2^64 - 1.
|
|
12469
|
-
record Rng do
|
|
12470
|
-
state : Integer = 0
|
|
12471
|
-
end
|
|
12472
|
-
|
|
12473
|
-
# Constructors and constants for the deterministic +Rng+. The draws
|
|
12474
|
-
# themselves are methods in the +make+ block below, so every one answers
|
|
12475
|
-
# to Uniform Function Call Syntax: +rng.nextBounded(6)+.
|
|
12476
|
-
module Rng do
|
|
12477
|
-
# One past the largest representable state: all arithmetic here is
|
|
12478
|
-
# modulo 2^64, keeping the word closed under the mixing below. Also the
|
|
12479
|
-
# largest exclusive bound one 64-bit draw can cover directly.
|
|
13013
|
+
# These generators are not cryptographic; do not use their output for secrets.
|
|
13014
|
+
# +Random.secureBytes+ and +Random.token+ are, and are what secrets come from.
|
|
13015
|
+
module Random do
|
|
13016
|
+
# Immutable state of a deterministic random sequence.
|
|
12480
13017
|
#
|
|
12481
|
-
#
|
|
12482
|
-
#
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
let gamma = 0x9e3779b97f4a7c15
|
|
13018
|
+
# Construct with +Random.seeded+ or +Random.fresh+. Each draw returns its
|
|
13019
|
+
# value first and the advanced generator second. Equal seeds reproduce equal
|
|
13020
|
+
# sequences for the same operations on both backends.
|
|
13021
|
+
record Generator do
|
|
13022
|
+
state : Integer = 0
|
|
13023
|
+
end
|
|
12488
13024
|
|
|
12489
|
-
#
|
|
12490
|
-
|
|
12491
|
-
|
|
13025
|
+
# Starts a deterministic sequence. Any Integer seed is reduced modulo 2^64.
|
|
13026
|
+
# @param seed [Integer] reproducible seed, including negative or large values
|
|
13027
|
+
# @return [Generator] initial state; no draw has been consumed
|
|
13028
|
+
let seeded(seed: Integer) -> Generator = Generator {
|
|
13029
|
+
state: seed.modulo(18446744073709551616)
|
|
13030
|
+
}
|
|
12492
13031
|
|
|
12493
|
-
#
|
|
12494
|
-
#
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
13032
|
+
# Starts a generator using fresh host entropy.
|
|
13033
|
+
# @return [Generator] initial state for a sequence that varies between runs
|
|
13034
|
+
foul fresh -> Generator do
|
|
13035
|
+
let high = Math.floor(Kex.Intrinsic.Retry.randomUnit() * 4294967296.0)
|
|
13036
|
+
let low = Math.floor(Kex.Intrinsic.Retry.randomUnit() * 4294967296.0)
|
|
13037
|
+
seeded(high * 4294967296 + low)
|
|
13038
|
+
end
|
|
13039
|
+
|
|
13040
|
+
module Generator do
|
|
13041
|
+
make Random.Generator do
|
|
13042
|
+
# Draws an integer between both endpoints, with the advanced generator.
|
|
13043
|
+
# The range must be ascending and contain at most 2^64 integers.
|
|
13044
|
+
# A singleton range is valid and still consumes a draw.
|
|
13045
|
+
# @param range [Range<Integer>] inclusive integer bounds
|
|
13046
|
+
# @return [(Integer, Generator)] drawn integer and advanced state
|
|
13047
|
+
# @example A reproducible die roll
|
|
13048
|
+
# let (roll, advanced) = Random.seeded(42).integer(1..6)
|
|
13049
|
+
let integer(range: Range<Integer>) -> (Integer, Generator) = drawInteger(this, range)
|
|
13050
|
+
|
|
13051
|
+
# Draws a float from the lower bound inclusive to the upper bound exclusive.
|
|
13052
|
+
# Defaults to +0.0..1.0+. Bounds must be finite and strictly ascending.
|
|
13053
|
+
# @param range [Range<Float>] half-open floating-point bounds
|
|
13054
|
+
# @return [(Float, Generator)] drawn float and advanced state
|
|
13055
|
+
# @example Initialize a model weight
|
|
13056
|
+
# let (weight, advanced) = Random.seeded(42).float(-1.0..1.0)
|
|
13057
|
+
let float(range: Range<Float>) -> (Float, Generator) = drawFloat(this, range)
|
|
13058
|
+
let float -> (Float, Generator) = drawFloat(this, 0.0..1.0)
|
|
13059
|
+
|
|
13060
|
+
# Draws a fair Boolean and returns the advanced generator.
|
|
13061
|
+
# @return [(Bool, Generator)] coin flip and advanced state
|
|
13062
|
+
let boolean -> (Bool, Generator) = drawBoolean(this)
|
|
13063
|
+
|
|
13064
|
+
# Draws true with the supplied probability. Consumes one draw even at 0 or 1.
|
|
13065
|
+
# @param probability [Float] probability from 0.0 through 1.0
|
|
13066
|
+
# @return [(Bool, Generator)] outcome and advanced state
|
|
13067
|
+
let chance?(probability: Float) -> (Bool, Generator) = drawChance(this, probability)
|
|
13068
|
+
|
|
13069
|
+
# Chooses an input position uniformly. Empty input returns None without a draw.
|
|
13070
|
+
# @param items [[X]] values to choose from
|
|
13071
|
+
# @return [(X?, Generator)] optional value and advanced state
|
|
13072
|
+
let choice(items: [X]) -> (X?, Generator) = drawChoice(this, items)
|
|
13073
|
+
|
|
13074
|
+
# Selects input positions without replacement, in random order.
|
|
13075
|
+
# Duplicate input values may appear more than once in the result.
|
|
13076
|
+
# A count larger than the input selects every position; zero consumes no draws.
|
|
13077
|
+
# @param items [[X]] population; unchanged by sampling
|
|
13078
|
+
# @param count [Integer] nonnegative number of positions to select
|
|
13079
|
+
# @return [([X], Generator)] selected values and advanced state
|
|
13080
|
+
# @example Deal five cards reproducibly
|
|
13081
|
+
# let (hand, advanced) = Random.seeded(42).sample(cards, count: 5)
|
|
13082
|
+
let sample(items: [X], count: Integer) -> ([X], Generator) = drawSample(this, items, count)
|
|
13083
|
+
|
|
13084
|
+
# Returns a random permutation, preserving duplicates and the input itself.
|
|
13085
|
+
# Empty input consumes no draws.
|
|
13086
|
+
# @param items [[X]] values to shuffle
|
|
13087
|
+
# @return [([X], Generator)] permutation and advanced state
|
|
13088
|
+
let shuffle(items: [X]) -> ([X], Generator) = drawSample(this, items, items.count)
|
|
13089
|
+
end
|
|
12511
13090
|
end
|
|
12512
13091
|
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
#
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
# Draws one 64-bit unsigned word and the generator that follows it.
|
|
12520
|
-
#
|
|
12521
|
-
# This is the primitive everything else here is built on: the raw
|
|
12522
|
-
# SplitMix64 output, uniformly spread over 0..2^64 - 1. Pure arithmetic
|
|
12523
|
-
# over unbounded integers with an explicit mask, so both backends agree
|
|
12524
|
-
# bit for bit with the reference C.
|
|
12525
|
-
#
|
|
12526
|
-
# @return [(Integer, Rng)] the word and the next generator
|
|
12527
|
-
#
|
|
12528
|
-
# @example
|
|
12529
|
-
# let (word, next) = Rng.seeded(0).nextUint64
|
|
12530
|
-
# word # => 16294208416658607535
|
|
12531
|
-
nextUint64 :> (Integer, Rng)
|
|
12532
|
-
let nextUint64 do
|
|
12533
|
-
let mask = Rng.maxBound - 1
|
|
12534
|
-
let advanced = Kex.Intrinsic.Bits.and(@state + Rng.gamma, mask)
|
|
12535
|
-
let spread1 = Kex.Intrinsic.Bits.xor(advanced, Kex.Intrinsic.Bits.shiftRight(advanced, 30))
|
|
12536
|
-
let mixed1 = Kex.Intrinsic.Bits.and(spread1 * Rng.mixA, mask)
|
|
12537
|
-
let spread2 = Kex.Intrinsic.Bits.xor(mixed1, Kex.Intrinsic.Bits.shiftRight(mixed1, 27))
|
|
12538
|
-
let mixed2 = Kex.Intrinsic.Bits.and(spread2 * Rng.mixB, mask)
|
|
12539
|
-
let output = Kex.Intrinsic.Bits.xor(mixed2, Kex.Intrinsic.Bits.shiftRight(mixed2, 31))
|
|
12540
|
-
return (output, Rng { state: advanced })
|
|
13092
|
+
# Draws an integer from an inclusive range using fresh entropy.
|
|
13093
|
+
# @param range [Range<Integer>] ascending inclusive bounds, span at most 2^64
|
|
13094
|
+
# @return [Integer] drawn value
|
|
13095
|
+
foul integer(range: Range<Integer>) -> Integer do
|
|
13096
|
+
let (value, _) = drawInteger(fresh, range)
|
|
13097
|
+
value
|
|
12541
13098
|
end
|
|
12542
13099
|
|
|
12543
|
-
# Draws a
|
|
12544
|
-
#
|
|
12545
|
-
#
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
#
|
|
12550
|
-
# @param bound [Integer] the exclusive upper bound, 1..2^64
|
|
12551
|
-
# @return [(Integer, Rng)] the draw and the next generator
|
|
12552
|
-
#
|
|
12553
|
-
# @example
|
|
12554
|
-
# let (roll, _) = Rng.seeded(42).nextBounded(6) # => 0..5
|
|
12555
|
-
#
|
|
12556
|
-
# @example Rolling again with the threaded state
|
|
12557
|
-
# var rng = Rng.seeded(42)
|
|
12558
|
-
# let (first, advanced) = rng.nextBounded(6)
|
|
12559
|
-
# rng = advanced
|
|
12560
|
-
nextBounded :> Integer -> (Integer, Rng)
|
|
12561
|
-
let nextBounded(bound: Integer) -> (Integer, Rng) do
|
|
12562
|
-
if bound <= 0
|
|
12563
|
-
die("Rng.nextBounded: bound must be positive, got ${bound}")
|
|
12564
|
-
end
|
|
12565
|
-
if bound > Rng.maxBound
|
|
12566
|
-
die("Rng.nextBounded: bound does not fit in 64 bits")
|
|
12567
|
-
end
|
|
12568
|
-
let (draw, advanced) = this.nextUint64
|
|
12569
|
-
# Draws at or above this limit would make some residues likelier than
|
|
12570
|
-
# others; there is always less than one bound's worth of them, so the
|
|
12571
|
-
# expected number of redraws stays below two.
|
|
12572
|
-
let limit = Rng.maxBound - Rng.maxBound.modulo(bound)
|
|
12573
|
-
return (draw.modulo(bound), advanced) if draw < limit
|
|
12574
|
-
return advanced.nextBounded(bound)
|
|
13100
|
+
# Draws a float; defaults to [0.0, 1.0). The upper bound is excluded.
|
|
13101
|
+
# @param range [Range<Float>] finite, strictly ascending bounds
|
|
13102
|
+
# @return [Float] drawn value
|
|
13103
|
+
foul float(range: Range<Float> = 0.0..1.0) -> Float do
|
|
13104
|
+
let (value, _) = drawFloat(fresh, range)
|
|
13105
|
+
value
|
|
12575
13106
|
end
|
|
12576
13107
|
|
|
12577
|
-
# Draws a
|
|
12578
|
-
#
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
#
|
|
12583
|
-
# @return [(Float, Rng)] the draw and the next generator
|
|
12584
|
-
#
|
|
12585
|
-
# @example
|
|
12586
|
-
# let (unit, _) = Rng.seeded(42).nextFloat # => 0.0..1.0
|
|
12587
|
-
nextFloat :> (Float, Rng)
|
|
12588
|
-
let nextFloat do
|
|
12589
|
-
let (draw, advanced) = this.nextUint64
|
|
12590
|
-
# Into a float by multiplication, not conversion: `* 1.0` is the
|
|
12591
|
-
# typed idiom (`Duration.seconds` is built the same way).
|
|
12592
|
-
let mantissa = Kex.Intrinsic.Bits.shiftRight(draw, 11) * 1.0
|
|
12593
|
-
return (mantissa / 9007199254740992.0, advanced)
|
|
13108
|
+
# Draws a fair coin flip using fresh entropy.
|
|
13109
|
+
# @return [Bool] true or false with equal probability
|
|
13110
|
+
foul boolean -> Bool do
|
|
13111
|
+
let (value, _) = drawBoolean(fresh)
|
|
13112
|
+
value
|
|
12594
13113
|
end
|
|
12595
13114
|
|
|
12596
|
-
#
|
|
12597
|
-
#
|
|
12598
|
-
#
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
#
|
|
12603
|
-
# @example
|
|
12604
|
-
# let (heads, _) = Rng.seeded(42).nextBoolean
|
|
12605
|
-
nextBoolean :> (Bool, Rng)
|
|
12606
|
-
let nextBoolean do
|
|
12607
|
-
let (draw, advanced) = this.nextUint64
|
|
12608
|
-
return (Kex.Intrinsic.Bits.shiftRight(draw, 63) == 1, advanced)
|
|
13115
|
+
# Returns true with the supplied probability using fresh entropy.
|
|
13116
|
+
# @param probability [Float] probability from 0.0 through 1.0
|
|
13117
|
+
# @return [Bool] sampled outcome
|
|
13118
|
+
foul chance?(probability: Float) -> Bool do
|
|
13119
|
+
let (value, _) = drawChance(fresh, probability)
|
|
13120
|
+
value
|
|
12609
13121
|
end
|
|
12610
13122
|
|
|
12611
|
-
#
|
|
12612
|
-
#
|
|
12613
|
-
#
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
# @param items [[A]] the elements to order
|
|
12618
|
-
# @return [([A], Rng)] the shuffled elements and the next generator
|
|
12619
|
-
#
|
|
12620
|
-
# @example
|
|
12621
|
-
# let (order, _) = Rng.seeded(42).shuffle([1, 2, 3])
|
|
12622
|
-
shuffle :> [A] -> ([A], Rng)
|
|
12623
|
-
let shuffle(items: [A]) -> ([A], Rng) do
|
|
12624
|
-
var pool = items
|
|
12625
|
-
var out: [A] = []
|
|
12626
|
-
var state = this
|
|
12627
|
-
while !pool.empty? do
|
|
12628
|
-
let (index, advanced) = state.nextBounded(pool.count)
|
|
12629
|
-
state = advanced
|
|
12630
|
-
# `index` is below `pool.count` by construction, so `None` is
|
|
12631
|
-
# unreachable; `die` says what the typechecker cannot.
|
|
12632
|
-
let picked = match pool.at(index) do
|
|
12633
|
-
Just(x) => x
|
|
12634
|
-
None => die("Rng.shuffle: unreachable empty draw")
|
|
12635
|
-
end
|
|
12636
|
-
out.push!(picked)
|
|
12637
|
-
pool = pool.take(index) + pool.drop(index + 1)
|
|
12638
|
-
end
|
|
12639
|
-
return (out, state)
|
|
13123
|
+
# Chooses a position uniformly, returning None for an empty input.
|
|
13124
|
+
# @param items [[X]] values to choose from
|
|
13125
|
+
# @return [X?] chosen value, if any
|
|
13126
|
+
foul choice(items: [X]) -> X? do
|
|
13127
|
+
let (value, _) = drawChoice(fresh, items)
|
|
13128
|
+
value
|
|
12640
13129
|
end
|
|
12641
13130
|
|
|
12642
|
-
#
|
|
12643
|
-
#
|
|
12644
|
-
#
|
|
12645
|
-
#
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
# @param n [Integer] how many to draw, 0 or more
|
|
12650
|
-
# @return [([A], Rng)] the drawn elements and the next generator
|
|
12651
|
-
#
|
|
12652
|
-
# @example
|
|
12653
|
-
# let (hand, _) = Rng.seeded(42).sample([1, 2, 3, 4, 5], 2)
|
|
12654
|
-
# hand.count # => 2
|
|
12655
|
-
sample :> [A] -> Integer -> ([A], Rng)
|
|
12656
|
-
let sample(items: [A], n: Integer) -> ([A], Rng) do
|
|
12657
|
-
if n < 0
|
|
12658
|
-
die("Rng.sample: count must not be negative, got ${n}")
|
|
12659
|
-
end
|
|
12660
|
-
let (order, advanced) = this.shuffle(items)
|
|
12661
|
-
return (order.take(n), advanced)
|
|
13131
|
+
# Samples positions without replacement; duplicate values remain possible.
|
|
13132
|
+
# @param items [[X]] population
|
|
13133
|
+
# @param count [Integer] nonnegative count; capped at the input size
|
|
13134
|
+
# @return [[X]] values selected in random order
|
|
13135
|
+
foul sample(items: [X], count: Integer) -> [X] do
|
|
13136
|
+
let (value, _) = drawSample(fresh, items, count)
|
|
13137
|
+
value
|
|
12662
13138
|
end
|
|
12663
13139
|
|
|
12664
|
-
#
|
|
12665
|
-
#
|
|
12666
|
-
#
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
# @param items [[A]] the elements to draw from
|
|
12671
|
-
# @return [(A?, Rng)] the drawn element, if any, and the next generator
|
|
12672
|
-
#
|
|
12673
|
-
# @example
|
|
12674
|
-
# let (pick, _) = Rng.seeded(42).choice(["a", "b", "c"])
|
|
12675
|
-
# ["a", "b", "c"].contains?(pick.or("")) # => true
|
|
12676
|
-
choice :> [A] -> (A?, Rng)
|
|
12677
|
-
let choice(items: [A]) -> (A?, Rng) do
|
|
12678
|
-
return (None, this) if items.empty?
|
|
12679
|
-
let (index, advanced) = this.nextBounded(items.count)
|
|
12680
|
-
return (items.at(index), advanced)
|
|
13140
|
+
# Returns a random permutation using fresh entropy.
|
|
13141
|
+
# @param items [[X]] values to shuffle
|
|
13142
|
+
# @return [[X]] shuffled copy
|
|
13143
|
+
foul shuffle(items: [X]) -> [X] do
|
|
13144
|
+
let (value, _) = drawSample(fresh, items, items.count)
|
|
13145
|
+
value
|
|
12681
13146
|
end
|
|
12682
|
-
end
|
|
12683
13147
|
|
|
12684
|
-
#
|
|
12685
|
-
#
|
|
12686
|
-
#
|
|
12687
|
-
# runs the deterministic core above on it, so answers differ between runs
|
|
12688
|
-
# the way the clock differs between reads. That is why every one is
|
|
12689
|
-
# +foul+: the same call with the same arguments may answer differently.
|
|
12690
|
-
#
|
|
12691
|
-
# Reach for +Rng+ instead when the answers must repeat: seeded simulation,
|
|
12692
|
-
# property tests, anything asserting on a particular draw.
|
|
12693
|
-
module Random do
|
|
12694
|
-
# Builds a generator from fresh host entropy.
|
|
13148
|
+
# Returns +count+ bytes from the operating system's cryptographically
|
|
13149
|
+
# secure generator: the source for keys, session tokens and unguessable
|
|
13150
|
+
# identifiers, which the generators above must never be used for.
|
|
12695
13151
|
#
|
|
12696
|
-
#
|
|
12697
|
-
# seed once here, then draw purely from the answer.
|
|
13152
|
+
# Nothing about it is reproducible, and there is no +Generator+ form.
|
|
12698
13153
|
#
|
|
12699
|
-
# @
|
|
13154
|
+
# @param count [Integer] how many bytes; zero or less gives an empty binary
|
|
13155
|
+
# @return [Binary] the random bytes
|
|
12700
13156
|
#
|
|
12701
|
-
# @example
|
|
12702
|
-
#
|
|
12703
|
-
|
|
12704
|
-
# rng = advanced
|
|
12705
|
-
foul fresh() -> Rng do
|
|
12706
|
-
let high = Math.floor(Kex.Intrinsic.Retry.randomUnit() * 4294967296.0)
|
|
12707
|
-
let low = Math.floor(Kex.Intrinsic.Retry.randomUnit() * 4294967296.0)
|
|
12708
|
-
return Rng.seeded(high * 4294967296 + low)
|
|
12709
|
-
end
|
|
13157
|
+
# @example A 256-bit key
|
|
13158
|
+
# let key = Random.secureBytes(32)
|
|
13159
|
+
foul secureBytes(count: Integer) -> Binary = Kex.Intrinsic.Random.secureBytes(count)
|
|
12710
13160
|
|
|
12711
|
-
#
|
|
12712
|
-
#
|
|
13161
|
+
# Returns an unguessable token: +bytes+ secure random bytes, as lowercase
|
|
13162
|
+
# hex, so the text is twice as long as +bytes+.
|
|
12713
13163
|
#
|
|
12714
|
-
# @param
|
|
12715
|
-
# @return [
|
|
13164
|
+
# @param bytes [Integer] how many random bytes the token carries
|
|
13165
|
+
# @return [String] the token
|
|
12716
13166
|
#
|
|
12717
|
-
# @example
|
|
12718
|
-
# let
|
|
12719
|
-
|
|
12720
|
-
let seeded(seed: Integer) -> Rng = Rng.seeded(seed)
|
|
13167
|
+
# @example A session identifier
|
|
13168
|
+
# let session = Random.token(32) # => "9f86d081884c7d65..." (64 characters)
|
|
13169
|
+
foul token(bytes: Integer = 32) -> String = secureBytes(bytes).hex
|
|
12721
13170
|
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
# let jitter = Random.float() * maxJitter
|
|
12728
|
-
foul float() -> Float do
|
|
12729
|
-
let (value, _) = Random.fresh().nextFloat
|
|
12730
|
-
return value
|
|
12731
|
-
end
|
|
13171
|
+
private do
|
|
13172
|
+
let drawInteger(source: Generator, range: Range<Integer>) -> (Integer, Generator) do
|
|
13173
|
+
let (low, high) = integerBounds(range)
|
|
13174
|
+
integerBetween(source, low, high)
|
|
13175
|
+
end
|
|
12732
13176
|
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
let (value, _) = Random.fresh().nextBounded(bound)
|
|
12743
|
-
return value
|
|
12744
|
-
end
|
|
13177
|
+
let drawFloat(source: Generator, range: Range<Float>) -> (Float, Generator) do
|
|
13178
|
+
let (low, high) = floatBounds(range)
|
|
13179
|
+
if !(low < high)
|
|
13180
|
+
die("Random.float: bounds must be finite and strictly ascending")
|
|
13181
|
+
end
|
|
13182
|
+
let (word, advanced) = nextWord(source)
|
|
13183
|
+
let unit = (Kex.Intrinsic.Bits.shiftRight(word, 11) * 1.0) / 9007199254740992.0
|
|
13184
|
+
(Kex.Intrinsic.Float.scaleUnit(low, high, unit), advanced)
|
|
13185
|
+
end
|
|
12745
13186
|
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
# @param low [Integer] the smallest answer
|
|
12750
|
-
# @param high [Integer] the largest answer
|
|
12751
|
-
# @return [Integer] the draw
|
|
12752
|
-
#
|
|
12753
|
-
# @example
|
|
12754
|
-
# Random.between(1, 6) # => a die roll
|
|
12755
|
-
foul between(low: Integer, high: Integer) -> Integer do
|
|
12756
|
-
if low > high
|
|
12757
|
-
die("Random.between: low must not exceed high, got ${low}..${high}")
|
|
13187
|
+
let drawBoolean(source: Generator) -> (Bool, Generator) do
|
|
13188
|
+
let (word, advanced) = nextWord(source)
|
|
13189
|
+
(Kex.Intrinsic.Bits.shiftRight(word, 63) == 1, advanced)
|
|
12758
13190
|
end
|
|
12759
|
-
return low + Random.integer(high - low + 1)
|
|
12760
|
-
end
|
|
12761
13191
|
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
let (value, _) = Random.fresh().nextBoolean
|
|
12770
|
-
return value
|
|
12771
|
-
end
|
|
13192
|
+
let drawChance(source: Generator, probability: Float) -> (Bool, Generator) do
|
|
13193
|
+
if !(probability >= 0.0 && probability <= 1.0)
|
|
13194
|
+
die("Random.chance?: probability must be between 0.0 and 1.0")
|
|
13195
|
+
end
|
|
13196
|
+
let (value, advanced) = drawFloat(source, 0.0..1.0)
|
|
13197
|
+
(value < probability, advanced)
|
|
13198
|
+
end
|
|
12772
13199
|
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
# @return [Bool] +true+ with probability +p+
|
|
12778
|
-
#
|
|
12779
|
-
# @example
|
|
12780
|
-
# Random.chance?(0.25) # => true about one call in four
|
|
12781
|
-
#
|
|
12782
|
-
# @example Simulating a failure rate
|
|
12783
|
-
# if Random.chance?(0.01) then Error("flaky") else Ok(send(request)) end
|
|
12784
|
-
foul chance?(p: Float) -> Bool do
|
|
12785
|
-
if p < 0.0 || p > 1.0
|
|
12786
|
-
die("Random.chance?: chance must be inside 0.0..1.0, got ${p}")
|
|
13200
|
+
let drawChoice(source: Generator, items: [X]) -> (X?, Generator) do
|
|
13201
|
+
return (None, source) if items.empty?
|
|
13202
|
+
let (index, advanced) = below(source, items.count)
|
|
13203
|
+
(items.at(index), advanced)
|
|
12787
13204
|
end
|
|
12788
|
-
return Random.float() < p
|
|
12789
|
-
end
|
|
12790
13205
|
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
13206
|
+
let drawSample(source: Generator, items: [X], count: Integer) -> ([X], Generator) do
|
|
13207
|
+
if count < 0
|
|
13208
|
+
die("Random.sample: count must not be negative")
|
|
13209
|
+
end
|
|
13210
|
+
var pool = items
|
|
13211
|
+
var selected: [X] = []
|
|
13212
|
+
var generator = source
|
|
13213
|
+
while !pool.empty? && selected.count < count do
|
|
13214
|
+
let (index, advanced) = below(generator, pool.count)
|
|
13215
|
+
generator = advanced
|
|
13216
|
+
selected.push!(pool.at(index).try)
|
|
13217
|
+
pool = pool.take(index) + pool.drop(index + 1)
|
|
13218
|
+
end
|
|
13219
|
+
(selected, generator)
|
|
13220
|
+
end
|
|
12802
13221
|
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
# @example
|
|
12812
|
-
# Random.sample(["a", "b", "c", "d"], 2) # => two of the four
|
|
12813
|
-
foul sample(items: [A], n: Integer) -> [A] do
|
|
12814
|
-
let (value, _) = Random.fresh().sample(items, n)
|
|
12815
|
-
return value
|
|
12816
|
-
end
|
|
13222
|
+
integerBetween : Generator -> Integer -> Integer -> (Integer, Generator)
|
|
13223
|
+
let integerBetween(generator, low, high) do
|
|
13224
|
+
if low > high
|
|
13225
|
+
die("Random.integer: lower bound must not exceed upper bound")
|
|
13226
|
+
end
|
|
13227
|
+
let (value, advanced) = below(generator, high - low + 1)
|
|
13228
|
+
(low + value, advanced)
|
|
13229
|
+
end
|
|
12817
13230
|
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
let (
|
|
12827
|
-
|
|
13231
|
+
integerBounds : Range<Integer> -> (Integer, Integer)
|
|
13232
|
+
let integerBounds(range) = Kex.Intrinsic.Range.bounds(range)
|
|
13233
|
+
|
|
13234
|
+
floatBounds : Range<Float> -> (Float, Float)
|
|
13235
|
+
let floatBounds(range) = Kex.Intrinsic.Range.bounds(range)
|
|
13236
|
+
|
|
13237
|
+
# SplitMix64's arithmetic stays private; public operations expose values.
|
|
13238
|
+
nextWord : Generator -> (Integer, Generator)
|
|
13239
|
+
let nextWord(generator: Generator) -> (Integer, Generator) do
|
|
13240
|
+
let mask = 18446744073709551615
|
|
13241
|
+
let advanced = Kex.Intrinsic.Bits.and(generator.state + 0x9e3779b97f4a7c15, mask)
|
|
13242
|
+
let spread1 = Kex.Intrinsic.Bits.xor(advanced, Kex.Intrinsic.Bits.shiftRight(advanced, 30))
|
|
13243
|
+
let mixed1 = Kex.Intrinsic.Bits.and(spread1 * 0xbf58476d1ce4e5b9, mask)
|
|
13244
|
+
let spread2 = Kex.Intrinsic.Bits.xor(mixed1, Kex.Intrinsic.Bits.shiftRight(mixed1, 27))
|
|
13245
|
+
let mixed2 = Kex.Intrinsic.Bits.and(spread2 * 0x94d049bb133111eb, mask)
|
|
13246
|
+
let word = Kex.Intrinsic.Bits.xor(mixed2, Kex.Intrinsic.Bits.shiftRight(mixed2, 31))
|
|
13247
|
+
(word, Generator { state: advanced })
|
|
13248
|
+
end
|
|
13249
|
+
|
|
13250
|
+
below : Generator -> Integer -> (Integer, Generator)
|
|
13251
|
+
let below(generator: Generator, bound: Integer) -> (Integer, Generator) do
|
|
13252
|
+
let maximum = 18446744073709551616
|
|
13253
|
+
if bound <= 0 || bound > maximum
|
|
13254
|
+
die("Random.integer: range must contain between 1 and 2^64 integers")
|
|
13255
|
+
end
|
|
13256
|
+
let (word, advanced) = nextWord(generator)
|
|
13257
|
+
let limit = maximum - maximum.modulo(bound)
|
|
13258
|
+
return (word.modulo(bound), advanced) if word < limit
|
|
13259
|
+
below(advanced, bound)
|
|
13260
|
+
end
|
|
12828
13261
|
end
|
|
12829
13262
|
end
|
|
12830
13263
|
# A span between two bounds, written +(1..10)+ or +('a'..'z')+.
|
|
12831
13264
|
#
|
|
12832
|
-
# A range stores
|
|
12833
|
-
#
|
|
13265
|
+
# A range stores its endpoints on both backends without building a list.
|
|
13266
|
+
# Integer and character ranges can be enumerated with both ends included.
|
|
13267
|
+
# Float ranges describe continuous bounds, for example +(-1.0..1.0)+ for
|
|
13268
|
+
# random sampling; they cannot be enumerated without an explicit step.
|
|
12834
13269
|
#
|
|
12835
13270
|
# (1..5).items # => [1, 2, 3, 4, 5]
|
|
12836
13271
|
# (1..5).sum # => 15
|
|
@@ -12914,11 +13349,8 @@ make Range, implement: Enumerable, Foldable do
|
|
|
12914
13349
|
# @example
|
|
12915
13350
|
# (3..7).first # => Just(3)
|
|
12916
13351
|
# The list operations, over the materialized items. A range is an ordered
|
|
12917
|
-
# collection, so
|
|
12918
|
-
#
|
|
12919
|
-
# already worked. Only the walker rejected them ("Undefined method: first
|
|
12920
|
-
# for Range"), so every one of these was a backend divergence rather than a
|
|
12921
|
-
# deliberate restriction.
|
|
13352
|
+
# collection, so +(1..5).first+ and +(1..5).length+ operate on its
|
|
13353
|
+
# materialized elements. Floating bounds do not define such a collection.
|
|
12922
13354
|
first :> A?
|
|
12923
13355
|
let first = this.items.first
|
|
12924
13356
|
|
|
@@ -16454,10 +16886,10 @@ module Time do
|
|
|
16454
16886
|
# Time.daysFromCivil(1970, 1, 1) # => 0
|
|
16455
16887
|
# Time.daysFromCivil(2026, 7, 30) # => 20664
|
|
16456
16888
|
let daysFromCivil(year: Integer, month: Integer, day: Integer) -> Integer do
|
|
16457
|
-
let y =
|
|
16458
|
-
let era =
|
|
16889
|
+
let y = month <= 2 then year - 1 else year
|
|
16890
|
+
let era = y < 0 then (y - 399) / 400 else y / 400
|
|
16459
16891
|
let yoe = y - era * 400
|
|
16460
|
-
let mp =
|
|
16892
|
+
let mp = month > 2 then month - 3 else month + 9
|
|
16461
16893
|
let doy = (153 * mp + 2) / 5 + day - 1
|
|
16462
16894
|
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
|
|
16463
16895
|
return era * 146097 + doe - 719468
|
|
@@ -16474,15 +16906,15 @@ module Time do
|
|
|
16474
16906
|
# Time.civilFromDays(20664).iso # => "2026-07-30"
|
|
16475
16907
|
let civilFromDays(epochDay: Integer) -> Date do
|
|
16476
16908
|
let z = epochDay + 719468
|
|
16477
|
-
let era =
|
|
16909
|
+
let era = z < 0 then (z - 146096) / 146097 else z / 146097
|
|
16478
16910
|
let doe = z - era * 146097
|
|
16479
16911
|
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
|
|
16480
16912
|
let y = yoe + era * 400
|
|
16481
16913
|
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100)
|
|
16482
16914
|
let mp = (5 * doy + 2) / 153
|
|
16483
16915
|
let day = doy - (153 * mp + 2) / 5 + 1
|
|
16484
|
-
let month =
|
|
16485
|
-
let year =
|
|
16916
|
+
let month = mp < 10 then mp + 3 else mp - 9
|
|
16917
|
+
let year = month <= 2 then y + 1 else y
|
|
16486
16918
|
return Date { year: year, month: month, day: day }
|
|
16487
16919
|
end
|
|
16488
16920
|
|
|
@@ -16617,8 +17049,8 @@ module Time do
|
|
|
16617
17049
|
let formatOffset(offset: Duration) -> String do
|
|
16618
17050
|
let total = offset.wholeSeconds
|
|
16619
17051
|
return "Z" if total == 0
|
|
16620
|
-
let sign =
|
|
16621
|
-
let magnitude =
|
|
17052
|
+
let sign = total < 0 then "-" else "+"
|
|
17053
|
+
let magnitude = total < 0 then 0 - total else total
|
|
16622
17054
|
return "${sign}${Time.pad2(magnitude / 3600)}:${Time.pad2(magnitude.modulo(3600) / 60)}"
|
|
16623
17055
|
end
|
|
16624
17056
|
|
|
@@ -17158,7 +17590,7 @@ make Duration do
|
|
|
17158
17590
|
#
|
|
17159
17591
|
# @example
|
|
17160
17592
|
# (1.hours - 90.minutes).abs.wholeMinutes # => 30
|
|
17161
|
-
let abs -> Duration = Duration { seconds:
|
|
17593
|
+
let abs -> Duration = Duration { seconds: this.seconds < 0.0 then 0.0 - this.seconds else this.seconds }
|
|
17162
17594
|
|
|
17163
17595
|
# Returns +true+ when the span is exactly zero.
|
|
17164
17596
|
#
|
|
@@ -17530,9 +17962,9 @@ make Period, implement: Inspectable, Showable do
|
|
|
17530
17962
|
# 2.months.iso # => "P2M"
|
|
17531
17963
|
let iso -> String do
|
|
17532
17964
|
return "P0D" if this.zero?
|
|
17533
|
-
let years =
|
|
17534
|
-
let months =
|
|
17535
|
-
let days =
|
|
17965
|
+
let years = this.years != 0 then "${this.years}Y" else ""
|
|
17966
|
+
let months = this.months != 0 then "${this.months}M" else ""
|
|
17967
|
+
let days = this.days != 0 then "${this.days}D" else ""
|
|
17536
17968
|
return "P${years}${months}${days}"
|
|
17537
17969
|
end
|
|
17538
17970
|
end
|
|
@@ -17696,7 +18128,7 @@ make Date, implement: Inspectable, Showable do
|
|
|
17696
18128
|
let month = total.modulo(12) + 1
|
|
17697
18129
|
let year = (total - total.modulo(12)) / 12
|
|
17698
18130
|
let last = Time.daysInValidMonth(year, month)
|
|
17699
|
-
let day =
|
|
18131
|
+
let day = this.day > last then last else this.day
|
|
17700
18132
|
return Date { year: year, month: month, day: day }
|
|
17701
18133
|
end
|
|
17702
18134
|
|
|
@@ -18580,7 +19012,7 @@ make Type do
|
|
|
18580
19012
|
# A function reads as its signature: `String -> String`, `foul () -> Void`.
|
|
18581
19013
|
if this.name == "Function" && this.args.count > 0
|
|
18582
19014
|
let arrow = this.args.map(&.to(String)).join(" -> ")
|
|
18583
|
-
let signature =
|
|
19015
|
+
let signature = this.args.count == 1 then "() -> ${arrow}" else arrow
|
|
18584
19016
|
return "foul ${signature}" if this.pure == false
|
|
18585
19017
|
return signature
|
|
18586
19018
|
end
|
|
@@ -18906,11 +19338,21 @@ make Float do
|
|
|
18906
19338
|
end
|
|
18907
19339
|
end
|
|
18908
19340
|
|
|
18909
|
-
make Measure do
|
|
19341
|
+
make Measure, implement: Showable do
|
|
18910
19342
|
let factor -> Float = this.unit.factor
|
|
18911
19343
|
let kind -> Atom = this.unit.kind
|
|
18912
19344
|
let symbol -> String = this.unit.symbol
|
|
18913
19345
|
let to(String) -> String = "${this.value} ${this.unit.symbol}"
|
|
19346
|
+
|
|
19347
|
+
# Renders the measure as its value and unit, so +IO.printLine+ and
|
|
19348
|
+
# interpolation show +5.0 W+ rather than the record's fields. Inspecting
|
|
19349
|
+
# it (the REPL's echo, +inspected+) still shows the record itself.
|
|
19350
|
+
#
|
|
19351
|
+
# @return [String] the value and the unit's symbol
|
|
19352
|
+
#
|
|
19353
|
+
# @example
|
|
19354
|
+
# "drawn: ${1.volt * 5.ampere}" # => "drawn: 5.0 W"
|
|
19355
|
+
let showValue -> String = this.to(String)
|
|
18914
19356
|
# Formatting into a target display unit deliberately has NO prelude clause.
|
|
18915
19357
|
# A unit module (Units.SI, Units.Data, ...) supplies `to(String, in:)` for
|
|
18916
19358
|
# the units it owns, and reaching one requires importing it. A catch-all
|