@orcalang/orca-lang 0.1.19 → 0.1.24
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/dist/compiler/dt-compiler.d.ts +4 -0
- package/dist/compiler/dt-compiler.d.ts.map +1 -1
- package/dist/compiler/dt-compiler.js +354 -4
- package/dist/compiler/dt-compiler.js.map +1 -1
- package/dist/health-check.js +75 -0
- package/dist/health-check.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/parser/ast-to-markdown.d.ts.map +1 -1
- package/dist/parser/ast-to-markdown.js +3 -1
- package/dist/parser/ast-to-markdown.js.map +1 -1
- package/dist/parser/ast.d.ts +1 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/dt-ast.d.ts +11 -1
- package/dist/parser/dt-ast.d.ts.map +1 -1
- package/dist/parser/dt-parser.d.ts.map +1 -1
- package/dist/parser/dt-parser.js +40 -8
- package/dist/parser/dt-parser.js.map +1 -1
- package/dist/parser/markdown-parser.d.ts.map +1 -1
- package/dist/parser/markdown-parser.js +14 -4
- package/dist/parser/markdown-parser.js.map +1 -1
- package/dist/skills.d.ts +3 -2
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +486 -28
- package/dist/skills.js.map +1 -1
- package/dist/tools.js +4 -4
- package/dist/tools.js.map +1 -1
- package/dist/verifier/dt-verifier.d.ts +28 -1
- package/dist/verifier/dt-verifier.d.ts.map +1 -1
- package/dist/verifier/dt-verifier.js +591 -32
- package/dist/verifier/dt-verifier.js.map +1 -1
- package/dist/verifier/properties.d.ts +4 -0
- package/dist/verifier/properties.d.ts.map +1 -1
- package/dist/verifier/properties.js +56 -20
- package/dist/verifier/properties.js.map +1 -1
- package/dist/verifier/structural.d.ts.map +1 -1
- package/dist/verifier/structural.js +6 -1
- package/dist/verifier/structural.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler/dt-compiler.ts +374 -4
- package/src/health-check.ts +79 -0
- package/src/index.ts +5 -1
- package/src/parser/ast-to-markdown.ts +2 -1
- package/src/parser/ast.ts +1 -0
- package/src/parser/dt-ast.ts +4 -2
- package/src/parser/dt-parser.ts +46 -8
- package/src/parser/markdown-parser.ts +11 -3
- package/src/skills.ts +520 -30
- package/src/tools.ts +4 -4
- package/src/verifier/dt-verifier.ts +639 -30
- package/src/verifier/properties.ts +78 -23
- package/src/verifier/structural.ts +5 -1
|
@@ -1,29 +1,66 @@
|
|
|
1
|
-
import { MachineDef, Property, ReachabilityProperty, PassesThroughProperty, RespondsProperty, InvariantProperty, GuardRef, GuardDef } from '../parser/ast.js';
|
|
1
|
+
import { MachineDef, Property, ReachabilityProperty, PassesThroughProperty, RespondsProperty, InvariantProperty, GuardRef, GuardDef, GuardExpression } from '../parser/ast.js';
|
|
2
2
|
import { VerificationResult, VerificationError, MachineAnalysis, StateInfo } from './types.js';
|
|
3
3
|
import { analyzeMachine, flattenStates, FlattenedState } from './structural.js';
|
|
4
4
|
import { resolveGuardExpression, isExpressionStaticallyFalse } from './determinism.js';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_MAX_STATES = 64;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Check if a guard expression is statically false given DT output domain constraints.
|
|
10
|
+
* A comparison `ctx.field == value` is false if the DT never outputs `value` for `field`.
|
|
11
|
+
* Handles AND (false if either branch is domain-blocked) and OR (false only if both are).
|
|
12
|
+
*/
|
|
13
|
+
function isExpressionBlockedByDomain(
|
|
14
|
+
expr: GuardExpression,
|
|
15
|
+
domain: Map<string, Set<string>>
|
|
16
|
+
): boolean {
|
|
17
|
+
if (expr.kind === 'compare' && expr.op === 'eq') {
|
|
18
|
+
if (expr.left.path.length === 2 && expr.left.path[0] === 'ctx') {
|
|
19
|
+
const field = expr.left.path[1];
|
|
20
|
+
const value = String(expr.right.value);
|
|
21
|
+
const possible = domain.get(field);
|
|
22
|
+
if (possible !== undefined && !possible.has(value)) return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (expr.kind === 'and') {
|
|
26
|
+
return isExpressionBlockedByDomain(expr.left, domain) ||
|
|
27
|
+
isExpressionBlockedByDomain(expr.right, domain);
|
|
28
|
+
}
|
|
29
|
+
if (expr.kind === 'or') {
|
|
30
|
+
return isExpressionBlockedByDomain(expr.left, domain) &&
|
|
31
|
+
isExpressionBlockedByDomain(expr.right, domain);
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
8
36
|
/**
|
|
9
37
|
* Check if a transition's guard is statically false (can never fire).
|
|
10
38
|
* Used in guard-aware BFS to prune impossible transitions.
|
|
39
|
+
* When dtOutputDomain is provided, also prunes guards that compare DT output
|
|
40
|
+
* fields against values the DT never produces.
|
|
11
41
|
*/
|
|
12
42
|
function isTransitionStaticallyBlocked(
|
|
13
43
|
transition: { guard?: GuardRef },
|
|
14
|
-
guardDefs: Map<string, GuardDef
|
|
44
|
+
guardDefs: Map<string, GuardDef>,
|
|
45
|
+
dtOutputDomain?: Map<string, Set<string>>
|
|
15
46
|
): boolean {
|
|
16
47
|
if (!transition.guard) return false; // No guard = always possible
|
|
17
48
|
|
|
18
49
|
const resolved = resolveGuardExpression(transition.guard, guardDefs);
|
|
19
50
|
if (!resolved) return false; // Can't resolve = assume possible
|
|
20
51
|
|
|
21
|
-
|
|
52
|
+
if (isExpressionStaticallyFalse(resolved)) return true;
|
|
53
|
+
|
|
54
|
+
if (dtOutputDomain && isExpressionBlockedByDomain(resolved, dtOutputDomain)) return true;
|
|
55
|
+
|
|
56
|
+
return false;
|
|
22
57
|
}
|
|
23
58
|
|
|
24
59
|
/**
|
|
25
60
|
* BFS from source state, returning reachable set and parent map for counterexample traces.
|
|
26
61
|
* Supports guard-aware mode: skips transitions with statically false guards.
|
|
62
|
+
* When dtOutputDomain is provided, also skips transitions whose guards are impossible
|
|
63
|
+
* given the DT output domain constraints.
|
|
27
64
|
*/
|
|
28
65
|
function bfs(
|
|
29
66
|
stateMap: Map<string, StateInfo>,
|
|
@@ -31,7 +68,8 @@ function bfs(
|
|
|
31
68
|
options?: {
|
|
32
69
|
excludeState?: string;
|
|
33
70
|
maxDepth?: number;
|
|
34
|
-
guardDefs?: Map<string, GuardDef>;
|
|
71
|
+
guardDefs?: Map<string, GuardDef>;
|
|
72
|
+
dtOutputDomain?: Map<string, Set<string>>;
|
|
35
73
|
}
|
|
36
74
|
): { reachable: Set<string>; parent: Map<string, { state: string; event: string; guard?: string }> } {
|
|
37
75
|
const reachable = new Set<string>();
|
|
@@ -56,8 +94,8 @@ function bfs(
|
|
|
56
94
|
// Skip excluded state
|
|
57
95
|
if (options?.excludeState && target === options.excludeState) continue;
|
|
58
96
|
|
|
59
|
-
// Guard-aware: skip transitions with statically false guards
|
|
60
|
-
if (options?.guardDefs && isTransitionStaticallyBlocked(t, options.guardDefs)) continue;
|
|
97
|
+
// Guard-aware: skip transitions with statically false guards (including DT domain constraints)
|
|
98
|
+
if (options?.guardDefs && isTransitionStaticallyBlocked(t, options.guardDefs, options.dtOutputDomain)) continue;
|
|
61
99
|
|
|
62
100
|
if (!reachable.has(target)) {
|
|
63
101
|
reachable.add(target);
|
|
@@ -157,7 +195,8 @@ function checkReachable(
|
|
|
157
195
|
prop: ReachabilityProperty,
|
|
158
196
|
analysis: MachineAnalysis,
|
|
159
197
|
flattenedStates: FlattenedState[],
|
|
160
|
-
guardDefs: Map<string, GuardDef
|
|
198
|
+
guardDefs: Map<string, GuardDef>,
|
|
199
|
+
dtOutputDomain?: Map<string, Set<string>>
|
|
161
200
|
): VerificationError[] {
|
|
162
201
|
const errors: VerificationError[] = [];
|
|
163
202
|
|
|
@@ -166,7 +205,7 @@ function checkReachable(
|
|
|
166
205
|
const toRes = resolveStateName(prop.to, flattenedStates);
|
|
167
206
|
if (toRes.error) return [toRes.error];
|
|
168
207
|
|
|
169
|
-
const { reachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
|
|
208
|
+
const { reachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
|
|
170
209
|
|
|
171
210
|
if (!reachable.has(toRes.resolved)) {
|
|
172
211
|
errors.push({
|
|
@@ -185,7 +224,8 @@ function checkUnreachable(
|
|
|
185
224
|
prop: ReachabilityProperty,
|
|
186
225
|
analysis: MachineAnalysis,
|
|
187
226
|
flattenedStates: FlattenedState[],
|
|
188
|
-
guardDefs: Map<string, GuardDef
|
|
227
|
+
guardDefs: Map<string, GuardDef>,
|
|
228
|
+
dtOutputDomain?: Map<string, Set<string>>
|
|
189
229
|
): VerificationError[] {
|
|
190
230
|
const errors: VerificationError[] = [];
|
|
191
231
|
|
|
@@ -194,7 +234,7 @@ function checkUnreachable(
|
|
|
194
234
|
const toRes = resolveStateName(prop.to, flattenedStates);
|
|
195
235
|
if (toRes.error) return [toRes.error];
|
|
196
236
|
|
|
197
|
-
const { reachable, parent } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
|
|
237
|
+
const { reachable, parent } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
|
|
198
238
|
|
|
199
239
|
if (reachable.has(toRes.resolved)) {
|
|
200
240
|
const path = reconstructPath(parent, fromRes.resolved, toRes.resolved);
|
|
@@ -218,7 +258,8 @@ function checkPassesThrough(
|
|
|
218
258
|
prop: PassesThroughProperty,
|
|
219
259
|
analysis: MachineAnalysis,
|
|
220
260
|
flattenedStates: FlattenedState[],
|
|
221
|
-
guardDefs: Map<string, GuardDef
|
|
261
|
+
guardDefs: Map<string, GuardDef>,
|
|
262
|
+
dtOutputDomain?: Map<string, Set<string>>
|
|
222
263
|
): VerificationError[] {
|
|
223
264
|
const errors: VerificationError[] = [];
|
|
224
265
|
|
|
@@ -230,7 +271,7 @@ function checkPassesThrough(
|
|
|
230
271
|
if (throughRes.error) return [throughRes.error];
|
|
231
272
|
|
|
232
273
|
// First check: is target reachable from source at all?
|
|
233
|
-
const { reachable: fullReachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
|
|
274
|
+
const { reachable: fullReachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
|
|
234
275
|
if (!fullReachable.has(toRes.resolved)) {
|
|
235
276
|
errors.push({
|
|
236
277
|
code: 'PROPERTY_PATH_FAIL',
|
|
@@ -246,6 +287,7 @@ function checkPassesThrough(
|
|
|
246
287
|
const { reachable: withoutThrough, parent } = bfs(analysis.stateMap, fromRes.resolved, {
|
|
247
288
|
excludeState: throughRes.resolved,
|
|
248
289
|
guardDefs,
|
|
290
|
+
dtOutputDomain,
|
|
249
291
|
});
|
|
250
292
|
|
|
251
293
|
if (withoutThrough.has(toRes.resolved)) {
|
|
@@ -265,14 +307,15 @@ function checkPassesThrough(
|
|
|
265
307
|
function checkLive(
|
|
266
308
|
analysis: MachineAnalysis,
|
|
267
309
|
flattenedStates: FlattenedState[],
|
|
268
|
-
guardDefs: Map<string, GuardDef
|
|
310
|
+
guardDefs: Map<string, GuardDef>,
|
|
311
|
+
dtOutputDomain?: Map<string, Set<string>>
|
|
269
312
|
): VerificationError[] {
|
|
270
313
|
const errors: VerificationError[] = [];
|
|
271
314
|
|
|
272
315
|
if (!analysis.initialState) return errors;
|
|
273
316
|
|
|
274
317
|
// Find all reachable states from initial
|
|
275
|
-
const { reachable: reachableFromInitial } = bfs(analysis.stateMap, analysis.initialState.name, { guardDefs });
|
|
318
|
+
const { reachable: reachableFromInitial } = bfs(analysis.stateMap, analysis.initialState.name, { guardDefs, dtOutputDomain });
|
|
276
319
|
|
|
277
320
|
// Find all final state names
|
|
278
321
|
const finalStateNames = new Set(analysis.finalStates.map(s => s.name));
|
|
@@ -285,7 +328,7 @@ function checkLive(
|
|
|
285
328
|
const fs = flattenedStates.find(f => f.name === stateName);
|
|
286
329
|
if (fs && (fs.isCompound || fs.isRegion)) continue;
|
|
287
330
|
|
|
288
|
-
const { reachable: reachableFromState } = bfs(analysis.stateMap, stateName, { guardDefs });
|
|
331
|
+
const { reachable: reachableFromState } = bfs(analysis.stateMap, stateName, { guardDefs, dtOutputDomain });
|
|
289
332
|
|
|
290
333
|
let canReachFinal = false;
|
|
291
334
|
for (const finalName of finalStateNames) {
|
|
@@ -313,7 +356,8 @@ function checkResponds(
|
|
|
313
356
|
prop: RespondsProperty,
|
|
314
357
|
analysis: MachineAnalysis,
|
|
315
358
|
flattenedStates: FlattenedState[],
|
|
316
|
-
guardDefs: Map<string, GuardDef
|
|
359
|
+
guardDefs: Map<string, GuardDef>,
|
|
360
|
+
dtOutputDomain?: Map<string, Set<string>>
|
|
317
361
|
): VerificationError[] {
|
|
318
362
|
const errors: VerificationError[] = [];
|
|
319
363
|
|
|
@@ -325,11 +369,12 @@ function checkResponds(
|
|
|
325
369
|
const { reachable } = bfs(analysis.stateMap, fromRes.resolved, {
|
|
326
370
|
maxDepth: prop.within,
|
|
327
371
|
guardDefs,
|
|
372
|
+
dtOutputDomain,
|
|
328
373
|
});
|
|
329
374
|
|
|
330
375
|
if (!reachable.has(toRes.resolved)) {
|
|
331
376
|
// Check if it's reachable at all (just beyond the bound)
|
|
332
|
-
const { reachable: unbounded } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
|
|
377
|
+
const { reachable: unbounded } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
|
|
333
378
|
const reachableButBeyondBound = unbounded.has(toRes.resolved);
|
|
334
379
|
|
|
335
380
|
const suffix = reachableButBeyondBound
|
|
@@ -453,7 +498,16 @@ function checkMachineSize(
|
|
|
453
498
|
|
|
454
499
|
// --- Main entry point ---
|
|
455
500
|
|
|
456
|
-
export function checkProperties(
|
|
501
|
+
export function checkProperties(
|
|
502
|
+
machine: MachineDef,
|
|
503
|
+
options?: {
|
|
504
|
+
maxStates?: number;
|
|
505
|
+
/** DT output domain from co-located aligned decision tables. When provided,
|
|
506
|
+
* the guard-aware BFS will prune transitions whose guards compare DT output
|
|
507
|
+
* fields against values the DT never produces, giving more precise results. */
|
|
508
|
+
dtOutputDomain?: Map<string, Set<string>>;
|
|
509
|
+
}
|
|
510
|
+
): VerificationResult {
|
|
457
511
|
const maxStates = options?.maxStates ?? DEFAULT_MAX_STATES;
|
|
458
512
|
const flattenedStates = flattenStates(machine.states);
|
|
459
513
|
const errors: VerificationError[] = [];
|
|
@@ -470,6 +524,7 @@ export function checkProperties(machine: MachineDef, options?: { maxStates?: num
|
|
|
470
524
|
}
|
|
471
525
|
|
|
472
526
|
const analysis = analyzeMachine(machine);
|
|
527
|
+
const dtOutputDomain = options?.dtOutputDomain;
|
|
473
528
|
|
|
474
529
|
// Build guard definition map for guard-aware BFS
|
|
475
530
|
const guardDefs = new Map<string, GuardDef>();
|
|
@@ -480,19 +535,19 @@ export function checkProperties(machine: MachineDef, options?: { maxStates?: num
|
|
|
480
535
|
for (const prop of machine.properties) {
|
|
481
536
|
switch (prop.kind) {
|
|
482
537
|
case 'reachable':
|
|
483
|
-
errors.push(...checkReachable(prop, analysis, flattenedStates, guardDefs));
|
|
538
|
+
errors.push(...checkReachable(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
|
|
484
539
|
break;
|
|
485
540
|
case 'unreachable':
|
|
486
|
-
errors.push(...checkUnreachable(prop, analysis, flattenedStates, guardDefs));
|
|
541
|
+
errors.push(...checkUnreachable(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
|
|
487
542
|
break;
|
|
488
543
|
case 'passes_through':
|
|
489
|
-
errors.push(...checkPassesThrough(prop, analysis, flattenedStates, guardDefs));
|
|
544
|
+
errors.push(...checkPassesThrough(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
|
|
490
545
|
break;
|
|
491
546
|
case 'live':
|
|
492
|
-
errors.push(...checkLive(analysis, flattenedStates, guardDefs));
|
|
547
|
+
errors.push(...checkLive(analysis, flattenedStates, guardDefs, dtOutputDomain));
|
|
493
548
|
break;
|
|
494
549
|
case 'responds':
|
|
495
|
-
errors.push(...checkResponds(prop, analysis, flattenedStates, guardDefs));
|
|
550
|
+
errors.push(...checkResponds(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
|
|
496
551
|
break;
|
|
497
552
|
case 'invariant':
|
|
498
553
|
errors.push(...checkInvariant(prop, machine, flattenedStates));
|
|
@@ -183,7 +183,11 @@ export function analyzeMachine(machine: MachineDef): MachineAnalysis {
|
|
|
183
183
|
if (fs) {
|
|
184
184
|
// Find the original state definition
|
|
185
185
|
const originalState = findOriginalState(machine.states, fs.simpleName, fs.parentName);
|
|
186
|
-
if (originalState?.
|
|
186
|
+
if (originalState?.ignoredAll) {
|
|
187
|
+
for (const event of machine.events) {
|
|
188
|
+
info.eventsIgnored.add(event.name);
|
|
189
|
+
}
|
|
190
|
+
} else if (originalState?.ignoredEvents) {
|
|
187
191
|
for (const event of originalState.ignoredEvents) {
|
|
188
192
|
info.eventsIgnored.add(event);
|
|
189
193
|
}
|