Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
28 views

Programming Language CSC804 Lecture Note

language structure

Uploaded by

Michaelcareca228
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views

Programming Language CSC804 Lecture Note

language structure

Uploaded by

Michaelcareca228
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 58

Course: CSC804 Programming Languages

Dr. S. C. Echezona
RECOMMENDED TEXTS

Download the following text from the internet;

1. Understanding Programming Language M. Ben-Ari. Weissman Institute of Science

2. Cos 210 Programming Language Lecture Notes.

3. Proagrammig in the C++ by D. Ravichandran

1
Course Outline
Comparative study of the organization and implementation of a variety of programming
Languages and language features. Design principles are explored and applied in a historical
review of major languages. Procedural, functional, Logic-based, Object-oriented languages.
Research issues such a polymorphism, formal semantics and verification explored in depth.

See if you can download and analyze Donald Trump Language ( the newest programming
language)

Definitions of programming languages


1. Can be defined as a notational system for describing computational in machine readable and
human readable forms.

2. A notational system for expressing in algorithms.

3. A set of rules that specify which sequences of symbols constitute a program and what
computation the program described.

4. A programming language is an abstraction mechanism. It enables a programmer to specify a


computation abstractly, and to let a program ( usually assembler, compiler or interpreter)
implement the specification in a detailed form needed or execution in a computer.

Why many programming languages?

There are at present thousands for programming languages because different implementations
tend to explore different abstractions of the problems. Hence programming language paradigms
or ways of reasoning about the problems ( school of thoughts).

Programming languages evolved from the earlier machines code and Assembler to higher level
FORTRAN, COBOL, to even higher Lisp, SNOBOL, APL, BASIC, to Algol, C, Pascal, PL/I,
Ada, Modula-2, C++, Small Talk, Prolog, Icon, Perl, Visual Basic, Python, Java, C#, Ruby, PHP,
etc.

Technically, programming languages are similar because each can be used to express the
different classes to problems or abstractions talked about before. However, some many pose
more challenges. So we say that any such programming languages that can be used to express all
possible kinds of algorithm is Turin complete. That implies that languages that targets exclusive
kind of problems (examples spreadsheets generative languages etc.) may not be classified as
Turin complete.

2
Obviously knowledgeability in different programming languages enables a programmer
handle all jobs with different demands. Hence enhances productivity. Although a good
knowledge in one will enhance the appreciations of others.

Paradigms and Languages

Paradigms or schools of thought or ways of solving problems. The following subset of


paradigms will be reviewed.

- Imperative Languages.

- Functional

- Data Oriented.

- Object Oriented.

- Non-imperative

Note that developers of programming languages are aligned to each of these paradigms even
when others combine/overlap with more than one paradigm.

1. Imperative Languages

These languages tend to give instructions to computer mainly in the way the computer will
understand the problem not to more to how human may understand and solve the problems.
Often referred to as procedure and functional since this is the way machine will understand it.
This is the default paradigms for other schools of thought eg FORTRAN, COBOL, PL1, Algol
and its descendants, C, C++, Ada.

2. Funtctional

This is when you give computer instructions as a pure mathematical equations or functions.
It is a special case of imperative programming languages. Eg. Math lab Mathematica and the
original FORTRAN IV etc.

3. Data Oriented Languages

These are languages that make it possible to create elaborate data structure for the data and
the procedure to handle the data structure. These class of programming languages makes it
possible to develop large and program for that may not be possible in others. Examples include:
COBOL, Lisp, APL, SNOBOL, Icon, SETL.

3
4. Object Oriented Languages

These are a class of languages that visualize problem in real world form, Extensive effort is
made to encapsulate the data requirements and the solutions (methods) in a class and these class
may not depend on any other to perform its function unless specified. This class of programs
encourages data hiding as efforts and made so that other programs/methods do not interfere with
other methods/data outside it. Examples: Java, C++, C#, Python etc.

5. Non-Imperative

These are languages that may not bother to perform the usual assignment statements found
in other languages but rather prefers to describe the computations in questions and allows the
system figure out the computation. Most often a lower level program goes to generate underlying
solutions to the problem described. For instance some data base application allows one to
describe the structure of forms and tables offline and allows a code to generate programs to solve
these problems. Generative package such as spreadsheet packages, graphic, design packages,
simulation packages, etc., are some of the examples.

Standardization
Standardization is when a body/organization approves a standard set of codes for a language.
This can be done during or after language development. The advantage is that if your
programming language is standardized, it will be possible to port the language across several
hardware with minimal changes in the language. Many languages may have different dialects,
sometimes most interpreters are designed to warn if not standard codes are used in the program.

If you are writing a software package that is run on a wide range of computers, you should
strictly adhere to standard otherwise your maintenance task will be extremely complicated
because you must keep track of several machine specific items.

History of Programming Languages


Languages evolved thus:

- Machines code assembler.


- FORTRAN, COBOL
- Lisp, SNOBOL, APL, BASIC
- Ada. Modula 2, C++
- Visual Basic, Python, Java, C++, Ruby, PHP

4
Machine Code, Assembler

Instructions set vary enormously in size, complexity, and capabilities. Difficult for humans
Basic unit of computations is the machines words often used as a number.

Example
include p16f84 inc”
extern SQR-Root, SQR, SQUARE

Udata ;Reserved static data

NUM_1 res 1 ;The first number

NUM_2 res 1 ;The second number

Sum res 2 ;Two bytes HI: 10 for the sum

RMS res 1 ;One bytes for the outcome

VECTORS code

Goto MAIN ;The Reset vectors

TEXT code

MAIN Movf NUM 1, w;Get Number 1

Call SQR ;Square it

Movf SQUARE +1;Get lower byte

Movwf SUM + 1 ;Is the lower byte of sum

Movf SQUARE, w ;Get upper byte

Movf SUM ;Is the high byte of sum

Movf NUM_2 ;Now get number 2

Call SQR ; square it

Movf SQUARE +1, w ; Get lower byte

Addwf SUM + 1,f ;Add to the lower byte to sum

5
Btfsc STATUS, C ;Check if produces carry

Incf SUM, f ;Add the carry

Movf SQUARE, w ;Get upper byte

Addf SUM, f ;Add to the high byte of sum

Call SQR-Root ;Work out the Square root

Movwf RMS ;which is the root mean square

Global SUM

End

FORTRAN COBOL

“High level Language Imperative paradigm. Enter human readable arithmetic expressions can
be written on a single line. Flowchart widely used to assuage the chaos entailed by Goto-based
program control flow.

Example.

PROGRAM wimp

Func(xx) =7-6*(xx**5)

Data nout/6/, nin /5/, nstart/8/, acc/.oe-03/

Write (*,101)

101 format (‘numerical integration with Simpson rule’)

Write (*,104)

104 format (‘input left and right boundary) read (x,x) xl, x2

106 format (‘Number of intervals at start =’,14 write (*107) acc

107 format (‘relative accuracy required =’, +8.4) write (*,109)

109 format (‘//,’number of intervals’, 5x, ‘intergral value’,1)

N=nstant

6
Fa=0.0

If (x!) 4,3,4

3 con=func(x2)

Go to 5

4 con=func(x1) =func(x2)

5 continue

6 dx=(x2-x1)/ float (n)

Sum1=0.0

Sum2=0.0

L=n/2-1

X=x1

Do 10 I=1 to l

X=x + dx

10 sum2=sum2 + func(x)

f=(con = 4 * sum1 2.0 * sum2) * dx / 3.0

Write(*,111) n , f

111 format( ‘ ‘, 5x, i5,12x, f8.4)

If (abs(f - fa) – acc * abs(fa) 13, 13, 12

12 n=2 * n

Fa= f

Go to 6

13 continue

Stop

End

7
Lisp, SNOBOL, APL, BASIC
Functional paradigm and alternatives interpretive user friendlier, slow. Enter functions and
other complex computations can be written in a line or two in some of these languages. More
important are advances such as automatic recycling of memory and the ability to construct or
modify new codes while the program is running. Example;

(defun m-length (list)

(if (mull list)

(+1 (m-length (cdr list)))))

(defun print-list (list)

(if (not) mull list))

(program

(print (car list))

(print-list ( cdr list)))))

((((15x) 01)a) 723)

(defun sum-members (list)

(if

Algol, C, Pascal, Pl/1


Structured languages solves/eliminate go to control flow problem. Imperative paradigm; goto
considered harmful. The mainstream of then 1970’s emphasis on fast execution, and protecting
programs from themselves and each other. Programs become unmaintainable as they grow
bigger. Example.

Program TPC32

Uses SysUtils

Windows

8
Parser

Scanner

10Utilities

CommonVariables

Begin

WriteIn (Greetingstring)

{$WARNINGS ON}

CommandlinePtr cmdline

{$WARNINGS ON}

Read configurationFile

Compiler ModeOptions (cmd creat Exefile cmd compileToDisk

LinkerOptions

StartOf SourceFileData SourceFileBuffer

ProcessCompilerParameters

StrCopy TempString DefaultmoduleData LibraryName

CurrentFileName TempString

FindFilePath TempString fd-TPL-CFG feOringinal

SaveRegisterAndSetErrorReturnAddress

LoadLiberary

Case LastError of

FileNotFoud WriteIn ‘Warning: ‘TempString’ not found

NoError

else WriteCompilationErrrorAndHalt end

9
Ada, Modula-2, C++
Modula systems Programming language Data abstraction. Improvement in scalability, to go
along with the fact that you have to write zillions lines to do anything.

Example

PROCEDURE CreaCRC32Table (PolyHi, PolyLo: BITSET)

VAR

I, J: CARDINAL;

Temp Hi:

Temp LO: CARDINAL;

DEXOR: BOOLEAN;

BEGIN

FOR I; =O TO 255 DO

TempHi := 0 :

Temp Lo ;=0 ;

For J; = 1 To 8 DO

IF ( TempLo MOD 2 =1) THEN

DOXOR =TRUE;

ELSE

DOXOR: =FALSE;

END;

TempLo: =TempL0 >> 1

IF (TempHI MOD 2 =1) Then

TempLO : = TempLo + 8000H;

END:

10
TempHi : =TempHi >> 1;

IF DOXOR THEN

TempHi : = CARDINAL (BISET ( TempHi)/ Poly Hi);

TempLO: = CARDINAL (BITSET(TempLo)/PolyLo);

END;

END

CRCtab [i}. Hi: = BITSET ( TempHi);

CRCtab [i}.Lo; = BITSET ( TempLo);

END;

END create CRC32 Table;

Small Talk, Prolog, Icon Perl


Pure versions of object-oriented, fu nctional, and declarative paradigms; rapid prototyping and
scripting languages. Extreme power, often within specific problem domains. Often GUI
oriented. (S/W prototyping is the activity of producing incomplete parts of versions of the
programs being developed.)

Visual Basic, Python, Java, C#, Ruby PHP,


GUI oriented and web languages, mix friendly languages. The learning curve may be more in the
programming environment.

Runtime Systems
Programming Languages semantics are partly defined by the compiler or interpreter and partly
by the runtime system. A runtime system consists of libraries that implement language
semantics. They range from tiny to gigantic. They may be linked into generated code, or linked
into an interpreter and sometimes embedded directly to generated code. They include things
like implementation languages built-ins that are not supported directly by hardware, to
memory managers and garbage collectors, to thread schedulers to input/output.

11
Aside from the benefit of programming to solve the goto control flow, memory management is
a dominant aspect of the modern computing. If its not solved by the language it will dominate
the efforts required to develop most programs. For instance memory debugging in C, C++ may
occupy 60% of the effort required in getting in working solution.

Tracing from early languages which laid out data statically as global variables. Then when we
started using functions for everything, we discovered that most of the data was short lived and
could be reused if we allocate it to a stack (ie, local variables) Machine hardware evolved to
dedicate 1-2 registers to this. When objects came, we discovered that longer lived data tended
to be association with application domain concepts, and that which data had highly variables
lifetimes best served by (automatically managed) heap. 00 systems typically dedicate another
register (“self” or “this”0 for this.

I/O
Almost all programming Languages consider I/O as an after-thought. It is obvious that I/O is
dominant in all aspects of modern computing and of the effort required to develop most
programs. The dominance of graphics, networking and storage in modern computing hardware
advances; necessity of I/O in communication of results to humans; Proliferation of different
computing devices with different I/O capabilities. The implication is that programming language
syntax and semantics should promote extensible I/O abstractions as central to their language
definitions. Ubiquitous I/O hardware should be supported by language built-ins.

Compilers and interpreter variants


Variants of compilers
-Classic

Source code to machine code

-Preprocessor

Source code to simple source code ( C front, Unicorn)

-JIT

Compilers at runtimes, VM-to-native or otherwise.

-???

Source code to hardware

12
-???

Source code to network message(s)

Variants on the interpreter


-classic

Executes human readable text possible a statement or a time at a time.

-tokenizing

Executes “tokenized” sources code (array of arrays of token)

-tree

Executes via tree trasversal

-VM

Executes via software via software interpreter of a virtual machine instruction set.

A post script is a language used to describe the appearances of a printed page developed by
Adobe.

Enscript
-enscript is a program that converts ASCII text file int a postscript. It has some basic options
for readable formatting.

Language designs criteria


Programming language design is compiler construction with the following criteria.

-efficiency of execution.

-writable (efficiency of construction)

-scalability ( really big programs)

-extensibility (rolling out or adding new button)

-portability (where all will it run

-stability (can you count on it?)

13
-Implementability (if not who care)

-consistency

-simplicity.

-expressiveness

Context free grammars


A control free grammar G has;

 A set of terminal symbols T.


 A set of nonterminal symbols N
 A short symbol S, which is a member of N.
 A set of production rules of the form A –w, where A is a nonterminal and w is a
string of terminal and nonterminal symbols.

A context free grammar can be used to generate strings in the corresponding language as
follows.

 Let x=the start symbols

While there is some nonterminal Y and X apply any more production rules using Y
eg. Y-w.

When x consists only of terminal symbol it is a string of the language denoted by the grammar.
Each iteration of the loop is a derivation step. If an iteration has several nonterminal to choose
from at some point, the rules of derivation would allow any of these to be applied. In practice
pairing algorithms tends to always choose the leftmost non terminal or the rightmost
nonterminal resulting in a string that are leftmost derivations or rightmost derivation.

LR parser simplified version of a canonical LR parser to parser (separate and analyse) a text
according to a set of production rules specified by a formal grammar for a computer language
LR= Left to right.

YACC
(Yet Another Compiler Compiler)

Takes a context free grammar as input and generates a parser as output.

The parsing algorithm used by YACC and Bison (LALR) can only handle a subset of all legal
context free grammars.

14
-Full context free parsers existing since 1970’s use so much time and space and so are
prohibitive.

- YACC runs in linear time that is proportional to input size (ie number of tokens). A very
desirable property for a tool that must handle large inputs all the time like compilers

-YACC space requirements are worse than linear, but use tricks such as identifying that rows
in table that are identical, to keep the parse table reasonable in size.

Hand Simulating an LR parser


Supppose we simulate the “calc” parser of an example input. It uses the following algorithm.
The details are sort of beyond the scope of the texture’ What you get out of this is some
intuition.

Ip=first symbol of input

Repeat {

S= state on top of parse stack

A=*ip

Case action [s, a] of {

SHIFT S’; { push(a); push (s) }

REDUCE: A –>B: {

POP 2*|B| symbols; s’=new state on top

Push A

Push goto (S’, A)

ACCEPT: return Φ//sucess*/

ERROR: { error (“syntax error) s, a) halt}

15
LR Parsing
Consider the grammar:

E : E ‘+” T / E ‘ –‘ T/ T ;

T : T ‘*’ G / T ‘/’ G /G ;

G : F ‘^’ G/ F ;

F: Num/ (‘E’) ;

Given ( 213* 11^5) -8

Note that most times lexical analysis is usually interleaved with parser such that yyparser() calls
for the yylex() once every time it shifts. This might result in the mix of I/O and CPU balance
although most often the I/O is buffered to realise good performance.

The array of characters looks like this ( 2 1 3 * 1 1 ^ 5) – 8

The parse stack is empty and yyparse () calls on yylex () to read first token.

Parse stack Convert token Remaining input


Empty “(” 213*11^518

Shift or reduce? Shift. Note that you could reduce even in this empty stack case if the
grammatical had production rule where there was some options thing at the start.

Parse stack Current token Remaining Input


‘C NUM 213 *11^57-8
Shift or reduce? Shift cannot reduce ‘(’

Parse stack Current token Remaining input


NUM213 * 11^57-8
Shift or reduce? Before we can shift a * onto the stack, we have to have an T. We don’t have
one, we have to reduce. What can we reduce? We can reduce NUM to F

Parse stack Current token Remaining input


F * 11^ 5 7-8
(

16
Shift or reduce? We still need to have a T and don’t, so reduce again.

Parse stack Current token Remaining input


T * 11^ 5 7-8
(
Shift or reduce? Shift the ‘*’.

Parse stack Current token Remaining input


* NUM ^ 5 7 -8
T
(
Shift or reduce??

Try to complete this.

Assertion invariant, Preconditions and Postconditions.


Recall that imperative programming languages, such as, Assemblers, C, C++, etc are
languages where you need to specify the actions the way computer will carry out the
processing using such constructs as loop, goto and other control flows, while declarative
language declares what to do but leaves it to the system to figure out how to do it.

Common constraints in imperatives programming include;

-Sequence of statements: compound statements

-Selection statements

-Looping statements

-Function calls and returns.

Assertion invariants, preconditions and post conditions:


The problem with imperative programming language is how to know if what you requested
is what you want. In particular, people write codes for same problem in different ways. We
reason about program correctness by inserting logical assertions into our code; these may be
annotations or actual checks at runtimes to verify that expected conditions are true. Curley
braces are often used to enclose assertions especially among former Pascal programmers. The
more common conventions are assert (expr), which is a macro available in most C compilers.

17
A preconditions is an assertion before program execution that defines the expected state. It
defines requirements that most be true in other for statements to do what it is supposed to do.
A post condition is an assertion after a statement executes that describes what the statements
has caused to become true. An invariant is an assertion of things that do not change during the
execution of a statement. A invariant is particularly useful with loop statements.

While X>=y do

(X>=y if we get here)

X : =x - y

Suppose {X>=O} and {y>O} is true. Then we can further say {X> y>O } inside the loop. After the
assignment different assertions holds:

{X>=y and y> O}

While X >= y do

{y>=o and X>=y}

X:= x – y;

{X>=O and y>O}

While these kinds of assertion can allow you to prove certain things about program
behaviour, they only allow you to prove that program behaviour corresponds to requirements if
requirements are defined in terms of formal logic. There is a certain difficult in scaling up this
approach to handle real world software systems requirements but there is certainly a great
need for every technique that helps programmers write corrects programs.

POLYMORPHISM

Polymorphism simply means different forms. This refers to languages that allow codes that can
behave differently at different situations (context sensitivity). Polymorphism can be static or
dynamic. In static polymorphism, the multiple forms are resolved at compile time and
appropriate machine code generated. Examples of static polymorphism are:

-Type conversion: a value is converted from one type to another.

-Overload: the same name is used for two or more different objects or subprograms
(including operators)

18
-Generic: a parameterized template of a subprogram is used to create several instance of a
subprogram.

In dynamic polymorphism, the structural uncertainty is not resolved until runtime;

-Variant and unconstrained records; one variable can have values of different types.

-Runtime dispatching; the selection of a program to call is done at runtime.

Type Conversion

Type conversion is the operation of taking a value of one type and converting it to a
value of another type. There are two variations (1) translating a value of one type to a valid
value of the other and (2) transferring the value of an uninterpreted bit string.

Converting of numeric values say floating point to integer, involves executing


instructions to rearrange the bits of floating-point value so that they represent a suitable
integer. In effect, type conversion is done by a function receieving a parameter of one type and
returning a result of another type. Ada syntax for type conversion is the same as that of a
function

I: Integer:=5;

F: Float: =Float(I);

C syntax is

Int i=5;

Float F=(float);

In addition C includes implicit type conversion between types (primarily numeric type);

Int I ;

Float F=I;

Explicit type conversion are safe because they are simply functions. If the predefined
type conversion did not exist, you could always write your own. Implicit type conversions are
more problematical because the reader of the program does not know if the conversion was
intended or if it is an unintended oversight. Using integer in a complicated floating point
expression should cause no problem. But other conversion must be written explicitly.

The second type of type conversion is to simply allow the program to use the same bit
string in two different ways. C uses the same syntax for both forms of conversion. If it makes

19
sense to do a type conversion, such as between numerical types or pointer types, a true
conversion is done otherwise the bit field is simply transferred as it is.

C++ while the retaining C type conversion for compatibility, has defined a new set of
cast operators.

Dynamic cast

 Static cast (An expression of type T1 can be static cast to type T2 if T1 can be
implicitly converted to T2 or conversely. Conversely , static cast would be used for
type safe conversions like float to int and conversely.)
 Reinterpreted cast (unsafe type conversion)
 Cast-cast (used to allow assignment to constant objects)

Overloading
Overloading is the use f the same name to denote different objects in a single scope. The use of
the same name for variables in the same two different procedures (scope) is notoverloading
because the two variables do not exist simultaneously. The idea of overloading comes from the
need to use mathematical and input-output libraries of different types. In C for instance,
different name need not be used for absolute values function on different types:

Int i=abs(25)

Double d= fabs(1.57):

Long l + labs(-256):

In Ada and C++, the same name can be used for two or more different subprograms provided
that their parameter signatures are different. As long as the number and/or types (but not just
the name or modes) of the formal parameters are different, the compiler will be able to resolve
any subprogram call by checking the number and types of actual parameters.

In Ada

Functions sin (x: in float) return float;

Function sin (x; in Long Float) return Long Float;

F1, F2: Float;

L1, L2: Long Float;

F1:=sin(F2);

20
L1:=sin(L2);

An interesting difference between the two languages is that Ada takes the function returns type
into account when searching for overloading, while C++ restricts itself to the formal
parameters.

Float sin (float)

Double sin(double): // overloads sin

Double sin (float): //error redefined scope

Of particular interest is the possibility of overloading predefined operators like + and * in Ada

Function “+” ( V1, V2, Vector) return Vector;

Of course you have to supply the function to implement the over loaded operator for the new
types note that the syntactic properties of the operator, in particular its precedence, are not
charged C++ has a similar facility for overloading.

Vector operator + ( const Vector &, const Vector &);

This is just like functions declaration except for the use of reserved word operator. Operator
overloading should only be used for operators that are similar to the predefined meaning so as
not to confuse maintainers of the program.

If used carefully overloading can reduce the size of the name space and ensure the portability
of a program. It can even enhance the clarity of a program because artificial names like fabs are
no longer needed. On the other hand, indiscriminate overloading can easily destroy readability
by assigning too many meanings to the same name. Overload should be restricted to
subprogram that do the same sort of computation. So that the reader of a program can
interprete the meaning just from the subprogram name.

Simple function overloading program in C++

Program to perform swapping of two data items of integer, floating point


numbers and character types without function overloading.

//Function without overloading

//Swap two items

#include <iostream.h>

21
void main(void)

void swap_int (int &ix, int &iy);

void swap_float (float &fx, float &fy);

void swap_char (char &cx, char &cy);

//Function declaration

int ix, iy;

float fx, fy;

char cx, cy;

cout <<”Enter any two integers”<< endl;

cin >> ix >> iy;

cout << “Enter any two floating point numbers “ << endl;

cin>> fx >> fy;

cout << “Enter any two characters” << endl;

cin >> cx >> cy;

//swap on integers

cout << “Swapping on integers \n”;

cout <<” ix =” << ix << “iy = “<< iy << endl;

swap_int(ix,iy);

cout << “After swapping \n”;

cout << “ix = “ << ix << “iy = “ << iy << endl;

//Floating point numbers

22
cout <<endl;

cout << “Swapping of floating point numbers \n”);

cout << “ fx= “ << fx << “fy = “ << fy <<endl;

swap_float( fx, fy);

cout << “After swapping \n”;

cout << “ fx = “ << fx << “fy = “<< fy << endl;

// Swapping of charracters

cout << endl;

cout << “Swapping of characters \n”;

cout << “cx = << cx << “cy = “ << cy<< endl;

swap_char (cx,cy);

cout << “After swapping \n”;

cout << “cx = “<< cx << “cy = “ << cy <<endl;

void swap_int (int &a, int &b)

Int temp;

temp = a;

a = b;

b = temp;

Void swap_float ( float &a, float &b)

23
{

float temp;

temp = a;

a = b;

b = temp;

Void swap_char (char &a, Char &b)

char temp;

temp = a;

a = b;

b = temp;

Output of the above program

Enter any two integers

10 20

Enter any two floating point numbers

11.11 -22.22

Enter any two characters

a b

swapping of integers

ix = 10 iy = 20

24
after swapping

ix = 20 iy = 10

swapping of floating point numbers

fx =11.11 fy= -22.219999

after swapping

fx =-22.219999 fy = 11.11

swapping of characters

cx = a cy = b

after swapping

cx = b cy = a

The following program demonstrates how function overloading is carried out for
swapping of two variables of various data types, namely integers floating point
numbers and character data types.

//Overloading of functions

//Swapping of two items

Include <iostream.h>

Void swap (int &ix, int &iy);

Void swap (float &fx, float &fy);

Void swap (char &cx, char &cy); // function declaration

Void main void

Int ix, iy;

25
float fx, fy;

char cf, cy;

cout << “enter any two integer “ << endl;

cin >> ix >> iy ;

cout << “ enter any two floating point numbers “ << endl;

cin >> fx >> fy;

cout << “ enter any two characters “ << endl;

cin >> cx >> cy;

// swapping on integers

cout << “swapping of integers \n” ;

cout << “ix = “ << ix << “iy = “ << iy <<endl;

swap (ix,iy);

cout << “after swapping \n”

cout << “ix = “<< ix << “iy = “ << iy << endl;

// floating point numbers

cout << endl;

cout << swapping of floating point numbers \n”;

cout << “fx =” << fx << “fy = “ << fy << endl;

swap (fx, fy);

cout << “ after swapping \n”;

cout << “fx = “ << fx << “fy = “ << fy << endl;

//swapping of characters

26
cout << endl;

cout << “ swapping characters \n”;

cout << “cx = “ << cx << “ cy = “ << cy << endl;

swap (cx, cy);

cout << “after swapping \n”;

cout << “cx =” << cx << “ cy = “<< cy << endl;

Void swap (int &a, int &b)

int temp;

temp = a;

a = b;

b = temp;

Void swap (float &a, float &b)

float temp;

temp = a;

a =b;

b = temp;

Void swap (char &a, char &b)

27
{

char temp;

temp = a;

a= b;

b= temp;

Output of the above program

Enter any two integers

100 200

Enter any two floating point numbers

-11.11 22.22

Enter any two characters

s t

Swapping of integers

ix =100 iy = 200

After swapping

ix = 200 iy = 100

Swapping of floating point numbers

fx = -11.11 fy = 22.219999

After swapping

fx = 22.219999 fy = -11.11

28
Swapping of characters

cx = s cy = t

After swapping

cx = t cy = s

Generics
Arrays, lists and trees are data structures that can store and retrieve data elements of arbitrary
type. It is necessary to store several types simultaneous by some form of dynamic
polymorphism is needed. However, if we are working only with homogenous data structures
such as an array of integers, or a list of floating point numbers, it is sufficient to use static
polymorphism to create instances of a program template at compile time.

For instance, consider a sort program to sort an array. The type of array element is used only in
two places. When comparing elements and when swapping elements. The complex
manipulations of indexes is the same whatever the array element type.

Ada sort program

Type Int Array is array (integer range <>) of integer;

procedure sort ( A: Int Array) is

Temp, Min: Integer:

Begin

For I in A’ first…A’ last-1 loop

Min= I

For j in j+1…A’ last loop

If A(J) <> A(Min) then Min:= J; end

--- compare elements using I

End loop

Temp := A (I); A(I): = A (Min):

29
A(Min) = Temp;

----Swap elements using=

End loop;

End sort

In fact, even the index type in irrelevant to the programming of the procedure, as long as a
discrete type (such as characters or integers) is used.

To obtain a sort procedure for some other elements type such as character, we could physically
copy the code and make the necessary modification but this will introduce the possibility of
errors. Furthermore, if we wish to modify the algorithm, we would have to do so in each copy
separately. Ada defines a facility called generics that allows the programmer to define a
template of a subprogram and then to create instances of the template for several types. While
C lacks a similar facility, its absence is less serious because void pointers, the size operators and
pointers to functions may be used to program if unsafe subprograms. C++ calls its
implementation template. Note that the use of generics does not ensure that any of the object
code will be common to the instantiations, in fact, an implementation may choose to produce
independent object code for each instantiation.

Here is a declaration of a generic subprogram with two generic formal parameters in Ada.

generic

Type Item is (<>);

Type Item_ Array is array (integers range <>) of Item;

Procedure Sort ( A: Item_Array):

This generic declaration does not declare a procedure, only the template of a procedure. A
procedure body must be supplied: The body will be written in terms of the generic parameters:

Porcedure Sort(A: Item Array) is

Temp Min Item;

Begin

……………….exactly as before

End sort

30
To get a (callable) procedure you must instantiate the generic declaration, that is, create an
instance by furnishing generic actual parameter:

Type Int_Array is array (integer range <>) of integer;

Type char sort (integer range of integer<>) of character;

Procedure Int_Sort (A: Int_Array ) is new Sort (Interger, Int_Array) ;

Procedure char Sort (A: char_Array) is new sort (character, char_Array);

These are actual procedure declarations instead of a body following the “is” keyword, a new
copy of the generic template is requested.

The generic parameters are compiled-time parameters and are used by the compiler to
generate the correct code for the instance. The parameters form a contract between the code
of the generic procedure and the instantiation. The first parameter Item is declared with the
notation(<>), which means that the instantiating program promises to supply a discrete type
such as Integer or character and the code promises to use only operations valid on such types
since every discrete type has the notational operator defined on its values, the procedure sort
is assured that “!” is valid. The second generic parameters Items Array is a clause in the
construct that says. Whatever type was given for the first parameters, the second parameters
must be an integer indexed array of that type.

The contract model works both ways. An attempt to do an arithmetic operation such as “+” on
values of type Item in the generic body is a compilation error, since there are discrete types
such as Boolean for which arithmetic is not defined. Conversely, the generic procedure could
not be instantiated with a record type because the procedure needs “!” which is not defined for
records.

The motivation for the contract model is to allow programmers use to reuse generic units with
the certain that they need not know how the generic body is implemented. Once the generic
body compilers correctly, an instantiation can fail only if the actual parameters do not fit the
contract. An instantiation will not cause compile errors in the body.

Templates in C++
In C++ generics are implemented with the template facility.

Template class Item_Array void Sort (item Array param)

31
:

There is no need for instantiation; a subprogram is created implicitly when the subprogram is
used.

Typedef int I-Array [100}

Typedef char C-Array [100]

I-Array a:

C-Array c;

Sort (a); //Instantiation for int array

Sort (c) //Instantiate for char arrays

Explicit Instantiation is an optional programmer-specified optimization; otherwise the compiler


decides exactly which instantiations needs to be done. Template can only be instantiated by
types and values, or more generally with classes.

Illustration of templating in C++

A function template is defined to find the sum of a given array of elements such as in, float and
double, etc.

template <class T>

T sum (T *array, int n)

T temp = 0;

for (int i = 0; i<=n-1; i++)

temp = temp + array[i];

return(temp);

A function template is defined to swap two given items of different data like int, float, double or
character.

32
template<class T>

T swap ( T &first, T &second)

T temp;

temp = first;

first = second;

second = temp;

return (0);

A program to define a function template for summing an array of integers and an array of
floating point numbers.

// using fuction template

#include <iostream.h>

template <class T> T sum ( T *array, int n)

T temp = 0;

for (int I = 0; I <= n-1; i++)

temp = temp +array[i];

return(temp);

int sum (int *a, int *b);

float sum (float *b, int n);

void main()

{
33
int n= 3, sum1;

float sum2;

static int a[3] = {1,2,3};

static float b[3] = {1.1,2.2,3.3};

sum1 = sum(a,n);

cout<< “ sum of integers = “ << sum1<< endl;

sum2 = sum(b,n);

cout<< “sum of floating point numbers =”<< sum2;

cout <<endl;

Output of the above program

Sum of the integers = 6

Sum of the floating point numbers = 6.6

A program to define the function template for swapping two items of various data types such as
integer and floating point numbers.

// Using function template

#include <iostream.h>

template<class T> T swap (T &first, T& second)

T temp;

temp = first;

first = second;

second = temp;

return(0);
34
}

int swap (int &a, &b);

float swap (float &a, float &b)

void main()

int ix, iy;

float fx, fy;

cout << “enter any two integers \n;

cin >> ix >> iy;

cout << “enter any two floating point numbers? \n;

cin >> fx >> fy;

swap(ix, iy);

cout << “After swapping integers \n;

cout << “ix = “ << ix << “ iy = << iy<< endl;

swap( fx, fy);

cout << “after swapping floating point numbers \n”;

cout << “ fx = “ << fx << “fy = “ << fy << endl;

Output of th above program

Enter any two integers

10 20

Enter any two floating point numbers ?

-11.22 33.33

35
After swapping integers

Ix = 20 iy = 10

After swapping floating point numbers

Fx 33.330002 fy = -11.22

POLYMORPHISM AND DATA STRUCTURES

In Ada and C++, we have two ways of constructing polymorphic data structures: Generics in Ada
and Templates in C++ for compile time polymorphism, and class wide types in Ada and pointer/
references to classes in C++ for runtime polymorphism. The advantage of generic/templates is
that the data structure is fixed when it is instantiated at compile time; this can improve both
the efficiency of code generation and the memory that needs to be allocated for the data
structure. i

Java chooses to implement only runtime polymorphism. As in Smalltalk and Eiffel every class in
Java is considered to be derived from a root class called object. This means that a value of any
non-primitive type can be assigned to an object of type Object (of course this works because of
the reference semantics).

To create a linked list, a node class would first be defined as containing (a pointer to) an Object.
The list class would then contain methods to insert and retrieve values of the object.

Class Node {

Object data;

Node next;

Class List {

Private Node head

Void put ( object data) {….} ;

Object Get () {….};

36
/*Java Code*/

If L is an object of type List and a is an object of type Airplane-Data, then L. Put (a) is valid
because Airplane-Data is derived from Object. When a value is retrieved from the list, it must
cast to the proper descendant of Object.

a = (Airplane_Data) List.Get();

Of course if the value returned is not type Airplane_Data ( or descendant from the type), an
exception will be raised.

The advantage of this paradigm is that it is very easy to write generalized data structures in
Java, but compared with generics/template there are two disadvantages: (1) the additional
overload of the reference semantics( even for a list of integers), (2) the dangers that an object
placed on the wrong queue will cause a runtime error when retrieved.

Variant Records
Variant records are used when it is necessary to interprete a value in several different ways
at runtime. Common examples are:

 Messages in a communication system and parameter blocks in operating system


calls. Usually the first field of the record is a code whose value determines the
number and types of the remaining fields in the record.
 Heterogeneous data structures such as a tree which may contain nodes of various
types.

To solve those types of problems programming languages introduce a new category of types
called variant records which have alternative list of fields. Thus a variable may initially contain a
value of one variant and later be assigned a value of a different variant with a completely
different set of fields. In addition to the alternative fields, there may be fields which are
common to all records of this type; such field usually includes a code which is used by the
program to determine which variant is actually being used. For example, suppose that we wish
to create a variant records whose fields may be either an array or a record.

Typedef int Arr[10];

Typedef struct {

Float f1 ;

Int i1;

37
} rec;

Let us first define a type that encodes the variant:

Typedef enum { Record-code Array-Code}codes;

Now a union type in C can be used to create a variant record which can itself be embedded into
structure that includes the common tag fields.

Typedef struct {

Codes code ; /*common tag field*/

Union { /*union with alternative field}

Arr a:

Rec r;

} data;

} S_Type;

S_Types S;

From a syntactical point of view this is first ordinary nesting of records and arrays within other
records. The difference is in the implementation; the fields data is allocated enough memory to
contain the longer of the array field s or the record r (fig 10.1). Since enough memory is
allocated

Code F1 i1

Code a
Fig. 10.1

to accommodate the largest possible field, variant records can be extremely wasteful of
memory if one alternative is very large and the others small:

Union {

Int a[1000];

Float f;

38
Char c;

/* C Code */

At the cost of complicating the programming, the best solution in this case is to use a pointer to
the long fields.

The assumption underlying variant records is that exactly one of the fields of the union is
meaningful at any one time, unlike an ordinary record where all fields exists simultaneously.

If (s.code ==Array.Code)

I= s.data. a [5]; /*Array variant*/

else

I =s.data.r.i1; /*Acess second variant/

The main problem with variant records is that they can potentially cause serious bugs. It is
possible to treat a value of one type as if it were a value of other type (say to access a floating
point number as an integer). Since the union construction enables the program to access the
same bit string in different ways. In fact Pascal programmers use variant records to do type
conversion which is not directly supported in the language. In the above example, the situation
is even worse because it is possible to access memory locations which have no meaning at all:
The field s. data.r might be 8bytes long to accommodate the two numbers, while the field
s..data.a might be 20 bytes long to accommodate ten integers. If a valid record is stored in
s.data. r, s.data. a [5] is meaningless.

Ada does not allow variant records to be used to break type checking. The “code” field that we
used in the example is now a required field called the discriminant, and access to the variant
fields are checked against the value of the discriminant. The discriminant appears as a
“parameter” to the type.

Formal Semantics 2: Denotational and Axiomatic Semantics


Denotational Semantics
-Semantics described via functions.

-Eg. a function that maps an integer expression to its value.

39
. Val: Expression –->value

-So

Val (2+*4) =14

and

Val (2+3)*4)=20

.The domain of a function such as Val is a syntactic space: The set of expression is a syntactic
entity.

.The range of a function such as Val is a semantic space: The set of integers is a semantic entity.

. In this context. 2+3*4 is said to denote 20.

.This is the origin of the term denotational semantics.

. In many programming languages, a program can be regarded as something that receives input
and produces output.

.Thus, the semantics of a program is a function mapping input to output.

. A semantic function for programs would be: Program –-> {Input - Output}

.The semantic space is a set of functions, from Input to Output.

.E.g. If p is the program:

Int main ()

{ int x ;

Scanf (“%d”, &x);

Printf (“%d/n, x)

Return 0;

.Here p denotes the identity function f from integers to integers: P(P)= f, where:

. f : Integers –> integers is given by f(x) =x

. The function symbol –> is right associative so we can simplify functions such as:

40
. P: Program—> { Input –Output}

To

. P: Program –> Input –Output

.This allows for a slightly simpler notation particularly as semantic spaces are frequently
functions.

. A denotational semantics for a programming language consists of three elements:

1. A definition of the syntactic space on which the semantic functions act,

2. A definition of the semantic space consisting of the values of the semantic functions

And

3. A definition of the semantic function themselves.

Syntactic Spaces
 In denotational semantics, we use a notation that is essentially the same as the abstract
syntax used in operational semantics.
 The sets are listed first using capital letters to denote set elements.
 The grammar rules are then listed which recursively define the elements of the set.

E.g. the syntactic spaces Number and Digit are specified as follows:

. D: Digit

N: Number

. N—>N D| D

D -’0’ |’1’|….|’9’

 A denotational definition views the spaces as sets of syntax trees specified by the
grammar rules.
 Semantics functions are defined recursively on these sets, based on the structure f a
syntax tree mode.
 These are the sets from which semantics functions take their values.
 They are sets like syntactic spaces but may have extra structure depending on their
use.

41
 E.g. the integers have the arithmetic operations “+”, “-“ and “*”.
 Such extended spaces are called algebras.
 Strictly, these need formal specification but we can often get away with “well-known”
spaces.
 E.g. the semantics space of the integers:

Space v: Integers = {…, -2,-1,0,1,2,...}

Operations:

+: Integer x Integer -Integer

-: Integer x Integer -Integer

*: Integer x Integer -Integer

 We restrict ourselves to three operations because they are the only ones used in our
sample language.
 The symbols v: indicate that the name v will be used to indicate an arbitrary member
of the set.
 A semantics function Is specified for each syntactic space.
 Thus the value function from the syntactic space Digit to the semantic space Integer is
written: D : Digit – Integer
 The value of a semantic function is specified recursively on the trees of the syntactic
spaces.
 This is done by giving a semantic equation for each grammar rule.

E.g the grammar rules for digits:

D—>‘0’ | ‘1’| …|’9’

Give rise to the syntax trees

D D …. D

| | |

‘ 0’ ‘1’ ‘ 9’

and the semantic function D is defined as follows:

D D D

D ( | ) =0, D ( | ) = 1, ..., D ( | )=9

42
‘ 0’ ‘1’ ‘9’

 This (revolting) notation is usually shortened to:


 D [[‘0’]]= 0, D[[‘1’]]=1, …, D [[‘9’]] =9

Where the [[….]] notation indicates that the argument is the syntax tree with the listed
argument(s) as child(ren).

Another Example

 The semantic function

N : Number - Integer

From numbers to integers is based on the syntax

. N - ND/D

And is given by

N[[ND]] = 10* N[[N]] + N[[D]]

N[[D]] =D[[D]]

Another Example

.The notation [[ND]] refers to the tree

N D

And [[D]] to the tree

Denotational Semantics for Integer Expressions

43
 Using the expression language of last week and the approach described here:
 Syntactic Spaces

E: Expression

N: Number

D: Digit

. E –>E1 ‘+’ E2 | E1 ‘–‘ E2| E1 ‘*’ E2 | ‘(‘ E’)’ | N

N – >N D | D

D -’0’ | ‘1’ | …| ‘9’

Semantic Spaces

. Space v; Integer = {…., -2, -1, 0, 1, 2, ….}

. Operations:

+:Integer x Integer –Integer

-: Integer x Integer – Integer

*: Integer x Integer –Integer

Semantics Functions

E: [[E1 ‘+’ E2]] = E [[E1]] + E[[E2]]

E[[E1 ‘–‘ E2]]= E [[E1]] – E[[E2]]

E[[E1’*’ E1]]= E[[E1]] * E [[E2]]

E[[‘(‘ E ‘)’]]= E[[E}]

E[[N]]= N[[N]]

N: Number –>Integer

N[[ND]] = 10* N[N]] + N[[D]]

N{{D]]= D[[D]]

D : Digit - Integer

D[[0]] =0, D[[1]] =1, …, D[[9]]=9


44
Evaluation with Denotational Semantics

Let’s see how it works to evaluate expression

‘(2+3) *4’

E [[ (‘2’+’3’) * ‘4’]]

= E [[ ( ‘2’ + ‘3’ ) ]]* E [[4]]

=(E[[‘2’ + 3]]) * N [[4}}

=(E [[2]] + E[[3}})* D[[4}]

= (N {[2}] +N [[3]] * 4

= (D [[2]] + D [[3]] + 4

(2+3) * 4

=5 * 4

=20

Environments and Assignment


 Once again, if we add the concept of variables to our language, we must add
environments to the semantics.
 This time, the set of environments is simply a new semantic space.

Space Env : Environment = Identifier –> Integer U { Undef}

 The value undef is given a special name, bottom, and is denoted by the symbol ∟.
 We write Integer {Undef} as Integer∟.

Environments
. We can now define Env0, the empty environment by

. Env0 (I) = ∟ for a identifiers I

45
 The evaluation of expressions within an environment must include the environment
as a parameter.
 Thus the semantic value of an expression now becomes a function mapping
environments to integers:

E “ Expression -Environment-Integer∟

 The value of an identifier is ite=s value in the environment:

E [[N]] ( Env) = N [[N}]

 In other expression cases, the environment is simply passed on to the


subexpressions.

Statements and Statement Lists

 To extend the semantics to these; we note that these are function mapping
environments to environment
 Execution of a statement simply adds its value to the environment.
 We will use the same & notation we saw last week for adding to an environment.
 . A statement list is simply the composition of the equivalent functions:

(f o g)(x) = f (g)(x) )

Denotational Semantics for the Augmented Language

 Syntactic Spaces

P : Program

L: Statements-List

S: Statement

E: Expression

N: Number

D: Digit

I: Identifier

A: Letter

46
Abstract Syntax

P –> L

L -L1 ‘;’ L2 | S

S –>I ‘:=’ E

E –E1 ‘+’ E2 | E1 ‘–‘ E2 | E1 ‘*’ E2 | ‘( ‘E’)’| N

N – >N D | D

D –> ‘0’ | ‘1’| ...| ‘9’

I-I A | A

A –>’a’ | ‘b’| ….|’z’

Semantics Spaces

. Spaces v: Integer = {…, -2. -1. 0. 1. 2,…}

. Operations

+: Integer X Integer -Integer

-: Integer x Integer--->Integer

*: Integer x Integer –>Integer

Space Env: Environment = Identifier -Integer

Semantics Functions

P : Program –>Environment

P:[[L]]= L [[L]] (Envo)

L : Statement List –> Environment –> Environment

L {L1 : L2 ]] (Env) = L [[L2]] 0 L [[L1]] (Env)

L [[S}} (Env) = S [[S]] (Env)

47
S: Statements—>Environment—>Environment

S [[S]] (Env) = S {[S}} (Env)

S; Statement ---Environment---Environment

S [[I = E]] ( Env ) = Env & { I =E[[E]] (Env}

E: Expression –>Environment –> Integer

E[[E1 ‘+’ E2]] (Env) = E[[E1]](Env) + E [E2]](Env)

E[[E1 ‘–‘ E2}] (Env) = E[E1]](Env) – E[[E2](Env)

E[E1 ‘*’ E2]](Env) = E[[E1]](Env) * E{E2]](Env)

E[[E]](Env) = E{[E](Env}

E[[I]](Env) = Env(I)

E[[N]]= N[[N]]

N: Number—>Integer

N[[ND]]= 10 N[[N]] + N[[D]]

N[[D]]= D[[D]]

D: Digit—>Integer

D [[‘0’]]=0, D[[‘1’]] =1, …, D{[‘9’]] =9

Denotational Semantics for Control Statements


 To complete the discussion, we extend our semantics to if and while statements
 Syntactic Space

S: Statement

 S—> I ‘: =’ E / ‘If’ E ‘then’ L1 ‘else’ L2 ‘fi’ | ‘While’ E ‘do’ L ‘od’


 We also need to extend the semantic function S

Semantics Function for if statements

48
 S: Statement -Environment—>Environment

S [[ ‘If’ E ‘then’ L1 ‘else’ L2 ‘fi’]] =

If E [[E]] (Env) > 0 then L {[L1}} (Env) else L[[L2]] (Env)

Where, given:

F: Environment –Integer

G: Environment –Environment

H: Environement –Environment

. (if F then G else H)(Env) = G(Env), if F(Env) > 0

H(Env), if F(Env) <= 0

Semantics for While Statements

This is more difficult.

.If we define F: Environment - Environment as

F(Env) = S[[while E do L od]]

Then

. F(Env) = if E[[E]] (Env) <0 then Env else F(L[[L]](Env))

This is a recursive equation for F

 To use this as a semantic function we must be sure that there exists a unique solution
for F in some sense.
 How this termination is assured is beyond the scope of this subject.

What about the loop;

I:= 1 :

While I do I : = I + od which does not terminate

 This cannot be assigned a function value.


 We define loops of this kind to have an undefined value.
 The semantic space of statements now becomes:

S: Statement—>Environment∟—>Environment∟ where

49
Environment∟ =(Identifier –>Integer∟)∟

Axiomatic Semantic
How we define the semantics of a program by describing the effect that its execution has on
assertions made about the program.

Assertions are of two kinds

Preconditions

and

Post-conditions.

Pre and Post conditions

E.g given the statement

x :=x + 1

We would expect that, whatever the value of x was before execution, its value after execution
is one greater.

This equivalent to setting a precondition of x = A and a post condition of x = A + 1

We write this:

{x=A} x : = x + 1 {x= A+1}

We can also write this as;

. { x = A}

X:=x+1

{x = A +}

A second example is:

x : =1/ y

Where the precondition is that y be non-zero and the post condition is that x = 1/y.

50
{y<>0}x := 1/y {x =1/y}

We can extend this notion to more than a single statement:

{n> =1 and for all I, 1 <= I<= n, a{i} =Ai}

Sort-program

{sorted(a) and permutation (a, A)}

We can often test such condition at execution time.

E.g.

The C assert mechanism.

Axiomatic Specification

An axiomatic specification of the program construct C takes the form:

{P}C{Q}

Where P and Q are assertions.

The semantics of this is that if P is true prior to execution of C then Q is true after execution.

This specification is not unique.

In general, given the assertion Q there are many possible assertions P with the property
{P}C{Q}.

E.g. For:

. C =x : =1/y

Q = x =1/y

Then

P = y <> 0 or y > 0 or y < 0

Are all appropriate precondition

Weakest Pre-condition

51
Where we have a range of possible pre-conditions it is possible to identify the most general or
weakest assertion which will result in Q being true.

We call this the weakest precondition and write:

wp(C,Q)

In the x =1/y example, wp(C, C,Q) is y <> 0

We can now redefine the property {P}C{Q} as follows

{P}C{Q} iff P—>wp(C, Q)

Now, we define the axiomatic semantic of the language construct C as the function wp (C, Q)
from assertion to assertions.

This is called a predicate transformer in that it takes a predicate (assertion) as argument and
returns a predicate(the weakest precondition) as result.

Examples of wp(C,Q)

wp (‘x : 1 / y’, x=1/y) = {y<>0}

wp(‘x := x+ 1’, x>0) = {x>-1}

wp (‘x :=x+A’,x=A) = {x-=A-1}

To completely determine the semantics of an assignment such as:

. x := E

We need to specify wp(‘x : =E’ , Q) for all postconditions Q.

Properties of wp

Law of the Excluded Miracle:

wp(C, false)=false

Distributivity of Conjunction:

wp(C, Q and R) =wp(C, Q) and wp (C, R)

Distributivity of Disjuntion:

wp(C, Q) or wp(C, R) =wp(C, Q or R)

52
Law of Monoticity

If Q—>R then wp (C, Q) –> wp (C, R)

Axiomatic Semantics of the Sample Language

 Note, first, that the specification of the semantics of isolated expressions is not normally
undertaken in axiomatic semantics.
 Essentially, the assertions axiomatic semantics are statements about the side effects of
language constructs.
 That is, they are statements about values of identifiers in the environment of the
construct.

The abstract syntax for which we will define wp is:

P -L

L—>L1’;’ L2 | S

S—>I ‘: =’ E | ‘if’ E ‘then’ L1 ‘else’ L2 ‘fi’ | ‘while’ E ‘do’ L ‘od’

Syntax rules such as P –> L and L -S do not need separated specification, since these rule
state that the wp function for a program is the same as for its associated statement list and that
if a statement list is a single statement then wp for the list is the same as that of the statement.

Statement lists:

wp (L1; L2, Q)= wp (L1, wp(L2, Q))

Assignment statements:

wp(I ;=E, Q) = Q[E/I]

This introduces a new notation Q[E/I] which is defined to be the assertion Q in which all free
occurrences of I are replaced with E.

A free occurrences is one which is not subject to “for all” or there exists”

Free Variables.

53
E.g. with

Q = {for all I, a{i}>a{1}}

We can evaluate

Q{i/j} ={for all I, a{i} > a{1}}

But

Q{1/i} = Q

This is because j is free in Q but i is not.

An Example of wp for Assignment

Consider the example:

wp ( ‘x : =x +1 ;’ X > 0)

Here:

I=x

E=x=1

Q={x>0}

So, from

wp(I:= E, Q) =Q[E/I}

We get

Q{E/I} ={x = 1 > 0}

={x>-1}

which is the result we obtained informally before.

Axiomatic Semantics for if statements

54
The weakest pre condition for the if-statement in our sample language is defined as;

wp (if E then L1 else L2, Q)= (E>0—>wp(L1, Q) and (E <0 –>wp (L2, Q))

This looks worse than it in fact is.

An Example if-statement

Consider:

wp(if x then x:=1 else x : = -1 fi, x = 1)

Here

Q= {x=1}

E=x

L1=x:=1

L2=x :=-1

wp (C,Q)= (x>0—>wp(x:=1,x=1)) and (x<0—>wp(x;=-1,x=1))

This can be simplified:

wp(C, Q) = (x>0—wp(x:=1, x=1)) and (x<0) –>wp(x:=-1,x=1))

=(x>0) –>1=1)and (x<=0-1=1)

Now:

(P—>Q) is the same as (Q or not P)

So:

(x>0—> 1=1)=((1=1) or not (x> 0)) =true

and

(x <= 0 -1=1)=((-1=1) or not (x<=0)) = not(x<=0)=x>0

Substituting back

wp (if x then x : =1 else x : = -1 fi, x=1) =

=(x>0-1=1) and (x<= 0 -1=1)

55
=true and x>0

=x>0

which is what we would expect.

Axiomatic Semantic for while-statements

The while-statement while E do L od executes as long as E > 0.

Once again, this recursive behaviour creates problems for formal semantics.

We will give an inductive definition based on the number of times the loop executes.

Let H(while E do L od, Q) mean that the loop executes i times and terminates in a state
satisfying Q.

Clearly:

H (while E do L od, Q) = E < = 0 and Q

and

H1(while E do L od, Q)

= E > 0 and wp( L, Q and E <=0)

=E> 0 and wp( L1 H0 (while E do L od, Q)

In general:

Hi+1(while E do L od, Q)

=E>0 and wp(L, Hi(while E do L od, Q)

We can now define wp for while statements as:

wp(while E do L od, Q)

= there exists an i such that Hi(while E do L od, Q)

Note that the loop must terminate for this to make sense.

A non-terminating loop always has false as its wp.

E.g.
56
wp(while 1 do od, Q) =false for all L, Q.

Axiomatic Semantics and Correctness Proofs

Axiomatic semantics were developed as a tool for proving the correctness of programs.

Let us examine an example:

{x=X and y=Y}

Swapxy

{x=Y and y=X}

We assert that the program:

{x=X and y =Y}

t :=x ;

x :=Y :

Y := t ;

{x=Y and y=X}

Is correct

To prove this we must compute wp(C, Q) and then show that P -wp (C, Q)

wp(t:=x; x:=y; y:=t, x=Y and y=X)

= wp(t:=x, wp[(x: =y; y:=t, X =Y and Y=X))

= wp(t:=X, wp(x:=y, wp(y:=t, x=Y and y =X)))

= wp(t:=x, wp(x:=y, wp(y;=t, x=Y)

and wp (y:=t, y=X)))

= wp(t;=x, wp(x:=y, wp(y;=t, x=Y))

and wp(x:=y, wp(y:=t, y=X)))

= wp(t:=x, wp(x;=y, wp(y:=t, x=Y)))

57
and wp(t:=x, wp(x:=y, wp(y:=t, y=X)))

Now:

wp(t:=x, wp(x:=y, wp(y:=t, x=Y))) =

wp(t:=x, wp(x:=y, x=Y)) =

wp(t;=x, y=Y) =

{y=Y}

and

wp(t:=x, wp(x:=y, wp(y:+t, y=X))) =

wp(t:=x, wp (x:y, t = X)) =

wp (t: =x, t=X) =

{x=X}

wp(t:=x; x:=y; y: =t, x=Y and y = X)

= wp(t:=x, wp(x:=y, wp(y:=t, x=Y)))

and wp(t:=x, wp(x:=y, wp(y:=t, Y=X)))

={y=Y} and {x=X}

In this case P = wp(C, Q) so the proof of P-wp(C, Q) is trivial.

As can be seen from even this simple example, formal proof of program correctness is
extremely complex.

The prospect of proving the correctness of 100,000 – line program, even a particularly carefully
written one, terrifying.

We can conclude that only trivial programs are provably correct.

Does this imply that all correct programs are trivial?

58

You might also like