@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.
- package/LICENSE.md +682 -0
- package/README.md +276 -0
- package/dist/src/analyze.d.ts +95 -0
- package/dist/src/analyze.d.ts.map +1 -0
- package/dist/src/analyze.js +1110 -0
- package/dist/src/analyze.js.map +1 -0
- package/dist/src/ast.d.ts +263 -0
- package/dist/src/ast.d.ts.map +1 -0
- package/dist/src/ast.js +151 -0
- package/dist/src/ast.js.map +1 -0
- package/dist/src/ast_to_ir.d.ts +24 -0
- package/dist/src/ast_to_ir.d.ts.map +1 -0
- package/dist/src/ast_to_ir.js +834 -0
- package/dist/src/ast_to_ir.js.map +1 -0
- package/dist/src/builtins.d.ts +18 -0
- package/dist/src/builtins.d.ts.map +1 -0
- package/dist/src/builtins.js +1105 -0
- package/dist/src/builtins.js.map +1 -0
- package/dist/src/comparison.d.ts +28 -0
- package/dist/src/comparison.d.ts.map +1 -0
- package/dist/src/comparison.js +1017 -0
- package/dist/src/comparison.js.map +1 -0
- package/dist/src/compile.d.ts +22 -0
- package/dist/src/compile.d.ts.map +1 -0
- package/dist/src/compile.js +3260 -0
- package/dist/src/compile.js.map +1 -0
- package/dist/src/containers/ref.d.ts +106 -0
- package/dist/src/containers/ref.d.ts.map +1 -0
- package/dist/src/containers/ref.js +100 -0
- package/dist/src/containers/ref.js.map +1 -0
- package/dist/src/containers/sortedmap.d.ts +165 -0
- package/dist/src/containers/sortedmap.d.ts.map +1 -0
- package/dist/src/containers/sortedmap.js +237 -0
- package/dist/src/containers/sortedmap.js.map +1 -0
- package/dist/src/containers/sortedset.d.ts +185 -0
- package/dist/src/containers/sortedset.d.ts.map +1 -0
- package/dist/src/containers/sortedset.js +312 -0
- package/dist/src/containers/sortedset.js.map +1 -0
- package/dist/src/containers/variant.d.ts +131 -0
- package/dist/src/containers/variant.d.ts.map +1 -0
- package/dist/src/containers/variant.js +68 -0
- package/dist/src/containers/variant.js.map +1 -0
- package/dist/src/datetime_format/parse.d.ts +50 -0
- package/dist/src/datetime_format/parse.d.ts.map +1 -0
- package/dist/src/datetime_format/parse.js +908 -0
- package/dist/src/datetime_format/parse.js.map +1 -0
- package/dist/src/datetime_format/print.d.ts +35 -0
- package/dist/src/datetime_format/print.d.ts.map +1 -0
- package/dist/src/datetime_format/print.js +157 -0
- package/dist/src/datetime_format/print.js.map +1 -0
- package/dist/src/datetime_format/tokenize.d.ts +76 -0
- package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
- package/dist/src/datetime_format/tokenize.js +271 -0
- package/dist/src/datetime_format/tokenize.js.map +1 -0
- package/dist/src/datetime_format/types.d.ts +99 -0
- package/dist/src/datetime_format/types.d.ts.map +1 -0
- package/dist/src/datetime_format/types.js +103 -0
- package/dist/src/datetime_format/types.js.map +1 -0
- package/dist/src/datetime_format/validate.d.ts +51 -0
- package/dist/src/datetime_format/validate.d.ts.map +1 -0
- package/dist/src/datetime_format/validate.js +208 -0
- package/dist/src/datetime_format/validate.js.map +1 -0
- package/dist/src/default.d.ts +21 -0
- package/dist/src/default.d.ts.map +1 -0
- package/dist/src/default.js +82 -0
- package/dist/src/default.js.map +1 -0
- package/dist/src/eastir.d.ts +33 -0
- package/dist/src/eastir.d.ts.map +1 -0
- package/dist/src/eastir.js +92 -0
- package/dist/src/eastir.js.map +1 -0
- package/dist/src/error.d.ts +13 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +8 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/expr/array.d.ts +1711 -0
- package/dist/src/expr/array.d.ts.map +1 -0
- package/dist/src/expr/array.js +1805 -0
- package/dist/src/expr/array.js.map +1 -0
- package/dist/src/expr/ast.d.ts +17 -0
- package/dist/src/expr/ast.d.ts.map +1 -0
- package/dist/src/expr/ast.js +302 -0
- package/dist/src/expr/ast.js.map +1 -0
- package/dist/src/expr/blob.d.ts +141 -0
- package/dist/src/expr/blob.d.ts.map +1 -0
- package/dist/src/expr/blob.js +198 -0
- package/dist/src/expr/blob.js.map +1 -0
- package/dist/src/expr/block.d.ts +201 -0
- package/dist/src/expr/block.d.ts.map +1 -0
- package/dist/src/expr/block.js +1505 -0
- package/dist/src/expr/block.js.map +1 -0
- package/dist/src/expr/boolean.d.ts +207 -0
- package/dist/src/expr/boolean.d.ts.map +1 -0
- package/dist/src/expr/boolean.js +261 -0
- package/dist/src/expr/boolean.js.map +1 -0
- package/dist/src/expr/datetime.d.ts +544 -0
- package/dist/src/expr/datetime.d.ts.map +1 -0
- package/dist/src/expr/datetime.js +980 -0
- package/dist/src/expr/datetime.js.map +1 -0
- package/dist/src/expr/dict.d.ts +1242 -0
- package/dist/src/expr/dict.d.ts.map +1 -0
- package/dist/src/expr/dict.js +1492 -0
- package/dist/src/expr/dict.js.map +1 -0
- package/dist/src/expr/expr.d.ts +95 -0
- package/dist/src/expr/expr.d.ts.map +1 -0
- package/dist/src/expr/expr.js +171 -0
- package/dist/src/expr/expr.js.map +1 -0
- package/dist/src/expr/float.d.ts +357 -0
- package/dist/src/expr/float.d.ts.map +1 -0
- package/dist/src/expr/float.js +637 -0
- package/dist/src/expr/float.js.map +1 -0
- package/dist/src/expr/function.d.ts +46 -0
- package/dist/src/expr/function.d.ts.map +1 -0
- package/dist/src/expr/function.js +58 -0
- package/dist/src/expr/function.js.map +1 -0
- package/dist/src/expr/index.d.ts +450 -0
- package/dist/src/expr/index.d.ts.map +1 -0
- package/dist/src/expr/index.js +423 -0
- package/dist/src/expr/index.js.map +1 -0
- package/dist/src/expr/integer.d.ts +256 -0
- package/dist/src/expr/integer.d.ts.map +1 -0
- package/dist/src/expr/integer.js +311 -0
- package/dist/src/expr/integer.js.map +1 -0
- package/dist/src/expr/libs/array.d.ts +106 -0
- package/dist/src/expr/libs/array.d.ts.map +1 -0
- package/dist/src/expr/libs/array.js +140 -0
- package/dist/src/expr/libs/array.js.map +1 -0
- package/dist/src/expr/libs/blob.d.ts +42 -0
- package/dist/src/expr/libs/blob.d.ts.map +1 -0
- package/dist/src/expr/libs/blob.js +70 -0
- package/dist/src/expr/libs/blob.js.map +1 -0
- package/dist/src/expr/libs/datetime.d.ts +479 -0
- package/dist/src/expr/libs/datetime.d.ts.map +1 -0
- package/dist/src/expr/libs/datetime.js +624 -0
- package/dist/src/expr/libs/datetime.js.map +1 -0
- package/dist/src/expr/libs/dict.d.ts +66 -0
- package/dist/src/expr/libs/dict.d.ts.map +1 -0
- package/dist/src/expr/libs/dict.js +77 -0
- package/dist/src/expr/libs/dict.js.map +1 -0
- package/dist/src/expr/libs/float.d.ts +299 -0
- package/dist/src/expr/libs/float.d.ts.map +1 -0
- package/dist/src/expr/libs/float.js +564 -0
- package/dist/src/expr/libs/float.js.map +1 -0
- package/dist/src/expr/libs/integer.d.ts +228 -0
- package/dist/src/expr/libs/integer.d.ts.map +1 -0
- package/dist/src/expr/libs/integer.js +398 -0
- package/dist/src/expr/libs/integer.js.map +1 -0
- package/dist/src/expr/libs/set.d.ts +59 -0
- package/dist/src/expr/libs/set.d.ts.map +1 -0
- package/dist/src/expr/libs/set.js +69 -0
- package/dist/src/expr/libs/set.js.map +1 -0
- package/dist/src/expr/libs/string.d.ts +71 -0
- package/dist/src/expr/libs/string.d.ts.map +1 -0
- package/dist/src/expr/libs/string.js +75 -0
- package/dist/src/expr/libs/string.js.map +1 -0
- package/dist/src/expr/never.d.ts +15 -0
- package/dist/src/expr/never.d.ts.map +1 -0
- package/dist/src/expr/never.js +12 -0
- package/dist/src/expr/never.js.map +1 -0
- package/dist/src/expr/null.d.ts +15 -0
- package/dist/src/expr/null.d.ts.map +1 -0
- package/dist/src/expr/null.js +12 -0
- package/dist/src/expr/null.js.map +1 -0
- package/dist/src/expr/ref.d.ts +103 -0
- package/dist/src/expr/ref.d.ts.map +1 -0
- package/dist/src/expr/ref.js +131 -0
- package/dist/src/expr/ref.js.map +1 -0
- package/dist/src/expr/regex_validation.d.ts +25 -0
- package/dist/src/expr/regex_validation.d.ts.map +1 -0
- package/dist/src/expr/regex_validation.js +130 -0
- package/dist/src/expr/regex_validation.js.map +1 -0
- package/dist/src/expr/set.d.ts +1071 -0
- package/dist/src/expr/set.d.ts.map +1 -0
- package/dist/src/expr/set.js +1137 -0
- package/dist/src/expr/set.js.map +1 -0
- package/dist/src/expr/string.d.ts +414 -0
- package/dist/src/expr/string.d.ts.map +1 -0
- package/dist/src/expr/string.js +683 -0
- package/dist/src/expr/string.js.map +1 -0
- package/dist/src/expr/struct.d.ts +48 -0
- package/dist/src/expr/struct.d.ts.map +1 -0
- package/dist/src/expr/struct.js +65 -0
- package/dist/src/expr/struct.js.map +1 -0
- package/dist/src/expr/types.d.ts +68 -0
- package/dist/src/expr/types.d.ts.map +1 -0
- package/dist/src/expr/types.js +6 -0
- package/dist/src/expr/types.js.map +1 -0
- package/dist/src/expr/variant.d.ts +137 -0
- package/dist/src/expr/variant.d.ts.map +1 -0
- package/dist/src/expr/variant.js +105 -0
- package/dist/src/expr/variant.js.map +1 -0
- package/dist/src/fuzz.d.ts +80 -0
- package/dist/src/fuzz.d.ts.map +1 -0
- package/dist/src/fuzz.js +300 -0
- package/dist/src/fuzz.js.map +1 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/internal.d.ts +36 -0
- package/dist/src/internal.d.ts.map +1 -0
- package/dist/src/internal.js +11 -0
- package/dist/src/internal.js.map +1 -0
- package/dist/src/ir.d.ts +1571 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +56 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/location.d.ts +48 -0
- package/dist/src/location.d.ts.map +1 -0
- package/dist/src/location.js +62 -0
- package/dist/src/location.js.map +1 -0
- package/dist/src/platform.d.ts +21 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +8 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/serialization/beast.d.ts +39 -0
- package/dist/src/serialization/beast.d.ts.map +1 -0
- package/dist/src/serialization/beast.js +555 -0
- package/dist/src/serialization/beast.js.map +1 -0
- package/dist/src/serialization/beast2-stream.d.ts +38 -0
- package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
- package/dist/src/serialization/beast2-stream.js +665 -0
- package/dist/src/serialization/beast2-stream.js.map +1 -0
- package/dist/src/serialization/beast2.d.ts +41 -0
- package/dist/src/serialization/beast2.d.ts.map +1 -0
- package/dist/src/serialization/beast2.js +489 -0
- package/dist/src/serialization/beast2.js.map +1 -0
- package/dist/src/serialization/binary-utils.d.ts +151 -0
- package/dist/src/serialization/binary-utils.d.ts.map +1 -0
- package/dist/src/serialization/binary-utils.js +929 -0
- package/dist/src/serialization/binary-utils.js.map +1 -0
- package/dist/src/serialization/east.d.ts +84 -0
- package/dist/src/serialization/east.d.ts.map +1 -0
- package/dist/src/serialization/east.js +1802 -0
- package/dist/src/serialization/east.js.map +1 -0
- package/dist/src/serialization/index.d.ts +11 -0
- package/dist/src/serialization/index.d.ts.map +1 -0
- package/dist/src/serialization/index.js +12 -0
- package/dist/src/serialization/index.js.map +1 -0
- package/dist/src/serialization/json.d.ts +36 -0
- package/dist/src/serialization/json.d.ts.map +1 -0
- package/dist/src/serialization/json.js +849 -0
- package/dist/src/serialization/json.js.map +1 -0
- package/dist/src/type_of_type.d.ts +115 -0
- package/dist/src/type_of_type.d.ts.map +1 -0
- package/dist/src/type_of_type.js +362 -0
- package/dist/src/type_of_type.js.map +1 -0
- package/dist/src/types.d.ts +648 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1631 -0
- package/dist/src/types.js.map +1 -0
- 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
|