|
1 | 1 | module Features |
2 | 2 |
|
3 | | -// Demonstrates less common specl features: |
4 | | -// - Set[T] with union, diff, in, intersect, len |
5 | | -// - if-then-else expressions in actions |
6 | | -// - Nondeterministic init (omit constraint to explore all valid initial states) |
| 3 | +// Comprehensive showcase of every specl language construct. |
7 | 4 | // |
8 | | -// 2 workers compete for 3 resources. |
| 5 | +// Domain: workers compete for tasks from a shared queue. Each worker |
| 6 | +// claims tasks and earns a score. The queue is bounded. |
| 7 | +// |
| 8 | +// Language features demonstrated (see section comments throughout): |
| 9 | +// Declarations: var, func, init, action, invariant, auxiliary invariant, view |
| 10 | +// Types: range, Bool, Set[T], Dict[K,V], Seq[T], (T1,T2) tuples |
| 11 | +// Expressions: let..in, if-then-else, fix, all/any quantifiers (nested) |
| 12 | +// Set ops: union, diff, intersect, in, not(..), subset_of, powerset, union_all, len |
| 13 | +// Set comprehension: {x in S if P} |
| 14 | +// Dict ops: comprehension, merge (|), lookup ([]), keys, values |
| 15 | +// Seq ops: literal [], concat ++, head, tail, len |
| 16 | +// Bool ops: and, or, not, implies, iff |
| 17 | +// Arithmetic: +, -, *, /, % |
| 18 | +// Tuple: literal (a,b), field access .0 .1 |
| 19 | +// Other: nondeterministic init, require guards, let binding in actions |
| 20 | +// |
| 21 | +// Note: const declarations (e.g. `const N: 0..10`) parameterize specs at |
| 22 | +// check time via `-c N=3`. See two-phase-commit.specl, paxos.specl, and |
| 23 | +// raft.specl for const-parameterized examples. |
9 | 24 | // |
10 | 25 | // Use: No constants needed |
11 | | -// Quick: specl check features.specl --no-deadlock --bfs |
| 26 | +// Quick: specl check features.specl --no-deadlock --bfs --no-auto |
| 27 | + |
| 28 | +// ─── var: mutable state ─── |
12 | 29 |
|
13 | | -// Which worker holds each resource (0 = free, 1..2 = worker id) |
14 | | -var owner: Dict[0..2, 0..2] |
| 30 | +var queue: Seq[0..3] // Seq type |
| 31 | +var busy: Set[0..1] // Set type |
| 32 | +var done: Dict[0..1, 0..3] // Dict type |
| 33 | +var next_id: 0..3 // range type |
| 34 | +var paused: Bool // Bool type |
| 35 | +var last_claim: (0..1, 0..3) // Tuple type |
15 | 36 |
|
16 | | -// Resources held by worker 1 and worker 2 |
17 | | -var held_1: Set[0..2] |
18 | | -var held_2: Set[0..2] |
| 37 | +// ─── view: project state for deduplication ─── |
| 38 | +// States identical on these vars are merged even if last_claim differs. |
19 | 39 |
|
20 | | -// Nondeterministic: mode unconstrained, checker explores all 3 initial values. |
21 | | -// 0 = idle, 1 = acquiring, 2 = releasing |
22 | | -var mode: 0..2 |
| 40 | +view { queue, busy, done, next_id, paused } |
| 41 | + |
| 42 | +// ─── func: reusable helper expressions ─── |
| 43 | + |
| 44 | +func ActiveWorkers() { |
| 45 | + {w in 0..1 if done[w] > 0} // Set comprehension with filter |
| 46 | +} |
| 47 | + |
| 48 | +func Max(a, b) { |
| 49 | + if a > b then a else b // if-then-else, multi-param func |
| 50 | +} |
| 51 | + |
| 52 | +// ─── init: starting state ─── |
| 53 | +// paused intentionally unconstrained: nondeterministic initial state. |
| 54 | +// The checker explores paused=true and paused=false. |
23 | 55 |
|
24 | 56 | init { |
25 | | - owner = {r: 0 for r in 0..2}; |
26 | | - held_1 = {}; |
27 | | - held_2 = {}; |
28 | | - // mode intentionally unconstrained: nondeterministic initial state |
| 57 | + queue = []; // empty Seq literal |
| 58 | + busy = {}; // empty Set literal |
| 59 | + done = {w: 0 for w in 0..1}; // Dict comprehension |
| 60 | + next_id = 0; |
| 61 | + last_claim = (0, 0); // Tuple literal |
| 62 | +} |
| 63 | + |
| 64 | +// ─── action: state transitions (guarded by require) ─── |
| 65 | + |
| 66 | +action Enqueue() { |
| 67 | + require not paused; // Bool: not |
| 68 | + require next_id < 3; // Arithmetic: < |
| 69 | + require len(queue) < 3; // Seq: len |
| 70 | + queue = queue ++ [next_id]; // Seq: concat (++) |
| 71 | + next_id = next_id + 1; // Arithmetic: + |
| 72 | +} |
| 73 | + |
| 74 | +action Claim(w: 0..1) { |
| 75 | + require len(queue) > 0; |
| 76 | + require not(w in busy); // Set: membership + not() |
| 77 | + let task = head(queue); // let binding in action, Seq: head |
| 78 | + queue = tail(queue); // Seq: tail |
| 79 | + busy = busy union {w}; // Set: union |
| 80 | + last_claim = (w, task); // Tuple literal |
| 81 | + done = done | {w: done[w]}; // Dict: merge (|), lookup ([]) |
| 82 | +} |
| 83 | + |
| 84 | +action Complete(w: 0..1) { |
| 85 | + require w in busy; // Set: in |
| 86 | + busy = busy diff {w}; // Set: diff |
| 87 | + done = done | {w: done[w] + 1}; // Dict: merge update |
| 88 | +} |
| 89 | + |
| 90 | +action TogglePause() { |
| 91 | + paused = if paused then false else true; // if-then-else expression |
| 92 | +} |
| 93 | + |
| 94 | +// ─── invariant: safety properties checked in every reachable state ─── |
| 95 | + |
| 96 | +// all quantifier + implies |
| 97 | +invariant ScoresValid { |
| 98 | + all w in 0..1: done[w] >= 0 implies done[w] <= 3 |
| 99 | +} |
| 100 | + |
| 101 | +// Nested quantifiers |
| 102 | +invariant NoDuplicateOverflow { |
| 103 | + all w1 in 0..1: |
| 104 | + all w2 in 0..1: |
| 105 | + (w1 != w2) implies (done[w1] + done[w2] <= 3) |
| 106 | +} |
| 107 | + |
| 108 | +// let..in binding (expression form, inside invariant) |
| 109 | +invariant QueueBounded { |
| 110 | + let q_len = len(queue) in |
| 111 | + let total = q_len + next_id in |
| 112 | + total <= 6 |
| 113 | +} |
| 114 | + |
| 115 | +// Set: intersect |
| 116 | +invariant EmptyIntersect { |
| 117 | + len(busy intersect {}) == 0 |
| 118 | +} |
| 119 | + |
| 120 | +// Set: subset_of + set comprehension with filter |
| 121 | +invariant DoneWorkersValid { |
| 122 | + {w in 0..1 if done[w] > 0} subset_of 0..1 |
| 123 | +} |
| 124 | + |
| 125 | +// iff: biconditional |
| 126 | +invariant PauseSymmetry { |
| 127 | + (paused iff paused) and (not paused iff not paused) |
| 128 | +} |
| 129 | + |
| 130 | +// any quantifier + or |
| 131 | +invariant SomeWorkerExists { |
| 132 | + any w in 0..1: done[w] >= 0 or w in busy |
29 | 133 | } |
30 | 134 |
|
31 | | -action Acquire1(r: 0..2) { |
32 | | - require owner[r] == 0; |
33 | | - owner = owner | {r: 1}; |
34 | | - held_1 = held_1 union {r}; |
35 | | - mode = if len(held_1) == 0 then 1 else mode; |
| 135 | +// Arithmetic: division (/), modular (%) |
| 136 | +invariant ArithmeticDemo { |
| 137 | + let half = 3 / 2 in |
| 138 | + let remainder = next_id % 4 in |
| 139 | + remainder >= 0 and half >= 0 |
36 | 140 | } |
37 | 141 |
|
38 | | -action Release1(r: 0..2) { |
39 | | - require r in held_1; |
40 | | - owner = owner | {r: 0}; |
41 | | - held_1 = held_1 diff {r}; |
42 | | - mode = 2; |
| 142 | +// Tuple: field access .0, .1 |
| 143 | +invariant LastClaimBounded { |
| 144 | + last_claim.0 <= 1 and last_claim.1 <= 3 |
43 | 145 | } |
44 | 146 |
|
45 | | -action Acquire2(r: 0..2) { |
46 | | - require owner[r] == 0; |
47 | | - owner = owner | {r: 2}; |
48 | | - held_2 = held_2 union {r}; |
49 | | - mode = if len(held_2) == 0 then 1 else mode; |
| 147 | +// Set: powerset |
| 148 | +invariant BusyInPowerset { |
| 149 | + busy in powerset(0..1) |
50 | 150 | } |
51 | 151 |
|
52 | | -action Release2(r: 0..2) { |
53 | | - require r in held_2; |
54 | | - owner = owner | {r: 0}; |
55 | | - held_2 = held_2 diff {r}; |
56 | | - mode = 2; |
| 152 | +// Dict: keys |
| 153 | +invariant AllWorkersTracked { |
| 154 | + len(keys(done)) == 2 |
57 | 155 | } |
58 | 156 |
|
59 | | -invariant NoConflict { |
60 | | - len(held_1 intersect held_2) == 0 |
| 157 | +// Dict: values |
| 158 | +invariant ValuesNonNegative { |
| 159 | + all v in values(done): v >= 0 |
61 | 160 | } |
62 | 161 |
|
63 | | -invariant OwnerConsistency { |
64 | | - all r in held_1: owner[r] == 1 |
| 162 | +// fix: find first value satisfying predicate |
| 163 | +invariant FixDemo { |
| 164 | + len(busy) == 0 or ( |
| 165 | + let first_busy = (fix w in 0..1 : w in busy) in |
| 166 | + first_busy >= 0 |
| 167 | + ) |
| 168 | +} |
| 169 | + |
| 170 | +// Set: union_all over set of sets |
| 171 | +invariant UnionAllDemo { |
| 172 | + let per_worker = {{w} for w in 0..1 if w in busy} in |
| 173 | + union_all(per_worker) == busy |
| 174 | +} |
| 175 | + |
| 176 | +// func call in invariant |
| 177 | +invariant ActiveWorkersValid { |
| 178 | + len(ActiveWorkers()) <= 2 |
| 179 | +} |
| 180 | + |
| 181 | +// Multi-param func + if-then-else |
| 182 | +invariant MaxDemo { |
| 183 | + Max(len(busy), len(queue)) >= 0 |
| 184 | +} |
| 185 | + |
| 186 | +// Arithmetic: subtraction (-), multiplication (*) |
| 187 | +invariant ArithMultSub { |
| 188 | + let product = 2 * next_id in |
| 189 | + let delta = 3 - len(queue) in |
| 190 | + product >= 0 and delta >= 0 - 3 |
| 191 | +} |
| 192 | + |
| 193 | +// ─── auxiliary invariant: assumed as hypothesis, not checked as goal ─── |
| 194 | +// Useful for inductive strengthening in symbolic/k-induction mode. |
| 195 | +// See examples/other/auxiliary-test.specl for a detailed example. |
| 196 | + |
| 197 | +auxiliary invariant NextIdBound { |
| 198 | + next_id >= len(done) |
65 | 199 | } |
0 commit comments