simple-graph-query 2.2.0 → 2.3.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.
@@ -27,6 +27,9 @@ export declare class ForgeExprEvaluator extends AbstractParseTreeVisitor<EvalRes
27
27
  private getLabelAsString;
28
28
  private getLabelAsBoolean;
29
29
  private getLabelAsNumber;
30
+ private isConvertibleToNumber;
31
+ private isConvertibleToBoolean;
32
+ private convertToBoolean;
30
33
  private dotJoin;
31
34
  private cacheResult;
32
35
  private getIden;
@@ -0,0 +1,39 @@
1
+ import { ExprContext } from "./forge-antlr/ForgeParser";
2
+ import { Tuple } from "./ForgeExprEvaluator";
3
+ /**
4
+ * Analyzes set comprehension constraints to detect simple numeric comparison patterns
5
+ * that can be optimized by generating only valid combinations instead of filtering.
6
+ */
7
+ export interface NumericConstraintPattern {
8
+ type: 'less_than' | 'greater_than' | 'less_equal' | 'greater_equal' | 'not_equal' | 'none';
9
+ leftVar: string;
10
+ rightVar: string;
11
+ }
12
+ /**
13
+ * Detects if a constraint expression is a simple numeric comparison between two variables.
14
+ * Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
15
+ *
16
+ * Note: Uses string matching on expression text which is simple but may miss expressions
17
+ * with extra whitespace or parentheses. This is intentionally conservative - we prefer
18
+ * to fall back to the standard approach rather than risk incorrect optimization.
19
+ *
20
+ * @param constraintExpr The constraint expression to analyze
21
+ * @param varNames The variable names in scope
22
+ * @returns Pattern info if detected, null otherwise
23
+ */
24
+ export declare function detectNumericComparisonPattern(constraintExpr: ExprContext, varNames: string[]): NumericConstraintPattern | null;
25
+ /**
26
+ * Checks if all values in the sets are numbers (or tuples containing single numbers).
27
+ * This is required for numeric comparison optimization to be applicable.
28
+ */
29
+ export declare function areAllNumericSets(sets: Tuple[][]): boolean;
30
+ /**
31
+ * Generates optimized combinations for numeric comparison constraints.
32
+ * Instead of generating all combinations and filtering, generates only valid ones.
33
+ *
34
+ * @param varNames Variable names in order
35
+ * @param quantifiedSets The sets each variable ranges over
36
+ * @param pattern The detected numeric comparison pattern
37
+ * @returns Only the tuples that satisfy the constraint
38
+ */
39
+ export declare function generateOptimizedNumericCombinations(varNames: string[], quantifiedSets: Tuple[][], pattern: NumericConstraintPattern): Tuple[];
@@ -48441,6 +48441,7 @@ exports.areTupleArraysEqual = areTupleArraysEqual;
48441
48441
  const AbstractParseTreeVisitor_1 = __webpack_require__(/*! antlr4ts/tree/AbstractParseTreeVisitor */ "./node_modules/antlr4ts/tree/AbstractParseTreeVisitor.js");
48442
48442
  const lodash_1 = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js");
48443
48443
  const ForgeExprFreeVariableFinder_1 = __webpack_require__(/*! ./ForgeExprFreeVariableFinder */ "./src/ForgeExprFreeVariableFinder.ts");
48444
+ const NumericConstraintOptimizer_1 = __webpack_require__(/*! ./NumericConstraintOptimizer */ "./src/NumericConstraintOptimizer.ts");
48444
48445
  ///// HELPER FUNCTIONS /////
48445
48446
  function isSingleValue(value) {
48446
48447
  return (typeof value === "string" ||
@@ -48618,28 +48619,11 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48618
48619
  this.relationCache = new Map();
48619
48620
  this.relationIndexCache = new Map();
48620
48621
  const relations = this.instanceData.getRelations();
48621
- const isConvertibleToNumber = (value) => {
48622
- return typeof value === "string" && !isNaN(Number(value));
48623
- };
48624
- const isConvertibleToBoolean = (value) => {
48625
- if (typeof value === "boolean")
48626
- return false; // already boolean
48627
- return value === "true" || value === "#t" || value === "false" || value === "#f";
48628
- };
48629
- const convertToBoolean = (value) => {
48630
- if (typeof value === "boolean")
48631
- return value;
48632
- if (value === "true" || value === "#t")
48633
- return true;
48634
- if (value === "false" || value === "#f")
48635
- return false;
48636
- throw new Error(`Cannot convert ${value} to boolean`);
48637
- };
48638
48622
  for (const relation of relations) {
48639
48623
  let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
48640
48624
  // Convert numeric and boolean strings to their actual types
48641
- relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
48642
- relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
48625
+ relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => this.isConvertibleToNumber(value) ? Number(value) : value));
48626
+ relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => this.isConvertibleToBoolean(value) ? this.convertToBoolean(value) : value));
48643
48627
  this.relationCache.set(relation.name, relationAtoms);
48644
48628
  // Build index for this relation: first element -> all tuples starting with that element
48645
48629
  const relationIndex = new Map();
@@ -48750,6 +48734,39 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48750
48734
  }
48751
48735
  return labelNum;
48752
48736
  }
48737
+ // Helper function to check if a value can be converted to a number
48738
+ isConvertibleToNumber(value) {
48739
+ if (typeof value === "number") {
48740
+ return true;
48741
+ }
48742
+ if (typeof value === "string") {
48743
+ return !isNaN(Number(value));
48744
+ }
48745
+ return false;
48746
+ }
48747
+ // Helper function to check if a value can be converted to a boolean
48748
+ isConvertibleToBoolean(value) {
48749
+ if (typeof value === "boolean") {
48750
+ return true;
48751
+ }
48752
+ if (typeof value === "string") {
48753
+ return (value === "true" || value === "#t" || value === "false" || value === "#f");
48754
+ }
48755
+ return false;
48756
+ }
48757
+ // Helper function to convert a value to boolean
48758
+ convertToBoolean(value) {
48759
+ if (typeof value === "boolean") {
48760
+ return value;
48761
+ }
48762
+ if (value === "true" || value === "#t") {
48763
+ return true;
48764
+ }
48765
+ if (value === "false" || value === "#f") {
48766
+ return false;
48767
+ }
48768
+ throw new Error(`Cannot convert ${value} to boolean`);
48769
+ }
48753
48770
  // Optimized dotJoin that can use pre-built relation indexes
48754
48771
  dotJoin(left, right, rightRelationName) {
48755
48772
  const leftExpr = isSingleValue(left) ? [[left]] : left;
@@ -48959,7 +48976,26 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48959
48976
  varNames.push(varName);
48960
48977
  quantifiedSets.push(varQuantifiedSets[varName]);
48961
48978
  }
48962
- const product = getCombinations(quantifiedSets);
48979
+ // Try to optimize numeric comparisons
48980
+ let product;
48981
+ let useOptimizedPath = false;
48982
+ if (!isDisjoint && varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
48983
+ // Try to detect and optimize numeric comparison patterns
48984
+ const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
48985
+ if (pattern && pattern.type !== 'none') {
48986
+ // Use optimized combination generation
48987
+ product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
48988
+ useOptimizedPath = true;
48989
+ }
48990
+ else {
48991
+ // Fall back to standard cartesian product
48992
+ product = getCombinations(quantifiedSets);
48993
+ }
48994
+ }
48995
+ else {
48996
+ // Fall back to standard cartesian product
48997
+ product = getCombinations(quantifiedSets);
48998
+ }
48963
48999
  const result = [];
48964
49000
  let foundTrue = false;
48965
49001
  let foundFalse = false;
@@ -48990,10 +49026,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48990
49026
  for (let j = 0; j < varNames.length; j++) {
48991
49027
  quantDeclEnv.env[varNames[j]] = tuple[j];
48992
49028
  }
48993
- // now, we want to evaluate the barExpr
48994
- const barExprValue = this.visit(barExpr);
48995
- if (!isBoolean(barExprValue)) {
48996
- throw new Error("Expected the expression after the bar to be a boolean!");
49029
+ // If we used the optimized path, we can skip constraint evaluation
49030
+ // since the combinations already satisfy the constraint
49031
+ let barExprValue;
49032
+ if (useOptimizedPath) {
49033
+ barExprValue = true;
49034
+ }
49035
+ else {
49036
+ // now, we want to evaluate the barExpr
49037
+ const evalResult = this.visit(barExpr);
49038
+ if (!isBoolean(evalResult)) {
49039
+ throw new Error("Expected the expression after the bar to be a boolean!");
49040
+ }
49041
+ barExprValue = evalResult;
48997
49042
  }
48998
49043
  if (barExprValue) {
48999
49044
  result.push(tuple);
@@ -49924,6 +49969,42 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49924
49969
  // }
49925
49970
  return value;
49926
49971
  }
49972
+ // Handle iden (identity relation)
49973
+ if (constant.IDEN_TOK() !== undefined) {
49974
+ // The identity relation contains tuples (x, x) for every atom x in the universe
49975
+ const atoms = this.instanceData.getAtoms();
49976
+ const idenRelation = [];
49977
+ for (const atom of atoms) {
49978
+ let atomValue = atom.id;
49979
+ // Convert numeric and boolean strings to their actual types
49980
+ if (this.isConvertibleToNumber(atomValue)) {
49981
+ atomValue = Number(atomValue);
49982
+ }
49983
+ else if (this.isConvertibleToBoolean(atomValue)) {
49984
+ atomValue = this.convertToBoolean(atomValue);
49985
+ }
49986
+ idenRelation.push([atomValue, atomValue]);
49987
+ }
49988
+ return idenRelation;
49989
+ }
49990
+ // Handle univ (universal relation - all atoms)
49991
+ if (constant.UNIV_TOK() !== undefined) {
49992
+ // The universal relation contains all atoms as unary tuples
49993
+ const atoms = this.instanceData.getAtoms();
49994
+ const univRelation = [];
49995
+ for (const atom of atoms) {
49996
+ let atomValue = atom.id;
49997
+ // Convert numeric and boolean strings to their actual types
49998
+ if (this.isConvertibleToNumber(atomValue)) {
49999
+ atomValue = Number(atomValue);
50000
+ }
50001
+ else if (this.isConvertibleToBoolean(atomValue)) {
50002
+ atomValue = this.convertToBoolean(atomValue);
50003
+ }
50004
+ univRelation.push([atomValue]);
50005
+ }
50006
+ return univRelation;
50007
+ }
49927
50008
  // Handle boolean constants
49928
50009
  if (constant.text === 'true') {
49929
50010
  return true;
@@ -49973,7 +50054,28 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49973
50054
  varNames.push(varName);
49974
50055
  quantifiedSets.push(varQuantifiedSets[varName]);
49975
50056
  }
49976
- const product = getCombinations(quantifiedSets);
50057
+ // Try to optimize numeric comparisons (same optimization as in quantifiers)
50058
+ // Note: Set comprehensions don't support the 'disj' keyword, so we don't check for it here.
50059
+ // Quantifiers do support 'disj', so they check !isDisjoint before optimizing.
50060
+ let product;
50061
+ let useOptimizedPath = false;
50062
+ if (varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
50063
+ // Try to detect and optimize numeric comparison patterns
50064
+ const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
50065
+ if (pattern && pattern.type !== 'none') {
50066
+ // Use optimized combination generation
50067
+ product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
50068
+ useOptimizedPath = true;
50069
+ }
50070
+ else {
50071
+ // Fall back to standard cartesian product
50072
+ product = getCombinations(quantifiedSets);
50073
+ }
50074
+ }
50075
+ else {
50076
+ // Fall back to standard cartesian product
50077
+ product = getCombinations(quantifiedSets);
50078
+ }
49977
50079
  const result = [];
49978
50080
  // Optimize: create environment once and reuse it
49979
50081
  const quantDeclEnv = {
@@ -49987,10 +50089,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49987
50089
  for (let j = 0; j < varNames.length; j++) {
49988
50090
  quantDeclEnv.env[varNames[j]] = tuple[j];
49989
50091
  }
49990
- // now, we want to evaluate the barExpr
49991
- const barExprValue = this.visit(barExpr);
49992
- if (!isBoolean(barExprValue)) {
49993
- throw new Error("Expected the expression after the bar to be a boolean value!");
50092
+ // If we used the optimized path, we can skip constraint evaluation
50093
+ // since the combinations already satisfy the constraint
50094
+ let barExprValue;
50095
+ if (useOptimizedPath) {
50096
+ barExprValue = true;
50097
+ }
50098
+ else {
50099
+ // now, we want to evaluate the barExpr
50100
+ const evalResult = this.visit(barExpr);
50101
+ if (!isBoolean(evalResult)) {
50102
+ throw new Error("Expected the expression after the bar to be a boolean value!");
50103
+ }
50104
+ barExprValue = evalResult;
49994
50105
  }
49995
50106
  if (barExprValue) {
49996
50107
  // will error if not boolean val, which we want
@@ -50125,46 +50236,15 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50125
50236
  }
50126
50237
  }
50127
50238
  }
50128
- // defining 3 helper functions here; not for use elsewhere
50129
- const isConvertibleToNumber = (value) => {
50130
- if (typeof value === "number") {
50131
- return true;
50132
- }
50133
- if (typeof value === "string") {
50134
- return !isNaN(Number(value));
50135
- }
50136
- return false;
50137
- };
50138
- const isConvertibleToBoolean = (value) => {
50139
- if (typeof value === "boolean") {
50140
- return true;
50141
- }
50142
- if (typeof value === "string") {
50143
- return (value === "true" || value === "#t" || value === "false" || value === "#f");
50144
- }
50145
- return false;
50146
- };
50147
- const convertToBoolean = (value) => {
50148
- if (typeof value === "boolean") {
50149
- return value;
50150
- }
50151
- if (value === "true" || value === "#t") {
50152
- return true;
50153
- }
50154
- if (value === "false" || value === "#f") {
50155
- return false;
50156
- }
50157
- throw new Error(`Cannot convert ${value} to boolean`);
50158
- };
50159
- // end of 3 helper functions
50239
+ // end of type search
50160
50240
  // check if it is a relation - use cache for faster lookups
50161
50241
  this.buildRelationCache();
50162
50242
  if (this.relationCache.has(identifier)) {
50163
50243
  return this.relationCache.get(identifier);
50164
50244
  }
50165
50245
  if (result !== undefined) {
50166
- result = result.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
50167
- result = result.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
50246
+ result = result.map((tuple) => tuple.map((value) => this.isConvertibleToNumber(value) ? Number(value) : value));
50247
+ result = result.map((tuple) => tuple.map((value) => this.isConvertibleToBoolean(value) ? this.convertToBoolean(value) : value));
50168
50248
  return result;
50169
50249
  }
50170
50250
  // return identifier;
@@ -50657,6 +50737,200 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
50657
50737
  exports.ForgeExprFreeVariableFinder = ForgeExprFreeVariableFinder;
50658
50738
 
50659
50739
 
50740
+ /***/ }),
50741
+
50742
+ /***/ "./src/NumericConstraintOptimizer.ts":
50743
+ /*!*******************************************!*\
50744
+ !*** ./src/NumericConstraintOptimizer.ts ***!
50745
+ \*******************************************/
50746
+ /***/ ((__unused_webpack_module, exports) => {
50747
+
50748
+ "use strict";
50749
+
50750
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
50751
+ exports.detectNumericComparisonPattern = detectNumericComparisonPattern;
50752
+ exports.areAllNumericSets = areAllNumericSets;
50753
+ exports.generateOptimizedNumericCombinations = generateOptimizedNumericCombinations;
50754
+ /**
50755
+ * Detects if a constraint expression is a simple numeric comparison between two variables.
50756
+ * Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
50757
+ *
50758
+ * Note: Uses string matching on expression text which is simple but may miss expressions
50759
+ * with extra whitespace or parentheses. This is intentionally conservative - we prefer
50760
+ * to fall back to the standard approach rather than risk incorrect optimization.
50761
+ *
50762
+ * @param constraintExpr The constraint expression to analyze
50763
+ * @param varNames The variable names in scope
50764
+ * @returns Pattern info if detected, null otherwise
50765
+ */
50766
+ function detectNumericComparisonPattern(constraintExpr, varNames) {
50767
+ // Parse the expression text to look for simple comparison patterns
50768
+ const exprText = constraintExpr.text;
50769
+ // Check if this is a simple binary comparison between two variables
50770
+ // Pattern: <varName1><compareOp><varName2> (no whitespace in text representation)
50771
+ // Note: The AST text property concatenates tokens without whitespace
50772
+ // Try to extract comparison operator and operands
50773
+ for (const leftVar of varNames) {
50774
+ for (const rightVar of varNames) {
50775
+ if (leftVar === rightVar)
50776
+ continue;
50777
+ // Check for different comparison operators (whitespace already removed in text)
50778
+ if (exprText === `${leftVar}<${rightVar}`) {
50779
+ return { type: 'less_than', leftVar, rightVar };
50780
+ }
50781
+ if (exprText === `${leftVar}>${rightVar}`) {
50782
+ return { type: 'greater_than', leftVar, rightVar };
50783
+ }
50784
+ if (exprText === `${leftVar}<=${rightVar}`) {
50785
+ return { type: 'less_equal', leftVar, rightVar };
50786
+ }
50787
+ if (exprText === `${leftVar}>=${rightVar}`) {
50788
+ return { type: 'greater_equal', leftVar, rightVar };
50789
+ }
50790
+ if (exprText === `${leftVar}!=${rightVar}`) {
50791
+ return { type: 'not_equal', leftVar, rightVar };
50792
+ }
50793
+ // Also check for negated equality: not a = b
50794
+ if (exprText === `not${leftVar}=${rightVar}`) {
50795
+ return { type: 'not_equal', leftVar, rightVar };
50796
+ }
50797
+ }
50798
+ }
50799
+ return null;
50800
+ }
50801
+ /**
50802
+ * Checks if all values in the sets are numbers (or tuples containing single numbers).
50803
+ * This is required for numeric comparison optimization to be applicable.
50804
+ */
50805
+ function areAllNumericSets(sets) {
50806
+ for (const set of sets) {
50807
+ for (const tuple of set) {
50808
+ if (tuple.length !== 1)
50809
+ return false;
50810
+ if (typeof tuple[0] !== 'number')
50811
+ return false;
50812
+ }
50813
+ }
50814
+ return true;
50815
+ }
50816
+ /**
50817
+ * Extracts numbers from tuples that contain single numbers.
50818
+ * Precondition: This should only be called after areAllNumericSets validation.
50819
+ */
50820
+ function extractNumbers(tuples) {
50821
+ return tuples.map(t => t[0]);
50822
+ }
50823
+ /**
50824
+ * Generates optimized combinations for numeric comparison constraints.
50825
+ * Instead of generating all combinations and filtering, generates only valid ones.
50826
+ *
50827
+ * @param varNames Variable names in order
50828
+ * @param quantifiedSets The sets each variable ranges over
50829
+ * @param pattern The detected numeric comparison pattern
50830
+ * @returns Only the tuples that satisfy the constraint
50831
+ */
50832
+ function generateOptimizedNumericCombinations(varNames, quantifiedSets, pattern) {
50833
+ // Map variable names to their indices and value sets
50834
+ const varIndexMap = new Map();
50835
+ varNames.forEach((name, idx) => {
50836
+ varIndexMap.set(name, idx);
50837
+ });
50838
+ const leftIdx = varIndexMap.get(pattern.leftVar);
50839
+ const rightIdx = varIndexMap.get(pattern.rightVar);
50840
+ if (leftIdx === undefined || rightIdx === undefined) {
50841
+ // Pattern variables don't match our variable set - this shouldn't happen
50842
+ // if detectNumericComparisonPattern is working correctly, but throw an error
50843
+ // to prevent incorrect results (caller should have validated pattern matches variables)
50844
+ throw new Error(`Internal error: Pattern variables ${pattern.leftVar}, ${pattern.rightVar} not found in variable list`);
50845
+ }
50846
+ // Extract numeric values
50847
+ const leftNumbers = extractNumbers(quantifiedSets[leftIdx]);
50848
+ const rightNumbers = extractNumbers(quantifiedSets[rightIdx]);
50849
+ // For other variables, we still need all combinations
50850
+ const otherVars = [];
50851
+ const otherSets = [];
50852
+ for (let i = 0; i < varNames.length; i++) {
50853
+ if (i !== leftIdx && i !== rightIdx) {
50854
+ otherVars.push(i);
50855
+ otherSets.push(extractNumbers(quantifiedSets[i]));
50856
+ }
50857
+ }
50858
+ const result = [];
50859
+ // Generate combinations based on the comparison type
50860
+ const generatePairs = (compareFunc) => {
50861
+ for (const leftVal of leftNumbers) {
50862
+ for (const rightVal of rightNumbers) {
50863
+ if (compareFunc(leftVal, rightVal)) {
50864
+ // This pair satisfies the constraint
50865
+ // Now combine with all other variables if any
50866
+ if (otherVars.length === 0) {
50867
+ // Simple case: only two variables
50868
+ const tuple = new Array(varNames.length);
50869
+ tuple[leftIdx] = leftVal;
50870
+ tuple[rightIdx] = rightVal;
50871
+ result.push(tuple);
50872
+ }
50873
+ else {
50874
+ // Need to generate cartesian product with other variables
50875
+ const otherCombos = cartesianProduct(otherSets);
50876
+ for (const otherCombo of otherCombos) {
50877
+ const tuple = new Array(varNames.length);
50878
+ tuple[leftIdx] = leftVal;
50879
+ tuple[rightIdx] = rightVal;
50880
+ for (let i = 0; i < otherVars.length; i++) {
50881
+ tuple[otherVars[i]] = otherCombo[i];
50882
+ }
50883
+ result.push(tuple);
50884
+ }
50885
+ }
50886
+ }
50887
+ }
50888
+ }
50889
+ };
50890
+ switch (pattern.type) {
50891
+ case 'less_than':
50892
+ generatePairs((a, b) => a < b);
50893
+ break;
50894
+ case 'greater_than':
50895
+ generatePairs((a, b) => a > b);
50896
+ break;
50897
+ case 'less_equal':
50898
+ generatePairs((a, b) => a <= b);
50899
+ break;
50900
+ case 'greater_equal':
50901
+ generatePairs((a, b) => a >= b);
50902
+ break;
50903
+ case 'not_equal':
50904
+ generatePairs((a, b) => a !== b);
50905
+ break;
50906
+ default:
50907
+ // Unknown pattern, return empty (will fall back to standard approach)
50908
+ return [];
50909
+ }
50910
+ return result;
50911
+ }
50912
+ /**
50913
+ * Helper to generate cartesian product of number arrays
50914
+ */
50915
+ function cartesianProduct(arrays) {
50916
+ if (arrays.length === 0)
50917
+ return [[]];
50918
+ if (arrays.some(arr => arr.length === 0))
50919
+ return [];
50920
+ let result = [[]];
50921
+ for (const arr of arrays) {
50922
+ const newResult = [];
50923
+ for (const existing of result) {
50924
+ for (const value of arr) {
50925
+ newResult.push([...existing, value]);
50926
+ }
50927
+ }
50928
+ result = newResult;
50929
+ }
50930
+ return result;
50931
+ }
50932
+
50933
+
50660
50934
  /***/ }),
50661
50935
 
50662
50936
  /***/ "./src/errorListener.ts":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
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",