5.1 KiB
PlusCal Reference
PlusCal is a higher-level language that compiles to TLA+. It provides imperative-style syntax while retaining full TLA+ expressiveness for operators and invariants.
Algorithm Structure
---- MODULE MySpec ----
EXTENDS Integers, Sequences, TLC
\* Constants and operators can go here
(* --algorithm algorithm_name
variables
x = 0;
y \in 1..5; \* nondeterministic: TLC checks ALL starting values
define
\* Operators that can reference PlusCal variables
TypeInvariant == x \in Nat /\ y \in 1..5
end define;
begin
Label1:
x := x + 1;
Label2:
y := y - 1;
end algorithm; *)
\* BEGIN TRANSLATION (auto-generated TLA+)
\* END TRANSLATION
\* Post-translation operators (can reference pc, stack, etc.)
====
Variables
variables
counter = 0; \* fixed initial value
seq = <<1, 2, 3>>; \* sequence
record = [a |-> 1, b |-> 2]; \* structure
x \in 1..10; \* nondeterministic: all values tested
items \in SUBSET {"a", "b", "c"}; \* all subsets tested
Using \in creates multiple starting states. TLC checks every combination.
Labels
Labels define the grain of atomicity. Everything in one label happens in a single step.
Rules
- Algorithm body must start with a label
whileloops must start with a label- Each variable can only be updated once per label (use
||for simultaneous assignment) gotomust be followed by a labelmacroandwithblocks cannot contain labels- If any branch in
if/eitherhas a label, the end of the block must be followed by a label
Simultaneous Assignment
Label:
seq[1] := seq[1] + 1 ||
seq[2] := seq[2] - 1;
Control Flow
if-elsif-else
if condition then
x := 1;
elsif other_condition then
x := 2;
else
x := 3;
end if;
while
Loop: \* REQUIRED label before while
while condition do
\* body (nonatomic — each iteration is a separate step)
end while;
either-or (nondeterministic choice)
either
x := 1;
or
x := 2;
or
x := 3;
end either;
with (temporary bindings and nondeterminism)
\* Deterministic
with tmp = x do
y := tmp + 1;
end with;
\* Nondeterministic — picks any element from set
with val \in {1, 2, 3} do
x := val;
end with;
goto, skip, assert, print
A:
goto C; \* must be followed by a label
B:
skip; \* noop
C:
assert x > 0; \* fails model check if false (needs EXTENDS TLC)
print x; \* debug output
await (blocking)
WaitForLock:
await lock = NULL; \* label only enabled when condition is TRUE
lock := self;
If no process can proceed → deadlock (reported as error by TLC).
Macros
Simple textual substitution. Must be defined before begin. Cannot contain labels.
macro send(queue, msg) begin
queue := Append(queue, msg);
end macro;
macro recv(queue, var) begin
await queue # <<>>;
var := Head(queue);
queue := Tail(queue);
end macro;
Procedures
Like macros but can contain labels. Use call to invoke, return to exit.
procedure enqueue(item)
begin
Enqueue:
queue := Append(queue, item);
return;
end procedure;
\* In a process:
call enqueue("hello");
Processes
Single Process
process worker = 1
begin
Work:
\* ...
end process;
Process Set
process worker \in 1..NumWorkers
variables local_var = 0; \* each process gets its own copy
begin
Work:
local_var := self; \* self = process value (1, 2, ... NumWorkers)
end process;
Rules
- All processes must have comparable types (all ints, all strings, or all model values)
- Different processes cannot share label names
selfkeyword returns the process's identifier value
Fairness
fair process worker \in Workers \* weak fairness — guaranteed to not stop forever
fair+ process worker \in Workers \* strong fairness — runs if repeatedly enabled
\* Per-label strong fairness:
CriticalStep:+
x := x + 1;
The pc Variable
The translator creates pc to track the current label:
- Single process:
pcis a string ("LabelName"or"Done") - Multiple processes:
pcis a function (pc[self])
Common pattern for post-completion invariants:
IsCorrect == pc = "Done" => result = ExpectedValue
\* With multiple processes:
AllDone == \A p \in Procs: pc[p] = "Done"
Correct == AllDone => result = Expected
Translation
PlusCal algorithms must be translated to TLA+ before model checking:
java -cp tla2tools.jar pcal.trans -nocfg spec.tla
The translation appears between \* BEGIN TRANSLATION and \* END TRANSLATION markers in the same file. Do not edit translation output by hand.
Define Block
Operators in define can reference PlusCal variables. They appear after variables and before begin:
define
TypeInvariant ==
/\ counter \in 0..MaxVal
/\ lock \in Processes \union {NULL}
IsUnique(s) ==
\A i, j \in 1..Len(s):
i # j => s[i] # s[j]
end define;