@@ -10,20 +10,55 @@ open Lean Elab Command
1010
1111macro "assert" t:term : term => `(assertGadget $t)
1212
13- macro "decreasing" t:term : term => `(decreasingGadget $t)
13+ /--
14+ Elaboration rule for the `decreasing` annotation. Here we will
15+ - Throw an error if the termination semantics is unspecified.
16+ - Throw an error if the decreasing annotation is missing in total correctness semantics.
17+ - Throw a warning if the decreasing annotation is present in partial correctness semantics.
18+ -/
19+ elab "decreasing" t:term : term => do
20+ let opts <- getOptions
21+ let semantics := opts.getString (defVal := "unspecified" ) `loom.semantics.termination
22+ if semantics = "unspecified" then
23+ throwError "First, you need to specify the termination semantics using `set_option loom.semantics.termination <partial/total>`"
24+ let decr <- `(decreasingGadget $t)
25+ let decrIsNone := match t with
26+ | `(none) => true
27+ | _ => false
28+ let errorsEnabled := opts.getBool (defVal := true ) `loom.linter.errors
29+ let warningsEnabled := opts.getBool (defVal := true ) `loom.linter.warnings
30+ if semantics = "total" && decrIsNone && errorsEnabled then
31+ throwError "Decreasing annotations are required in total correctness semantics
32+ To turn off this error, use `set_option loom.linter.errors false`."
33+ if semantics = "partial" && !decrIsNone && warningsEnabled then
34+ logWarningAt (<- getRef) "Decreasing annotations are not checked in partial correctness semantics
35+ To turn off this warning, use `set_option loom.linter.warnings false`."
36+ Term.elabTerm decr none
37+
38+ elab "invariants" invs:term : term => do
39+ let opts <- getOptions
40+ let invsIsEmpty := match invs with
41+ | `([]) => true
42+ | _ => false
43+ let warningsEnabled := opts.getBool (defVal := true ) `loom.linter.warnings
44+ if invsIsEmpty && warningsEnabled then
45+ logWarningAt (<- getRef) "Invariant annotations are not specified, so the loop invariant is assumed to be just `True`.
46+ To turn off this warning, use `set_option loom.linter.warnings false`."
47+ Term.elabTerm (<- `(invariantGadget $invs:term)) none
48+
1449
1550declare_syntax_cat doneWith
1651declare_syntax_cat decreasingTerm
1752declare_syntax_cat invariantClause
18- declare_syntax_cat invariants
53+ declare_syntax_cat invariantClauses
1954syntax "invariant" termBeforeDo linebreak : invariantClause
2055syntax "done_with" termBeforeDo : doneWith
2156syntax "decreasing" termBeforeDo : decreasingTerm
22- syntax (invariantClause linebreak)* : invariants
57+ syntax (invariantClause linebreak)* : invariantClauses
2358
2459syntax "let" term ":|" term : doElem
2560syntax "while" term
26- (invariantClause)+
61+ (invariantClause)*
2762 (doneWith)?
2863 (decreasingTerm)?
2964 "do" doSeq : doElem
@@ -39,32 +74,76 @@ syntax "for" ident "in" termBeforeInvariant
3974macro_rules
4075 | `(doElem| let $x:term :| $t) => `(doElem| let $x:term <- pickSuchThat _ (fun $x => $t))
4176
77+ /-
78+ ### While loop elaboration rules
79+
80+ The general syntax for a while loop is:
81+ ```lean
82+ while <condition>
83+ [invariant <invariant1>]
84+ [invariant <invariant2>]
85+ ...
86+ [done_with <termination_condition>]?
87+ [decreasing <measure>]?
88+ do <body>
89+ ```
90+ Unlike ordinary Lean's `while` loop, `Velvet` loops require at least one
91+ invariant annotation. Invariant annotations are followed by an optional `done_with` statement.
92+ In a nutshell, `done_with` is a condition that is true upon loop termination. Such condition is
93+ usefull to specify when the loop contains `break` or `continue` statements. Which means that loop
94+ might terminate without reaching the stated where the loop condition is false.
95+ If unspecified, it is assumed to be the negation of the loop condition.
96+
97+ The optional `decreasing` annotation is used to specify a measure function that is used to prove termination.
98+ This measure elaborates to a term of type `β → Option ℕ` where `β` is a record of the loop variables. We need
99+ `Option ℕ` to incorporate the cases when the measure is not specified, which should be the case for partial
100+ correctness semantics.
101+ If `decreasing` is unspecified, measure is assumed to be `none`.
102+ -/
42103macro_rules
104+ | `(doElem| while $t do $seq:doSeq) => do
105+ let decr <- withRef (<- getRef) `(decreasing none)
106+ let invs <- withRef (<- getRef) `(invariants [])
107+ `(doElem|
108+ for _ in Lean.Loop.mk do
109+ $invs:term
110+ onDoneGadget (with_name_prefix `done ¬$t:term)
111+ $decr:term
112+ if $t then
113+ $seq:doSeq
114+ else break )
43115 | `(doElem| while $t
44116 $[invariant $inv:term
45117 ]*
46118 $[done_with $inv_done]?
47119 $[decreasing $measure]?
48120 do $seq:doSeq) => do
121+ let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
49122 let invd_some ← match inv_done with
50123 | some invd_some => withRef invd_some ``($invd_some)
51124 | none => ``(¬$t:term)
52125 match measure with
53- | some measure_some => `(doElem|
54- for _ in Lean.Loop.mk do
55- invariantGadget [ $[(with_name_prefix `invariant $inv:term)],* ]
56- onDoneGadget (with_name_prefix `done $invd_some:term)
57- decreasingGadget (type_with_name_prefix `decreasing $measure_some:term)
58- if $t then
59- $seq:doSeq
60- else break )
61- | none => `(doElem|
62- for _ in Lean.Loop.mk do
63- invariantGadget [ $[(with_name_prefix `invariant $inv:term)],* ]
64- onDoneGadget (with_name_prefix `done $invd_some:term)
65- if $t then
66- $seq:doSeq
67- else break )
126+ | some measure_some =>
127+ let decr <- withRef measure_some `(decreasing type_with_name_prefix `decreasing $measure_some)
128+ `(doElem|
129+ for _ in Lean.Loop.mk do
130+ $invs:term
131+ onDoneGadget (with_name_prefix `done $invd_some:term)
132+ $decr:term
133+ if $t then
134+ $seq:doSeq
135+ else break )
136+ | none => do
137+ let decr <- withRef (<- getRef) `(decreasing none)
138+ let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
139+ `(doElem|
140+ for _ in Lean.Loop.mk do
141+ $invs:term
142+ onDoneGadget (with_name_prefix `done $invd_some:term)
143+ $decr:term
144+ if $t then
145+ $seq:doSeq
146+ else break )
68147 | `(doElem| while_some $x:ident :| $t do $seq:doSeq) =>
69148 match seq with
70149 | `(doSeq| $[$seq:doElem]*)
@@ -80,34 +159,36 @@ macro_rules
80159 ]*
81160 $[done_with $inv_done]? do
82161 $seq:doSeq) => do
162+ let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
83163 let invd_some ← match inv_done with
84164 | some invd_some => withRef invd_some ``($invd_some)
85165 | none => ``(¬$t:term)
86166 match seq with
87167 | `(doSeq| $[$seq:doElem]*)
88168 | `(doSeq| $[$seq:doElem;]*)
89169 | `(doSeq| { $[$seq:doElem]* }) =>
170+ let decr <- withRef (<- getRef) `(decreasing none)
90171 `(doElem|
91172 for _ in Lean.Loop.mk do
92- invariantGadget [ $[(with_name_prefix `invariant $inv :term)],* ]
173+ $invs :term
93174 onDoneGadget (with_name_prefix `done $invd_some:term)
175+ $decr:term
94176 if ∃ $x:ident, $t then
95177 let $x :| $t
96178 $[$seq:doElem]*
97179 else break )
98180 | _ => Lean.Macro.throwError "while_some expects a sequence of do-elements"
99181 | `(doElem| for $x:ident in $t
100- invariant $inv':term
101182 $[invariant $inv:term
102183 ]*
103- do $seq:doSeq) =>
184+ do $seq:doSeq) => do
185+ let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
104186 match seq with
105187 | `(doSeq| $[$seq:doElem]*)
106188 | `(doSeq| $[$seq:doElem;]*)
107189 | `(doSeq| { $[$seq:doElem]* }) =>
108- -- let inv := invs.push inv
109190 `(doElem|
110191 for $x:ident in $t do
111- invariantGadget [ $inv' :term, $[$inv:term],* ]
192+ $invs :term
112193 $[$seq:doElem]*)
113194 | _ => Lean.Macro.throwError "for expects a sequence of do-elements"
0 commit comments