@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,1492 @@
1
+ import { get_location } from "../location.js";
2
+ import { DictType, BooleanType, FunctionType, IntegerType, NullType, OptionType, SetType, NeverType, VariantType, printType, FloatType, isTypeEqual, ArrayType } from "../types.js";
3
+ import { valueOrExprToAst, valueOrExprToAstTyped } from "./ast.js";
4
+ import { AstSymbol, Expr, FactorySymbol, TypeSymbol } from "./expr.js";
5
+ import { none, some } from "../containers/variant.js";
6
+ /**
7
+ * Expression representing dictionary (key-value map) values and operations.
8
+ *
9
+ * DictExpr provides methods for working with sorted dictionaries (maps) including lookup,
10
+ * insertion, deletion, iteration, mapping, filtering, grouping, and aggregation operations.
11
+ * Dictionaries maintain their entries sorted by key using East's total ordering.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Creating and manipulating dictionaries
16
+ * const updateCounts = East.function([DictType(StringType, IntegerType), StringType], DictType(StringType, IntegerType), ($, counts, word) => {
17
+ * // Increment count for a word, initializing to 0 if missing
18
+ * $.return(counts.merge(word, 1n, ($, existing, increment) => existing.add(increment), () => 0n));
19
+ * });
20
+ * const compiled = East.compile(updateCounts.toIR(), []);
21
+ * const counts = new Map([["hello", 5n], ["world", 3n]]);
22
+ * compiled(counts, "hello"); // Map([["hello", 6n], ["world", 3n]])
23
+ * compiled(counts, "new"); // Map([["hello", 6n], ["new", 1n], ["world", 3n]])
24
+ * ```
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // Filtering and mapping
29
+ * const filterHighScores = East.function([DictType(StringType, IntegerType)], DictType(StringType, IntegerType), ($, scores) => {
30
+ * $.return(scores.filter(($, score, name) => score.greaterOrEqual(100n)));
31
+ * });
32
+ * const compiled = East.compile(filterHighScores.toIR(), []);
33
+ * const scores = new Map([["alice", 150n], ["bob", 75n], ["charlie", 200n]]);
34
+ * compiled(scores); // Map([["alice", 150n], ["charlie", 200n]])
35
+ * ```
36
+ */
37
+ export class DictExpr extends Expr {
38
+ key_type;
39
+ value_type;
40
+ constructor(key_type, value_type, ast, createExpr) {
41
+ super(ast.type, ast, createExpr);
42
+ this.key_type = key_type;
43
+ this.value_type = value_type;
44
+ }
45
+ /**
46
+ * Returns the number of key-value pairs in the dictionary.
47
+ *
48
+ * @returns An IntegerExpr representing the count of entries
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const getSize = East.function([DictType(StringType, IntegerType)], IntegerType, ($, dict) => {
53
+ * $.return(dict.size());
54
+ * });
55
+ * const compiled = East.compile(getSize.toIR(), []);
56
+ * compiled(new Map([["a", 1n], ["b", 2n], ["c", 3n]])); // 3n
57
+ * compiled(new Map()); // 0n
58
+ * ```
59
+ */
60
+ size() {
61
+ return this[FactorySymbol]({
62
+ ast_type: "Builtin",
63
+ type: IntegerType,
64
+ location: get_location(2),
65
+ builtin: "DictSize",
66
+ type_parameters: [this.key_type, this.value_type],
67
+ arguments: [this[AstSymbol]],
68
+ });
69
+ }
70
+ /**
71
+ * Checks if a key exists in the dictionary.
72
+ *
73
+ * @param key - The key to search for
74
+ * @returns A BooleanExpr that is true if the key exists, false otherwise
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const hasKey = East.function([DictType(StringType, IntegerType), StringType], BooleanType, ($, dict, key) => {
79
+ * $.return(dict.has(key));
80
+ * });
81
+ * const compiled = East.compile(hasKey.toIR(), []);
82
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
83
+ * compiled(dict, "a"); // true
84
+ * compiled(dict, "c"); // false
85
+ * ```
86
+ */
87
+ has(key) {
88
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
89
+ return this[FactorySymbol]({
90
+ ast_type: "Builtin",
91
+ type: BooleanType,
92
+ location: get_location(2),
93
+ builtin: "DictHas",
94
+ type_parameters: [this.key_type, this.value_type],
95
+ arguments: [this[AstSymbol], keyAst],
96
+ });
97
+ }
98
+ /**
99
+ * Gets the value associated with a key in the dictionary.
100
+ *
101
+ * @param key - The key to look up
102
+ * @param onMissing - Optional function to call if key is not found; if omitted, an error is thrown on missing key
103
+ * @returns An expression of the value type
104
+ *
105
+ * @throws East runtime error if key is not found and onMissing is not provided
106
+ *
107
+ * @see {@link tryGet} for a safe lookup that returns an Option type
108
+ * @see {@link getOrInsert} to insert a default value if the key is missing
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const getValue = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, dict, key) => {
113
+ * $.return(dict.get(key));
114
+ * });
115
+ * const compiled = East.compile(getValue.toIR(), []);
116
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
117
+ * compiled(dict, "a"); // 1n
118
+ * // compiled(dict, "c") would throw error
119
+ * ```
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * // With missing handler
124
+ * const getWithDefault = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, dict, key) => {
125
+ * $.return(dict.get(key, () => -1n));
126
+ * });
127
+ * const compiled = East.compile(getWithDefault.toIR(), []);
128
+ * compiled(dict, "a"); // 1n
129
+ * compiled(dict, "c"); // -1n
130
+ * ```
131
+ */
132
+ get(key, onMissing) {
133
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
134
+ if (onMissing === undefined) {
135
+ return this[FactorySymbol]({
136
+ ast_type: "Builtin",
137
+ type: this.value_type,
138
+ location: get_location(2),
139
+ builtin: "DictGet",
140
+ type_parameters: [this.key_type, this.value_type],
141
+ arguments: [this[AstSymbol], keyAst],
142
+ });
143
+ }
144
+ else {
145
+ const onMissingExpr = Expr.from(onMissing, FunctionType([this.key_type], this.value_type, null));
146
+ const onMissingAst = Expr.ast(onMissingExpr);
147
+ return this[FactorySymbol]({
148
+ ast_type: "Builtin",
149
+ type: this.value_type,
150
+ location: get_location(2),
151
+ builtin: "DictGetOrDefault",
152
+ type_parameters: [this.key_type, this.value_type],
153
+ arguments: [this[AstSymbol], keyAst, onMissingAst],
154
+ });
155
+ }
156
+ }
157
+ /**
158
+ * Safely gets a value from the dictionary, returning an Option type.
159
+ *
160
+ * @param key - The key to look up
161
+ * @returns An Option containing the value if found (.some(value)), or .none if not found
162
+ *
163
+ * @see {@link get} for a version that throws an error on missing keys
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * const tryGetValue = East.function([DictType(StringType, IntegerType), StringType], OptionType(IntegerType), ($, dict, key) => {
168
+ * $.return(dict.tryGet(key));
169
+ * });
170
+ * const compiled = East.compile(tryGetValue.toIR(), []);
171
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
172
+ * compiled(dict, "a"); // { tag: "some", value: 1n }
173
+ * compiled(dict, "c"); // { tag: "none", value: null }
174
+ * ```
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * // Using with match
179
+ * const getOrDefault = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, dict, key) => {
180
+ * $.return($.match(dict.tryGet(key), {
181
+ * some: ($, value) => value,
182
+ * none: () => 0n
183
+ * }));
184
+ * });
185
+ * const compiled = East.compile(getOrDefault.toIR(), []);
186
+ * compiled(dict, "a"); // 1n
187
+ * compiled(dict, "c"); // 0n
188
+ * ```
189
+ */
190
+ tryGet(key) {
191
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
192
+ return this[FactorySymbol]({
193
+ ast_type: "Builtin",
194
+ type: OptionType(this.value_type),
195
+ location: get_location(2),
196
+ builtin: "DictTryGet",
197
+ type_parameters: [this.key_type, this.value_type],
198
+ arguments: [this[AstSymbol], keyAst],
199
+ });
200
+ }
201
+ /**
202
+ * Inserts a new key-value pair into the dictionary.
203
+ *
204
+ * @param key - The key to insert
205
+ * @param value - The value to associate with the key
206
+ * @returns A NullExpr
207
+ *
208
+ * @throws East runtime error if the key already exists in the dictionary
209
+ *
210
+ * @see {@link update} for replacing an existing value
211
+ * @see {@link insertOrUpdate} for inserting or updating without error
212
+ * @see {@link getOrInsert} to insert a value lazily on lookup
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const insertEntry = East.function([DictType(StringType, IntegerType), StringType, IntegerType], NullType, ($, dict, key, value) => {
217
+ * $(dict.insert(key, value));
218
+ * $.return(null);
219
+ * });
220
+ * const compiled = East.compile(insertEntry.toIR(), []);
221
+ * const dict = new Map([["a", 1n]]);
222
+ * compiled(dict, "b", 2n); // dict now has Map([["a", 1n], ["b", 2n]])
223
+ * // compiled(dict, "a", 10n) would throw error (duplicate key)
224
+ * ```
225
+ */
226
+ insert(key, value) {
227
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
228
+ const valueAst = valueOrExprToAstTyped(value, this.value_type);
229
+ return this[FactorySymbol]({
230
+ ast_type: "Builtin",
231
+ type: NullType,
232
+ location: get_location(2),
233
+ builtin: "DictInsert",
234
+ type_parameters: [this.key_type, this.value_type],
235
+ arguments: [this[AstSymbol], keyAst, valueAst],
236
+ });
237
+ }
238
+ /**
239
+ * Inserts or updates a key-value pair in the dictionary.
240
+ *
241
+ * If the key already exists, the value is overwritten with the provided value. This operation is idempotent.
242
+ *
243
+ * @param key - The key to insert or update
244
+ * @param value - The new value to associate with the key
245
+ * @returns A NullExpr
246
+ *
247
+ * @see {@link insert} for inserting only (errors on duplicate)
248
+ * @see {@link update} for updating only (errors on missing key)
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const upsert = East.function([DictType(StringType, IntegerType), StringType, IntegerType], NullType, ($, dict, key, value) => {
253
+ * $(dict.insertOrUpdate(key, value));
254
+ * $.return(null);
255
+ * });
256
+ * const compiled = East.compile(upsert.toIR(), []);
257
+ * const dict = new Map([["a", 1n]]);
258
+ * compiled(dict, "b", 2n); // dict now has Map([["a", 1n], ["b", 2n]])
259
+ * compiled(dict, "a", 10n); // dict now has Map([["a", 10n], ["b", 2n]])
260
+ * ```
261
+ */
262
+ insertOrUpdate(key, value) {
263
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
264
+ const valueAst = valueOrExprToAstTyped(value, this.value_type);
265
+ return this[FactorySymbol]({
266
+ ast_type: "Builtin",
267
+ type: NullType,
268
+ location: get_location(2),
269
+ builtin: "DictInsertOrUpdate",
270
+ type_parameters: [this.key_type, this.value_type],
271
+ arguments: [this[AstSymbol], keyAst, valueAst],
272
+ });
273
+ }
274
+ /**
275
+ * Updates an existing value in the dictionary.
276
+ *
277
+ * @param key - The key to update
278
+ * @param value - The new value to set
279
+ * @returns A NullExpr
280
+ *
281
+ * @throws East runtime error if the key does not exist
282
+ *
283
+ * @see {@link insertOrUpdate} for inserting or updating without error
284
+ * @see {@link getOrInsert} to get or insert a value
285
+ * @see {@link merge} to update based on the existing value
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * const updateValue = East.function([DictType(StringType, IntegerType), StringType, IntegerType], NullType, ($, dict, key, newValue) => {
290
+ * $(dict.update(key, newValue));
291
+ * $.return(null);
292
+ * });
293
+ * const compiled = East.compile(updateValue.toIR(), []);
294
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
295
+ * compiled(dict, "a", 10n); // dict now has Map([["a", 10n], ["b", 2n]])
296
+ * // compiled(dict, "c", 3n) would throw error (key not found)
297
+ * ```
298
+ */
299
+ update(key, value) {
300
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
301
+ const valueAst = valueOrExprToAstTyped(value, this.value_type);
302
+ return this[FactorySymbol]({
303
+ ast_type: "Builtin",
304
+ type: NullType,
305
+ location: get_location(2),
306
+ builtin: "DictUpdate",
307
+ type_parameters: [this.key_type, this.value_type],
308
+ arguments: [this[AstSymbol], keyAst, valueAst],
309
+ });
310
+ }
311
+ /**
312
+ * Modifies a dictionary value at a key by merging it with a new value.
313
+ *
314
+ * This is useful for patterns where you want to update an entry based on its current value, e.g. incrementing a number,
315
+ * appending to a string, updating fields in a struct, or pushing to an array.
316
+ *
317
+ * @param key - The key to update
318
+ * @param value - The value to merge with the existing value
319
+ * @param updateFn - Function accepting (existing, new, key) and returning the merged value
320
+ * @param initialFn - Optional function to produce an initial value if key is missing; if omitted, an error is thrown on missing key
321
+ * @returns A NullExpr
322
+ *
323
+ * @throws East runtime error if key is not found and initialFn is not provided
324
+ *
325
+ * @see {@link insertOrUpdate} and {@link update} for simply replacing the value
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * // Increment counter, initializing to 0 if missing
330
+ * const increment = East.function([DictType(StringType, IntegerType), StringType], NullType, ($, counts, word) => {
331
+ * $(counts.merge(word, 1n, ($, existing, inc) => existing.add(inc), () => 0n));
332
+ * $.return(null);
333
+ * });
334
+ * const compiled = East.compile(increment.toIR(), []);
335
+ * const counts = new Map([["hello", 5n]]);
336
+ * compiled(counts, "hello"); // counts now has Map([["hello", 6n]])
337
+ * compiled(counts, "world"); // counts now has Map([["hello", 6n], ["world", 1n]])
338
+ * ```
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * // Append to string
343
+ * const appendText = East.function([DictType(StringType, StringType), StringType, StringType], NullType, ($, dict, key, suffix) => {
344
+ * $(dict.merge(key, suffix, ($, existing, newVal) => existing.concat(newVal), () => ""));
345
+ * $.return(null);
346
+ * });
347
+ * const compiled = East.compile(appendText.toIR(), []);
348
+ * const dict = new Map([["greeting", "Hello"]]);
349
+ * compiled(dict, "greeting", " World"); // Map([["greeting", "Hello World"]])
350
+ * compiled(dict, "new", "Hi"); // Map([["greeting", "Hello World"], ["new", "Hi"]])
351
+ * ```
352
+ */
353
+ merge(key, value, updateFn, initialFn) {
354
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
355
+ const valueAst = valueOrExprToAst(value);
356
+ const value2Type = valueAst.type;
357
+ const updateFnExpr = Expr.from(updateFn, FunctionType([this.value_type, value2Type, this.key_type], this.value_type, null));
358
+ let initialExpr;
359
+ if (initialFn === undefined) {
360
+ // Default: create function that throws error
361
+ initialExpr = Expr.function([this.key_type], this.value_type, ($, key) => $.error(Expr.str `Key ${key} not found in dictionary`, get_location(2)));
362
+ }
363
+ else {
364
+ initialExpr = Expr.from(initialFn, FunctionType([this.key_type], this.value_type, null));
365
+ }
366
+ return this[FactorySymbol]({
367
+ ast_type: "Builtin",
368
+ type: NullType,
369
+ location: get_location(2),
370
+ builtin: "DictMerge",
371
+ type_parameters: [this.key_type, this.value_type, value2Type],
372
+ arguments: [this[AstSymbol], keyAst, valueAst, Expr.ast(updateFnExpr), Expr.ast(initialExpr)],
373
+ });
374
+ }
375
+ /**
376
+ * Gets a value from the dictionary or inserts it if the key is not present.
377
+ *
378
+ * The default value is evaluated and inserted if the key is not present, otherwise the existing value is returned.
379
+ *
380
+ * @param key - The key to get or insert
381
+ * @param defaultValue - Function that computes the default value if key is missing
382
+ * @returns The existing value or the newly inserted value
383
+ *
384
+ * @see {@link get} for getting with a missing handler
385
+ * @see {@link insert} for inserting with a conflict handler
386
+ * @see {@link update} for updating existing values
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * const getOrCreate = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, dict, key) => {
391
+ * $.return(dict.getOrInsert(key, () => 0n));
392
+ * });
393
+ * const compiled = East.compile(getOrCreate.toIR(), []);
394
+ * const dict = new Map([["a", 1n]]);
395
+ * compiled(dict, "a"); // 1n (existing value)
396
+ * compiled(dict, "b"); // 0n (new value inserted)
397
+ * // dict now has Map([["a", 1n], ["b", 0n]])
398
+ * ```
399
+ */
400
+ getOrInsert(key, defaultValue) {
401
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
402
+ const defaultValueExpr = Expr.from(defaultValue, FunctionType([this.key_type], this.value_type, null));
403
+ return this[FactorySymbol]({
404
+ ast_type: "Builtin",
405
+ type: this.value_type,
406
+ location: get_location(2),
407
+ builtin: "DictGetOrInsert",
408
+ type_parameters: [this.key_type, this.value_type],
409
+ arguments: [this[AstSymbol], keyAst, Expr.ast(defaultValueExpr)],
410
+ });
411
+ }
412
+ /**
413
+ * Deletes a key from the dictionary.
414
+ *
415
+ * @param key - The key to delete
416
+ * @returns A NullExpr
417
+ *
418
+ * @throws East runtime error if the key does not exist
419
+ *
420
+ * @see {@link tryDelete} for a version that doesn't error on missing keys
421
+ * @see {@link clear} to remove all keys
422
+ * @see {@link pop} to delete and return the value
423
+ *
424
+ * @example
425
+ * ```ts
426
+ * const deleteKey = East.function([DictType(StringType, IntegerType), StringType], NullType, ($, dict, key) => {
427
+ * $(dict.delete(key));
428
+ * $.return(null);
429
+ * });
430
+ * const compiled = East.compile(deleteKey.toIR(), []);
431
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
432
+ * compiled(dict, "a"); // dict now has Map([["b", 2n]])
433
+ * // compiled(dict, "c") would throw error (key not found)
434
+ * ```
435
+ */
436
+ delete(key) {
437
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
438
+ return this[FactorySymbol]({
439
+ ast_type: "Builtin",
440
+ type: NullType,
441
+ location: get_location(2),
442
+ builtin: "DictDelete",
443
+ type_parameters: [this.key_type, this.value_type],
444
+ arguments: [this[AstSymbol], keyAst],
445
+ });
446
+ }
447
+ /**
448
+ * Tries to delete a key from the dictionary, returning whether it was deleted.
449
+ *
450
+ * @param key - The key to delete
451
+ * @returns A BooleanExpr that is true if the key was deleted, false if not found
452
+ *
453
+ * @see {@link delete} for a version that errors on missing keys
454
+ * @see {@link clear} to remove all keys
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * const tryDeleteKey = East.function([DictType(StringType, IntegerType), StringType], BooleanType, ($, dict, key) => {
459
+ * $.return(dict.tryDelete(key));
460
+ * });
461
+ * const compiled = East.compile(tryDeleteKey.toIR(), []);
462
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
463
+ * compiled(dict, "a"); // true (dict now has Map([["b", 2n]]))
464
+ * compiled(dict, "c"); // false (key not found, dict unchanged)
465
+ * ```
466
+ */
467
+ tryDelete(key) {
468
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
469
+ return this[FactorySymbol]({
470
+ ast_type: "Builtin",
471
+ type: BooleanType,
472
+ location: get_location(2),
473
+ builtin: "DictTryDelete",
474
+ type_parameters: [this.key_type, this.value_type],
475
+ arguments: [this[AstSymbol], keyAst],
476
+ });
477
+ }
478
+ /**
479
+ * Removes a key from the dictionary and returns its value.
480
+ *
481
+ * @param key - The key to pop
482
+ * @returns The value that was associated with the key
483
+ *
484
+ * @throws East runtime error if the key does not exist
485
+ *
486
+ * @see {@link delete} to remove a key without returning the value
487
+ * @see {@link swap} to replace a value and return the old one
488
+ *
489
+ * @example
490
+ * ```ts
491
+ * const popKey = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, dict, key) => {
492
+ * $.return(dict.pop(key));
493
+ * });
494
+ * const compiled = East.compile(popKey.toIR(), []);
495
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
496
+ * compiled(dict, "a"); // 1n (dict now has Map([["b", 2n]]))
497
+ * // compiled(dict, "c") would throw error (key not found)
498
+ * ```
499
+ */
500
+ pop(key) {
501
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
502
+ return this[FactorySymbol]({
503
+ ast_type: "Builtin",
504
+ type: this.value_type,
505
+ location: get_location(2),
506
+ builtin: "DictPop",
507
+ type_parameters: [this.key_type, this.value_type],
508
+ arguments: [this[AstSymbol], keyAst],
509
+ });
510
+ }
511
+ /**
512
+ * Swaps a value in the dictionary, returning the previous value.
513
+ *
514
+ * @param key - The key to swap
515
+ * @param value - The new value to insert
516
+ * @returns The previous value that was associated with the key
517
+ *
518
+ * @throws East runtime error if the key does not exist
519
+ *
520
+ * @see {@link pop} to remove a key and return its value
521
+ * @see {@link update} to update without returning the old value
522
+ *
523
+ * @example
524
+ * ```ts
525
+ * const swapValue = East.function([DictType(StringType, IntegerType), StringType, IntegerType], IntegerType, ($, dict, key, newValue) => {
526
+ * $.return(dict.swap(key, newValue));
527
+ * });
528
+ * const compiled = East.compile(swapValue.toIR(), []);
529
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
530
+ * compiled(dict, "a", 10n); // 1n (dict now has Map([["a", 10n], ["b", 2n]]))
531
+ * // compiled(dict, "c", 3n) would throw error (key not found)
532
+ * ```
533
+ */
534
+ swap(key, value) {
535
+ const keyAst = valueOrExprToAstTyped(key, this.key_type);
536
+ const valueAst = valueOrExprToAstTyped(value, this.value_type);
537
+ return this[FactorySymbol]({
538
+ ast_type: "Builtin",
539
+ type: this.value_type,
540
+ location: get_location(2),
541
+ builtin: "DictSwap",
542
+ type_parameters: [this.key_type, this.value_type],
543
+ arguments: [this[AstSymbol], keyAst, valueAst],
544
+ });
545
+ }
546
+ /**
547
+ * Removes all keys from the dictionary.
548
+ *
549
+ * @returns A NullExpr
550
+ *
551
+ * @see {@link delete} or {@link tryDelete} to remove individual keys
552
+ *
553
+ * @example
554
+ * ```ts
555
+ * const clearDict = East.function([DictType(StringType, IntegerType)], NullType, ($, dict) => {
556
+ * $(dict.clear());
557
+ * $.return(null);
558
+ * });
559
+ * const compiled = East.compile(clearDict.toIR(), []);
560
+ * const dict = new Map([["a", 1n], ["b", 2n], ["c", 3n]]);
561
+ * compiled(dict); // dict is now Map([])
562
+ * ```
563
+ */
564
+ clear() {
565
+ return this[FactorySymbol]({
566
+ ast_type: "Builtin",
567
+ type: NullType,
568
+ location: get_location(2),
569
+ builtin: "DictClear",
570
+ type_parameters: [this.key_type, this.value_type],
571
+ arguments: [this[AstSymbol]],
572
+ });
573
+ }
574
+ /**
575
+ * Unions another dictionary into this one in place.
576
+ *
577
+ * If a key exists in both dictionaries, a merger function can be provided to combine the values.
578
+ * Without a merger function, an error is thrown on overlapping keys.
579
+ *
580
+ * @param dict2 - The dictionary to union with this one
581
+ * @param mergeFn - Optional function to combine values when keys overlap; accepts (existing, new, key) and returns merged value
582
+ * @returns A NullExpr
583
+ *
584
+ * @throws East runtime error if keys overlap and mergeFn is not provided
585
+ *
586
+ * @see {@link mergeAll} for a more general merge operation with different value types
587
+ *
588
+ * @example
589
+ * ```ts
590
+ * const unionDicts = East.function([DictType(StringType, IntegerType), DictType(StringType, IntegerType)], NullType, ($, dict1, dict2) => {
591
+ * $(dict1.unionInPlace(dict2, ($, existing, newVal) => existing.add(newVal)));
592
+ * $.return(null);
593
+ * });
594
+ * const compiled = East.compile(unionDicts.toIR(), []);
595
+ * const dict1 = new Map([["a", 1n], ["b", 2n]]);
596
+ * const dict2 = new Map([["b", 3n], ["c", 4n]]);
597
+ * compiled(dict1, dict2); // dict1 now has Map([["a", 1n], ["b", 5n], ["c", 4n]])
598
+ * ```
599
+ */
600
+ unionInPlace(dict2, mergeFn) {
601
+ const dict2Expr = Expr.from(dict2, this[TypeSymbol]);
602
+ let mergerAst;
603
+ if (mergeFn === undefined) {
604
+ // Default: replace existing value with new value (ignore key and existing, return new)
605
+ const mergerExpr = Expr.function([this.value_type, this.value_type, this.key_type], this.value_type, ($, _v1, _v2, k) => $.error(Expr.str `Key ${k} exists in both dictionaries`, get_location(2)));
606
+ mergerAst = Expr.ast(mergerExpr);
607
+ }
608
+ else {
609
+ const mergerExpr = Expr.from(mergeFn, FunctionType([this.value_type, this.value_type, this.key_type], this.value_type, null));
610
+ mergerAst = Expr.ast(mergerExpr);
611
+ }
612
+ return this[FactorySymbol]({
613
+ ast_type: "Builtin",
614
+ type: NullType,
615
+ location: get_location(2),
616
+ builtin: "DictUnionInPlace",
617
+ type_parameters: [this.key_type, this.value_type],
618
+ arguments: [this[AstSymbol], Expr.ast(dict2Expr), mergerAst],
619
+ });
620
+ }
621
+ /**
622
+ * Merges all values from another dictionary into this one using a merge function.
623
+ *
624
+ * The type of the other dictionary can be different from this one, so long as the merge function can combine
625
+ * the two value types into this dictionary's value type. This is useful for patterns like merging counts from
626
+ * multiple dictionaries, pushing to nested arrays, or partially updating dictionaries-of-structs.
627
+ *
628
+ * @param dict2 - The dictionary whose values to merge into this one
629
+ * @param mergeFn - Function accepting (existing, new, key) and returning the merged value
630
+ * @param initialFn - Optional function to produce initial value for missing keys; if omitted, an error is thrown on missing key
631
+ * @returns A NullExpr
632
+ *
633
+ * @throws East runtime error if a key is missing and initialFn is not provided
634
+ *
635
+ * @see {@link unionInPlace} for merging dictionaries of the same type
636
+ * @see {@link merge} to merge a single key
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * // Merge counts from two dictionaries
641
+ * const mergeCounts = East.function(
642
+ * [DictType(StringType, IntegerType), DictType(StringType, IntegerType)],
643
+ * NullType,
644
+ * ($, counts1, counts2) => {
645
+ * $(counts1.mergeAll(counts2, ($, existing, newVal) => existing.add(newVal), () => 0n));
646
+ * $.return(null);
647
+ * }
648
+ * );
649
+ * const compiled = East.compile(mergeCounts.toIR(), []);
650
+ * const counts1 = new Map([["apple", 5n], ["banana", 3n]]);
651
+ * const counts2 = new Map([["banana", 2n], ["cherry", 7n]]);
652
+ * compiled(counts1, counts2); // counts1 now has Map([["apple", 5n], ["banana", 5n], ["cherry", 7n]])
653
+ * ```
654
+ */
655
+ mergeAll(dict2, mergeFn, initialFn) {
656
+ const dict2Ast = Expr.ast(dict2);
657
+ const value2Type = dict2Ast.type.value;
658
+ const mergerExpr = Expr.from(mergeFn, FunctionType([this.value_type, value2Type, this.key_type], this.value_type, null));
659
+ let initialExpr;
660
+ if (initialFn === undefined) {
661
+ // Default: create function that throws error
662
+ initialExpr = Expr.function([this.key_type], this.value_type, ($, key) => $.error(Expr.str `Key ${key} not found in dictionary`, get_location(2)));
663
+ }
664
+ else {
665
+ initialExpr = Expr.from(initialFn, FunctionType([this.key_type], this.value_type, null));
666
+ }
667
+ return this[FactorySymbol]({
668
+ ast_type: "Builtin",
669
+ type: NullType,
670
+ location: get_location(2),
671
+ builtin: "DictMergeAll",
672
+ type_parameters: [this.key_type, this.value_type, value2Type],
673
+ arguments: [this[AstSymbol], dict2Ast, Expr.ast(mergerExpr), Expr.ast(initialExpr)],
674
+ });
675
+ }
676
+ /**
677
+ * Returns a set containing all keys in the dictionary.
678
+ *
679
+ * @returns A SetExpr containing all the dictionary's keys
680
+ *
681
+ * @see {@link getKeys} to get values for a subset of keys
682
+ *
683
+ * @example
684
+ * ```ts
685
+ * const getKeys = East.function([DictType(StringType, IntegerType)], SetType(StringType), ($, dict) => {
686
+ * $.return(dict.keys());
687
+ * });
688
+ * const compiled = East.compile(getKeys.toIR(), []);
689
+ * const dict = new Map([["a", 1n], ["b", 2n], ["c", 3n]]);
690
+ * compiled(dict); // Set(["a", "b", "c"])
691
+ * ```
692
+ */
693
+ keys() {
694
+ return this[FactorySymbol]({
695
+ ast_type: "Builtin",
696
+ type: SetType(this.key_type),
697
+ location: get_location(2),
698
+ builtin: "DictKeys",
699
+ type_parameters: [this.key_type, this.value_type],
700
+ arguments: [this[AstSymbol]],
701
+ });
702
+ }
703
+ /**
704
+ * Collects the values associated with the given keys in a new dictionary.
705
+ *
706
+ * @param keys - Set of keys to look up
707
+ * @param onMissing - Optional function to produce values for missing keys; if omitted, an error is thrown on missing key
708
+ * @returns A new dictionary containing only the specified keys
709
+ *
710
+ * @throws East runtime error if a key is not found and onMissing is not provided
711
+ *
712
+ * @see {@link keys} to get all keys as a set
713
+ *
714
+ * @example
715
+ * ```ts
716
+ * const selectKeys = East.function([DictType(StringType, IntegerType), SetType(StringType)], DictType(StringType, IntegerType), ($, dict, keysToSelect) => {
717
+ * $.return(dict.getKeys(keysToSelect));
718
+ * });
719
+ * const compiled = East.compile(selectKeys.toIR(), []);
720
+ * const dict = new Map([["a", 1n], ["b", 2n], ["c", 3n], ["d", 4n]]);
721
+ * const keysToSelect = new Set(["a", "c"]);
722
+ * compiled(dict, keysToSelect); // Map([["a", 1n], ["c", 3n]])
723
+ * ```
724
+ *
725
+ * @example
726
+ * ```ts
727
+ * // With missing handler
728
+ * const selectWithDefault = East.function([DictType(StringType, IntegerType), SetType(StringType)], DictType(StringType, IntegerType), ($, dict, keys) => {
729
+ * $.return(dict.getKeys(keys, () => 0n));
730
+ * });
731
+ * const compiled = East.compile(selectWithDefault.toIR(), []);
732
+ * const keysWithMissing = new Set(["a", "x"]);
733
+ * compiled(dict, keysWithMissing); // Map([["a", 1n], ["x", 0n]])
734
+ * ```
735
+ */
736
+ getKeys(keys, onMissing) {
737
+ const keysAst = valueOrExprToAstTyped(keys, SetType(this.key_type));
738
+ let onMissingAst;
739
+ if (onMissing === undefined) {
740
+ // Default: throw error with key information if key doesn't exist
741
+ const defaultFunction = Expr.function([this.key_type], this.value_type, ($, key) => $.error(Expr.str `Key ${key} not found in dictionary`, get_location(2)));
742
+ onMissingAst = Expr.ast(defaultFunction);
743
+ }
744
+ else {
745
+ const onMissingExpr = Expr.from(onMissing, FunctionType([this.key_type], this.value_type, null));
746
+ onMissingAst = Expr.ast(onMissingExpr);
747
+ }
748
+ return this[FactorySymbol]({
749
+ ast_type: "Builtin",
750
+ type: this[TypeSymbol],
751
+ location: get_location(2),
752
+ builtin: "DictGetKeys",
753
+ type_parameters: [this.key_type, this.value_type],
754
+ arguments: [this[AstSymbol], keysAst, onMissingAst],
755
+ });
756
+ }
757
+ /**
758
+ * Iterates over each key-value pair in the dictionary, applying a function to each.
759
+ *
760
+ * @param fn - Function to apply to each (value, key) pair
761
+ * @returns A NullExpr
762
+ *
763
+ * @example
764
+ * ```ts
765
+ * const printEntries = East.function([DictType(StringType, IntegerType)], NullType, ($, dict) => {
766
+ * $(dict.forEach(($, value, key) => {
767
+ * // In a real platform, you could use a platform function to log
768
+ * // For this example, we just demonstrate the iteration structure
769
+ * }));
770
+ * $.return(null);
771
+ * });
772
+ * const compiled = East.compile(printEntries.toIR(), []);
773
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
774
+ * compiled(dict); // Iterates over all entries
775
+ * ```
776
+ */
777
+ forEach(fn) {
778
+ let fnAst;
779
+ if (typeof fn === "function") {
780
+ const fnExpr = Expr.function([this.value_type, this.key_type], undefined, fn);
781
+ fnAst = Expr.ast(fnExpr);
782
+ }
783
+ else {
784
+ const fnExpr = Expr.from(fn, FunctionType([this.value_type, this.key_type], undefined, null));
785
+ fnAst = Expr.ast(fnExpr);
786
+ }
787
+ const returnType = fnAst.type.output;
788
+ return this[FactorySymbol]({
789
+ ast_type: "Builtin",
790
+ type: NullType,
791
+ location: get_location(2),
792
+ builtin: "DictForEach",
793
+ type_parameters: [this.key_type, this.value_type, returnType],
794
+ arguments: [this[AstSymbol], fnAst],
795
+ });
796
+ }
797
+ /**
798
+ * Creates a shallow copy of the dictionary.
799
+ *
800
+ * @returns A new DictExpr containing the same key-value pairs
801
+ *
802
+ * @example
803
+ * ```ts
804
+ * const copyDict = East.function([DictType(StringType, IntegerType)], DictType(StringType, IntegerType), ($, dict) => {
805
+ * $.return(dict.copy());
806
+ * });
807
+ * const compiled = East.compile(copyDict.toIR(), []);
808
+ * const dict = new Map([["a", 1n], ["b", 2n]]);
809
+ * const copy = compiled(dict); // Map([["a", 1n], ["b", 2n]])
810
+ * // Modifying copy doesn't affect dict
811
+ * ```
812
+ */
813
+ copy() {
814
+ return this[FactorySymbol]({
815
+ ast_type: "Builtin",
816
+ type: this[TypeSymbol],
817
+ location: get_location(2),
818
+ builtin: "DictCopy",
819
+ type_parameters: [this.key_type, this.value_type],
820
+ arguments: [this[AstSymbol]],
821
+ });
822
+ }
823
+ map(fn) {
824
+ let fnExpr;
825
+ if (typeof fn === "function") {
826
+ fnExpr = Expr.function([this.value_type, this.key_type], undefined, fn);
827
+ }
828
+ else {
829
+ fnExpr = fn;
830
+ }
831
+ const fnAst = Expr.ast(fnExpr);
832
+ const returnType = fnAst.type.output;
833
+ return Expr.fromAst({
834
+ ast_type: "Builtin",
835
+ type: DictType(this.key_type, returnType),
836
+ location: get_location(2),
837
+ builtin: "DictMap",
838
+ type_parameters: [this.key_type, this.value_type, returnType],
839
+ arguments: [this[AstSymbol], Expr.ast(fnExpr)],
840
+ });
841
+ }
842
+ /**
843
+ * Filters the dictionary by applying a predicate function to each entry.
844
+ *
845
+ * @param fn - Predicate function that returns true to keep an entry, false to discard it
846
+ * @returns A new DictExpr containing only entries for which the predicate returned true
847
+ *
848
+ * @see {@link filterMap} to filter and map in one operation
849
+ * @see {@link map} to transform values without filtering
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * const filterLargeValues = East.function([DictType(StringType, IntegerType)], DictType(StringType, IntegerType), ($, dict) => {
854
+ * $.return(dict.filter(($, value, key) => value.greaterOrEqual(10n)));
855
+ * });
856
+ * const compiled = East.compile(filterLargeValues.toIR(), []);
857
+ * const dict = new Map([["a", 5n], ["b", 15n], ["c", 20n], ["d", 8n]]);
858
+ * compiled(dict); // Map([["b", 15n], ["c", 20n]])
859
+ * ```
860
+ */
861
+ filter(fn) {
862
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([this.value_type, this.key_type], BooleanType, null));
863
+ return Expr.fromAst({
864
+ ast_type: "Builtin",
865
+ type: this[TypeSymbol],
866
+ location: get_location(2),
867
+ builtin: "DictFilter",
868
+ type_parameters: [this.key_type, this.value_type],
869
+ arguments: [this[AstSymbol], fnAst],
870
+ });
871
+ }
872
+ filterMap(fn) {
873
+ let fnExpr;
874
+ if (typeof fn === "function") {
875
+ fnExpr = Expr.function([this.value_type, this.key_type], undefined, fn);
876
+ }
877
+ else {
878
+ fnExpr = fn;
879
+ }
880
+ const fnAst = Expr.ast(fnExpr);
881
+ const returnType = fnAst.type.output;
882
+ if (returnType.type !== "Variant") {
883
+ throw new Error(`Expected Function to return an Option type, got ${printType(returnType)}`);
884
+ }
885
+ if (!Object.keys(returnType.cases).every(k => k === "none" || k === "some")) {
886
+ throw new Error(`Expected Function to return an Option type, got ${printType(returnType)}`);
887
+ }
888
+ const someType = returnType.cases["some"] ?? NeverType;
889
+ return Expr.fromAst({
890
+ ast_type: "Builtin",
891
+ type: this[TypeSymbol],
892
+ location: get_location(2),
893
+ builtin: "DictFilterMap",
894
+ type_parameters: [this.key_type, this.value_type, someType],
895
+ arguments: [this[AstSymbol], fnAst],
896
+ });
897
+ }
898
+ toArray(fn) {
899
+ let fnExpr;
900
+ if (fn === undefined) {
901
+ // Identity function
902
+ fnExpr = Expr.function([this.value_type, this.key_type], this.value_type, ($, value) => value);
903
+ }
904
+ else if (typeof fn === "function") {
905
+ fnExpr = Expr.function([this.value_type, this.key_type], undefined, fn);
906
+ }
907
+ else {
908
+ fnExpr = fn;
909
+ }
910
+ const fnType = fnExpr[TypeSymbol];
911
+ const returnType = fnType.output;
912
+ return Expr.fromAst({
913
+ ast_type: "Builtin",
914
+ type: ArrayType(returnType),
915
+ location: get_location(2),
916
+ builtin: "DictToArray",
917
+ type_parameters: [this.key_type, this.value_type, returnType],
918
+ arguments: [this[AstSymbol], Expr.ast(fnExpr)],
919
+ });
920
+ }
921
+ toSet(keyFn) {
922
+ let keyFnAst;
923
+ if (keyFn !== undefined) {
924
+ let fnExpr;
925
+ if (typeof keyFn === "function") {
926
+ fnExpr = Expr.function([this.value_type, this.key_type], undefined, keyFn);
927
+ }
928
+ else {
929
+ fnExpr = keyFn;
930
+ }
931
+ keyFnAst = Expr.ast(fnExpr);
932
+ }
933
+ else {
934
+ // Identity function
935
+ const identityFunction = Expr.function([this.value_type, this.key_type], this.value_type, ($, value) => value);
936
+ keyFnAst = Expr.ast(identityFunction);
937
+ }
938
+ const keyType = keyFnAst.type.output;
939
+ return Expr.fromAst({
940
+ ast_type: "Builtin",
941
+ type: SetType(keyType),
942
+ location: get_location(2),
943
+ builtin: "DictToSet",
944
+ type_parameters: [this.key_type, this.value_type, keyType],
945
+ arguments: [this[AstSymbol], keyFnAst],
946
+ });
947
+ }
948
+ toDict(keyFn, valueFn, onConflictFn) {
949
+ const keyFnAst = valueOrExprToAstTyped(keyFn ?? ((_$, v, k) => k), FunctionType([this.value_type, this.key_type], undefined, null));
950
+ const keyType = keyFnAst.type.output;
951
+ const valueFnAst = valueOrExprToAstTyped(valueFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
952
+ const valueType = valueFnAst.type.output;
953
+ let onConflictAst;
954
+ if (onConflictFn === undefined) {
955
+ const location = get_location(2);
956
+ const onConflictFunction = Expr.function([valueType, valueType, keyType], valueType, ($, existing, value, key) => $.error(Expr.str `Cannot insert duplicate key ${key} into dict`, location));
957
+ onConflictAst = Expr.ast(onConflictFunction);
958
+ }
959
+ else {
960
+ onConflictAst = valueOrExprToAstTyped(onConflictFn, FunctionType([valueType, valueType, keyType], valueType, null));
961
+ }
962
+ return Expr.fromAst({
963
+ ast_type: "Builtin",
964
+ type: DictType(keyType, valueType),
965
+ location: get_location(2),
966
+ builtin: "DictToDict",
967
+ type_parameters: [this.key_type, this.value_type, keyType, valueType],
968
+ arguments: [this[AstSymbol], keyFnAst, valueFnAst, onConflictAst],
969
+ });
970
+ }
971
+ flattenToArray(fn) {
972
+ const fnAst = valueOrExprToAstTyped(fn ?? ((_$, x) => x), FunctionType([this.value_type, this.key_type], undefined, null));
973
+ const returnType = fnAst.type.output;
974
+ if (returnType.type !== "Array") {
975
+ throw new Error(`Expected Function to return an Array type, got ${printType(returnType)}`);
976
+ }
977
+ const elementType = returnType.value;
978
+ return Expr.fromAst({
979
+ ast_type: "Builtin",
980
+ type: ArrayType(elementType),
981
+ location: get_location(2),
982
+ builtin: "DictFlattenToArray",
983
+ type_parameters: [this.key_type, this.value_type, elementType],
984
+ arguments: [this[AstSymbol], fnAst],
985
+ });
986
+ }
987
+ flattenToSet(fn) {
988
+ const fnAst = valueOrExprToAstTyped(fn ?? ((_$, x) => x), FunctionType([this.value_type, this.key_type], undefined, null));
989
+ const returnType = fnAst.type.output;
990
+ if (returnType.type !== "Set") {
991
+ throw new Error(`Expected Function to return a Set type, got ${printType(returnType)}`);
992
+ }
993
+ const elementType = returnType.key;
994
+ return Expr.fromAst({
995
+ ast_type: "Builtin",
996
+ type: SetType(elementType),
997
+ location: get_location(2),
998
+ builtin: "DictFlattenToSet",
999
+ type_parameters: [this.key_type, this.value_type, elementType],
1000
+ arguments: [this[AstSymbol], fnAst],
1001
+ });
1002
+ }
1003
+ flattenToDict(fn, onConflictFn) {
1004
+ const fnAst = valueOrExprToAstTyped(fn ?? ((_$, x) => x), FunctionType([this.value_type, this.key_type], undefined, null));
1005
+ const returnType = fnAst.type.output;
1006
+ if (returnType.type !== "Dict") {
1007
+ throw new Error(`Expected Function to return a Dict type, got ${printType(returnType)}`);
1008
+ }
1009
+ const keyType = returnType.key;
1010
+ const valueType = returnType.value;
1011
+ let onConflictAst;
1012
+ if (onConflictFn === undefined) {
1013
+ const location = get_location(2);
1014
+ const onConflictFunction = Expr.function([valueType, valueType, keyType], valueType, ($, existing, value, key) => $.error(Expr.str `Cannot insert duplicate key ${key} into dict`, location));
1015
+ onConflictAst = Expr.ast(onConflictFunction);
1016
+ }
1017
+ else {
1018
+ onConflictAst = valueOrExprToAstTyped(onConflictFn, FunctionType([valueType, valueType, keyType], valueType, null));
1019
+ }
1020
+ return Expr.fromAst({
1021
+ ast_type: "Builtin",
1022
+ type: DictType(keyType, valueType),
1023
+ location: get_location(2),
1024
+ builtin: "DictFlattenToDict",
1025
+ type_parameters: [this.key_type, this.value_type, keyType, valueType],
1026
+ arguments: [this[AstSymbol], fnAst, onConflictAst],
1027
+ });
1028
+ }
1029
+ groupReduce(keyFn, initFn, reduceFn) {
1030
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1031
+ const keyType = keyFnAst.type.output;
1032
+ const initFnAst = valueOrExprToAstTyped(initFn, FunctionType([keyType], undefined, null));
1033
+ const initType = initFnAst.type.output;
1034
+ const reduceFnAst = valueOrExprToAstTyped(reduceFn, FunctionType([initType, this.value_type, this.key_type], initType, null));
1035
+ return Expr.fromAst({
1036
+ ast_type: "Builtin",
1037
+ type: DictType(keyType, initType),
1038
+ location: get_location(2),
1039
+ builtin: "DictGroupFold",
1040
+ type_parameters: [this.key_type, this.value_type, keyType, initType],
1041
+ arguments: [this[AstSymbol], keyFnAst, initFnAst, reduceFnAst],
1042
+ });
1043
+ }
1044
+ /**
1045
+ * Collect entries in each group into arrays.
1046
+ *
1047
+ * @param keyFn - Function that computes the grouping key
1048
+ * @param valueFn - Optional projection function for values
1049
+ * @returns Dictionary mapping each key to an array of elements in that group
1050
+ *
1051
+ * @example
1052
+ * ```ts
1053
+ * new Dict({ a: 1n, b: 2n, c: 3n, d: 4n }).groupToArrays(($, v, k) => v.remainder(2n))
1054
+ * // Result: { 0n: [2n, 4n], 1n: [1n, 3n] }
1055
+ * ```
1056
+ */
1057
+ groupToArrays(keyFn, valueFn) {
1058
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1059
+ const valueFnAst = valueOrExprToAstTyped(valueFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
1060
+ const keyFnExpr = Expr.fromAst(keyFnAst);
1061
+ const valueFnExpr = Expr.fromAst(valueFnAst);
1062
+ const valueType = valueFnAst.type.output;
1063
+ return this.groupReduce(((_$, value, key) => keyFnExpr(value, key)), ((_$, _groupKey) => Expr.from([], ArrayType(valueType))), (($, acc, value, key) => {
1064
+ const val = valueFnExpr(value, key);
1065
+ $(acc.pushLast(val));
1066
+ return acc;
1067
+ }));
1068
+ }
1069
+ /**
1070
+ * Collect entries in each group into sets, ignoring duplicates.
1071
+ *
1072
+ * @param keyFn - Function that computes the grouping key
1073
+ * @param valueFn - Optional projection function for values
1074
+ * @returns Dictionary mapping each key to a set of elements in that group
1075
+ *
1076
+ * @example
1077
+ * ```ts
1078
+ * new Dict({ a: 1n, b: 2n, c: 1n, d: 2n }).groupToSets(($, v, k) => v.remainder(2n))
1079
+ * // Result: { 0n: Set([2n]), 1n: Set([1n]) }
1080
+ * ```
1081
+ */
1082
+ groupToSets(keyFn, valueFn) {
1083
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1084
+ const valueFnAst = valueOrExprToAstTyped(valueFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
1085
+ const keyFnExpr = Expr.fromAst(keyFnAst);
1086
+ const valueFnExpr = Expr.fromAst(valueFnAst);
1087
+ const valueType = valueFnAst.type.output;
1088
+ return this.groupReduce(((_$, value, key) => keyFnExpr(value, key)), ((_$, _groupKey) => Expr.from(new Set(), SetType(valueType))), (($, acc, value, key) => {
1089
+ const val = valueFnExpr(value, key);
1090
+ $(acc.tryInsert(val));
1091
+ return acc;
1092
+ }));
1093
+ }
1094
+ /**
1095
+ * Group entries into nested dictionaries.
1096
+ *
1097
+ * @param keyFn - Function that computes the outer grouping key
1098
+ * @param keyFn2 - Function that computes the inner dictionary key
1099
+ * @param valueFn - Optional projection function for inner dictionary values
1100
+ * @param combineFn - Optional function to resolve conflicts when the same inner key appears multiple times
1101
+ * @returns Dictionary-of-dictionaries mapping group keys to dictionaries
1102
+ *
1103
+ * @example
1104
+ * ```ts
1105
+ * // Without conflict handler - errors on duplicate keys
1106
+ * orders.groupToDicts(
1107
+ * ($, v, k) => v.department,
1108
+ * ($, v, k) => v.role
1109
+ * )
1110
+ * // Result: { "eng": { "dev": user1, "lead": user2 }, "sales": { "rep": user3 } }
1111
+ *
1112
+ * // With conflict handler - merges duplicate keys
1113
+ * orders.groupToDicts(
1114
+ * ($, v, k) => v.customer,
1115
+ * ($, v, k) => v.product,
1116
+ * ($, v, k) => v.quantity,
1117
+ * ($, a, b) => a.add(b)
1118
+ * )
1119
+ * // Sums quantities for same customer+product
1120
+ * ```
1121
+ */
1122
+ groupToDicts(keyFn, keyFn2, valueFn, combineFn) {
1123
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1124
+ const keyFn2Ast = valueOrExprToAstTyped(keyFn2, FunctionType([this.value_type, this.key_type], undefined, null));
1125
+ const valueFnAst = valueOrExprToAstTyped(valueFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
1126
+ const keyFnExpr = Expr.fromAst(keyFnAst);
1127
+ const keyFn2Expr = Expr.fromAst(keyFn2Ast);
1128
+ const valueFnExpr = Expr.fromAst(valueFnAst);
1129
+ const key2Type = keyFn2Ast.type.output;
1130
+ const valueType = valueFnAst.type.output;
1131
+ if (combineFn !== undefined) {
1132
+ // With conflict resolution - use tryGet + match to check existence, then insert or combine
1133
+ const combineFnAst = valueOrExprToAstTyped(combineFn, FunctionType([valueType, valueType], valueType, null));
1134
+ const combineFnExpr = Expr.fromAst(combineFnAst);
1135
+ return this.groupReduce(((_$, value, key) => keyFnExpr(value, key)), ((_$, _groupKey) => Expr.from(new Map(), DictType(key2Type, valueType))), (($, dict, value, key) => {
1136
+ const innerKey = keyFn2Expr(value, key);
1137
+ const val = valueFnExpr(value, key);
1138
+ $.match(dict.tryGet(innerKey), {
1139
+ some: ($, existing) => {
1140
+ const combined = combineFnExpr(existing, val);
1141
+ $(dict.update(innerKey, combined));
1142
+ },
1143
+ none: ($) => {
1144
+ $(dict.insert(innerKey, val));
1145
+ }
1146
+ });
1147
+ return dict;
1148
+ }));
1149
+ }
1150
+ else {
1151
+ // Without conflict resolution - use insert (errors on duplicate)
1152
+ return this.groupReduce(((_$, value, key) => keyFnExpr(value, key)), ((_$, _groupKey) => Expr.from(new Map(), DictType(key2Type, valueType))), (($, dict, value, key) => {
1153
+ const innerKey = keyFn2Expr(value, key);
1154
+ const val = valueFnExpr(value, key);
1155
+ $(dict.insert(innerKey, val));
1156
+ return dict;
1157
+ }));
1158
+ }
1159
+ }
1160
+ groupSize(keyFn) {
1161
+ const keyFnAst = valueOrExprToAstTyped(keyFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
1162
+ return this.toDict(((_$, value, key) => Expr.fromAst(keyFnAst)(value, key)), ((_$) => 1n), ((_$, a, b) => a.add(b)));
1163
+ }
1164
+ groupEvery(keyFn, predFn) {
1165
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1166
+ const predFnAst = valueOrExprToAstTyped(predFn, FunctionType([this.value_type, this.key_type], BooleanType, null));
1167
+ return this.groupReduce(((_$, value, key) => Expr.fromAst(keyFnAst)(value, key)), (() => true), ((_$, acc, value, key) => {
1168
+ const pred = Expr.fromAst(predFnAst)(value, key);
1169
+ return acc.and(() => pred);
1170
+ }));
1171
+ }
1172
+ groupSome(keyFn, predFn) {
1173
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1174
+ const predFnAst = valueOrExprToAstTyped(predFn, FunctionType([this.value_type, this.key_type], BooleanType, null));
1175
+ return this.groupReduce(((_$, value, key) => Expr.fromAst(keyFnAst)(value, key)), (() => false), ((_$, acc, value, key) => {
1176
+ const pred = Expr.fromAst(predFnAst)(value, key);
1177
+ return acc.or(() => pred);
1178
+ }));
1179
+ }
1180
+ groupSum(keyFn, valueFn) {
1181
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1182
+ const valueFnAst = valueOrExprToAstTyped(valueFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
1183
+ const valueType = valueFnAst.type.output;
1184
+ const isInteger = isTypeEqual(valueType, IntegerType);
1185
+ const isFloat = isTypeEqual(valueType, FloatType);
1186
+ if (!isInteger && !isFloat) {
1187
+ throw new Error(`Can only perform groupSum on Integer or Float values, got ${printType(valueType)}`);
1188
+ }
1189
+ return this.toDict(((_$, value, key) => Expr.fromAst(keyFnAst)(value, key)), ((_$, value, key) => Expr.fromAst(valueFnAst)(value, key)), ((_$, a, b) => a.add(b)));
1190
+ }
1191
+ groupMean(keyFn, valueFn) {
1192
+ const keyFnAst = valueOrExprToAstTyped(keyFn, FunctionType([this.value_type, this.key_type], undefined, null));
1193
+ const valueFnAst = valueOrExprToAstTyped(valueFn ?? ((_$, v) => v), FunctionType([this.value_type, this.key_type], undefined, null));
1194
+ const valueType = valueFnAst.type.output;
1195
+ const isInteger = isTypeEqual(valueType, IntegerType);
1196
+ const isFloat = isTypeEqual(valueType, FloatType);
1197
+ if (!isInteger && !isFloat) {
1198
+ throw new Error(`Can only perform groupMean on Integer or Float values, got ${printType(valueType)}`);
1199
+ }
1200
+ return this.toDict(((_$, value, key) => Expr.fromAst(keyFnAst)(value, key)), ((_$, value, key) => {
1201
+ const val = Expr.fromAst(valueFnAst)(value, key);
1202
+ return { sum: isInteger ? val.toFloat() : val, count: 1n };
1203
+ }), ((_$, a, b) => ({ sum: a.sum.add(b.sum), count: a.count.add(b.count) }))).map(((_$, v) => v.sum.divide(v.count.toFloat())));
1204
+ }
1205
+ /**
1206
+ * Reduces the dictionary to a single value using an accumulator function.
1207
+ *
1208
+ * @param fn - Function accepting (accumulator, value, key) and returning the new accumulator value
1209
+ * @param init - Initial value for the accumulator
1210
+ * @returns The final accumulated value
1211
+ *
1212
+ * @see {@link mapReduce} for a version that projects values before combining
1213
+ * @see {@link sum} and {@link mean} for common numeric reductions
1214
+ *
1215
+ * @example
1216
+ * ```ts
1217
+ * const sumValues = East.function([DictType(StringType, IntegerType)], IntegerType, ($, dict) => {
1218
+ * $.return(dict.reduce(($, acc, value, key) => acc.add(value), 0n));
1219
+ * });
1220
+ * const compiled = East.compile(sumValues.toIR(), []);
1221
+ * const dict = new Map([["a", 1n], ["b", 2n], ["c", 3n]]);
1222
+ * compiled(dict); // 6n
1223
+ * ```
1224
+ *
1225
+ * @example
1226
+ * ```ts
1227
+ * // Concatenate keys
1228
+ * const concatKeys = East.function([DictType(StringType, IntegerType)], StringType, ($, dict) => {
1229
+ * $.return(dict.reduce(($, acc, value, key) => acc.concat(",").concat(key), ""));
1230
+ * });
1231
+ * const compiled = East.compile(concatKeys.toIR(), []);
1232
+ * compiled(dict); // ",a,b,c"
1233
+ * ```
1234
+ */
1235
+ reduce(fn, init) {
1236
+ const initAst = valueOrExprToAst(init);
1237
+ const returnType = initAst.type;
1238
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([returnType, this.value_type, this.key_type], returnType, null));
1239
+ return this[FactorySymbol]({
1240
+ ast_type: "Builtin",
1241
+ type: returnType,
1242
+ location: get_location(2),
1243
+ builtin: "DictReduce",
1244
+ type_parameters: [this.key_type, this.value_type, returnType],
1245
+ arguments: [this[AstSymbol], fnAst, initAst],
1246
+ });
1247
+ }
1248
+ mapReduce(mapFn, combineFn) {
1249
+ const mapAst = valueOrExprToAstTyped(mapFn, FunctionType([this.value_type, this.key_type], undefined, null));
1250
+ const mapType = mapAst.type.output;
1251
+ const combineAst = valueOrExprToAstTyped(combineFn, FunctionType([mapType, mapType], mapType, null));
1252
+ return this[FactorySymbol]({
1253
+ ast_type: "Builtin",
1254
+ type: mapType,
1255
+ location: get_location(2),
1256
+ builtin: "DictMapReduce",
1257
+ type_parameters: [this.key_type, this.value_type, mapType],
1258
+ arguments: [this[AstSymbol], mapAst, combineAst],
1259
+ });
1260
+ }
1261
+ firstMap(fn) {
1262
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([this.value_type, this.key_type], undefined, null));
1263
+ const returnType = fnAst.type.output;
1264
+ if (returnType.type !== "Variant") {
1265
+ throw new Error(`Expected Function to return an Option type, got ${printType(returnType)}`);
1266
+ }
1267
+ if (!Object.keys(returnType.cases).every(k => k === "none" || k === "some")) {
1268
+ throw new Error(`Expected Function to return an Option type, got ${printType(returnType)}`);
1269
+ }
1270
+ const someType = returnType.cases["some"] ?? NeverType;
1271
+ return Expr.fromAst({
1272
+ ast_type: "Builtin",
1273
+ type: returnType,
1274
+ location: get_location(2),
1275
+ builtin: "DictFirstMap",
1276
+ type_parameters: [this.key_type, this.value_type, someType],
1277
+ arguments: [this[AstSymbol], fnAst],
1278
+ });
1279
+ }
1280
+ // Common reducers are provided, based on reduce
1281
+ /**
1282
+ * Returns true if every value in the dictionary is true, or false otherwise.
1283
+ *
1284
+ * This method short-circuits on the first false value. Note that empty dictionaries always return true.
1285
+ * For dictionaries whose values are not Boolean, a mapping function must be provided.
1286
+ *
1287
+ * @param fn - Optional function to map each (value, key) pair to a boolean
1288
+ * @returns A BooleanExpr that is true if all values satisfy the condition
1289
+ *
1290
+ * @see {@link some} to check if at least one element is true
1291
+ * @see {@link groupEvery} to check every element within groups
1292
+ *
1293
+ * @example
1294
+ * ```ts
1295
+ * const allPositive = East.function([DictType(StringType, IntegerType)], BooleanType, ($, dict) => {
1296
+ * $.return(dict.every(($, value, key) => value.greater(0n)));
1297
+ * });
1298
+ * const compiled = East.compile(allPositive.toIR(), []);
1299
+ * const dict1 = new Map([["a", 1n], ["b", 2n], ["c", 3n]]);
1300
+ * compiled(dict1); // true
1301
+ * const dict2 = new Map([["a", 1n], ["b", -2n], ["c", 3n]]);
1302
+ * compiled(dict2); // false (short-circuits at "b")
1303
+ * compiled(new Map()); // true (empty dictionary)
1304
+ * ```
1305
+ */
1306
+ every(fn) {
1307
+ if (fn === undefined) {
1308
+ if (!isTypeEqual(this.value_type, BooleanType)) {
1309
+ throw new Error(`Can only perform every on dict of booleans, got ${printType(this.value_type)}`);
1310
+ }
1311
+ // Short-circuit on first false value - use explicit function AST to avoid type inference issues
1312
+ const optionType = VariantType({ none: NullType, some: NullType });
1313
+ const valueParam = {
1314
+ ast_type: "Variable",
1315
+ type: this.value_type,
1316
+ location: get_location(2),
1317
+ mutable: false,
1318
+ };
1319
+ const keyParam = {
1320
+ ast_type: "Variable",
1321
+ type: this.key_type,
1322
+ location: get_location(2),
1323
+ mutable: false,
1324
+ };
1325
+ // Check if boolean value is NOT true, then return some(null) to stop, otherwise return none to continue
1326
+ const notCondition = {
1327
+ ast_type: "Builtin",
1328
+ type: BooleanType,
1329
+ location: get_location(2),
1330
+ builtin: "BooleanNot",
1331
+ type_parameters: [],
1332
+ arguments: [valueParam]
1333
+ };
1334
+ const checkFnAst = {
1335
+ ast_type: "Function",
1336
+ type: FunctionType([this.value_type, this.key_type], optionType, []),
1337
+ location: get_location(2),
1338
+ parameters: [valueParam, keyParam],
1339
+ body: {
1340
+ ast_type: "IfElse",
1341
+ type: optionType,
1342
+ location: get_location(2),
1343
+ ifs: [{
1344
+ predicate: notCondition,
1345
+ body: { ast_type: "Variant", type: optionType, location: get_location(2), case: "some", value: { ast_type: "Value", type: NullType, location: get_location(2), value: null } }
1346
+ }],
1347
+ else_body: { ast_type: "Variant", type: optionType, location: get_location(2), case: "none", value: { ast_type: "Value", type: NullType, location: get_location(2), value: null } }
1348
+ }
1349
+ };
1350
+ const result = Expr.fromAst({
1351
+ ast_type: "Builtin",
1352
+ type: optionType,
1353
+ location: get_location(2),
1354
+ builtin: "DictFirstMap",
1355
+ type_parameters: [this.key_type, this.value_type, NullType],
1356
+ arguments: [this[AstSymbol], checkFnAst],
1357
+ });
1358
+ return Expr.match(result, { some: () => false, none: () => true });
1359
+ }
1360
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([this.value_type, this.key_type], BooleanType, null));
1361
+ // Short-circuit on first false value
1362
+ const result = this.firstMap(($, value, key) => {
1363
+ const result = Expr.fromAst(fnAst)(value, key);
1364
+ return result.not().ifElse(() => some(null), () => none);
1365
+ });
1366
+ return Expr.match(result, { some: () => false, none: () => true });
1367
+ }
1368
+ /**
1369
+ * Returns true if at least one value in the dictionary is true, or false otherwise.
1370
+ *
1371
+ * This method short-circuits on the first true value. Note that empty dictionaries always return false.
1372
+ * For dictionaries whose values are not Boolean, a mapping function must be provided.
1373
+ *
1374
+ * @param fn - Optional function to map each (value, key) pair to a boolean
1375
+ * @returns A BooleanExpr that is true if any value satisfies the condition
1376
+ *
1377
+ * @see {@link every} to check if all elements are true
1378
+ * @see {@link groupSome} to check if some element exists within groups
1379
+ *
1380
+ * @example
1381
+ * ```ts
1382
+ * const hasNegative = East.function([DictType(StringType, IntegerType)], BooleanType, ($, dict) => {
1383
+ * $.return(dict.some(($, value, key) => value.less(0n)));
1384
+ * });
1385
+ * const compiled = East.compile(hasNegative.toIR(), []);
1386
+ * const dict1 = new Map([["a", 1n], ["b", 2n], ["c", 3n]]);
1387
+ * compiled(dict1); // false
1388
+ * const dict2 = new Map([["a", 1n], ["b", -2n], ["c", 3n]]);
1389
+ * compiled(dict2); // true (short-circuits at "b")
1390
+ * compiled(new Map()); // false (empty dictionary)
1391
+ * ```
1392
+ */
1393
+ some(fn) {
1394
+ if (fn === undefined) {
1395
+ if (!isTypeEqual(this.value_type, BooleanType)) {
1396
+ throw new Error(`Can only perform some on dict of booleans, got ${printType(this.value_type)}`);
1397
+ }
1398
+ // Short-circuit on first true value - use explicit function AST to avoid type inference issues
1399
+ const optionType = VariantType({ none: NullType, some: NullType });
1400
+ const valueParam = {
1401
+ ast_type: "Variable",
1402
+ type: this.value_type,
1403
+ location: get_location(2),
1404
+ mutable: false,
1405
+ };
1406
+ const keyParam = {
1407
+ ast_type: "Variable",
1408
+ type: this.key_type,
1409
+ location: get_location(2),
1410
+ mutable: false,
1411
+ };
1412
+ // Check if boolean value is true, then return some(null) to stop, otherwise return none to continue
1413
+ const checkFnAst = {
1414
+ ast_type: "Function",
1415
+ type: FunctionType([this.value_type, this.key_type], optionType, []),
1416
+ location: get_location(2),
1417
+ parameters: [valueParam, keyParam],
1418
+ body: {
1419
+ ast_type: "IfElse",
1420
+ type: optionType,
1421
+ location: get_location(2),
1422
+ ifs: [{
1423
+ predicate: valueParam,
1424
+ body: { ast_type: "Variant", type: optionType, location: get_location(2), case: "some", value: { ast_type: "Value", type: NullType, location: get_location(2), value: null } }
1425
+ }],
1426
+ else_body: { ast_type: "Variant", type: optionType, location: get_location(2), case: "none", value: { ast_type: "Value", type: NullType, location: get_location(2), value: null } }
1427
+ }
1428
+ };
1429
+ const result = Expr.fromAst({
1430
+ ast_type: "Builtin",
1431
+ type: optionType,
1432
+ location: get_location(2),
1433
+ builtin: "DictFirstMap",
1434
+ type_parameters: [this.key_type, this.value_type, NullType],
1435
+ arguments: [this[AstSymbol], checkFnAst],
1436
+ });
1437
+ return Expr.match(result, { some: () => true, none: () => false });
1438
+ }
1439
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([this.value_type, this.key_type], BooleanType, null));
1440
+ // Short-circuit on first true value
1441
+ const result = this.firstMap(($, value, key) => {
1442
+ const result = Expr.fromAst(fnAst)(value, key);
1443
+ return result.ifElse(() => some(null), () => none);
1444
+ });
1445
+ return Expr.match(result, { some: () => true, none: () => false });
1446
+ }
1447
+ sum(fn) {
1448
+ if (fn === undefined) {
1449
+ if (!(isTypeEqual(this.value_type, IntegerType) || isTypeEqual(this.value_type, FloatType))) {
1450
+ throw new Error(`Can only perform sum on dict of numbers (Integer or Float), got ${printType(this.value_type)}`);
1451
+ }
1452
+ const zero = isTypeEqual(this.value_type, IntegerType) ? 0n : 0.0;
1453
+ return this.reduce(($, previous, value) => previous.add(value), zero);
1454
+ }
1455
+ else {
1456
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([this.value_type, this.key_type], undefined, null));
1457
+ const returnType = fnAst.type.output;
1458
+ if (!(isTypeEqual(returnType, IntegerType) || isTypeEqual(returnType, FloatType))) {
1459
+ throw new Error(`Can only perform sum on dict of numbers (Integer or Float), got ${printType(returnType)}`);
1460
+ }
1461
+ const zero = isTypeEqual(returnType, IntegerType) ? 0n : 0.0;
1462
+ return this.reduce(($, previous, value, key) => previous.add(Expr.fromAst(fnAst)(value, key)), zero);
1463
+ }
1464
+ }
1465
+ mean(fn) {
1466
+ if (fn === undefined) {
1467
+ if (isTypeEqual(this.value_type, IntegerType)) {
1468
+ return this.reduce(($, previous, value) => previous.add(value.toFloat()), 0.0).divide(this.size().toFloat());
1469
+ }
1470
+ else if (isTypeEqual(this.value_type, FloatType)) {
1471
+ return this.reduce(($, previous, value) => previous.add(value), 0.0).divide(this.size().toFloat());
1472
+ }
1473
+ else {
1474
+ throw new Error(`Can only perform mean on dict of numbers (Integer or Float), got ${printType(this.value_type)}`);
1475
+ }
1476
+ }
1477
+ else {
1478
+ const fnAst = valueOrExprToAstTyped(fn, FunctionType([this.value_type, this.key_type], undefined, null));
1479
+ const returnType = fnAst.type.output;
1480
+ if (isTypeEqual(returnType, IntegerType)) {
1481
+ return this.reduce(($, previous, value, key) => previous.add(Expr.fromAst(fnAst)(value, key).toFloat()), 0.0).divide(this.size().toFloat());
1482
+ }
1483
+ else if (isTypeEqual(returnType, FloatType)) {
1484
+ return this.reduce(($, previous, value, key) => previous.add(Expr.fromAst(fnAst)(value, key)), 0.0).divide(this.size().toFloat());
1485
+ }
1486
+ else {
1487
+ throw new Error(`Can only perform mean on dict of numbers (Integer or Float), got ${printType(returnType)}`);
1488
+ }
1489
+ }
1490
+ }
1491
+ }
1492
+ //# sourceMappingURL=dict.js.map