@grain/stdlib 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/array.gr +61 -1
  3. package/array.md +113 -0
  4. package/bigint.md +30 -30
  5. package/buffer.gr +24 -22
  6. package/char.gr +2 -2
  7. package/float32.md +3 -3
  8. package/float64.md +3 -3
  9. package/immutablemap.gr +493 -0
  10. package/immutablemap.md +479 -0
  11. package/immutablepriorityqueue.gr +360 -0
  12. package/immutablepriorityqueue.md +291 -0
  13. package/immutableset.gr +498 -0
  14. package/immutableset.md +449 -0
  15. package/list.gr +75 -2
  16. package/list.md +110 -0
  17. package/map.gr +1 -2
  18. package/marshal.gr +1058 -0
  19. package/marshal.md +76 -0
  20. package/number.gr +689 -23
  21. package/number.md +362 -27
  22. package/package.json +1 -1
  23. package/pervasives.gr +16 -5
  24. package/pervasives.md +28 -0
  25. package/priorityqueue.gr +261 -0
  26. package/priorityqueue.md +309 -0
  27. package/queue.gr +14 -1
  28. package/queue.md +16 -1
  29. package/regex.gr +90 -67
  30. package/runtime/bigint.gr +4 -4
  31. package/runtime/compare.gr +179 -0
  32. package/runtime/compare.md +6 -0
  33. package/runtime/equal.gr +3 -3
  34. package/runtime/exception.gr +9 -5
  35. package/runtime/exception.md +8 -2
  36. package/runtime/gc.gr +2 -1
  37. package/runtime/malloc.gr +1 -3
  38. package/runtime/numberUtils.gr +11 -11
  39. package/runtime/numbers.gr +423 -100
  40. package/runtime/numbers.md +50 -0
  41. package/runtime/string.gr +4 -2
  42. package/set.gr +26 -27
  43. package/stack.gr +12 -0
  44. package/stack.md +15 -0
  45. package/string.gr +409 -53
  46. package/string.md +164 -1
  47. package/sys/file.gr +4 -4
  48. package/sys/file.md +3 -3
  49. package/sys/process.gr +3 -3
  50. package/sys/process.md +3 -3
  51. package/sys/random.gr +2 -2
  52. package/sys/random.md +2 -2
  53. package/sys/time.gr +2 -2
  54. package/sys/time.md +2 -2
package/marshal.gr ADDED
@@ -0,0 +1,1058 @@
1
+ /**
2
+ * @module Marshal: Utilities for serializing and deserializing Grain data.
3
+ *
4
+ * @example import Marshal from "marshal"
5
+ *
6
+ * @since v0.5.3
7
+ */
8
+
9
+ /*
10
+ SERIALIZED BINARY FORMAT
11
+
12
+ Grain stack-allocated values are serialized as-is. Heap-allocated values are
13
+ largely serialized the same as their in-memory representation, with the
14
+ exception that pointers are offsets into the byte sequence rather than
15
+ pointers into memory. Structures are kept 8-byte aligned to be consistent
16
+ with Grain's in-memory representation. The order in which these structures
17
+ appear in the byte sequence is not significant.
18
+
19
+ The first 32-bit value in the byte sequence is either a stack-allocated value
20
+ or a "pointer" to a heap-allocated value.
21
+ */
22
+
23
+ /**
24
+ * @section Values: Functions for marshaling and unmarshaling data.
25
+ */
26
+
27
+ import WasmI32, {
28
+ add as (+),
29
+ mul as (*),
30
+ and as (&),
31
+ eq as (==),
32
+ ne as (!=),
33
+ gtU as (>),
34
+ ltU as (<),
35
+ leU as (<=),
36
+ load,
37
+ store,
38
+ fromGrain,
39
+ toGrain,
40
+ } from "runtime/unsafe/wasmi32"
41
+ import WasmI64 from "runtime/unsafe/wasmi64"
42
+ import Memory from "runtime/unsafe/memory"
43
+ import Tags from "runtime/unsafe/tags"
44
+ import { allocateBytes, newInt32 } from "runtime/dataStructures"
45
+ import Map from "map"
46
+ import Set from "set"
47
+ import Option from "option"
48
+
49
+ @unsafe
50
+ let roundTo8 = n => {
51
+ n + 7n & 0xfffffff8n
52
+ }
53
+
54
+ @unsafe
55
+ let isHeapPtr = value =>
56
+ (value & Tags._GRAIN_GENERIC_TAG_MASK) ==
57
+ Tags._GRAIN_GENERIC_HEAP_TAG_TYPE
58
+
59
+ @unsafe
60
+ let rec size = (value, acc, valuesSeen, toplevel) => {
61
+ if (isHeapPtr(value)) {
62
+ let asInt32 = toGrain(newInt32(value)): Int32
63
+ if (Set.contains(asInt32, valuesSeen)) {
64
+ // We've detected a cycle, and we'll refer to the existing instance
65
+ acc
66
+ } else {
67
+ Set.add(asInt32, valuesSeen)
68
+
69
+ let acc = if (toplevel) {
70
+ // We'll write a word (and 32 bits of padding) to indicate that this is a heap value
71
+ acc + 8n
72
+ } else {
73
+ acc
74
+ }
75
+ let heapPtr = value
76
+ match (load(heapPtr, 0n)) {
77
+ t when (
78
+ t == Tags._GRAIN_STRING_HEAP_TAG || t == Tags._GRAIN_BYTES_HEAP_TAG
79
+ ) => {
80
+ acc + roundTo8(8n + load(heapPtr, 4n))
81
+ },
82
+ t when t == Tags._GRAIN_ADT_HEAP_TAG => {
83
+ let arity = load(heapPtr, 16n)
84
+
85
+ let mut acc = acc + roundTo8(20n + arity * 4n)
86
+
87
+ let a = arity * 4n
88
+ for (let mut i = 0n; i < a; i += 4n) {
89
+ acc = size(load(heapPtr + i, 20n), acc, valuesSeen, false)
90
+ }
91
+
92
+ acc
93
+ },
94
+ t when t == Tags._GRAIN_RECORD_HEAP_TAG => {
95
+ let arity = load(heapPtr, 12n)
96
+
97
+ let mut acc = acc + roundTo8(16n + arity * 4n)
98
+
99
+ let a = arity * 4n
100
+ for (let mut i = 0n; i < a; i += 4n) {
101
+ acc = size(load(heapPtr + i, 16n), acc, valuesSeen, false)
102
+ }
103
+
104
+ acc
105
+ },
106
+ t when t == Tags._GRAIN_ARRAY_HEAP_TAG => {
107
+ let arity = load(heapPtr, 4n)
108
+
109
+ let mut acc = acc + roundTo8(8n + arity * 4n)
110
+
111
+ let a = arity * 4n
112
+ for (let mut i = 0n; i < a; i += 4n) {
113
+ acc = size(load(heapPtr + i, 8n), acc, valuesSeen, false)
114
+ }
115
+
116
+ acc
117
+ },
118
+ t when t == Tags._GRAIN_TUPLE_HEAP_TAG => {
119
+ let arity = load(heapPtr, 4n)
120
+
121
+ let mut acc = acc + roundTo8(8n + arity * 4n)
122
+
123
+ let l = arity * 4n
124
+ for (let mut i = 0n; i < l; i += 4n) {
125
+ acc = size(load(heapPtr + i, 8n), acc, valuesSeen, false)
126
+ }
127
+
128
+ acc
129
+ },
130
+ t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
131
+ let arity = load(heapPtr, 12n)
132
+
133
+ let mut acc = acc + roundTo8(16n + arity * 4n)
134
+
135
+ let a = arity * 4n
136
+ for (let mut i = 0n; i < a; i += 4n) {
137
+ acc = size(load(heapPtr + i, 16n), acc, valuesSeen, false)
138
+ }
139
+
140
+ acc
141
+ },
142
+ t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => {
143
+ let tag = load(heapPtr, 4n)
144
+ match (tag) {
145
+ t when (
146
+ t == Tags._GRAIN_INT32_BOXED_NUM_TAG ||
147
+ t == Tags._GRAIN_INT64_BOXED_NUM_TAG ||
148
+ t == Tags._GRAIN_FLOAT32_BOXED_NUM_TAG ||
149
+ t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG
150
+ ) => {
151
+ // The 32-bit values only take 12 bytes of memory, but we report 16
152
+ // for alignment
153
+ acc + 16n
154
+ },
155
+ t when t == Tags._GRAIN_BIGINT_BOXED_NUM_TAG => {
156
+ acc + 16n + load(heapPtr, 8n) * 8n
157
+ },
158
+ t when t == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG => {
159
+ acc +
160
+ 16n +
161
+ size(load(value, 8n), 0n, valuesSeen, false) +
162
+ size(load(value, 12n), 0n, valuesSeen, false)
163
+ },
164
+ _ => {
165
+ fail "Unknown number type"
166
+ },
167
+ }
168
+ },
169
+ _ => {
170
+ fail "Unknown heap type"
171
+ },
172
+ }
173
+ }
174
+ } else {
175
+ // Handle non-heap values: booleans, chars, void, etc.
176
+ if (toplevel) {
177
+ acc + 4n
178
+ } else {
179
+ acc
180
+ }
181
+ }
182
+ }
183
+
184
+ @unsafe
185
+ let size = value => {
186
+ size(value, 0n, Set.make(), true)
187
+ }
188
+
189
+ @unsafe
190
+ let rec marshalHeap = (heapPtr, buf, offset, valuesSeen) => {
191
+ let asInt32 = toGrain(newInt32(heapPtr)): Int32
192
+ let offsetAsInt32 = toGrain(newInt32(offset)): Int32
193
+ Map.set(asInt32, offsetAsInt32, valuesSeen)
194
+
195
+ match (load(heapPtr, 0n)) {
196
+ t when (
197
+ t == Tags._GRAIN_STRING_HEAP_TAG || t == Tags._GRAIN_BYTES_HEAP_TAG
198
+ ) => {
199
+ let size = 8n + load(heapPtr, 4n)
200
+ Memory.copy(buf + offset, heapPtr, size)
201
+ roundTo8(offset + size)
202
+ },
203
+ t when t == Tags._GRAIN_ADT_HEAP_TAG => {
204
+ Memory.copy(buf + offset, heapPtr, 20n)
205
+
206
+ let arity = load(heapPtr, 16n)
207
+
208
+ let mut payloadOffset = roundTo8(offset + 20n + arity * 4n)
209
+
210
+ let a = arity * 4n
211
+ for (let mut i = 0n; i < a; i += 4n) {
212
+ let value = load(heapPtr + i, 20n)
213
+ if (isHeapPtr(value)) {
214
+ let asInt32 = toGrain(newInt32(value)): Int32
215
+ match (Map.get(asInt32, valuesSeen)) {
216
+ Some(value) => {
217
+ let ptr = load(fromGrain(value), 8n)
218
+ store(buf, ptr, offset + i + 20n)
219
+ },
220
+ None => {
221
+ store(buf, payloadOffset, offset + i + 20n)
222
+ payloadOffset = marshalHeap(value, buf, payloadOffset, valuesSeen)
223
+ },
224
+ }
225
+ } else {
226
+ store(buf, value, offset + i + 20n)
227
+ }
228
+ }
229
+
230
+ payloadOffset
231
+ },
232
+ t when t == Tags._GRAIN_RECORD_HEAP_TAG => {
233
+ Memory.copy(buf + offset, heapPtr, 16n)
234
+
235
+ let arity = load(heapPtr, 12n)
236
+
237
+ let mut payloadOffset = roundTo8(offset + 16n + arity * 4n)
238
+
239
+ let a = arity * 4n
240
+ for (let mut i = 0n; i < a; i += 4n) {
241
+ let value = load(heapPtr + i, 16n)
242
+ if (isHeapPtr(value)) {
243
+ let asInt32 = toGrain(newInt32(value)): Int32
244
+ match (Map.get(asInt32, valuesSeen)) {
245
+ Some(value) => {
246
+ let ptr = load(fromGrain(value), 8n)
247
+ store(buf, ptr, offset + i + 16n)
248
+ },
249
+ None => {
250
+ store(buf, payloadOffset, offset + i + 16n)
251
+ payloadOffset = marshalHeap(value, buf, payloadOffset, valuesSeen)
252
+ },
253
+ }
254
+ } else {
255
+ store(buf, value, offset + i + 16n)
256
+ }
257
+ }
258
+
259
+ payloadOffset
260
+ },
261
+ t when t == Tags._GRAIN_ARRAY_HEAP_TAG => {
262
+ Memory.copy(buf + offset, heapPtr, 8n)
263
+
264
+ let arity = load(heapPtr, 4n)
265
+
266
+ let mut payloadOffset = roundTo8(offset + 8n + arity * 4n)
267
+
268
+ let a = arity * 4n
269
+ for (let mut i = 0n; i < a; i += 4n) {
270
+ let value = load(heapPtr + i, 8n)
271
+ if (isHeapPtr(value)) {
272
+ let asInt32 = toGrain(newInt32(value)): Int32
273
+ match (Map.get(asInt32, valuesSeen)) {
274
+ Some(value) => {
275
+ let ptr = load(fromGrain(value), 8n)
276
+ store(buf, ptr, offset + i + 8n)
277
+ },
278
+ None => {
279
+ store(buf, payloadOffset, offset + i + 8n)
280
+ payloadOffset = marshalHeap(value, buf, payloadOffset, valuesSeen)
281
+ },
282
+ }
283
+ } else {
284
+ store(buf, value, offset + i + 8n)
285
+ }
286
+ }
287
+
288
+ payloadOffset
289
+ },
290
+ t when t == Tags._GRAIN_TUPLE_HEAP_TAG => {
291
+ Memory.copy(buf + offset, heapPtr, 8n)
292
+
293
+ let arity = load(heapPtr, 4n)
294
+
295
+ let mut payloadOffset = roundTo8(offset + 8n + arity * 4n)
296
+
297
+ let a = arity * 4n
298
+ for (let mut i = 0n; i < a; i += 4n) {
299
+ let value = load(heapPtr + i, 8n)
300
+ if (isHeapPtr(value)) {
301
+ let asInt32 = toGrain(newInt32(value)): Int32
302
+ match (Map.get(asInt32, valuesSeen)) {
303
+ Some(value) => {
304
+ let ptr = load(fromGrain(value), 8n)
305
+ store(buf, ptr, offset + i + 8n)
306
+ },
307
+ None => {
308
+ store(buf, payloadOffset, offset + i + 8n)
309
+ payloadOffset = marshalHeap(value, buf, payloadOffset, valuesSeen)
310
+ },
311
+ }
312
+ } else {
313
+ store(buf, value, offset + i + 8n)
314
+ }
315
+ }
316
+
317
+ payloadOffset
318
+ },
319
+ t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
320
+ Memory.copy(buf + offset, heapPtr, 16n)
321
+
322
+ let arity = load(heapPtr, 12n)
323
+
324
+ let mut payloadOffset = roundTo8(offset + 16n + arity * 4n)
325
+
326
+ let a = arity * 4n
327
+ for (let mut i = 0n; i < a; i += 4n) {
328
+ let value = load(heapPtr + i, 16n)
329
+ if (isHeapPtr(value)) {
330
+ let asInt32 = toGrain(newInt32(value)): Int32
331
+ match (Map.get(asInt32, valuesSeen)) {
332
+ Some(value) => {
333
+ let ptr = load(fromGrain(value), 8n)
334
+ store(buf, ptr, offset + i + 16n)
335
+ },
336
+ None => {
337
+ store(buf, payloadOffset, offset + i + 16n)
338
+ payloadOffset = marshalHeap(value, buf, payloadOffset, valuesSeen)
339
+ },
340
+ }
341
+ } else {
342
+ store(buf, value, offset + i + 16n)
343
+ }
344
+ }
345
+
346
+ payloadOffset
347
+ },
348
+ t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => {
349
+ let tag = load(heapPtr, 4n)
350
+ match (tag) {
351
+ t when t == Tags._GRAIN_INT32_BOXED_NUM_TAG => {
352
+ Memory.copy(buf + offset, heapPtr, 12n)
353
+ offset + 16n
354
+ },
355
+ t when t == Tags._GRAIN_INT64_BOXED_NUM_TAG => {
356
+ Memory.copy(buf + offset, heapPtr, 16n)
357
+ offset + 16n
358
+ },
359
+ t when t == Tags._GRAIN_BIGINT_BOXED_NUM_TAG => {
360
+ let size = 16n + load(heapPtr, 8n) * 8n
361
+ Memory.copy(buf + offset, heapPtr, size)
362
+ offset + size
363
+ },
364
+ t when t == Tags._GRAIN_FLOAT32_BOXED_NUM_TAG => {
365
+ Memory.copy(buf + offset, heapPtr, 12n)
366
+ offset + 16n
367
+ },
368
+ t when t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG => {
369
+ Memory.copy(buf + offset, heapPtr, 16n)
370
+ offset + 16n
371
+ },
372
+ t when t == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG => {
373
+ Memory.copy(buf + offset, heapPtr, 8n)
374
+ let mut payloadOffset = offset + 16n
375
+ store(buf, payloadOffset, offset + 8n)
376
+ payloadOffset = marshalHeap(
377
+ load(heapPtr, 8n),
378
+ buf,
379
+ payloadOffset,
380
+ valuesSeen
381
+ )
382
+ store(buf, payloadOffset, offset + 12n)
383
+ payloadOffset = marshalHeap(
384
+ load(heapPtr, 12n),
385
+ buf,
386
+ payloadOffset,
387
+ valuesSeen
388
+ )
389
+ payloadOffset
390
+ },
391
+ _ => {
392
+ fail "Unknown number type"
393
+ },
394
+ }
395
+ },
396
+ _ => {
397
+ fail "Unknown heap type"
398
+ },
399
+ }
400
+ }
401
+
402
+ @unsafe
403
+ let marshal = (value, buf) => {
404
+ if (isHeapPtr(value)) {
405
+ store(buf, 8n, 0n)
406
+ marshalHeap(value, buf, 8n, Map.make())
407
+ } else {
408
+ // Handle non-heap values: booleans, numbers, chars, void, etc.
409
+ store(buf, value, 0n)
410
+ 4n
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Serialize a value into a byte-based representation suitable for transmission
416
+ * across a network or disk storage. The byte-based representation can be
417
+ * deserialized at a later time to restore the value.
418
+ *
419
+ * @param value: The value to serialize
420
+ * @returns A byte-based representation of the value
421
+ *
422
+ * @since v0.5.3
423
+ */
424
+ @unsafe
425
+ export let marshal = value => {
426
+ let valuePtr = fromGrain(value)
427
+ let buf = allocateBytes(size(valuePtr))
428
+ marshal(valuePtr, buf + 8n)
429
+ toGrain(buf): Bytes
430
+ }
431
+
432
+ @unsafe
433
+ let reportError = (message, offset) => {
434
+ Some(message ++ " at offset " ++ toString(toGrain(newInt32(offset))))
435
+ }
436
+
437
+ // When Grain has exception handling, validation could potentially occur during
438
+ // unmarshaling.
439
+
440
+ @unsafe
441
+ let validateStack = (value, offset) => {
442
+ match (value) {
443
+ _ when (
444
+ value == fromGrain(true) ||
445
+ value == fromGrain(false) ||
446
+ value == fromGrain(void) ||
447
+ (value & Tags._GRAIN_NUMBER_TAG_MASK) == Tags._GRAIN_NUMBER_TAG_TYPE ||
448
+ (value & Tags._GRAIN_GENERIC_TAG_MASK) == Tags._GRAIN_CHAR_TAG_TYPE
449
+ ) =>
450
+ None,
451
+ _ => reportError("Unknown value", offset),
452
+ }
453
+ }
454
+
455
+ @unsafe
456
+ let rec validateHeap = (buf, bufSize, offset, valuesChecked) => {
457
+ let offsetAsInt32 = toGrain(newInt32(offset)): Int32
458
+ Set.add(offsetAsInt32, valuesChecked)
459
+
460
+ let valuePtr = buf + offset
461
+ match (load(valuePtr, 0n)) {
462
+ t when (
463
+ t == Tags._GRAIN_STRING_HEAP_TAG || t == Tags._GRAIN_BYTES_HEAP_TAG
464
+ ) => {
465
+ let size = 8n + load(valuePtr, 4n)
466
+ if (offset + size > bufSize) {
467
+ reportError("String/Bytes length exceeds buffer size", offset)
468
+ } else {
469
+ None
470
+ }
471
+ },
472
+ t when t == Tags._GRAIN_ADT_HEAP_TAG => {
473
+ let arity = load(valuePtr, 16n)
474
+ let size = 20n + arity * 4n
475
+
476
+ let mut error = if (offset + size > bufSize) {
477
+ reportError("Enum payload size exceeds buffer size", offset)
478
+ } else {
479
+ None
480
+ }
481
+
482
+ let a = arity * 4n
483
+ for (let mut i = 0n; i < a; i += 4n) {
484
+ if (Option.isSome(error)) break
485
+
486
+ let value = load(valuePtr + i, 20n)
487
+ if (isHeapPtr(value)) {
488
+ let asInt32 = toGrain(newInt32(value)): Int32
489
+ if (Set.contains(asInt32, valuesChecked)) {
490
+ continue
491
+ }
492
+ error = Option.or(
493
+ error,
494
+ validateHeap(buf, bufSize, value, valuesChecked)
495
+ )
496
+ } else {
497
+ error = Option.or(error, validateStack(value, offset))
498
+ }
499
+ }
500
+
501
+ error
502
+ },
503
+ t when t == Tags._GRAIN_RECORD_HEAP_TAG => {
504
+ let arity = load(valuePtr, 12n)
505
+ let size = 16n + arity * 4n
506
+
507
+ let mut error = if (offset + size > bufSize) {
508
+ reportError("Record payload size exceeds buffer size", offset)
509
+ } else {
510
+ None
511
+ }
512
+
513
+ let a = arity * 4n
514
+ for (let mut i = 0n; i < a; i += 4n) {
515
+ if (Option.isSome(error)) break
516
+
517
+ let value = load(valuePtr + i, 16n)
518
+ if (isHeapPtr(value)) {
519
+ let asInt32 = toGrain(newInt32(value)): Int32
520
+ if (Set.contains(asInt32, valuesChecked)) {
521
+ continue
522
+ }
523
+ error = Option.or(
524
+ error,
525
+ validateHeap(buf, bufSize, value, valuesChecked)
526
+ )
527
+ } else {
528
+ error = Option.or(error, validateStack(value, offset))
529
+ }
530
+ }
531
+
532
+ error
533
+ },
534
+ t when t == Tags._GRAIN_ARRAY_HEAP_TAG => {
535
+ let arity = load(valuePtr, 4n)
536
+ let size = 8n + arity * 4n
537
+
538
+ let mut error = if (offset + size > bufSize) {
539
+ reportError("Array payload size exceeds buffer size", offset)
540
+ } else {
541
+ None
542
+ }
543
+
544
+ let a = arity * 4n
545
+ for (let mut i = 0n; i < a; i += 4n) {
546
+ if (Option.isSome(error)) break
547
+
548
+ let value = load(valuePtr + i, 8n)
549
+ if (isHeapPtr(value)) {
550
+ let asInt32 = toGrain(newInt32(value)): Int32
551
+ if (Set.contains(asInt32, valuesChecked)) {
552
+ continue
553
+ }
554
+ error = Option.or(
555
+ error,
556
+ validateHeap(buf, bufSize, value, valuesChecked)
557
+ )
558
+ } else {
559
+ error = Option.or(error, validateStack(value, offset))
560
+ }
561
+ }
562
+
563
+ error
564
+ },
565
+ t when t == Tags._GRAIN_TUPLE_HEAP_TAG => {
566
+ let arity = load(valuePtr, 4n)
567
+ let size = 8n + arity * 4n
568
+
569
+ let mut error = if (offset + size > bufSize) {
570
+ reportError("Tuple payload size exceeds buffer size", offset)
571
+ } else {
572
+ None
573
+ }
574
+
575
+ let a = arity * 4n
576
+ for (let mut i = 0n; i < a; i += 4n) {
577
+ if (Option.isSome(error)) break
578
+
579
+ let value = load(valuePtr + i, 8n)
580
+ if (isHeapPtr(value)) {
581
+ let asInt32 = toGrain(newInt32(value)): Int32
582
+ if (Set.contains(asInt32, valuesChecked)) {
583
+ continue
584
+ }
585
+ error = Option.or(
586
+ error,
587
+ validateHeap(buf, bufSize, value, valuesChecked)
588
+ )
589
+ } else {
590
+ error = Option.or(error, validateStack(value, offset))
591
+ }
592
+ }
593
+
594
+ error
595
+ },
596
+ t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
597
+ let arity = load(valuePtr, 12n)
598
+ let size = 16n + arity * 4n
599
+
600
+ let mut error = if (offset + size > bufSize) {
601
+ reportError("Closure payload size exceeds buffer size", offset)
602
+ } else {
603
+ None
604
+ }
605
+
606
+ let a = arity * 4n
607
+ for (let mut i = 0n; i < a; i += 4n) {
608
+ if (Option.isSome(error)) break
609
+
610
+ let value = load(valuePtr + i, 16n)
611
+ if (isHeapPtr(value)) {
612
+ let asInt32 = toGrain(newInt32(value)): Int32
613
+ if (Set.contains(asInt32, valuesChecked)) {
614
+ continue
615
+ }
616
+ error = Option.or(
617
+ error,
618
+ validateHeap(buf, bufSize, value, valuesChecked)
619
+ )
620
+ } else {
621
+ error = Option.or(error, validateStack(value, offset))
622
+ }
623
+ }
624
+
625
+ error
626
+ },
627
+ t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => {
628
+ let tag = load(valuePtr, 4n)
629
+ match (tag) {
630
+ t when t == Tags._GRAIN_INT32_BOXED_NUM_TAG => {
631
+ if (offset + 12n > bufSize) {
632
+ reportError(
633
+ "Not enough bytes remaining in buffer for Int32/Number",
634
+ offset
635
+ )
636
+ } else {
637
+ None
638
+ }
639
+ },
640
+ t when t == Tags._GRAIN_INT64_BOXED_NUM_TAG => {
641
+ if (offset + 16n > bufSize) {
642
+ reportError(
643
+ "Not enough bytes remaining in buffer for Int64/Number",
644
+ offset
645
+ )
646
+ } else {
647
+ None
648
+ }
649
+ },
650
+ t when t == Tags._GRAIN_BIGINT_BOXED_NUM_TAG => {
651
+ let size = 16n + load(valuePtr, 8n) * 8n
652
+ if (offset + size > bufSize) {
653
+ reportError("BigInt/Number payload size exeeds buffer size", offset)
654
+ } else {
655
+ None
656
+ }
657
+ },
658
+ t when t == Tags._GRAIN_FLOAT32_BOXED_NUM_TAG => {
659
+ if (offset + 12n > bufSize) {
660
+ reportError(
661
+ "Not enough bytes remaining in buffer for Float32/Number",
662
+ offset
663
+ )
664
+ } else {
665
+ None
666
+ }
667
+ },
668
+ t when t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG => {
669
+ if (offset + 16n > bufSize) {
670
+ reportError(
671
+ "Not enough bytes remaining in buffer for Float64/Number",
672
+ offset
673
+ )
674
+ } else {
675
+ None
676
+ }
677
+ },
678
+ t when t == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG => {
679
+ if (offset + 16n > bufSize) {
680
+ reportError(
681
+ "Not enough bytes remaining in buffer for Rational/Number",
682
+ offset
683
+ )
684
+ } else {
685
+ let numeratorOffset = load(valuePtr, 8n)
686
+ let denominatorOffset = load(valuePtr, 12n)
687
+ let error = Option.or(
688
+ validateHeap(buf, bufSize, numeratorOffset, valuesChecked),
689
+ validateHeap(buf, bufSize, denominatorOffset, valuesChecked)
690
+ )
691
+ if (Option.isNone(error)) {
692
+ let numeratorError = if (
693
+ load(buf, numeratorOffset) != Tags._GRAIN_BOXED_NUM_HEAP_TAG &&
694
+ load(buf, numeratorOffset + 4n) !=
695
+ Tags._GRAIN_BIGINT_BOXED_NUM_TAG
696
+ ) {
697
+ reportError(
698
+ "Rational/Number numerator was not in the expected format",
699
+ offset
700
+ )
701
+ } else {
702
+ None
703
+ }
704
+ let denominatorError = if (
705
+ load(buf, denominatorOffset) !=
706
+ Tags._GRAIN_BOXED_NUM_HEAP_TAG &&
707
+ load(buf, denominatorOffset + 4n) !=
708
+ Tags._GRAIN_BIGINT_BOXED_NUM_TAG
709
+ ) {
710
+ reportError(
711
+ "Rational/Number denominator was not in the expected format",
712
+ offset
713
+ )
714
+ } else {
715
+ None
716
+ }
717
+ Option.or(numeratorError, denominatorError)
718
+ } else {
719
+ error
720
+ }
721
+ }
722
+ },
723
+ _ => {
724
+ None
725
+ },
726
+ }
727
+ },
728
+ _ => {
729
+ None
730
+ },
731
+ }
732
+ }
733
+
734
+ @unsafe
735
+ let validate = (buf, bufSize) => {
736
+ if (bufSize < 4n) {
737
+ reportError("No bytes remaining in buffer", 0n)
738
+ } else {
739
+ let value = load(buf, 0n)
740
+ if (isHeapPtr(value)) {
741
+ validateHeap(buf, bufSize, value, Set.make())
742
+ } else {
743
+ // Handle non-heap values: booleans, chars, void, etc.
744
+ match (value) {
745
+ _ when (
746
+ value == fromGrain(true) ||
747
+ value == fromGrain(false) ||
748
+ value == fromGrain(void) ||
749
+ (value & Tags._GRAIN_NUMBER_TAG_MASK) ==
750
+ Tags._GRAIN_NUMBER_TAG_TYPE ||
751
+ (value & Tags._GRAIN_GENERIC_TAG_MASK) == Tags._GRAIN_CHAR_TAG_TYPE
752
+ ) =>
753
+ None,
754
+ _ => reportError("Unknown value", 0n),
755
+ }
756
+ }
757
+ }
758
+ }
759
+
760
+ @unsafe
761
+ let rec unmarshalHeap = (buf, offset, valuesUnmarshaled) => {
762
+ let offsetAsInt32 = toGrain(newInt32(offset)): Int32
763
+
764
+ let valuePtr = buf + offset
765
+ match (load(valuePtr, 0n)) {
766
+ t when (
767
+ t == Tags._GRAIN_STRING_HEAP_TAG || t == Tags._GRAIN_BYTES_HEAP_TAG
768
+ ) => {
769
+ let size = 8n + load(valuePtr, 4n)
770
+ let value = Memory.malloc(size)
771
+ Memory.copy(value, valuePtr, size)
772
+
773
+ let asInt32 = toGrain(newInt32(value)): Int32
774
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
775
+
776
+ value
777
+ },
778
+ t when t == Tags._GRAIN_ADT_HEAP_TAG => {
779
+ let arity = load(valuePtr, 16n)
780
+ let size = 20n + arity * 4n
781
+
782
+ let value = Memory.malloc(size)
783
+ Memory.copy(value, valuePtr, 20n)
784
+
785
+ let asInt32 = toGrain(newInt32(value)): Int32
786
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
787
+
788
+ let a = arity * 4n
789
+ for (let mut i = 0n; i < a; i += 4n) {
790
+ let subvalue = load(valuePtr + i, 20n)
791
+ if (isHeapPtr(subvalue)) {
792
+ let asInt32 = toGrain(newInt32(subvalue)): Int32
793
+ match (Map.get(asInt32, valuesUnmarshaled)) {
794
+ Some(ptr) => {
795
+ let ptr = load(fromGrain(ptr), 8n)
796
+ store(value + i, Memory.incRef(ptr), 20n)
797
+ },
798
+ None => {
799
+ store(
800
+ value + i,
801
+ unmarshalHeap(buf, subvalue, valuesUnmarshaled),
802
+ 20n
803
+ )
804
+ },
805
+ }
806
+ } else {
807
+ store(value + i, subvalue, 20n)
808
+ }
809
+ }
810
+
811
+ value
812
+ },
813
+ t when t == Tags._GRAIN_RECORD_HEAP_TAG => {
814
+ let arity = load(valuePtr, 12n)
815
+ let size = 16n + arity * 4n
816
+
817
+ let value = Memory.malloc(size)
818
+ Memory.copy(value, valuePtr, 16n)
819
+
820
+ let asInt32 = toGrain(newInt32(value)): Int32
821
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
822
+
823
+ let a = arity * 4n
824
+ for (let mut i = 0n; i < a; i += 4n) {
825
+ let subvalue = load(valuePtr + i, 16n)
826
+ if (isHeapPtr(subvalue)) {
827
+ let asInt32 = toGrain(newInt32(subvalue)): Int32
828
+ match (Map.get(asInt32, valuesUnmarshaled)) {
829
+ Some(ptr) => {
830
+ let ptr = load(fromGrain(ptr), 8n)
831
+ store(value + i, Memory.incRef(ptr), 16n)
832
+ },
833
+ None => {
834
+ store(
835
+ value + i,
836
+ unmarshalHeap(buf, subvalue, valuesUnmarshaled),
837
+ 16n
838
+ )
839
+ },
840
+ }
841
+ } else {
842
+ store(value + i, subvalue, 16n)
843
+ }
844
+ }
845
+
846
+ value
847
+ },
848
+ t when t == Tags._GRAIN_ARRAY_HEAP_TAG => {
849
+ let arity = load(valuePtr, 4n)
850
+ let size = 8n + arity * 4n
851
+
852
+ let value = Memory.malloc(size)
853
+ Memory.copy(value, valuePtr, 8n)
854
+
855
+ let asInt32 = toGrain(newInt32(value)): Int32
856
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
857
+
858
+ let a = arity * 4n
859
+ for (let mut i = 0n; i < a; i += 4n) {
860
+ let subvalue = load(valuePtr + i, 8n)
861
+ if (isHeapPtr(subvalue)) {
862
+ let asInt32 = toGrain(newInt32(subvalue)): Int32
863
+ match (Map.get(asInt32, valuesUnmarshaled)) {
864
+ Some(ptr) => {
865
+ let ptr = load(fromGrain(ptr), 8n)
866
+ store(value + i, Memory.incRef(ptr), 8n)
867
+ },
868
+ None => {
869
+ store(
870
+ value + i,
871
+ unmarshalHeap(buf, subvalue, valuesUnmarshaled),
872
+ 8n
873
+ )
874
+ },
875
+ }
876
+ } else {
877
+ store(value + i, subvalue, 8n)
878
+ }
879
+ }
880
+
881
+ value
882
+ },
883
+ t when t == Tags._GRAIN_TUPLE_HEAP_TAG => {
884
+ let arity = load(valuePtr, 4n)
885
+ let size = 8n + arity * 4n
886
+
887
+ let value = Memory.malloc(size)
888
+ Memory.copy(value, valuePtr, 8n)
889
+
890
+ let asInt32 = toGrain(newInt32(value)): Int32
891
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
892
+
893
+ let a = arity * 4n
894
+ for (let mut i = 0n; i < a; i += 4n) {
895
+ let subvalue = load(valuePtr + i, 8n)
896
+ if (isHeapPtr(subvalue)) {
897
+ let asInt32 = toGrain(newInt32(subvalue)): Int32
898
+ match (Map.get(asInt32, valuesUnmarshaled)) {
899
+ Some(ptr) => {
900
+ let ptr = load(fromGrain(ptr), 8n)
901
+ store(value + i, Memory.incRef(ptr), 8n)
902
+ },
903
+ None => {
904
+ store(
905
+ value + i,
906
+ unmarshalHeap(buf, subvalue, valuesUnmarshaled),
907
+ 8n
908
+ )
909
+ },
910
+ }
911
+ } else {
912
+ store(value + i, subvalue, 8n)
913
+ }
914
+ }
915
+
916
+ value
917
+ },
918
+ t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
919
+ let arity = load(valuePtr, 12n)
920
+ let size = 16n + arity * 4n
921
+
922
+ let value = Memory.malloc(size)
923
+ Memory.copy(value, valuePtr, 16n)
924
+
925
+ let asInt32 = toGrain(newInt32(value)): Int32
926
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
927
+
928
+ let a = arity * 4n
929
+ for (let mut i = 0n; i < a; i += 4n) {
930
+ let subvalue = load(valuePtr + i, 16n)
931
+ if (isHeapPtr(subvalue)) {
932
+ let asInt32 = toGrain(newInt32(subvalue)): Int32
933
+ match (Map.get(asInt32, valuesUnmarshaled)) {
934
+ Some(ptr) => {
935
+ let ptr = load(fromGrain(ptr), 8n)
936
+ store(value + i, Memory.incRef(ptr), 16n)
937
+ },
938
+ None => {
939
+ store(
940
+ value + i,
941
+ unmarshalHeap(buf, subvalue, valuesUnmarshaled),
942
+ 16n
943
+ )
944
+ },
945
+ }
946
+ } else {
947
+ store(value + i, subvalue, 16n)
948
+ }
949
+ }
950
+
951
+ value
952
+ },
953
+ t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => {
954
+ let tag = load(valuePtr, 4n)
955
+ match (tag) {
956
+ t when t == Tags._GRAIN_INT32_BOXED_NUM_TAG => {
957
+ let value = Memory.malloc(12n)
958
+ Memory.copy(value, valuePtr, 12n)
959
+
960
+ let asInt32 = toGrain(newInt32(value)): Int32
961
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
962
+
963
+ value
964
+ },
965
+ t when t == Tags._GRAIN_INT64_BOXED_NUM_TAG => {
966
+ let value = Memory.malloc(16n)
967
+ Memory.copy(value, valuePtr, 16n)
968
+
969
+ let asInt32 = toGrain(newInt32(value)): Int32
970
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
971
+
972
+ value
973
+ },
974
+ t when t == Tags._GRAIN_BIGINT_BOXED_NUM_TAG => {
975
+ let size = 16n + load(valuePtr, 8n) * 8n
976
+ let value = Memory.malloc(size)
977
+ Memory.copy(value, valuePtr, size)
978
+
979
+ let asInt32 = toGrain(newInt32(value)): Int32
980
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
981
+
982
+ value
983
+ },
984
+ t when t == Tags._GRAIN_FLOAT32_BOXED_NUM_TAG => {
985
+ let value = Memory.malloc(12n)
986
+ Memory.copy(value, valuePtr, 12n)
987
+
988
+ let asInt32 = toGrain(newInt32(value)): Int32
989
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
990
+
991
+ value
992
+ },
993
+ t when t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG => {
994
+ let value = Memory.malloc(16n)
995
+ Memory.copy(value, valuePtr, 16n)
996
+
997
+ let asInt32 = toGrain(newInt32(value)): Int32
998
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
999
+
1000
+ value
1001
+ },
1002
+ t when t == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG => {
1003
+ let value = Memory.malloc(16n)
1004
+ Memory.copy(value, valuePtr, 8n)
1005
+
1006
+ let asInt32 = toGrain(newInt32(value)): Int32
1007
+ Map.set(offsetAsInt32, asInt32, valuesUnmarshaled)
1008
+
1009
+ let num = unmarshalHeap(buf, load(valuePtr, 8n), valuesUnmarshaled)
1010
+ store(value, num, 8n)
1011
+ let denom = unmarshalHeap(buf, load(valuePtr, 12n), valuesUnmarshaled)
1012
+ store(value, denom, 12n)
1013
+ value
1014
+ },
1015
+ _ => {
1016
+ fail "Unknown number type"
1017
+ },
1018
+ }
1019
+ },
1020
+ _ => {
1021
+ fail "Unknown heap type"
1022
+ },
1023
+ }
1024
+ }
1025
+
1026
+ @unsafe
1027
+ let unmarshal = buf => {
1028
+ let value = load(buf, 0n)
1029
+ if (isHeapPtr(value)) {
1030
+ unmarshalHeap(buf, value, Map.make())
1031
+ } else {
1032
+ // Non-heap values: booleans, numbers, chars, void, etc.
1033
+ value
1034
+ }
1035
+ }
1036
+
1037
+ /**
1038
+ * Deserialize the byte-based representation of a value back into an in-memory
1039
+ * value. This operation is not type-safe, and it is recommended that a type
1040
+ * annotation is used to declare the type of the unmarshaled value. While
1041
+ * attempts to unmarshal bad data will fail, this operation is still generally
1042
+ * unsafe and great care should be taken to ensure that the data being
1043
+ * unmarshaled corresponds to the expected type.
1044
+ *
1045
+ * @param bytes: The data to deserialize
1046
+ * @returns An in-memory value
1047
+ *
1048
+ * @since v0.5.3
1049
+ */
1050
+ @unsafe
1051
+ export let unmarshal = (bytes: Bytes) => {
1052
+ let buf = fromGrain(bytes) + 8n
1053
+ let bufSize = load(fromGrain(bytes), 4n)
1054
+ match (validate(buf, bufSize)) {
1055
+ Some(error) => Err(error),
1056
+ None => Ok(toGrain(unmarshal(buf))),
1057
+ }
1058
+ }