@suss/resolution 0.21.1 → 0.22.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.
- package/README.md +26 -0
- package/dist/index.cjs +444 -339
- package/dist/index.d.cts +78 -2
- package/dist/index.d.ts +78 -2
- package/dist/index.js +103 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _suss_datalog from '@suss/datalog';
|
|
2
|
-
import { Database, Atom, Tuple, Proof } from '@suss/datalog';
|
|
2
|
+
import { Database, Atom, Tuple, Proof, Rule } from '@suss/datalog';
|
|
3
3
|
|
|
4
4
|
/** The tuples of one relation, as strings, which is all a case compares. */
|
|
5
5
|
type FactsOf = (relation: string) => string[][];
|
|
@@ -148,6 +148,82 @@ declare function mountPathsOf(edges: MountEdges, childId: string): readonly stri
|
|
|
148
148
|
*/
|
|
149
149
|
declare function agreedMountPrefix(edges: MountEdges, childId: string): string | null;
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* The result shape a why session hands back, shared by every language
|
|
153
|
+
* adapter so the CLI reads one type regardless of which adapter
|
|
154
|
+
* answered. Each adapter follows its own source to a witness proof
|
|
155
|
+
* over `RESOLUTION_RULES` and renders it through `explainResolutionProof`;
|
|
156
|
+
* this is what that render comes back as.
|
|
157
|
+
*
|
|
158
|
+
* `explainResolvedKey` is the proof pass itself, for an adapter that
|
|
159
|
+
* keeps its facts in a `Database` keyed by source location: it reruns
|
|
160
|
+
* the rules with witnesses over a copy of those facts, reads the one
|
|
161
|
+
* target the asked-about key resolves to, and renders the proof. An
|
|
162
|
+
* adapter supplies how a key is said in a sentence and nothing else.
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/** A value or function said the way an answer prints it. */
|
|
166
|
+
interface ValueLocation {
|
|
167
|
+
name: string;
|
|
168
|
+
/** Relative to the session root when under it, absolute otherwise. */
|
|
169
|
+
file: string;
|
|
170
|
+
line: number;
|
|
171
|
+
}
|
|
172
|
+
/** What one witness re-evaluation cost, said rather than hidden. */
|
|
173
|
+
interface ExplainStats {
|
|
174
|
+
/** Facts the run had extracted, which the proof pass reran. */
|
|
175
|
+
baseFacts: number;
|
|
176
|
+
/** Facts the exhaustive pass derived on top of those. */
|
|
177
|
+
derivedFacts: number;
|
|
178
|
+
evaluateMs: number;
|
|
179
|
+
}
|
|
180
|
+
interface WhyExplained {
|
|
181
|
+
explanation: ResolutionExplanation;
|
|
182
|
+
/** The chain's atoms, each said in source terms. */
|
|
183
|
+
chain: string[];
|
|
184
|
+
/** The chain and its reasons as printable lines. */
|
|
185
|
+
lines: string[];
|
|
186
|
+
target: ValueLocation;
|
|
187
|
+
stats: ExplainStats;
|
|
188
|
+
}
|
|
189
|
+
interface ExplainResolvedKeyOptions {
|
|
190
|
+
/** The facts the session extracted. Read, never written to. */
|
|
191
|
+
db: Database;
|
|
192
|
+
/** The resolution rules plus whatever the language adds to them. */
|
|
193
|
+
rules: Rule[];
|
|
194
|
+
/** The fact key of the value asked about. */
|
|
195
|
+
key: string;
|
|
196
|
+
/** Where a fact key was written, or null for one the session never indexed. */
|
|
197
|
+
locate: (key: string) => ValueLocation | null;
|
|
198
|
+
/** How to say a key that has no location, which is a file path. */
|
|
199
|
+
displayPath: (key: string) => string;
|
|
200
|
+
maxDepth?: number;
|
|
201
|
+
phrases?: Record<string, StepPhrase>;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Why `key` resolves to what it does: the witness proof, flattened to
|
|
205
|
+
* the chain and rendered. Null when the key resolves to nothing or to
|
|
206
|
+
* more than one function, which the caller says in its own words.
|
|
207
|
+
*/
|
|
208
|
+
declare function explainResolvedKey(options: ExplainResolvedKeyOptions): WhyExplained | null;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The single-answer policy shared by every question shaped `[key,
|
|
212
|
+
* answer]`, such as `wantedIsWrittenAs` or `wantedSubjectWritten`. A
|
|
213
|
+
* call is written as itself, so a call asked about directly always
|
|
214
|
+
* matches its own key; that row is dropped before the count so one
|
|
215
|
+
* other answer settles it instead of counting as two.
|
|
216
|
+
*
|
|
217
|
+
* A placeholder write, `client = None` before a guard fills it in, is
|
|
218
|
+
* set aside the same way when the key has any other answer. A key
|
|
219
|
+
* written only as a placeholder keeps it.
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
/** The one answer each key settles on, once a key's match against itself is dropped. */
|
|
223
|
+
declare function singleAnswers(rows: Iterable<Tuple>, placeholders?: ReadonlySet<string>): Map<string, string>;
|
|
224
|
+
/** The keys an adapter marked `placeholderValue`, for passing to `singleAnswers`. */
|
|
225
|
+
declare function placeholderValues(db: Database): ReadonlySet<string>;
|
|
226
|
+
|
|
151
227
|
/** A step to the value x is written as. */
|
|
152
228
|
declare const VALUE_STEP: _suss_datalog.Term;
|
|
153
229
|
/**
|
|
@@ -196,4 +272,4 @@ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
|
|
|
196
272
|
/** The relations `RESOLUTION_QUESTIONS` answers into. */
|
|
197
273
|
declare const ANSWER_RELATIONS: string[];
|
|
198
274
|
|
|
199
|
-
export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, type DescribeAtom, type ExplainOptions, FACT_CONTRACT_CASES, type FactsOf, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ResolutionExplanation, type ResolutionStep, type StepContext, type StepPhrase, VALUE_STEP, agreedMountPrefix, checkFactContract, explainResolutionProof, joinMountedPath, mountPathsOf, renderExplanation };
|
|
275
|
+
export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, type DescribeAtom, type ExplainOptions, type ExplainResolvedKeyOptions, type ExplainStats, FACT_CONTRACT_CASES, type FactsOf, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ResolutionExplanation, type ResolutionStep, type StepContext, type StepPhrase, VALUE_STEP, type ValueLocation, type WhyExplained, agreedMountPrefix, checkFactContract, explainResolutionProof, explainResolvedKey, joinMountedPath, mountPathsOf, placeholderValues, renderExplanation, singleAnswers };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _suss_datalog from '@suss/datalog';
|
|
2
|
-
import { Database, Atom, Tuple, Proof } from '@suss/datalog';
|
|
2
|
+
import { Database, Atom, Tuple, Proof, Rule } from '@suss/datalog';
|
|
3
3
|
|
|
4
4
|
/** The tuples of one relation, as strings, which is all a case compares. */
|
|
5
5
|
type FactsOf = (relation: string) => string[][];
|
|
@@ -148,6 +148,82 @@ declare function mountPathsOf(edges: MountEdges, childId: string): readonly stri
|
|
|
148
148
|
*/
|
|
149
149
|
declare function agreedMountPrefix(edges: MountEdges, childId: string): string | null;
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* The result shape a why session hands back, shared by every language
|
|
153
|
+
* adapter so the CLI reads one type regardless of which adapter
|
|
154
|
+
* answered. Each adapter follows its own source to a witness proof
|
|
155
|
+
* over `RESOLUTION_RULES` and renders it through `explainResolutionProof`;
|
|
156
|
+
* this is what that render comes back as.
|
|
157
|
+
*
|
|
158
|
+
* `explainResolvedKey` is the proof pass itself, for an adapter that
|
|
159
|
+
* keeps its facts in a `Database` keyed by source location: it reruns
|
|
160
|
+
* the rules with witnesses over a copy of those facts, reads the one
|
|
161
|
+
* target the asked-about key resolves to, and renders the proof. An
|
|
162
|
+
* adapter supplies how a key is said in a sentence and nothing else.
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/** A value or function said the way an answer prints it. */
|
|
166
|
+
interface ValueLocation {
|
|
167
|
+
name: string;
|
|
168
|
+
/** Relative to the session root when under it, absolute otherwise. */
|
|
169
|
+
file: string;
|
|
170
|
+
line: number;
|
|
171
|
+
}
|
|
172
|
+
/** What one witness re-evaluation cost, said rather than hidden. */
|
|
173
|
+
interface ExplainStats {
|
|
174
|
+
/** Facts the run had extracted, which the proof pass reran. */
|
|
175
|
+
baseFacts: number;
|
|
176
|
+
/** Facts the exhaustive pass derived on top of those. */
|
|
177
|
+
derivedFacts: number;
|
|
178
|
+
evaluateMs: number;
|
|
179
|
+
}
|
|
180
|
+
interface WhyExplained {
|
|
181
|
+
explanation: ResolutionExplanation;
|
|
182
|
+
/** The chain's atoms, each said in source terms. */
|
|
183
|
+
chain: string[];
|
|
184
|
+
/** The chain and its reasons as printable lines. */
|
|
185
|
+
lines: string[];
|
|
186
|
+
target: ValueLocation;
|
|
187
|
+
stats: ExplainStats;
|
|
188
|
+
}
|
|
189
|
+
interface ExplainResolvedKeyOptions {
|
|
190
|
+
/** The facts the session extracted. Read, never written to. */
|
|
191
|
+
db: Database;
|
|
192
|
+
/** The resolution rules plus whatever the language adds to them. */
|
|
193
|
+
rules: Rule[];
|
|
194
|
+
/** The fact key of the value asked about. */
|
|
195
|
+
key: string;
|
|
196
|
+
/** Where a fact key was written, or null for one the session never indexed. */
|
|
197
|
+
locate: (key: string) => ValueLocation | null;
|
|
198
|
+
/** How to say a key that has no location, which is a file path. */
|
|
199
|
+
displayPath: (key: string) => string;
|
|
200
|
+
maxDepth?: number;
|
|
201
|
+
phrases?: Record<string, StepPhrase>;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Why `key` resolves to what it does: the witness proof, flattened to
|
|
205
|
+
* the chain and rendered. Null when the key resolves to nothing or to
|
|
206
|
+
* more than one function, which the caller says in its own words.
|
|
207
|
+
*/
|
|
208
|
+
declare function explainResolvedKey(options: ExplainResolvedKeyOptions): WhyExplained | null;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The single-answer policy shared by every question shaped `[key,
|
|
212
|
+
* answer]`, such as `wantedIsWrittenAs` or `wantedSubjectWritten`. A
|
|
213
|
+
* call is written as itself, so a call asked about directly always
|
|
214
|
+
* matches its own key; that row is dropped before the count so one
|
|
215
|
+
* other answer settles it instead of counting as two.
|
|
216
|
+
*
|
|
217
|
+
* A placeholder write, `client = None` before a guard fills it in, is
|
|
218
|
+
* set aside the same way when the key has any other answer. A key
|
|
219
|
+
* written only as a placeholder keeps it.
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
/** The one answer each key settles on, once a key's match against itself is dropped. */
|
|
223
|
+
declare function singleAnswers(rows: Iterable<Tuple>, placeholders?: ReadonlySet<string>): Map<string, string>;
|
|
224
|
+
/** The keys an adapter marked `placeholderValue`, for passing to `singleAnswers`. */
|
|
225
|
+
declare function placeholderValues(db: Database): ReadonlySet<string>;
|
|
226
|
+
|
|
151
227
|
/** A step to the value x is written as. */
|
|
152
228
|
declare const VALUE_STEP: _suss_datalog.Term;
|
|
153
229
|
/**
|
|
@@ -196,4 +272,4 @@ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
|
|
|
196
272
|
/** The relations `RESOLUTION_QUESTIONS` answers into. */
|
|
197
273
|
declare const ANSWER_RELATIONS: string[];
|
|
198
274
|
|
|
199
|
-
export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, type DescribeAtom, type ExplainOptions, FACT_CONTRACT_CASES, type FactsOf, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ResolutionExplanation, type ResolutionStep, type StepContext, type StepPhrase, VALUE_STEP, agreedMountPrefix, checkFactContract, explainResolutionProof, joinMountedPath, mountPathsOf, renderExplanation };
|
|
275
|
+
export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, type DescribeAtom, type ExplainOptions, type ExplainResolvedKeyOptions, type ExplainStats, FACT_CONTRACT_CASES, type FactsOf, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ResolutionExplanation, type ResolutionStep, type StepContext, type StepPhrase, VALUE_STEP, type ValueLocation, type WhyExplained, agreedMountPrefix, checkFactContract, explainResolutionProof, explainResolvedKey, joinMountedPath, mountPathsOf, placeholderValues, renderExplanation, singleAnswers };
|
package/dist/index.js
CHANGED
|
@@ -477,6 +477,93 @@ function pathsOf(edges, memo, childId, visiting) {
|
|
|
477
477
|
return result;
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
// src/session.ts
|
|
481
|
+
import { Database, evaluate, proofOf, witnesses } from "@suss/datalog";
|
|
482
|
+
function explainResolvedKey(options) {
|
|
483
|
+
const { db, rules, key, locate, displayPath } = options;
|
|
484
|
+
const proofDb = new Database();
|
|
485
|
+
let baseFacts = 0;
|
|
486
|
+
for (const relation of db.relationNames()) {
|
|
487
|
+
for (const tuple of db.facts(relation)) {
|
|
488
|
+
proofDb.add(relation, tuple);
|
|
489
|
+
baseFacts++;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const started = performance.now();
|
|
493
|
+
evaluate(proofDb, rules, witnesses);
|
|
494
|
+
const evaluateMs = performance.now() - started;
|
|
495
|
+
const derivedFacts = proofDb.relationNames().reduce((count, relation) => count + proofDb.size(relation), 0) - baseFacts;
|
|
496
|
+
const targets = new Set(
|
|
497
|
+
proofDb.lookup("resolves", 0, key).map((tuple) => String(tuple[1]))
|
|
498
|
+
);
|
|
499
|
+
if (targets.size !== 1) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const target = [...targets][0];
|
|
503
|
+
const proof = proofOf(
|
|
504
|
+
proofDb,
|
|
505
|
+
"resolves",
|
|
506
|
+
[key, target],
|
|
507
|
+
options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth }
|
|
508
|
+
);
|
|
509
|
+
const describe = (atom) => {
|
|
510
|
+
const location = locate(String(atom));
|
|
511
|
+
return location === null ? displayPath(String(atom)) : `${location.name} (${location.file}:${location.line})`;
|
|
512
|
+
};
|
|
513
|
+
const explanation = explainResolutionProof(proof, {
|
|
514
|
+
describe,
|
|
515
|
+
...options.phrases === void 0 ? {} : { phrases: options.phrases }
|
|
516
|
+
});
|
|
517
|
+
if (explanation === null) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
const targetLocation = locate(target);
|
|
521
|
+
if (targetLocation === null) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
explanation,
|
|
526
|
+
chain: explanation.atoms.map(describe),
|
|
527
|
+
lines: renderExplanation(explanation, describe),
|
|
528
|
+
target: targetLocation,
|
|
529
|
+
stats: { baseFacts, derivedFacts, evaluateMs }
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/singleAnswer.ts
|
|
534
|
+
var NO_PLACEHOLDERS = /* @__PURE__ */ new Set();
|
|
535
|
+
function singleAnswers(rows, placeholders = NO_PLACEHOLDERS) {
|
|
536
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const row of rows) {
|
|
538
|
+
const key = String(row[0]);
|
|
539
|
+
const answer = String(row[1]);
|
|
540
|
+
if (answer === key) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const set = candidates.get(key) ?? /* @__PURE__ */ new Set();
|
|
544
|
+
set.add(answer);
|
|
545
|
+
candidates.set(key, set);
|
|
546
|
+
}
|
|
547
|
+
const settled = /* @__PURE__ */ new Map();
|
|
548
|
+
for (const [key, set] of candidates) {
|
|
549
|
+
const answers = withoutPlaceholders(set, placeholders);
|
|
550
|
+
if (answers.length === 1) {
|
|
551
|
+
settled.set(key, answers[0]);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return settled;
|
|
555
|
+
}
|
|
556
|
+
function placeholderValues(db) {
|
|
557
|
+
return new Set(db.facts("placeholderValue").map((row) => String(row[0])));
|
|
558
|
+
}
|
|
559
|
+
function withoutPlaceholders(answers, placeholders) {
|
|
560
|
+
const kept = [...answers].filter((answer) => !placeholders.has(answer));
|
|
561
|
+
if (kept.length === 0) {
|
|
562
|
+
return [...answers];
|
|
563
|
+
}
|
|
564
|
+
return kept;
|
|
565
|
+
}
|
|
566
|
+
|
|
480
567
|
// src/index.ts
|
|
481
568
|
import { constant, lit, rule, variable as v } from "@suss/datalog";
|
|
482
569
|
var VALUE_STEP = constant("value");
|
|
@@ -670,6 +757,18 @@ var RESOLUTION_RULES = [
|
|
|
670
757
|
[v("x"), v("z")],
|
|
671
758
|
[lit("reaches", v("x"), v("z"), VALUE_STEP), lit("objectValue", v("z"))]
|
|
672
759
|
),
|
|
760
|
+
// A call is written as whatever its callee's return value is written
|
|
761
|
+
// as. Unlike `comesTo`, this has nothing to protect: which expression
|
|
762
|
+
// a value was written as does not compete with unwrapping it.
|
|
763
|
+
rule(
|
|
764
|
+
"isWrittenAs",
|
|
765
|
+
[v("x"), v("z")],
|
|
766
|
+
[
|
|
767
|
+
lit("invokes", v("x"), v("f")),
|
|
768
|
+
lit("returnsValue", v("f"), v("ret")),
|
|
769
|
+
lit("isWrittenAs", v("ret"), v("z"))
|
|
770
|
+
]
|
|
771
|
+
),
|
|
673
772
|
// What one call site put in a parameter, told apart from what the
|
|
674
773
|
// other callers passed.
|
|
675
774
|
rule(
|
|
@@ -1216,7 +1315,10 @@ export {
|
|
|
1216
1315
|
agreedMountPrefix,
|
|
1217
1316
|
checkFactContract,
|
|
1218
1317
|
explainResolutionProof,
|
|
1318
|
+
explainResolvedKey,
|
|
1219
1319
|
joinMountedPath,
|
|
1220
1320
|
mountPathsOf,
|
|
1221
|
-
|
|
1321
|
+
placeholderValues,
|
|
1322
|
+
renderExplanation,
|
|
1323
|
+
singleAnswers
|
|
1222
1324
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/resolution",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Language-neutral rules for following a value to the function it comes down to.",
|
|
6
6
|
"keywords": [
|
|
@@ -53,6 +53,6 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@suss/datalog": "0.
|
|
56
|
+
"@suss/datalog": "0.22.0"
|
|
57
57
|
}
|
|
58
58
|
}
|