simple-graph-query 2.8.2 → 3.0.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.
- package/README.md +85 -0
- package/dist/ForgeExprEvaluator.d.ts +41 -0
- package/dist/ForgeExprStaticAnalyzer.d.ts +25 -1
- package/dist/forge-antlr/ForgeLexer.d.ts +1 -1
- package/dist/forge-antlr/ForgeParser.d.ts +4 -3
- package/dist/index.d.ts +21 -1
- package/dist/simple-graph-query.bundle.js +2 -63526
- package/dist/simple-graph-query.bundle.js.LICENSE.txt +11 -0
- package/package.json +3 -8
package/README.md
CHANGED
|
@@ -6,6 +6,91 @@ A TypeScript library for evaluating relational + some more expressions with a br
|
|
|
6
6
|
|
|
7
7
|
MIT
|
|
8
8
|
|
|
9
|
+
## Strings
|
|
10
|
+
|
|
11
|
+
Strings are written as double-quoted literals:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
{y : Color | @:y = "Black"}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
A literal always denotes a string, whatever the instance happens to contain. It
|
|
18
|
+
may hold any characters — spaces, punctuation, reserved keywords — and supports
|
|
19
|
+
the escapes `\"`, `\\`, `\n`, `\t`, `\r`, and `\0`; any other escaped character
|
|
20
|
+
stands for itself.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
"dark blue" "#FF0000" "2 of clubs" "set" "say \"hi\""
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
There is no implicit conversion: `"12" = 12` and `"true" = true` are both false.
|
|
27
|
+
Use `@num:` / `@bool:` to convert explicitly.
|
|
28
|
+
|
|
29
|
+
### Embedding in YAML
|
|
30
|
+
|
|
31
|
+
`@` is a reserved indicator in YAML, so an expression beginning with `@:` cannot
|
|
32
|
+
be a plain scalar — it has to be quoted whether or not it contains a string
|
|
33
|
+
literal. Single-quoted and block scalars pass `"` through untouched and are the
|
|
34
|
+
recommended styles:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
selector: '@:y = "Black"'
|
|
38
|
+
|
|
39
|
+
selector: |-
|
|
40
|
+
@:y = "Black"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A double-quoted YAML scalar also works, but requires escaping: `"@:y = \"Black\""`.
|
|
44
|
+
|
|
45
|
+
### Prior to 3.0
|
|
46
|
+
|
|
47
|
+
Any identifier that failed to resolve was silently reinterpreted as a string, so
|
|
48
|
+
`@:y = Black` worked only as long as nothing in the instance was named `Black`.
|
|
49
|
+
Adding a sig by that name silently changed what the query meant, labels
|
|
50
|
+
containing spaces or punctuation were inexpressible, and typos evaluated to
|
|
51
|
+
themselves instead of being reported. Bare names are no longer strings; quote
|
|
52
|
+
them.
|
|
53
|
+
|
|
54
|
+
## Unresolved names
|
|
55
|
+
|
|
56
|
+
A name that matches nothing in the instance evaluates to the **empty relation**
|
|
57
|
+
and raises a diagnostic. It is not an error:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const { value, diagnostics } = evaluator.evaluateExpressionWithDiagnostics("Playr");
|
|
61
|
+
// value -> []
|
|
62
|
+
// diagnostics -> [{
|
|
63
|
+
// kind: "unresolved-name",
|
|
64
|
+
// severity: "warning",
|
|
65
|
+
// name: "Playr",
|
|
66
|
+
// suggestion: "Player",
|
|
67
|
+
// message: "'Playr' does not name a type, relation, or atom in this instance; ..."
|
|
68
|
+
// }]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
An instance carries only *populated* types and relations, so a sig with no atoms
|
|
72
|
+
is absent from it entirely — and a sig can empty out between frames of one
|
|
73
|
+
trace. A missing name is therefore indistinguishable from a typo at evaluation
|
|
74
|
+
time, which is why this is a warning for the consumer to surface rather than an
|
|
75
|
+
error. `evaluateExpression` is unchanged and returns the value alone.
|
|
76
|
+
|
|
77
|
+
Note that empty means predicates pass vacuously: `no Playr` is `true`. This is
|
|
78
|
+
correct for an empty set, and the diagnostic is the only thing distinguishing it
|
|
79
|
+
from a real result — so consumers should show these warnings, not drop them.
|
|
80
|
+
|
|
81
|
+
### Static analysis
|
|
82
|
+
|
|
83
|
+
`analyzeForgeExpression(expr, schema)` reports the same names in
|
|
84
|
+
`unresolvedNames`, alongside (not instead of) its status. Given a *schema* it
|
|
85
|
+
is conclusive, because a schema declares every entity whether populated or not:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
analyzeForgeExpression("none & Playr", schema);
|
|
89
|
+
// { status: "empty", reason: "...", unresolvedNames: ["Playr"] }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Without a schema the field is omitted — there is nothing to check names against.
|
|
93
|
+
|
|
9
94
|
## Selector synthesis overview
|
|
10
95
|
|
|
11
96
|
The selector synthesizer infers a relational expression that returns exactly the atoms supplied in a set of training examples (pairs of `Set<IAtom>` and `IDataInstance`). The current implementation uses a bounded, enumerative search over a compact expression grammar (identifiers, unions/intersections/differences, joins, and transitive closure) that mirrors a lightweight Alloy fragment. The search works breadth-first by depth so that the first solution found is the simplest expression within the bound, and it only explores identifiers shared by every example plus Alloy built-ins like `univ` and `iden`.
|
|
@@ -7,6 +7,30 @@ export type Tuple = SingleValue[];
|
|
|
7
7
|
export type EvalResult = SingleValue | Tuple[];
|
|
8
8
|
export declare function areTupleArraysEqual(a: Tuple[], b: Tuple[]): boolean;
|
|
9
9
|
export declare const SUPPORTED_BUILTINS: string[];
|
|
10
|
+
/**
|
|
11
|
+
* A non-fatal condition noticed while evaluating an expression.
|
|
12
|
+
*
|
|
13
|
+
* Diagnostics never change the value of an expression — they are advisory, and
|
|
14
|
+
* consumers are expected to surface them to whoever wrote the query. Severity
|
|
15
|
+
* is always `"warning"`: the evaluator sees a single instance, not a schema, so
|
|
16
|
+
* it is not in a position to declare an unresolved name an error.
|
|
17
|
+
*/
|
|
18
|
+
export interface Diagnostic {
|
|
19
|
+
kind: "unresolved-name";
|
|
20
|
+
severity: "warning";
|
|
21
|
+
/** The name that could not be resolved. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Human-readable message suitable for showing to a query author. */
|
|
24
|
+
message: string;
|
|
25
|
+
/** Closest name in the instance, when one is close enough to be worth offering. */
|
|
26
|
+
suggestion?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Strip the surrounding double quotes from a STRING_TOK and resolve its escape
|
|
30
|
+
* sequences. The lexer rule is `'"' (~["\\] | '\\' .)* '"'`, so a backslash
|
|
31
|
+
* always consumes exactly one following character.
|
|
32
|
+
*/
|
|
33
|
+
export declare function unquoteStringLiteral(text: string): string;
|
|
10
34
|
/**
|
|
11
35
|
* A recursive evaluator for Forge expressions.
|
|
12
36
|
* This visitor walks the parse tree and prints the type of operation encountered.
|
|
@@ -19,7 +43,24 @@ export declare class ForgeExprEvaluator extends AbstractParseTreeVisitor<EvalRes
|
|
|
19
43
|
private instanceData;
|
|
20
44
|
private relationCache;
|
|
21
45
|
private relationIndexCache;
|
|
46
|
+
private diagnostics;
|
|
22
47
|
constructor(datum: IDataInstance);
|
|
48
|
+
/** Diagnostics recorded since the last `resetDiagnostics()`. */
|
|
49
|
+
getDiagnostics(): Diagnostic[];
|
|
50
|
+
/** Clear diagnostics. Callers that reuse an evaluator must call this per evaluation. */
|
|
51
|
+
resetDiagnostics(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Record a name that could not be resolved against this instance.
|
|
54
|
+
*
|
|
55
|
+
* This is deliberately a warning rather than an error. The instance format
|
|
56
|
+
* carries only populated types and relations, so a sig with no atoms in this
|
|
57
|
+
* instance is indistinguishable from a typo — and a sig can empty out between
|
|
58
|
+
* frames of the same trace. Failing hard would break working selectors at
|
|
59
|
+
* exactly the frames where a set happens to be empty.
|
|
60
|
+
*/
|
|
61
|
+
private reportUnresolvedName;
|
|
62
|
+
/** Closest name in this instance within a small edit distance, if any. */
|
|
63
|
+
private suggestName;
|
|
23
64
|
private buildRelationCache;
|
|
24
65
|
private updateFreeVariables;
|
|
25
66
|
private constructFreeVariableKey;
|
|
@@ -21,7 +21,19 @@ type Abstract = {
|
|
|
21
21
|
} | {
|
|
22
22
|
kind: "unknown";
|
|
23
23
|
};
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Names the expression references that the schema does not declare.
|
|
26
|
+
*
|
|
27
|
+
* This is reported alongside — not instead of — the status, because an
|
|
28
|
+
* expression can both name something undeclared and be provably empty. It is
|
|
29
|
+
* populated only when a schema is supplied: without one there is nothing to
|
|
30
|
+
* check a name against.
|
|
31
|
+
*
|
|
32
|
+
* Unlike the evaluator's `unresolved-name` diagnostic, this IS conclusive. A
|
|
33
|
+
* schema declares every entity whether or not it is populated, so a name absent
|
|
34
|
+
* from the schema cannot be explained away as a sig that happens to be empty.
|
|
35
|
+
*/
|
|
36
|
+
export type StaticAnalysis = ({
|
|
25
37
|
status: "unsat";
|
|
26
38
|
reason: string;
|
|
27
39
|
} | {
|
|
@@ -35,10 +47,17 @@ export type StaticAnalysis = {
|
|
|
35
47
|
reason: string;
|
|
36
48
|
} | {
|
|
37
49
|
status: "unknown";
|
|
50
|
+
}) & {
|
|
51
|
+
unresolvedNames?: string[];
|
|
38
52
|
};
|
|
39
53
|
export declare class ForgeExprStaticAnalyzer extends AbstractParseTreeVisitor<Abstract> implements ForgeVisitor<Abstract> {
|
|
40
54
|
private readonly schema?;
|
|
55
|
+
private readonly boundScopes;
|
|
56
|
+
private readonly unresolved;
|
|
41
57
|
constructor(schema?: IForgeSchema);
|
|
58
|
+
private isBound;
|
|
59
|
+
/** Run `body` with `names` treated as bound. */
|
|
60
|
+
private withBoundScope;
|
|
42
61
|
private collectNamesFromList;
|
|
43
62
|
private collectBoundNames;
|
|
44
63
|
private illTypedIfBindsReservedName;
|
|
@@ -67,5 +86,10 @@ export declare class ForgeExprStaticAnalyzer extends AbstractParseTreeVisitor<Ab
|
|
|
67
86
|
visitExpr17(ctx: Expr17Context): Abstract;
|
|
68
87
|
visitExpr18(ctx: Expr18Context): Abstract;
|
|
69
88
|
visitName(ctx: NameContext): Abstract;
|
|
89
|
+
/** Walk the whole tree, recording every name the schema does not declare. */
|
|
90
|
+
private collectUnresolvedNames;
|
|
91
|
+
/** The binder parts of a quantifier or set comprehension, if this node is one. */
|
|
92
|
+
private asBinder;
|
|
93
|
+
private recordIfUnresolved;
|
|
70
94
|
}
|
|
71
95
|
export {};
|
|
@@ -7,7 +7,7 @@ export declare class ForgeLexer extends Lexer {
|
|
|
7
7
|
static readonly LEFT_SQUARE_TOK = 2;
|
|
8
8
|
static readonly RIGHT_SQUARE_TOK = 3;
|
|
9
9
|
static readonly AS_TOK = 4;
|
|
10
|
-
static readonly
|
|
10
|
+
static readonly STRING_TOK = 5;
|
|
11
11
|
static readonly VAR_TOK = 6;
|
|
12
12
|
static readonly ABSTRACT_TOK = 7;
|
|
13
13
|
static readonly SIG_TOK = 8;
|
|
@@ -13,7 +13,7 @@ export declare class ForgeParser extends Parser {
|
|
|
13
13
|
static readonly LEFT_SQUARE_TOK = 2;
|
|
14
14
|
static readonly RIGHT_SQUARE_TOK = 3;
|
|
15
15
|
static readonly AS_TOK = 4;
|
|
16
|
-
static readonly
|
|
16
|
+
static readonly STRING_TOK = 5;
|
|
17
17
|
static readonly VAR_TOK = 6;
|
|
18
18
|
static readonly ABSTRACT_TOK = 7;
|
|
19
19
|
static readonly SIG_TOK = 8;
|
|
@@ -385,7 +385,7 @@ export declare class ImportDeclContext extends ParserRuleContext {
|
|
|
385
385
|
RIGHT_SQUARE_TOK(): TerminalNode | undefined;
|
|
386
386
|
AS_TOK(): TerminalNode | undefined;
|
|
387
387
|
name(): NameContext | undefined;
|
|
388
|
-
|
|
388
|
+
STRING_TOK(): TerminalNode | undefined;
|
|
389
389
|
constructor(parent: ParserRuleContext | undefined, invokingState: number);
|
|
390
390
|
get ruleIndex(): number;
|
|
391
391
|
enterRule(listener: ForgeListener): void;
|
|
@@ -651,6 +651,7 @@ export declare class ConstContext extends ParserRuleContext {
|
|
|
651
651
|
IDEN_TOK(): TerminalNode | undefined;
|
|
652
652
|
number(): NumberContext | undefined;
|
|
653
653
|
MINUS_TOK(): TerminalNode | undefined;
|
|
654
|
+
STRING_TOK(): TerminalNode | undefined;
|
|
654
655
|
constructor(parent: ParserRuleContext | undefined, invokingState: number);
|
|
655
656
|
get ruleIndex(): number;
|
|
656
657
|
enterRule(listener: ForgeListener): void;
|
|
@@ -846,7 +847,7 @@ export declare class OptionDeclContext extends ParserRuleContext {
|
|
|
846
847
|
OPTION_TOK(): TerminalNode;
|
|
847
848
|
qualName(): QualNameContext[];
|
|
848
849
|
qualName(i: number): QualNameContext;
|
|
849
|
-
|
|
850
|
+
STRING_TOK(): TerminalNode | undefined;
|
|
850
851
|
number(): NumberContext | undefined;
|
|
851
852
|
MINUS_TOK(): TerminalNode | undefined;
|
|
852
853
|
constructor(parent: ParserRuleContext | undefined, invokingState: number);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ForgeListenerImpl } from './forge-antlr/ForgeListenerImpl';
|
|
2
2
|
import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker';
|
|
3
|
-
import { EvalResult } from './ForgeExprEvaluator';
|
|
3
|
+
import { Diagnostic, EvalResult } from './ForgeExprEvaluator';
|
|
4
4
|
import { ForgeExprStaticAnalyzer, StaticAnalysis } from './ForgeExprStaticAnalyzer';
|
|
5
5
|
import { IDataInstance, IForgeSchema } from './types';
|
|
6
6
|
export type ErrorResult = {
|
|
@@ -44,6 +44,7 @@ export declare class SimpleGraphQueryEvaluator {
|
|
|
44
44
|
private parseTreeCache;
|
|
45
45
|
private cachedEvaluator;
|
|
46
46
|
private cachedEvaluatorDatum;
|
|
47
|
+
private lastDiagnostics;
|
|
47
48
|
constructor(datum: IDataInstance);
|
|
48
49
|
/**
|
|
49
50
|
* Discard the cached inner evaluator (and its relation index /
|
|
@@ -56,6 +57,24 @@ export declare class SimpleGraphQueryEvaluator {
|
|
|
56
57
|
*/
|
|
57
58
|
invalidate(): void;
|
|
58
59
|
getExpressionParseTree(forgeExpr: string): import("./forge-antlr/ForgeParser").ParseExprContext;
|
|
60
|
+
/**
|
|
61
|
+
* Evaluate `forgeExpr` and return both its value and any diagnostics raised
|
|
62
|
+
* along the way.
|
|
63
|
+
*
|
|
64
|
+
* Diagnostics are advisory and never change the value — today the only kind
|
|
65
|
+
* is an unresolved name, which evaluates to the empty set. Consumers should
|
|
66
|
+
* surface them to whoever authored the query; see {@link Diagnostic}.
|
|
67
|
+
*
|
|
68
|
+
* Note that an unresolved name is a warning rather than an error on purpose.
|
|
69
|
+
* An instance carries only populated types and relations, so a sig that is
|
|
70
|
+
* empty here looks exactly like a typo, and a sig can empty out between
|
|
71
|
+
* frames of one trace. Deciding which it is needs context this library does
|
|
72
|
+
* not have.
|
|
73
|
+
*/
|
|
74
|
+
evaluateExpressionWithDiagnostics(forgeExpr: string): {
|
|
75
|
+
value: EvaluationResult;
|
|
76
|
+
diagnostics: Diagnostic[];
|
|
77
|
+
};
|
|
59
78
|
evaluateExpression(forgeExpr: string): EvaluationResult;
|
|
60
79
|
}
|
|
61
80
|
/**
|
|
@@ -75,5 +94,6 @@ export declare class SimpleGraphQueryEvaluator {
|
|
|
75
94
|
export declare function analyzeForgeExpression(forgeExpr: string, schema?: IForgeSchema): StaticAnalysis;
|
|
76
95
|
export { ForgeExprStaticAnalyzer, StaticAnalysis };
|
|
77
96
|
export type { IForgeSchema };
|
|
97
|
+
export type { Diagnostic };
|
|
78
98
|
export { synthesizeSelector, synthesizeBinaryRelation, synthesizeBinaryRelationWithWhy, synthesizeSelectorWithWhy, AtomSelectionExample, BinaryRelationExample, SelectorSynthesisError, SynthesisWhy, SynthesisWhyExample, WhyNode, } from './SelectorSynthesizer';
|
|
79
99
|
export { getIdentifierName, quoteIfReserved, FORGE_RESERVED_KEYWORDS, } from './forge-antlr/utils';
|