plan-verify 0.0.0-stage → 0.1.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/.github/workflows/ci.yml +48 -0
- package/README.md +1431 -2
- package/assets/icon.png +0 -0
- package/assets/logo-mark.png +0 -0
- package/assets/logo.png +0 -0
- package/examples/checkout.ears.md +74 -0
- package/examples/order-always.ears.md +3 -0
- package/examples/order-awkward.ears.md +8 -0
- package/examples/order-bad.json +1 -0
- package/examples/order-chain.ears.md +9 -0
- package/examples/order-chosen.ears.md +7 -0
- package/examples/order-clear.ears.md +8 -0
- package/examples/order-combined.ears.md +10 -0
- package/examples/order-conflict.ears.md +8 -0
- package/examples/order-covered.ears.md +9 -0
- package/examples/order-duplicate.ears.md +8 -0
- package/examples/order-fail.json +5 -0
- package/examples/order-misuse.ears.md +7 -0
- package/examples/order-negation.ears.md +7 -0
- package/examples/order-pass.json +5 -0
- package/examples/order-quantity.ears.md +8 -0
- package/examples/order-redundant.ears.md +9 -0
- package/examples/order-repair.ears.md +9 -0
- package/examples/order-repeat.ears.md +13 -0
- package/examples/order-same.ears.md +3 -0
- package/examples/order-status.ears.md +7 -0
- package/examples/order-unanswered.ears.md +8 -0
- package/examples/order-unhandled.ears.md +8 -0
- package/examples/order-unmeasured.ears.md +10 -0
- package/examples/order-unused.ears.md +13 -0
- package/examples/order-vacuous.ears.md +9 -0
- package/examples/order-vague.ears.md +7 -0
- package/examples/order-wide.ears.md +5 -0
- package/examples/order-wording.ears.md +8 -0
- package/examples/order.ears.md +7 -0
- package/examples/payment-if.ears.md +7 -0
- package/examples/plan-to-problems/README.md +54 -0
- package/examples/plan-to-problems/expected-problems.md +25 -0
- package/examples/plan-to-problems/generate.mjs +186 -0
- package/examples/plan-to-problems/generated.ears.md +27 -0
- package/examples/plan-to-problems/lint.json +576 -0
- package/examples/plan-to-problems/plan.md +5 -0
- package/examples/plan-to-problems/planted.txt +5 -0
- package/examples/plan-to-problems/problems.md +112 -0
- package/examples/status-placed.ears.md +3 -0
- package/package.json +25 -4
- package/scripts/install-scriptc.sh +16 -0
- package/skills/plan-verify/SKILL.md +1515 -0
- package/src/check.ts +250 -0
- package/src/decide.ts +1324 -0
- package/src/ears.ts +1520 -0
- package/src/jsonvalue.ts +228 -0
- package/src/lint.ts +1245 -0
- package/src/main.ts +273 -0
- package/src/policy.ts +148 -0
- package/src/repair.ts +567 -0
- package/src/report.ts +329 -0
- package/src/scenarios.ts +149 -0
- package/src/smt.ts +97 -0
- package/src/solver.ts +868 -0
- package/src/spec.ts +307 -0
- package/src/term.ts +331 -0
- package/src/text.ts +95 -0
- package/test/fixtures/malformed.ears.md +3 -0
- package/test/fixtures/unknown-phrase.ears.md +5 -0
- package/test/golden/check-fail-json.exit +1 -0
- package/test/golden/check-fail-json.stderr +0 -0
- package/test/golden/check-fail-json.stdout +13 -0
- package/test/golden/check-fail.exit +1 -0
- package/test/golden/check-fail.stderr +0 -0
- package/test/golden/check-fail.stdout +7 -0
- package/test/golden/check-pass-json.exit +1 -0
- package/test/golden/check-pass-json.stderr +0 -0
- package/test/golden/check-pass-json.stdout +6 -0
- package/test/golden/check-pass.exit +1 -0
- package/test/golden/check-pass.stderr +0 -0
- package/test/golden/check-pass.stdout +3 -0
- package/test/golden/emit-smt-json-usage.exit +1 -0
- package/test/golden/emit-smt-json-usage.stderr +0 -0
- package/test/golden/emit-smt-json-usage.stdout +4 -0
- package/test/golden/emit-smt-json.exit +1 -0
- package/test/golden/emit-smt-json.stderr +0 -0
- package/test/golden/emit-smt-json.stdout +5 -0
- package/test/golden/emit-smt.exit +1 -0
- package/test/golden/emit-smt.stderr +0 -0
- package/test/golden/emit-smt.stdout +10 -0
- package/test/golden/help.exit +1 -0
- package/test/golden/help.stderr +0 -0
- package/test/golden/help.stdout +8 -0
- package/test/golden/lint-awkward.exit +1 -0
- package/test/golden/lint-awkward.stderr +0 -0
- package/test/golden/lint-awkward.stdout +29 -0
- package/test/golden/lint-chain.exit +1 -0
- package/test/golden/lint-chain.stderr +0 -0
- package/test/golden/lint-chain.stdout +56 -0
- package/test/golden/lint-checkout.exit +1 -0
- package/test/golden/lint-checkout.stderr +0 -0
- package/test/golden/lint-checkout.stdout +301 -0
- package/test/golden/lint-clean.exit +1 -0
- package/test/golden/lint-clean.stderr +0 -0
- package/test/golden/lint-clean.stdout +9 -0
- package/test/golden/lint-clear.exit +1 -0
- package/test/golden/lint-clear.stderr +0 -0
- package/test/golden/lint-clear.stdout +9 -0
- package/test/golden/lint-conflict-json.exit +1 -0
- package/test/golden/lint-conflict-json.stderr +0 -0
- package/test/golden/lint-conflict-json.stdout +80 -0
- package/test/golden/lint-conflict.exit +1 -0
- package/test/golden/lint-conflict.stderr +0 -0
- package/test/golden/lint-conflict.stdout +41 -0
- package/test/golden/lint-duplicate.exit +1 -0
- package/test/golden/lint-duplicate.stderr +0 -0
- package/test/golden/lint-duplicate.stdout +14 -0
- package/test/golden/lint-gap.exit +1 -0
- package/test/golden/lint-gap.stderr +0 -0
- package/test/golden/lint-gap.stdout +12 -0
- package/test/golden/lint-misuse.exit +1 -0
- package/test/golden/lint-misuse.stderr +0 -0
- package/test/golden/lint-misuse.stdout +18 -0
- package/test/golden/lint-negation.exit +1 -0
- package/test/golden/lint-negation.stderr +0 -0
- package/test/golden/lint-negation.stdout +13 -0
- package/test/golden/lint-quantity.exit +1 -0
- package/test/golden/lint-quantity.stderr +0 -0
- package/test/golden/lint-quantity.stdout +41 -0
- package/test/golden/lint-redundant.exit +1 -0
- package/test/golden/lint-redundant.stderr +0 -0
- package/test/golden/lint-redundant.stdout +14 -0
- package/test/golden/lint-repair-json.exit +1 -0
- package/test/golden/lint-repair-json.stderr +0 -0
- package/test/golden/lint-repair-json.stdout +127 -0
- package/test/golden/lint-repair.exit +1 -0
- package/test/golden/lint-repair.stderr +0 -0
- package/test/golden/lint-repair.stdout +54 -0
- package/test/golden/lint-repeat.exit +1 -0
- package/test/golden/lint-repeat.stderr +0 -0
- package/test/golden/lint-repeat.stdout +48 -0
- package/test/golden/lint-status.exit +1 -0
- package/test/golden/lint-status.stderr +0 -0
- package/test/golden/lint-status.stdout +44 -0
- package/test/golden/lint-unanswered.exit +1 -0
- package/test/golden/lint-unanswered.stderr +0 -0
- package/test/golden/lint-unanswered.stdout +12 -0
- package/test/golden/lint-unhandled.exit +1 -0
- package/test/golden/lint-unhandled.stderr +0 -0
- package/test/golden/lint-unhandled.stdout +12 -0
- package/test/golden/lint-unmeasured.exit +1 -0
- package/test/golden/lint-unmeasured.stderr +0 -0
- package/test/golden/lint-unmeasured.stdout +13 -0
- package/test/golden/lint-unused-json.exit +1 -0
- package/test/golden/lint-unused-json.stderr +0 -0
- package/test/golden/lint-unused-json.stdout +37 -0
- package/test/golden/lint-unused.exit +1 -0
- package/test/golden/lint-unused.stderr +0 -0
- package/test/golden/lint-unused.stdout +25 -0
- package/test/golden/lint-vacuous-json.exit +1 -0
- package/test/golden/lint-vacuous-json.stderr +0 -0
- package/test/golden/lint-vacuous-json.stdout +27 -0
- package/test/golden/lint-vacuous.exit +1 -0
- package/test/golden/lint-vacuous.stderr +0 -0
- package/test/golden/lint-vacuous.stdout +19 -0
- package/test/golden/lint-vague.exit +1 -0
- package/test/golden/lint-vague.stderr +0 -0
- package/test/golden/lint-vague.stdout +21 -0
- package/test/golden/lint-wide.exit +1 -0
- package/test/golden/lint-wide.stderr +0 -0
- package/test/golden/lint-wide.stdout +9 -0
- package/test/golden/lint-wording.exit +1 -0
- package/test/golden/lint-wording.stderr +0 -0
- package/test/golden/lint-wording.stdout +21 -0
- package/test/golden/malformed-scenario.exit +1 -0
- package/test/golden/malformed-scenario.stderr +1 -0
- package/test/golden/malformed-scenario.stdout +0 -0
- package/test/golden/malformed-spec.exit +1 -0
- package/test/golden/malformed-spec.stderr +1 -0
- package/test/golden/malformed-spec.stdout +0 -0
- package/test/golden/policy-counterexample.exit +1 -0
- package/test/golden/policy-counterexample.stderr +0 -0
- package/test/golden/policy-counterexample.stdout +13 -0
- package/test/golden/policy-covered.exit +1 -0
- package/test/golden/policy-covered.stderr +0 -0
- package/test/golden/policy-covered.stdout +19 -0
- package/test/golden/policy-holds.exit +1 -0
- package/test/golden/policy-holds.stderr +0 -0
- package/test/golden/policy-holds.stdout +3 -0
- package/test/golden/policy-json.exit +1 -0
- package/test/golden/policy-json.stderr +0 -0
- package/test/golden/policy-json.stdout +34 -0
- package/test/golden/policy-status.exit +1 -0
- package/test/golden/policy-status.stderr +0 -0
- package/test/golden/policy-status.stdout +16 -0
- package/test/golden/policy-vacuous.exit +1 -0
- package/test/golden/policy-vacuous.stderr +0 -0
- package/test/golden/policy-vacuous.stdout +3 -0
- package/test/golden/policy-wide.exit +1 -0
- package/test/golden/policy-wide.stderr +0 -0
- package/test/golden/policy-wide.stdout +15 -0
- package/test/golden/policy.exit +1 -0
- package/test/golden/policy.stderr +0 -0
- package/test/golden/policy.stdout +19 -0
- package/test/golden/scenarios-json.exit +1 -0
- package/test/golden/scenarios-json.stderr +0 -0
- package/test/golden/scenarios-json.stdout +75 -0
- package/test/golden/scenarios-status.exit +1 -0
- package/test/golden/scenarios-status.stderr +0 -0
- package/test/golden/scenarios-status.stdout +24 -0
- package/test/golden/scenarios.exit +1 -0
- package/test/golden/scenarios.stderr +0 -0
- package/test/golden/scenarios.stdout +26 -0
- package/test/golden/unit.exit +1 -0
- package/test/golden/unit.stderr +0 -0
- package/test/golden/unit.stdout +2 -0
- package/test/golden/unknown-phrase.exit +1 -0
- package/test/golden/unknown-phrase.stderr +1 -0
- package/test/golden/unknown-phrase.stdout +0 -0
- package/test/golden/usage.exit +1 -0
- package/test/golden/usage.stderr +9 -0
- package/test/golden/usage.stdout +0 -0
- package/test/run-all.ts +499 -0
- package/test/soundness.ts +375 -0
- package/test/unit.ts +1352 -0
- package/tsconfig.json +15 -0
package/src/check.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate one fully assigned scenario. This does not search.
|
|
3
|
+
* A requirement fails when its condition holds and its response does not.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JsonObject, JsonValue } from "./jsonvalue.ts";
|
|
7
|
+
import { describeAssignment, displayCode, literalShown, reportBool, speakBound, type Requirement, type Spec, type Term } from "./spec.ts";
|
|
8
|
+
import { Binding, evalAssertion, evalBool, formatTerm, mentionedIndexes, T_FALSE, T_TRUE, T_UNKNOWN } from "./term.ts";
|
|
9
|
+
import { capitalize, displayQuote, englishList, formatInt, pastVoice } from "./text.ts";
|
|
10
|
+
|
|
11
|
+
export interface Violation {
|
|
12
|
+
id: string;
|
|
13
|
+
line: number;
|
|
14
|
+
sentence: string;
|
|
15
|
+
because: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type BindResult = { ok: true; env: Binding } | { ok: false; message: string };
|
|
19
|
+
|
|
20
|
+
export function bindScenario(spec: Spec, object: JsonObject): BindResult {
|
|
21
|
+
const env = new Binding(spec.symbols.length);
|
|
22
|
+
const used: boolean[] = [];
|
|
23
|
+
for (let i = 0; i < object.keys.length; i++) used.push(false);
|
|
24
|
+
for (let s = 0; s < spec.symbols.length; s++) {
|
|
25
|
+
const sym = spec.symbols[s];
|
|
26
|
+
let found = -1;
|
|
27
|
+
for (let k = 0; k < object.keys.length; k++) {
|
|
28
|
+
if (object.keys[k] === sym.name) found = k;
|
|
29
|
+
}
|
|
30
|
+
if (found < 0) return { ok: false, message: "missing " + sym.name };
|
|
31
|
+
used[found] = true;
|
|
32
|
+
const value = object.values[found];
|
|
33
|
+
const bound = bindValue(spec, s, value);
|
|
34
|
+
if (!bound.ok) return bound;
|
|
35
|
+
env.known[s] = true;
|
|
36
|
+
env.code[s] = bound.code;
|
|
37
|
+
}
|
|
38
|
+
for (let k = 0; k < object.keys.length; k++) {
|
|
39
|
+
if (!used[k]) return { ok: false, message: "unexpected " + object.keys[k] };
|
|
40
|
+
}
|
|
41
|
+
return { ok: true, env: env };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function bindValue(spec: Spec, index: number, value: JsonValue): { ok: true; code: number } | { ok: false; message: string } {
|
|
45
|
+
const sym = spec.symbols[index];
|
|
46
|
+
if (sym.sort.kind === "bool") {
|
|
47
|
+
if (value.kind !== "bool") return { ok: false, message: sym.name + " must be true or false" };
|
|
48
|
+
return { ok: true, code: value.value ? 1 : 0 };
|
|
49
|
+
}
|
|
50
|
+
if (sym.sort.kind === "int") {
|
|
51
|
+
if (value.kind !== "int") {
|
|
52
|
+
return { ok: false, message: sym.name + " must be an integer from " + formatInt(sym.sort.min) + " to " + formatInt(sym.sort.max) };
|
|
53
|
+
}
|
|
54
|
+
if (value.value < sym.sort.min || value.value > sym.sort.max) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
message: sym.name + " is " + formatInt(value.value) + ", outside " + formatInt(sym.sort.min) + ".." + formatInt(sym.sort.max),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, code: value.value };
|
|
61
|
+
}
|
|
62
|
+
const decl = spec.enums;
|
|
63
|
+
let variants: string[] = [];
|
|
64
|
+
for (let i = 0; i < decl.length; i++) {
|
|
65
|
+
if (decl[i].name === sym.sort.enumName) variants = decl[i].variants;
|
|
66
|
+
}
|
|
67
|
+
let expected = "";
|
|
68
|
+
for (let i = 0; i < variants.length; i++) {
|
|
69
|
+
if (i > 0) expected += " or ";
|
|
70
|
+
expected += variants[i];
|
|
71
|
+
}
|
|
72
|
+
if (value.kind !== "string") return { ok: false, message: sym.name + " must be " + expected };
|
|
73
|
+
for (let i = 0; i < variants.length; i++) {
|
|
74
|
+
if (variants[i] === value.value) return { ok: true, code: i };
|
|
75
|
+
}
|
|
76
|
+
return { ok: false, message: sym.name + " is " + displayQuote(value.value) + ", expected " + expected };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function evaluate(spec: Spec, env: Binding): Violation[] {
|
|
80
|
+
return evaluateRequirements(spec, spec.requirements, env);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function evaluateRequirements(spec: Spec, requirements: Requirement[], env: Binding): Violation[] {
|
|
84
|
+
const violations: Violation[] = [];
|
|
85
|
+
for (let i = 0; i < requirements.length; i++) {
|
|
86
|
+
const req = requirements[i];
|
|
87
|
+
const result = evalAssertion(req.antecedent, req.consequent, spec, env);
|
|
88
|
+
if (result === T_FALSE) {
|
|
89
|
+
violations.push({
|
|
90
|
+
id: req.id,
|
|
91
|
+
line: req.line,
|
|
92
|
+
sentence: req.sentence,
|
|
93
|
+
because: because(spec, env, req.antecedent, req.consequent),
|
|
94
|
+
});
|
|
95
|
+
} else if (result === T_UNKNOWN) {
|
|
96
|
+
violations.push({
|
|
97
|
+
id: req.id,
|
|
98
|
+
line: req.line,
|
|
99
|
+
sentence: req.sentence,
|
|
100
|
+
because: "could not decide this requirement from the scenario.",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return violations;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function explainHold(spec: Spec, env: Binding, antecedent: Term | null, consequent: Term): string {
|
|
108
|
+
return narrate(spec, env, antecedent, consequent, "and");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function because(spec: Spec, env: Binding, antecedent: Term | null, consequent: Term): string {
|
|
112
|
+
return narrate(spec, env, antecedent, consequent, "but");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function narrate(spec: Spec, env: Binding, antecedent: Term | null, consequent: Term, join: string): string {
|
|
116
|
+
const indexes = mentionedIndexes(antecedent, consequent);
|
|
117
|
+
if (indexes.length === 0) return "the response is false.";
|
|
118
|
+
if (!usesVoice(spec, indexes)) {
|
|
119
|
+
const parts: string[] = [];
|
|
120
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
121
|
+
const sym = spec.symbols[indexes[i]];
|
|
122
|
+
const code = env.code[indexes[i]];
|
|
123
|
+
parts.push(describeAssignment(sym, displayCode(spec, sym, code), code === 1));
|
|
124
|
+
}
|
|
125
|
+
return englishList(parts) + ".";
|
|
126
|
+
}
|
|
127
|
+
const held: string[] = [];
|
|
128
|
+
if (antecedent !== null) collectHeld(spec, env, antecedent, held);
|
|
129
|
+
const actual = describeActual(spec, env, consequent);
|
|
130
|
+
if (held.length === 0) return capitalize(actual) + ".";
|
|
131
|
+
return capitalize(englishList(held)) + ", " + join + " " + actual + ".";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function usesVoice(spec: Spec, indexes: number[]): boolean {
|
|
135
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
136
|
+
const sym = spec.symbols[indexes[i]];
|
|
137
|
+
if (sym.phrase.length > 0 || sym.outcomeYes.length > 0) return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function collectHeld(spec: Spec, env: Binding, term: Term, out: string[]): void {
|
|
143
|
+
if (term.kind === "and") {
|
|
144
|
+
collectHeld(spec, env, term.left, out);
|
|
145
|
+
collectHeld(spec, env, term.right, out);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (term.kind === "or") {
|
|
149
|
+
const leftTrue = evalBool(term.left, spec, env) === T_TRUE;
|
|
150
|
+
const rightTrue = evalBool(term.right, spec, env) === T_TRUE;
|
|
151
|
+
if (leftTrue) collectHeld(spec, env, term.left, out);
|
|
152
|
+
if (rightTrue) collectHeld(spec, env, term.right, out);
|
|
153
|
+
if (!leftTrue && !rightTrue) out.push(spokenOrPast(spec, term));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (term.kind === "sym") {
|
|
157
|
+
const sym = spec.symbols[term.index];
|
|
158
|
+
if (sym.sort.kind === "bool") {
|
|
159
|
+
out.push(reportBool(sym, env.code[term.index] === 1));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (term.kind === "not") {
|
|
164
|
+
const inner = term.term;
|
|
165
|
+
if (inner.kind === "sym") {
|
|
166
|
+
const sym = spec.symbols[inner.index];
|
|
167
|
+
if (sym.sort.kind === "bool") {
|
|
168
|
+
out.push(reportBool(sym, env.code[inner.index] === 1));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
out.push(spokenOrPast(spec, term));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function spokenOrPast(spec: Spec, term: Term): string {
|
|
177
|
+
const spoken = spokenComparison(spec, term);
|
|
178
|
+
if (spoken.length > 0) return spoken;
|
|
179
|
+
return pastVoice(formatTerm(term));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function spokenComparison(spec: Spec, term: Term): string {
|
|
183
|
+
if (
|
|
184
|
+
term.kind !== "eq" &&
|
|
185
|
+
term.kind !== "neq" &&
|
|
186
|
+
term.kind !== "lt" &&
|
|
187
|
+
term.kind !== "le" &&
|
|
188
|
+
term.kind !== "gt" &&
|
|
189
|
+
term.kind !== "ge"
|
|
190
|
+
) {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
if (term.left.kind !== "sym") return "";
|
|
194
|
+
const sym = spec.symbols[term.left.index];
|
|
195
|
+
if (sym.sort.kind === "bool") return "";
|
|
196
|
+
const shown = literalShown(term.right);
|
|
197
|
+
if (shown.length > 0) return speakBound(sym, shown, term.kind, false);
|
|
198
|
+
return speakBound(sym, formatTerm(term.right), term.kind, true);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function describeActual(spec: Spec, env: Binding, term: Term): string {
|
|
202
|
+
if (term.kind === "sym") {
|
|
203
|
+
const sym = spec.symbols[term.index];
|
|
204
|
+
if (sym.sort.kind === "bool") return reportBool(sym, env.code[term.index] === 1);
|
|
205
|
+
}
|
|
206
|
+
if (term.kind === "not") {
|
|
207
|
+
const inner = term.term;
|
|
208
|
+
if (inner.kind === "sym") {
|
|
209
|
+
const sym = spec.symbols[inner.index];
|
|
210
|
+
if (sym.sort.kind === "bool") return reportBool(sym, env.code[inner.index] === 1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const indexes = mentionedIndexes(null, term);
|
|
214
|
+
if (indexes.length === 0) return pastVoice(formatTerm(term));
|
|
215
|
+
const parts: string[] = [];
|
|
216
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
217
|
+
const sym = spec.symbols[indexes[i]];
|
|
218
|
+
const code = env.code[indexes[i]];
|
|
219
|
+
parts.push(describeAssignment(sym, displayCode(spec, sym, code), code === 1));
|
|
220
|
+
}
|
|
221
|
+
return englishList(parts);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function formatCheckText(specPath: string, scenarioPath: string, violations: Violation[]): string {
|
|
225
|
+
const lines: string[] = [];
|
|
226
|
+
lines.push("spec: " + specPath);
|
|
227
|
+
lines.push("scenario: " + scenarioPath);
|
|
228
|
+
lines.push("violations: " + formatInt(violations.length));
|
|
229
|
+
for (let i = 0; i < violations.length; i++) {
|
|
230
|
+
const item = violations[i];
|
|
231
|
+
lines.push("");
|
|
232
|
+
lines.push(item.id + " line " + formatInt(item.line));
|
|
233
|
+
lines.push(item.sentence);
|
|
234
|
+
lines.push(item.because);
|
|
235
|
+
}
|
|
236
|
+
return lines.join("\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function formatCheckJson(specPath: string, scenarioPath: string, violations: Violation[]): string {
|
|
240
|
+
const items: { id: string; line: number; sentence: string; because: string }[] = [];
|
|
241
|
+
for (let i = 0; i < violations.length; i++) {
|
|
242
|
+
const item = violations[i];
|
|
243
|
+
items.push({ id: item.id, line: item.line, sentence: item.sentence, because: item.because });
|
|
244
|
+
}
|
|
245
|
+
return JSON.stringify({ ok: violations.length === 0, spec: specPath, scenario: scenarioPath, violations: items }, null, 2);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function formatErrorJson(message: string): string {
|
|
249
|
+
return JSON.stringify({ ok: false, error: message }, null, 2);
|
|
250
|
+
}
|