simple-graph-query 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -8,7 +8,7 @@ export type BinaryRelationExample = {
|
|
|
8
8
|
pairs: Set<AtomPair>;
|
|
9
9
|
datum: IDataInstance;
|
|
10
10
|
};
|
|
11
|
-
type ExpressionNode = {
|
|
11
|
+
export type ExpressionNode = {
|
|
12
12
|
kind: "identifier";
|
|
13
13
|
name: string;
|
|
14
14
|
} | {
|
|
@@ -22,6 +22,41 @@ type ExpressionNode = {
|
|
|
22
22
|
} | {
|
|
23
23
|
kind: "closure";
|
|
24
24
|
child: ExpressionNode;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "reflexive-closure";
|
|
27
|
+
child: ExpressionNode;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "transpose";
|
|
30
|
+
child: ExpressionNode;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "comprehension";
|
|
33
|
+
varName: string;
|
|
34
|
+
domain: ExpressionNode;
|
|
35
|
+
body: ExpressionNode;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "all" | "some" | "no" | "one" | "lone";
|
|
38
|
+
varName: string;
|
|
39
|
+
domain: ExpressionNode;
|
|
40
|
+
body: ExpressionNode;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "and" | "or" | "implies" | "iff";
|
|
43
|
+
left: ExpressionNode;
|
|
44
|
+
right: ExpressionNode;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "not";
|
|
47
|
+
child: ExpressionNode;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "in" | "eq" | "neq";
|
|
50
|
+
left: ExpressionNode;
|
|
51
|
+
right: ExpressionNode;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "lt" | "gt" | "lte" | "gte";
|
|
54
|
+
left: ExpressionNode;
|
|
55
|
+
right: ExpressionNode;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "box-join";
|
|
58
|
+
base: ExpressionNode;
|
|
59
|
+
args: ExpressionNode[];
|
|
25
60
|
};
|
|
26
61
|
export type WhyNode = {
|
|
27
62
|
kind: ExpressionNode["kind"];
|
|
@@ -45,4 +80,3 @@ export type SynthesisWhy = {
|
|
|
45
80
|
};
|
|
46
81
|
export declare function synthesizeSelectorWithWhy(examples: AtomSelectionExample[], maxDepth?: number): SynthesisWhy;
|
|
47
82
|
export declare function synthesizeBinaryRelationWithWhy(examples: BinaryRelationExample[], maxDepth?: number): SynthesisWhy;
|
|
48
|
-
export {};
|
|
@@ -50955,10 +50955,20 @@ function nodeToString(node) {
|
|
|
50955
50955
|
switch (node.kind) {
|
|
50956
50956
|
case "identifier":
|
|
50957
50957
|
return node.name;
|
|
50958
|
+
// Unary relational operators
|
|
50958
50959
|
case "closure": {
|
|
50959
50960
|
const inner = nodeToString(node.child);
|
|
50960
50961
|
return `^${wrapForPrefix(inner)}`;
|
|
50961
50962
|
}
|
|
50963
|
+
case "reflexive-closure": {
|
|
50964
|
+
const inner = nodeToString(node.child);
|
|
50965
|
+
return `*${wrapForPrefix(inner)}`;
|
|
50966
|
+
}
|
|
50967
|
+
case "transpose": {
|
|
50968
|
+
const inner = nodeToString(node.child);
|
|
50969
|
+
return `~${wrapForPrefix(inner)}`;
|
|
50970
|
+
}
|
|
50971
|
+
// Binary relational operators
|
|
50962
50972
|
case "join": {
|
|
50963
50973
|
const left = wrapForJoin(node.left);
|
|
50964
50974
|
const right = wrapForJoin(node.right);
|
|
@@ -50970,10 +50980,59 @@ function nodeToString(node) {
|
|
|
50970
50980
|
return `(${nodeToString(node.left)} & ${nodeToString(node.right)})`;
|
|
50971
50981
|
case "difference":
|
|
50972
50982
|
return `(${nodeToString(node.left)} - ${nodeToString(node.right)})`;
|
|
50983
|
+
// Set comprehension
|
|
50984
|
+
case "comprehension":
|
|
50985
|
+
return `{${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)}}`;
|
|
50986
|
+
// Quantified expressions
|
|
50987
|
+
case "all":
|
|
50988
|
+
return `(all ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
50989
|
+
case "some":
|
|
50990
|
+
return `(some ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
50991
|
+
case "no":
|
|
50992
|
+
return `(no ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
50993
|
+
case "one":
|
|
50994
|
+
return `(one ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
50995
|
+
case "lone":
|
|
50996
|
+
return `(lone ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
50997
|
+
// Logical operators
|
|
50998
|
+
case "and":
|
|
50999
|
+
return `(${nodeToString(node.left)} and ${nodeToString(node.right)})`;
|
|
51000
|
+
case "or":
|
|
51001
|
+
return `(${nodeToString(node.left)} or ${nodeToString(node.right)})`;
|
|
51002
|
+
case "implies":
|
|
51003
|
+
return `(${nodeToString(node.left)} => ${nodeToString(node.right)})`;
|
|
51004
|
+
case "iff":
|
|
51005
|
+
return `(${nodeToString(node.left)} <=> ${nodeToString(node.right)})`;
|
|
51006
|
+
case "not":
|
|
51007
|
+
return `!${wrapForPrefix(nodeToString(node.child))}`;
|
|
51008
|
+
// Relational comparison
|
|
51009
|
+
case "in":
|
|
51010
|
+
return `(${nodeToString(node.left)} in ${nodeToString(node.right)})`;
|
|
51011
|
+
case "eq":
|
|
51012
|
+
return `(${nodeToString(node.left)} = ${nodeToString(node.right)})`;
|
|
51013
|
+
case "neq":
|
|
51014
|
+
return `(${nodeToString(node.left)} != ${nodeToString(node.right)})`;
|
|
51015
|
+
// Numeric comparison
|
|
51016
|
+
case "lt":
|
|
51017
|
+
return `(${nodeToString(node.left)} < ${nodeToString(node.right)})`;
|
|
51018
|
+
case "gt":
|
|
51019
|
+
return `(${nodeToString(node.left)} > ${nodeToString(node.right)})`;
|
|
51020
|
+
case "lte":
|
|
51021
|
+
return `(${nodeToString(node.left)} <= ${nodeToString(node.right)})`;
|
|
51022
|
+
case "gte":
|
|
51023
|
+
return `(${nodeToString(node.left)} >= ${nodeToString(node.right)})`;
|
|
51024
|
+
// Box join
|
|
51025
|
+
case "box-join":
|
|
51026
|
+
return `${nodeToString(node.base)}[${node.args.map(nodeToString).join(", ")}]`;
|
|
50973
51027
|
}
|
|
50974
51028
|
}
|
|
50975
51029
|
function wrapForJoin(node) {
|
|
50976
|
-
|
|
51030
|
+
// Identifiers and prefix unary operators don't need wrapping
|
|
51031
|
+
if (node.kind === "identifier" ||
|
|
51032
|
+
node.kind === "closure" ||
|
|
51033
|
+
node.kind === "reflexive-closure" ||
|
|
51034
|
+
node.kind === "transpose" ||
|
|
51035
|
+
node.kind === "box-join") {
|
|
50977
51036
|
return nodeToString(node);
|
|
50978
51037
|
}
|
|
50979
51038
|
return `(${nodeToString(node)})`;
|
|
@@ -50997,10 +51056,16 @@ function normalizeUnaryResult(result) {
|
|
|
50997
51056
|
return null;
|
|
50998
51057
|
}
|
|
50999
51058
|
const value = tuple[0];
|
|
51000
|
-
|
|
51059
|
+
// Accept strings or numbers (integers are often returned as numbers)
|
|
51060
|
+
if (typeof value === "string") {
|
|
51061
|
+
ids.add(value);
|
|
51062
|
+
}
|
|
51063
|
+
else if (typeof value === "number") {
|
|
51064
|
+
ids.add(String(value));
|
|
51065
|
+
}
|
|
51066
|
+
else {
|
|
51001
51067
|
return null;
|
|
51002
51068
|
}
|
|
51003
|
-
ids.add(value);
|
|
51004
51069
|
}
|
|
51005
51070
|
return ids;
|
|
51006
51071
|
}
|
|
@@ -51015,15 +51080,17 @@ function normalizeBinaryResult(result) {
|
|
|
51015
51080
|
return null;
|
|
51016
51081
|
}
|
|
51017
51082
|
const [first, second] = tuple;
|
|
51018
|
-
|
|
51083
|
+
// Accept strings or numbers for both elements
|
|
51084
|
+
const firstStr = typeof first === "string" ? first : typeof first === "number" ? String(first) : null;
|
|
51085
|
+
const secondStr = typeof second === "string" ? second : typeof second === "number" ? String(second) : null;
|
|
51086
|
+
if (firstStr === null || secondStr === null) {
|
|
51019
51087
|
return null;
|
|
51020
51088
|
}
|
|
51021
|
-
ids.add(`${
|
|
51089
|
+
ids.add(`${firstStr}\u0000${secondStr}`);
|
|
51022
51090
|
}
|
|
51023
51091
|
return ids;
|
|
51024
51092
|
}
|
|
51025
|
-
function evaluateExpression(node,
|
|
51026
|
-
const evaluator = new index_1.SimpleGraphQueryEvaluator(datum);
|
|
51093
|
+
function evaluateExpression(node, evaluator, normalizer) {
|
|
51027
51094
|
const expression = nodeToString(node);
|
|
51028
51095
|
const result = evaluator.evaluateExpression(expression);
|
|
51029
51096
|
return normalizer(result);
|
|
@@ -51055,15 +51122,145 @@ function setsEqual(a, b) {
|
|
|
51055
51122
|
}
|
|
51056
51123
|
return true;
|
|
51057
51124
|
}
|
|
51125
|
+
function classifyIdentifier(name, datums) {
|
|
51126
|
+
if (name === "univ" || name === "iden") {
|
|
51127
|
+
return "builtin";
|
|
51128
|
+
}
|
|
51129
|
+
for (const datum of datums) {
|
|
51130
|
+
if (datum.getRelations().some((relation) => relation.name === name)) {
|
|
51131
|
+
return "relation";
|
|
51132
|
+
}
|
|
51133
|
+
if (datum.getTypes().some((type) => type.id === name)) {
|
|
51134
|
+
return "type";
|
|
51135
|
+
}
|
|
51136
|
+
}
|
|
51137
|
+
return "other";
|
|
51138
|
+
}
|
|
51139
|
+
// Build semantically meaningful join candidates based on type-relation compatibility.
|
|
51140
|
+
// For a relation with types [T1, T2, ...], we generate joins like T1.relation
|
|
51141
|
+
// which projects the relation to its range. This helps synthesize expressions
|
|
51142
|
+
// like "Node.key" when selecting integer values that are keys of nodes.
|
|
51143
|
+
function buildSemanticJoinCandidates(datums) {
|
|
51144
|
+
const candidates = [];
|
|
51145
|
+
const seenExpressions = new Set();
|
|
51146
|
+
// Get relations and types that exist across all datums
|
|
51147
|
+
const sharedRelationNames = new Set();
|
|
51148
|
+
const sharedTypeIds = new Set();
|
|
51149
|
+
if (datums.length === 0)
|
|
51150
|
+
return candidates;
|
|
51151
|
+
// Initialize with first datum
|
|
51152
|
+
const firstDatum = datums[0];
|
|
51153
|
+
firstDatum.getRelations().forEach((r) => sharedRelationNames.add(r.name));
|
|
51154
|
+
firstDatum.getTypes().forEach((t) => sharedTypeIds.add(t.id));
|
|
51155
|
+
// Intersect with remaining datums
|
|
51156
|
+
for (let i = 1; i < datums.length; i++) {
|
|
51157
|
+
const datum = datums[i];
|
|
51158
|
+
const datumRelations = new Set(datum.getRelations().map((r) => r.name));
|
|
51159
|
+
const datumTypes = new Set(datum.getTypes().map((t) => t.id));
|
|
51160
|
+
for (const name of sharedRelationNames) {
|
|
51161
|
+
if (!datumRelations.has(name))
|
|
51162
|
+
sharedRelationNames.delete(name);
|
|
51163
|
+
}
|
|
51164
|
+
for (const id of sharedTypeIds) {
|
|
51165
|
+
if (!datumTypes.has(id))
|
|
51166
|
+
sharedTypeIds.delete(id);
|
|
51167
|
+
}
|
|
51168
|
+
}
|
|
51169
|
+
// For each shared relation, check if its domain type is also shared
|
|
51170
|
+
// and create Type.relation join candidates
|
|
51171
|
+
for (const relationName of sharedRelationNames) {
|
|
51172
|
+
// Get the relation's type signature from the first datum (should be consistent)
|
|
51173
|
+
const relation = firstDatum.getRelations().find((r) => r.name === relationName);
|
|
51174
|
+
if (!relation || relation.types.length < 2)
|
|
51175
|
+
continue;
|
|
51176
|
+
const domainType = relation.types[0];
|
|
51177
|
+
// Check if the domain type (or a compatible type) is available
|
|
51178
|
+
// We look for types in the hierarchy that could be the domain
|
|
51179
|
+
for (const typeId of sharedTypeIds) {
|
|
51180
|
+
const typeObj = firstDatum.getTypes().find((t) => t.id === typeId);
|
|
51181
|
+
if (!typeObj)
|
|
51182
|
+
continue;
|
|
51183
|
+
// Check if this type is compatible with the relation's domain
|
|
51184
|
+
// Either the type itself matches, or it's in the type hierarchy
|
|
51185
|
+
const isCompatible = typeId === domainType ||
|
|
51186
|
+
typeObj.types.includes(domainType) ||
|
|
51187
|
+
// Also check if the domain type is a subtype of this type
|
|
51188
|
+
firstDatum.getTypes().find((t) => t.id === domainType)?.types.includes(typeId);
|
|
51189
|
+
if (isCompatible) {
|
|
51190
|
+
const joinNode = {
|
|
51191
|
+
kind: "join",
|
|
51192
|
+
left: { kind: "identifier", name: typeId },
|
|
51193
|
+
right: { kind: "identifier", name: relationName },
|
|
51194
|
+
};
|
|
51195
|
+
const exprString = nodeToString(joinNode);
|
|
51196
|
+
if (!seenExpressions.has(exprString)) {
|
|
51197
|
+
seenExpressions.add(exprString);
|
|
51198
|
+
candidates.push(joinNode);
|
|
51199
|
+
}
|
|
51200
|
+
}
|
|
51201
|
+
}
|
|
51202
|
+
}
|
|
51203
|
+
// Also add relation.Type joins for inverse projections (getting domain values)
|
|
51204
|
+
for (const relationName of sharedRelationNames) {
|
|
51205
|
+
const relation = firstDatum.getRelations().find((r) => r.name === relationName);
|
|
51206
|
+
if (!relation || relation.types.length < 2)
|
|
51207
|
+
continue;
|
|
51208
|
+
const rangeType = relation.types[relation.types.length - 1];
|
|
51209
|
+
for (const typeId of sharedTypeIds) {
|
|
51210
|
+
const typeObj = firstDatum.getTypes().find((t) => t.id === typeId);
|
|
51211
|
+
if (!typeObj)
|
|
51212
|
+
continue;
|
|
51213
|
+
const isCompatible = typeId === rangeType ||
|
|
51214
|
+
typeObj.types.includes(rangeType) ||
|
|
51215
|
+
firstDatum.getTypes().find((t) => t.id === rangeType)?.types.includes(typeId);
|
|
51216
|
+
if (isCompatible) {
|
|
51217
|
+
const joinNode = {
|
|
51218
|
+
kind: "join",
|
|
51219
|
+
left: { kind: "identifier", name: relationName },
|
|
51220
|
+
right: { kind: "identifier", name: typeId },
|
|
51221
|
+
};
|
|
51222
|
+
const exprString = nodeToString(joinNode);
|
|
51223
|
+
if (!seenExpressions.has(exprString)) {
|
|
51224
|
+
seenExpressions.add(exprString);
|
|
51225
|
+
candidates.push(joinNode);
|
|
51226
|
+
}
|
|
51227
|
+
}
|
|
51228
|
+
}
|
|
51229
|
+
}
|
|
51230
|
+
return candidates;
|
|
51231
|
+
}
|
|
51058
51232
|
function buildBaseNodes(datums) {
|
|
51059
51233
|
const baseNames = intersectNames(datums);
|
|
51060
51234
|
// Always include standard top-level identifiers when present in the language
|
|
51061
51235
|
["univ", "iden"].forEach((builtin) => baseNames.add(builtin));
|
|
51062
|
-
|
|
51236
|
+
const orderedNames = Array.from(baseNames).sort((left, right) => {
|
|
51237
|
+
const priority = {
|
|
51238
|
+
relation: 0,
|
|
51239
|
+
type: 1,
|
|
51240
|
+
builtin: 2,
|
|
51241
|
+
other: 3,
|
|
51242
|
+
};
|
|
51243
|
+
const leftPriority = priority[classifyIdentifier(left, datums)];
|
|
51244
|
+
const rightPriority = priority[classifyIdentifier(right, datums)];
|
|
51245
|
+
if (leftPriority !== rightPriority) {
|
|
51246
|
+
return leftPriority - rightPriority;
|
|
51247
|
+
}
|
|
51248
|
+
return left.localeCompare(right);
|
|
51249
|
+
});
|
|
51250
|
+
return orderedNames.map((name) => ({ kind: "identifier", name }));
|
|
51251
|
+
}
|
|
51252
|
+
function getOrCreateEvaluator(datum, cache) {
|
|
51253
|
+
const existing = cache.get(datum);
|
|
51254
|
+
if (existing) {
|
|
51255
|
+
return existing;
|
|
51256
|
+
}
|
|
51257
|
+
const evaluator = new index_1.SimpleGraphQueryEvaluator(datum);
|
|
51258
|
+
cache.set(datum, evaluator);
|
|
51259
|
+
return evaluator;
|
|
51063
51260
|
}
|
|
51064
51261
|
function matchesTargets(node, examples, normalizer) {
|
|
51065
51262
|
for (const example of examples) {
|
|
51066
|
-
const result = evaluateExpression(node, example.
|
|
51263
|
+
const result = evaluateExpression(node, example.evaluator, normalizer);
|
|
51067
51264
|
if (!result)
|
|
51068
51265
|
return false;
|
|
51069
51266
|
if (!setsEqual(result, example.target)) {
|
|
@@ -51079,11 +51276,30 @@ function synthesizeExpressionNode(examples, normalizer, maxDepth = 3) {
|
|
|
51079
51276
|
if (examples.length === 0) {
|
|
51080
51277
|
throw new SelectorSynthesisError("No examples provided for synthesis");
|
|
51081
51278
|
}
|
|
51082
|
-
const
|
|
51279
|
+
const evaluatorCache = new Map();
|
|
51280
|
+
const evaluatedExamples = examples.map((example) => ({
|
|
51281
|
+
...example,
|
|
51282
|
+
evaluator: getOrCreateEvaluator(example.datum, evaluatorCache),
|
|
51283
|
+
}));
|
|
51284
|
+
const datums = evaluatedExamples.map((example) => example.datum);
|
|
51083
51285
|
const baseNodes = buildBaseNodes(datums);
|
|
51084
51286
|
if (baseNodes.length === 0) {
|
|
51085
51287
|
throw new SelectorSynthesisError("No shared identifiers available across provided data instances");
|
|
51086
51288
|
}
|
|
51289
|
+
// Check base identifiers first
|
|
51290
|
+
for (const node of baseNodes) {
|
|
51291
|
+
if (matchesTargets(node, evaluatedExamples, normalizer)) {
|
|
51292
|
+
return node;
|
|
51293
|
+
}
|
|
51294
|
+
}
|
|
51295
|
+
// Check semantic join candidates early - these are type-aware joins like Node.key
|
|
51296
|
+
// that are likely to be what the user wants when selecting relation ranges/domains
|
|
51297
|
+
const semanticJoins = buildSemanticJoinCandidates(datums);
|
|
51298
|
+
for (const node of semanticJoins) {
|
|
51299
|
+
if (matchesTargets(node, evaluatedExamples, normalizer)) {
|
|
51300
|
+
return node;
|
|
51301
|
+
}
|
|
51302
|
+
}
|
|
51087
51303
|
const queue = [];
|
|
51088
51304
|
const queued = new Set();
|
|
51089
51305
|
const visited = new Set();
|
|
@@ -51095,6 +51311,10 @@ function synthesizeExpressionNode(examples, normalizer, maxDepth = 3) {
|
|
|
51095
51311
|
queue.push({ node, depth });
|
|
51096
51312
|
queued.add(key);
|
|
51097
51313
|
};
|
|
51314
|
+
// Mark semantic joins as already visited so we don't re-check them
|
|
51315
|
+
for (const node of semanticJoins) {
|
|
51316
|
+
visited.add(nodeToString(node));
|
|
51317
|
+
}
|
|
51098
51318
|
baseNodes.forEach((node) => enqueue(node, 0));
|
|
51099
51319
|
while (queue.length > 0) {
|
|
51100
51320
|
const current = queue.shift();
|
|
@@ -51104,24 +51324,75 @@ function synthesizeExpressionNode(examples, normalizer, maxDepth = 3) {
|
|
|
51104
51324
|
continue;
|
|
51105
51325
|
}
|
|
51106
51326
|
visited.add(key);
|
|
51107
|
-
if (matchesTargets(current.node,
|
|
51327
|
+
if (matchesTargets(current.node, evaluatedExamples, normalizer)) {
|
|
51108
51328
|
return current.node;
|
|
51109
51329
|
}
|
|
51110
51330
|
if (current.depth >= maxDepth) {
|
|
51111
51331
|
continue;
|
|
51112
51332
|
}
|
|
51113
|
-
// Unary expansions
|
|
51333
|
+
// Unary relational expansions
|
|
51114
51334
|
enqueue({ kind: "closure", child: current.node }, current.depth + 1);
|
|
51335
|
+
enqueue({ kind: "reflexive-closure", child: current.node }, current.depth + 1);
|
|
51336
|
+
enqueue({ kind: "transpose", child: current.node }, current.depth + 1);
|
|
51115
51337
|
for (const other of combinationPool) {
|
|
51116
51338
|
const leftKey = nodeToString(current.node);
|
|
51117
51339
|
const rightKey = nodeToString(other);
|
|
51340
|
+
// Set operations (commutative, so canonicalize order)
|
|
51118
51341
|
const [unionLeft, unionRight] = leftKey < rightKey ? [current.node, other] : [other, current.node];
|
|
51119
51342
|
enqueue({ kind: "union", left: unionLeft, right: unionRight }, current.depth + 1);
|
|
51120
51343
|
const [interLeft, interRight] = leftKey < rightKey ? [current.node, other] : [other, current.node];
|
|
51121
51344
|
enqueue({ kind: "intersection", left: interLeft, right: interRight }, current.depth + 1);
|
|
51345
|
+
// Difference is not commutative
|
|
51346
|
+
enqueue({ kind: "difference", left: current.node, right: other }, current.depth + 1);
|
|
51347
|
+
if (leftKey !== rightKey) {
|
|
51348
|
+
enqueue({ kind: "difference", left: other, right: current.node }, current.depth + 1);
|
|
51349
|
+
}
|
|
51350
|
+
// Join is not commutative
|
|
51122
51351
|
enqueue({ kind: "join", left: current.node, right: other }, current.depth + 1);
|
|
51123
51352
|
enqueue({ kind: "join", left: other, right: current.node }, current.depth + 1);
|
|
51124
51353
|
}
|
|
51354
|
+
// Generate set comprehensions: {v: domain | body}
|
|
51355
|
+
// For each type, try comprehensions with simple membership conditions
|
|
51356
|
+
// This allows synthesizing expressions like {n: Node | n.key in SomeSet}
|
|
51357
|
+
if (current.depth + 2 <= maxDepth) {
|
|
51358
|
+
for (const domainNode of combinationPool) {
|
|
51359
|
+
// Only use types as comprehension domains
|
|
51360
|
+
if (domainNode.kind !== "identifier")
|
|
51361
|
+
continue;
|
|
51362
|
+
const domainName = domainNode.name;
|
|
51363
|
+
const classification = classifyIdentifier(domainName, datums);
|
|
51364
|
+
if (classification !== "type")
|
|
51365
|
+
continue;
|
|
51366
|
+
const varName = "v"; // Use a simple variable name
|
|
51367
|
+
// Try: {v: Domain | v in current.node}
|
|
51368
|
+
const varNode = { kind: "identifier", name: varName };
|
|
51369
|
+
enqueue({
|
|
51370
|
+
kind: "comprehension",
|
|
51371
|
+
varName,
|
|
51372
|
+
domain: domainNode,
|
|
51373
|
+
body: { kind: "in", left: varNode, right: current.node },
|
|
51374
|
+
}, current.depth + 2);
|
|
51375
|
+
// Try: {v: Domain | v.relation in SomeSet} for relations
|
|
51376
|
+
for (const relNode of combinationPool) {
|
|
51377
|
+
if (relNode.kind !== "identifier")
|
|
51378
|
+
continue;
|
|
51379
|
+
if (classifyIdentifier(relNode.name, datums) !== "relation")
|
|
51380
|
+
continue;
|
|
51381
|
+
const joinExpr = {
|
|
51382
|
+
kind: "join",
|
|
51383
|
+
left: varNode,
|
|
51384
|
+
right: relNode,
|
|
51385
|
+
};
|
|
51386
|
+
// {v: Domain | v.relation in current.node}
|
|
51387
|
+
enqueue({
|
|
51388
|
+
kind: "comprehension",
|
|
51389
|
+
varName,
|
|
51390
|
+
domain: domainNode,
|
|
51391
|
+
body: { kind: "in", left: joinExpr, right: current.node },
|
|
51392
|
+
}, current.depth + 2);
|
|
51393
|
+
}
|
|
51394
|
+
}
|
|
51395
|
+
}
|
|
51125
51396
|
}
|
|
51126
51397
|
throw new SelectorSynthesisError("Unable to synthesize an expression matching all examples");
|
|
51127
51398
|
}
|
|
@@ -51141,27 +51412,67 @@ function synthesizeBinaryRelation(examples, maxDepth = 3) {
|
|
|
51141
51412
|
const node = synthesizeExpressionNode(synthesisExamples, normalizeBinaryResult, maxDepth);
|
|
51142
51413
|
return nodeToString(node);
|
|
51143
51414
|
}
|
|
51144
|
-
function buildWhyNode(node,
|
|
51145
|
-
const result = evaluateExpression(node,
|
|
51415
|
+
function buildWhyNode(node, evaluator, normalizer) {
|
|
51416
|
+
const result = evaluateExpression(node, evaluator, normalizer);
|
|
51146
51417
|
const base = {
|
|
51147
51418
|
kind: node.kind,
|
|
51148
51419
|
expression: nodeToString(node),
|
|
51149
51420
|
result,
|
|
51150
51421
|
};
|
|
51151
51422
|
switch (node.kind) {
|
|
51423
|
+
// Leaves
|
|
51152
51424
|
case "identifier":
|
|
51153
51425
|
return base;
|
|
51426
|
+
// Unary operators
|
|
51154
51427
|
case "closure":
|
|
51155
|
-
|
|
51428
|
+
case "reflexive-closure":
|
|
51429
|
+
case "transpose":
|
|
51430
|
+
case "not":
|
|
51431
|
+
return { ...base, children: [buildWhyNode(node.child, evaluator, normalizer)] };
|
|
51432
|
+
// Binary operators
|
|
51156
51433
|
case "join":
|
|
51157
51434
|
case "union":
|
|
51158
51435
|
case "intersection":
|
|
51159
51436
|
case "difference":
|
|
51437
|
+
case "and":
|
|
51438
|
+
case "or":
|
|
51439
|
+
case "implies":
|
|
51440
|
+
case "iff":
|
|
51441
|
+
case "in":
|
|
51442
|
+
case "eq":
|
|
51443
|
+
case "neq":
|
|
51444
|
+
case "lt":
|
|
51445
|
+
case "gt":
|
|
51446
|
+
case "lte":
|
|
51447
|
+
case "gte":
|
|
51448
|
+
return {
|
|
51449
|
+
...base,
|
|
51450
|
+
children: [
|
|
51451
|
+
buildWhyNode(node.left, evaluator, normalizer),
|
|
51452
|
+
buildWhyNode(node.right, evaluator, normalizer),
|
|
51453
|
+
],
|
|
51454
|
+
};
|
|
51455
|
+
// Quantified and comprehension expressions
|
|
51456
|
+
case "all":
|
|
51457
|
+
case "some":
|
|
51458
|
+
case "no":
|
|
51459
|
+
case "one":
|
|
51460
|
+
case "lone":
|
|
51461
|
+
case "comprehension":
|
|
51462
|
+
return {
|
|
51463
|
+
...base,
|
|
51464
|
+
children: [
|
|
51465
|
+
buildWhyNode(node.domain, evaluator, normalizer),
|
|
51466
|
+
buildWhyNode(node.body, evaluator, normalizer),
|
|
51467
|
+
],
|
|
51468
|
+
};
|
|
51469
|
+
// Box join
|
|
51470
|
+
case "box-join":
|
|
51160
51471
|
return {
|
|
51161
51472
|
...base,
|
|
51162
51473
|
children: [
|
|
51163
|
-
buildWhyNode(node.
|
|
51164
|
-
buildWhyNode(
|
|
51474
|
+
buildWhyNode(node.base, evaluator, normalizer),
|
|
51475
|
+
...node.args.map((arg) => buildWhyNode(arg, evaluator, normalizer)),
|
|
51165
51476
|
],
|
|
51166
51477
|
};
|
|
51167
51478
|
default:
|
|
@@ -51175,12 +51486,16 @@ function synthesizeSelectorWithWhy(examples, maxDepth = 3) {
|
|
|
51175
51486
|
}));
|
|
51176
51487
|
const node = synthesizeExpressionNode(synthesisExamples, normalizeUnaryResult, maxDepth);
|
|
51177
51488
|
const expression = nodeToString(node);
|
|
51178
|
-
const
|
|
51179
|
-
|
|
51180
|
-
|
|
51181
|
-
|
|
51182
|
-
|
|
51183
|
-
|
|
51489
|
+
const evaluatorCache = new Map();
|
|
51490
|
+
const explanationExamples = synthesisExamples.map((example) => {
|
|
51491
|
+
const evaluator = getOrCreateEvaluator(example.datum, evaluatorCache);
|
|
51492
|
+
return {
|
|
51493
|
+
datum: example.datum,
|
|
51494
|
+
target: example.target,
|
|
51495
|
+
result: evaluateExpression(node, evaluator, normalizeUnaryResult),
|
|
51496
|
+
why: buildWhyNode(node, evaluator, normalizeUnaryResult),
|
|
51497
|
+
};
|
|
51498
|
+
});
|
|
51184
51499
|
return { expression, examples: explanationExamples };
|
|
51185
51500
|
}
|
|
51186
51501
|
function synthesizeBinaryRelationWithWhy(examples, maxDepth = 3) {
|
|
@@ -51190,12 +51505,16 @@ function synthesizeBinaryRelationWithWhy(examples, maxDepth = 3) {
|
|
|
51190
51505
|
});
|
|
51191
51506
|
const node = synthesizeExpressionNode(synthesisExamples, normalizeBinaryResult, maxDepth);
|
|
51192
51507
|
const expression = nodeToString(node);
|
|
51193
|
-
const
|
|
51194
|
-
|
|
51195
|
-
|
|
51196
|
-
|
|
51197
|
-
|
|
51198
|
-
|
|
51508
|
+
const evaluatorCache = new Map();
|
|
51509
|
+
const explanationExamples = synthesisExamples.map((example) => {
|
|
51510
|
+
const evaluator = getOrCreateEvaluator(example.datum, evaluatorCache);
|
|
51511
|
+
return {
|
|
51512
|
+
datum: example.datum,
|
|
51513
|
+
target: example.target,
|
|
51514
|
+
result: evaluateExpression(node, evaluator, normalizeBinaryResult),
|
|
51515
|
+
why: buildWhyNode(node, evaluator, normalizeBinaryResult),
|
|
51516
|
+
};
|
|
51517
|
+
});
|
|
51199
51518
|
return { expression, examples: explanationExamples };
|
|
51200
51519
|
}
|
|
51201
51520
|
|
package/package.json
CHANGED