Examples of Three Semantic Specifications:

	Operational semantics is based upon the imperative model of the language,
	describing the semantics of a language by mapping programs in that 
	language into equivalent programs implemented on an abstract machine.
	Denotational semantics use functional model of languages to map 
	programs in that language into equivalent functions.
	Axiomatic semantics is based on the logic model of programming languages
  	describing  the action of a program by determining logical assertions that
	hold before and after its execution.

	For all examples, we use the following Calculator Grammar

		Cal ->	Exp =
		Exp ->	val | val opr Exp	// Right-associativity
		val ->	sign int | int  | sign int . dec  | int . dec
		int ->  d | int d
		dec ->  d | d dec
		d -> 0 | 1 | 2 |3 | 4 | 5 | 6 | 7 | 8 | 9
		sign -> +  | -
		opr ->  +  |  -  |  *  |  /

	1. Operational
	
	A.  Use a simple stack machine as our abstract machine with
	    the following six instructions:
		push value:	push the given value on the stack
		pop:		pop the top value
		add:		add top two values, replacing them by their sum
		sub:		substract top two values, replacing likewise.
		mult:		multiply top two values, replacing likewise.
		div:		divide the top two values, replacing likewise.

	B. Stack machine codes for the Operational Semantic.

	   For each syntax production rule, these codes need to be defined.
	   For example, for the rule:  int -> int d
	   the following stack machine code may be defined:
		Code to be generated by RHS int, followed by
		push 10
		mult
		Code to be generated by RHS d
		add

	   Complete Operational Semantics:
		
		Cal -> Exp =          	|	#Exp#	// code to be generated
		                      	|	     	// by RHS Exp
		                       	|	pop		
		Exp -> val             	|	#val#
		Exp -> val opr Exp    	|	#Exp#
		                      	|	#val#
		                      	|	#opr#
		Val -> sign int	     	|	#int#
		                       	|	#sign#
		Val -> int            	|	#int#
		Val -> sign int . dec  	|	#int#
		                      	|	#dec#
		                      	|	add
		                      	|	#sign#
		Val -> int . dec       	|	#int#	
		                       	|	#dec#
		                      	|	add
		int -> d               	|	#d#
		int -> int d           	|	#int#
		                       	|	push 10
		                       	| 	mult
		                      	|	#d#
		                       	|	add
		dec -> d              	|	push 10
		                      	|	#d#
		                       	|	div
		dec -> d dec          	|	push 10
		                       	|	#dec#
		                       	|	div
		                      	|	push 10
		                       	|	#d#
		                       	|	div
		                      	|	add
		d -> 0	               	|	push 0
		d -> 1                	|	push 1 
		d -> 2                 	|	push 2
	        // And, similarly, for each of other seven digits
		sign -> +              	|	No op.
		sign -> -              	|	push -1
		                      	|	mult
		opr -> +              	|	add
		opr -> -              	|	sub
		opr -> *               	|	Mult
		opr -> /               	|	div	
	
	Example: 1.5*-40=
		The following codes will be generated:
		push 4
		push 10
		mult
		push 0		End for int(40)
		add
		push -1		
		mult		End for val(-40) and for Exp(-40)
		push 1
		push 10
		push 5
		div
		add
		mult
		pop
	
	2. Denotational Semantics:
	
	A. Key Ideas:
	   a. For each nonterminal of the Grammar,
	      we define a function that maps a syntactic structure
	      (in the Syntactic Domain) into a value in the Semantic Domain.
	   b. In defining such a function, we consider all production rules
	      of the corresponding nonterminal.
	   c. When the final (ultimate) function for the starting nonterminal
	      is applied to a source program, the function must yield
	      the expected output(s) of this program.

	   For example, for our example Calculator grammar, if Cal() is the function
	   for the starting nonterminal, then
	   Cal("100-20+30=") must yield 50.

	B. Example production rule.
	   Let us consider the following production rule and its function:
		int -> int d
	   And assume that Int() is the function for this nonterminal, int.
	 	Int ("int" "d") = 10*Int("int") + D("d")
		where D() is the function for the nonterminal d
	   For the other production rule, int -> d, we will have
		Int ("d") = D ("d")

	C. Complete Denotational Semantic specification.

	   Cal(expression) = Exp(expression)
	   Exp(value) = V(value)
	   Exp(value '+' expression) = V(value) + Exp(expression)
	   Exp(value '-' expression) = V(value) - Exp(expression)
	   Exp(value '*' expression) = V(value) * Exp(expression)
	   Exp(value '/' expression) = V(value) / Exp(expression)
	   V('+' int) = Int(int)
	   V('-' int) = - Int(int)
	   V(int) = Int(int)
	   V('+' int '.' dec) = Int(int) + Dec(dec)
	   V('-' int '.' dec) = - Int(int) - Dec(dec)
	   V(int '.' dec) = Int(int) + Dec(dec)
	   Int(d) = D(d)
	   Int(int d ) = 10*Int(int) + D(d)
	   Dec(d) = 0.1*D(d)
	   Dec(d dec) = 0.1(Dec(dec)+D(d))
	   D('0') = 0
	   D('1') = 1
	   ...
	   D('9') = 9
	
	D. Derivation of Denotational functions for: 1.56*-40
		Cal("1.56*-40") =
		Exp("1.56*-40") =
		V("1.56") * Exp("-40")
		(Int("1") + Dec("56")) * Exp("-40")
		(D("1") + Dec("56")) * Exp("-40")
		(1 + Dec("56")) * Exp("-40")
		(1 + 0.1(Dec("6")+D("5"))) * Exp("-40")
		(1 + 0.1(0.1(D("6"))+D("5"))) * Exp("-40")
		(1 + 0.1(0.1(6)+D("5"))) * Exp("-40")
		(1 + 0.1(0.6+D("5"))) * Exp("-40")
		(1 + 0.1(0.6+5)) * Exp("-40")
		(1.56) * Exp("-40")
		1.56 * V("-40")
		1.56 * (-Int("40"))
		1.56 * (-(10*Int("4")+D("0")))
		1.56 * (-(10*D("4")+D("0")))
		1.56 * (-(10*4+0))
		1.56 * (-40)
		-62.4

	3. Axiomatic Semantics	 

	A. Key ideas
	   a. For each production rule of the grammar, we define
	      the effect of executing a structure defined by the rule
	      in terms of the precondition and the postcondition.
	   b. The precondition is a logical assertion to be held true
	      before a structure is executed and the postcondition
	      is a logical assertion to be true after its execution.
	   c. The postcondition of the starting nonterminal represents
	      what the outputs of a program should represent.
	   d. Useful in program correctness proof.
	
	B. Some notations.
	   a. $S$->v   	: An assertion that a structure S evaluates to a 
	               	  value v
	   b. output(v)	: A value v being output
	   c. apply(opr, v1, v2)
	               	: The operator 'opr' is applied to the two operands
	               	  v1 and v2.
	   d. Exp(l)   	: The nonterminal Exp on the LHS of the rule.
	   e. Exp(r)   	: The nonterminal Exp on the RHS of the rule.

	C. Complete specification of our Calculator grammar:
	  
	   Production rule     	Precondition           	Postcondition
	   ---------------     	------------           	-------------
	   Cal -> Exp =	       	$Exp$-> v              	output(v)
	   Exp -> val          	$val$-> v              	$Exp$-> v
	   Exp -> val opr Exp  	$val$-> v1 and         	$Exp$->
	                       	$Exp(r)$-> v2         	apply(opr,v1,v2)
	   val -> sign int    	$sign$->v1 and        	$val$->v1*v2    
	                      	$int$->v2 
	   val -> int          	$int$->v              	$val$->v
	   val -> int . dec   	$int$->v1 and          	$val$->v1+v2
	                      	$dec$->v2 
	   val -> sign int . dec
	                      	$sign$->v1 and $int$->v2
	                      	$dec$->v3              	$val$->v1*(v2+v3)
	   int -> d           	$d$->v                 	$int$->v
	   int -> int d        	$int(r)$->v1 and       	$int(l)$->10*v1+v2
	                       	$d$->v2
	   dec -> d            	$d$->v                	$dec$->v/10
	   dec -> d dec       	$d$->v1 and            	$dec(l)$->(v1+v2)/10)
	                       	$dec(r)$->v2
	   d -> 0              	No precondition        	$d$->0
	   d -> 1              	No precondition        	$d$->1
	   ...
	   d -> 9              	No precondition        	$d$->9
	   sign -> +           	No precondition        	$sign$->1
	   sign -> -           	No precondition        	$sign$->-1	

	D. Another Example
	   Specifying precodition and postcondition for other statements
	   (including assignment statement that is used in the above example)

	a. Assignment statement: X->E
	   {P}S{Q}      //Notation for a statement S with precondition P
	                  and postcondition Q
	                  As a theorem, it says that if P is true before
	                  the execution of the statement and if the statement
	                  is actually executed, then Q is true after the
	                  execution.
	                  That is, (P and execution of S) -> Q
	   It is more convenient to compute the precondition from
	   the desired postcondition as generally we will have a certain
	   expected results (and conditions for the outcomes of executing
	   statements and the whole program) and also some assignment
	   statements are not easily invertible.
	   For example,  X=X*X+Y*Y+Z*Z
	     It is not straightforward or often even impossible to express
	     the old value of X and of Y and of Z, respectively,
	     in terms of the new value of X, which we would need if
	     we compute the postcondition from the precondition.
	   For assignment statement, we can compute the (weakest) precondition
	   as an axiom (as an immediately obtainable logical expression) in
	   	P = Q(X->E) where Q(X->E) is the assertion to be obtained
		            by replacing every occurrence of X by E.
	   For example, {P} X=X+Y {X>Y+Z}
	     In this case, P=((X+Y)>Y+Z)
	                    =X>Z
	b. Selection statement: If B then S1 else S2.
	   There cannot be an axiom as the precondition depends on
	   the type of two statement S1 and S2.
	   We can specify a rule (of inference) to specify how the
	   precondition can be computed.
	     In order to have: {P} (If B then S1 else S2) {Q}
	     it is necessary that {B and P} S1 {Q} and
	                          {not(B) and P} S2 {Q}
	   For example, consider the following if statement:
		if (X>Y) then X=X-1  else Y=Y-2
	        Suppose that postcondition is X+Y>Z
	        {P1} X=X-1 {X+Y>Z}  where P1 is the weakest precondition
	                            for the 'then' part
	             P1 = (X+Y>Z+1)
	        {P2} Y=Y-2 {X+Y>Z}
	        from which, we get P2 = (X+Y>Z+2)
	        Since P2 implies P1, we can take P2 for P.
	   That is, {X+Y>Z+2} if (X>Y) then X=X-1  else Y=Y-2 {X+Y>Z}

	c. Logical Pretest Loops:  while B do S end
	   This is most difficult type of a statement as a loop is a
	   sequence of unknown number of statements comprising the loop body.
	   The key idea is to figure out an assertion, called "Loop Invariant"
	   that is going to hold both before and after each execution of
	   the loop body, S.
	   This is because if "{I and B} S {I}" where I is the loop invariant
	   then "{I} while B do S end {I and not(B)}.
	   And, any P that implies I and any Q implied by (I and not(B))
	   can be the wanted precondition and postcondition.
	   To be useful, the loop invariant must satisfy the following:
		a. P -> I 	//(if P is true then I is true)
		b. {I} B {I} 	// Evaluation of B does not change I
		c. {I and B} S {I}  //I is true before and after each
		                    //iteration of the loop body
		d. (I and not(B)) -> Q
		e. The loop terminates
	   Actually finding a suitable/appropriate invariant is not
	   easy. It is much similar to the problem of finding the inductive
	   hypothsis in mathematical induction.
	   We will just check for some initial numbers of loop
	   iterations including zero iteration to see if any pattern
	   develops.

	   Example:  XX=X
	             YY=Y
	             while (YY<>0) do   // repeat as long as YY not zero
	             { T=XX
	               XX=YY
	               YY=(T%YY)        // Remainder
	             } 
		In the above example, if the postcondition is: XX=GCD(X,Y)
	        then I can be GCD(X,Y)=GCD(XX,YY)?
	        So, we can set P to I.
	        That is, 
	        {GCD(X,Y)=GCD(XX,YY)} the loop statement {XX=GCD(X,Y)}

	4. Comparison of three specifications.
	   a. Operational
	      Most detailed
	      Close to compiler/interpreter, and useful for language
	      implementation tool as long as the descriptions are kept
	      simple and informal.
	      Depends on algorithms and not mathematics.
	      The statements of one language are described in terms of
	      statements of a lower-level programming language, which can
	      lead to circularities
	   b. Denotational
	      Most rigorous widely known and practical method.
	      Solidly based on recursive function theory
	      as functions are used in mapping a syntactic structure
	      onto instances of well-defined mathematical objects and
	      the resulting mathematically objects together represent
	      the meaning of a program.
	      A good language design aid (a construct for which the
	      specification is complex and difficult may suggest
	      that it is not that viable)
	      Because of the complexity, a little use to language users.
	   c. Axiomatic
	      Defining an axiom or a rule of inference for each type
	      of a programming language has proven to be a difficult task.
	      Powerful tool for research into program correctness proofs.
	      Provides an excellent framework for reasoning about programs.
	      Its usefulness in describing the meaning of a programming
	      language to either language users or compiler writers is
	      limited.