@elaraai/east 0.0.1-beta.0

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 (251) hide show
  1. package/LICENSE.md +682 -0
  2. package/README.md +276 -0
  3. package/dist/src/analyze.d.ts +95 -0
  4. package/dist/src/analyze.d.ts.map +1 -0
  5. package/dist/src/analyze.js +1110 -0
  6. package/dist/src/analyze.js.map +1 -0
  7. package/dist/src/ast.d.ts +263 -0
  8. package/dist/src/ast.d.ts.map +1 -0
  9. package/dist/src/ast.js +151 -0
  10. package/dist/src/ast.js.map +1 -0
  11. package/dist/src/ast_to_ir.d.ts +24 -0
  12. package/dist/src/ast_to_ir.d.ts.map +1 -0
  13. package/dist/src/ast_to_ir.js +834 -0
  14. package/dist/src/ast_to_ir.js.map +1 -0
  15. package/dist/src/builtins.d.ts +18 -0
  16. package/dist/src/builtins.d.ts.map +1 -0
  17. package/dist/src/builtins.js +1105 -0
  18. package/dist/src/builtins.js.map +1 -0
  19. package/dist/src/comparison.d.ts +28 -0
  20. package/dist/src/comparison.d.ts.map +1 -0
  21. package/dist/src/comparison.js +1017 -0
  22. package/dist/src/comparison.js.map +1 -0
  23. package/dist/src/compile.d.ts +22 -0
  24. package/dist/src/compile.d.ts.map +1 -0
  25. package/dist/src/compile.js +3260 -0
  26. package/dist/src/compile.js.map +1 -0
  27. package/dist/src/containers/ref.d.ts +106 -0
  28. package/dist/src/containers/ref.d.ts.map +1 -0
  29. package/dist/src/containers/ref.js +100 -0
  30. package/dist/src/containers/ref.js.map +1 -0
  31. package/dist/src/containers/sortedmap.d.ts +165 -0
  32. package/dist/src/containers/sortedmap.d.ts.map +1 -0
  33. package/dist/src/containers/sortedmap.js +237 -0
  34. package/dist/src/containers/sortedmap.js.map +1 -0
  35. package/dist/src/containers/sortedset.d.ts +185 -0
  36. package/dist/src/containers/sortedset.d.ts.map +1 -0
  37. package/dist/src/containers/sortedset.js +312 -0
  38. package/dist/src/containers/sortedset.js.map +1 -0
  39. package/dist/src/containers/variant.d.ts +131 -0
  40. package/dist/src/containers/variant.d.ts.map +1 -0
  41. package/dist/src/containers/variant.js +68 -0
  42. package/dist/src/containers/variant.js.map +1 -0
  43. package/dist/src/datetime_format/parse.d.ts +50 -0
  44. package/dist/src/datetime_format/parse.d.ts.map +1 -0
  45. package/dist/src/datetime_format/parse.js +908 -0
  46. package/dist/src/datetime_format/parse.js.map +1 -0
  47. package/dist/src/datetime_format/print.d.ts +35 -0
  48. package/dist/src/datetime_format/print.d.ts.map +1 -0
  49. package/dist/src/datetime_format/print.js +157 -0
  50. package/dist/src/datetime_format/print.js.map +1 -0
  51. package/dist/src/datetime_format/tokenize.d.ts +76 -0
  52. package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
  53. package/dist/src/datetime_format/tokenize.js +271 -0
  54. package/dist/src/datetime_format/tokenize.js.map +1 -0
  55. package/dist/src/datetime_format/types.d.ts +99 -0
  56. package/dist/src/datetime_format/types.d.ts.map +1 -0
  57. package/dist/src/datetime_format/types.js +103 -0
  58. package/dist/src/datetime_format/types.js.map +1 -0
  59. package/dist/src/datetime_format/validate.d.ts +51 -0
  60. package/dist/src/datetime_format/validate.d.ts.map +1 -0
  61. package/dist/src/datetime_format/validate.js +208 -0
  62. package/dist/src/datetime_format/validate.js.map +1 -0
  63. package/dist/src/default.d.ts +21 -0
  64. package/dist/src/default.d.ts.map +1 -0
  65. package/dist/src/default.js +82 -0
  66. package/dist/src/default.js.map +1 -0
  67. package/dist/src/eastir.d.ts +33 -0
  68. package/dist/src/eastir.d.ts.map +1 -0
  69. package/dist/src/eastir.js +92 -0
  70. package/dist/src/eastir.js.map +1 -0
  71. package/dist/src/error.d.ts +13 -0
  72. package/dist/src/error.d.ts.map +1 -0
  73. package/dist/src/error.js +8 -0
  74. package/dist/src/error.js.map +1 -0
  75. package/dist/src/expr/array.d.ts +1711 -0
  76. package/dist/src/expr/array.d.ts.map +1 -0
  77. package/dist/src/expr/array.js +1805 -0
  78. package/dist/src/expr/array.js.map +1 -0
  79. package/dist/src/expr/ast.d.ts +17 -0
  80. package/dist/src/expr/ast.d.ts.map +1 -0
  81. package/dist/src/expr/ast.js +302 -0
  82. package/dist/src/expr/ast.js.map +1 -0
  83. package/dist/src/expr/blob.d.ts +141 -0
  84. package/dist/src/expr/blob.d.ts.map +1 -0
  85. package/dist/src/expr/blob.js +198 -0
  86. package/dist/src/expr/blob.js.map +1 -0
  87. package/dist/src/expr/block.d.ts +201 -0
  88. package/dist/src/expr/block.d.ts.map +1 -0
  89. package/dist/src/expr/block.js +1505 -0
  90. package/dist/src/expr/block.js.map +1 -0
  91. package/dist/src/expr/boolean.d.ts +207 -0
  92. package/dist/src/expr/boolean.d.ts.map +1 -0
  93. package/dist/src/expr/boolean.js +261 -0
  94. package/dist/src/expr/boolean.js.map +1 -0
  95. package/dist/src/expr/datetime.d.ts +544 -0
  96. package/dist/src/expr/datetime.d.ts.map +1 -0
  97. package/dist/src/expr/datetime.js +980 -0
  98. package/dist/src/expr/datetime.js.map +1 -0
  99. package/dist/src/expr/dict.d.ts +1242 -0
  100. package/dist/src/expr/dict.d.ts.map +1 -0
  101. package/dist/src/expr/dict.js +1492 -0
  102. package/dist/src/expr/dict.js.map +1 -0
  103. package/dist/src/expr/expr.d.ts +95 -0
  104. package/dist/src/expr/expr.d.ts.map +1 -0
  105. package/dist/src/expr/expr.js +171 -0
  106. package/dist/src/expr/expr.js.map +1 -0
  107. package/dist/src/expr/float.d.ts +357 -0
  108. package/dist/src/expr/float.d.ts.map +1 -0
  109. package/dist/src/expr/float.js +637 -0
  110. package/dist/src/expr/float.js.map +1 -0
  111. package/dist/src/expr/function.d.ts +46 -0
  112. package/dist/src/expr/function.d.ts.map +1 -0
  113. package/dist/src/expr/function.js +58 -0
  114. package/dist/src/expr/function.js.map +1 -0
  115. package/dist/src/expr/index.d.ts +450 -0
  116. package/dist/src/expr/index.d.ts.map +1 -0
  117. package/dist/src/expr/index.js +423 -0
  118. package/dist/src/expr/index.js.map +1 -0
  119. package/dist/src/expr/integer.d.ts +256 -0
  120. package/dist/src/expr/integer.d.ts.map +1 -0
  121. package/dist/src/expr/integer.js +311 -0
  122. package/dist/src/expr/integer.js.map +1 -0
  123. package/dist/src/expr/libs/array.d.ts +106 -0
  124. package/dist/src/expr/libs/array.d.ts.map +1 -0
  125. package/dist/src/expr/libs/array.js +140 -0
  126. package/dist/src/expr/libs/array.js.map +1 -0
  127. package/dist/src/expr/libs/blob.d.ts +42 -0
  128. package/dist/src/expr/libs/blob.d.ts.map +1 -0
  129. package/dist/src/expr/libs/blob.js +70 -0
  130. package/dist/src/expr/libs/blob.js.map +1 -0
  131. package/dist/src/expr/libs/datetime.d.ts +479 -0
  132. package/dist/src/expr/libs/datetime.d.ts.map +1 -0
  133. package/dist/src/expr/libs/datetime.js +624 -0
  134. package/dist/src/expr/libs/datetime.js.map +1 -0
  135. package/dist/src/expr/libs/dict.d.ts +66 -0
  136. package/dist/src/expr/libs/dict.d.ts.map +1 -0
  137. package/dist/src/expr/libs/dict.js +77 -0
  138. package/dist/src/expr/libs/dict.js.map +1 -0
  139. package/dist/src/expr/libs/float.d.ts +299 -0
  140. package/dist/src/expr/libs/float.d.ts.map +1 -0
  141. package/dist/src/expr/libs/float.js +564 -0
  142. package/dist/src/expr/libs/float.js.map +1 -0
  143. package/dist/src/expr/libs/integer.d.ts +228 -0
  144. package/dist/src/expr/libs/integer.d.ts.map +1 -0
  145. package/dist/src/expr/libs/integer.js +398 -0
  146. package/dist/src/expr/libs/integer.js.map +1 -0
  147. package/dist/src/expr/libs/set.d.ts +59 -0
  148. package/dist/src/expr/libs/set.d.ts.map +1 -0
  149. package/dist/src/expr/libs/set.js +69 -0
  150. package/dist/src/expr/libs/set.js.map +1 -0
  151. package/dist/src/expr/libs/string.d.ts +71 -0
  152. package/dist/src/expr/libs/string.d.ts.map +1 -0
  153. package/dist/src/expr/libs/string.js +75 -0
  154. package/dist/src/expr/libs/string.js.map +1 -0
  155. package/dist/src/expr/never.d.ts +15 -0
  156. package/dist/src/expr/never.d.ts.map +1 -0
  157. package/dist/src/expr/never.js +12 -0
  158. package/dist/src/expr/never.js.map +1 -0
  159. package/dist/src/expr/null.d.ts +15 -0
  160. package/dist/src/expr/null.d.ts.map +1 -0
  161. package/dist/src/expr/null.js +12 -0
  162. package/dist/src/expr/null.js.map +1 -0
  163. package/dist/src/expr/ref.d.ts +103 -0
  164. package/dist/src/expr/ref.d.ts.map +1 -0
  165. package/dist/src/expr/ref.js +131 -0
  166. package/dist/src/expr/ref.js.map +1 -0
  167. package/dist/src/expr/regex_validation.d.ts +25 -0
  168. package/dist/src/expr/regex_validation.d.ts.map +1 -0
  169. package/dist/src/expr/regex_validation.js +130 -0
  170. package/dist/src/expr/regex_validation.js.map +1 -0
  171. package/dist/src/expr/set.d.ts +1071 -0
  172. package/dist/src/expr/set.d.ts.map +1 -0
  173. package/dist/src/expr/set.js +1137 -0
  174. package/dist/src/expr/set.js.map +1 -0
  175. package/dist/src/expr/string.d.ts +414 -0
  176. package/dist/src/expr/string.d.ts.map +1 -0
  177. package/dist/src/expr/string.js +683 -0
  178. package/dist/src/expr/string.js.map +1 -0
  179. package/dist/src/expr/struct.d.ts +48 -0
  180. package/dist/src/expr/struct.d.ts.map +1 -0
  181. package/dist/src/expr/struct.js +65 -0
  182. package/dist/src/expr/struct.js.map +1 -0
  183. package/dist/src/expr/types.d.ts +68 -0
  184. package/dist/src/expr/types.d.ts.map +1 -0
  185. package/dist/src/expr/types.js +6 -0
  186. package/dist/src/expr/types.js.map +1 -0
  187. package/dist/src/expr/variant.d.ts +137 -0
  188. package/dist/src/expr/variant.d.ts.map +1 -0
  189. package/dist/src/expr/variant.js +105 -0
  190. package/dist/src/expr/variant.js.map +1 -0
  191. package/dist/src/fuzz.d.ts +80 -0
  192. package/dist/src/fuzz.d.ts.map +1 -0
  193. package/dist/src/fuzz.js +300 -0
  194. package/dist/src/fuzz.js.map +1 -0
  195. package/dist/src/index.d.ts +21 -0
  196. package/dist/src/index.d.ts.map +1 -0
  197. package/dist/src/index.js +21 -0
  198. package/dist/src/index.js.map +1 -0
  199. package/dist/src/internal.d.ts +36 -0
  200. package/dist/src/internal.d.ts.map +1 -0
  201. package/dist/src/internal.js +11 -0
  202. package/dist/src/internal.js.map +1 -0
  203. package/dist/src/ir.d.ts +1571 -0
  204. package/dist/src/ir.d.ts.map +1 -0
  205. package/dist/src/ir.js +56 -0
  206. package/dist/src/ir.js.map +1 -0
  207. package/dist/src/location.d.ts +48 -0
  208. package/dist/src/location.d.ts.map +1 -0
  209. package/dist/src/location.js +62 -0
  210. package/dist/src/location.js.map +1 -0
  211. package/dist/src/platform.d.ts +21 -0
  212. package/dist/src/platform.d.ts.map +1 -0
  213. package/dist/src/platform.js +8 -0
  214. package/dist/src/platform.js.map +1 -0
  215. package/dist/src/serialization/beast.d.ts +39 -0
  216. package/dist/src/serialization/beast.d.ts.map +1 -0
  217. package/dist/src/serialization/beast.js +555 -0
  218. package/dist/src/serialization/beast.js.map +1 -0
  219. package/dist/src/serialization/beast2-stream.d.ts +38 -0
  220. package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
  221. package/dist/src/serialization/beast2-stream.js +665 -0
  222. package/dist/src/serialization/beast2-stream.js.map +1 -0
  223. package/dist/src/serialization/beast2.d.ts +41 -0
  224. package/dist/src/serialization/beast2.d.ts.map +1 -0
  225. package/dist/src/serialization/beast2.js +489 -0
  226. package/dist/src/serialization/beast2.js.map +1 -0
  227. package/dist/src/serialization/binary-utils.d.ts +151 -0
  228. package/dist/src/serialization/binary-utils.d.ts.map +1 -0
  229. package/dist/src/serialization/binary-utils.js +929 -0
  230. package/dist/src/serialization/binary-utils.js.map +1 -0
  231. package/dist/src/serialization/east.d.ts +84 -0
  232. package/dist/src/serialization/east.d.ts.map +1 -0
  233. package/dist/src/serialization/east.js +1802 -0
  234. package/dist/src/serialization/east.js.map +1 -0
  235. package/dist/src/serialization/index.d.ts +11 -0
  236. package/dist/src/serialization/index.d.ts.map +1 -0
  237. package/dist/src/serialization/index.js +12 -0
  238. package/dist/src/serialization/index.js.map +1 -0
  239. package/dist/src/serialization/json.d.ts +36 -0
  240. package/dist/src/serialization/json.d.ts.map +1 -0
  241. package/dist/src/serialization/json.js +849 -0
  242. package/dist/src/serialization/json.js.map +1 -0
  243. package/dist/src/type_of_type.d.ts +115 -0
  244. package/dist/src/type_of_type.d.ts.map +1 -0
  245. package/dist/src/type_of_type.js +362 -0
  246. package/dist/src/type_of_type.js.map +1 -0
  247. package/dist/src/types.d.ts +648 -0
  248. package/dist/src/types.d.ts.map +1 -0
  249. package/dist/src/types.js +1631 -0
  250. package/dist/src/types.js.map +1 -0
  251. package/package.json +87 -0
@@ -0,0 +1,1802 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { printIdentifier, TypeWiden, isTypeEqual, NeverType, NullType, BooleanType, IntegerType, FloatType, StringType, DateTimeType, BlobType, ArrayType, SetType, DictType, StructType, VariantType } from "../types.js";
6
+ import { compareFor } from "../comparison.js";
7
+ import { SortedMap } from "../containers/sortedmap.js";
8
+ import { SortedSet } from "../containers/sortedset.js";
9
+ import { isVariant, variant } from "../containers/variant.js";
10
+ import { EastTypeValueType, toEastTypeValue } from "../type_of_type.js";
11
+ import { ref } from "../containers/ref.js";
12
+ class ParseError extends Error {
13
+ position;
14
+ path;
15
+ constructor(message, position, path = '') {
16
+ super(message);
17
+ this.position = position;
18
+ this.path = path;
19
+ this.name = "ParseError";
20
+ }
21
+ }
22
+ /**
23
+ * Find the length of the common prefix between two path component arrays.
24
+ */
25
+ function _commonPrefixLength(a, b) {
26
+ let i = 0;
27
+ while (i < a.length && i < b.length && a[i] === b[i]) {
28
+ i++;
29
+ }
30
+ return i;
31
+ }
32
+ /**
33
+ * Convert path component array to punctuated string for lookup/storage.
34
+ * Example: [".field", "[3]"] -> ".field[3]"
35
+ */
36
+ function _pathToPunctuated(path) {
37
+ return path.join("");
38
+ }
39
+ /**
40
+ * Compute a relative reference string from currentPath to targetPath.
41
+ * Returns a string like "2#.foo[0]" or "1#"
42
+ *
43
+ * The format is: "upLevels#remaining_path_components_as_punctuated"
44
+ */
45
+ function _encodeRelativeRef(currentPath, targetPath) {
46
+ const commonLen = _commonPrefixLength(currentPath, targetPath);
47
+ const upLevels = currentPath.length - commonLen;
48
+ const remaining = targetPath.slice(commonLen);
49
+ if (remaining.length === 0) {
50
+ return `${upLevels}#`;
51
+ }
52
+ const remainingStr = remaining.join("");
53
+ return `${upLevels}#${remainingStr}`;
54
+ }
55
+ /**
56
+ * Decode a relative reference string and return the target path array.
57
+ * Input like "2#.foo[0]" returns the target path array.
58
+ * Input like "1#" returns the target path array.
59
+ */
60
+ function _decodeRelativeRef(refStr, currentPath) {
61
+ const hashIdx = refStr.indexOf('#');
62
+ if (hashIdx === -1) {
63
+ throw new Error(`Invalid relative reference: ${refStr}`);
64
+ }
65
+ const upLevelStr = refStr.substring(0, hashIdx);
66
+ const remainingStr = refStr.substring(hashIdx + 1);
67
+ let upLevels;
68
+ try {
69
+ upLevels = parseInt(upLevelStr);
70
+ }
71
+ catch {
72
+ throw new Error(`Invalid relative reference: ${refStr}`);
73
+ }
74
+ if (upLevels < 0 || upLevels > currentPath.length) {
75
+ throw new Error(`Invalid relative reference: going up ${upLevels} levels from depth ${currentPath.length}`);
76
+ }
77
+ // Build target path
78
+ const targetPath = currentPath.slice(0, currentPath.length - upLevels);
79
+ // Add remaining components if any
80
+ if (remainingStr.length > 0) {
81
+ // Parse the remaining punctuated path
82
+ // Format: .field[0][key] etc.
83
+ let pos = 0;
84
+ while (pos < remainingStr.length) {
85
+ if (remainingStr[pos] === '.') {
86
+ // Identifier follows
87
+ pos++;
88
+ let end = pos;
89
+ while (end < remainingStr.length && /[a-zA-Z0-9_]/.test(remainingStr[end])) {
90
+ end++;
91
+ }
92
+ targetPath.push(`.${remainingStr.substring(pos, end)}`);
93
+ pos = end;
94
+ }
95
+ else if (remainingStr[pos] === '[') {
96
+ // Bracket expression
97
+ let end = pos + 1;
98
+ let depth = 1;
99
+ while (end < remainingStr.length && depth > 0) {
100
+ if (remainingStr[end] === '[')
101
+ depth++;
102
+ else if (remainingStr[end] === ']')
103
+ depth--;
104
+ end++;
105
+ }
106
+ targetPath.push(remainingStr.substring(pos, end));
107
+ pos = end;
108
+ }
109
+ else {
110
+ pos++;
111
+ }
112
+ }
113
+ }
114
+ return targetPath;
115
+ }
116
+ export function encodeEastFor(type) {
117
+ // Convert EastType to EastTypeValue if necessary
118
+ if (!isVariant(type)) {
119
+ type = toEastTypeValue(type);
120
+ }
121
+ const textEncoder = new TextEncoder();
122
+ const printer = printFor(type);
123
+ return (x) => textEncoder.encode(printer(x, { refs: new Map(), currentPath: [] }));
124
+ }
125
+ export function decodeEastFor(type) {
126
+ // Convert EastType to EastTypeValue if necessary
127
+ if (!isVariant(type)) {
128
+ type = toEastTypeValue(type);
129
+ }
130
+ const textDecoder = new TextDecoder();
131
+ const parser = parseFor(type);
132
+ return (x) => {
133
+ const result = parser(textDecoder.decode(x));
134
+ if (result.success) {
135
+ return result.value;
136
+ }
137
+ else {
138
+ throw new Error(`Failed to decode East value of type ${printFor(EastTypeValueType)(type)}: ${result.error}`);
139
+ }
140
+ };
141
+ }
142
+ export function printFor(type, typeCtx = []) {
143
+ // Convert EastType to EastTypeValue if necessary
144
+ if (!isVariant(type)) {
145
+ type = toEastTypeValue(type);
146
+ }
147
+ if (type.type === "Never") {
148
+ return (_, _ctx) => { throw new Error(`Attempted to print value of type .Never`); };
149
+ }
150
+ else if (type.type === "Null") {
151
+ return (_, _ctx) => "null";
152
+ }
153
+ else if (type.type === "Boolean") {
154
+ return (x, _ctx) => x.toString();
155
+ }
156
+ else if (type.type === "Integer") {
157
+ return (x, _ctx) => x.toString();
158
+ }
159
+ else if (type.type === "Float") {
160
+ return (x, _ctx) => {
161
+ // Handle negative zero specially
162
+ if (Object.is(x, -0)) {
163
+ return "-0.0";
164
+ }
165
+ const str = x.toString();
166
+ // check if str is integer-like
167
+ if (/^-?\d+$/.test(str)) {
168
+ // append a .0 to indicate it's a float
169
+ return str + ".0";
170
+ }
171
+ else {
172
+ return str;
173
+ }
174
+ };
175
+ }
176
+ else if (type.type === "String") {
177
+ return (x, _ctx) => JSON.stringify(x);
178
+ }
179
+ else if (type.type === "DateTime") {
180
+ return (x, _ctx) => x.toISOString().substring(0, 23);
181
+ }
182
+ else if (type.type === "Blob") {
183
+ return (x, _ctx) => `0x${[...x].map(b => b.toString(16).padStart(2, "0")).join("")}`;
184
+ }
185
+ else if (type.type === "Ref") {
186
+ let value_printer;
187
+ const ret = (x, ctx = { refs: new Map(), currentPath: [] }) => {
188
+ // Check for mutable alias
189
+ if (ctx.refs.has(x)) {
190
+ const targetPath = ctx.refs.get(x);
191
+ const refStr = _encodeRelativeRef(ctx.currentPath, targetPath);
192
+ return refStr;
193
+ }
194
+ // Register this ref
195
+ ctx.refs.set(x, [...ctx.currentPath]);
196
+ // Serialize ref
197
+ ctx.currentPath.push(`[]`);
198
+ const str = value_printer(x.value, ctx);
199
+ ctx.currentPath.pop();
200
+ return `&${str}`;
201
+ };
202
+ typeCtx.push(ret);
203
+ value_printer = printFor(type.value, typeCtx);
204
+ typeCtx.pop();
205
+ return ret;
206
+ }
207
+ else if (type.type === "Array") {
208
+ let value_printer;
209
+ const ret = (x, ctx = { refs: new Map(), currentPath: [] }) => {
210
+ // Check for mutable alias
211
+ if (ctx.refs.has(x)) {
212
+ const targetPath = ctx.refs.get(x);
213
+ const refStr = _encodeRelativeRef(ctx.currentPath, targetPath);
214
+ return refStr;
215
+ }
216
+ // Register this array
217
+ ctx.refs.set(x, [...ctx.currentPath]);
218
+ // Serialize elements
219
+ const parts = [];
220
+ for (let i = 0; i < x.length; i++) {
221
+ ctx.currentPath.push(`[${i}]`);
222
+ parts.push(value_printer(x[i], ctx));
223
+ ctx.currentPath.pop();
224
+ }
225
+ return `[${parts.join(", ")}]`;
226
+ };
227
+ typeCtx.push(ret);
228
+ value_printer = printFor(type.value, typeCtx);
229
+ typeCtx.pop();
230
+ return ret;
231
+ }
232
+ else if (type.type === "Set") {
233
+ const key_printer = printFor(type.value, typeCtx);
234
+ return (x, ctx = { refs: new Map(), currentPath: [] }) => {
235
+ // Check for mutable alias
236
+ if (ctx.refs.has(x)) {
237
+ const targetPath = ctx.refs.get(x);
238
+ const refStr = _encodeRelativeRef(ctx.currentPath, targetPath);
239
+ return refStr;
240
+ }
241
+ // Register this set
242
+ ctx.refs.set(x, [...ctx.currentPath]);
243
+ // Serialize elements
244
+ const parts = [];
245
+ for (const k of x) {
246
+ // Use fresh context for keys - no path syntax to reference inside keys
247
+ parts.push(key_printer(k));
248
+ }
249
+ return `{${parts.join(",")}}`;
250
+ };
251
+ }
252
+ else if (type.type === "Dict") {
253
+ const key_printer = printFor(type.value.key, typeCtx);
254
+ let value_printer;
255
+ const ret = (x, ctx = { refs: new Map(), currentPath: [] }) => {
256
+ // Check for mutable alias
257
+ if (ctx.refs.has(x)) {
258
+ const targetPath = ctx.refs.get(x);
259
+ const refStr = _encodeRelativeRef(ctx.currentPath, targetPath);
260
+ return refStr;
261
+ }
262
+ // Register this dict
263
+ ctx.refs.set(x, [...ctx.currentPath]);
264
+ if (x.size === 0)
265
+ return "{:}";
266
+ // Serialize entries
267
+ const parts = [];
268
+ for (const [k, v] of x) {
269
+ // Use fresh context for keys - no path syntax to reference inside keys
270
+ const keyStr = key_printer(k);
271
+ // Print value with path tracking
272
+ ctx.currentPath.push(`[${keyStr}]`);
273
+ const valueStr = value_printer(v, ctx);
274
+ ctx.currentPath.pop();
275
+ parts.push(`${keyStr}:${valueStr}`);
276
+ }
277
+ return `{${parts.join(",")}}`;
278
+ };
279
+ typeCtx.push(ret);
280
+ value_printer = printFor(type.value.value, typeCtx);
281
+ typeCtx.pop();
282
+ return ret;
283
+ }
284
+ else if (type.type === "Struct") {
285
+ const field_printers = [];
286
+ const ret = (x, ctx = { refs: new Map(), currentPath: [] }) => {
287
+ const parts = [];
288
+ for (const [k, printer] of field_printers) {
289
+ const fieldName = printIdentifier(k);
290
+ const pathComponent = k === fieldName ? `.${k}` : `.${fieldName}`;
291
+ ctx.currentPath.push(pathComponent);
292
+ const printed = printer(x[k], ctx);
293
+ ctx.currentPath.pop();
294
+ parts.push(`${fieldName}=${printed}`);
295
+ }
296
+ return `(${parts.join(", ")})`;
297
+ };
298
+ typeCtx.push(ret);
299
+ for (const { name: k, type: t } of type.value) {
300
+ field_printers.push([k, printFor(t, typeCtx)]);
301
+ }
302
+ typeCtx.pop();
303
+ return ret;
304
+ }
305
+ else if (type.type === "Variant") {
306
+ const case_printers = {};
307
+ const ret = (x, ctx = { refs: new Map(), currentPath: [] }) => {
308
+ // Add variant case to path
309
+ ctx.currentPath.push(`.${x.type}`);
310
+ const result = case_printers[x.type](x.value, ctx);
311
+ ctx.currentPath.pop();
312
+ return result;
313
+ };
314
+ typeCtx.push(ret);
315
+ for (const { name: k, type: t } of type.value) {
316
+ if (t.type === "Null") {
317
+ const prefix = `.${printIdentifier(k)}`;
318
+ case_printers[k] = (_value, _ctx) => prefix;
319
+ }
320
+ else {
321
+ const prefix = `.${printIdentifier(k)} `;
322
+ const print_data = printFor(t, typeCtx);
323
+ case_printers[k] = (value, ctx) => prefix + print_data(value, ctx);
324
+ }
325
+ }
326
+ typeCtx.pop();
327
+ return ret;
328
+ }
329
+ else if (type.type === "Function") {
330
+ return (_, _ctx) => "λ";
331
+ }
332
+ else if (type.type === "Recursive") {
333
+ const ret = typeCtx[typeCtx.length - Number(type.value)];
334
+ if (ret === undefined) {
335
+ throw new Error(`Internal error: Recursive type context not found`);
336
+ }
337
+ return ret;
338
+ }
339
+ else {
340
+ throw new Error(`Unhandled type ${type.type}`);
341
+ }
342
+ }
343
+ /** Create a parser for a specific EastType.
344
+ *
345
+ * You can optional specify that the resulting value should be frozen (making the collection types immutable).
346
+ */
347
+ // Helper to convert position to line and column
348
+ function getLineAndColumn(input, position) {
349
+ let line = 1;
350
+ let column = 1;
351
+ for (let i = 0; i < position && i < input.length; i++) {
352
+ if (input[i] === '\n') {
353
+ line++;
354
+ column = 1;
355
+ }
356
+ else {
357
+ column++;
358
+ }
359
+ }
360
+ return { line, column };
361
+ }
362
+ export function parseFor(type, frozen = false) {
363
+ // Convert EastType to EastTypeValue if necessary
364
+ if (!isVariant(type)) {
365
+ type = toEastTypeValue(type);
366
+ }
367
+ const parser = createParser(type, frozen);
368
+ return (x) => {
369
+ try {
370
+ const { value, position } = parser(x, 0);
371
+ const pos = consumeWhitespace(x, position);
372
+ if (pos < x.length) {
373
+ const { line, column } = getLineAndColumn(x, pos);
374
+ return {
375
+ success: false,
376
+ error: `Error occurred because unexpected input after parsed value (line ${line}, col ${column}) while parsing value of type "${printFor(EastTypeValueType)(type)}"`,
377
+ position: pos,
378
+ };
379
+ }
380
+ return { success: true, value, position };
381
+ }
382
+ catch (e) {
383
+ if (e instanceof ParseError) {
384
+ const { line, column } = getLineAndColumn(x, e.position);
385
+ const pathStr = e.path ? ` at ${e.path}` : '';
386
+ return {
387
+ success: false,
388
+ error: `Error occurred because ${e.message}${pathStr} (line ${line}, col ${column}) while parsing value of type "${printFor(EastTypeValueType)(type)}"`,
389
+ position: e.position,
390
+ };
391
+ }
392
+ throw e;
393
+ }
394
+ };
395
+ }
396
+ // Consume whitespace by returning new position
397
+ const consumeWhitespace = (input, pos) => {
398
+ while (pos < input.length && /\s/.test(input[pos])) {
399
+ pos++;
400
+ }
401
+ return pos;
402
+ };
403
+ const createParser = (type, frozen, typeCtx = []) => {
404
+ switch (type.type) {
405
+ case "Null":
406
+ return parseNull;
407
+ case "Boolean":
408
+ return parseBoolean;
409
+ case "Integer":
410
+ return parseInteger;
411
+ case "Float":
412
+ return parseFloat;
413
+ case "String":
414
+ return parseString;
415
+ case "DateTime":
416
+ return parseDateTime(frozen);
417
+ case "Blob":
418
+ return parseBlob(frozen);
419
+ case "Ref":
420
+ return createRefParser(type.value, frozen, typeCtx);
421
+ case "Array":
422
+ return createArrayParser(type.value, frozen, typeCtx);
423
+ case "Set":
424
+ return createSetParser(type.value, frozen, typeCtx);
425
+ case "Dict":
426
+ return createDictParser(type.value.key, type.value.value, frozen, typeCtx);
427
+ case "Struct":
428
+ return createStructParser(type.value, frozen, typeCtx);
429
+ case "Variant":
430
+ return createVariantParser(type.value, frozen, typeCtx);
431
+ case "Function":
432
+ throw new Error(`Cannot parse .Function`);
433
+ case "Never":
434
+ return (_input, pos, _ctx) => { throw new ParseError(`Attempted to parse value of type .Never`, pos); };
435
+ case "Recursive":
436
+ const ret = typeCtx[typeCtx.length - Number(type.value)];
437
+ if (ret === undefined) {
438
+ throw new Error(`Internal error: Recursive type context not found`);
439
+ }
440
+ return ret;
441
+ default:
442
+ throw new Error(`Unhandled type: ${type.type}`);
443
+ }
444
+ };
445
+ const parseInteger = (input, pos, _ctx) => {
446
+ pos = consumeWhitespace(input, pos);
447
+ if (pos >= input.length) {
448
+ throw new ParseError("expected integer, got end of input", pos);
449
+ }
450
+ let start = pos;
451
+ if (input[pos] === '-') {
452
+ pos++;
453
+ }
454
+ let hasDigits = false;
455
+ while (pos < input.length && /\d/.test(input[pos])) {
456
+ hasDigits = true;
457
+ pos++;
458
+ }
459
+ if (!hasDigits) {
460
+ const got = start >= input.length ? 'end of input' : `'${input[start]}'`;
461
+ throw new ParseError(`expected integer, got ${got}`, start);
462
+ }
463
+ const intStr = input.slice(start, pos);
464
+ let value;
465
+ try {
466
+ value = BigInt(intStr);
467
+ }
468
+ catch {
469
+ // BigInt() throws if the string is invalid (shouldn't happen given our parsing logic)
470
+ throw new ParseError(`expected integer, got ${JSON.stringify(intStr)}`, start);
471
+ }
472
+ // Check for 64-bit signed integer range: -2^63 to 2^63-1
473
+ if (value < -9223372036854775808n || value > 9223372036854775807n) {
474
+ throw new ParseError(`integer out of range (must be 64-bit signed), got ${intStr}`, start);
475
+ }
476
+ return {
477
+ value,
478
+ position: pos
479
+ };
480
+ };
481
+ const parseFloat = (input, pos, _ctx) => {
482
+ pos = consumeWhitespace(input, pos);
483
+ if (pos >= input.length) {
484
+ throw new ParseError("expected float, got end of input", pos);
485
+ }
486
+ // Accept NaN and Infinity
487
+ if (input.startsWith('NaN', pos)) {
488
+ return { value: NaN, position: pos + 3 };
489
+ }
490
+ if (input.startsWith('Infinity', pos)) {
491
+ return { value: Infinity, position: pos + 8 };
492
+ }
493
+ let start = pos;
494
+ if (input[pos] === '-') {
495
+ pos++;
496
+ if (input.startsWith('Infinity', pos)) {
497
+ return { value: -Infinity, position: pos + 8 };
498
+ }
499
+ }
500
+ let hasDigits = false;
501
+ while (pos < input.length && /\d/.test(input[pos])) {
502
+ hasDigits = true;
503
+ pos++;
504
+ }
505
+ if (!hasDigits) {
506
+ const got = start >= input.length ? 'end of input' : `'${input[start]}'`;
507
+ throw new ParseError(`expected float, got ${got}`, start);
508
+ }
509
+ if (pos < input.length && input[pos] === '.') {
510
+ pos++;
511
+ while (pos < input.length && /\d/.test(input[pos])) {
512
+ pos++;
513
+ }
514
+ }
515
+ if (pos < input.length && (input[pos] === 'e' || input[pos] === 'E')) {
516
+ pos++;
517
+ if (pos < input.length && (input[pos] === '+' || input[pos] === '-')) {
518
+ pos++;
519
+ }
520
+ let expDigits = false;
521
+ while (pos < input.length && /\d/.test(input[pos])) {
522
+ expDigits = true;
523
+ pos++;
524
+ }
525
+ if (!expDigits) {
526
+ throw new ParseError("expected digits in float exponent", pos);
527
+ }
528
+ }
529
+ return {
530
+ value: Number(input.slice(start, pos)),
531
+ position: pos
532
+ };
533
+ };
534
+ /**
535
+ * Checks if the given position is at a valid token terminator.
536
+ * Valid terminators are: EOF, whitespace, comma, or closing brackets/parens/braces.
537
+ */
538
+ const isTokenTerminator = (input, pos) => {
539
+ if (pos >= input.length)
540
+ return true; // EOF
541
+ const ch = input[pos];
542
+ return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' ||
543
+ ch === ',' || ch === ')' || ch === ']' || ch === '}';
544
+ };
545
+ /**
546
+ * Parses a $.path reference and returns the referenced value.
547
+ * Returns null if not at a reference (doesn't start with '$').
548
+ * Throws ParseError if the reference is invalid or undefined.
549
+ */
550
+ const parseReference = (input, pos, ctx) => {
551
+ // Check if this looks like a reference: either "#..." or "N#..." where N is a digit
552
+ let refStart = pos;
553
+ let isRelative = false;
554
+ if (pos >= input.length || input[pos] !== '#') {
555
+ // Check if it starts with a digit followed by '#' (relative reference)
556
+ let digitEnd = pos;
557
+ while (digitEnd < input.length && /[0-9]/.test(input[digitEnd])) {
558
+ digitEnd++;
559
+ }
560
+ if (digitEnd > pos && digitEnd < input.length && input[digitEnd] === '#') {
561
+ isRelative = true;
562
+ refStart = pos;
563
+ }
564
+ else {
565
+ return null; // Not a reference
566
+ }
567
+ }
568
+ if (!ctx) {
569
+ throw new ParseError("#reference outside recursive context", pos);
570
+ }
571
+ // Parse the path - it can contain:
572
+ // - #.field (dot followed by identifier)
573
+ // - #[index] (bracket with number or value)
574
+ // - combinations like #.a.b[0].c
575
+ // OR for relative: N#.field etc.
576
+ let pathStart;
577
+ if (isRelative) {
578
+ // Find the # position
579
+ pathStart = input.indexOf('#', pos);
580
+ refStart = pathStart + 1; // Start after '#'
581
+ }
582
+ else {
583
+ refStart = pos + 1; // Start after '#'
584
+ }
585
+ let pathEnd = refStart;
586
+ while (pathEnd < input.length) {
587
+ const ch = input[pathEnd];
588
+ if (ch === '.') {
589
+ // Dot followed by identifier - keep going until we hit non-identifier char
590
+ pathEnd++;
591
+ while (pathEnd < input.length && /[a-zA-Z0-9_]/.test(input[pathEnd])) {
592
+ pathEnd++;
593
+ }
594
+ }
595
+ else if (ch === '[') {
596
+ // Bracket - find the matching close bracket
597
+ pathEnd++;
598
+ let depth = 1;
599
+ while (pathEnd < input.length && depth > 0) {
600
+ if (input[pathEnd] === '[')
601
+ depth++;
602
+ else if (input[pathEnd] === ']')
603
+ depth--;
604
+ pathEnd++;
605
+ }
606
+ }
607
+ else {
608
+ // Hit something that's not part of the path
609
+ break;
610
+ }
611
+ }
612
+ const refStr = input.substring(pos, pathEnd);
613
+ // Resolve the target path
614
+ let targetPathStr;
615
+ try {
616
+ if (isRelative) {
617
+ // refStr is like "1#.a" - pass it directly to _decodeRelativeRef
618
+ const targetPath = _decodeRelativeRef(refStr, ctx.currentPath);
619
+ targetPathStr = _pathToPunctuated(targetPath);
620
+ }
621
+ else {
622
+ // Absolute reference: just use the path part directly (without the leading #)
623
+ targetPathStr = refStr.substring(1);
624
+ }
625
+ }
626
+ catch (e) {
627
+ throw new ParseError(`invalid reference ${refStr}: ${e instanceof Error ? e.message : String(e)}`, pos);
628
+ }
629
+ // Look up in refs map
630
+ if (!ctx.refs.has(targetPathStr)) {
631
+ throw new ParseError(`undefined reference ${refStr}`, pos);
632
+ }
633
+ return { value: ctx.refs.get(targetPathStr), position: pathEnd };
634
+ };
635
+ const parseBoolean = (input, pos, _ctx) => {
636
+ pos = consumeWhitespace(input, pos);
637
+ if (input.startsWith('true', pos)) {
638
+ return { value: true, position: pos + 4 };
639
+ }
640
+ if (input.startsWith('false', pos)) {
641
+ return { value: false, position: pos + 5 };
642
+ }
643
+ const got = pos >= input.length ? 'end of input' : `'${input[pos]}'`;
644
+ throw new ParseError(`expected boolean, got ${got}`, pos);
645
+ };
646
+ const parseNull = (input, pos, _ctx) => {
647
+ pos = consumeWhitespace(input, pos);
648
+ if (input.startsWith('null', pos) && isTokenTerminator(input, pos + 4)) {
649
+ return { value: null, position: pos + 4 };
650
+ }
651
+ const got = pos >= input.length ? 'end of input' : `'${input[pos]}'`;
652
+ throw new ParseError(`expected null, got ${got}`, pos);
653
+ };
654
+ const parseString = (input, pos, _ctx) => {
655
+ pos = consumeWhitespace(input, pos);
656
+ if (input[pos] !== '"') {
657
+ const got = pos >= input.length ? 'end of input' : `'${input[pos]}'`;
658
+ throw new ParseError(`expected '"', got ${got}`, pos);
659
+ }
660
+ pos += 1;
661
+ let rangeStart = pos;
662
+ let result = "";
663
+ // search through for escape characters, copy string out in ranges
664
+ while (pos < input.length) {
665
+ const char = input[pos];
666
+ if (char === '\\') {
667
+ result = result + input.substring(rangeStart, pos);
668
+ pos += 1;
669
+ if (pos < input.length) {
670
+ const char2 = input[pos];
671
+ if (char2 === '\\' || char2 === '\"') {
672
+ result = result + char2;
673
+ pos += 1;
674
+ rangeStart = pos;
675
+ }
676
+ else {
677
+ // TODO we need to (a) deal with escape sequences at least for unprintable characters and (b) safely handle LTR-RTL transition characters
678
+ throw new ParseError("unexpected escape sequence in string", pos);
679
+ }
680
+ }
681
+ else {
682
+ throw new ParseError("unterminated string (missing closing quote)", pos);
683
+ }
684
+ }
685
+ else if (char === '\"') {
686
+ result = result + input.substring(rangeStart, pos);
687
+ return { value: result, position: pos + 1 };
688
+ }
689
+ else {
690
+ pos += 1;
691
+ }
692
+ }
693
+ throw new ParseError("unterminated string (missing closing quote)", pos);
694
+ };
695
+ const parseDateTime = (frozen) => (input, pos, _ctx) => {
696
+ // Datetime format is YYYY-MM-DDTHH:MM:SS.sss (optional miliseconds, no quotes, no timezone or Z)
697
+ pos = consumeWhitespace(input, pos);
698
+ const datetimeRegex = /^-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?/;
699
+ const match = datetimeRegex.exec(input.slice(pos));
700
+ if (!match) {
701
+ throw new ParseError("expected DateTime in format YYYY-MM-DDTHH:MM:SS.sss", pos);
702
+ }
703
+ const datetimeStr = match[0];
704
+ const date = new Date(datetimeStr + 'Z'); // Our naive datetimes are treated as UTC
705
+ if (isNaN(date.getTime())) {
706
+ throw new ParseError(`invalid DateTime value, got ${JSON.stringify(datetimeStr)}`, pos);
707
+ }
708
+ if (frozen) {
709
+ Object.freeze(date);
710
+ }
711
+ return { value: date, position: pos + datetimeStr.length };
712
+ };
713
+ const parseBlob = (frozen) => (input, pos, _ctx) => {
714
+ pos = consumeWhitespace(input, pos);
715
+ if (pos >= input.length || input[pos] !== '0' || input[pos + 1] !== 'x') {
716
+ throw new ParseError("expected Blob starting with 0x", pos);
717
+ }
718
+ pos += 2;
719
+ let start = pos;
720
+ while (pos < input.length && /[0-9a-fA-F]/.test(input[pos])) {
721
+ pos++;
722
+ }
723
+ const hexStr = input.slice(start, pos);
724
+ // Check for odd length or invalid hex characters
725
+ if (hexStr.length % 2 !== 0) {
726
+ throw new ParseError(`invalid hex string (odd length), got "0x${hexStr}"`, start - 2);
727
+ }
728
+ // Note: The while loop above already validates that all characters are valid hex,
729
+ // so we don't need an additional check here. If there were invalid characters,
730
+ // they would be left in the input and caught by downstream parsing.
731
+ const byteLength = hexStr.length / 2;
732
+ const bytes = new Uint8Array(byteLength);
733
+ for (let i = 0; i < byteLength; i++) {
734
+ bytes[i] = parseInt(hexStr.substring(i * 2, i * 2 + 2), 16);
735
+ }
736
+ if (frozen) {
737
+ Object.freeze(bytes);
738
+ }
739
+ return { value: bytes, position: pos };
740
+ };
741
+ const createRefParser = (value_type, frozen, typeCtx) => {
742
+ let valueParser;
743
+ const ret = (input, pos, ctx) => {
744
+ pos = consumeWhitespace(input, pos);
745
+ // Create context if we don't have one (for top-level parsing with aliases)
746
+ const needsOwnContext = !ctx;
747
+ if (needsOwnContext) {
748
+ ctx = { refs: new Map(), currentPath: [] };
749
+ }
750
+ // Check for reference first
751
+ const reference = parseReference(input, pos, ctx);
752
+ if (reference) {
753
+ return reference;
754
+ }
755
+ if (pos >= input.length || input[pos] !== '&') {
756
+ throw new ParseError("expected '&' to start ref", pos);
757
+ }
758
+ pos = consumeWhitespace(input, pos + 1);
759
+ const r = ref(undefined);
760
+ // Pre-register this ref if we have context
761
+ if (ctx) {
762
+ const pathStr = _pathToPunctuated(ctx.currentPath);
763
+ ctx.refs.set(pathStr, r);
764
+ }
765
+ if (ctx)
766
+ ctx.currentPath.push(`[]`);
767
+ let value;
768
+ let position;
769
+ try {
770
+ const result = valueParser(input, pos, ctx);
771
+ value = result.value;
772
+ position = result.position;
773
+ }
774
+ catch (e) {
775
+ if (e instanceof ParseError) {
776
+ const newPath = `[]` + (e.path ? e.path : '');
777
+ throw new ParseError(e.message, e.position, newPath);
778
+ }
779
+ throw e;
780
+ }
781
+ finally {
782
+ if (ctx)
783
+ ctx.currentPath.pop();
784
+ }
785
+ r.value = value;
786
+ return { value: r, position };
787
+ };
788
+ typeCtx.push(ret);
789
+ valueParser = createParser(value_type, frozen, typeCtx);
790
+ typeCtx.pop();
791
+ return ret;
792
+ };
793
+ const createArrayParser = (value_type, frozen, typeCtx) => {
794
+ let valueParser;
795
+ const ret = (input, pos, ctx) => {
796
+ pos = consumeWhitespace(input, pos);
797
+ // Create context if we don't have one (for top-level parsing with aliases)
798
+ const needsOwnContext = !ctx;
799
+ if (needsOwnContext) {
800
+ ctx = { refs: new Map(), currentPath: [] };
801
+ }
802
+ // Check for reference first
803
+ const ref = parseReference(input, pos, ctx);
804
+ if (ref) {
805
+ return ref;
806
+ }
807
+ if (pos >= input.length || input[pos] !== '[') {
808
+ throw new ParseError("expected '[' to start array", pos);
809
+ }
810
+ pos = consumeWhitespace(input, pos + 1);
811
+ const values = [];
812
+ // Pre-register this array if we have context
813
+ if (ctx) {
814
+ const pathStr = _pathToPunctuated(ctx.currentPath);
815
+ ctx.refs.set(pathStr, values);
816
+ }
817
+ // Handle empty array
818
+ if (input[pos] === ']') {
819
+ if (frozen) {
820
+ Object.freeze(values);
821
+ }
822
+ return { value: values, position: pos + 1 };
823
+ }
824
+ let elementIndex = 0;
825
+ while (true) {
826
+ if (ctx)
827
+ ctx.currentPath.push(`[${elementIndex}]`);
828
+ try {
829
+ const { value: elementValue, position: elementPos } = valueParser(input, pos, ctx);
830
+ values.push(elementValue);
831
+ pos = consumeWhitespace(input, elementPos);
832
+ }
833
+ catch (e) {
834
+ if (e instanceof ParseError) {
835
+ const newPath = `[${elementIndex}]` + (e.path ? e.path : '');
836
+ throw new ParseError(e.message, e.position, newPath);
837
+ }
838
+ throw e;
839
+ }
840
+ finally {
841
+ if (ctx)
842
+ ctx.currentPath.pop();
843
+ }
844
+ if (input[pos] === ']') {
845
+ if (frozen) {
846
+ Object.freeze(values);
847
+ }
848
+ return { value: values, position: pos + 1 };
849
+ }
850
+ if (input[pos] !== ',') {
851
+ throw new ParseError("expected ',' or ']' after array element", pos);
852
+ }
853
+ pos = consumeWhitespace(input, pos + 1);
854
+ elementIndex++;
855
+ }
856
+ };
857
+ typeCtx.push(ret);
858
+ valueParser = createParser(value_type, frozen, typeCtx);
859
+ typeCtx.pop();
860
+ return ret;
861
+ };
862
+ const createSetParser = (key_type, frozen, typeCtx) => {
863
+ const keyParser = createParser(key_type, frozen, typeCtx);
864
+ const keyCompare = compareFor(key_type);
865
+ return (input, pos, ctx) => {
866
+ pos = consumeWhitespace(input, pos);
867
+ // Create context if we don't have one (for top-level parsing with aliases)
868
+ const needsOwnContext = !ctx;
869
+ if (needsOwnContext) {
870
+ ctx = { refs: new Map(), currentPath: [] };
871
+ }
872
+ // Check for reference first
873
+ const ref = parseReference(input, pos, ctx);
874
+ if (ref) {
875
+ return ref;
876
+ }
877
+ if (pos >= input.length || input[pos] !== '{') {
878
+ throw new ParseError("expected '{' to start set", pos);
879
+ }
880
+ pos = consumeWhitespace(input, pos + 1);
881
+ const values = new SortedSet(undefined, keyCompare);
882
+ // Pre-register this set if we have context
883
+ if (ctx) {
884
+ const pathStr = _pathToPunctuated(ctx.currentPath);
885
+ ctx.refs.set(pathStr, values);
886
+ }
887
+ // Handle empty set
888
+ if (input[pos] === '}') {
889
+ if (frozen) {
890
+ Object.freeze(values);
891
+ }
892
+ return { value: values, position: pos + 1 };
893
+ }
894
+ let elementIndex = 0;
895
+ while (true) {
896
+ try {
897
+ const keyResult = keyParser(input, pos, ctx);
898
+ values.add(keyResult.value);
899
+ pos = consumeWhitespace(input, keyResult.position);
900
+ }
901
+ catch (e) {
902
+ if (e instanceof ParseError) {
903
+ const newPath = `[${elementIndex}]` + (e.path ? e.path : '');
904
+ throw new ParseError(e.message, e.position, newPath);
905
+ }
906
+ throw e;
907
+ }
908
+ if (input[pos] === '}') {
909
+ if (frozen) {
910
+ Object.freeze(values);
911
+ }
912
+ return { value: values, position: pos + 1 };
913
+ }
914
+ if (input[pos] !== ',') {
915
+ throw new ParseError("expected ',' or '}' after set element", pos);
916
+ }
917
+ pos = consumeWhitespace(input, pos + 1);
918
+ elementIndex++;
919
+ }
920
+ };
921
+ };
922
+ const createDictParser = (key_type, value_type, frozen, typeCtx) => {
923
+ const keyParser = createParser(key_type, frozen, typeCtx);
924
+ let valueParser;
925
+ const keyCompare = compareFor(key_type);
926
+ const keyPrinter = printFor(key_type);
927
+ const ret = (input, pos, ctx) => {
928
+ pos = consumeWhitespace(input, pos);
929
+ // Create context if we don't have one (for top-level parsing with aliases)
930
+ const needsOwnContext = !ctx;
931
+ if (needsOwnContext) {
932
+ ctx = { refs: new Map(), currentPath: [] };
933
+ }
934
+ // Check for reference first
935
+ const ref = parseReference(input, pos, ctx);
936
+ if (ref) {
937
+ return ref;
938
+ }
939
+ if (pos >= input.length || input[pos] !== '{') {
940
+ throw new ParseError("expected '{' to start dict", pos);
941
+ }
942
+ pos = consumeWhitespace(input, pos + 1);
943
+ const values = new SortedMap(undefined, keyCompare);
944
+ // Pre-register this dict if we have context
945
+ if (ctx) {
946
+ const pathStr = _pathToPunctuated(ctx.currentPath);
947
+ ctx.refs.set(pathStr, values);
948
+ }
949
+ // Handle empty dict
950
+ if (input[pos] === ':') {
951
+ pos = consumeWhitespace(input, pos + 1);
952
+ if (input[pos] === '}') {
953
+ if (frozen) {
954
+ Object.freeze(values);
955
+ }
956
+ return { value: values, position: pos + 1 };
957
+ }
958
+ else {
959
+ throw new ParseError("expected '}' after ':' in empty dict", pos);
960
+ }
961
+ }
962
+ // be lenient and allow empty set syntax as well
963
+ if (input[pos] === '}') {
964
+ if (frozen) {
965
+ Object.freeze(values);
966
+ }
967
+ return { value: values, position: pos + 1 };
968
+ }
969
+ let entryIndex = 0;
970
+ while (true) {
971
+ let dictKey;
972
+ try {
973
+ const { value: keyValue, position: keyPos } = keyParser(input, pos, ctx);
974
+ dictKey = keyValue;
975
+ pos = consumeWhitespace(input, keyPos);
976
+ }
977
+ catch (e) {
978
+ if (e instanceof ParseError) {
979
+ const newPath = `[${entryIndex}](key)` + (e.path ? e.path : '');
980
+ throw new ParseError(e.message, e.position, newPath);
981
+ }
982
+ throw e;
983
+ }
984
+ if (input[pos] !== ':') {
985
+ throw new ParseError(`expected ':' after dict key at entry ${entryIndex}`, pos);
986
+ }
987
+ // Track path for dict values
988
+ if (ctx) {
989
+ const keyStr = keyPrinter(dictKey);
990
+ ctx.currentPath.push(`[${keyStr}]`);
991
+ }
992
+ try {
993
+ const { value: dictValue, position: valuePos } = valueParser(input, pos + 1, ctx);
994
+ values.set(dictKey, dictValue);
995
+ pos = consumeWhitespace(input, valuePos);
996
+ }
997
+ catch (e) {
998
+ if (e instanceof ParseError) {
999
+ const keyStr = keyPrinter(dictKey);
1000
+ const newPath = `[${keyStr}]` + (e.path ? e.path : '');
1001
+ throw new ParseError(e.message, e.position, newPath);
1002
+ }
1003
+ throw e;
1004
+ }
1005
+ finally {
1006
+ if (ctx)
1007
+ ctx.currentPath.pop();
1008
+ }
1009
+ if (input[pos] === '}') {
1010
+ if (frozen) {
1011
+ Object.freeze(values);
1012
+ }
1013
+ return { value: values, position: pos + 1 };
1014
+ }
1015
+ if (input[pos] !== ',') {
1016
+ throw new ParseError("expected ',' or '}' after dict entry", pos);
1017
+ }
1018
+ pos = consumeWhitespace(input, pos + 1);
1019
+ entryIndex++;
1020
+ }
1021
+ };
1022
+ typeCtx.push(ret);
1023
+ valueParser = createParser(value_type, frozen, typeCtx);
1024
+ typeCtx.pop();
1025
+ return ret;
1026
+ };
1027
+ const parseQuotedIdentifier = (input, pos, _ctx) => {
1028
+ pos = consumeWhitespace(input, pos);
1029
+ if (input[pos] !== '`') {
1030
+ throw new ParseError("expected identifier (opening `)", pos);
1031
+ }
1032
+ pos += 1;
1033
+ let rangeStart = pos;
1034
+ let result = "";
1035
+ // search through for escape characters, copy string out in ranges
1036
+ while (pos < input.length) {
1037
+ const char = input[pos];
1038
+ if (char === '\\') {
1039
+ result = result + input.substring(rangeStart, pos);
1040
+ pos += 1;
1041
+ if (pos < input.length) {
1042
+ const char2 = input[pos];
1043
+ if (char2 === '\\' || char2 === '`') {
1044
+ rangeStart = pos;
1045
+ pos += 1;
1046
+ }
1047
+ else {
1048
+ // TODO we need to (a) deal with escape sequences at least for unprintable characters and (b) safely handle LTR-RTL transition characters
1049
+ throw new ParseError("unexpected escape sequence in identifier", pos);
1050
+ }
1051
+ }
1052
+ else {
1053
+ throw new ParseError("unterminated identifier (missing closing `)", pos);
1054
+ }
1055
+ }
1056
+ else if (char === '`') {
1057
+ result = result + input.substring(rangeStart, pos);
1058
+ return { value: result, position: pos + 1 };
1059
+ }
1060
+ pos += 1;
1061
+ }
1062
+ throw new ParseError("unterminated identifier (missing closing `)", pos);
1063
+ };
1064
+ const parseIdentifier = (input, pos, _ctx) => {
1065
+ pos = consumeWhitespace(input, pos);
1066
+ if (input[pos] === '`') {
1067
+ return parseQuotedIdentifier(input, pos, _ctx);
1068
+ }
1069
+ if (pos < input.length && /[a-zA-Z_]/.test(input[pos])) {
1070
+ const initial_pos = pos;
1071
+ pos += 1;
1072
+ while (pos < input.length && /[a-zA-Z0-9_]/.test(input[pos])) {
1073
+ pos += 1;
1074
+ }
1075
+ // TODO possibly should check that the following character is valid (EOF, comma, bracket, etc)?
1076
+ return { value: input.substring(initial_pos, pos), position: pos };
1077
+ }
1078
+ throw new ParseError("expected identifier", pos);
1079
+ };
1080
+ const createStructParser = (fields, frozen, typeCtx) => {
1081
+ const fieldNames = fields.map(f => f.name);
1082
+ const fieldParsers = [];
1083
+ const ret = (input, pos, ctx) => {
1084
+ pos = consumeWhitespace(input, pos);
1085
+ // Create context if we don't have one (for top-level parsing with aliases)
1086
+ const needsOwnContext = !ctx;
1087
+ if (needsOwnContext) {
1088
+ ctx = { refs: new Map(), currentPath: [] };
1089
+ }
1090
+ if (pos >= input.length || input[pos] !== '(') {
1091
+ throw new ParseError("expected '(' to start struct", pos);
1092
+ }
1093
+ pos += 1;
1094
+ const result = {};
1095
+ let fieldIndex = 0;
1096
+ while (fieldIndex < fieldNames.length) {
1097
+ const fieldName = fieldNames[fieldIndex];
1098
+ pos = consumeWhitespace(input, pos);
1099
+ // Check for early closing paren (missing fields)
1100
+ if (input[pos] === ')') {
1101
+ throw new ParseError(`missing required field '${fieldName}'`, pos);
1102
+ }
1103
+ // Try to parse an identifier
1104
+ let parsedFieldName;
1105
+ let namePos;
1106
+ try {
1107
+ const identResult = parseIdentifier(input, pos, ctx);
1108
+ parsedFieldName = identResult.value;
1109
+ namePos = identResult.position;
1110
+ }
1111
+ catch (e) {
1112
+ if (e instanceof ParseError) {
1113
+ // If we can't parse an identifier, the field is missing
1114
+ throw new ParseError(`missing required field '${fieldName}'`, pos);
1115
+ }
1116
+ throw e;
1117
+ }
1118
+ // Check if the parsed field name matches what we expect
1119
+ if (parsedFieldName !== fieldName) {
1120
+ throw new ParseError(`unknown field '${parsedFieldName}', expected one of: ${fieldNames.join(', ')}`, pos);
1121
+ }
1122
+ pos = namePos;
1123
+ pos = consumeWhitespace(input, pos);
1124
+ if (pos >= input.length || input[pos] !== '=') {
1125
+ throw new ParseError(`expected '=' after field name '${fieldName}'`, pos);
1126
+ }
1127
+ pos += 1;
1128
+ const parser = fieldParsers[fieldIndex];
1129
+ // Track path for struct fields
1130
+ const fieldPath = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(fieldName) ? `.${fieldName}` : `[${JSON.stringify(fieldName)}]`;
1131
+ if (ctx)
1132
+ ctx.currentPath.push(fieldPath);
1133
+ try {
1134
+ const { value: fieldValue, position: valuePos } = parser(input, pos, ctx);
1135
+ result[fieldName] = fieldValue;
1136
+ pos = valuePos;
1137
+ }
1138
+ catch (e) {
1139
+ if (e instanceof ParseError) {
1140
+ const newPath = fieldPath + (e.path ? e.path : '');
1141
+ throw new ParseError(e.message, e.position, newPath);
1142
+ }
1143
+ throw e;
1144
+ }
1145
+ finally {
1146
+ if (ctx)
1147
+ ctx.currentPath.pop();
1148
+ }
1149
+ // Look for comma or closing paren
1150
+ pos = consumeWhitespace(input, pos);
1151
+ if (pos >= input.length) {
1152
+ throw new ParseError("unexpected end of input in struct", pos);
1153
+ }
1154
+ if (input[pos] === ',') {
1155
+ pos = consumeWhitespace(input, pos + 1);
1156
+ fieldIndex++;
1157
+ }
1158
+ else if (input[pos] === ')') {
1159
+ // Check if we've parsed all fields
1160
+ if (fieldIndex !== fieldNames.length - 1) {
1161
+ throw new ParseError(`missing required field '${fieldNames[fieldIndex + 1]}'`, pos);
1162
+ }
1163
+ // All fields parsed, break and handle closing paren after loop
1164
+ break;
1165
+ }
1166
+ else {
1167
+ throw new ParseError("expected ',' or ')' after struct field", pos);
1168
+ }
1169
+ }
1170
+ // After loop, we should be at ')'
1171
+ pos = consumeWhitespace(input, pos);
1172
+ if (pos >= input.length || input[pos] !== ')') {
1173
+ // We've parsed all fields but there's more content - must be an unknown field
1174
+ if (pos < input.length) {
1175
+ try {
1176
+ const { value: extraField } = parseIdentifier(input, pos, ctx);
1177
+ throw new ParseError(`unknown field '${extraField}', expected one of: ${fieldNames.join(', ')}`, pos);
1178
+ }
1179
+ catch (e) {
1180
+ if (e instanceof ParseError && e.message.startsWith("Unknown field")) {
1181
+ throw e;
1182
+ }
1183
+ // Couldn't parse identifier, just say we expected ')'
1184
+ throw new ParseError("expected ')' to close struct", pos);
1185
+ }
1186
+ }
1187
+ throw new ParseError("expected ')' to close struct", pos);
1188
+ }
1189
+ if (frozen) {
1190
+ Object.freeze(result);
1191
+ }
1192
+ return { value: result, position: pos + 1 };
1193
+ };
1194
+ typeCtx.push(ret);
1195
+ for (const field of fields) {
1196
+ fieldParsers.push(createParser(field.type, frozen, typeCtx));
1197
+ }
1198
+ typeCtx.pop();
1199
+ return ret;
1200
+ };
1201
+ const createVariantParser = (cases, frozen, typeCtx) => {
1202
+ const caseParsers = {};
1203
+ const caseNames = cases.map(c => c.name);
1204
+ const ret = (input, pos, ctx) => {
1205
+ pos = consumeWhitespace(input, pos);
1206
+ // Create context if we don't have one (for top-level parsing with aliases)
1207
+ const needsOwnContext = !ctx;
1208
+ if (needsOwnContext) {
1209
+ ctx = { refs: new Map(), currentPath: [] };
1210
+ }
1211
+ if (pos >= input.length || input[pos] !== '.') {
1212
+ throw new ParseError("expected '.' to start variant case", pos);
1213
+ }
1214
+ pos += 1;
1215
+ // We cannot have whitespace
1216
+ const pos2 = consumeWhitespace(input, pos);
1217
+ if (pos2 !== pos) {
1218
+ throw new ParseError("whitespace not allowed between '.' and case identifier", pos);
1219
+ }
1220
+ const { value: caseName, position: casePos } = parseIdentifier(input, pos, ctx);
1221
+ const valueParser = caseParsers[caseName];
1222
+ if (valueParser === undefined) {
1223
+ throw new ParseError(`unknown variant case .${printIdentifier(caseName)}, expected one of: ${caseNames.map(c => `.${printIdentifier(c)}`).join(', ')}`, pos);
1224
+ }
1225
+ // Create variant with undefined placeholder
1226
+ const v = variant(caseName, undefined);
1227
+ // Parse the value with case name in path
1228
+ if (ctx)
1229
+ ctx.currentPath.push(`.${caseName}`);
1230
+ try {
1231
+ const { value: caseValue, position: valuePos } = valueParser(input, casePos, ctx);
1232
+ // Mutate the variant's value field
1233
+ v.value = caseValue;
1234
+ if (frozen) {
1235
+ Object.freeze(v);
1236
+ }
1237
+ return { value: v, position: valuePos };
1238
+ }
1239
+ catch (e) {
1240
+ if (e instanceof ParseError) {
1241
+ const newPath = `.${caseName}` + (e.path ? e.path : '');
1242
+ throw new ParseError(e.message, e.position, newPath);
1243
+ }
1244
+ throw e;
1245
+ }
1246
+ finally {
1247
+ if (ctx)
1248
+ ctx.currentPath.pop();
1249
+ }
1250
+ };
1251
+ typeCtx.push(ret);
1252
+ for (const { name, type: caseType } of cases) {
1253
+ if (caseType.type === 'Null') {
1254
+ // Special case for null type - the "null" is actually optional
1255
+ caseParsers[name] = (input, pos, _ctx) => {
1256
+ pos = consumeWhitespace(input, pos);
1257
+ if (input.startsWith('null', pos) && isTokenTerminator(input, pos + 4)) {
1258
+ return { value: null, position: pos + 4 };
1259
+ }
1260
+ else if (isTokenTerminator(input, pos)) {
1261
+ // No "null" present, but we're at a valid terminator - that's ok for optional null
1262
+ return { value: null, position: pos };
1263
+ }
1264
+ else {
1265
+ const got = pos >= input.length ? 'end of input' : `'${input[pos]}'`;
1266
+ throw new ParseError(`expected null, got ${got}`, pos);
1267
+ }
1268
+ };
1269
+ }
1270
+ else {
1271
+ caseParsers[name] = createParser(caseType, frozen, typeCtx);
1272
+ }
1273
+ }
1274
+ typeCtx.pop();
1275
+ return ret;
1276
+ };
1277
+ /**
1278
+ * Parse an East value and infer its type from the syntax.
1279
+ *
1280
+ * @param input - The string to parse
1281
+ * @param frozen - Whether to freeze mutable containers (default: false)
1282
+ * @returns A tuple of [inferred type, parsed value]
1283
+ * @throws {Error} When parsing fails or type conflicts are detected
1284
+ *
1285
+ * @remarks
1286
+ * Type inference rules:
1287
+ * - Primitive types are distinguished by syntax (floats have `.` or `e`, integers don't)
1288
+ * - Empty array `[]` has type `Array<Never>`
1289
+ * - Empty set `{}` has type `Set<Never>`
1290
+ * - Empty dict `{:}` has type `Dict<Never, Never>`
1291
+ * - Non-empty collections widen element/key/value types via `TypeWiden`
1292
+ * - Each variant literal `.case data` creates a single-case variant, multiple cases widen
1293
+ * - Structs infer each field independently
1294
+ * - Recursive types are never inferred (circular references cause errors)
1295
+ * - Functions cannot be parsed
1296
+ *
1297
+ * @example
1298
+ * ```typescript
1299
+ * // Integer
1300
+ * const [type1, value1] = parseInferred("42");
1301
+ * // type1 = IntegerType, value1 = 42n
1302
+ *
1303
+ * // Float
1304
+ * const [type2, value2] = parseInferred("3.14");
1305
+ * // type2 = FloatType, value2 = 3.14
1306
+ *
1307
+ * // Array with widening
1308
+ * const [type3, value3] = parseInferred("[1, 2, 3]");
1309
+ * // type3 = ArrayType(IntegerType), value3 = [1n, 2n, 3n]
1310
+ *
1311
+ * // Variant with widening
1312
+ * const [type4, value4] = parseInferred(".some 42");
1313
+ * // type4 = VariantType({ some: IntegerType }), value4 = variant("some", 42n)
1314
+ *
1315
+ * // Type mismatch error
1316
+ * parseInferred("[1, 2.5]"); // Error: cannot widen Integer to Float
1317
+ * ```
1318
+ */
1319
+ export function parseInferred(input, frozen = false) {
1320
+ try {
1321
+ const result = parseInferredValue(input, 0, frozen);
1322
+ const pos = consumeWhitespace(input, result.position);
1323
+ if (pos < input.length) {
1324
+ const { line, column } = getLineAndColumn(input, pos);
1325
+ throw new Error(`Unexpected input after parsed value at line ${line}, col ${column}`);
1326
+ }
1327
+ return [result.type, result.value];
1328
+ }
1329
+ catch (e) {
1330
+ if (e instanceof ParseError) {
1331
+ const { line, column } = getLineAndColumn(input, e.position);
1332
+ const pathStr = e.path ? ` at ${e.path}` : '';
1333
+ throw new Error(`${e.message}${pathStr} (line ${line}, col ${column})`);
1334
+ }
1335
+ throw e;
1336
+ }
1337
+ }
1338
+ /**
1339
+ * Parse a value and infer its type.
1340
+ */
1341
+ function parseInferredValue(input, startPos, frozen) {
1342
+ const pos = consumeWhitespace(input, startPos);
1343
+ if (pos >= input.length) {
1344
+ throw new ParseError("unexpected end of input", pos);
1345
+ }
1346
+ const ch = input[pos];
1347
+ // Check for circular references - not supported in inference
1348
+ if (ch === '#' || (ch >= '0' && ch <= '9' && input.indexOf('#', pos) !== -1 && input.indexOf('#', pos) < pos + 10)) {
1349
+ // Rough heuristic: if we see a digit followed by # nearby, it might be a reference
1350
+ const nextHash = input.indexOf('#', pos);
1351
+ if (nextHash !== -1 && nextHash <= pos + 10) {
1352
+ throw new ParseError("circular references not supported in type inference", pos);
1353
+ }
1354
+ }
1355
+ // Dispatch based on first character
1356
+ if (ch === '[') {
1357
+ return parseInferredArray(input, pos, frozen);
1358
+ }
1359
+ else if (ch === '{') {
1360
+ return parseInferredSetOrDict(input, pos, frozen);
1361
+ }
1362
+ else if (ch === '(') {
1363
+ return parseInferredStruct(input, pos, frozen);
1364
+ }
1365
+ else if (ch === '.') {
1366
+ return parseInferredVariant(input, pos, frozen);
1367
+ }
1368
+ else if (ch === '"') {
1369
+ const { value, position } = parseString(input, pos);
1370
+ return { type: StringType, value, position };
1371
+ }
1372
+ else if (ch === '&') {
1373
+ throw new ParseError("ref types not supported in type inference", pos);
1374
+ }
1375
+ else if (input.startsWith('null', pos) && isTokenTerminator(input, pos + 4)) {
1376
+ return { type: NullType, value: null, position: pos + 4 };
1377
+ }
1378
+ else if (input.startsWith('true', pos) && isTokenTerminator(input, pos + 4)) {
1379
+ return { type: BooleanType, value: true, position: pos + 4 };
1380
+ }
1381
+ else if (input.startsWith('false', pos) && isTokenTerminator(input, pos + 5)) {
1382
+ return { type: BooleanType, value: false, position: pos + 5 };
1383
+ }
1384
+ else if (input.startsWith('0x', pos)) {
1385
+ const { value, position } = parseBlob(frozen)(input, pos);
1386
+ return { type: BlobType, value, position };
1387
+ }
1388
+ else if (input.startsWith('NaN', pos)) {
1389
+ return { type: FloatType, value: NaN, position: pos + 3 };
1390
+ }
1391
+ else if (input.startsWith('Infinity', pos)) {
1392
+ return { type: FloatType, value: Infinity, position: pos + 8 };
1393
+ }
1394
+ else if (ch === '-' && input.startsWith('-Infinity', pos)) {
1395
+ return { type: FloatType, value: -Infinity, position: pos + 9 };
1396
+ }
1397
+ else if (ch === '-' || (ch >= '0' && ch <= '9')) {
1398
+ // Check for datetime before trying number
1399
+ const datetimeMatch = /^-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?/.exec(input.slice(pos));
1400
+ if (datetimeMatch) {
1401
+ const { value, position } = parseDateTime(frozen)(input, pos);
1402
+ return { type: DateTimeType, value, position };
1403
+ }
1404
+ return parseInferredNumber(input, pos);
1405
+ }
1406
+ else {
1407
+ throw new ParseError(`unexpected character '${ch}'`, pos);
1408
+ }
1409
+ }
1410
+ /**
1411
+ * Parse a number and infer whether it's an integer or float.
1412
+ * Floats must contain '.' or 'e'/'E', otherwise it's an integer.
1413
+ */
1414
+ function parseInferredNumber(input, pos) {
1415
+ // Try to parse as float first (lookahead for . or e)
1416
+ let hasDecimal = false;
1417
+ let hasExponent = false;
1418
+ let scanPos = pos;
1419
+ if (input[scanPos] === '-')
1420
+ scanPos++;
1421
+ while (scanPos < input.length && input[scanPos] >= '0' && input[scanPos] <= '9') {
1422
+ scanPos++;
1423
+ }
1424
+ if (scanPos < input.length && input[scanPos] === '.') {
1425
+ hasDecimal = true;
1426
+ }
1427
+ if (scanPos < input.length && (input[scanPos] === 'e' || input[scanPos] === 'E')) {
1428
+ hasExponent = true;
1429
+ }
1430
+ if (hasDecimal || hasExponent) {
1431
+ // It's a float
1432
+ const { value, position } = parseFloat(input, pos);
1433
+ return { type: FloatType, value, position };
1434
+ }
1435
+ else {
1436
+ // It's an integer
1437
+ const { value, position } = parseInteger(input, pos);
1438
+ return { type: IntegerType, value, position };
1439
+ }
1440
+ }
1441
+ /**
1442
+ * Parse an array and infer the element type by widening.
1443
+ */
1444
+ function parseInferredArray(input, startPos, frozen) {
1445
+ let pos = consumeWhitespace(input, startPos);
1446
+ if (input[pos] !== '[') {
1447
+ throw new ParseError("expected '[' to start array", pos);
1448
+ }
1449
+ pos = consumeWhitespace(input, pos + 1);
1450
+ const values = [];
1451
+ let elementType = NeverType;
1452
+ // Handle empty array
1453
+ if (input[pos] === ']') {
1454
+ const arr = frozen ? Object.freeze(values) : values;
1455
+ return { type: ArrayType(NeverType), value: arr, position: pos + 1 };
1456
+ }
1457
+ let elementIndex = 0;
1458
+ while (true) {
1459
+ try {
1460
+ const element = parseInferredValue(input, pos, frozen);
1461
+ values.push(element.value);
1462
+ // Widen element type
1463
+ try {
1464
+ elementType = TypeWiden(elementType, element.type);
1465
+ }
1466
+ catch (cause) {
1467
+ if (cause instanceof Error && cause.name === "TypeMismatchError") {
1468
+ throw new ParseError(`incompatible array element types: ${cause.message}`, pos, `[${elementIndex}]`);
1469
+ }
1470
+ throw cause;
1471
+ }
1472
+ pos = consumeWhitespace(input, element.position);
1473
+ }
1474
+ catch (e) {
1475
+ if (e instanceof ParseError) {
1476
+ const newPath = `[${elementIndex}]` + (e.path ? e.path : '');
1477
+ throw new ParseError(e.message, e.position, newPath);
1478
+ }
1479
+ throw e;
1480
+ }
1481
+ if (input[pos] === ']') {
1482
+ const arr = frozen ? Object.freeze(values) : values;
1483
+ return { type: ArrayType(elementType), value: arr, position: pos + 1 };
1484
+ }
1485
+ if (input[pos] !== ',') {
1486
+ throw new ParseError("expected ',' or ']' after array element", pos);
1487
+ }
1488
+ pos = consumeWhitespace(input, pos + 1);
1489
+ elementIndex++;
1490
+ }
1491
+ }
1492
+ /**
1493
+ * Parse either a set or dict and infer types.
1494
+ * Distinguishes based on presence of ':' (dict) vs no ':' (set).
1495
+ */
1496
+ function parseInferredSetOrDict(input, startPos, frozen) {
1497
+ let pos = consumeWhitespace(input, startPos);
1498
+ if (input[pos] !== '{') {
1499
+ throw new ParseError("expected '{' to start set or dict", pos);
1500
+ }
1501
+ pos = consumeWhitespace(input, pos + 1);
1502
+ // Check for empty dict syntax {:}
1503
+ if (input[pos] === ':') {
1504
+ pos = consumeWhitespace(input, pos + 1);
1505
+ if (input[pos] === '}') {
1506
+ const dict = new SortedMap(undefined, compareFor(NeverType));
1507
+ if (frozen)
1508
+ Object.freeze(dict);
1509
+ return { type: DictType(NeverType, NeverType), value: dict, position: pos + 1 };
1510
+ }
1511
+ else {
1512
+ throw new ParseError("expected '}' after ':' in empty dict", pos);
1513
+ }
1514
+ }
1515
+ // Check for empty set syntax {}
1516
+ if (input[pos] === '}') {
1517
+ const set = new SortedSet(undefined, compareFor(NeverType));
1518
+ if (frozen)
1519
+ Object.freeze(set);
1520
+ return { type: SetType(NeverType), value: set, position: pos + 1 };
1521
+ }
1522
+ // Parse first element to determine if set or dict
1523
+ const firstElementPos = pos;
1524
+ const firstElement = parseInferredValue(input, pos, frozen);
1525
+ pos = consumeWhitespace(input, firstElement.position);
1526
+ if (input[pos] === ':') {
1527
+ // It's a dict
1528
+ return parseInferredDictRest(input, startPos, frozen, firstElement, firstElementPos);
1529
+ }
1530
+ else {
1531
+ // It's a set
1532
+ return parseInferredSetRest(input, startPos, frozen, firstElement, firstElementPos);
1533
+ }
1534
+ }
1535
+ /**
1536
+ * Continue parsing a set after determining it's a set.
1537
+ */
1538
+ function parseInferredSetRest(input, startPos, frozen, firstElement, firstElementPos) {
1539
+ let keyType = firstElement.type;
1540
+ const compare = compareFor(keyType);
1541
+ const values = new SortedSet(undefined, compare);
1542
+ try {
1543
+ values.add(firstElement.value);
1544
+ }
1545
+ catch (e) {
1546
+ throw new ParseError(`invalid set key: ${e instanceof Error ? e.message : String(e)}`, firstElementPos);
1547
+ }
1548
+ let pos = consumeWhitespace(input, firstElement.position);
1549
+ let elementIndex = 1;
1550
+ while (true) {
1551
+ if (input[pos] === '}') {
1552
+ if (frozen)
1553
+ Object.freeze(values);
1554
+ return { type: SetType(keyType), value: values, position: pos + 1 };
1555
+ }
1556
+ if (input[pos] !== ',') {
1557
+ throw new ParseError("expected ',' or '}' after set element", pos);
1558
+ }
1559
+ pos = consumeWhitespace(input, pos + 1);
1560
+ try {
1561
+ const element = parseInferredValue(input, pos, frozen);
1562
+ // Widen key type
1563
+ try {
1564
+ const newKeyType = TypeWiden(keyType, element.type);
1565
+ if (!isTypeEqual(keyType, newKeyType)) {
1566
+ // Type changed - need to rebuild set with new comparator
1567
+ const newCompare = compareFor(newKeyType);
1568
+ const newValues = new SortedSet(values, newCompare);
1569
+ values.clear();
1570
+ for (const v of newValues) {
1571
+ values.add(v);
1572
+ }
1573
+ keyType = newKeyType;
1574
+ }
1575
+ }
1576
+ catch (cause) {
1577
+ if (cause instanceof Error && cause.name === "TypeMismatchError") {
1578
+ throw new ParseError(`incompatible set element types: ${cause.message}`, pos, `[${elementIndex}]`);
1579
+ }
1580
+ throw cause;
1581
+ }
1582
+ values.add(element.value);
1583
+ pos = consumeWhitespace(input, element.position);
1584
+ }
1585
+ catch (e) {
1586
+ if (e instanceof ParseError) {
1587
+ const newPath = `[${elementIndex}]` + (e.path ? e.path : '');
1588
+ throw new ParseError(e.message, e.position, newPath);
1589
+ }
1590
+ throw e;
1591
+ }
1592
+ elementIndex++;
1593
+ }
1594
+ }
1595
+ /**
1596
+ * Continue parsing a dict after determining it's a dict.
1597
+ */
1598
+ function parseInferredDictRest(input, _startPos, frozen, firstKey, _firstKeyPos) {
1599
+ let keyType = firstKey.type;
1600
+ let valueType = NeverType;
1601
+ const compare = compareFor(keyType);
1602
+ const values = new SortedMap(undefined, compare);
1603
+ let pos = consumeWhitespace(input, firstKey.position);
1604
+ if (input[pos] !== ':') {
1605
+ throw new ParseError("expected ':' after dict key", pos);
1606
+ }
1607
+ pos = consumeWhitespace(input, pos + 1);
1608
+ try {
1609
+ const firstValue = parseInferredValue(input, pos, frozen);
1610
+ valueType = firstValue.type;
1611
+ values.set(firstKey.value, firstValue.value);
1612
+ pos = consumeWhitespace(input, firstValue.position);
1613
+ }
1614
+ catch (e) {
1615
+ if (e instanceof ParseError) {
1616
+ const keyPrinter = printFor(keyType);
1617
+ const keyStr = keyPrinter(firstKey.value);
1618
+ const newPath = `[${keyStr}]` + (e.path ? e.path : '');
1619
+ throw new ParseError(e.message, e.position, newPath);
1620
+ }
1621
+ throw e;
1622
+ }
1623
+ let entryIndex = 1;
1624
+ while (true) {
1625
+ if (input[pos] === '}') {
1626
+ if (frozen)
1627
+ Object.freeze(values);
1628
+ return { type: DictType(keyType, valueType), value: values, position: pos + 1 };
1629
+ }
1630
+ if (input[pos] !== ',') {
1631
+ throw new ParseError("expected ',' or '}' after dict entry", pos);
1632
+ }
1633
+ pos = consumeWhitespace(input, pos + 1);
1634
+ let dictKey;
1635
+ let dictKeyType;
1636
+ let keyPos;
1637
+ try {
1638
+ const keyElement = parseInferredValue(input, pos, frozen);
1639
+ dictKey = keyElement.value;
1640
+ dictKeyType = keyElement.type;
1641
+ keyPos = pos;
1642
+ pos = consumeWhitespace(input, keyElement.position);
1643
+ }
1644
+ catch (e) {
1645
+ if (e instanceof ParseError) {
1646
+ const newPath = `[${entryIndex}](key)` + (e.path ? e.path : '');
1647
+ throw new ParseError(e.message, e.position, newPath);
1648
+ }
1649
+ throw e;
1650
+ }
1651
+ if (input[pos] !== ':') {
1652
+ throw new ParseError("expected ':' after dict key", pos);
1653
+ }
1654
+ pos = consumeWhitespace(input, pos + 1);
1655
+ try {
1656
+ const valueElement = parseInferredValue(input, pos, frozen);
1657
+ // Widen key and value types
1658
+ try {
1659
+ const newKeyType = TypeWiden(keyType, dictKeyType);
1660
+ const newValueType = TypeWiden(valueType, valueElement.type);
1661
+ if (!isTypeEqual(keyType, newKeyType)) {
1662
+ // Key type changed - need to rebuild dict with new comparator
1663
+ const newCompare = compareFor(newKeyType);
1664
+ const newValues = new SortedMap(values, newCompare);
1665
+ values.clear();
1666
+ for (const [k, v] of newValues) {
1667
+ values.set(k, v);
1668
+ }
1669
+ keyType = newKeyType;
1670
+ }
1671
+ valueType = newValueType;
1672
+ }
1673
+ catch (cause) {
1674
+ if (cause instanceof Error && cause.name === "TypeMismatchError") {
1675
+ throw new ParseError(`incompatible dict types: ${cause.message}`, keyPos, `[${entryIndex}]`);
1676
+ }
1677
+ throw cause;
1678
+ }
1679
+ values.set(dictKey, valueElement.value);
1680
+ pos = consumeWhitespace(input, valueElement.position);
1681
+ }
1682
+ catch (e) {
1683
+ if (e instanceof ParseError) {
1684
+ const keyPrinter = printFor(keyType);
1685
+ const keyStr = keyPrinter(dictKey);
1686
+ const newPath = `[${keyStr}]` + (e.path ? e.path : '');
1687
+ throw new ParseError(e.message, e.position, newPath);
1688
+ }
1689
+ throw e;
1690
+ }
1691
+ entryIndex++;
1692
+ }
1693
+ }
1694
+ /**
1695
+ * Parse a struct and infer field types.
1696
+ */
1697
+ function parseInferredStruct(input, startPos, frozen) {
1698
+ let pos = consumeWhitespace(input, startPos);
1699
+ if (input[pos] !== '(') {
1700
+ throw new ParseError("expected '(' to start struct", pos);
1701
+ }
1702
+ pos = consumeWhitespace(input, pos + 1);
1703
+ const fields = {};
1704
+ const values = {};
1705
+ const fieldOrder = [];
1706
+ // Handle empty struct
1707
+ if (input[pos] === ')') {
1708
+ const struct = frozen ? Object.freeze(values) : values;
1709
+ return { type: StructType({}), value: struct, position: pos + 1 };
1710
+ }
1711
+ while (true) {
1712
+ // Parse field name
1713
+ const { value: fieldName, position: namePos } = parseIdentifier(input, pos);
1714
+ pos = consumeWhitespace(input, namePos);
1715
+ if (input[pos] !== '=') {
1716
+ throw new ParseError(`expected '=' after field name '${fieldName}'`, pos);
1717
+ }
1718
+ pos = consumeWhitespace(input, pos + 1);
1719
+ // Parse field value and infer type
1720
+ const fieldPath = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(fieldName) ? `.${fieldName}` : `[${JSON.stringify(fieldName)}]`;
1721
+ try {
1722
+ const fieldValue = parseInferredValue(input, pos, frozen);
1723
+ fields[fieldName] = fieldValue.type;
1724
+ values[fieldName] = fieldValue.value;
1725
+ fieldOrder.push(fieldName);
1726
+ pos = consumeWhitespace(input, fieldValue.position);
1727
+ }
1728
+ catch (e) {
1729
+ if (e instanceof ParseError) {
1730
+ const newPath = fieldPath + (e.path ? e.path : '');
1731
+ throw new ParseError(e.message, e.position, newPath);
1732
+ }
1733
+ throw e;
1734
+ }
1735
+ if (input[pos] === ')') {
1736
+ // Reorder fields to match insertion order
1737
+ const orderedFields = {};
1738
+ for (const name of fieldOrder) {
1739
+ orderedFields[name] = fields[name];
1740
+ }
1741
+ const struct = frozen ? Object.freeze(values) : values;
1742
+ return { type: StructType(orderedFields), value: struct, position: pos + 1 };
1743
+ }
1744
+ if (input[pos] !== ',') {
1745
+ throw new ParseError("expected ',' or ')' after struct field", pos);
1746
+ }
1747
+ pos = consumeWhitespace(input, pos + 1);
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Parse a variant and infer the case type.
1752
+ */
1753
+ function parseInferredVariant(input, startPos, frozen) {
1754
+ let pos = consumeWhitespace(input, startPos);
1755
+ if (input[pos] !== '.') {
1756
+ throw new ParseError("expected '.' to start variant case", pos);
1757
+ }
1758
+ pos += 1;
1759
+ // Check for whitespace after '.'
1760
+ const pos2 = consumeWhitespace(input, pos);
1761
+ if (pos2 !== pos) {
1762
+ throw new ParseError("whitespace not allowed between '.' and case identifier", pos);
1763
+ }
1764
+ // Parse case name
1765
+ const { value: caseName, position: casePos } = parseIdentifier(input, pos);
1766
+ pos = consumeWhitespace(input, casePos);
1767
+ // Check if there's data following
1768
+ let caseType;
1769
+ let caseValue;
1770
+ let finalPos;
1771
+ if (isTokenTerminator(input, pos)) {
1772
+ // No data - this is a null variant
1773
+ caseType = NullType;
1774
+ caseValue = null;
1775
+ finalPos = pos;
1776
+ }
1777
+ else {
1778
+ // Parse the associated data
1779
+ try {
1780
+ const dataResult = parseInferredValue(input, pos, frozen);
1781
+ caseType = dataResult.type;
1782
+ caseValue = dataResult.value;
1783
+ finalPos = dataResult.position;
1784
+ }
1785
+ catch (e) {
1786
+ if (e instanceof ParseError) {
1787
+ const newPath = `.${caseName}` + (e.path ? e.path : '');
1788
+ throw new ParseError(e.message, e.position, newPath);
1789
+ }
1790
+ throw e;
1791
+ }
1792
+ }
1793
+ const v = variant(caseName, caseValue);
1794
+ if (frozen)
1795
+ Object.freeze(v);
1796
+ return {
1797
+ type: VariantType({ [caseName]: caseType }),
1798
+ value: v,
1799
+ position: finalPos
1800
+ };
1801
+ }
1802
+ //# sourceMappingURL=east.js.map