1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
// RUN: %dafny /compile:0 /rprint:"%t.rprint" "%s" > "%t"
// RUN: %diff "%s.expect" "%t"
// This file is a Dafny encoding of chapter 3 from "Concrete Semantics: With Isabelle/HOL" by
// Tobias Nipkow and Gerwin Klein.
// ----- lists -----
datatype List<T> = Nil | Cons(head: T, tail: List<T>)
function append(xs: List, ys: List): List
{
match xs
case Nil => ys
case Cons(x, tail) => Cons(x, append(tail, ys))
}
// ----- arithmetic expressions -----
type vname = string // variable names
datatype aexp = N(n: int) | V(x: vname) | Plus(0: aexp, 1: aexp) // arithmetic expressions
type val = int
type state = vname -> val
// In Dafny, functions can in general read the heap (which is not interesting to these examples--in fact, for
// the examples in this file, the fact that functions can read the state is just a distraction, so you can
// just ignore all the lines "reads s.reads" if you prefer) and may have preconditions (that is, the function
// may have some domain that is not specific than what its type says).
// The following predicate holds for a given s if s can be applied to any vname
predicate Total(s: state)
reads s.reads // this says that Total(s) can read anything that s can (on any input)
{
// the following line is the conjunction, over all x, of the precondition of the call s(x)
forall x :: s.requires(x)
}
function aval(a: aexp, s: state): val
reads s.reads
requires Total(s)
{
match a
case N(n) => n
case V(x) => s(x)
case Plus(a0, a1) => aval(a0, s) + aval(a1, s)
}
lemma Example0()
{
var y := aval(Plus(N(3), V("x")), x => 0);
// The following line confirms that y is 3. If you don't know what y is, you can use the
// verification debugger to figure it out, like this: Put any value in the assert (for example,
// "assert y == 0;". If you're lucky and picked the right value, the verifier will prove the
// assertion for you. If the verifier says it's unable to prove it, then click on the error
// (in the Dafny IDE), which brings up the verification debugger. There, inspect the value
// of y. This is probably the right value, but due to incompleteness in the verifier, it
// could happen that the value you see is some value that verifier wasn't able to properly
// exclude. Therefore, it's best to now take the value you see in the verification debugger,
// say K, and put that into the assert ("assert y == K;"), to have the verifier confirm that
// K really is the answer.
assert y == 3;
}
// ----- constant folding -----
function asimp_const(a: aexp): aexp
{
match a
case N(n) => a
case V(x) => a
case Plus(a0, a1) =>
var as0, as1 := asimp_const(a0), asimp_const(a1);
if as0.N? && as1.N? then
N(as0.n + as1.n)
else
Plus(as0, as1)
}
lemma AsimpConst(a: aexp, s: state)
requires Total(s)
ensures aval(asimp_const(a), s) == aval(a, s)
{
// by induction
forall a' | a' < a {
AsimpConst(a', s); // this invokes the induction hypothesis for every a' that is structurally smaller than a
}
/* Here is an alternative proof. In the first two cases, the proof is trivial. The Plus case uses two invocations
of the induction hypothesis.
match a
case N(n) =>
case V(x) =>
case Plus(a0, a1) =>
AsimpConst(a0, s);
AsimpConst(a1, s);
*/
}
// more constant folding
function plus(a0: aexp, a1: aexp): aexp
{
if a0.N? && a1.N? then
N(a0.n + a1.n)
else if a0.N? then
if a0.n == 0 then a1 else Plus(a0, a1)
else if a1.N? then
if a1.n == 0 then a0 else Plus(a0, a1)
else
Plus(a0, a1)
}
lemma AvalPlus(a0: aexp, a1: aexp, s: state)
requires Total(s)
ensures aval(plus(a0, a1), s) == aval(a0, s) + aval(a1, s)
{
// this proof is done automatically
}
function asimp(a: aexp): aexp
{
match a
case N(n) => a
case V(x) => a
case Plus(a0, a1) => plus(asimp(a0), asimp(a1))
}
lemma AsimpCorrect(a: aexp, s: state)
requires Total(s)
ensures aval(asimp(a), s) == aval(a, s)
{
// call the induction hypothesis on every value a' that is structurally smaller than a
forall a' | a' < a { AsimpCorrect(a', s); }
}
// ----- boolean expressions -----
datatype bexp = Bc(v: bool) | Not(op: bexp) | And(0: bexp, 1: bexp) | Less(a0: aexp, a1: aexp)
function bval(b: bexp, s: state): bool
reads s.reads
requires Total(s)
{
match b
case Bc(v) => v
case Not(b) => !bval(b, s)
case And(b0, b1) => bval(b0, s) && bval(b1, s)
case Less(a0, a1) => aval(a0, s) < aval(a1, s)
}
// constant folding for booleans
function not(b: bexp): bexp
{
match b
case Bc(b0) => Bc(!b0)
case Not(b0) => b0 // this case is not in the Nipkow and Klein book, but it seems a nice one to include
case And(_, _) => Not(b)
case Less(_, _) => Not(b)
}
function and(b0: bexp, b1: bexp): bexp
{
if b0.Bc? then
if b0.v then b1 else b0
else if b1.Bc? then
if b1.v then b0 else b1
else
And(b0, b1)
}
function less(a0: aexp, a1: aexp): bexp
{
if a0.N? && a1.N? then
Bc(a0.n < a1.n)
else
Less(a0, a1)
}
function bsimp(b: bexp): bexp
{
match b
case Bc(v) => b
case Not(b0) => not(bsimp(b0))
case And(b0, b1) => and(bsimp(b0), bsimp(b1))
case Less(a0, a1) => less(asimp(a0), asimp(a1))
}
lemma BsimpCorrect(b: bexp, s: state)
requires Total(s)
ensures bval(bsimp(b), s) == bval(b, s)
{
/* Here is one proof, which uses the induction hypothesis any anything smaller than b and also invokes
the lemma AsimpCorrect on anything smaller than b.
forall b' | b' < b { BsimpCorrect(b', s); }
forall a' | a' < b { AsimpCorrect(a', s); }
*/
// Here is another proof, which makes explicit the uses of the induction hypothesis and the other lemma.
match b
case Bc(v) =>
case Not(b0) =>
BsimpCorrect(b0, s);
case And(b0, b1) =>
BsimpCorrect(b0, s); BsimpCorrect(b1, s);
case Less(a0, a1) =>
AsimpCorrect(a0, s); AsimpCorrect(a1, s);
}
// ----- stack machine -----
datatype instr = LOADI(val) | LOAD(vname) | ADD
type stack = List<val>
function exec1(i: instr, s: state, stk: stack): stack
reads s.reads
requires Total(s)
{
match i
case LOADI(n) => Cons(n, stk)
case LOAD(x) => Cons(s(x), stk)
case ADD =>
if stk.Cons? && stk.tail.Cons? then
var Cons(a1, Cons(a0, tail)) := stk;
Cons(a0 + a1, tail)
else // stack underflow
Nil // an alternative would be to return Cons(n, Nil) for an arbitrary value n--that is what Nipkow and Klein do
}
function exec(ii: List<instr>, s: state, stk: stack): stack
reads s.reads
requires Total(s)
{
match ii
case Nil => stk
case Cons(i, rest) => exec(rest, s, exec1(i, s, stk))
}
// ----- compilation -----
function comp(a: aexp): List<instr>
{
match a
case N(n) => Cons(LOADI(n), Nil)
case V(x) => Cons(LOAD(x), Nil)
case Plus(a0, a1) => append(append(comp(a0), comp(a1)), Cons(ADD, Nil))
}
lemma CorrectCompilation(a: aexp, s: state, stk: stack)
requires Total(s)
ensures exec(comp(a), s, stk) == Cons(aval(a, s), stk)
{
match a
case N(n) =>
case V(x) =>
case Plus(a0, a1) =>
// This proof spells out the proof as a series of equality-preserving steps. Each
// expression in the calculation is terminated by a semi-colon. In some cases, a hint
// for the step is needed. Such hints are given in curly braces.
calc {
exec(comp(a), s, stk);
// definition of comp on Plus
exec(append(append(comp(a0), comp(a1)), Cons(ADD, Nil)), s, stk);
{ ExecAppend(append(comp(a0), comp(a1)), Cons(ADD, Nil), s, stk); }
exec(Cons(ADD, Nil), s, exec(append(comp(a0), comp(a1)), s, stk));
{ ExecAppend(comp(a0), comp(a1), s, stk); }
exec(Cons(ADD, Nil), s, exec(comp(a1), s, exec(comp(a0), s, stk)));
{ CorrectCompilation(a0, s, stk); }
exec(Cons(ADD, Nil), s, exec(comp(a1), s, Cons(aval(a0, s), stk)));
{ CorrectCompilation(a1, s, Cons(aval(a0, s), stk)); }
exec(Cons(ADD, Nil), s, Cons(aval(a1, s), Cons(aval(a0, s), stk)));
// definition of comp on ADD
Cons(aval(a1, s) + aval(a0, s), stk);
// definition of aval on Plus
Cons(aval(a, s), stk);
}
}
lemma ExecAppend(ii0: List<instr>, ii1: List<instr>, s: state, stk: stack)
requires Total(s)
ensures exec(append(ii0, ii1), s, stk) == exec(ii1, s, exec(ii0, s, stk))
{
// the proof (which is by induction) is done automatically
}
|