Skip to content

Commit 5b05a54

Browse files
authored
Merge pull request #9 from verse-lab/simple-tester
A simple testing mechanism
2 parents c14cbc9 + dfcbaba commit 5b05a54

File tree

4 files changed

+164
-2
lines changed

4 files changed

+164
-2
lines changed

CaseStudies/Extension.lean

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,22 @@ initialize velvetObligations :
4545
res := res.union <| Std.HashMap.ofList a.toList
4646
return res
4747
}
48+
49+
/-- Storing slightly more information than `VelvetObligation`. -/
50+
structure VelvetTestingCtx extends VelvetObligation where
51+
newIds : Array Ident
52+
modBinders : Array (TSyntax `Lean.Parser.Term.bracketedBinder)
53+
deriving Inhabited
54+
55+
abbrev VelvetTestingContextMap := Std.HashMap Name VelvetTestingCtx
56+
57+
initialize velvetTestingContextMap :
58+
SimplePersistentEnvExtension (Name × VelvetTestingCtx) VelvetTestingContextMap ←
59+
registerSimplePersistentEnvExtension {
60+
addEntryFn := fun s (n, o) => s.insert n o
61+
addImportedFn := fun as => Id.run do
62+
let mut res : VelvetTestingContextMap := ∅
63+
for a in as do
64+
res := res.union <| Std.HashMap.ofList a.toList
65+
return res
66+
}

CaseStudies/TestingUtil.lean

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Aesop
2+
import Batteries.Tactic.PermuteGoals
3+
import Batteries.Tactic.Basic
4+
5+
def Decidable.Nat.decidableBallLT' {p : Nat → Prop} (n : Nat)
6+
[∀ i, Decidable (p i)] :
7+
((∀ i, i < n → p i) ↔ (∀ i, p i)) → Decidable (∀ i, p i) := by
8+
intro h ; rw [← h] ; infer_instance
9+
10+
open Lean Meta Macro Parser Tactic in
11+
def deriveDecidableNatUpperBound (tms : List <| TSyntax `term)
12+
: MacroM (TSyntax `Lean.Parser.Tactic.tacticSeq) := do
13+
match tms with
14+
| [] => `(Lean.Parser.Tactic.tacticSeq| (infer_instance) )
15+
| tm :: tms' =>
16+
let res ← deriveDecidableNatUpperBound tms'
17+
let h := mkIdent `h
18+
`(Lean.Parser.Tactic.tacticSeq|
19+
refine @$(mkIdent ``Decidable.Nat.decidableBallLT') _ ($tm) ?_ ?_
20+
on_goal 1=> intro _
21+
on_goal 1=>
22+
$res
23+
constructor
24+
next => (intro $h:ident ; intros ; apply $h:ident <;> (try split_ands) <;> (solve
25+
| omega
26+
| aesop))
27+
next => aesop)
28+
29+
macro "decidable_by_nat_upperbound" "[" tms:term,* "]" : term => do
30+
let res ← deriveDecidableNatUpperBound tms.getElems.toList
31+
`(term| by $res)

CaseStudies/Velvet/Syntax.lean

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ elab_rules : command
223223
$[require $req:term]*
224224
$[ensures $ens:term]* do $doSeq:doSeq
225225
) => do
226-
let (defCmd, obligation) ← Command.runTermElabM fun _vs => do
226+
let (defCmd, obligation, testingCtx) ← Command.runTermElabM fun _vs => do
227227
let bindersIdents ← toBracketedBinderArrayLeafny binders
228228

229229
let modIds ← getModIds binders
@@ -251,6 +251,9 @@ elab_rules : command
251251
let pre <- req.andListWithName reqName
252252
let post <- ens.andListWithName ensName
253253

254+
let namelessPre <- req.andList
255+
let namelessPost <- ens.andList
256+
254257
let mut ret <- `(term| ())
255258
for modId in modIds do
256259
let modId := mkIdent <| modId.getId.appendAfter "New"
@@ -265,9 +268,13 @@ elab_rules : command
265268
pre := pre
266269
post := post
267270
}
268-
return (defCmd, obligation)
271+
let newIds := modIds.map (fun x => Lean.mkIdent <| x.getId.appendAfter "New")
272+
let modBinders ← newIds.zip mutTypes |>.mapM fun (newId, mutType) =>
273+
`(bracketedBinder| ($newId : $mutType))
274+
return (defCmd, obligation, { obligation with pre := namelessPre , post := namelessPost , modBinders , newIds })
269275
elabCommand defCmd
270276
velvetObligations.modify (·.insert name.getId obligation)
277+
velvetTestingContextMap.modify (·.insert name.getId testingCtx)
271278

272279
notation "{" P "}" c "{" v "," Q "}" => triple P c (fun v => Q)
273280

@@ -302,3 +309,88 @@ elab_rules : command
302309

303310
set_option linter.unusedVariables false in
304311
def atomicAssertion {α : Type u} (n : Name) (a : α) := a
312+
313+
syntax "extract_program_for" ident : command
314+
syntax "prove_precondition_decidable_for" ident ("by" tacticSeq)? : command
315+
syntax "prove_postcondition_decidable_for" ident ("by" tacticSeq)? : command
316+
syntax "derive_tester_for" ident : command
317+
318+
def obtainVelvetTestingCtx (nameRaw : Ident) : CommandElabM (VelvetTestingCtx × Name) := do
319+
let ctxMap ← velvetTestingContextMap.get
320+
let name := nameRaw.getId
321+
unless ctxMap.contains name do
322+
throwError "{name} is not a Velvet program"
323+
return (ctxMap[name]!, name)
324+
325+
elab_rules : command
326+
| `(command| extract_program_for $nameRaw:ident ) => do
327+
-- assuming the thing is computable, then extract the program first
328+
let (ctx, name) ← obtainVelvetTestingCtx nameRaw
329+
let bindersIdents := ctx.binderIdents
330+
let ids := ctx.ids
331+
let execName := name.appendAfter "Exec"
332+
let execDefCmd ← `(command|
333+
def $(mkIdent execName) $bindersIdents* :=
334+
$(mkIdent ``NonDetT.extractWeak) ($nameRaw $ids*) (by extract_tactic))
335+
elabCommand execDefCmd
336+
337+
def elabDefiningDecidableInstancesForVelvetSpec (nameRaw : Ident)
338+
(pre? : Bool) (tac : Option (TSyntax `Lean.Parser.Tactic.tacticSeq)) : CommandElabM Unit := do
339+
let (ctx, name) ← obtainVelvetTestingCtx nameRaw
340+
let bindersIdents := ctx.binderIdents
341+
let (target, suffix, binders) :=
342+
if pre?
343+
then (ctx.pre, "PreDecidable", bindersIdents)
344+
else (ctx.post, "PostDecidable", bindersIdents ++ ctx.modBinders)
345+
let decidableInstName := name.appendAfter suffix
346+
-- let proof := tac.getD (← `(term| (by infer_instance) ))
347+
let tac := tac.getD (← `(Lean.Parser.Tactic.tacticSeq| skip ))
348+
let proof := (← `(Lean.Parser.Tactic.tacticSeq|
349+
repeat' refine @instDecidableAnd _ _ ?_ ?_
350+
all_goals (try infer_instance)
351+
($tac) ))
352+
let decidableInstDefCmd ← `(command|
353+
def $(mkIdent decidableInstName) $binders* :
354+
$(mkIdent ``Decidable) ($target) := by $proof)
355+
elabCommand decidableInstDefCmd
356+
357+
elab_rules : command
358+
| `(command| prove_precondition_decidable_for $nameRaw:ident ) => do
359+
elabDefiningDecidableInstancesForVelvetSpec nameRaw true none
360+
| `(command| prove_precondition_decidable_for $nameRaw:ident by $tac) => do
361+
elabDefiningDecidableInstancesForVelvetSpec nameRaw true (some tac)
362+
| `(command| prove_postcondition_decidable_for $nameRaw:ident ) => do
363+
elabDefiningDecidableInstancesForVelvetSpec nameRaw false none
364+
| `(command| prove_postcondition_decidable_for $nameRaw:ident by $tac) => do
365+
elabDefiningDecidableInstancesForVelvetSpec nameRaw false (some tac)
366+
367+
elab_rules : command
368+
| `(command| derive_tester_for $nameRaw:ident ) => do
369+
let (ctx, name) ← obtainVelvetTestingCtx nameRaw
370+
let execName ← do
371+
try resolveGlobalConstNoOverloadCore <| name.appendAfter "Exec"
372+
catch _ =>
373+
throwError "no executable found for {name}, please extract the program first"
374+
let ids := ctx.ids
375+
let retId := ctx.retId
376+
let ret := ctx.ret
377+
let bindersIdents := ctx.binderIdents
378+
let bundle (pre? : Bool) := if pre?
379+
then (ctx.pre, name.appendAfter "PreDecidable", ids)
380+
else (ctx.post, name.appendAfter "PostDecidable", ids ++ ctx.newIds)
381+
let decideTerm bundled : CommandElabM (TSyntax `term) := do
382+
let (target, instname, args) := bundled
383+
try
384+
let instname ← resolveGlobalConstNoOverloadCore instname
385+
`(term| (@$(mkIdent ``decide) _ ($(Syntax.mkApp (mkIdent instname) args))))
386+
catch _ =>
387+
`(term| ($(mkIdent ``decide) ($target)))
388+
let matcherTerm ← `(term|
389+
match ($(Syntax.mkApp (mkIdent execName) ids)) with
390+
| $(mkIdent ``DivM.res) ⟨$retId, $ret⟩ => $(← decideTerm <| bundle false)
391+
| _ => false)
392+
let ifTerm ← `(term| if $(← decideTerm <| bundle true) then $matcherTerm else true)
393+
let testerName := name.appendAfter "Tester"
394+
let testerDefCmd ← `(command|
395+
def $(mkIdent testerName) $bindersIdents* : Bool := $ifTerm)
396+
elabCommand testerDefCmd

CaseStudies/Velvet/VelvetExamples/Examples.lean

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Loom.MonadAlgebras.WP.Tactic
99
import Loom.MonadAlgebras.WP.DoNames'
1010

1111
import CaseStudies.Velvet.Std
12+
import CaseStudies.TestingUtil
1213

1314
open PartialCorrectness DemonicChoice Lean.Elab.Term.DoNames
1415

@@ -91,6 +92,25 @@ method insertionSort
9192
mind := mind - 1
9293
n := n + 1
9394
return
95+
96+
extract_program_for insertionSort
97+
prove_precondition_decidable_for insertionSort
98+
prove_postcondition_decidable_for insertionSort by
99+
(exact (decidable_by_nat_upperbound [(size arr), (size arr)]))
100+
derive_tester_for insertionSort
101+
102+
-- doing simple testing
103+
run_elab do
104+
let g : Plausible.Gen (_ × Bool) := do
105+
let arr ← Plausible.SampleableExt.interpSample (Array Int)
106+
let res := insertionSortTester arr
107+
pure (arr, res)
108+
for _ in [1: 500] do
109+
let res ← Plausible.Gen.run g 10
110+
unless res.2 do
111+
IO.println s!"postcondition violated for input {res.1}"
112+
break
113+
94114
prove_correct insertionSort by
95115
dsimp [insertionSort]
96116
loom_solve

0 commit comments

Comments
 (0)