Small-step Operational Semantics Consider the language of arithmetic expressions: x, y, x in Var n, m in Int e in Expr e ::= x | n | e1 + e2 | e1 * e2 Operational semantics describes the evaluation of expressions as successive reductions of the expression on an abstract machine. Configurations capture the state of the abstract machine, and consist of the expression that remains to be evaluated and a store: Config = Expr x Store Store = Var -> Int We will denote configurations using angle brackets. For instance, <(x+2)*(y+1), s>, where x is a variable and s a store. We describe the successive reductions in the evaluation using a transition function: OneStep included in Config x Config For instance, (<(4+2)*(y+1), s>, <6*(y+1), s>) in OneStep. For better readability, we will write "mc -> mc'" instead of "(mc,mc') in OneStep". Hence: <(4+2)*(y+1), s> -> <6*(y+1), s>. Now defining the semantics of the language boils down to defining the one step evaluation relation "->" that describes the transition between machine configurations. One issue here is that the domain of integers is infinite, and so is the domain of expressions. Therefore, there is an infinite number of possible machine configurations, and an infinite number of possible one-step transitions. We need to use a finite description for the infinite set of transitions. We can compactly describe the transition function OneStep using inference rules: (var) -------------- where n = s(x) -> (plus) ---------------- where p is the sum of n,m -> (mul) ---------------- where p is the product of n,m -> -> (lplus) ----------------------- -> -> (rplus) --------------------- -> -> (lmul) ----------------------- -> -> (rmul) --------------------- -> The meaning of an inference rule is that if the fact above the line holds and the side conditions also hold, then the fact below the line hold. The fact(s) above the line are called premises; the fact below the line is called the conclusion. The rules without premises are axioms; and the rules with premises are inductive rules. Using the Semantic Rules Let's see how we can use these rules. Consider that we want to evaluate expression (x+2) * (y+1) in a store s = {x=4, y=3}. That is, we want to find the transition for configuration <(x+2)*(y+1),s>. For this, we look for a rule with this form of a configuration in the conclusion. By inspecting the rules, we find that the only matching rule is (lmul), where e1 = x+2, e2 = y+1, but e1' is not yet known: -> (lmul) ------------------------------- <(x+2)*(y+1),s> -> Now we need to show that the premise actually holds and find out who e1' is. We look for a rule whose conclusion matches -> . We find that (lplus) is the only matching rule: -> (lplus) --------------------- -> where e1' = e1''+2. We repeat this reasoning for -> , and we find that the only applicable rule is the axiom (var): (var) -------------- -> <4,s> because the store is s = {x=4, y=3}. Since this is an axiom and has no premises, there is nothing left to prove. Hence, e'' = 4 and e1'= 4 + 2. We can put together the above pieces and build the following proof: (var) -------------- -> <4,s> (lplus) ------------------ -> <4+2,s> (lmul) --------------------------------- <(x+2)*(y+1),s> -> <(4+2)*(y+1),s> This proves that, given our inference rules, the one-step transition <(x+2)*(y+1),s> -> <(4+2)*(y+1),s> is possible. The above proof structure is called a "proof tree" or "derivation". It is important to keep in mind that proof trees must be finite for the conclusion to be valid. We can use a similar reasoning to find out the next evaluation step: (plus) ---------------- <4+2,s> -> <6,s> (lmul) ------------------------------ <(4+2)*(y+1),s> -> <6*(y+1),s> And we can continue this process. At the end, we can put together all of these transitions, to get a view of the entire computation: <(x+2)*(y+1),s> -> <(4+2)*(y+1),s> -> <6*(y+1),s> -> -> <6*(3+1),s> -> <6*4,s> -> <24,s> The result of the computation is a number, 24. The machine configuration that contains the final result is the point where the evaluation stops; they are called final configurations. For our language of expressions, the final configurations are of the form where n is a number and s is a store. We describe multiple steps of evaluation using a relation ->*, which is the transitive, reflexive closure of ->: ->* = {(mc,mc') | exists n >= 0 such that mc ->n mc'}. Expressing Program Properties We can now formally express different properties of programs. For instance: [Termination] The evaluation of each expression terminates: forall e in Expr, forall s in Store, exists n in Int: ->* or: [Deterministic Result] The evaluation result for any expression is deterministic: forall e in Expr, forall s in Store, forall n,m in Int: ->* and ->* implies n = n' or [Progress] For each store s and expression e that is not an integer, there exists a possible transition for : forall e, forall s: (e in Int) or (exists e': -> ) How can we prove such kinds of properties? Let's take the last property above and rephrase is as: for all expressions e, P(e) holds, where: P(e) = forall s: (e in Int) or (exists e': -> ) The idea is to build a proof that follows the inductive structure in the grammar of expressions: e = x | n | e1 + e2 | e1 * e2. This is called "structural induction on the expressions e". We must examine each case in the grammar and show that P(e) holds for that case. Since the grammar productions e = e1+e2 and e = e1 * e2 are inductive definitions of expression, they are inductive steps in the proof; the other two cases e = x and e = n are the basis of induction. The proof goes as follows. Case e = x. By the (var) axiom, we can evaluate in any state: -> , where n = s(x). So e' = n is a witness that there exists e' such that -> , and P(x) holds. Case e = n. Then e not in Int, so P(n) trivially holds. Case e = e1 + e2. This is an inductive step. We assume that P holds for subexpressions e1 and e2 and we want to prove that is holds for e. In other words, P(e1) and P(e2) implies P(e). Let's expand these properties. We know that: P(e1) = forall s: (e1 in Int) or (exists e': -> ) P(e2) = forall s: (e2 in Int) or (exists e': -> ) and want to show: P(e) = forall s: (e in Int) or (exists e': -> ) We must inspect several subcases. First, if both e1 and e2 are integer constants, say e1 = n1 and e2 = n2, then by rule (plus) we know that the transition -> is valid, where m is the sum of n1 and n2. Hence, P(e) = P(n1+n2) holds (with witness e' = m). Second, if e1 is not an integer constant, then by the inductive hypothesis P(e1) we know that -> for some e'. We can then use rule (lplus) to conclude -> , so P(e) = P(e1+e2) holds. Third, if e1 is an integer constant, say e1 = n1, but e2 is not, then by the inductive hypothesis P(e2) we know that -> for some e'. We can then use rule (lplus) to conclude -> , so P(e) = P(n1+e2) holds. Case e = e1 * e2. The proof in this case is similar to that in the previous case.