simple-graph-query 2.5.0 → 2.5.2
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 {};
|
|
@@ -49080,6 +49080,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49080
49080
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
49081
49081
|
return value;
|
|
49082
49082
|
}
|
|
49083
|
+
if (multExpr.TWO_TOK() && result.length > 2) {
|
|
49084
|
+
this.environmentStack.pop();
|
|
49085
|
+
const value = false;
|
|
49086
|
+
this.cacheResult(ctx, freeVarsKey, value);
|
|
49087
|
+
return value;
|
|
49088
|
+
}
|
|
49083
49089
|
}
|
|
49084
49090
|
}
|
|
49085
49091
|
this.environmentStack.pop();
|
|
@@ -49111,7 +49117,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49111
49117
|
return value;
|
|
49112
49118
|
}
|
|
49113
49119
|
else if (multExpr.TWO_TOK()) {
|
|
49114
|
-
|
|
49120
|
+
const value = result.length === 2;
|
|
49121
|
+
this.cacheResult(ctx, freeVarsKey, value);
|
|
49122
|
+
return value;
|
|
49115
49123
|
}
|
|
49116
49124
|
}
|
|
49117
49125
|
// TODO: don't have support for SUM_TOK yet
|
|
@@ -49205,12 +49213,27 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49205
49213
|
if (!isBoolean(leftChildValue)) {
|
|
49206
49214
|
throw new Error("IMP operator expected 2 boolean operands!");
|
|
49207
49215
|
}
|
|
49216
|
+
const expr3Values = ctx.expr3() ?? [];
|
|
49217
|
+
const thenExpr = expr3Values[0];
|
|
49218
|
+
const elseExpr = expr3Values[1];
|
|
49219
|
+
if (ctx.ELSE_TOK()) {
|
|
49220
|
+
if (!thenExpr || !elseExpr) {
|
|
49221
|
+
throw new Error("Expected the ELSE operator to have 2 operands!");
|
|
49222
|
+
}
|
|
49223
|
+
const branchValue = this.visit(leftChildValue ? thenExpr : elseExpr);
|
|
49224
|
+
if (!isBoolean(branchValue)) {
|
|
49225
|
+
throw new Error("IMP operator expected 2 boolean operands!");
|
|
49226
|
+
}
|
|
49227
|
+
return branchValue;
|
|
49228
|
+
}
|
|
49208
49229
|
if (!leftChildValue) {
|
|
49209
49230
|
// short circuit if the antecedent is false
|
|
49210
49231
|
return true;
|
|
49211
49232
|
}
|
|
49212
|
-
|
|
49213
|
-
|
|
49233
|
+
if (!thenExpr) {
|
|
49234
|
+
throw new Error("Expected the IMP operator to have a consequent expression!");
|
|
49235
|
+
}
|
|
49236
|
+
const rightChildValue = this.visit(thenExpr);
|
|
49214
49237
|
if (!isBoolean(rightChildValue)) {
|
|
49215
49238
|
throw new Error("IMP operator expected 2 boolean operands!");
|
|
49216
49239
|
}
|
|
@@ -49426,37 +49449,31 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49426
49449
|
results = leftNum >= rightNum;
|
|
49427
49450
|
break;
|
|
49428
49451
|
case "in":
|
|
49452
|
+
case "ni": {
|
|
49453
|
+
let membershipResult;
|
|
49429
49454
|
// this should be true if the left value is equal to the right value,
|
|
49430
49455
|
// or a subset of it
|
|
49431
49456
|
if (isTupleArray(leftChildValue) && isTupleArray(rightChildValue)) {
|
|
49432
49457
|
if (areTupleArraysEqual(leftChildValue, rightChildValue)) {
|
|
49433
|
-
|
|
49458
|
+
membershipResult = true;
|
|
49434
49459
|
}
|
|
49435
49460
|
else {
|
|
49436
49461
|
// check if left is subset of right
|
|
49437
|
-
|
|
49462
|
+
membershipResult = isTupleArraySubset(leftChildValue, rightChildValue);
|
|
49438
49463
|
}
|
|
49439
49464
|
}
|
|
49440
49465
|
else if (isTupleArray(rightChildValue)) {
|
|
49441
|
-
|
|
49466
|
+
membershipResult = rightChildValue.some((tuple) => tuple.length === 1 && tuple[0] === leftChildValue);
|
|
49442
49467
|
}
|
|
49443
49468
|
else {
|
|
49444
49469
|
// left is a tuple array but right is a single value, so false
|
|
49445
|
-
|
|
49470
|
+
membershipResult = false;
|
|
49446
49471
|
}
|
|
49472
|
+
results = ctx.compareOp()?.text === "ni" ? !membershipResult : membershipResult;
|
|
49447
49473
|
break;
|
|
49474
|
+
}
|
|
49448
49475
|
case "is":
|
|
49449
49476
|
throw new Error("**NOT IMPLEMENTING FOR NOW** Type Check (`is`)");
|
|
49450
|
-
case "ni":
|
|
49451
|
-
results.push(["**UNIMPLEMENTED** Set Non-Membership (`ni`)"]);
|
|
49452
|
-
// TODO: implement this using leftValue and rightValue
|
|
49453
|
-
// for now, just returning over here. what we need to do instead
|
|
49454
|
-
// is to implement this, set the value of results to what we get
|
|
49455
|
-
// from this, and then call break (so that we can negate before
|
|
49456
|
-
// returning the final value, if required)
|
|
49457
|
-
return results;
|
|
49458
|
-
// removed by dead control flow
|
|
49459
|
-
{} // redundant, but it won't be once we implement the TODO above
|
|
49460
49477
|
default:
|
|
49461
49478
|
throw new Error(`Unexpected compare operator provided: ${ctx.compareOp()?.text}`);
|
|
49462
49479
|
}
|
|
@@ -49479,13 +49496,13 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49479
49496
|
const childrenResults = this.visit(ctx.expr8());
|
|
49480
49497
|
//console.log('childrenResults:', childrenResults);
|
|
49481
49498
|
if (ctx.SET_TOK()) {
|
|
49482
|
-
|
|
49499
|
+
return childrenResults;
|
|
49483
49500
|
}
|
|
49484
49501
|
if (ctx.ONE_TOK()) {
|
|
49485
49502
|
return isTupleArray(childrenResults) && childrenResults.length === 1;
|
|
49486
49503
|
}
|
|
49487
49504
|
if (ctx.TWO_TOK()) {
|
|
49488
|
-
|
|
49505
|
+
return isTupleArray(childrenResults) && childrenResults.length === 2;
|
|
49489
49506
|
}
|
|
49490
49507
|
if (ctx.NO_TOK()) {
|
|
49491
49508
|
return isTupleArray(childrenResults) && childrenResults.length === 0;
|
|
@@ -50955,10 +50972,20 @@ function nodeToString(node) {
|
|
|
50955
50972
|
switch (node.kind) {
|
|
50956
50973
|
case "identifier":
|
|
50957
50974
|
return node.name;
|
|
50975
|
+
// Unary relational operators
|
|
50958
50976
|
case "closure": {
|
|
50959
50977
|
const inner = nodeToString(node.child);
|
|
50960
50978
|
return `^${wrapForPrefix(inner)}`;
|
|
50961
50979
|
}
|
|
50980
|
+
case "reflexive-closure": {
|
|
50981
|
+
const inner = nodeToString(node.child);
|
|
50982
|
+
return `*${wrapForPrefix(inner)}`;
|
|
50983
|
+
}
|
|
50984
|
+
case "transpose": {
|
|
50985
|
+
const inner = nodeToString(node.child);
|
|
50986
|
+
return `~${wrapForPrefix(inner)}`;
|
|
50987
|
+
}
|
|
50988
|
+
// Binary relational operators
|
|
50962
50989
|
case "join": {
|
|
50963
50990
|
const left = wrapForJoin(node.left);
|
|
50964
50991
|
const right = wrapForJoin(node.right);
|
|
@@ -50970,10 +50997,59 @@ function nodeToString(node) {
|
|
|
50970
50997
|
return `(${nodeToString(node.left)} & ${nodeToString(node.right)})`;
|
|
50971
50998
|
case "difference":
|
|
50972
50999
|
return `(${nodeToString(node.left)} - ${nodeToString(node.right)})`;
|
|
51000
|
+
// Set comprehension
|
|
51001
|
+
case "comprehension":
|
|
51002
|
+
return `{${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)}}`;
|
|
51003
|
+
// Quantified expressions
|
|
51004
|
+
case "all":
|
|
51005
|
+
return `(all ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
51006
|
+
case "some":
|
|
51007
|
+
return `(some ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
51008
|
+
case "no":
|
|
51009
|
+
return `(no ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
51010
|
+
case "one":
|
|
51011
|
+
return `(one ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
51012
|
+
case "lone":
|
|
51013
|
+
return `(lone ${node.varName}: ${nodeToString(node.domain)} | ${nodeToString(node.body)})`;
|
|
51014
|
+
// Logical operators
|
|
51015
|
+
case "and":
|
|
51016
|
+
return `(${nodeToString(node.left)} and ${nodeToString(node.right)})`;
|
|
51017
|
+
case "or":
|
|
51018
|
+
return `(${nodeToString(node.left)} or ${nodeToString(node.right)})`;
|
|
51019
|
+
case "implies":
|
|
51020
|
+
return `(${nodeToString(node.left)} => ${nodeToString(node.right)})`;
|
|
51021
|
+
case "iff":
|
|
51022
|
+
return `(${nodeToString(node.left)} <=> ${nodeToString(node.right)})`;
|
|
51023
|
+
case "not":
|
|
51024
|
+
return `!${wrapForPrefix(nodeToString(node.child))}`;
|
|
51025
|
+
// Relational comparison
|
|
51026
|
+
case "in":
|
|
51027
|
+
return `(${nodeToString(node.left)} in ${nodeToString(node.right)})`;
|
|
51028
|
+
case "eq":
|
|
51029
|
+
return `(${nodeToString(node.left)} = ${nodeToString(node.right)})`;
|
|
51030
|
+
case "neq":
|
|
51031
|
+
return `(${nodeToString(node.left)} != ${nodeToString(node.right)})`;
|
|
51032
|
+
// Numeric comparison
|
|
51033
|
+
case "lt":
|
|
51034
|
+
return `(${nodeToString(node.left)} < ${nodeToString(node.right)})`;
|
|
51035
|
+
case "gt":
|
|
51036
|
+
return `(${nodeToString(node.left)} > ${nodeToString(node.right)})`;
|
|
51037
|
+
case "lte":
|
|
51038
|
+
return `(${nodeToString(node.left)} <= ${nodeToString(node.right)})`;
|
|
51039
|
+
case "gte":
|
|
51040
|
+
return `(${nodeToString(node.left)} >= ${nodeToString(node.right)})`;
|
|
51041
|
+
// Box join
|
|
51042
|
+
case "box-join":
|
|
51043
|
+
return `${nodeToString(node.base)}[${node.args.map(nodeToString).join(", ")}]`;
|
|
50973
51044
|
}
|
|
50974
51045
|
}
|
|
50975
51046
|
function wrapForJoin(node) {
|
|
50976
|
-
|
|
51047
|
+
// Identifiers and prefix unary operators don't need wrapping
|
|
51048
|
+
if (node.kind === "identifier" ||
|
|
51049
|
+
node.kind === "closure" ||
|
|
51050
|
+
node.kind === "reflexive-closure" ||
|
|
51051
|
+
node.kind === "transpose" ||
|
|
51052
|
+
node.kind === "box-join") {
|
|
50977
51053
|
return nodeToString(node);
|
|
50978
51054
|
}
|
|
50979
51055
|
return `(${nodeToString(node)})`;
|
|
@@ -50997,10 +51073,16 @@ function normalizeUnaryResult(result) {
|
|
|
50997
51073
|
return null;
|
|
50998
51074
|
}
|
|
50999
51075
|
const value = tuple[0];
|
|
51000
|
-
|
|
51076
|
+
// Accept strings or numbers (integers are often returned as numbers)
|
|
51077
|
+
if (typeof value === "string") {
|
|
51078
|
+
ids.add(value);
|
|
51079
|
+
}
|
|
51080
|
+
else if (typeof value === "number") {
|
|
51081
|
+
ids.add(String(value));
|
|
51082
|
+
}
|
|
51083
|
+
else {
|
|
51001
51084
|
return null;
|
|
51002
51085
|
}
|
|
51003
|
-
ids.add(value);
|
|
51004
51086
|
}
|
|
51005
51087
|
return ids;
|
|
51006
51088
|
}
|
|
@@ -51015,10 +51097,13 @@ function normalizeBinaryResult(result) {
|
|
|
51015
51097
|
return null;
|
|
51016
51098
|
}
|
|
51017
51099
|
const [first, second] = tuple;
|
|
51018
|
-
|
|
51100
|
+
// Accept strings or numbers for both elements
|
|
51101
|
+
const firstStr = typeof first === "string" ? first : typeof first === "number" ? String(first) : null;
|
|
51102
|
+
const secondStr = typeof second === "string" ? second : typeof second === "number" ? String(second) : null;
|
|
51103
|
+
if (firstStr === null || secondStr === null) {
|
|
51019
51104
|
return null;
|
|
51020
51105
|
}
|
|
51021
|
-
ids.add(`${
|
|
51106
|
+
ids.add(`${firstStr}\u0000${secondStr}`);
|
|
51022
51107
|
}
|
|
51023
51108
|
return ids;
|
|
51024
51109
|
}
|
|
@@ -51068,6 +51153,99 @@ function classifyIdentifier(name, datums) {
|
|
|
51068
51153
|
}
|
|
51069
51154
|
return "other";
|
|
51070
51155
|
}
|
|
51156
|
+
// Build semantically meaningful join candidates based on type-relation compatibility.
|
|
51157
|
+
// For a relation with types [T1, T2, ...], we generate joins like T1.relation
|
|
51158
|
+
// which projects the relation to its range. This helps synthesize expressions
|
|
51159
|
+
// like "Node.key" when selecting integer values that are keys of nodes.
|
|
51160
|
+
function buildSemanticJoinCandidates(datums) {
|
|
51161
|
+
const candidates = [];
|
|
51162
|
+
const seenExpressions = new Set();
|
|
51163
|
+
// Get relations and types that exist across all datums
|
|
51164
|
+
const sharedRelationNames = new Set();
|
|
51165
|
+
const sharedTypeIds = new Set();
|
|
51166
|
+
if (datums.length === 0)
|
|
51167
|
+
return candidates;
|
|
51168
|
+
// Initialize with first datum
|
|
51169
|
+
const firstDatum = datums[0];
|
|
51170
|
+
firstDatum.getRelations().forEach((r) => sharedRelationNames.add(r.name));
|
|
51171
|
+
firstDatum.getTypes().forEach((t) => sharedTypeIds.add(t.id));
|
|
51172
|
+
// Intersect with remaining datums
|
|
51173
|
+
for (let i = 1; i < datums.length; i++) {
|
|
51174
|
+
const datum = datums[i];
|
|
51175
|
+
const datumRelations = new Set(datum.getRelations().map((r) => r.name));
|
|
51176
|
+
const datumTypes = new Set(datum.getTypes().map((t) => t.id));
|
|
51177
|
+
for (const name of sharedRelationNames) {
|
|
51178
|
+
if (!datumRelations.has(name))
|
|
51179
|
+
sharedRelationNames.delete(name);
|
|
51180
|
+
}
|
|
51181
|
+
for (const id of sharedTypeIds) {
|
|
51182
|
+
if (!datumTypes.has(id))
|
|
51183
|
+
sharedTypeIds.delete(id);
|
|
51184
|
+
}
|
|
51185
|
+
}
|
|
51186
|
+
// For each shared relation, check if its domain type is also shared
|
|
51187
|
+
// and create Type.relation join candidates
|
|
51188
|
+
for (const relationName of sharedRelationNames) {
|
|
51189
|
+
// Get the relation's type signature from the first datum (should be consistent)
|
|
51190
|
+
const relation = firstDatum.getRelations().find((r) => r.name === relationName);
|
|
51191
|
+
if (!relation || relation.types.length < 2)
|
|
51192
|
+
continue;
|
|
51193
|
+
const domainType = relation.types[0];
|
|
51194
|
+
// Check if the domain type (or a compatible type) is available
|
|
51195
|
+
// We look for types in the hierarchy that could be the domain
|
|
51196
|
+
for (const typeId of sharedTypeIds) {
|
|
51197
|
+
const typeObj = firstDatum.getTypes().find((t) => t.id === typeId);
|
|
51198
|
+
if (!typeObj)
|
|
51199
|
+
continue;
|
|
51200
|
+
// Check if this type is compatible with the relation's domain
|
|
51201
|
+
// Either the type itself matches, or it's in the type hierarchy
|
|
51202
|
+
const isCompatible = typeId === domainType ||
|
|
51203
|
+
typeObj.types.includes(domainType) ||
|
|
51204
|
+
// Also check if the domain type is a subtype of this type
|
|
51205
|
+
firstDatum.getTypes().find((t) => t.id === domainType)?.types.includes(typeId);
|
|
51206
|
+
if (isCompatible) {
|
|
51207
|
+
const joinNode = {
|
|
51208
|
+
kind: "join",
|
|
51209
|
+
left: { kind: "identifier", name: typeId },
|
|
51210
|
+
right: { kind: "identifier", name: relationName },
|
|
51211
|
+
};
|
|
51212
|
+
const exprString = nodeToString(joinNode);
|
|
51213
|
+
if (!seenExpressions.has(exprString)) {
|
|
51214
|
+
seenExpressions.add(exprString);
|
|
51215
|
+
candidates.push(joinNode);
|
|
51216
|
+
}
|
|
51217
|
+
}
|
|
51218
|
+
}
|
|
51219
|
+
}
|
|
51220
|
+
// Also add relation.Type joins for inverse projections (getting domain values)
|
|
51221
|
+
for (const relationName of sharedRelationNames) {
|
|
51222
|
+
const relation = firstDatum.getRelations().find((r) => r.name === relationName);
|
|
51223
|
+
if (!relation || relation.types.length < 2)
|
|
51224
|
+
continue;
|
|
51225
|
+
const rangeType = relation.types[relation.types.length - 1];
|
|
51226
|
+
for (const typeId of sharedTypeIds) {
|
|
51227
|
+
const typeObj = firstDatum.getTypes().find((t) => t.id === typeId);
|
|
51228
|
+
if (!typeObj)
|
|
51229
|
+
continue;
|
|
51230
|
+
const isCompatible = typeId === rangeType ||
|
|
51231
|
+
typeObj.types.includes(rangeType) ||
|
|
51232
|
+
firstDatum.getTypes().find((t) => t.id === rangeType)?.types.includes(typeId);
|
|
51233
|
+
if (isCompatible) {
|
|
51234
|
+
const joinNode = {
|
|
51235
|
+
kind: "join",
|
|
51236
|
+
left: { kind: "identifier", name: relationName },
|
|
51237
|
+
right: { kind: "identifier", name: typeId },
|
|
51238
|
+
};
|
|
51239
|
+
const exprString = nodeToString(joinNode);
|
|
51240
|
+
if (!seenExpressions.has(exprString)) {
|
|
51241
|
+
seenExpressions.add(exprString);
|
|
51242
|
+
candidates.push(joinNode);
|
|
51243
|
+
}
|
|
51244
|
+
}
|
|
51245
|
+
}
|
|
51246
|
+
}
|
|
51247
|
+
return candidates;
|
|
51248
|
+
}
|
|
51071
51249
|
function buildBaseNodes(datums) {
|
|
51072
51250
|
const baseNames = intersectNames(datums);
|
|
51073
51251
|
// Always include standard top-level identifiers when present in the language
|
|
@@ -51125,11 +51303,20 @@ function synthesizeExpressionNode(examples, normalizer, maxDepth = 3) {
|
|
|
51125
51303
|
if (baseNodes.length === 0) {
|
|
51126
51304
|
throw new SelectorSynthesisError("No shared identifiers available across provided data instances");
|
|
51127
51305
|
}
|
|
51306
|
+
// Check base identifiers first
|
|
51128
51307
|
for (const node of baseNodes) {
|
|
51129
51308
|
if (matchesTargets(node, evaluatedExamples, normalizer)) {
|
|
51130
51309
|
return node;
|
|
51131
51310
|
}
|
|
51132
51311
|
}
|
|
51312
|
+
// Check semantic join candidates early - these are type-aware joins like Node.key
|
|
51313
|
+
// that are likely to be what the user wants when selecting relation ranges/domains
|
|
51314
|
+
const semanticJoins = buildSemanticJoinCandidates(datums);
|
|
51315
|
+
for (const node of semanticJoins) {
|
|
51316
|
+
if (matchesTargets(node, evaluatedExamples, normalizer)) {
|
|
51317
|
+
return node;
|
|
51318
|
+
}
|
|
51319
|
+
}
|
|
51133
51320
|
const queue = [];
|
|
51134
51321
|
const queued = new Set();
|
|
51135
51322
|
const visited = new Set();
|
|
@@ -51141,6 +51328,10 @@ function synthesizeExpressionNode(examples, normalizer, maxDepth = 3) {
|
|
|
51141
51328
|
queue.push({ node, depth });
|
|
51142
51329
|
queued.add(key);
|
|
51143
51330
|
};
|
|
51331
|
+
// Mark semantic joins as already visited so we don't re-check them
|
|
51332
|
+
for (const node of semanticJoins) {
|
|
51333
|
+
visited.add(nodeToString(node));
|
|
51334
|
+
}
|
|
51144
51335
|
baseNodes.forEach((node) => enqueue(node, 0));
|
|
51145
51336
|
while (queue.length > 0) {
|
|
51146
51337
|
const current = queue.shift();
|
|
@@ -51156,18 +51347,69 @@ function synthesizeExpressionNode(examples, normalizer, maxDepth = 3) {
|
|
|
51156
51347
|
if (current.depth >= maxDepth) {
|
|
51157
51348
|
continue;
|
|
51158
51349
|
}
|
|
51159
|
-
// Unary expansions
|
|
51350
|
+
// Unary relational expansions
|
|
51160
51351
|
enqueue({ kind: "closure", child: current.node }, current.depth + 1);
|
|
51352
|
+
enqueue({ kind: "reflexive-closure", child: current.node }, current.depth + 1);
|
|
51353
|
+
enqueue({ kind: "transpose", child: current.node }, current.depth + 1);
|
|
51161
51354
|
for (const other of combinationPool) {
|
|
51162
51355
|
const leftKey = nodeToString(current.node);
|
|
51163
51356
|
const rightKey = nodeToString(other);
|
|
51357
|
+
// Set operations (commutative, so canonicalize order)
|
|
51164
51358
|
const [unionLeft, unionRight] = leftKey < rightKey ? [current.node, other] : [other, current.node];
|
|
51165
51359
|
enqueue({ kind: "union", left: unionLeft, right: unionRight }, current.depth + 1);
|
|
51166
51360
|
const [interLeft, interRight] = leftKey < rightKey ? [current.node, other] : [other, current.node];
|
|
51167
51361
|
enqueue({ kind: "intersection", left: interLeft, right: interRight }, current.depth + 1);
|
|
51362
|
+
// Difference is not commutative
|
|
51363
|
+
enqueue({ kind: "difference", left: current.node, right: other }, current.depth + 1);
|
|
51364
|
+
if (leftKey !== rightKey) {
|
|
51365
|
+
enqueue({ kind: "difference", left: other, right: current.node }, current.depth + 1);
|
|
51366
|
+
}
|
|
51367
|
+
// Join is not commutative
|
|
51168
51368
|
enqueue({ kind: "join", left: current.node, right: other }, current.depth + 1);
|
|
51169
51369
|
enqueue({ kind: "join", left: other, right: current.node }, current.depth + 1);
|
|
51170
51370
|
}
|
|
51371
|
+
// Generate set comprehensions: {v: domain | body}
|
|
51372
|
+
// For each type, try comprehensions with simple membership conditions
|
|
51373
|
+
// This allows synthesizing expressions like {n: Node | n.key in SomeSet}
|
|
51374
|
+
if (current.depth + 2 <= maxDepth) {
|
|
51375
|
+
for (const domainNode of combinationPool) {
|
|
51376
|
+
// Only use types as comprehension domains
|
|
51377
|
+
if (domainNode.kind !== "identifier")
|
|
51378
|
+
continue;
|
|
51379
|
+
const domainName = domainNode.name;
|
|
51380
|
+
const classification = classifyIdentifier(domainName, datums);
|
|
51381
|
+
if (classification !== "type")
|
|
51382
|
+
continue;
|
|
51383
|
+
const varName = "v"; // Use a simple variable name
|
|
51384
|
+
// Try: {v: Domain | v in current.node}
|
|
51385
|
+
const varNode = { kind: "identifier", name: varName };
|
|
51386
|
+
enqueue({
|
|
51387
|
+
kind: "comprehension",
|
|
51388
|
+
varName,
|
|
51389
|
+
domain: domainNode,
|
|
51390
|
+
body: { kind: "in", left: varNode, right: current.node },
|
|
51391
|
+
}, current.depth + 2);
|
|
51392
|
+
// Try: {v: Domain | v.relation in SomeSet} for relations
|
|
51393
|
+
for (const relNode of combinationPool) {
|
|
51394
|
+
if (relNode.kind !== "identifier")
|
|
51395
|
+
continue;
|
|
51396
|
+
if (classifyIdentifier(relNode.name, datums) !== "relation")
|
|
51397
|
+
continue;
|
|
51398
|
+
const joinExpr = {
|
|
51399
|
+
kind: "join",
|
|
51400
|
+
left: varNode,
|
|
51401
|
+
right: relNode,
|
|
51402
|
+
};
|
|
51403
|
+
// {v: Domain | v.relation in current.node}
|
|
51404
|
+
enqueue({
|
|
51405
|
+
kind: "comprehension",
|
|
51406
|
+
varName,
|
|
51407
|
+
domain: domainNode,
|
|
51408
|
+
body: { kind: "in", left: joinExpr, right: current.node },
|
|
51409
|
+
}, current.depth + 2);
|
|
51410
|
+
}
|
|
51411
|
+
}
|
|
51412
|
+
}
|
|
51171
51413
|
}
|
|
51172
51414
|
throw new SelectorSynthesisError("Unable to synthesize an expression matching all examples");
|
|
51173
51415
|
}
|
|
@@ -51195,14 +51437,31 @@ function buildWhyNode(node, evaluator, normalizer) {
|
|
|
51195
51437
|
result,
|
|
51196
51438
|
};
|
|
51197
51439
|
switch (node.kind) {
|
|
51440
|
+
// Leaves
|
|
51198
51441
|
case "identifier":
|
|
51199
51442
|
return base;
|
|
51443
|
+
// Unary operators
|
|
51200
51444
|
case "closure":
|
|
51445
|
+
case "reflexive-closure":
|
|
51446
|
+
case "transpose":
|
|
51447
|
+
case "not":
|
|
51201
51448
|
return { ...base, children: [buildWhyNode(node.child, evaluator, normalizer)] };
|
|
51449
|
+
// Binary operators
|
|
51202
51450
|
case "join":
|
|
51203
51451
|
case "union":
|
|
51204
51452
|
case "intersection":
|
|
51205
51453
|
case "difference":
|
|
51454
|
+
case "and":
|
|
51455
|
+
case "or":
|
|
51456
|
+
case "implies":
|
|
51457
|
+
case "iff":
|
|
51458
|
+
case "in":
|
|
51459
|
+
case "eq":
|
|
51460
|
+
case "neq":
|
|
51461
|
+
case "lt":
|
|
51462
|
+
case "gt":
|
|
51463
|
+
case "lte":
|
|
51464
|
+
case "gte":
|
|
51206
51465
|
return {
|
|
51207
51466
|
...base,
|
|
51208
51467
|
children: [
|
|
@@ -51210,6 +51469,29 @@ function buildWhyNode(node, evaluator, normalizer) {
|
|
|
51210
51469
|
buildWhyNode(node.right, evaluator, normalizer),
|
|
51211
51470
|
],
|
|
51212
51471
|
};
|
|
51472
|
+
// Quantified and comprehension expressions
|
|
51473
|
+
case "all":
|
|
51474
|
+
case "some":
|
|
51475
|
+
case "no":
|
|
51476
|
+
case "one":
|
|
51477
|
+
case "lone":
|
|
51478
|
+
case "comprehension":
|
|
51479
|
+
return {
|
|
51480
|
+
...base,
|
|
51481
|
+
children: [
|
|
51482
|
+
buildWhyNode(node.domain, evaluator, normalizer),
|
|
51483
|
+
buildWhyNode(node.body, evaluator, normalizer),
|
|
51484
|
+
],
|
|
51485
|
+
};
|
|
51486
|
+
// Box join
|
|
51487
|
+
case "box-join":
|
|
51488
|
+
return {
|
|
51489
|
+
...base,
|
|
51490
|
+
children: [
|
|
51491
|
+
buildWhyNode(node.base, evaluator, normalizer),
|
|
51492
|
+
...node.args.map((arg) => buildWhyNode(arg, evaluator, normalizer)),
|
|
51493
|
+
],
|
|
51494
|
+
};
|
|
51213
51495
|
default:
|
|
51214
51496
|
return base;
|
|
51215
51497
|
}
|
package/package.json
CHANGED