simple-graph-query 2.7.2 → 2.8.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.
@@ -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
  /**
@@ -48987,6 +48987,46 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48987
48987
  varNames.push(varName);
48988
48988
  quantifiedSets.push(varQuantifiedSets[varName]);
48989
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
+ }
48990
49030
  // Try to optimize numeric comparisons
48991
49031
  let product;
48992
49032
  let useOptimizedPath = false;
@@ -49133,7 +49173,8 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49133
49173
  return value;
49134
49174
  }
49135
49175
  }
49136
- // 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.
49137
49178
  }
49138
49179
  // TODO: fix this!
49139
49180
  const childrenResults = this.visitChildren(ctx);
@@ -49461,25 +49502,21 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49461
49502
  break;
49462
49503
  case "in":
49463
49504
  case "ni": {
49464
- let membershipResult;
49465
- // this should be true if the left value is equal to the right value,
49466
- // or a subset of it
49467
- if (isTupleArray(leftChildValue) && isTupleArray(rightChildValue)) {
49468
- if (areTupleArraysEqual(leftChildValue, rightChildValue)) {
49469
- membershipResult = true;
49470
- }
49471
- else {
49472
- // check if left is subset of right
49473
- membershipResult = isTupleArraySubset(leftChildValue, rightChildValue);
49474
- }
49475
- }
49476
- else if (isTupleArray(rightChildValue)) {
49477
- membershipResult = rightChildValue.some((tuple) => tuple.length === 1 && tuple[0] === leftChildValue);
49478
- }
49479
- else {
49480
- // left is a tuple array but right is a single value, so false
49481
- membershipResult = false;
49482
- }
49505
+ // In Alloy/Forge everything is a relation, and a scalar is just a
49506
+ // singleton set. Lift any scalar operand to a singleton tuple so `in`
49507
+ // is uniformly subset:
49508
+ // a in b ({a} {b}) -> equality when both are scalars
49509
+ // a in {..} ({a} ⊆ set) -> membership
49510
+ // {..} in b (set ⊆ {b}) -> subset of a singleton
49511
+ // isTupleArraySubset already returns true for equal sets and for an
49512
+ // empty left-hand set, so a single subset check covers every case.
49513
+ const leftSet = isSingleValue(leftChildValue)
49514
+ ? [[leftChildValue]]
49515
+ : leftChildValue;
49516
+ const rightSet = isSingleValue(rightChildValue)
49517
+ ? [[rightChildValue]]
49518
+ : rightChildValue;
49519
+ const membershipResult = isTupleArraySubset(leftSet, rightSet);
49483
49520
  results = ctx.compareOp()?.text === "ni" ? !membershipResult : membershipResult;
49484
49521
  break;
49485
49522
  }
@@ -50367,7 +50404,7 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50367
50404
  result = arg1 * arg2;
50368
50405
  break;
50369
50406
  case "divide":
50370
- result = Math.floor(arg1 / arg2); // Integer division
50407
+ result = arg1 / arg2; // Real (floating-point) division
50371
50408
  break;
50372
50409
  case "remainder":
50373
50410
  result = arg1 % arg2;
@@ -50378,17 +50415,17 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50378
50415
  return result;
50379
50416
  }
50380
50417
  evaluateUnaryOperation(operation, args) {
50381
- if (!isSingleValue(args) || !isNumber(args)) {
50418
+ const v = extractNumber(args);
50419
+ if (v === undefined) {
50382
50420
  throw new Error(`Expected 1 argument for ${operation} that evaluates to a number.`);
50383
50421
  }
50384
- let v = args;
50385
50422
  // Possible unary operations:
50386
- //abs[]: returns the absolute value of value
50387
- //sign[]: returns 1 if value is > 0, 0 if value is 0, and -1 if value is < 0
50423
+ //abs[]: returns the absolute value of value
50424
+ //sign[]: returns 1 if value is > 0, 0 if value is 0, and -1 if value is < 0
50425
+ //floor[]: rounds value down to the nearest integer
50426
+ //ceil[]: rounds value up to the nearest integer
50388
50427
  if (operation === "abs") {
50389
- let res = Math.abs(v);
50390
- // Now adjust to the bitwidth
50391
- return res;
50428
+ return Math.abs(v);
50392
50429
  }
50393
50430
  else if (operation === "sign") {
50394
50431
  if (v > 0) {
@@ -50401,6 +50438,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50401
50438
  return 0;
50402
50439
  }
50403
50440
  }
50441
+ else if (operation === "floor") {
50442
+ return Math.floor(v);
50443
+ }
50444
+ else if (operation === "ceil") {
50445
+ return Math.ceil(v);
50446
+ }
50404
50447
  else {
50405
50448
  throw new Error(`Unsupported operation: ${operation}`);
50406
50449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.7.2",
3
+ "version": "2.8.2",
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",