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
- if (node.kind === "identifier" || node.kind === "closure") {
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
- if (typeof value !== "string") {
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
- if (typeof first !== "string" || typeof second !== "string") {
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(`${first}\u0000${second}`);
51089
+ ids.add(`${firstStr}\u0000${secondStr}`);
51022
51090
  }
51023
51091
  return ids;
51024
51092
  }
51025
- function evaluateExpression(node, datum, normalizer) {
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
- return Array.from(baseNames).map((name) => ({ kind: "identifier", name }));
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.datum, normalizer);
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 datums = examples.map((example) => example.datum);
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, examples, normalizer)) {
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, datum, normalizer) {
51145
- const result = evaluateExpression(node, datum, normalizer);
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
- return { ...base, children: [buildWhyNode(node.child, datum, normalizer)] };
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.left, datum, normalizer),
51164
- buildWhyNode(node.right, datum, normalizer),
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 explanationExamples = synthesisExamples.map((example) => ({
51179
- datum: example.datum,
51180
- target: example.target,
51181
- result: evaluateExpression(node, example.datum, normalizeUnaryResult),
51182
- why: buildWhyNode(node, example.datum, normalizeUnaryResult),
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 explanationExamples = synthesisExamples.map((example) => ({
51194
- datum: example.datum,
51195
- target: example.target,
51196
- result: evaluateExpression(node, example.datum, normalizeBinaryResult),
51197
- why: buildWhyNode(node, example.datum, normalizeBinaryResult),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "TypeScript evaluator for Forge expressions with browser-compatible UMD bundle",
5
5
  "main": "dist/simple-graph-query.bundle.js",
6
6
  "types": "dist/index.d.ts",