@grain/stdlib 0.5.13 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/LICENSE +1 -1
  3. package/README.md +25 -2
  4. package/array.gr +1512 -199
  5. package/array.md +2032 -94
  6. package/bigint.gr +239 -140
  7. package/bigint.md +450 -106
  8. package/buffer.gr +595 -102
  9. package/buffer.md +903 -145
  10. package/bytes.gr +401 -110
  11. package/bytes.md +551 -63
  12. package/char.gr +228 -49
  13. package/char.md +373 -7
  14. package/exception.gr +26 -12
  15. package/exception.md +29 -5
  16. package/float32.gr +130 -109
  17. package/float32.md +185 -57
  18. package/float64.gr +112 -99
  19. package/float64.md +185 -57
  20. package/hash.gr +62 -40
  21. package/hash.md +27 -3
  22. package/int16.gr +430 -0
  23. package/int16.md +618 -0
  24. package/int32.gr +200 -269
  25. package/int32.md +254 -289
  26. package/int64.gr +142 -225
  27. package/int64.md +254 -289
  28. package/int8.gr +511 -0
  29. package/int8.md +786 -0
  30. package/json.gr +2071 -0
  31. package/json.md +646 -0
  32. package/list.gr +120 -68
  33. package/list.md +125 -80
  34. package/map.gr +560 -57
  35. package/map.md +672 -56
  36. package/marshal.gr +239 -227
  37. package/marshal.md +36 -4
  38. package/number.gr +626 -676
  39. package/number.md +738 -153
  40. package/option.gr +33 -35
  41. package/option.md +58 -42
  42. package/package.json +2 -2
  43. package/path.gr +148 -187
  44. package/path.md +47 -96
  45. package/pervasives.gr +75 -416
  46. package/pervasives.md +85 -180
  47. package/priorityqueue.gr +433 -74
  48. package/priorityqueue.md +422 -54
  49. package/queue.gr +362 -80
  50. package/queue.md +433 -38
  51. package/random.gr +67 -75
  52. package/random.md +68 -40
  53. package/range.gr +135 -63
  54. package/range.md +198 -43
  55. package/rational.gr +284 -0
  56. package/rational.md +545 -0
  57. package/regex.gr +933 -1066
  58. package/regex.md +59 -60
  59. package/result.gr +23 -25
  60. package/result.md +54 -39
  61. package/runtime/atof/common.gr +78 -82
  62. package/runtime/atof/common.md +22 -10
  63. package/runtime/atof/decimal.gr +102 -127
  64. package/runtime/atof/decimal.md +28 -7
  65. package/runtime/atof/lemire.gr +56 -71
  66. package/runtime/atof/lemire.md +9 -1
  67. package/runtime/atof/parse.gr +83 -110
  68. package/runtime/atof/parse.md +12 -2
  69. package/runtime/atof/slow.gr +28 -35
  70. package/runtime/atof/slow.md +9 -1
  71. package/runtime/atof/table.gr +19 -18
  72. package/runtime/atof/table.md +10 -2
  73. package/runtime/atoi/parse.gr +153 -136
  74. package/runtime/atoi/parse.md +50 -1
  75. package/runtime/bigint.gr +410 -517
  76. package/runtime/bigint.md +71 -57
  77. package/runtime/compare.gr +176 -85
  78. package/runtime/compare.md +31 -1
  79. package/runtime/dataStructures.gr +144 -32
  80. package/runtime/dataStructures.md +267 -31
  81. package/runtime/debugPrint.gr +34 -15
  82. package/runtime/debugPrint.md +37 -5
  83. package/runtime/equal.gr +53 -52
  84. package/runtime/equal.md +30 -1
  85. package/runtime/exception.gr +38 -47
  86. package/runtime/exception.md +10 -8
  87. package/runtime/gc.gr +23 -152
  88. package/runtime/gc.md +13 -17
  89. package/runtime/malloc.gr +31 -31
  90. package/runtime/malloc.md +11 -3
  91. package/runtime/numberUtils.gr +193 -174
  92. package/runtime/numberUtils.md +29 -9
  93. package/runtime/numbers.gr +1695 -1021
  94. package/runtime/numbers.md +1098 -134
  95. package/runtime/string.gr +543 -245
  96. package/runtime/string.md +76 -6
  97. package/runtime/unsafe/constants.gr +30 -13
  98. package/runtime/unsafe/constants.md +80 -0
  99. package/runtime/unsafe/conv.gr +55 -28
  100. package/runtime/unsafe/conv.md +41 -9
  101. package/runtime/unsafe/memory.gr +10 -30
  102. package/runtime/unsafe/memory.md +15 -19
  103. package/runtime/unsafe/tags.gr +37 -21
  104. package/runtime/unsafe/tags.md +88 -8
  105. package/runtime/unsafe/wasmf32.gr +30 -36
  106. package/runtime/unsafe/wasmf32.md +64 -56
  107. package/runtime/unsafe/wasmf64.gr +30 -36
  108. package/runtime/unsafe/wasmf64.md +64 -56
  109. package/runtime/unsafe/wasmi32.gr +49 -66
  110. package/runtime/unsafe/wasmi32.md +102 -94
  111. package/runtime/unsafe/wasmi64.gr +52 -79
  112. package/runtime/unsafe/wasmi64.md +108 -100
  113. package/runtime/utils/printing.gr +13 -15
  114. package/runtime/utils/printing.md +11 -3
  115. package/runtime/wasi.gr +294 -295
  116. package/runtime/wasi.md +62 -42
  117. package/set.gr +574 -64
  118. package/set.md +634 -54
  119. package/stack.gr +181 -64
  120. package/stack.md +271 -42
  121. package/string.gr +453 -533
  122. package/string.md +241 -151
  123. package/uint16.gr +369 -0
  124. package/uint16.md +585 -0
  125. package/uint32.gr +470 -0
  126. package/uint32.md +737 -0
  127. package/uint64.gr +471 -0
  128. package/uint64.md +737 -0
  129. package/uint8.gr +369 -0
  130. package/uint8.md +585 -0
  131. package/uri.gr +1093 -0
  132. package/uri.md +477 -0
  133. package/{sys → wasi}/file.gr +914 -500
  134. package/{sys → wasi}/file.md +454 -50
  135. package/wasi/process.gr +292 -0
  136. package/{sys → wasi}/process.md +164 -6
  137. package/wasi/random.gr +77 -0
  138. package/wasi/random.md +80 -0
  139. package/{sys → wasi}/time.gr +15 -22
  140. package/{sys → wasi}/time.md +5 -5
  141. package/immutablearray.gr +0 -929
  142. package/immutablearray.md +0 -1038
  143. package/immutablemap.gr +0 -493
  144. package/immutablemap.md +0 -479
  145. package/immutablepriorityqueue.gr +0 -360
  146. package/immutablepriorityqueue.md +0 -291
  147. package/immutableset.gr +0 -498
  148. package/immutableset.md +0 -449
  149. package/runtime/debug.gr +0 -2
  150. package/runtime/debug.md +0 -6
  151. package/runtime/unsafe/errors.gr +0 -36
  152. package/runtime/unsafe/errors.md +0 -204
  153. package/sys/process.gr +0 -254
  154. package/sys/random.gr +0 -79
  155. package/sys/random.md +0 -66
package/runtime/string.gr CHANGED
@@ -1,139 +1,244 @@
1
- /* grainc-flags --no-pervasives */
2
-
3
- import WasmI32, {
4
- add as (+),
5
- sub as (-),
6
- mul as (*),
7
- divS as (/),
8
- shl as (<<),
9
- shrS as (>>),
10
- shrU as (>>>),
11
- and as (&),
12
- or as (|),
13
- eq as (==),
14
- ne as (!=),
15
- geS as (>=),
16
- gtS as (>),
17
- leS as (<=),
18
- ltS as (<),
19
- } from "runtime/unsafe/wasmi32"
20
- import WasmI64 from "runtime/unsafe/wasmi64"
21
- import WasmF32 from "runtime/unsafe/wasmf32"
22
- import WasmF64 from "runtime/unsafe/wasmf64"
23
- import BI from "runtime/bigint"
24
- import Memory from "runtime/unsafe/memory"
25
- import Tags from "runtime/unsafe/tags"
26
- import NumberUtils from "runtime/numberUtils"
27
-
28
- import { allocateString, allocateArray } from "runtime/dataStructures"
29
-
30
- import foreign wasm fd_write: (
31
- WasmI32,
32
- WasmI32,
33
- WasmI32,
34
- WasmI32,
35
- ) -> WasmI32 from "wasi_snapshot_preview1"
36
-
37
- primitive (!): Bool -> Bool = "@not"
38
- primitive (&&): (Bool, Bool) -> Bool = "@and"
39
- primitive (||): (Bool, Bool) -> Bool = "@or"
40
-
41
- enum StringList {
42
- [],
43
- [...](String, StringList),
44
- }
45
-
46
- primitive _RUNTIME_TYPE_METADATA_PTR: WasmI32 = "@heap.type_metadata"
47
-
48
- @unsafe
49
- let findTypeMetadata = (moduleId, typeId) => {
50
- let mut metadataPtr = WasmI32.load(_RUNTIME_TYPE_METADATA_PTR, 0n)
51
- let mut modData = -1n
52
- let mut modTypesCount = 0n
53
- while (metadataPtr != 0n) {
54
- if (WasmI32.load(metadataPtr, 4n) == moduleId) {
55
- modData = metadataPtr + 12n
56
- modTypesCount = WasmI32.load(metadataPtr, 8n)
57
- break
1
+ @noPervasives
2
+ module String
3
+
4
+ from "runtime/unsafe/wasmi32" include WasmI32
5
+ use WasmI32.{
6
+ (+),
7
+ (-),
8
+ (*),
9
+ (/),
10
+ remS as (%),
11
+ (<<),
12
+ (>>),
13
+ (&),
14
+ (>>>),
15
+ (|),
16
+ (==),
17
+ (!=),
18
+ (>=),
19
+ (>),
20
+ (<=),
21
+ (<),
22
+ }
23
+ from "runtime/unsafe/wasmi64" include WasmI64
24
+ from "runtime/unsafe/wasmf32" include WasmF32
25
+ from "runtime/unsafe/wasmf64" include WasmF64
26
+ from "runtime/bigint" include Bigint as BI
27
+ from "runtime/unsafe/memory" include Memory
28
+ from "runtime/unsafe/tags" include Tags
29
+ from "runtime/numberUtils" include NumberUtils
30
+
31
+ from "runtime/dataStructures" include DataStructures
32
+ use DataStructures.{ allocateString, allocateArray, untagSimpleNumber }
33
+
34
+ foreign wasm fd_write:
35
+ (WasmI32, WasmI32, WasmI32, WasmI32) => WasmI32 from "wasi_snapshot_preview1"
36
+
37
+ primitive (!) = "@not"
38
+ primitive (&&) = "@and"
39
+ primitive (||) = "@or"
40
+ primitive builtinId = "@builtin.id"
41
+ primitive ignore = "@ignore"
42
+ primitive throw = "@throw"
43
+
44
+ exception MalformedUnicode
45
+
46
+ @unsafe
47
+ primitive typeMetadata = "@heap.type_metadata"
48
+
49
+ @unsafe
50
+ let findTypeMetadata = typeHash => {
51
+ let typeMetadata = typeMetadata()
52
+ let numBuckets = WasmI32.load(typeMetadata, 0n)
53
+ let hashHash = typeHash % numBuckets
54
+ // First 8 bytes of metadata are for table size
55
+ let bucketPtr = typeMetadata + 8n + (hashHash << 3n) // 8 bytes/bucket
56
+ let bucketDataOffset = WasmI32.load(bucketPtr, 0n)
57
+ let bucketSize = WasmI32.load(bucketPtr, 4n)
58
+ let beginDataPtr = typeMetadata + bucketDataOffset
59
+ let endDataPtr = beginDataPtr + (bucketSize << 3n)
60
+ for (let mut ptr = beginDataPtr; ptr < endDataPtr; ptr += 8n) {
61
+ if (WasmI32.load(ptr, 0n) == typeHash) {
62
+ return typeMetadata + WasmI32.load(ptr, 4n)
58
63
  }
59
- metadataPtr = WasmI32.load(metadataPtr, 0n)
60
64
  }
65
+ return -1n
66
+ }
67
+
68
+ @unsafe
69
+ let _LIST_ID = untagSimpleNumber(builtinId("List"))
70
+ @unsafe
71
+ let _OPTION_ID = untagSimpleNumber(builtinId("Option"))
72
+ @unsafe
73
+ let _RESULT_ID = untagSimpleNumber(builtinId("Result"))
74
+
75
+ let _SOME = "Some"
76
+ let _NONE = "None"
77
+ let _OK = "Ok"
78
+ let _ERR = "Err"
79
+
80
+ // Resizable arrays: <num items> <capacity> <...data>
81
+ @unsafe
82
+ let _VEC_LEN_OFFSET = 0n
83
+ @unsafe
84
+ let _VEC_CAP_OFFSET = 4n
85
+ @unsafe
86
+ let _VEC_DATA_OFFSET = 8n
87
+
88
+ @unsafe
89
+ let _VISITED_BIT = 0x80000000n
90
+
91
+ @unsafe
92
+ let makeVecBox = () => {
93
+ let vecBox = Memory.malloc(4n)
94
+ WasmI32.store(vecBox, 0n, 0n)
95
+ vecBox
96
+ }
97
+
98
+ @unsafe
99
+ let initVec = vecBox => {
100
+ let initCap = 4n
101
+ let vec = Memory.malloc(8n + initCap * 4n)
102
+ WasmI32.store(vec, 0n, _VEC_LEN_OFFSET)
103
+ WasmI32.store(vec, initCap, _VEC_CAP_OFFSET)
104
+ WasmI32.store(vecBox, vec, 0n)
105
+ vec
106
+ }
107
+
108
+ @unsafe
109
+ let freeVecBox = vecBox => {
110
+ let vecPtr = WasmI32.load(vecBox, 0n)
111
+ if (vecPtr != 0n) {
112
+ Memory.free(vecPtr)
113
+ }
114
+ Memory.free(vecBox)
115
+ }
116
+
117
+ @unsafe
118
+ let vecPush = (vecBox, val) => {
119
+ let mut vecPtr = WasmI32.load(vecBox, 0n)
120
+ if (vecPtr == 0n) {
121
+ vecPtr = initVec(vecBox)
122
+ }
123
+ let len = WasmI32.load(vecPtr, _VEC_LEN_OFFSET)
124
+ let cap = WasmI32.load(vecPtr, _VEC_CAP_OFFSET)
125
+ if (len == cap) {
126
+ let newCap = cap * 2n
127
+ let newVec = Memory.malloc(8n + newCap * 4n)
128
+ Memory.copy(newVec, vecPtr, 8n + cap * 4n)
129
+ WasmI32.store(newVec, newCap, _VEC_CAP_OFFSET)
130
+ Memory.free(vecPtr)
131
+ WasmI32.store(vecBox, newVec, 0n)
132
+ vecPtr = newVec
133
+ }
134
+ WasmI32.store(vecPtr + len * 4n, val, _VEC_DATA_OFFSET)
135
+ WasmI32.store(vecPtr, len + 1n, _VEC_LEN_OFFSET)
136
+ }
61
137
 
62
- if (modData == -1n) {
63
- -1n
138
+ @unsafe
139
+ let vecLen = vecBox => {
140
+ let vecPtr = WasmI32.load(vecBox, 0n)
141
+ if (vecPtr == 0n) {
142
+ 0n
64
143
  } else {
65
- let mut typeData = -1n
66
- for (let mut i = 0n; i < modTypesCount; i += 1n) {
67
- if (WasmI32.load(modData, 4n) == typeId) {
68
- typeData = modData
69
- break
70
- }
71
- modData += WasmI32.load(modData, 0n)
72
- }
144
+ WasmI32.load(vecPtr, _VEC_LEN_OFFSET)
145
+ }
146
+ }
73
147
 
74
- typeData
148
+ @unsafe
149
+ let vecFindIndex = (vecBox, val) => {
150
+ let vecPtr = WasmI32.load(vecBox, 0n)
151
+ let len = vecLen(vecBox)
152
+ for (let mut i = 0n; i < len; i += 1n) {
153
+ if (WasmI32.load(vecPtr + i * 4n, _VEC_DATA_OFFSET) == val) {
154
+ return i
155
+ }
75
156
  }
157
+ return -1n
76
158
  }
77
159
 
78
160
  @unsafe
79
- let getVariantName = variant => {
80
- let moduleId = WasmI32.load(variant, 4n) >> 1n
161
+ let isListVariant = variant => {
81
162
  let typeId = WasmI32.load(variant, 8n) >> 1n
82
- let variantId = WasmI32.load(variant, 12n) >> 1n
163
+ typeId == _LIST_ID
164
+ }
83
165
 
84
- let mut block = findTypeMetadata(moduleId, typeId)
166
+ @unsafe
167
+ let getBuiltinVariantName = variant => {
168
+ let typeId = WasmI32.load(variant, 8n) >> 1n
169
+ let variantId = WasmI32.load(variant, 12n) >> 1n
85
170
 
86
- if (block == -1n) {
87
- -1n
88
- } else {
89
- let sectionLength = WasmI32.load(block, 0n)
90
- block += 8n
91
-
92
- let end = block + sectionLength
93
- let mut result = -1n
94
- while (block < end) {
95
- if (WasmI32.load(block, 4n) == variantId) {
96
- let length = WasmI32.load(block, 8n)
97
- let str = allocateString(length)
98
- Memory.copy(str + 8n, block + 12n, length)
99
- result = str
100
- break
171
+ match (typeId) {
172
+ id when id == _OPTION_ID => {
173
+ if (variantId == 0n) {
174
+ Memory.incRef(WasmI32.fromGrain(_SOME))
175
+ } else {
176
+ Memory.incRef(WasmI32.fromGrain(_NONE))
101
177
  }
102
- block += WasmI32.load(block, 0n)
103
- }
104
-
105
- result
178
+ },
179
+ id when id == _RESULT_ID => {
180
+ if (variantId == 0n) {
181
+ Memory.incRef(WasmI32.fromGrain(_OK))
182
+ } else {
183
+ Memory.incRef(WasmI32.fromGrain(_ERR))
184
+ }
185
+ },
186
+ _ => -1n,
106
187
  }
107
188
  }
108
189
 
109
190
  @unsafe
110
- let getRecordFieldNames = record_ => {
111
- let moduleId = WasmI32.load(record_, 4n) >> 1n
112
- let typeId = WasmI32.load(record_, 8n) >> 1n
113
- let arity = WasmI32.load(record_, 12n)
191
+ let getFieldArray = (fields, arity) => {
192
+ let fieldArray = allocateArray(arity)
193
+
194
+ let mut fieldOffset = 0n
195
+ for (let mut i = 0n; i < arity; i += 1n) {
196
+ let fieldLength = WasmI32.load(fields + fieldOffset, 4n)
197
+ let fieldName = allocateString(fieldLength)
198
+ Memory.incRef(fieldName)
199
+ Memory.copy(fieldName + 8n, fields + fieldOffset + 8n, fieldLength)
200
+ WasmI32.store(fieldArray + i * 4n, fieldName, 8n)
201
+
202
+ fieldOffset += WasmI32.load(fields + fieldOffset, 0n)
203
+ }
114
204
 
115
- let mut fields = findTypeMetadata(moduleId, typeId)
205
+ fieldArray
206
+ }
116
207
 
117
- if (fields == -1n) {
118
- -1n
119
- } else {
120
- fields += 8n
208
+ @unsafe
209
+ let getVariantMetadata = variant => {
210
+ let typeHash = WasmI32.load(variant, 4n) >> 1n
211
+ let variantId = WasmI32.load(variant, 12n) >> 1n
121
212
 
122
- let fieldArray = allocateArray(arity)
213
+ let mut block = findTypeMetadata(typeHash)
123
214
 
124
- let mut fieldOffset = 0n
125
- for (let mut i = 0n; i < arity; i += 1n) {
126
- let fieldLength = WasmI32.load(fields + fieldOffset, 4n)
127
- let fieldName = allocateString(fieldLength)
128
- Memory.incRef(fieldName)
129
- Memory.copy(fieldName + 8n, fields + fieldOffset + 8n, fieldLength)
130
- WasmI32.store(fieldArray + i * 4n, fieldName, 8n)
215
+ if (block == -1n) return -1n
131
216
 
132
- fieldOffset += WasmI32.load(fields + fieldOffset, 0n)
133
- }
217
+ let sectionLength = WasmI32.load(block, 0n)
218
+ block += 4n
134
219
 
135
- fieldArray
220
+ let end = block + sectionLength
221
+ while (block < end) {
222
+ if (WasmI32.load(block, 8n) == variantId) {
223
+ return block
224
+ }
225
+ block += WasmI32.load(block, 0n)
136
226
  }
227
+
228
+ return -1n
229
+ }
230
+
231
+ @unsafe
232
+ let getRecordFieldNames = record_ => {
233
+ let typeHash = WasmI32.load(record_, 4n) >> 1n
234
+ let arity = WasmI32.load(record_, 12n)
235
+
236
+ let mut fields = findTypeMetadata(typeHash)
237
+
238
+ if (fields == -1n) return -1n
239
+
240
+ fields += 4n
241
+ return getFieldArray(fields, arity)
137
242
  }
138
243
 
139
244
  @unsafe
@@ -178,10 +283,21 @@ let reverse = list => {
178
283
  iter(list, [])
179
284
  }
180
285
 
286
+ /**
287
+ * Concatenate two strings.
288
+ *
289
+ * @param str1: The beginning string
290
+ * @param str2: The ending string
291
+ * @returns The combined string
292
+ *
293
+ * @example "Foo" ++ "Bar" == "FooBar"
294
+ *
295
+ * @since v0.2.0
296
+ */
181
297
  @unsafe
182
- export let concat = (s1: String, s2: String) => {
183
- let ptr1 = WasmI32.fromGrain(s1)
184
- let ptr2 = WasmI32.fromGrain(s2)
298
+ provide let concat = (str1: String, str2: String) => {
299
+ let ptr1 = WasmI32.fromGrain(str1)
300
+ let ptr2 = WasmI32.fromGrain(str2)
185
301
 
186
302
  let size1 = WasmI32.load(ptr1, 4n)
187
303
  let size2 = WasmI32.load(ptr2, 4n)
@@ -218,7 +334,6 @@ let escape = (ptr, isString) => {
218
334
  let byte = WasmI32.load8U(ptr + i, startOffset)
219
335
  if (
220
336
  byte >= _SEQ_B && byte <= _SEQ_R || /* b, f, n, r, t, v */
221
- byte == _SEQ_V ||
222
337
  byte == _SEQ_SLASH ||
223
338
  byte == _SEQ_QUOTE
224
339
  ) {
@@ -237,7 +352,6 @@ let escape = (ptr, isString) => {
237
352
  let byte = WasmI32.load8U(ptr + i, startOffset)
238
353
  if (
239
354
  byte >= _SEQ_B && byte <= _SEQ_R || /* b, f, n, r, t, v */
240
- byte == _SEQ_V ||
241
355
  byte == _SEQ_SLASH ||
242
356
  byte == _SEQ_QUOTE
243
357
  ) {
@@ -296,12 +410,12 @@ let usvToString = usv => {
296
410
  offset = 0xF0n
297
411
  }
298
412
  let string = allocateString(count + 1n)
299
- WasmI32.store8(string, (usv >>> 6n * count) + offset, 8n)
413
+ WasmI32.store8(string, (usv >>> (6n * count)) + offset, 8n)
300
414
 
301
415
  let mut n = 0n
302
416
  while (count > 0n) {
303
417
  n += 1n
304
- let temp = usv >>> 6n * (count - 1n)
418
+ let temp = usv >>> (6n * (count - 1n))
305
419
  WasmI32.store8(string + n, 0x80n | temp & 0x3Fn, 8n)
306
420
  count -= 1n
307
421
  }
@@ -311,14 +425,28 @@ let usvToString = usv => {
311
425
  }
312
426
 
313
427
  @unsafe
314
- let isListVariant = (variant: String) => {
315
- // Only lists can start with `[`, so only need to check for that
316
- // Sort of a hack until we have a better solution
317
- WasmI32.load8U(WasmI32.fromGrain(variant), 8n) == 0x5Bn
428
+ let reportCycle = (ptr, cycles) => {
429
+ let mut cycleNum = vecFindIndex(cycles, ptr)
430
+ if (cycleNum == -1n) {
431
+ cycleNum = vecLen(cycles)
432
+ vecPush(cycles, ptr)
433
+ }
434
+ let numStr = NumberUtils.itoa32(cycleNum + 1n, 10n)
435
+ join(["<cycle to <", numStr, ">>"])
436
+ }
437
+
438
+ @unsafe
439
+ let cyclePrefix = (ptr, cycles) => {
440
+ let cycleNum = vecFindIndex(cycles, ptr)
441
+ if (cycleNum != -1n) {
442
+ join(["<", NumberUtils.itoa32(cycleNum + 1n, 10n), "> "])
443
+ } else {
444
+ ""
445
+ }
318
446
  }
319
447
 
320
448
  @unsafe
321
- let rec heapValueToString = (ptr, extraIndents, toplevel) => {
449
+ let rec heapValueToString = (ptr, extraIndents, toplevel, cycles) => {
322
450
  let tag = WasmI32.load(ptr, 0n)
323
451
  match (tag) {
324
452
  t when t == Tags._GRAIN_STRING_HEAP_TAG => {
@@ -338,9 +466,10 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
338
466
  needsEllipsis = true
339
467
  }
340
468
  let headBytes = 8n // <bytes:
341
- let hexBytes = numBytes * 3n
469
+ // This is two digits and a space for each byte, minus one space for the last byte
470
+ let hexBytes = numBytes * 3n - 1n
342
471
  let tailBytes = if (needsEllipsis) {
343
- 5n // ...>
472
+ 4n // ...>
344
473
  } else {
345
474
  1n // >
346
475
  }
@@ -370,38 +499,47 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
370
499
  },
371
500
  t when t == Tags._GRAIN_ADT_HEAP_TAG => {
372
501
  // [ <value type tag>, <module_tag>, <type_tag>, <variant_tag>, <arity>, elts ... ]
373
- let variantName = getVariantName(ptr)
374
-
375
- if (variantName == -1n) {
376
- "<enum value>"
502
+ let builtinVariantName = getBuiltinVariantName(ptr)
503
+ if (builtinVariantName != -1n) {
504
+ // Assumes that all builtin variants do not have inline record
505
+ // constructors; if this changes this should be changed as well
506
+ tupleVariantToString(
507
+ ptr,
508
+ WasmI32.toGrain(builtinVariantName),
509
+ extraIndents,
510
+ cycles
511
+ )
512
+ } else if (isListVariant(ptr)) {
513
+ listToString(ptr, extraIndents, cycles)
377
514
  } else {
378
- let variantName = WasmI32.toGrain(variantName): String
379
- // Check if this is a list
380
- if (isListVariant(variantName)) {
381
- listToString(ptr, extraIndents)
515
+ let variantPtr = getVariantMetadata(ptr)
516
+ if (variantPtr == -1n) {
517
+ "<enum value>"
382
518
  } else {
383
- let variantArity = WasmI32.load(ptr, 16n)
384
- if (variantArity == 0n) {
385
- variantName
386
- } else {
387
- let comspace = ", "
388
- let rparen = ")"
389
- let mut strings = [rparen]
390
- for (let mut i = variantArity * 4n - 4n; i >= 0n; i -= 4n) {
391
- let tmp = toStringHelp(
392
- WasmI32.load(ptr + i, 20n),
393
- extraIndents,
394
- false
395
- )
396
- strings = [tmp, ...strings]
397
- if (i > 0n) {
398
- strings = [comspace, ...strings]
399
- }
400
- }
401
- let lparen = "("
402
- strings = [variantName, lparen, ...strings]
519
+ let length = WasmI32.load(variantPtr, 12n)
520
+ let variantName = allocateString(length)
521
+ Memory.copy(variantName + 8n, variantPtr + 16n, length)
522
+ let variantName = WasmI32.toGrain(variantName): String
523
+ let distToRecordFields = WasmI32.load(variantPtr, 4n)
524
+ let isRecordVariant = distToRecordFields != 0n
525
+ if (isRecordVariant) {
526
+ let fields = variantPtr + distToRecordFields
527
+ let recordArity = WasmI32.load(ptr, 16n)
528
+ let recordVariantFields = getFieldArray(fields, recordArity)
529
+ let recordString = recordToString(
530
+ ptr,
531
+ recordArity,
532
+ recordVariantFields,
533
+ 20n,
534
+ extraIndents,
535
+ cycles
536
+ )
537
+ Memory.decRef(recordVariantFields)
538
+ let strings = [variantName, recordString]
403
539
 
404
540
  join(strings)
541
+ } else {
542
+ tupleVariantToString(ptr, variantName, extraIndents, cycles)
405
543
  }
406
544
  }
407
545
  }
@@ -412,68 +550,55 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
412
550
  if (fields == -1n) {
413
551
  "<record value>"
414
552
  } else {
415
- let prevPadAmount = extraIndents * 2n
416
- let prevSpacePadding = if (prevPadAmount == 0n) {
417
- ""
553
+ if ((recordArity & _VISITED_BIT) != 0n) {
554
+ reportCycle(ptr, cycles)
418
555
  } else {
419
- let v = allocateString(prevPadAmount)
420
- Memory.fill(
421
- v + 8n,
422
- 0x20n,
423
- prevPadAmount
424
- ) // create indentation for closing brace
425
- WasmI32.toGrain(v): String
426
- }
427
- let padAmount = (extraIndents + 1n) * 2n
428
- let spacePadding = allocateString(padAmount)
429
- Memory.fill(spacePadding + 8n, 0x20n, padAmount) // create indentation
430
- let spacePadding = WasmI32.toGrain(spacePadding): String
431
- let newline = "\n"
432
- let rbrace = "}"
433
- let mut strings = [newline, prevSpacePadding, rbrace]
434
- let colspace = ": "
435
- let comlf = ",\n"
436
- for (let mut i = recordArity * 4n - 4n; i >= 0n; i -= 4n) {
437
- let fieldName = WasmI32.toGrain(WasmI32.load(fields + i, 8n)): String
438
- let fieldValue = toStringHelp(
439
- WasmI32.load(ptr + i, 16n),
440
- extraIndents + 1n,
441
- false
556
+ WasmI32.store(ptr, _VISITED_BIT | recordArity, 12n)
557
+ let result = recordToString(
558
+ ptr,
559
+ recordArity,
560
+ fields,
561
+ 16n,
562
+ extraIndents,
563
+ cycles
442
564
  )
443
- strings = [spacePadding, fieldName, colspace, fieldValue, ...strings]
444
- if (i > 0n) {
445
- strings = [comlf, ...strings]
446
- }
565
+ Memory.decRef(fields)
566
+ WasmI32.store(ptr, recordArity, 12n)
567
+ join([cyclePrefix(ptr, cycles), result])
447
568
  }
448
- let lbrace = "{\n"
449
- strings = [lbrace, ...strings]
450
-
451
- join(strings)
452
569
  }
453
570
  },
454
571
  t when t == Tags._GRAIN_ARRAY_HEAP_TAG => {
455
572
  let arity = WasmI32.load(ptr, 4n)
456
- let rbrack = "]"
457
- let mut strings = [rbrack]
458
- let comspace = ", "
459
- for (let mut i = arity * 4n - 4n; i >= 0n; i -= 4n) {
460
- let item = toStringHelp(WasmI32.load(ptr + i, 8n), extraIndents, false)
461
- strings = [item, ...strings]
462
- if (i > 0n) {
463
- strings = [comspace, ...strings]
573
+ if ((arity & _VISITED_BIT) != 0n) {
574
+ reportCycle(ptr, cycles)
575
+ } else {
576
+ WasmI32.store(ptr, _VISITED_BIT | arity, 4n)
577
+ let rbrack = "]"
578
+ let mut strings = [rbrack]
579
+ let comspace = ", "
580
+ for (let mut i = arity * 4n - 4n; i >= 0n; i -= 4n) {
581
+ let item = toStringHelp(
582
+ WasmI32.load(ptr + i, 8n),
583
+ extraIndents,
584
+ false,
585
+ cycles
586
+ )
587
+ strings = [item, ...strings]
588
+ if (i > 0n) {
589
+ strings = [comspace, ...strings]
590
+ }
464
591
  }
465
- }
466
- let lbrack = "[> "
467
- strings = [lbrack, ...strings]
592
+ WasmI32.store(ptr, arity, 4n)
593
+ let lbrack = "[> "
594
+ strings = [lbrack, ...strings]
468
595
 
469
- join(strings)
596
+ join([cyclePrefix(ptr, cycles), ...strings])
597
+ }
470
598
  },
471
599
  t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => {
472
600
  let numberTag = WasmI32.load(ptr, 4n)
473
601
  match (numberTag) {
474
- t when t == Tags._GRAIN_INT32_BOXED_NUM_TAG => {
475
- NumberUtils.itoa32(WasmI32.load(ptr, 8n), 10n)
476
- },
477
602
  t when t == Tags._GRAIN_INT64_BOXED_NUM_TAG => {
478
603
  NumberUtils.itoa64(WasmI64.load(ptr, 8n), 10n)
479
604
  },
@@ -487,9 +612,6 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
487
612
  let strings = [numerator, slash, denominator]
488
613
  join(strings)
489
614
  },
490
- t when t == Tags._GRAIN_FLOAT32_BOXED_NUM_TAG => {
491
- NumberUtils.dtoa(WasmF64.promoteF32(WasmF32.load(ptr, 8n)))
492
- },
493
615
  t when t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG => {
494
616
  NumberUtils.dtoa(WasmF64.load(ptr, 8n))
495
617
  },
@@ -498,26 +620,34 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
498
620
  },
499
621
  }
500
622
  },
623
+ t when t == Tags._GRAIN_INT32_HEAP_TAG => {
624
+ NumberUtils.itoa32(WasmI32.load(ptr, 4n), 10n)
625
+ },
626
+ t when t == Tags._GRAIN_FLOAT32_HEAP_TAG => {
627
+ NumberUtils.dtoa(WasmF64.promoteF32(WasmF32.load(ptr, 4n)))
628
+ },
629
+ t when t == Tags._GRAIN_UINT32_HEAP_TAG => {
630
+ NumberUtils.utoa32(WasmI32.load(ptr, 4n), 10n)
631
+ },
632
+ t when t == Tags._GRAIN_UINT64_HEAP_TAG => {
633
+ NumberUtils.utoa64(WasmI64.load(ptr, 8n), 10n)
634
+ },
501
635
  t when t == Tags._GRAIN_TUPLE_HEAP_TAG => {
502
636
  let tupleLength = WasmI32.load(ptr, 4n)
503
- if ((tupleLength & 0x80000000n) != 0n) {
504
- "<cyclic tuple>"
637
+ if ((tupleLength & _VISITED_BIT) != 0n) {
638
+ reportCycle(ptr, cycles)
505
639
  } else {
506
- WasmI32.store(ptr, 0x80000000n | tupleLength, 4n)
640
+ WasmI32.store(ptr, _VISITED_BIT | tupleLength, 4n)
507
641
  let comspace = ", "
508
642
  let rparen = ")"
643
+ let mut lparen = "("
509
644
  let mut strings = [rparen]
510
- if (tupleLength <= 1n) {
511
- // Special case: unary tuple
512
- let comma = ","
513
- strings = [comma, ...strings]
514
- void
515
- }
516
645
  for (let mut i = tupleLength * 4n - 4n; i >= 0n; i -= 4n) {
517
646
  let item = toStringHelp(
518
647
  WasmI32.load(ptr + i, 8n),
519
648
  extraIndents,
520
- false
649
+ false,
650
+ cycles
521
651
  )
522
652
  strings = [item, ...strings]
523
653
  if (i > 0n) {
@@ -527,10 +657,13 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
527
657
  WasmI32.store(ptr, tupleLength, 4n)
528
658
 
529
659
  Memory.incRef(WasmI32.fromGrain(strings))
530
- let lparen = "("
531
660
  strings = [lparen, ...strings]
532
-
533
- join(strings)
661
+ if (tupleLength <= 1n) {
662
+ // Special case: unary tuple, which is not valid Grain syntax; however, boxed values
663
+ // are stored as a unary tuple, so we keep this in case one gets printed
664
+ strings = ["box", ...strings]
665
+ }
666
+ join([cyclePrefix(ptr, cycles), ...strings])
534
667
  }
535
668
  },
536
669
  t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
@@ -547,21 +680,37 @@ let rec heapValueToString = (ptr, extraIndents, toplevel) => {
547
680
  join(strings)
548
681
  },
549
682
  }
550
- },
551
- toStringHelp = (grainValue, extraIndents, toplevel) => {
683
+ }
684
+ and toStringHelp = (grainValue, extraIndents, toplevel, cycles) => {
552
685
  if ((grainValue & 1n) != 0n) {
553
686
  // Simple (unboxed) numbers
554
687
  NumberUtils.itoa32(grainValue >> 1n, 10n)
555
688
  } else {
556
689
  let tag = grainValue & 7n
557
690
  if (tag == Tags._GRAIN_GENERIC_HEAP_TAG_TYPE) {
558
- heapValueToString(grainValue, extraIndents, toplevel)
559
- } else if (tag == Tags._GRAIN_CHAR_TAG_TYPE) {
560
- let string = usvToString(grainValue >> 3n)
561
- if (toplevel) {
562
- string
691
+ heapValueToString(grainValue, extraIndents, toplevel, cycles)
692
+ } else if (tag == Tags._GRAIN_SHORTVAL_TAG_TYPE) {
693
+ let shortVal = grainValue >> 8n
694
+ let shortValTag = (grainValue & 0xF8n) >> 3n
695
+ if (shortValTag == Tags._GRAIN_CHAR_SHORTVAL_TAG) {
696
+ let string = usvToString(shortVal)
697
+ if (toplevel) {
698
+ string
699
+ } else {
700
+ escapeChar(string)
701
+ }
702
+ } else if (
703
+ shortValTag == Tags._GRAIN_INT8_SHORTVAL_TAG ||
704
+ shortValTag == Tags._GRAIN_INT16_SHORTVAL_TAG
705
+ ) {
706
+ NumberUtils.itoa32(shortVal, 10n)
707
+ } else if (
708
+ shortValTag == Tags._GRAIN_UINT8_SHORTVAL_TAG ||
709
+ shortValTag == Tags._GRAIN_UINT16_SHORTVAL_TAG
710
+ ) {
711
+ NumberUtils.utoa32(shortVal, 10n)
563
712
  } else {
564
- escapeChar(string)
713
+ "<unknown small value>"
565
714
  }
566
715
  } else if (grainValue == WasmI32.fromGrain(true)) {
567
716
  "true"
@@ -573,8 +722,8 @@ toStringHelp = (grainValue, extraIndents, toplevel) => {
573
722
  "<unknown value>"
574
723
  }
575
724
  }
576
- },
577
- listToString = (ptr, extraIndents) => {
725
+ }
726
+ and listToString = (ptr, extraIndents, cycles) => {
578
727
  let mut cur = ptr
579
728
  let mut isFirst = true
580
729
 
@@ -584,14 +733,19 @@ listToString = (ptr, extraIndents) => {
584
733
 
585
734
  while (true) {
586
735
  let variantId = WasmI32.load(cur, 12n) >> 1n // tagged number
587
- if (variantId == 0n) {
736
+ if (variantId == 1n) {
588
737
  break
589
738
  } else {
590
739
  if (!isFirst) {
591
740
  strings = [commaspace, ...strings]
592
741
  }
593
742
  isFirst = false
594
- let item = toStringHelp(WasmI32.load(cur, 20n), extraIndents, false)
743
+ let item = toStringHelp(
744
+ WasmI32.load(cur, 20n),
745
+ extraIndents,
746
+ false,
747
+ cycles
748
+ )
595
749
  strings = [item, ...strings]
596
750
  cur = WasmI32.load(cur, 24n)
597
751
  }
@@ -601,32 +755,176 @@ listToString = (ptr, extraIndents) => {
601
755
  let reversed = reverse(strings)
602
756
  join(reversed)
603
757
  }
758
+ and tupleVariantToString = (ptr, variantName, extraIndents, cycles) => {
759
+ let variantArity = WasmI32.load(ptr, 16n)
760
+ if (variantArity == 0n) {
761
+ variantName
762
+ } else {
763
+ let comspace = ", "
764
+ let rparen = ")"
765
+ let mut strings = [rparen]
766
+ for (let mut i = variantArity * 4n - 4n; i >= 0n; i -= 4n) {
767
+ let tmp = toStringHelp(
768
+ WasmI32.load(ptr + i, 20n),
769
+ extraIndents,
770
+ false,
771
+ cycles
772
+ )
773
+ strings = [tmp, ...strings]
774
+ if (i > 0n) {
775
+ strings = [comspace, ...strings]
776
+ }
777
+ }
778
+ let lparen = "("
779
+ strings = [variantName, lparen, ...strings]
604
780
 
781
+ join(strings)
782
+ }
783
+ }
784
+ and recordToString = (
785
+ ptr,
786
+ recordArity,
787
+ fields,
788
+ contentOffset,
789
+ extraIndents,
790
+ cycles,
791
+ ) => {
792
+ let prevPadAmount = extraIndents * 2n
793
+ let prevSpacePadding = if (prevPadAmount == 0n) {
794
+ ""
795
+ } else {
796
+ let v = allocateString(prevPadAmount)
797
+ Memory.fill(v + 8n, 0x20n, prevPadAmount) // create indentation for closing brace
798
+ WasmI32.toGrain(v): String
799
+ }
800
+ let padAmount = (extraIndents + 1n) * 2n
801
+ let spacePadding = allocateString(padAmount)
802
+ Memory.fill(spacePadding + 8n, 0x20n, padAmount) // create indentation
803
+ let spacePadding = WasmI32.toGrain(spacePadding): String
804
+ let newline = "\n"
805
+ let rbrace = "}"
806
+ let mut strings = [newline, prevSpacePadding, rbrace]
807
+ let colspace = ": "
808
+ let comlf = ",\n"
809
+ for (let mut i = recordArity * 4n - 4n; i >= 0n; i -= 4n) {
810
+ let fieldName = WasmI32.toGrain(WasmI32.load(fields + i, 8n)): String
811
+ let fieldValue = toStringHelp(
812
+ WasmI32.load(ptr + i, contentOffset),
813
+ extraIndents + 1n,
814
+ false,
815
+ cycles
816
+ )
817
+ strings = [spacePadding, fieldName, colspace, fieldValue, ...strings]
818
+ if (i > 0n) {
819
+ strings = [comlf, ...strings]
820
+ }
821
+ }
822
+ let lbrace = "{\n"
823
+ strings = [lbrace, ...strings]
824
+
825
+ join(strings)
826
+ }
827
+
828
+ /**
829
+ * Converts the given operand to a string.
830
+ * Provides a better representation of data types if those types are provided from the module.
831
+ *
832
+ * @param value: The operand
833
+ * @returns The operand, as a string
834
+ *
835
+ * @since v0.1.0
836
+ */
605
837
  @unsafe
606
- export let toString = value => {
607
- let value = WasmI32.fromGrain(value)
608
- toStringHelp(value, 0n, true)
838
+ provide let toString = value => {
839
+ let ptr = WasmI32.fromGrain(value)
840
+ let cycles = makeVecBox()
841
+ let string = toStringHelp(ptr, 0n, true, cycles)
842
+ freeVecBox(cycles)
843
+ // Prevent the tail call to allow the value to stay alive
844
+ // while we operate on the raw pointer
845
+ ignore(value)
846
+ string
609
847
  }
610
848
 
849
+ /**
850
+ * Prints the given operand to the console. Works for any type. Internally, calls `toString`
851
+ * on the operand, so a better representation of data type will be printed if those types
852
+ * are provided from the module.
853
+ *
854
+ * @param value: The operand
855
+ * @param suffix: The string to print after the argument
856
+ *
857
+ * @since v0.1.0
858
+ */
611
859
  @unsafe
612
- export let print = value => {
860
+ provide let print = (value, suffix="\n") => {
613
861
  // First convert the value to string, if it isn't one already.
614
862
  let valuePtr = WasmI32.fromGrain(value)
615
863
  let s = toString(value)
616
- let ptr = WasmI32.fromGrain(s)
617
- // iov: [<ptr to string> <nbytes of string> <ptr to newline> <nbytes of newline>] (32 bytes)
618
- // buf: <iov> <written> <newline char>
864
+ let combined = concat(s, suffix)
865
+ let ptr = WasmI32.fromGrain(combined)
866
+ // iov: [<ptr to string> <nbytes of string>]
867
+ // buf: <iov> <written>
619
868
  // fd_write(STDOUT (1), iov, len(iov), written)
620
- let buf = Memory.malloc(37n)
869
+ let buf = Memory.malloc(20n)
621
870
  let iov = buf
622
- let written = buf + 32n
623
- let lf = buf + 36n
871
+ let written = buf + 16n
624
872
  WasmI32.store(iov, ptr + 8n, 0n)
625
873
  WasmI32.store(iov, WasmI32.load(ptr, 4n), 4n)
626
- WasmI32.store8(lf, 10n, 0n)
627
- WasmI32.store(iov, lf, 8n)
628
- WasmI32.store(iov, 1n, 12n)
629
- fd_write(1n, iov, 2n, written)
874
+ fd_write(1n, iov, 1n, written)
630
875
  Memory.free(buf)
876
+ ignore(value)
877
+ ignore(suffix)
631
878
  void
632
879
  }
880
+
881
+ @unsafe
882
+ provide let getCodePoint = (ptr: WasmI32) => {
883
+ // Algorithm from https://encoding.spec.whatwg.org/#utf-8-decoder
884
+ use WasmI32.{ (+), (&), (|), (<<), leU as (<=), geU as (>=), (==) }
885
+
886
+ let mut codePoint = 0n
887
+ let mut bytesSeen = 0n
888
+ let mut bytesNeeded = 0n
889
+ let mut lowerBoundary = 0x80n
890
+ let mut upperBoundary = 0xBFn
891
+
892
+ let mut offset = 0n
893
+
894
+ while (true) {
895
+ let byte = WasmI32.load8U(ptr + offset, 0n)
896
+ offset += 1n
897
+ if (bytesNeeded == 0n) {
898
+ if (byte >= 0x00n && byte <= 0x7Fn) {
899
+ return byte
900
+ } else if (byte >= 0xC2n && byte <= 0xDFn) {
901
+ bytesNeeded = 1n
902
+ codePoint = byte & 0x1Fn
903
+ } else if (byte >= 0xE0n && byte <= 0xEFn) {
904
+ if (byte == 0xE0n) lowerBoundary = 0xA0n
905
+ if (byte == 0xEDn) upperBoundary = 0x9Fn
906
+ bytesNeeded = 2n
907
+ codePoint = byte & 0xFn
908
+ } else if (byte >= 0xF0n && byte <= 0xF4n) {
909
+ if (byte == 0xF0n) lowerBoundary = 0x90n
910
+ if (byte == 0xF4n) upperBoundary = 0x8Fn
911
+ bytesNeeded = 3n
912
+ codePoint = byte & 0x7n
913
+ } else {
914
+ throw MalformedUnicode
915
+ }
916
+ continue
917
+ }
918
+ if (!(lowerBoundary <= byte && byte <= upperBoundary)) {
919
+ throw MalformedUnicode
920
+ }
921
+ lowerBoundary = 0x80n
922
+ upperBoundary = 0xBFn
923
+ codePoint = codePoint << 6n | byte & 0x3Fn
924
+ bytesSeen += 1n
925
+ if (bytesSeen == bytesNeeded) {
926
+ return codePoint
927
+ }
928
+ }
929
+ return 0n
930
+ }