Skip to content

Commit a49dbbc

Browse files
committed
feat(features): provide full language construct spec
The Features spec is rewritten to showcase all specl language constructs in a single example. This richer spec serves as both a learning resource and a testbed for verifying the full capabilities of the language.
1 parent a196699 commit a49dbbc

1 file changed

Lines changed: 176 additions & 42 deletions

File tree

Lines changed: 176 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,199 @@
11
module Features
22

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.
74
//
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.
924
//
1025
// 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 ───
1229

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
1536

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.
1939

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.
2355

2456
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
29133
}
30134

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
36140
}
37141

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
43145
}
44146

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)
50150
}
51151

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
57155
}
58156

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
61160
}
62161

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)
65199
}

0 commit comments

Comments
 (0)