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.
Files changed (222) hide show
  1. package/.github/workflows/ci.yml +48 -0
  2. package/README.md +1431 -2
  3. package/assets/icon.png +0 -0
  4. package/assets/logo-mark.png +0 -0
  5. package/assets/logo.png +0 -0
  6. package/examples/checkout.ears.md +74 -0
  7. package/examples/order-always.ears.md +3 -0
  8. package/examples/order-awkward.ears.md +8 -0
  9. package/examples/order-bad.json +1 -0
  10. package/examples/order-chain.ears.md +9 -0
  11. package/examples/order-chosen.ears.md +7 -0
  12. package/examples/order-clear.ears.md +8 -0
  13. package/examples/order-combined.ears.md +10 -0
  14. package/examples/order-conflict.ears.md +8 -0
  15. package/examples/order-covered.ears.md +9 -0
  16. package/examples/order-duplicate.ears.md +8 -0
  17. package/examples/order-fail.json +5 -0
  18. package/examples/order-misuse.ears.md +7 -0
  19. package/examples/order-negation.ears.md +7 -0
  20. package/examples/order-pass.json +5 -0
  21. package/examples/order-quantity.ears.md +8 -0
  22. package/examples/order-redundant.ears.md +9 -0
  23. package/examples/order-repair.ears.md +9 -0
  24. package/examples/order-repeat.ears.md +13 -0
  25. package/examples/order-same.ears.md +3 -0
  26. package/examples/order-status.ears.md +7 -0
  27. package/examples/order-unanswered.ears.md +8 -0
  28. package/examples/order-unhandled.ears.md +8 -0
  29. package/examples/order-unmeasured.ears.md +10 -0
  30. package/examples/order-unused.ears.md +13 -0
  31. package/examples/order-vacuous.ears.md +9 -0
  32. package/examples/order-vague.ears.md +7 -0
  33. package/examples/order-wide.ears.md +5 -0
  34. package/examples/order-wording.ears.md +8 -0
  35. package/examples/order.ears.md +7 -0
  36. package/examples/payment-if.ears.md +7 -0
  37. package/examples/plan-to-problems/README.md +54 -0
  38. package/examples/plan-to-problems/expected-problems.md +25 -0
  39. package/examples/plan-to-problems/generate.mjs +186 -0
  40. package/examples/plan-to-problems/generated.ears.md +27 -0
  41. package/examples/plan-to-problems/lint.json +576 -0
  42. package/examples/plan-to-problems/plan.md +5 -0
  43. package/examples/plan-to-problems/planted.txt +5 -0
  44. package/examples/plan-to-problems/problems.md +112 -0
  45. package/examples/status-placed.ears.md +3 -0
  46. package/package.json +25 -4
  47. package/scripts/install-scriptc.sh +16 -0
  48. package/skills/plan-verify/SKILL.md +1515 -0
  49. package/src/check.ts +250 -0
  50. package/src/decide.ts +1324 -0
  51. package/src/ears.ts +1520 -0
  52. package/src/jsonvalue.ts +228 -0
  53. package/src/lint.ts +1245 -0
  54. package/src/main.ts +273 -0
  55. package/src/policy.ts +148 -0
  56. package/src/repair.ts +567 -0
  57. package/src/report.ts +329 -0
  58. package/src/scenarios.ts +149 -0
  59. package/src/smt.ts +97 -0
  60. package/src/solver.ts +868 -0
  61. package/src/spec.ts +307 -0
  62. package/src/term.ts +331 -0
  63. package/src/text.ts +95 -0
  64. package/test/fixtures/malformed.ears.md +3 -0
  65. package/test/fixtures/unknown-phrase.ears.md +5 -0
  66. package/test/golden/check-fail-json.exit +1 -0
  67. package/test/golden/check-fail-json.stderr +0 -0
  68. package/test/golden/check-fail-json.stdout +13 -0
  69. package/test/golden/check-fail.exit +1 -0
  70. package/test/golden/check-fail.stderr +0 -0
  71. package/test/golden/check-fail.stdout +7 -0
  72. package/test/golden/check-pass-json.exit +1 -0
  73. package/test/golden/check-pass-json.stderr +0 -0
  74. package/test/golden/check-pass-json.stdout +6 -0
  75. package/test/golden/check-pass.exit +1 -0
  76. package/test/golden/check-pass.stderr +0 -0
  77. package/test/golden/check-pass.stdout +3 -0
  78. package/test/golden/emit-smt-json-usage.exit +1 -0
  79. package/test/golden/emit-smt-json-usage.stderr +0 -0
  80. package/test/golden/emit-smt-json-usage.stdout +4 -0
  81. package/test/golden/emit-smt-json.exit +1 -0
  82. package/test/golden/emit-smt-json.stderr +0 -0
  83. package/test/golden/emit-smt-json.stdout +5 -0
  84. package/test/golden/emit-smt.exit +1 -0
  85. package/test/golden/emit-smt.stderr +0 -0
  86. package/test/golden/emit-smt.stdout +10 -0
  87. package/test/golden/help.exit +1 -0
  88. package/test/golden/help.stderr +0 -0
  89. package/test/golden/help.stdout +8 -0
  90. package/test/golden/lint-awkward.exit +1 -0
  91. package/test/golden/lint-awkward.stderr +0 -0
  92. package/test/golden/lint-awkward.stdout +29 -0
  93. package/test/golden/lint-chain.exit +1 -0
  94. package/test/golden/lint-chain.stderr +0 -0
  95. package/test/golden/lint-chain.stdout +56 -0
  96. package/test/golden/lint-checkout.exit +1 -0
  97. package/test/golden/lint-checkout.stderr +0 -0
  98. package/test/golden/lint-checkout.stdout +301 -0
  99. package/test/golden/lint-clean.exit +1 -0
  100. package/test/golden/lint-clean.stderr +0 -0
  101. package/test/golden/lint-clean.stdout +9 -0
  102. package/test/golden/lint-clear.exit +1 -0
  103. package/test/golden/lint-clear.stderr +0 -0
  104. package/test/golden/lint-clear.stdout +9 -0
  105. package/test/golden/lint-conflict-json.exit +1 -0
  106. package/test/golden/lint-conflict-json.stderr +0 -0
  107. package/test/golden/lint-conflict-json.stdout +80 -0
  108. package/test/golden/lint-conflict.exit +1 -0
  109. package/test/golden/lint-conflict.stderr +0 -0
  110. package/test/golden/lint-conflict.stdout +41 -0
  111. package/test/golden/lint-duplicate.exit +1 -0
  112. package/test/golden/lint-duplicate.stderr +0 -0
  113. package/test/golden/lint-duplicate.stdout +14 -0
  114. package/test/golden/lint-gap.exit +1 -0
  115. package/test/golden/lint-gap.stderr +0 -0
  116. package/test/golden/lint-gap.stdout +12 -0
  117. package/test/golden/lint-misuse.exit +1 -0
  118. package/test/golden/lint-misuse.stderr +0 -0
  119. package/test/golden/lint-misuse.stdout +18 -0
  120. package/test/golden/lint-negation.exit +1 -0
  121. package/test/golden/lint-negation.stderr +0 -0
  122. package/test/golden/lint-negation.stdout +13 -0
  123. package/test/golden/lint-quantity.exit +1 -0
  124. package/test/golden/lint-quantity.stderr +0 -0
  125. package/test/golden/lint-quantity.stdout +41 -0
  126. package/test/golden/lint-redundant.exit +1 -0
  127. package/test/golden/lint-redundant.stderr +0 -0
  128. package/test/golden/lint-redundant.stdout +14 -0
  129. package/test/golden/lint-repair-json.exit +1 -0
  130. package/test/golden/lint-repair-json.stderr +0 -0
  131. package/test/golden/lint-repair-json.stdout +127 -0
  132. package/test/golden/lint-repair.exit +1 -0
  133. package/test/golden/lint-repair.stderr +0 -0
  134. package/test/golden/lint-repair.stdout +54 -0
  135. package/test/golden/lint-repeat.exit +1 -0
  136. package/test/golden/lint-repeat.stderr +0 -0
  137. package/test/golden/lint-repeat.stdout +48 -0
  138. package/test/golden/lint-status.exit +1 -0
  139. package/test/golden/lint-status.stderr +0 -0
  140. package/test/golden/lint-status.stdout +44 -0
  141. package/test/golden/lint-unanswered.exit +1 -0
  142. package/test/golden/lint-unanswered.stderr +0 -0
  143. package/test/golden/lint-unanswered.stdout +12 -0
  144. package/test/golden/lint-unhandled.exit +1 -0
  145. package/test/golden/lint-unhandled.stderr +0 -0
  146. package/test/golden/lint-unhandled.stdout +12 -0
  147. package/test/golden/lint-unmeasured.exit +1 -0
  148. package/test/golden/lint-unmeasured.stderr +0 -0
  149. package/test/golden/lint-unmeasured.stdout +13 -0
  150. package/test/golden/lint-unused-json.exit +1 -0
  151. package/test/golden/lint-unused-json.stderr +0 -0
  152. package/test/golden/lint-unused-json.stdout +37 -0
  153. package/test/golden/lint-unused.exit +1 -0
  154. package/test/golden/lint-unused.stderr +0 -0
  155. package/test/golden/lint-unused.stdout +25 -0
  156. package/test/golden/lint-vacuous-json.exit +1 -0
  157. package/test/golden/lint-vacuous-json.stderr +0 -0
  158. package/test/golden/lint-vacuous-json.stdout +27 -0
  159. package/test/golden/lint-vacuous.exit +1 -0
  160. package/test/golden/lint-vacuous.stderr +0 -0
  161. package/test/golden/lint-vacuous.stdout +19 -0
  162. package/test/golden/lint-vague.exit +1 -0
  163. package/test/golden/lint-vague.stderr +0 -0
  164. package/test/golden/lint-vague.stdout +21 -0
  165. package/test/golden/lint-wide.exit +1 -0
  166. package/test/golden/lint-wide.stderr +0 -0
  167. package/test/golden/lint-wide.stdout +9 -0
  168. package/test/golden/lint-wording.exit +1 -0
  169. package/test/golden/lint-wording.stderr +0 -0
  170. package/test/golden/lint-wording.stdout +21 -0
  171. package/test/golden/malformed-scenario.exit +1 -0
  172. package/test/golden/malformed-scenario.stderr +1 -0
  173. package/test/golden/malformed-scenario.stdout +0 -0
  174. package/test/golden/malformed-spec.exit +1 -0
  175. package/test/golden/malformed-spec.stderr +1 -0
  176. package/test/golden/malformed-spec.stdout +0 -0
  177. package/test/golden/policy-counterexample.exit +1 -0
  178. package/test/golden/policy-counterexample.stderr +0 -0
  179. package/test/golden/policy-counterexample.stdout +13 -0
  180. package/test/golden/policy-covered.exit +1 -0
  181. package/test/golden/policy-covered.stderr +0 -0
  182. package/test/golden/policy-covered.stdout +19 -0
  183. package/test/golden/policy-holds.exit +1 -0
  184. package/test/golden/policy-holds.stderr +0 -0
  185. package/test/golden/policy-holds.stdout +3 -0
  186. package/test/golden/policy-json.exit +1 -0
  187. package/test/golden/policy-json.stderr +0 -0
  188. package/test/golden/policy-json.stdout +34 -0
  189. package/test/golden/policy-status.exit +1 -0
  190. package/test/golden/policy-status.stderr +0 -0
  191. package/test/golden/policy-status.stdout +16 -0
  192. package/test/golden/policy-vacuous.exit +1 -0
  193. package/test/golden/policy-vacuous.stderr +0 -0
  194. package/test/golden/policy-vacuous.stdout +3 -0
  195. package/test/golden/policy-wide.exit +1 -0
  196. package/test/golden/policy-wide.stderr +0 -0
  197. package/test/golden/policy-wide.stdout +15 -0
  198. package/test/golden/policy.exit +1 -0
  199. package/test/golden/policy.stderr +0 -0
  200. package/test/golden/policy.stdout +19 -0
  201. package/test/golden/scenarios-json.exit +1 -0
  202. package/test/golden/scenarios-json.stderr +0 -0
  203. package/test/golden/scenarios-json.stdout +75 -0
  204. package/test/golden/scenarios-status.exit +1 -0
  205. package/test/golden/scenarios-status.stderr +0 -0
  206. package/test/golden/scenarios-status.stdout +24 -0
  207. package/test/golden/scenarios.exit +1 -0
  208. package/test/golden/scenarios.stderr +0 -0
  209. package/test/golden/scenarios.stdout +26 -0
  210. package/test/golden/unit.exit +1 -0
  211. package/test/golden/unit.stderr +0 -0
  212. package/test/golden/unit.stdout +2 -0
  213. package/test/golden/unknown-phrase.exit +1 -0
  214. package/test/golden/unknown-phrase.stderr +1 -0
  215. package/test/golden/unknown-phrase.stdout +0 -0
  216. package/test/golden/usage.exit +1 -0
  217. package/test/golden/usage.stderr +9 -0
  218. package/test/golden/usage.stdout +0 -0
  219. package/test/run-all.ts +499 -0
  220. package/test/soundness.ts +375 -0
  221. package/test/unit.ts +1352 -0
  222. package/tsconfig.json +15 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Seeded differential test. Random small specs are checked two ways:
3
+ * the decision procedure, and a brute-force enumerator that tries every
4
+ * driver situation and every output assignment. Both must agree on
5
+ * satisfiability, the least model, and the minimal conflict sets.
6
+ * The seed is fixed, so Node and the native binary see the same specs.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import { parseSpec } from "../src/ears.ts";
11
+ import { codeAt, domainSize, isDriver, type Spec } from "../src/spec.ts";
12
+ import { conflictingSets, firstScenario } from "../src/solver.ts";
13
+ import { Binding, evalAssertion, T_TRUE } from "../src/term.ts";
14
+
15
+ const SEED = 20261022;
16
+ const RANDOM_SPECS = 1000;
17
+
18
+ class Rng {
19
+ state: number;
20
+
21
+ constructor(seed: number) {
22
+ this.state = seed % 2147483647;
23
+ if (this.state <= 0) this.state = 1;
24
+ }
25
+
26
+ next(bound: number): number {
27
+ this.state = (this.state * 48271) % 2147483647;
28
+ return this.state % bound;
29
+ }
30
+ }
31
+
32
+ interface Named {
33
+ name: string;
34
+ kind: string;
35
+ }
36
+
37
+ export function runSoundness(): number {
38
+ let count = 0;
39
+ const files = [
40
+ "examples/order.ears.md",
41
+ "examples/order-conflict.ears.md",
42
+ "examples/order-chain.ears.md",
43
+ "examples/order-covered.ears.md",
44
+ "examples/order-quantity.ears.md",
45
+ "examples/order-status.ears.md",
46
+ "examples/order-redundant.ears.md",
47
+ "examples/payment-if.ears.md",
48
+ ];
49
+ for (let i = 0; i < files.length; i++) {
50
+ const text = readFileSync(files[i], "utf8");
51
+ const parsed = parseSpec(text);
52
+ if (!parsed.ok) failSound("example parse " + files[i], text);
53
+ else if (parsed.spec.requirements.length <= 8 && domainProduct(parsed.spec) <= 4000) {
54
+ compareSpec(parsed.spec, text);
55
+ count++;
56
+ }
57
+ }
58
+ const coupled =
59
+ 'input quantity: Int 0..5 "Q."\noutput shipped: Int 0..5 "S."\n\nR1 The Box shall shipped = quantity + 1.\n';
60
+ const hole = 'input n: Int 0..6 "N."\noutput o: Bool "O."\n\nR1 The Box shall n <= 1 or n >= 4.\nR2 When n >= 4, the Box shall o.\n';
61
+ const fixed = [coupled, hole];
62
+ for (let i = 0; i < fixed.length; i++) {
63
+ const parsed = parseSpec(fixed[i]);
64
+ if (!parsed.ok) failSound("fixed parse " + parsed.message, fixed[i]);
65
+ else {
66
+ compareSpec(parsed.spec, fixed[i]);
67
+ count++;
68
+ }
69
+ }
70
+ const rng = new Rng(SEED);
71
+ for (let i = 0; i < RANDOM_SPECS; i++) {
72
+ const text = randomSpec(rng);
73
+ const parsed = parseSpec(text);
74
+ if (!parsed.ok) failSound("random parse " + parsed.message, text);
75
+ else {
76
+ compareSpec(parsed.spec, text);
77
+ count++;
78
+ }
79
+ }
80
+ return count;
81
+ }
82
+
83
+ function compareSpec(spec: Spec, text: string): void {
84
+ const groups = conflictingSets(spec);
85
+ const lists: number[][] = [];
86
+ for (let i = 0; i < groups.length; i++) lists.push(groups[i].indexes);
87
+ const solverCores = coreKey(lists);
88
+ const bruteCores = bruteKey(spec);
89
+ const solverUnsat = solverCores.length > 0;
90
+ const bruteUnsat = bruteCores.length > 0;
91
+ if (solverUnsat !== bruteUnsat || solverCores !== bruteCores) {
92
+ failSound("mismatch solver [" + solverCores + "] brute [" + bruteCores + "]", text);
93
+ }
94
+ const solverModel = firstScenario(spec, 0, []);
95
+ const bruteModel = bruteLex(spec);
96
+ if (!sameBinding(solverModel, bruteModel)) {
97
+ failSound("model mismatch", text);
98
+ }
99
+ if (solverModel !== null && !allHold(spec, everyIndex(spec), solverModel)) {
100
+ failSound("model does not satisfy the requirements", text);
101
+ }
102
+ }
103
+
104
+ function everyIndex(spec: Spec): number[] {
105
+ const indexes: number[] = [];
106
+ for (let i = 0; i < spec.requirements.length; i++) indexes.push(i);
107
+ return indexes;
108
+ }
109
+
110
+ function sameBinding(left: Binding | null, right: Binding | null): boolean {
111
+ if (left === null || right === null) return left === null && right === null;
112
+ if (left.code.length !== right.code.length) return false;
113
+ for (let i = 0; i < left.code.length; i++) {
114
+ if (left.known[i] !== right.known[i]) return false;
115
+ if (left.known[i] && left.code[i] !== right.code[i]) return false;
116
+ }
117
+ return true;
118
+ }
119
+
120
+ function bruteLex(spec: Spec): Binding | null {
121
+ const env = new Binding(spec.symbols.length);
122
+ return walkLex(spec, env, 0);
123
+ }
124
+
125
+ function walkLex(spec: Spec, env: Binding, pos: number): Binding | null {
126
+ if (pos === spec.symbols.length) {
127
+ if (allHold(spec, everyIndex(spec), env)) return env.copy();
128
+ return null;
129
+ }
130
+ const count = domainSize(spec, spec.symbols[pos]);
131
+ for (let v = 0; v < count; v++) {
132
+ env.known[pos] = true;
133
+ env.code[pos] = codeAt(spec.symbols[pos], v);
134
+ const found = walkLex(spec, env, pos + 1);
135
+ if (found !== null) return found;
136
+ }
137
+ env.known[pos] = false;
138
+ return null;
139
+ }
140
+
141
+ function coreKey(groups: number[][]): string {
142
+ const parts: string[] = [];
143
+ for (let i = 0; i < groups.length; i++) {
144
+ const ids = groups[i];
145
+ let part = "";
146
+ for (let j = 0; j < ids.length; j++) {
147
+ if (j > 0) part += ",";
148
+ part += String(ids[j]);
149
+ }
150
+ parts.push(part);
151
+ }
152
+ for (let i = 0; i < parts.length; i++) {
153
+ for (let j = i + 1; j < parts.length; j++) {
154
+ if (parts[j] < parts[i]) {
155
+ const tmp = parts[i];
156
+ parts[i] = parts[j];
157
+ parts[j] = tmp;
158
+ }
159
+ }
160
+ }
161
+ return parts.join(";");
162
+ }
163
+
164
+ function bruteKey(spec: Spec): string {
165
+ const n = spec.requirements.length;
166
+ const found: number[] = [];
167
+ const limit = pow2(n);
168
+ for (let width = 1; width <= n; width++) {
169
+ for (let mask = 1; mask < limit; mask++) {
170
+ if (popcount(mask) !== width) continue;
171
+ let covered = false;
172
+ for (let f = 0; f < found.length; f++) {
173
+ if (includesAll(mask, found[f])) covered = true;
174
+ }
175
+ if (covered) continue;
176
+ if (realizable(spec, maskIndexes(mask, n))) continue;
177
+ let minimal = true;
178
+ for (let bit = 0; bit < n; bit++) {
179
+ if (!hasBit(mask, bit)) continue;
180
+ if (!realizable(spec, maskIndexes(mask - pow2(bit), n))) minimal = false;
181
+ }
182
+ if (minimal) found.push(mask);
183
+ }
184
+ }
185
+ const groups: number[][] = [];
186
+ for (let i = 0; i < found.length; i++) groups.push(maskIndexes(found[i], n));
187
+ return coreKey(groups);
188
+ }
189
+
190
+ function realizable(spec: Spec, indexes: number[]): boolean {
191
+ if (indexes.length === 0) return true;
192
+ const drivers: number[] = [];
193
+ const outputs: number[] = [];
194
+ for (let i = 0; i < spec.symbols.length; i++) {
195
+ if (isDriver(spec.symbols[i])) drivers.push(i);
196
+ else outputs.push(i);
197
+ }
198
+ const env = new Binding(spec.symbols.length);
199
+ return everyDriver(spec, indexes, drivers, outputs, env, 0);
200
+ }
201
+
202
+ function everyDriver(spec: Spec, indexes: number[], drivers: number[], outputs: number[], env: Binding, pos: number): boolean {
203
+ if (pos === drivers.length) return someOutput(spec, indexes, outputs, env, 0);
204
+ const si = drivers[pos];
205
+ const count = domainSize(spec, spec.symbols[si]);
206
+ for (let v = 0; v < count; v++) {
207
+ env.known[si] = true;
208
+ env.code[si] = codeAt(spec.symbols[si], v);
209
+ if (!everyDriver(spec, indexes, drivers, outputs, env, pos + 1)) return false;
210
+ }
211
+ env.known[si] = false;
212
+ return true;
213
+ }
214
+
215
+ function someOutput(spec: Spec, indexes: number[], outputs: number[], env: Binding, pos: number): boolean {
216
+ if (pos === outputs.length) return allHold(spec, indexes, env);
217
+ const si = outputs[pos];
218
+ const count = domainSize(spec, spec.symbols[si]);
219
+ for (let v = 0; v < count; v++) {
220
+ env.known[si] = true;
221
+ env.code[si] = codeAt(spec.symbols[si], v);
222
+ if (someOutput(spec, indexes, outputs, env, pos + 1)) return true;
223
+ }
224
+ env.known[si] = false;
225
+ return false;
226
+ }
227
+
228
+ function allHold(spec: Spec, indexes: number[], env: Binding): boolean {
229
+ for (let i = 0; i < indexes.length; i++) {
230
+ const req = spec.requirements[indexes[i]];
231
+ if (evalAssertion(req.antecedent, req.consequent, spec, env) !== T_TRUE) return false;
232
+ }
233
+ return true;
234
+ }
235
+
236
+ function randomSpec(rng: Rng): string {
237
+ const lines: string[] = [];
238
+ const useEnum = rng.next(2) === 1;
239
+ if (useEnum) lines.push("enum Color { red, blue, green }");
240
+ const bools: Named[] = [];
241
+ const ints: Named[] = [];
242
+ const enums: Named[] = [];
243
+ const count = 3 + rng.next(2);
244
+ let drivers = 0;
245
+ let outputs = 0;
246
+ for (let i = 0; i < count; i++) {
247
+ const kindRoll = rng.next(useEnum ? 3 : 2);
248
+ let kind = "bool";
249
+ if (kindRoll === 1) kind = "int";
250
+ if (kindRoll === 2) kind = "enum";
251
+ let role = "input";
252
+ if (kind === "bool") {
253
+ const roleRoll = rng.next(4);
254
+ if (roleRoll === 0) role = "event";
255
+ else if (roleRoll === 1) role = "state";
256
+ else if (roleRoll === 2) role = "input";
257
+ else role = "output";
258
+ } else if (rng.next(2) === 1) role = "output";
259
+ const name = kind.slice(0, 1) + String(i);
260
+ if (role === "output") outputs++;
261
+ else drivers++;
262
+ if (kind === "bool") {
263
+ bools.push({ name: name, kind: kind });
264
+ lines.push(role + " " + name + ': Bool "D."');
265
+ } else if (kind === "int") {
266
+ const max = 1 + rng.next(2);
267
+ ints.push({ name: name, kind: kind });
268
+ lines.push(role + " " + name + ": Int 0.." + String(max) + ' "D."');
269
+ } else {
270
+ enums.push({ name: name, kind: kind });
271
+ lines.push(role + " " + name + ': Color "D."');
272
+ }
273
+ }
274
+ if (drivers === 0) {
275
+ bools.push({ name: "bd", kind: "bool" });
276
+ lines.push('event bd: Bool "D."');
277
+ }
278
+ if (outputs === 0) {
279
+ bools.push({ name: "bo", kind: "bool" });
280
+ lines.push('output bo: Bool "D."');
281
+ }
282
+ const reqs = 1 + rng.next(4);
283
+ for (let r = 0; r < reqs; r++) {
284
+ const shape = rng.next(3);
285
+ const guard = expr(rng, bools, ints, enums);
286
+ const body = expr(rng, bools, ints, enums);
287
+ if (shape === 0) lines.push("R" + String(r + 1) + " The Box shall " + body + ".");
288
+ else if (shape === 1) lines.push("R" + String(r + 1) + " When " + guard + ", the Box shall " + body + ".");
289
+ else lines.push("R" + String(r + 1) + " If " + guard + ", then the Box shall " + body + ".");
290
+ }
291
+ return lines.join("\n") + "\n";
292
+ }
293
+
294
+ function expr(rng: Rng, bools: Named[], ints: Named[], enums: Named[]): string {
295
+ const roll = rng.next(6);
296
+ if (roll === 0 && bools.length > 0) return bools[rng.next(bools.length)].name;
297
+ if (roll === 1 && bools.length > 0) return "not " + bools[rng.next(bools.length)].name;
298
+ if (roll === 2 && bools.length > 1) {
299
+ const left = bools[rng.next(bools.length)].name;
300
+ const right = bools[rng.next(bools.length)].name;
301
+ if (rng.next(2) === 0) return left + " and " + right;
302
+ return left + " or " + right;
303
+ }
304
+ if (roll === 3 && ints.length > 0) {
305
+ const name = ints[rng.next(ints.length)].name;
306
+ const value = rng.next(3);
307
+ const opRoll = rng.next(3);
308
+ if (opRoll === 0) return name + " = " + String(value);
309
+ if (opRoll === 1) return name + " <= " + String(value);
310
+ return name + " >= " + String(value);
311
+ }
312
+ if (roll === 4 && enums.length > 0) {
313
+ const name = enums[rng.next(enums.length)].name;
314
+ const variants = ["red", "blue", "green"];
315
+ return name + " = " + variants[rng.next(3)];
316
+ }
317
+ if (bools.length > 0) return bools[rng.next(bools.length)].name;
318
+ if (ints.length > 0) return ints[0].name + " = 0";
319
+ if (enums.length > 0) return enums[0].name + " = red";
320
+ return "true";
321
+ }
322
+
323
+ function domainProduct(spec: Spec): number {
324
+ let product = 1;
325
+ for (let i = 0; i < spec.symbols.length; i++) {
326
+ product = product * domainSize(spec, spec.symbols[i]);
327
+ }
328
+ return product;
329
+ }
330
+
331
+ function failSound(message: string, text: string): void {
332
+ console.error("FAIL soundness " + message);
333
+ console.error(text);
334
+ process.exit(1);
335
+ }
336
+
337
+ function maskIndexes(mask: number, n: number): number[] {
338
+ const indexes: number[] = [];
339
+ for (let bit = 0; bit < n; bit++) {
340
+ if (hasBit(mask, bit)) indexes.push(bit);
341
+ }
342
+ return indexes;
343
+ }
344
+
345
+ function includesAll(candidate: number, found: number): boolean {
346
+ let bit = 0;
347
+ let rest = found;
348
+ while (rest > 0) {
349
+ if (rest % 2 === 1 && !hasBit(candidate, bit)) return false;
350
+ rest = Math.trunc(rest / 2);
351
+ bit++;
352
+ }
353
+ return true;
354
+ }
355
+
356
+ function hasBit(mask: number, bit: number): boolean {
357
+ const bitValue = pow2(bit);
358
+ return Math.trunc(mask / bitValue) % 2 === 1;
359
+ }
360
+
361
+ function pow2(bit: number): number {
362
+ let value = 1;
363
+ for (let i = 0; i < bit; i++) value = value * 2;
364
+ return value;
365
+ }
366
+
367
+ function popcount(mask: number): number {
368
+ let count = 0;
369
+ let rest = mask;
370
+ while (rest > 0) {
371
+ if (rest % 2 === 1) count++;
372
+ rest = Math.trunc(rest / 2);
373
+ }
374
+ return count;
375
+ }