LATER: Another nice challenge exercise at some point would be to add
C-style arrays (i.e., indirect read/write). This sets up some
really nice challenge problems in Hoare (reasoning about arrays /
aliasing / etc.).
SOONER: BCP 25: Maybe we should write / instead of && in assertions,
to save a mismatch in the dec_minimum exercise in Hoare₂?
At some point we could consider moving material from the old
HoareLists to this chapter (and into later files, as
appropriate). We haven't done it yet because it's a shame to
complicate the nice simple presentation here when it's used as the
basis for applications like Xavier's static analysis lectures.
Also, we now have a whole volume on real separation logic...
We concentrate here on defining the syntax and semantics of Imp;
later in this volume we develop a theory of program equivalence and introduce
Hoare Logic, a popular logic for reasoning about imperative programs.
Since we'll want to look variables up to find out their current values,
we'll use total maps from the Typeclasses chapter of Logical Foundations. A machine state (or
just state) represents the current values of all variables at some
point in the execution of a program.
We give the type of variable identifiers a name, Ident. For now it is just
String; naming it makes the intent clearer.
We can add variables to the arithmetic expressions we had before simply
by including one more constructor. (This is a fresh Aexp, replacing
the variable-free one from the Slang chapter.)
Notation encoding: arithmetic expressions/-- Arithmetic expressions of Imp -/declare_syntax_catimp_aexp/-- Numeric literal -/syntax:maxnum:imp_aexp/-- `Ident` or Lean identifier -/syntax:maxident:imp_aexp/-- Addition -/syntax:65imp_aexp:65" + "imp_aexp:66:imp_aexp/-- Subtraction -/syntax:65imp_aexp:65" - "imp_aexp:66:imp_aexp/-- Multiplication -/syntax:70imp_aexp:70" * "imp_aexp:71:imp_aexp/-- Parentheses for grouping -/syntax:max"("imp_aexp")":imp_aexp/-- Escape to Lean -/syntax:max"~"term:max:imp_aexp/-- Embed an Imp arithmetic expression into a Lean term -/syntax:80"aexp ""{"imp_aexp"}":termnamespaceImp.ElabopenLeanElabTermMetadefwithSourceInfoOf{kind:Name}(ref:Syntax)(stx:TSyntaxkind)(canonical:=true):TSyntaxkind:=letinfo:=SourceInfo.fromRefref(canonical:=canonical)⟨stx.raw.setInfoinfo⟩defisGreek(c:Char):Bool:=letn:=c.val.toNatdecide((0x0370≤n∧n≤0x03ff)∨(0x1f00≤n∧n≤0x1fff))inductiveIdentKindwhere|object|metavarderivingBEqdefclassifyIdent?(id:Lean.Ident):Option(IdentKind×String):=doletName.str.anonymouss:=id.getId.eraseMacroScopes|failureifs.isEmpty||s.contains'.'thenfailureletc:=s.frontifc.isUpperthenreturn(.object,s)elseifc.isLower||isGreekcthenreturn(.metavar,s)elsefailuredefmkObjectIdentFrom(ref:Syntax)(name:String):Lean.Ident:=mkIdentFromref(Name.mkSimplename)defelabMetavarOnlyIdent(what:String)(expectedType:Term)(x:Lean.Ident):MacroMTerm:=domatchclassifyIdent?xwith|some(.metavar,_)=>`(($x:$expectedType))|some(.object,name)=>Macro.throwErrorAtxs!"no Imp {what} named `{name}`; capitalized bare names are always \
read as Imp identifiers — use a lowercase name or escape with `~` to refer to Lean name"|none=>Macro.throwErrorAtx"invalid bare identifier"macro_rules|`(aexp{$exp:imp_aexp})=>doletstx←matchexpwith|`(imp_aexp|$n:num)=>``(Aexp.num$n)|`(imp_aexp|~$e:term)=>``(($e:Aexp))|`(imp_aexp|$x:ident)=>matchclassifyIdent?xwith|some(.object,name)=>letnameLit:Term:=⟨Syntax.mkStrLitname⟩``(Aexp.id$nameLit)|some(.metavar,_)=>``(($x:Aexp))|none=>Macro.throwErrorAtx"invalid bare identifier"|`(imp_aexp|$a+$b)=>``(Aexp.plus(aexp{$a})(aexp{$b}))|`(imp_aexp|$a-$b)=>``(Aexp.minus(aexp{$a})(aexp{$b}))|`(imp_aexp|$a*$b)=>``(Aexp.mult(aexp{$a})(aexp{$b}))|`(imp_aexp|($a))=>``(aexp{$a})|_=>Lean.Macro.throwUnsupportedreturnwithSourceInfoOfexpstxendImp.ElabNotation encoding: boolean expressions/-- Boolean expressions of Imp -/declare_syntax_catimp_bexp/-- Boolean literal (`true` or `false`) and Lean identifier -/syntax:maxident:imp_bexp/-- Equality of arithmetic expressions -/syntax:50imp_aexp:51" = "imp_aexp:51:imp_bexp/-- Disequality of arithmetic expressions -/syntax:50imp_aexp:51" ≠ "imp_aexp:51:imp_bexp/-- Less than or equal -/syntax:50imp_aexp:51" ≤ "imp_aexp:51:imp_bexp/-- Greater than -/syntax:50imp_aexp:51" > "imp_aexp:51:imp_bexp/-- Boolean negation -/syntax:70"¬ "imp_bexp:70:imp_bexp/-- Boolean conjunction (right associative) -/syntax:35imp_bexp:36" ∧ "imp_bexp:35:imp_bexp/-- Parentheses for grouping -/syntax:max"("imp_bexp")":imp_bexp/-- Escape to Lean -/syntax:max"~"term:max:imp_bexp/-- Embed an Imp boolean expression into a Lean term -/syntax:80"bexp ""{"imp_bexp"}":termNotation encoding: boolean expressions, macro rulesnamespaceImp.ElabopenLeanmacro_rules|`(bexp{$exp:imp_bexp})=>doletstx←matchexpwith|`(imp_bexp|true)=>``(Bexp.booltrue)|`(imp_bexp|false)=>``(Bexp.boolfalse)|`(imp_bexp|$x:ident)=>doelabMetavarOnlyIdent"boolean"(←``(Bexp))x|`(imp_bexp|~$e:term)=>``(($e:Bexp))|`(imp_bexp|$a:imp_aexp=$b:imp_aexp)=>``(Bexp.eq(aexp{$a})(aexp{$b}))|`(imp_bexp|$a:imp_aexp≠$b:imp_aexp)=>``(Bexp.neq(aexp{$a})(aexp{$b}))|`(imp_bexp|$a:imp_aexp≤$b:imp_aexp)=>``(Bexp.le(aexp{$a})(aexp{$b}))|`(imp_bexp|$a:imp_aexp>$b:imp_aexp)=>``(Bexp.gt(aexp{$a})(aexp{$b}))|`(imp_bexp|¬$b:imp_bexp)=>``(Bexp.not(bexp{$b}))|`(imp_bexp|$b₁:imp_bexp∧$b₂:imp_bexp)=>``(Bexp.and(bexp{$b₁})(bexp{$b₂}))|`(imp_bexp|($b:imp_bexp))=>``(bexp{$b})|_=>Macro.throwUnsupportedreturnwithSourceInfoOfexpstxendImp.Elab(Aexp.num3).plus((Aexp.id"X").mult(Aexp.num2)) : Aexp#checkaexp{3+(X*2)}(Bexp.booltrue).and(Bexp.le(Aexp.id"X")(Aexp.num4)).not : Bexp#checkbexp{true∧¬(X≤4)}
Notation encoding: printing expressions backnamespaceImp.DelabopenLeanPrettyPrinterDelaboratorSubExprParenthesizerImp.Elab@[category_parenthesizerimp_aexp]defimp_aexp.parenthesizer:CategoryParenthesizer:=funprec=>domaybeParenthesize`imp_aexptruewrapParensprec<|parenthesizeCategoryCore`imp_aexpprecwherewrapParens(stx:Syntax):Syntax:=Unhygienic.rundoletstxInfo:=SourceInfo.fromRefstxletstx:=stx.setInfo.noneletpstx←`(imp_aexp|($(⟨stx⟩)))returnpstx.raw.setInfostxInfo@[category_parenthesizerimp_bexp]defimp_bexp.parenthesizer:CategoryParenthesizer:=funprec=>doParenthesizer.maybeParenthesize`imp_bexptruewrapParensprec<|Parenthesizer.parenthesizeCategoryCore`imp_bexpprecwherewrapParens(stx:Syntax):Syntax:=Unhygienic.rundoletstxInfo:=SourceInfo.fromRefstxletstx:=stx.setInfo.noneletpstx←`(imp_bexp|($(⟨stx⟩)))returnpstx.raw.setInfostxInfoNotation encoding: registering the delaborators/--
Recognizes a term as being an `aexp { ... }` expression.
-/defgetAexp(stx:Term):TSyntax`imp_aexp:=withSourceInfoOf(canonical:=false)stx<|Unhygienic.rundomatchstxwith|`(aexp{$e:imp_aexp})=>returne|`($id:ident)=>matchclassifyIdent?idwith|some(.metavar,_)=>`(imp_aexp|$id:ident)|_=>`(imp_aexp|~$stx)|_=>`(imp_aexp|~$stx)@[app_unexpanderAexp.num]privatedefAexp.unexpandNum:Unexpander|`($_$n:num)=>`(aexp{$n:num})|_=>throw()@[app_unexpanderAexp.id]privatedefAexp.unexpandId:Unexpander|`($_$s:str)=>doletid:=mkObjectIdentFroms.raws.getStringmatchclassifyIdent?idwith|some(.object,_)=>`(aexp{$id:ident})|_=>throw()|_=>throw()@[app_unexpanderAexp.plus]privatedefAexp.unexpandPlus:Unexpander|`($_$a$b)=>`(aexp{$(getAexpa)+$(getAexpb)})|_=>throw()@[app_unexpanderAexp.minus]privatedefAexp.unexpandMinus:Unexpander|`($_$a$b)=>`(aexp{$(getAexpa)-$(getAexpb)})|_=>throw()@[app_unexpanderAexp.mult]privatedefAexp.unexpandMult:Unexpander|`($_$a$b)=>`(aexp{$(getAexpa)*$(getAexpb)})|_=>throw()/--
Recognizes a term as being an `bexp { ... }` expression.
-/defgetBexp(stx:Term):TSyntax`imp_bexp:=withSourceInfoOf(canonical:=false)stx<|Unhygienic.rundomatchstxwith|`(bexp{$e:imp_bexp})=>returne|`($id:ident)=>matchclassifyIdent?idwith|some(.metavar,_)=>`(imp_bexp|$id:ident)|_=>`(imp_bexp|~$stx)|_=>`(imp_bexp|~$stx)/--
Delaborator for `Bexp.bool`. This is needed since we want to be sure we are
matching on the actual `true`/`false` expressions, rather than matching on the
delaborated identifiers `true`/`false` (which might not be accurate).
-/@[app_delabBexp.bool]privatedefBExp.delabBool:Delab:=whenPPOptiongetPPNotationdolete←getExprguard<|e.isAppOfArity``Bexp.bool1match_expre.appArg!with|true=>`(bexp{$(mkIdent`true):ident})|false=>`(bexp{$(mkIdent`false):ident})|_=>failure@[app_unexpanderBexp.eq]privatedefBexp.unexpandEq:Unexpander|`($_$a$b)=>`(bexp{$(getAexpa):imp_aexp=$(getAexpb):imp_aexp})|_=>throw()@[app_unexpanderBexp.neq]privatedefBexp.unexpandNeq:Unexpander|`($_$a$b)=>`(bexp{$(getAexpa):imp_aexp≠$(getAexpb):imp_aexp})|_=>throw()@[app_unexpanderBexp.le]privatedefBexp.unexpandLe:Unexpander|`($_$a$b)=>`(bexp{$(getAexpa):imp_aexp≤$(getAexpb):imp_aexp})|_=>throw()@[app_unexpanderBexp.gt]privatedefBexp.unexpandGt:Unexpander|`($_$a$b)=>`(bexp{$(getAexpa):imp_aexp>$(getAexpb):imp_aexp})|_=>throw()@[app_unexpanderBexp.not]privatedefBexp.unexpandNot:Unexpander|`($_$a)=>`(bexp{¬$(getBexpa):imp_bexp})|_=>throw()@[app_unexpanderBexp.and]privatedefBexp.unexpandAnd:Unexpander|`($_$a$b)=>`(bexp{$(getBexpa):imp_bexp∧$(getBexpb):imp_bexp})|_=>throw()endImp.Delab/-- info: aexp {3 + X * 2} : Aexp -/#guard_msgsin#checkaexp{3+(X*2)}/-- info: bexp {true ∧ ¬ (X ≤ 4)} : Bexp -/#guard_msgsin#checkbexp{true∧¬(X≤4)}
inductiveComwhere|skip|asgn(x:Ident)(a:Aexp)|seq(c₁c₂:Com)|cond(b:Bexp)(c₁c₂:Com)|whileDo(b:Bexp)(c:Com)Notation encoding: commands, macro rules/-- Imp commands -/declare_syntax_catimp_com/-- The command that does nothing (`skip`) -/syntax:maxident:imp_com/-- Sequencing: one command after another (right associative. min + 1 = 11) -/syntax:80imp_com:11Lean.Parser.semicolonOrLinebreakppHardSpaceimp_com:min:imp_com/-- Assignment -/syntax:maxidentppHardSpace":="ppHardSpaceimp_aexp:imp_com/-- Conditional -/syntax:max"if ""("imp_bexp")"ppHardSpace"{"imp_com"}"ppHardSpace"else"ppHardSpace"{"imp_com"}":imp_com/-- Loop -/syntax:max"while ""("imp_bexp")"ppHardSpace"{"imp_com"}":imp_com/-- Escape to Lean -/syntax:max"~"term:max:imp_com/-- Include an Imp command in Lean code -/syntax:80"imp"ppHardSpace"{"imp_com"}":termnamespaceComopenLeanImp.Elabscopedmacro_rules|`(imp{$s})=>doletstx←matchswith|`(imp_com|skip)=>``(Com.skip)|`(imp_com|$x:ident)=>doelabMetavarOnlyIdent"command"(←``(Com))x|`(imp_com|$c₁;$c₂)=>``(Com.seq(imp{$c₁})(imp{$c₂}))|`(imp_com|$x:ident:=$a)=>matchclassifyIdent?xwith|some(.object,name)=>letnameLit:Term:=⟨Syntax.mkStrLitname⟩``(Com.asgn$nameLit(aexp{$a}))|some(.metavar,_)=>``(Com.asgn$x(aexp{$a}))|none=>Macro.throwErrorAtx"invalid bare identifier"|`(imp_com|if($b){$c₁}else{$c₂})=>``(Com.cond(bexp{$b})(imp{$c₁})(imp{$c₂}))|`(imp_com|while($b){$c})=>``(Com.whileDo(bexp{$b})(imp{$c}))|`(imp_com|~$c)=>`(($c:Com))|_=>Macro.throwUnsupportedreturnwithSourceInfoOfsstxendComopenscopedComNotation encoding: printing commands backnamespaceImp.DelabopenLeanPrettyPrinterDelaboratorSubExprImp.Elab/--
Recognizes a term as being an `imp { ... }` expression.
-/defgetImp(stx:Term):TSyntax`imp_com:=withSourceInfoOf(canonical:=false)stx<|Unhygienic.rundomatchstxwith|`(imp{$e:imp_com})=>returne|`($id:ident)=>matchclassifyIdent?idwith|some(.metavar,_)=>`(imp_com|$id:ident)|_=>`(imp_com|~$stx)|_=>`(imp_com|~$stx)@[app_unexpanderCom.skip]defunexpandComSkip:Unexpander|_=>`(imp{$(mkIdent`skip):ident})@[app_unexpanderCom.asgn]defunexpandComAsgn:Unexpander|`($_$x:ident$a)=>`(imp{$x:ident:=$(getAexpa)})|`($_$s:str$a)=>doletid:=mkObjectIdentFroms.raws.getStringmatchclassifyIdent?idwith|some(.object,_)=>`(imp{$id:ident:=$(getAexpa)})|_=>throw()|_=>throw()@[app_unexpanderCom.seq]defunexpandComSeq:Unexpander|`($_$a$b)=>matchawith|`(imp{$_;$_})=>-- seq syntax is right associative, so need to quote `a``(imp{~$a;$(getImpb):imp_com})|_=>`(imp{$(getImpa):imp_com;$(getImpb):imp_com})|_=>throw()@[app_unexpanderCom.cond]defunexpandComCond:Unexpander|`($_$b$c₁$c₂)=>`(imp{if($(getBexpb)){$(getImpc₁)}else{$(getImpc₂)}})|_=>throw()@[app_unexpanderCom.whileDo]defunexpandComWhileDo:Unexpander|`($_$b$c)=>`(imp{while($(getBexpb)){$(getImpc)}})|_=>throw()endImp.Delabdeffact_in_lean:Com:=imp{Z:=XY:=1while(Z≠0){Y:=Y*ZZ:=Z-1}}deffact_in_lean : Com :=imp{Z:=X;Y:=1;while(Z≠0){Y:=Y*Z;Z:=Z-1}}#printfact_in_lean
Even though the notations are useful for getting the high-level picture,
it's sometimes helpful to turn off the notation to see the parsed structure as a plain term.
This can be done with set_option pp.notation false
(which we briefly mentioned in the Typeclasses chapter) as follows:
In a more conventional functional language like OCaml or Haskell, we could define
the evaluation function as follows:
deffail to show termination forCom.evalwith errorsfailed to infer structural recursion:Cannot use parameter st:the type TotalMapIdentNat does not have a `.brecOn` recursorCannot use parameter c:failed to eliminate recursive applicationevalst(imp{c;while(b){c}})failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goalst:Stateb:Bexpc:Comh✝:Bexp.evalstb=true⊢ 1+sizeOfc+(1+sizeOfb+sizeOfc)<1+sizeOfb+sizeOfcCom.eval(st:State)(c:Com):State:=matchcwith|imp{skip}=>st|imp{x:=a}=>(x→ₜa.evalst;st)|imp{c₁;c₂}=>letst':=evalstc₁evalst'c₂|imp{if(b){c₁}else{c₂}}=>ifb.evalstthenevalstc₁elseevalstc₂|imp{while(b){c}}=>ifb.evalstthenevalst(imp{c;while(b){c}})-- ^-- recursive call without a decreasing argumentelsest
fail to show termination forCom.evalwith errorsfailed to infer structural recursion:Cannot use parameter st:the type TotalMapIdentNat does not have a `.brecOn` recursorCannot use parameter c:failed to eliminate recursive applicationevalst(imp{c;while(b){c}})failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goalst:Stateb:Bexpc:Comh✝:Bexp.evalstb=true⊢ 1+sizeOfc+(1+sizeOfb+sizeOfc)<1+sizeOfb+sizeOfc
Note to developers
Perhaps that discussion should be moved to -- or previewed in --
Logic.v? MRC'20: It's already in ProofObjects (which not everyone
sees).
A nonterminating theorem loop_false (n : Nat) : False := loop_false n would make False
provable, so Lean rejects it.
Here's a better way: define Com.eval as a relation rather than a
function -- i.e., make its result a Prop rather than a State,
similar to what we did for Aexp.EvalR in the Slang chapter.
Note to developers (Michael Hicks @mwhicks1)
I kind of hate this notation. Is there something more standard
in Lean? CSLib precedent maybe?
We'll use the notation st =[ c ]=> st' for the Com.EvalR relation:
st =[ c ]=> st' means that executing program c in a starting state
st results in an ending state st'. This can be pronounced "c takes
state st to st'".
Here is an informal definition of evaluation, presented as inference rules
for readability:
----------------- (skip)
st =[ skip ]=> st
a.eval st = n
-------------------------------- (asgn)
st =[ x := a ]=> (x →ₜ n ; st)
st =[ c₁ ]=> st'
st' =[ c₂ ]=> st''
--------------------- (seq)
st =[ c₁;c₂ ]=> st''
b.eval st = true
st =[ c₁ ]=> st'
--------------------------------------- (ifTrue)
st =[ if (b) { c₁ } else { c₂ } ]=> st'
b.eval st = false
st =[ c₂ ]=> st'
--------------------------------------- (ifFalse)
st =[ if (b) { c₁ } else { c₂ } ]=> st'
b.eval st = false
---------------------------- (whileFalse)
st =[ while (b) { c } ]=> st
b.eval st = true
st =[ c ]=> st'
st' =[ while (b) { c } ]=> st''
------------------------------- (whileTrue)
st =[ while (b) { c } ]=> st''
Here is the formal definition. Make sure you understand how it
corresponds to the inference rules.
The cost of defining evaluation as a relation instead of a function is
that we now need to construct a proof that some program evaluates to
some result state, rather than letting Lean's computation mechanism do
it for us.
After apply EvalR.seq (st' := {X ↦ 2}), the infoview shows imp {X := 2}.EvalR ∅ {X ↦ 2} instead of ∅ =[ X := 2 ]=> {X ↦ 2}.
It would be silly to use apply EvalR.seq (st' := {X ↦ 2}) <;> try simp only [evalR_eq] at *.
Since the total-map update notation (→ₜ) is difficult to type, we prefer to use the {}-notation with KVPairs.
In the above proof, using EvalR.asgn rfl is convenient because it computes the value of the right-hand side and can use it to determine st'.
Note the use of ~ here, since .num x is a Lean term that we want to splice into Imp.
example{x:Nat}:∅=[X:=~(.numx)]=>{X↦x}:=x:Nat⊢ ∅=[X:=~(Aexp.numx)]=>{X↦x}x:Nat⊢ Aexp.eval∅(Aexp.numx)=(X↦x).value-- `⊢ Aexp.eval ∅ x = (X ↦ x).value`, which we can prove with `simp` or `rfl`All goals completed! 🐙example{x:Nat}:∅=[X:=~(.numx)]=>{X↦x}:=x:Nat⊢ ∅=[X:=~(Aexp.numx)]=>{X↦x}All goals completed! 🐙example:∅=[X:=2;Y:=3]=>{Y↦3,X↦2}:=⊢ ∅=[X:=2;Y:=3]=>{Y↦3,X↦2}⊢ imp{X:=2}.EvalR∅?st'⊢ imp{Y:=3}.EvalR?st'{Y↦3,X↦2}⊢ State⊢ imp{X:=2}.EvalR∅?st'-- `⊢ imp {X := 2}.EvalR ∅ ?st'`All goals completed! 🐙-- assigns the metavariable `?st'` to `{X ↦ 2}` (or equivalent)⊢ imp{Y:=3}.EvalR("X"→ₜAexp.eval∅(aexp{2})){Y↦3,X↦2}⊢ ("X"→ₜ2)=[Y:=3]=>{Y↦3,X↦2}All goals completed! 🐙
This is a case where rfl is more powerful than simp, because it can assign the ?st' metavariable.
To demonstrate, here's a version with simp:
What sorts of things might we want to prove using these definitions? Here are
some simple examples...
Note to developers
PR: I phrased these quizzes with the following alternatives:
(A) Not true
(B) True and easily provable
(C) True and takes more work to prove
(D) True and cannot be proved without additional axioms
Quiz
Is the following proposition provable?
∀ (c : Com) (st st' : State),
st =[ skip; c ]=> st' →
st =[ c ]=> st'
theoremplus2_spec{st:State}{n:Nat}{st':State}(hx:st[X]=n)(heval:st=[plus2]=>st'):st'[X]=n+2:=byst:Staten:Natst':Statehx:st[X]=nheval:st=[plus2]=>st'⊢ st'[X]=n+2-- Inverting `heval` forces one step of the evaluation relation: since-- `plus2` is an assignment, `st'` must be `st` extended at `X`.rw[plus2st:Staten:Natst':Statehx:st[X]=nheval:st=[X:=X+2]=>st'⊢ st'[X]=n+2]athevalst:Staten:Natst':Statehx:st[X]=nheval:st=[X:=X+2]=>st'⊢ st'[X]=n+2inversionhevalwith|asgnmh=>simp[hx]ath⊢asgnst:Staten:Nathx:st[X]=nm:Nath:n+2=m⊢ m=n+2liaAll goals completed! 🐙
Note to developers (Niklas Halonen @xhalo32)
We need to explain the generalize tactic.
I've changed some Hoare proofs from have key to generalize but the tactic hasn't been explained yet.
Note to developers (One An @meluge)
At least currently, it looks like generalize is introduced in Automation.lean.
Are we doing anything different here with generalize that is
unexplained there?
We would like to show that they agree -- if we start factCom in
a state where variable X contains some number n, then it will
terminate in a state where variable Y contains the factorial of
n.
To show this, we rely on the critical idea of a loop
invariant.
One might wonder whether all this work with poking at states and
unfolding definitions could be ameliorated with some more powerful
lemmas and/or more uniform reasoning principles... Indeed, this is
exactly the point of the Hoare chapters!
Old HP calculators, programming languages like Forth and Postscript,
and abstract machines like the Java Virtual Machine all evaluate
arithmetic expressions using a stack. For instance, the expression
(2*3)+(3*(4-2))
would be written as
2 3 * 3 4 2 - * +
and evaluated like this (where we show the program being evaluated
on the right and the contents of the stack on the left):
Write a function to evaluate programs in the stack language. It
should take as input a state, a stack represented as a list of
numbers (top stack item is the head of the list), and a program
represented as a list of instructions, and it should return the
stack after executing the program. Test your function on the
examples below.
Note that it is unspecified what to do when encountering an
sPlus, sMinus, or sMult instruction if the stack contains
fewer than two elements. In a sense, it is immaterial what we do,
since a correct compiler will never emit such a malformed program.
But for the sake of later exercises, it would be best to skip the
offending instruction and continue with the next one.
defdeclaration uses `sorry`sExecute(st:State)(stack:ListNat)(prog:ListSinstr):ListNat:=sorry-- FILL IN HEREtheoremdeclaration uses `sorry`sExecute1:sExecute∅[][sPush5,sPush3,sPush1,sMinus]=[2,5]:=by⊢ sExecute∅[][sPush5,sPush3,sPush1,sMinus]=[2,5]sorryAll goals completed! 🐙theoremdeclaration uses `sorry`sExecute2:sExecute{X↦3}[3,4][sPush4,sLoadX,sMult,sPlus]=[15,4]:=by⊢ sExecute{X↦3}[3,4][sPush4,sLoadX,sMult,sPlus]=[15,4]sorryAll goals completed! 🐙
Next, write a function that compiles an Aexp into a stack
machine program. The effect of running the program should be the
same as pushing the value of the expression on the stack.
defdeclaration uses `sorry`sCompile(a:Aexp):ListSinstr:=sorry-- FILL IN HERE
After you've defined sCompile, prove the following to test that it works.
Execution can be decomposed in the following sense: executing
stack program p₁ ++ p₂ is the same as executing p₁, taking
the resulting stack, and executing p₂ from that stack. Prove
that fact.
Now we'll prove the correctness of the compiler implemented in the
previous exercise. Begin by proving the following lemma. If it
becomes difficult, consider whether your implementation of
sExecute or sCompile could be simplified.