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
|
-
|
|
48630
|
-
//
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
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 =
|
|
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
|
-
|
|
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[]:
|
|
50378
|
-
//sign[]:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
|
51750
|
-
if (
|
|
51751
|
-
const cols =
|
|
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.
|
|
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",
|