simple-graph-query 2.7.0 → 2.8.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.
@@ -48590,7 +48590,7 @@ function bitwidthWraparound(value, bitwidth) {
48590
48590
  // this is a list of forge builtin functions we currently support; add to this
48591
48591
  // list as we support more
48592
48592
  const SUPPORTED_BINARY_BUILTINS = ["add", "subtract", "multiply", "divide", "remainder"];
48593
- const SUPPORTED_UNARY_BUILTINS = ["abs", "sign"];
48593
+ const SUPPORTED_UNARY_BUILTINS = ["abs", "sign", "floor", "ceil"];
48594
48594
  const SUPPORTED_SET_BUILTINS = ["min", "max"];
48595
48595
  exports.SUPPORTED_BUILTINS = SUPPORTED_BINARY_BUILTINS.concat(SUPPORTED_UNARY_BUILTINS, SUPPORTED_SET_BUILTINS);
48596
48596
  /**
@@ -48626,10 +48626,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48626
48626
  // Convert numeric and boolean strings to their actual types
48627
48627
  relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => this.isConvertibleToNumber(value) ? Number(value) : value));
48628
48628
  relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => this.isConvertibleToBoolean(value) ? this.convertToBoolean(value) : value));
48629
- this.relationCache.set(relation.name, relationAtoms);
48630
- // Build index for this relation: first element -> all tuples starting with that element
48629
+ // Relation NAMES are not unique — only the qualified id (`Sig<:label`) is.
48630
+ // e.g. `open util/ordering[A]` and `open util/ordering[B]` both expose a field
48631
+ // named `Next` (`A/Ord<:Next` and `B/Ord<:Next`). A bare-name reference must
48632
+ // resolve to the UNION of every relation sharing that name; keying by name and
48633
+ // overwriting silently erases all but the last. https://github.com/sidprasad/simple-graph-query/issues/55
48634
+ const existing = this.relationCache.get(relation.name);
48635
+ this.relationCache.set(relation.name, existing ? existing.concat(relationAtoms) : relationAtoms);
48636
+ }
48637
+ // Build first-element indexes from the merged (union'd) tuples, so an index
48638
+ // never reflects only a subset of a name's relations.
48639
+ for (const [name, tuples] of this.relationCache.entries()) {
48631
48640
  const relationIndex = new Map();
48632
- for (const tuple of relationAtoms) {
48641
+ for (const tuple of tuples) {
48633
48642
  if (tuple.length > 0) {
48634
48643
  const key = tuple[0];
48635
48644
  if (!relationIndex.has(key)) {
@@ -48638,7 +48647,7 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48638
48647
  relationIndex.get(key).push(tuple);
48639
48648
  }
48640
48649
  }
48641
- this.relationIndexCache.set(relation.name, relationIndex);
48650
+ this.relationIndexCache.set(name, relationIndex);
48642
48651
  }
48643
48652
  }
48644
48653
  //helper function
@@ -48978,6 +48987,46 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48978
48987
  varNames.push(varName);
48979
48988
  quantifiedSets.push(varQuantifiedSets[varName]);
48980
48989
  }
48990
+ // `sum <decls> | <intExpr>`: accumulate the numeric body over every
48991
+ // binding of the quantified variables. Unlike the boolean quantifiers
48992
+ // below, the body evaluates to a number, so this needs its own loop --
48993
+ // and it must not use the boolean numeric-comparison optimization, which
48994
+ // skips body evaluation entirely.
48995
+ if (ctx.quant().SUM_TOK()) {
48996
+ const sumCombinations = getCombinations(quantifiedSets);
48997
+ const sumEnv = { env: {}, type: "quantDecl" };
48998
+ this.environmentStack.push(sumEnv);
48999
+ let total = 0;
49000
+ for (const tuple of sumCombinations) {
49001
+ if (isDisjoint) {
49002
+ const seen = new Set();
49003
+ let tupleDisjoint = true;
49004
+ for (const val of tuple) {
49005
+ if (seen.has(val)) {
49006
+ tupleDisjoint = false;
49007
+ break;
49008
+ }
49009
+ seen.add(val);
49010
+ }
49011
+ if (!tupleDisjoint) {
49012
+ continue;
49013
+ }
49014
+ }
49015
+ for (let j = 0; j < varNames.length; j++) {
49016
+ sumEnv.env[varNames[j]] = tuple[j];
49017
+ }
49018
+ const bodyValue = this.visit(barExpr);
49019
+ const bodyNumber = extractNumber(bodyValue);
49020
+ if (bodyNumber === undefined) {
49021
+ this.environmentStack.pop();
49022
+ throw new Error("Expected the expression after the bar in a `sum` to evaluate to a number!");
49023
+ }
49024
+ total += bodyNumber;
49025
+ }
49026
+ this.environmentStack.pop();
49027
+ this.cacheResult(ctx, freeVarsKey, total);
49028
+ return total;
49029
+ }
48981
49030
  // Try to optimize numeric comparisons
48982
49031
  let product;
48983
49032
  let useOptimizedPath = false;
@@ -49124,7 +49173,8 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49124
49173
  return value;
49125
49174
  }
49126
49175
  }
49127
- // TODO: don't have support for SUM_TOK yet
49176
+ // NOTE: SUM_TOK is handled above (it returns a number, not a boolean),
49177
+ // before this boolean-quantifier result logic.
49128
49178
  }
49129
49179
  // TODO: fix this!
49130
49180
  const childrenResults = this.visitChildren(ctx);
@@ -50358,7 +50408,7 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50358
50408
  result = arg1 * arg2;
50359
50409
  break;
50360
50410
  case "divide":
50361
- result = Math.floor(arg1 / arg2); // Integer division
50411
+ result = arg1 / arg2; // Real (floating-point) division
50362
50412
  break;
50363
50413
  case "remainder":
50364
50414
  result = arg1 % arg2;
@@ -50369,17 +50419,17 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50369
50419
  return result;
50370
50420
  }
50371
50421
  evaluateUnaryOperation(operation, args) {
50372
- if (!isSingleValue(args) || !isNumber(args)) {
50422
+ const v = extractNumber(args);
50423
+ if (v === undefined) {
50373
50424
  throw new Error(`Expected 1 argument for ${operation} that evaluates to a number.`);
50374
50425
  }
50375
- let v = args;
50376
50426
  // Possible unary operations:
50377
- //abs[]: returns the absolute value of value
50378
- //sign[]: returns 1 if value is > 0, 0 if value is 0, and -1 if value is < 0
50427
+ //abs[]: returns the absolute value of value
50428
+ //sign[]: returns 1 if value is > 0, 0 if value is 0, and -1 if value is < 0
50429
+ //floor[]: rounds value down to the nearest integer
50430
+ //ceil[]: rounds value up to the nearest integer
50379
50431
  if (operation === "abs") {
50380
- let res = Math.abs(v);
50381
- // Now adjust to the bitwidth
50382
- return res;
50432
+ return Math.abs(v);
50383
50433
  }
50384
50434
  else if (operation === "sign") {
50385
50435
  if (v > 0) {
@@ -50392,6 +50442,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50392
50442
  return 0;
50393
50443
  }
50394
50444
  }
50445
+ else if (operation === "floor") {
50446
+ return Math.floor(v);
50447
+ }
50448
+ else if (operation === "ceil") {
50449
+ return Math.ceil(v);
50450
+ }
50395
50451
  else {
50396
50452
  throw new Error(`Unsupported operation: ${operation}`);
50397
50453
  }
@@ -50821,10 +50877,17 @@ const UNKNOWN = { kind: "unknown" };
50821
50877
  class SchemaInfo {
50822
50878
  constructor(schema) {
50823
50879
  this.typesById = new Map(schema.getTypes().map((t) => [t.id, t]));
50824
- this.relationsByName = new Map(schema.getRelations().map((r) => [r.name, r]));
50880
+ this.relationsByName = new Map();
50881
+ for (const r of schema.getRelations()) {
50882
+ const existing = this.relationsByName.get(r.name);
50883
+ if (existing)
50884
+ existing.push(r);
50885
+ else
50886
+ this.relationsByName.set(r.name, [r]);
50887
+ }
50825
50888
  }
50826
50889
  getType(id) { return this.typesById.get(id); }
50827
- getRelation(name) { return this.relationsByName.get(name); }
50890
+ getRelations(name) { return this.relationsByName.get(name) ?? []; }
50828
50891
  // A ⊆ B when B is in A's lineage. `IType.types` is the lineage in ascending
50829
50892
  // order, conventionally starting with the type itself.
50830
50893
  isSubtypeOf(a, b) {
@@ -50965,7 +51028,7 @@ class ForgeExprStaticAnalyzer extends AbstractParseTreeVisitor_1.AbstractParseTr
50965
51028
  if (!this.schema)
50966
51029
  return undefined;
50967
51030
  for (const name of names) {
50968
- if (this.schema.getType(name) || this.schema.getRelation(name)) {
51031
+ if (this.schema.getType(name) || this.schema.getRelations(name).length > 0) {
50969
51032
  return {
50970
51033
  kind: "ill-typed",
50971
51034
  reason: `cannot bind variable named '${name}': it is a reserved schema ` +
@@ -51746,11 +51809,26 @@ class ForgeExprStaticAnalyzer extends AbstractParseTreeVisitor_1.AbstractParseTr
51746
51809
  const t = this.schema.getType(id);
51747
51810
  if (t)
51748
51811
  return { kind: "typed", arity: 1, columnTypes: [t.id] };
51749
- const r = this.schema.getRelation(id);
51750
- if (r) {
51751
- const cols = r.types;
51812
+ const rels = this.schema.getRelations(id);
51813
+ if (rels.length === 1) {
51814
+ const cols = rels[0].types;
51752
51815
  return { kind: "typed", arity: cols.length, columnTypes: cols };
51753
51816
  }
51817
+ if (rels.length > 1) {
51818
+ // A bare name denotes the union of every relation sharing it. Different
51819
+ // arities have no single typing, so stay UNKNOWN. When arities agree,
51820
+ // keep the arity; keep per-column types only where every relation agrees
51821
+ // (otherwise omit, so downstream disjointness checks soundly skip rather
51822
+ // than fire on a widened column).
51823
+ const arity = rels[0].types.length;
51824
+ if (rels.every((r) => r.types.length === arity)) {
51825
+ const cols = rels[0].types.map((col, i) => rels.every((r) => r.types[i] === col) ? col : undefined);
51826
+ const allKnown = cols.every((c) => c !== undefined);
51827
+ return allKnown
51828
+ ? { kind: "typed", arity, columnTypes: cols }
51829
+ : { kind: "typed", arity };
51830
+ }
51831
+ }
51754
51832
  }
51755
51833
  return UNKNOWN;
51756
51834
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.7.0",
3
+ "version": "2.8.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",
@@ -33,10 +33,15 @@
33
33
  "prepublishOnly": "npm run build:full",
34
34
  "serve": "python3 -m http.server 8080",
35
35
  "test": "jest",
36
- "antlr4ts": "cd src/forge-antlr && antlr4ts -visitor ForgeLexer.g4 Forge.g4 && cd ../.."
36
+ "antlr4ts": "cd src/forge-antlr && antlr4ts -visitor ForgeLexer.g4 Forge.g4 && cd ../..",
37
+ "release:tag": "git diff-index --quiet HEAD -- || (echo 'Working tree is dirty, commit first' && exit 1) && git tag v$npm_package_version && git push origin v$npm_package_version"
37
38
  },
38
39
  "author": "Siddhartha Prasad",
39
40
  "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/sidprasad/simple-graph-query.git"
44
+ },
40
45
  "devDependencies": {
41
46
  "@types/jest": "^29.5.14",
42
47
  "antlr4ts-cli": "^0.5.0-alpha.4",