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

Algorithms cp

The document is a course support material for 'Algorithmiques et Structures de Données 2' at the University of Adrar, focusing on programming languages and their paradigms. It covers various programming languages, their definitions, characteristics, and the evolution of programming from low-level to high-level languages, including pseudo-codes and interpreters. Additionally, it provides examples of encoding operations and flow control in programming, emphasizing the importance of choosing the right language for efficient programming.

Uploaded by

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

Algorithms cp

The document is a course support material for 'Algorithmiques et Structures de Données 2' at the University of Adrar, focusing on programming languages and their paradigms. It covers various programming languages, their definitions, characteristics, and the evolution of programming from low-level to high-level languages, including pseudo-codes and interpreters. Additionally, it provides examples of encoding operations and flow control in programming, emphasizing the importance of choosing the right language for efficient programming.

Uploaded by

Ilham Imene
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 100

Dr.

Omari Mohammed
Maître de Conférences Classe A
Université d’Adrar
Courriel : omarinmt@gmail.com

SUPPORT DE COURS

Matière : Algorithmiques et Structures de Données 2


Niveau : 2ème Année Licence en Informatique
Algorithmiques et Structures de Données 2 :

Principles of Programming Languages


Program:
Section 1: Imperative Languages.
Chapter 1: Pseudo-codes and Interpreters
Chapter 2: 1st Generation: Efficient Languages (Fortran)
Chapter 3: 2nd Generation: Powerful Languages (Algol-60)
Chapter 4: 3rd Generation: Simple and Secure Languages (Pascal/Modula2)
Chapter 5: 4th Generation: Module-Based Languages (Ada)
Section 2: Function-Oriented Languages (LISP)

Textbook: Principles of Programming Languages, Bruce J. Maclennan, Third Edition, 1999.

-1-
The Programming Languages

Definition: A programming language is a language intended for the description of programs. Often a
program is expressed in a programming language so that it can be executed on a computer:

Read (a, b) 0011010001010100


0101010010101000
ax+ b = 0 1010101110101010
If a ≠ 0 then x← -b/a
1010100010101110
Write(x) 1010101010101001
1010101010101110

An Idea A Program An Executable Code


(Machine Language)

A programming language is capable of expressing any computer program. However, not all
languages are equally powerful. Therefore, we will study the theoretical concept of some languages, and
concentrate on the practical power of each. Hence, we will be able to choose among languages the best
that fits our needs in an efficient way.

Language Paradigms: Usually, any language has to belong to one paradigm. The latter describes a
group or a concept that a language basically rely on.
There are four language paradigms:
- Imperative paradigm: Which include languages that are based on the sequential execution of
instructions; e.g., Fortran, Pascal, C, …
- Functional paradigm: In this group, the function is considered as a “first-class-citizen”. All
applications are interpreted into function calls. This paradigm is characterized by the un-necessity
of memory storage. The most common function-oriented language is LISP.
- Object-Oriented Paradigm: Even though the programs are still imperative, object-oriented
languages focus on the classification of data and programs into objects that interact with each
other by sending messages. Languages of this paradigm are widely used nowadays such as Java
and C++.
- Logic-Oriented Paradigm: This paradigm takes a step towards the “non-procedural” aspect. It is
based on “what-to-do” more than “how-to-do”. Prolog is considered as the king of logic-oriented
languages.

-2-
Pseudo-Codes and Interpreters
A language is a tool to implement our ideas on a machine. However, there are
many layers that separate our code from the hardware:
1- Micro Level: where instructions are given to open/close electronic gates.

The INUT Bus


in in in in in in

R1 R2 R3 IR AR ACC

out out out out out out


The OUTPUT Bus

So, in order to construct a program, we should provide a sequence of micro-


code lines as follows:

R1in R1out R2in R2out R3in R3out IRin IRout ARin ARout ACCin ACCout

0 0 0 0 0 0 0 1 1 0 0 0

Example: The code 000000011000 is a translation of “Store IR into AR”

2- Macro Level: or the Assembly level. It was invented as a pseudo code which
provides a set of basic instructions that are easily translated to a machine code (micro-
code).

ASSEMBLER
Assembly Micro-Code
Translating

Example:
- MOV R1, R2 (Store R1 into R2)
- ADD R1, R2 (Add R1 to R2 and store the result in R2)

3- H.L.L Level: High level languages provide more flexible features for
programmers. Simplicity, portability, maintainability and readability are easier
implemented in H.L.L.

COMPILER
H.L.L Assembly
Translating

Example: sqrtdelta := sqrt(b*b – 4*a*c)

-3-
4- V.H.L.L Level: Very H.L.Ls are designed as packages of routines to be used
directly by programmers (Java, C++, Simila, …)

PRE-PROCESSOR
V.H.L.L H.L.L
Translating

Example: a C++ program is pre-processed to generate C code.

H/W
Micro-Coding
Assembly-Coding
More Speed More : Readability,
Portability,
HLL Programs Maintainability, …

VHLL Programs

I- Characteristics of Programming with Early Computers:


- Difficult (micro-coding).
- Little storage (few thousands words was considered a large memory!).
- Computers were very slow; thus, programs should be programmed efficiently.
- Floating point arithmetic and indexing were simulated only: Most scientific
programmers that needed floating point operations were facing the obstacle of
using the built-in integer facilities or operations. Therefore, the programmer
had to care a lot about simulating floating point arithmetic. Also, variable
indexing in an array was a big lack in early computers that needed to be
simulated.

II- Invention of Pseudo-Code Interpreters:


Since floating point and indexing operations were not built-in operations in
early computers, programmers generated routines that handled these operations.
Hence, there was a need to write a “higher level” codes (on virtual machines)
avoiding repetition of these routines.

-4-
Virtual Machine Real Machine

Virtual Instructions: Real Instructions:


- Floating point - Integer
arithmetic. Simulation arithmetic.
- Indexing. - H/W
- Labeling. Interruptions.
- Input-Output

III- Designing a Pseudo-Code Language:


Early computers used punched cards in order to load programs/data into a
machine. These cards will be read by a card reader: the program and the data are
loaded into the memory:

Punched Card
holes

A line of program code


or data.

Coding Example: Operation Operand1 Operand2 Operand3 Operand4


code code code code code

INTERPRETER

Initial data values Card loading


Memory
program instructions Reader
input data values

In order to better understand a pseudo code and be able to interpret it, we will
choose to encode using digits (0 to 9) instead of bits ( 0 and 1).
Let’s imagine a machine with a memory of 1000 words. Each word consists of
11 digits. This allows us to encode pseudo instructions of the following format:
Op Op1 Op2 Dest
Where:
-5-
Op: 2 digits encoding an operation (±0 to ±9).
Op1: 3 digits encoding the address of operand 1 (000 to 999).
Op2: 3 digits encoding the address of operand 2 (000 to 999).
Dest: 3 digits encoding the address of the destination (000 to 999).

Memory (Data Section)


000 + 0 0 0 0 0 0 0 0 0 0 a word
001 + 0 0 0 0 0 0 0 1 0 0 the integer +100
-
002 + + 0 0 0 2 0 0 0 0 0 the float 1 x 102
003 0 0 0 0 .0 0 0 0 0 0 1

.
.
.

999 + 0 0 0 0 0 0 0 0 0 0

- +, − are encode 0, 1 respectively.


- An integer occupies 11 digits: 1 for the sign, 10 for the absolute value
(−9999999999 to +9999999999).
- A floating point number occupies 22 digits: 1 for the sign, 5 for the exponent
(e) (including its sign), and 16 for the mantissa (m) = ±m.10±e
(−9999999999999999.10+9999 to +9999999999999999.10+9999 with the
possibility of storing small numbers as 1.10-9999).

III-1 Encoding Arithmetic Operations:


Since most of the arithmetic operations come in pairs, (+ and −, × and ÷), we
can use the sign to distinguish these pairs:

+ (0) −(1)
+ −
1
(addition) (subtraction)
2 × ÷
(multiplication) (division)
3 square square root

-6-
Memory (Program Section)
000 + 1 3 2 0 4 5 6 6 5 7 an instruction
001 - 2 5 0 4 3 7 4 7 8 0 Divide the value stored at
location ‘504’ on the value
002 + 3 6 8 7 1 0 0 2 0 0 stored at location ‘374’ and
store the result at location
.
‘780’.
.
.
.

999 + 0 0 0 0 0 0 0 0 0 0

Here is the complete table of our pseudo code which takes care of flow-control
and input output operations:

+ −

0 Move ?

1 + −

2 × ÷

3 Square Square root

4 = ≠

5 ≥ <

6 X[Y]→Z X→Y[Z]

Increment and
7 ?
test

8 Read Print

9 Stop ?

-7-
III-2 Encoding The MOVE operation:
This operation the simplest one where we need to move a content of one
location to another:
+0 150 000 280
effectively moves the content of location 150 to location 280. As you can notice, the
move operation has two parameters only: source and destination. Therefore, the
second operand is unused (000).

III-3 Encoding Flow-Control Operations:


Flow control operations are different from arithmetic operations in terms of
which instruction will be executed next. Once an arithmetic operation is executed,
automatically we move to the next instruction. However, with flow control operations,
the result of comparison for instance decides the address of the next instruction.
Example:
+4 200 201 035
means that if the content of location 200 is equal to that of 201 then branch to location
035 (change the flow of execution); otherwise continue (don’t change the flow of
execution).

Memory (Program Section)


000 + 1 3 2 0 4 5 6 6 5 7 [320] + [456] → [657]
001 + 4 5 0 4 3 7 4 0 0 3 If [504] = [374] then branch to [003]
002 + 3 6 8 7 0 0 0 2 0 0 else [687]^2 → [200]
003 - 3 6 8 .7 0 0 0 2 0 0 sqrt([687]) → [200]
.
.
.

999 + 9 0 0 0 0 0 0 0 0 0

-5 200 201 035


means that if the content of location 200 is less than that of 201 then branch to
location 035; otherwise continue. So, in order to upgrade our pseudo code to be able
to test negative and positive values, we need to store the zero value in a well-known
location, let’s say 000. Therefore:

-8-
-5 200 000 035 means that if the content of location 200 is negative then branch
to execute operation in location 035.
Here is an example of a pseudo code that computes the absolute value of the
content of location 270 and stores the result in 305:
Program Section
(loc 000) +5 270 000 002 (if [270] >= 0 goto 002)
(loc 001) -1 000 270 270 (else -[270] → [270])
(loc 002) +0 270 000 305 ([270] → [305])

III-4 Encoding Loops and Indexing Operations:


One of the justifications for our pseudo code is to provide indexing facilities.
So, all we need is to provide the address of the first element in the table and the
address of the index:
+6 xxx iii zzz means to move the content of x[i] into location z, and
-6 xxx yyy iii means to move the content of location x into y[i].
(xxx, yyy, zzz and iii are addresses of some x, y, z, i values).
In order to encode loop operations, we need also the address of the counter (i)
and the address of the upper bound (n) and the address of the location to jump into.
Therefore,
+7 iii nnn ddd means to increment the index i (at location iii) and branch (or
jump) to operation at location ddd if i is till less than to n (at location nnn).
Here is an example of extracting the minimum of an array of 5 integers:
Data Section
(loc 000) +0 000 000 000 (the zero value)
(loc 001) +0 000 000 000 (the index i)
(loc 002) +0 000 000 005 (the upper bound n)
(loc 003) +0 000 000 000 (the minimum)
(loc 004) +0 000 000 000 (temporary location)
(loc 005) +0 000 000 073 (A[0])
(loc 006) +0 000 000 018 (A[1])
(loc 007) +0 000 001 056 (A[2])
(loc 008) -0 007 060 080 (A[3])
(loc 009) +0 000 120 034 (A[4])

-9-
Program Section
(loc 000) +6 005 000 003 (A[0] → MIN)
(loc 001) +6 005 001 004 (A[i] → TEMP)
(loc 002) +5 004 003 004 (if TEMP >= MIN goto 004)
(loc 003) +0 004 000 003 (else TEMP → MIN)
(loc 004) +7 001 002 001 (inc i; if i < 5 goto 001)

III-5 Input/Output Operations:


A program is not usually useful if it doesn’t read or print some values.
Therefore, we’ll use the ±8 code to encode read/print operations:
+8 000 000 203 means read a value (from a punched card, a keyboard, …) and
store it into memory location 203.
-8 203 000 000 means print the value stored at memory location 203 (on a
punched card, a monitor, …).

III-6 the STOP (HALT) Operation:


In order for the interpreter to know when it should no further carry on the
execution of a program, we need to add encoding a stop operation. A +9 is enough to
do so.

IV- Program Structure


Typical programs include initial data values which serve to allocate and
initialize data into memory (variables, arrays, …). Also, they include program
instructions to be executed. If reading instructions are among program instructions
(+8 operations), input data values are also to be provided.
Now, how can we distinguish between initial data values, program
instructions, and input data values? Especially if they are all encoded as words? We
need to use some ‘sign’ to distinguish between them. Therefore, we’ll use a special
card holding the +9 999 999 999 code as a separator.

-10-
V- The Loader
The loader is the program who’s responsible for reading the punched cards
and load initial data values, program instructions into memory. However, input data
are not read into memory until executing a +8 instruction. Therefore, the loader will
load initial data values (variables) into the data section in memory until it reads the
+9999999999 card. Then, it starts reading the program instructions into the program
section, until reading another +9999999999 card. The rest of the input data will be
read datum by datum once a read instruction (+8) is executed.

The Memory

Initial data values

Data Section
+9999999999

Program
program instructions
Section
+8 000 000 305 Before
execution
While
executing
+9999999999

Input data values

-11-
Here is an example of a complete program that computes the minimum of 5
integers:
(loc 000) +0 000 000 000 (the zero value)
(loc 001) +0 000 000 000 (the index i)
(loc 002) +0 000 000 000 (the upper bound n)
(loc 003) +0 000 000 000 (the minimum)
(loc 004) +0 000 000 000 (temporary location)
(loc 005) +0 000 000 000 (A[0])
(loc 006) +0 000 000 000 (A[1])
(loc 007) +0 000 000 000 (A[2])
(loc 008) +0 000 000 000 (A[3])
(loc 009) +0 000 000 000 (A[4])
+9 999 999 999 Separator
(loc 000) +8 000 000 002 (Read n)
(loc 001) +8 000 000 004 (Read TEMP)
(loc 002) -6 004 005 001 (TEMP → A[i])
(loc 003) +7 001 002 001 (inc i; if i < n goto 001)
(loc 004) +0 000 000 001 (0 → i)
(loc 005) +6 005 001 003 (A[0] → MIN)
(loc 007) +6 005 001 004 (A[i] → TEMP)
(loc 008) +5 004 003 010 (if TEMP >= MIN goto 010)
(loc 009) +0 004 000 003 (else TEMP → MIN)
(loc 010) +7 001 002 007 (inc i; if i < 5 goto 007)
(loc 011) -8 003 000 000 (Print MIN)
(loc 012) +9 000 000 000 (stop)
+9 999 999 999 Separator
+0 000 000 005 input data
+0 000 000 073 input data
+0 000 000 018 input data
+0 000 001 056 input data
-0 007 060 080 input data
+0 000 120 034 input data

-12-
VI- The Interpreter
When initial data values and program instructions are loaded into memory, the
interpreter should start an execution cycle. But first, we need to know the data
structures we are using:
/* main data structures*/
char[1000][11] Program;
char[1000][11] Data;
/* additional data structures*/
char[11] Instruction;
char[2] Operation;
int IP, Operand1, Operand2, Destination;
The interpreter starts executing program instructions following the IP value
(Instruction pointer). In the beginning, IP = 0 (or location 000). The execution cycle
of the interpreter looks like the following:
1- Read the next instruction (Instruction = Program[IP]).
2- Increment the IP (IP = IP + 1).
3- Decode the Instruction (Operation = ...; Operand1 = ...).
4- Execute the Instruction (if (Operation == ‘+0’) {...).
5- Go to Step 1.
As we can see, the IP value is incremented automatically at step 2. Yet, it is still
possible to branch to another instruction location rather than the next one if the
current instruction is a flow-control instruction. In this case, at execution step (3), the
IP value is altered based on the result of the comparison:
if (Operation == ‘+4’) { /* if operation is EQUALITY*/
if (strcmp(Data[Operand1], Data[Operand2]) == 0)
IP = Destination;
}
Last thing to say about the interpreter that it should break the execution cycle
when the STOP instruction is read:
if (Operation == ‘+9’) { /* if operation is STOP*/
exit(); /* quit*/
}

-13-
if (Operation == ‘-8’) { /* if operation is Print*/
printf("%s", Data[Operand1]);
}

-14-
1st Generation Languages:
Emphasis on Efficiency
(FORTRAN)
1-Overview:
Interpreters came to execute virtual codes that took care of the lacking
operations of floating point operations and indexing facilities. When IBM developed
its IBM 704 machine with built-in floating point operations, it made current
interpreters look less attractive due to the overhead of simulating such facilities. In
addition, programming with decimal numbers was opposed by many programmers
and the sympathy to use high level language was in the rise.

John Backus IBM 704

John Backus of IBM proposed to write programs in conventional


mathematical notation, and then automatically and efficiently generate a machine
code. FORTRAN (stands for FORmula TRANslation) was first invented in 1954 by
Backus and his team, though many other versions came up in the next years
(FORTRAN II 1957, FORTRAN III 1958, FORTRAN IV 1962, ANS FORTRAN
1966, FORTRAN 77 1977, and FORTRAN 90 1990).

Punch cards:
Even though the IBM 704 used tapes to store data, punch cards were also
widely used as primary inputs for data and programs. Therefore, FORTRAN inherited

-15-
the fixed format of pseudo codes, and programmers used a specific code to write their
FORTRAN programs. The IBM 80-column card had 12 rows (0-9, 11 and 12), and 80
columns divided as follows:
Column Purpose
1-5 Statement Number
6 Continuation
7-72 Fortran Statement
73-80 Identification

So each statement was coded in one card or more. Each character had a
specific punching code. For instance, Z was coded 0-9, which means the 0th and the
9th row is to be punched to encode a Z. 1 was simply coded 1, 2 for 2 and so forth.
The previous figure shows an encoding for the statement Z(1) = Y + W(1), with
the identification of PROJ039. The identification was only used by the programmer; it
was ignored by the FORTRAN compiler/interpreter.
When the statement needed more than 66 characters (7 to 72), the next card
was to be punched at one hole of the 6th column for continuation. Also, if column 1
encodes the character C, the current statement is considered as a comment.

-16-
2- Data Structure:
In the early versions of FORTRAN, only 4 scalar types were used: Integer,
Float, Double, and Complex.
INTEGER I
FLOAT X
DOUBLE D
COMPLEX C
Since FORTRAN was designed to program scientific operations, these types
were first-class citizen objects; hence, adding two complex numbers was simply
handled by a simple plus (+) rather than a special add-function.
FORTRAN aims big at achieving efficiency even in memory usage. Most
computers of that era were word oriented (the basic unit of data representation was a
word). For instance, integer and float variables used 1 word, double and complex used
2 words:

s b14 b13 ...................................... b1 b0 Integer

sm se c3 c2 c1 m10 m9 ……… m1 m0 float

The only data structure that FORTRAN used was the array of 1, 2, and 3
dimensions:
DIMENSION A(10), MATRIX(10, 10), SPACE(10, 10, 10)
The unit used in DIMENSION declaration is word only. Therefore, arrays
could be used for integers or floats only.

-17-
3- Program Structure:
FORTRAN was first to introduce subprogram facilities. The overall
programming structure looked as follows:

Main Program

Subprogram 1
.
.
.
Subprogram n

Here is a FORTRAN I program (with no subprogram) that computes the


average of 100 absolute integer values:
C (* MAIN PROGRAM *)
DIMENSION A(900)
10 FORMAT(I3)
20 FORMAT(F10.6)
30 FORMAT(6H AVG =, F10.6)
SUM = 0.0
READ 10, N
DO 60 I = 1, N
READ 20, A(I)
IF (A(I)) 40, 50, 50
40 A(I) = -A(I)
50 SUM = SUM + A(I)
60 CONTINUE
C COERCION from Integer to Float
AVG = SUM / FLOAT(N)
PRINT 30, AVG
STOP

The previous program should a specific feature of FORTRAN: Implicit


declaration. When a variable is not declared in the declaration section, the FORTRAN
compiler declares it implicitly (automatically) as follows:
- If it begins with i, j, k, l, m, and n then it is considered as an integer (i, joul, …).
- Otherwise, it is considered as a float (x, cal, …).

-18-
3-1 Declarative and Imperative Constructs:
A FORTRAN program is divided into two sections:
- Declarative constructs, which describes variables, their length, and their initial
values. This section is non executable; it is used by the compiler to allocate and set
memory location only (INTEGER I).
- Imperative constructs (instructions), which contained the commands to be executed
by the CPU, which make it an executable section (I = I + 1).

3-2 Name Binding and Data Initialization:


One impact of FORTRAN’s efficiency is the static name binding feature.
When a variable is declared, the compiler binds the name of that variable to a specific
(logical) address, once for all:

Memory
000
001 X
002
003

.
.
.

FORTRAN also affords an efficient way to initialize data using the DATA
construct. For instance,
DIMENSION A(900)
DATA A / 900*0.0
Initializes, in one shot, 900 location floats, which is more efficient than initializing
them using a loop:
DO 10 I = 1, 900
A(I) = 0.0
10 CONTINUE

-19-
3-3 Conditions, Conditional and Unconditional Branches:
FORTRAN was originally designed as a programming language for the IBM
704 computer; Backus never thought that it would be used for other machines.
Therefore, some FORTRAN’s instructions were designed similarly as the assembly
language of the IBM 704.

FORTRAN Instruction IBM 704 Assembly Instruction

GOTO 100 TRA 100 (direct transfer)

IF ACCUMULATOR OVERFLOW n, m TOV n (transfer on


accumulator overflow)

STOP HLT

This made FOTRAN disregard a very important programming language principle:

The Portability Principle:


Avoid features of facilities that are dependent on a particular computer or a
small class of computers.

Conditions were also inspired from the assembly language comparisons:


c Equality
IF (A .EQ. B) X = X + 1
c Less than
IF (A .LT. B) X = X + 1
c Less than or equal
IF (A .LE. B) X = X + 1
c Greater than
IF (A .GT. B) X = X + 1
c Greater than or equal
IF (A .GE. B) X = X + 1
c Logical operations
IF ((A .EQ. B .AND. C .GT. D) .OR. .NOT. (D .LE. E))
X = X + 1

-20-
3-4 GOTO: The Workhorse of Control Flow:
The GOTO statement is a fundamental control structure in FORTRAN.
However, its use caused many implications in terms of readability, consistency and
structure. Nevertheless, we can not escape from the fact that we need a tool to control
the execution flow. Since FORTRAN emphasized on efficiency, the use of the weakly
structured GOTO generated a big push toward efficient coding.

Here is an example that handles the alternative:


c Absolute Value of N
IF (N .GE. 0) GOTO 10
M = -N
GOTO 20
10 M = N
20 PRINT 30, M
30 FORMAT(7H ABS = , I5)
STOP

Here is a second example that handles the switch case:


c Read either one, two, or three values
GOTO (10, 20, 30), I
c IF I = 1
10 READ 40, X
GOTO 70
c IF I = 2
20 READ 50, X, Y
GOTO 70
c IF I = 3
30 READ 60, X, Y, Z
GOTO 70
40 FORMAT(F10.5)
50 FORMAT(F10.5, 1H , F10.5)
60 FORMAT(F10.5, 1H , F10.5, 1H , F10.5)
70 STOP

-21-
Here is a third example that handles loops:
c Read 100 elements
DIMENSION A(100)
I = 1
10 READ 20, A(I)
I = I + 1
IF (I .LE. 100) GOTO 10
20 FORMAT(F10.5)
70 STOP

So, it is clearly seen that FORTRAN has one static structure (the GOTO) with
is related to three dynamic structures: the alternative, the case-switch, and the loop. In
other words, it is difficult to correlate the meaning of the program execution (dynamic
behavior) with the program code (static behavior). Therefore, FORTRAN disregarded
another principle of programming languages:

The Structure Principle:


The static structure of a program should correspond in simple way to the
dynamic structure of the corresponding computations.

3-5 Assigned GOTO vs. Computed GOTO:


One of the major pitfalls of FORTRAN was the design of two similar
constructs of control flow:
The computed GOTO construct:
GOTO (10, 20, 50), I
which means that we should make a jump to 10 if I =1, 20 if I =2, or 50 if I = 3.
However, the assigned GOTO goes as follows:
GOTO I, (10, 20, 50)
which means that we should make a jump to the address stored at I, where the
sequence (10, 20, 50) is the set of addresses that I could take. So, the list (10, 20, 50)
in this case is considered as a comment to be ignored.

-22-
As we can see, the programmer can easily get confused between the two, simply
because they look alike. This is a violation of the Syntactic Consistency Principle:
The Syntactic Consistency Principle:
Things that look similar should be similar and things that look different should
be different.

3-6 The Overloaded Operations:


In Mathematics, arithmetic operations (+, -, * /) are basically defined on three
sets: Integer, real, and complex numbers. However, we know that these operations are
implemented differently from one set to another. Yet, it is still necessary to unify the
use of these operations on these sets. Therefore, the arithmetic operations were
overloaded, and the symbols, ‘+’ for instance, were generic or polymorphic
operations. In addition, the real operation binding ( + to integer addition, or to real
addition, …) is done corresponding to the context:
- Integer + Integer → Integer Addition
- Real + Real → Real Addition
- Double + Double → Double precision Addition
- Complex + Complex → Complex Addition
Somebody might ask: what if we want to add an integer with real number.
Early version of FORTRAN did not permit this. Due to efficiency purposes, it was
necessary to do explicit coercion from one type to another:
X = Y + FLOAT(I)
I = J + IFIX(X)
Most programming languages today perform implicit coercion between
integers and reals:
- X = X + I → X = X + FLOAT(I)
- I = X + I → I = IFIX(X) + I

3-7 The Overworked Types:


FORTRAN used only four major types: Integer, Float, Double and Complex.
However, programmers needed other types such as addresses and strings. Early
versions of FORTRAN overworked the integer type by dealing with addresses and
Hollerith constants (strings) as integers, due to efficiency of course.

-23-
However, this weak typing language made it insecure for use, especially by amateur
programmers. For instance, the square operation that is defined on integers has no
meaning when it comes to strings and addresses. This made FORTRAN violates
another language principle:
The Security Principle:
No program that violates the definition of the language, or its own intended
structure, should escape detection.

3-8 The DO Loop:


We’ve seen that we can use the GOTO and the IF constructs to build a loop,
which is primitive and low level. In other words, it is very similar to the assembly or
the pseudo code loops.
In order to enhance its programs readability, FORTRAN introduced its unique
built-in high-level control structure: the DO loop. This loop is similar to the FOR loop
of PASCAL:
DO 100 I = 1, N I = 1
A(I) = A(I)*2 10 IF (I .GT. N) GOTO 20
100 CONTINUE A(I) = A(I) * 2
I = I + 1
GOTO 10
20 ...

Contrary to the low level loop of GOTO, the DO loop is more abstract: the
programmer provides only the necessary ingredients: the counter, the upper limit, the
body of the loop, and the delimiter. In this way, we ask the programmer WHAT TO
DO (less errors), not HOW TO DO it (more errors) like the GOTO construct. Thus
the DO loop illustrates:
The Impossible-Error Principle:
Making errors impossible to commit is preferable to detecting them after their
commission.

-24-
3-9 Subprograms:
Subprograms visualize a very important feature in any language: Abstraction.
Subprograms save a lot of writing, and make it easier to read and maintain. Here is an
example of a subprogram definition and call:
FLOAT A, B, C
READ 10, A
READ 10, B
CALL MIN(A, B, C)
PRINT 20, C
STOP
10 FORMAT(F10.3)
20 FORMAT(7H MIN = , F10.3)
C Procedure MIN
SUBROUTINE MIN(X, Y, Z)
IF (X .GT. Y) GOTO 30
Z = X
GOTO 40
30 Z = Y
40 RETURN
END
The Abstraction Principle:
Avoid requiring something to be stated more than once; factor out the
recurring pattern.
3-10 Activation Records:
Once a caller (main program for instance) calls a callee (subprogram), the
state of the caller might be saved. The state represents all the parameters, the CPU
registers values …
MAIN PROGRAM MIN SUBPROGRAM
IP=0 ________ IP=0 ________
IP=1 ________ IP=1 ________
IP=2 CALL MIN(A, B, C) IP=2 ________
IP=3 ________ IP=3 ________
.. .. .. IP=4 RETURN

-25-
For that matter, a specific place in memory called activation record is reserved for
each main or subprogram:

Activation Record Main


Temporary Storage :
Register values, …
DL
Parameters :
Par1, Par2, …
Subprogram1
Instruction Pointer : IP
Dynamic Link Pointer
DL
Subprogram2

DL

How is the execution of the caller resumed when the callee is terminated?
Well, we simply assign the saved value of IP (in the caller’s activation record) in the
H/W IP (CPU).
In non-recursive languages as FORTRAN, an activation record is reserved for
each subprogram, whether it is called or not.
3-11 Pass by Reference:
One of the advantages of pass by reference is that it is always efficient; a very good
reason for FORTRAN to embrace it. No matter what is the size of the parameter to be
passed, only its reference or address (few bytes) is transmitted to the callee. The gain
in efficiency can easily be seen when an array is passed as a parameter. However,
pass by reference considers that the parameter to be passed is an input/output
parameter: we read its value in the beginning and store any modification in it
immediately.
So what will happen when we don’t want to update a passed parameter? Simply we
use another copy to pass it. But this solution does not work quiet right with constants.
FORTRAN reserves a memory location for each constant like variables. Once a
constant is passed by reference, it is possible that its value is also modified:
SUROUTINE SWITCH(X, Y)
Z = X
X = Y
Y = Z

-26-
RETURN
END
What happens is we make a call as follows: SWITCH(1, 2)? The compiler
won’t reject it since it’s legal. However, the value of these constants will be altered!!!

The Memory The Memory


1 100 2 100
2 101 1 101

SWITCH(1, 2)

Then the next execution of I = I + 1 results in adding 2 to I, which made


FORTARN’s pass by reference a very big security loophole.
3-12 Shared Areas: The COMMON and EQUIVALENCE constructs:
In early versions of FORTRAN, all variables were local in scope: they were
accessed only by the defining subprograms. However, subprogram names were
global: any subprogram can use other subprogram, yet in a non-recursive manner:

R
S
Main Program
X

Call R(X)
Call S(2)

Subprogram R Subprogram S
N N
X Y
Y
Call S(N) Call R(Y)

Would you guess why variables are local in scope? It’s definitely Efficiency.
But how can subprograms share data then? It’s clear that passing variables as
parameters in subprograms call would do so, but that wasn’t enough.

-27-
FORTRAN introduced another construct for data sharing: the COMMON
construct. For example, a subprogram R declares the following common block:
COMMON /C1/ A(100), B(200)
Where S declares:
COMMON /C1/ C(100), D(200)
Which means that a common block C1 of 300 words is declared to be global,
shared by both subprograms R and S.

R
S
Main Program
X

Call R(X)
Call S(2)

Subprogram R Subprogram S
N N
X Y
Y
COMMON /C1/ A(100), COMMON /C1/ C(100),
B(200) D(200)
CALL S(N) CALL R(Y)

However, it is still legal to use C1 with the following layout:


COMMON /C1/ K, L, C(50), D(248)
since C1’s size is still 300 words. In that case, the integer K is corresponding
to the float C[1] of S, which might lead to a hard-to-detect mistake. This made
FORTRAN violates the security principle.
FORTRAN added another flavor of data-sharing and dynamic storage
management by adding the EQUIVALENCE construct. If two variables are
independent in use, we might manage them share the same area. This construct is
basically used to save memory inside the same subprogram.
Let’s recall the program to solve a quadratic equation. We can notice that the
coefficient C is not needed once the determinant is calculated, which give us a hint to
reuse it as an equivalent to another variable:

-28-
FLOAT A, B, C, DELTA, X
EQUIVALENCE C, X
C Section where C is needed, but not X
DELTA = B**2 – 4*A*C
C Section where X is needed, but not C
IF (DELTA .EQ. 0) X = -B/A

In short, C and X are using the same memory place; or the same memory area
have two aliases or two names to access with: the alias C and the alias X.
However, the EQUIVALENCE structure is outlived: its basic need was for
saving memory, which is not a very big concern nowadays. In addition, it still not
secure to use it in large programs, since the user (only source of error!) has to decide
whether one variable is not to be used after the second one is operational.

4. Syntactic Structure:
Any language is defined by its lexics, syntax and its semantics. Usually the
syntax is defined by a grammar:
<DO-LOOP> ::= DO <LABEL> <VAR> = <EXPR>, <EXPR> [, <EXPR>]
<LABEL> ::= <DIGIT> | <DIGIT> <LABEL>
<DIGIT> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
The lexical analyzer, or the scanner, verifies that a program symbols or
characters belongs to the language, and then generates the useful words or tokens of
the program. The syntactic analyzer, or the parser, verifies that a program respects a
language’s grammar.

4.1 Ignoring Blanks:


FORTRAN adopted the convention of ignoring blanks everywhere, the same as with
comments. Therefore:
IF (X .EQ. Y) GOTO 100
is equivalent to
IF(X.EQ.Y)GOTO100
This explains why logical operators are placed between two dots: .EQ., .AND., … It
explains also why labels are only numbers (not names).

-29-
However, FORTRAN precautions didn’t cover all the loopholes. Let’s see the
following example:
DO 20 I = 1. 100
which remarkably looks like the DO statement:
DO 20 I = 1 , 100
Logically, the compiler should catch this error since it’s not a legal DO statement.
Yet, due the adopted implicit declaration, the statement, after ignoring blanks, is
interpreted as an assignment of 1.100 to the real variable DO20I:
DO20I=1.100
So, this error was not caught because of two major flaws: ignoring blanks and implicit
declaration. Had FORTRAN not adopting one of them, the error would’ve been easily
detected. This made FORTRAN missed another principle:
The Defense in Depth Principle:
Have a series of defenses (or defense lines) so that if an error is not caught by
one, it will probably be caught be another.

4.2 Operation Precedence:


Following the mathematical conventions, FORTRAN adopted the precedence
between arithmetic operators, in case of lacking brackets, in the following order:
- Exponentiation
- Multiplication and division
- Addition and subtraction
So A**2*B/C+D-E is interpreted as (((((A**2)*B)/C)+D)-E)

-30-
PROS:
- First high-level language.
- Algebraic notation was a part of the language.

-Efficiency.
CONS:
- Very static.
- Restricted format.
- Use of confusing GOTO.
- Weak typing.
- No global variables.
- Insecure imposed pass by reference.
- Insecure aliasing with EQUIVALENCE and COMMON constructs.
- NO RECURSION.
- Insecure…
- …

-31-
2nd Generation Languages:
Elegancy and Power
(Algol-60)
1-Overview:
ALGOL (Algorithmic Language) was designed to be a universal language.
This language was meant to be machine-independent; though it should satisfy the
condition that it should mechanically (or automatically) be transferable to machine
programs.

Algol
Program
(Independant)

Algol
Compiler
(dependant)

Machine
Assembly
(dependant)

ALGOL-60 was designed by a group of scientists from many associations like


ACM. John Backus with Naur had developed a new specification method to define a
language: the BNF form.
<DIGIT> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<UNSIGNED INTEGER> ::= <DIGIT>+
<SIGN> ::= + | -
<INTEGER> ::= <SIGN><UNSIGNED INTEGER>

-32-
2-Data and Name Structure:

2.1- Primitive Types


Algol-60 used mathematical scalar as primitive types: integer, real, and
Boolean. There was no double-precision and complex numbers only to avoid machine
dependency since the latter types requires double words. Algol-60 went farther
towards machine dependency by not specifying input/output operations in the
language. Instead, Algol-60 defines variables of second-class-citizen types such as
string, and passes them to library input/output procedures that are written in other
machine dependant languages, like in assembly.

2- The 0-1-∞
∞ Principle:
Recall that early versions of FORTRAN allowed 3 dimensions only for arrays.
Also, identifiers were limited to 6 characters, and at most 19 continuation cards were
allowed. As you can see, the programmer has always to remember these numbers
which add another burden besides programming.
On the contrary, Algol-60 prefers to avoid imposing any numbers in the
language, which made Algol-60 the first embracer of:
The Zero-One-Infinity Principle:
The only reasonable numbers in a programming language design are zero, one,
and infinity.

3- Generalized Arrays:
Following the 0-1-∞ principle, Algol-60 made it easier to define arrays of any
number of dimensions. Moreover, indices were of free bounds:
Integer array angle [-180: 180]
Algol-60 adopted another approach in declaring arrays: dynamic arrays. Since
it is not unusual to ignore the size of an array at compile-time, it is possible to decide
it at run-time. Here is an example of computing the average of N absolute values:

-33-
begin
integer N;

Read Int (N);


begin
real array Data[1:N];
real sum, avg;
integer i;

sum := 0;
for i := 1 step 1 until N do
begin
real val;

Read Real (val);


Data[i] := if val < 0 then -val else val;
end;
for i := 1 step 1 until N do
sum := sum + Data[i];
avg := sum / N;
Print Real (avg);
end;
end.

As we can see, the previous program is structured into blocks: the variables of
each one are reserved when its execution is reached. Therefore, unlike FORTRAN,
memory reservation of variables is dynamic in Algol-60.
Dynamic memory reservation leads automatically to dynamic name-binding: a
variable name is not bound to a memory location until the creation of its defining
environment at run-time.

-34-
3- The Block:
Algol-60 adopted another powerful feature: nested environment. For that
matter, it allowed users to use blocks which are similar to compound statement, yet
only blocks that can have declaration section:
A Compound Statement A Block
Begin Begin
statements; declarations;
end; statements;
end;
A compound statement is used to group a set of instructions inside the usual
control structures: if-then-else statement, switch-statement and for-loop.
An Algol-60 programmer can use blocks to define environment of use of some
variables: if a variable is needed only in small section of the program, we might
explicitly define it inside a block that encounter the using section.

N Block A

Data Block B
sum
avg
i
val Block C

Data[i] := if val < 0 then


-val else val;

A Countour Diagram of an Algol Program

4- The Scope of a Name:


Blocks help us defining a scope for every name (variable, procedure) in the
program. When a variable is declared within a block, its static scope is basically
defined as the blocks where it can be seen or used: its defining block and the blocks
nested within it (children).

-35-
For instance, the scope of the variable val is only the block C, which means
that val is local to the block C. However, the scope of variable N is all three blocks
A, B, and C, which means that N is global: can be used everywhere in the program.

5- The Environment of a Construct:


Even though the static scope of a variable defines exactly where it can be
used, compilers use another concept to interpret variables that are used in constructs.
The environment of a construct is defined basically as its utilizing block and the set of
its parent blocks.
So when a compiler is done with syntactic analysis of a construct, it passes to
analyze its semantics by checking whether the used variables are within the
environment of that construct. For instance, Data is used inside a construct within
block C. So, first the compiler checks for Data in block C, then in its parent B.
The compiler (of statically scoped languages) can easily check the
environment of any construct using the chain of static links in the activation record of
each block or procedure.

BLOCK A
N BLOCK B
Data
sum
avg
i
SL=NULL
DL

SL BLOCK C
DL val

SL
DL

-36-
5- Static Scoping vs. Dynamic Scoping:
In all languages, the interpretation of a statement or an expression is
determined by the context of that statement or expression. The context or the
environment is decided based on the rules of scopes adopted by the language.
In statically scoped languages, the scope of names is determined statically by
the structure of the program; which means that the interpretation is fixed. On the
contrary, in dynamically scoped languages, the scope of names is determined
dynamically at run-time.
Let’s investigate both static and dynamic scoping of the following program:
Begin
real a, b, c;
.
procedure Add; . Add’s
. Env.
begin SL
c := a + b; DL
end; a 1
procedure P; b 2
integer a, b, c; c 0 P’s
. Env.
begin .
.
a := 1; SL
b := 2; DL
a 3.0
c := 0;
b 4.0
Add;
c 0.0
end;
Add <ipAdd, epAdd>
a := 3.0; Main’s
P <ipP, epP>
. Env.
b := 4.0; .
c := 0.0; .
SL
P;
DL
Add;
end. Stack of Activation Records

So which variables will be used inside the Add procedure? In other words,
which c will be used in that construct, is it that of the main program, or that of the
P procedure?

-37-
In order to answer such a question, we need to know whether the language is
statically or dynamically scoped.
If the language is statically scoped, then the interpretation of variable names is
done statically, following the static link chain: we search first in the local
environment, then in its definer’s, and so on. Since the variables a, b, and c are not
declared in the Add procedure, we should check in its definer’s environment, i.e., the
main program. Therefore, c (of the main program) receives the value of 7.
On the other hand, when the language is dynamically scoped, the
interpretation of variable names is done dynamically, following the dynamic link
chain: we search first in the local environment, then in its caller’s, and so on. Since
the variables a, b, and c are not declared in the Add procedure, we should check in
its caller’s environment, i.e., the P procedure. Therefore, c (of the P procedure)
receives the value of 3.
You might have noticed that even the defined procedures have some
information in the stack of activation records: the closure <ip, ep>. The closure
serves as the value of a procedure like the variable’s value. The ip is the instruction
pointer that point on the address of the first instruction of the procedure. The ep is the
environment pointer that point on the activation record of the definer of the procedure.

Pros Cons
-Static type checking. -Less powerful.
Static -Static behavior reflects dynamic
Scoping behavior (The structure principle).
-Dynamic type checking: less
Dynamic -Power of generecity = Power. efficient, insecure.
Scoping -Static behavior is different than
dynamic behavior.

-38-
3-Control and Syntactic Structures:
3-1 The Alternative Construct : IF
Algol-60 enhanced FORTRAN’s IF structure by adding an alternative ELSE:
if condition then statement else statement;
this construct let the programmer able to avoid the use of GOTOs and labels,
and make the programming more structured.
However, the syntactic convention of Algol did have some problems. Consider
the following example:
if x = 1 then if y = 1 then z := 0 else z := 1;
it is still ambiguous whether the else corresponds to the first or the second if. This
problem is called the dangling else problem. Algol decides to reject the previous
statement and make it illegal; it imposes the use of a compound statement to alleviate
the ambiguity. Therefore, if the else can be associated with the first if as follows:
if x = 1 then begin if y = 1 then z := 0; end else z :=1;
or the second if as follows:
if x = 1 then begin if y = 1 then z := 0 else z :=1; end;
Other languages solved the dangling else problem by associating else with
nearest if.
Another version of IF was invented by Algol: the conditional expression:
absx := if x < 0 then –x else x;
3-2 The Loop Construct : FOR
The FOR construct was very powerful: the loop’s control variable gets its values from
the for-list-elements. These elements can be specified as follows:
1- explicit: e.g., 1.
2- expression: e.g., if x =1 then 2 else -2, i + j*k.
3- step-until format: 1 step 2 until 9.
4- while format: I + 1 while I < 10
For example, the for loop:
for i := 3, 7,
11 step 1 until 16,
i/2 while i>= 1,
2 step I until 32
do print (i)

-39-
will print the sequence: 3, 7, 11, 12, 13, 14, 15, 16, 8, 4, 2, 1, 2, 4, 8, 16, 32.
So basically, the for loop is no more a definite loop where a control variable is
ranging between a minimum and a maximum bound. Then, the for-list-elements are
reevaluated after each iteration (e.g., i/2) to check whether the loop condition is still
satisfied. Hence, when the programmer writes a simple for loop (for i := 1
step 1 until 10 do…), the reevaluation is still performed after each iteration
though it is not needed, which wastes time. Therefore Algol violated an important
language principle:
The Localized Cost Principle:
Users should pay only for what they use; avoid distributed costs.

3-3 Procedures:
Algol used the term procedure for both as what we know now as procedures
and functions; an untyped procedure is a subroutine that doesn’t return a value; a
typed procedure is a function. Also, the procedure is divided into a declaration section
a body:
procedure abs(x, absx) real procedure abs(x)
real x, absx; real x;
value x; value x;
begin begin
if x < 0 then if x < 0 then
absx := –x abs := –x
else else
absx := x; abs := x;
end; end;
Unlike Pascal, Algol didn’t impose the use of a compound statement for the
body of a procedure:
procedure abs(x, absx)
real x, absx;
value x;
absx := if x < 0 then –x else x;

Parameters are by default passed by name; otherwise, the keyword value


should be used to specify passing it by value.

-40-
As we’ve mentioned above, a procedure’s activation record is created only
once the procedure is called, and due this dynamic feature, the activation record is to
be removed once the procedure is terminated:

B B B

A A A A A A A

Main Main Main Main Main Main Main Main Main

3-4 Nesting:
The nesting feature adopted by Algol made it possible to declare one
procedure inside another or one block inside another. It enhanced also the structure of
usual constructs as the IF and the loop constructs:
FORTRAN ALGOL-60
IF (.NOT. condition) GOTO 100 if condition then
statement 1 begin
. statement 1;
. .
statement m .
GOTO 200 statement m;
100 statement 1 end
. else
. begin
statement n statement 1;
200 ... .
.
statement n;
end;

-41-
It is clearly seen that there are two blocks nested within the then-body and the
else-body. Hence, Now you can easily imagine a more complicated structure: a block
of IF nested within a block of FOR which is nested within another block of IF, …
FORTRAN ALGOL-60
if condition1 then
for i := 1 step 1 until 10 do

?
if condition2 then
begin
statement 1;
.
.
good luck! statement n;
end
else
begin
statement 1;
.
.
statement n;
end;

Therefore, Algol clearly adopted the structure principle.


3-5 Recursion:
Algol permits the procedures to be defined recursively. Moreover, procedures
can also call each other recursively, which was not allowed in FORTRAN: A calls B
that calls C that calls A.
Let’s look at the example of computing the factorial of an integer:
n × (n − 1)! if n > 0

 1 if n = 0

The corresponding function in Algol looks like:


integer procedure fac(n);
value n; integer n;
fac := if n = 0 then 1 else n * fac(n-1);

-42-
3-6 Pass by Name:
Algol-60 adopted two mechanisms for parameter passing: by value and by
name. Pass by value is safe; the passed variable is not altered. However, it is too
expensive when large data structures are to be passed (e.g., arrays).
Unlike FORTRAN, Algol escaped using pass by reference and adopted
another mechanism that is more powerful: pass by name.
In pass by reference, the address of the actual parameter is calculated once and only
once at the beginning of the procedure and bound for good to the formal parameter:

begin
real a;
procedure P(x);
real x;
begin
print(x); (* Pass by Ref: prints 1*)
a := 2;
print(x); (* Pass by Ref: prints 2*)
end;
a := 1;
P(a);
end;

-43-
Now, let’s consider this second program:
begin
real A[1..10];
integer i;
procedure P(x);
x: real; (* Pass by Ref: x is bound to A[1]*)
begin
print(x); (* Pass by Ref: prints 1*)
i := 2;
print(x); (* Pass by Ref: prints 1*)
end;
A[1] := 1;
A[2] := 2;
i := 1;
P(A[i]);
end;
However, with pass by name, the formal parameter is always reevaluated or
reinterpreted whenever the need to use arises:
begin
real A[1..10];
integer i;
procedure P(x);
x: real; (* Pass by name: no binding here*)
begin
print(x);(* Pass by Name: reevaluate x = A[i]
x is bound to A[1], prints 1
i := 2;
print(x);(* Pass by Name: reevaluate x = A[i]
x is bound to A[2], prints 2
end;
A[1] := 1; A[2] := 2;
i := 1;
P(A[i]); (* P(a + b + c + d) *)
end;

-44-
Pass by name is very powerful: it makes procedure calls very generic. Here is
the famous Jensen’s device example:
n
x = ∑Vi
i =1

begin
real x, V[1..N];
integer i;
real procedure Sum(counter, start, finish, element);
value start, finish;
integer counter, start, finish;
real element;
begin
real S;
S := 0;
for counter := start step 1 until finish do
S := S + element;
Sum := S;
end;
x := Sum(i, 1, n, V[i]);
end.

The sum procedure is generic: it can be used to calculate:


n m
x = ∑∑Vij
i =1 j =1

with a simple call : x := Sum(i, 1, n, Sum(j, 1, m, V[i, j]));

-45-
However, pass by name is expensive and dangerous: if the programmer wants
to pass a simple variable by reference, he must pass it by name where the variable is
reevaluated needlessly. Also, the swap operation, which needs pass by reference,
doesn’t work in some cases like SWAP(i, A[i]):

procedure SWAP(x, y);


integer x, y;
begin
integer t;
t := x; (* t = i*)
x := y; (* i = A[i]*)
y := t; (* A[i] = t*)
end;

-46-
4-Pros:
- Structure facilities: Compound statements, blocks, control structures (IF, FOR,
SWITCH, …).
- Nesting environment (blocks, procedures).
- Stack model of computation.
- Power: recursion, pass by name.
- Efficiency: Dynamic Allocation.
- Stronger typing system.
- Free format.
- 0-1-∞ principle.
- Introducing BNF to describe languages.
- Global scoping: data sharing.
4-Cons:
- No Input/Output primitives.
- Limited typing system.
- No data structure capabilities.
- Dangerous pass by name mechanism.
- Expensive constructs: FOR, SWITCH, pass by name.
- Global variables violate information hiding.

-47-
Third Generation Class of Languages :
Return to Simplicity (PASCAL and MODULA2)

1- History and Motivation


Many languages like FORTRAN, Algol and Cobol were successful in their first
application. In fact, the need to combine their features was a necessity. PL/I was a huge
language that included many features as the block structure of Algol, the record and file
handling capabilities of COBOL, and the syntactic style of FORTRAN.
However, PL/I was criticized by many scientist like Djikstra due to the difficulty of
learning all its heterogeneous features. Using PL/I must be like flying a plane with 7000
buttons, switches, and handles. Hence, PL/I was soon rejected due to the fear of not
controlling its syntax.
The experience scientist learned from the first and the second generation languages
and the failure of PL/I led them to think of a language that is competitor to FORTRAN; it
should have clear advantages such as handling non-numeric data but maintaining the same
efficiency at compile and run time of FORTRAN.
PASCAL derived its syntax from Algol due to its easy-to-learn and elegant syntax. Its
main goal was reliability and efficiency that was lacked in Algol. The spread of PASCAL was
aided by the development of compiler that generates pseudo-code designed for PASCAL.
This had encouraged the use of PASCAL on micro computers in the late 1970s and early
1980s.

2- Data Structures
2-1 The Primitive Types:
PASCAL inherited the primitive types of data from Algol: integer, real and Booleans.
It has also extended its primitive types by providing characters data type, char. Strings were
implemented as an array of characters.
2-2 Enumerated types:
Although primitive types were handled in PASCAL, it was necessary to provide a
mechanism to define non-numeric types, such as days, months, sex, ....
Consider the following Algol example that handles days operations as well as gender
operations:

-48-
begin
integer sat, sun, mon, tue, wed, thu, fri;
integer today, tomorrow;
integer male, female;
integer gender;

sat := 0; sun := 1; mon := 2; tue := 3;


wed := 4; thu := 5; fri := 6;
today := tue;
tomorrow := today + 1;

male := 0;
female := 1;
gender := male
end
this example shows the possibility to handle non-numeric data. Yet, these data are not
typed separately; they are handled as integers, and the compiler cannot detect errors that are
related to these types of data:
today := 25;
sun := (mon + tue)/wed ;
gender := tomorrow;
In order to eliminate this lack of security, PASCAL provided enumeration types: types all
constructed via an enumeration or a list of values:
type
DayOfWeek = (Sat, Sun, Mon, Tue, Wed, Thu, Fri);
Sex = (Male, Female);
var
today, tomorrow, yesterday: DayOfWeek;
gender : Sex;
begin
today := Tue;
tomorrow := succ(today); yesterday := pred(today);
gender := Male;
end.

-49-
An abstract data type is defined as the set of values that variables, of that type, can take; and a
set of operations allowed on such type. Enumerated types are abstract data types; hence the
following error:
gender := Tue;
can easily be detected since the value Tue does not belong to the set of values corresponding
to Sex. Moreover, operations like :=, succ, pred, =, >, <, >=, <= are also
allowed and interpreted following the order of the values that appear in the type declaration:
Tue > Sat (returns true)
Tue = Wed (returns false)
succ(Tue) (returns Wed)
succ(Fri) (error at compile-time)
tomorrow := today + 1 (error at compile-time)

Enumerated types are also efficient in memory. In the previous example, gender is a
type of two values. Hence, a bit is enough to represent all values of gender type:
Male Female
0 1
Also, only 3 bits are needed to represent the DayOfWeek type values:
Sat Sun Mon Tue Wed Thu Fri
000 001 010 011 100 101 110
In general, a type of m values needs (┌lg2m┐) bits to represent one of its variables.

2-3 Subranges:
In parallel with the need of non-numeric data, there was also a need to limit an
existing type, or defining a subrange (subtype). For instance, days of the months are integer
values that ranges between 1 and 31. It is clearly possible to declare them as integers, yet the
compiler cannot detect illegal out-of-range assignment such as:
Day := 34;
Subranges allow the programmer to limit the values of an existing type, but they
inherit all the properties of their parent type:

-50-
type
DayOfMonth = 1..31;
var
Day1, Day2, Day3,Day4 : DayOfMonth;
begin
Day1 := 7; (*valid*)
Day2 := succ(Day1); (*valid*)
Day3 := 50; (*not valid*)
Day4 := Day1 * Day2; (*valid*)
end.

Subrange types can also be based on user-defined types:


type
DayOfWeek = (Sun, Mon, Tue, Wed, Thu, Fri, Sat);
WorkingDay = Sun..Thu;
WeekEndDay = Fri..Sat;

Subrange types are also efficient in term of memory use. For instance, the compiler
reserves 5 bits only in order to represent a DayOfMonth variable.

2-4 Sets:
Pascal provides the ability to manipulate sets following sets theory. A set type is
described based on an enumerated type, a subrange, … where the size of the set is finite and
determined:
var
S, T: set of 1..10;
begin
S := [1, 2, 3, 5, 7];
T := [1..6];
if S <= T then write(‘S is included in T’); (* inclusion*)
if 3 in S then write(‘3 belongs to S’); (* belonging*)
S := S * T; (* Intersection*)
T := S + T; (* Union*)
end

-51-
Other set operations are also provided: the difference (-), the equality (=), the non-
equality (<>). However, inexplicably, the pure inclusion (<) and the pure super-set(>) are not
provided.
Sets are also implemented efficiently in Pascal. Each set is assigned a bit string, where
a bit matches an element of the set base type; 0 means that the corresponding element doesn’t
belong to the set; 1 means that it belongs:
S = 1 1 1 0 1 0 1 0 0 0 (*{1, 2, 3, 5, 7}*)
T = 1 1 1 1 1 1 0 0 0 0 (*{1, 2, 3, 4, 5, 6}*)
S∪T = 1 1 1 1 1 1 1 0 0 0 (*{1, 2, 3, 4, 5, 6, 7}*)
S∩T = 1 1 1 0 1 0 0 0 0 0 (*{1, 2, 3, 5}*)
Therefore, a simple bit-by-bit multiplication of the two bit string produces a bit string
corresponding to S∩T. The addition produces a bit string corresponding to S∪T.
In order to test whether an element belongs to a set, a left-shift is performed on the
corresponding string followed by a single test of the current shifted bit:
Does 3 belong to S:
1 1 1 0 1 0 1 0 0 0
← (left shift by 3) 1 0 1 0 1 0 0 0 0 0 0

2-5 Arrays (Homogeneous Data):


Pascal inherited the same multidimensional aspect of array implementation. However,
arrays were static instead. In addition to integer indices, Pascal allows using other types of
indices such as enumerated types, characters, …
var
A: array[1..100] of real;
B: array[1..100, 2..4] of real;
Occurrence: array[‘a’..’z’] of integer;
HoursWorked: array[Sat..Wed] of 0..24;
begin
A[1] := 12;
B[1,2] := 0;
Occurrence[‘y’] := 2;
HoursWorked[Tue] := 12;
end.

-52-
Strong typing is a feature where type matching is forced when checking types of actuals and
formals. Pascal considered that the size and the index (dimensions) are part of an array type.
Let’s investigate the following example:
type
vector = array[1..100] of real;
var
U: vector; (*type : vector*)
V : array[1..75] of real; (*type : t1*)
W : array[0..99] of real; (*type : t2*)
Z : array[1..100] of real; (*type : t3*)
S : real;
function Sum(x : vector) : real;
var
i: integer;
begin
for i := 1 to 100 do
sum := sum + x[i];
end;
begin
S := Sum(U); (*legal*)
S := Sum(V); (*illegal*)
S := Sum(W); (*illegal*)
S := Sum(Z); (*illegal*)
end.

So, whenever the dimensions are altered, new functions and procedures have to be
provided; a violation of the Abstraction Principle.
Pascal alleviated this problem by defining a conformant array schema, allowing
subprograms to pass the dimensions as parameters:

-53-
function Sum(x : array[lwb..upb: integer] of real) : real;
var
i: integer;
begin
for i := lwb to upb do
sum := sum + x[i];
end;

2-6 Records (Heterogeneous Data):


One of the most important data structure provided by Pascal was the record type data
structure. This structure allows heterogeneous data to be grouped together. Unlike arrays,
record elements can be of different types:
type
Date = Record
Day : 1..31;
Month : (Jan, Feb, Mar, Apr, May, Jun, Jul,
Aug, Sept, Oct, Nov, Dec);
Year: 1900..2100;
end;
Person = Record
Name : String;
Age : 16..100;
Salary: 12000..100000;
Sex: (male, female);
BirthDate: Date;
HireDate: Date;
end;
var
P: Person; Student: array[1..10] of Person;
begin
P.Name := ‘Ali’;
P.Sex := male;
Student[2].BirthDate.Day := 25;
end;

-54-
Pascal permits factoring out the prefix used to access a record type using the with-statement:
with P do
begin
x := 1;
Name := ‘Ali’;
Sex := male;
end;

Variant records are also provided using the record constructor. Suppose that we want to build
a structure to hold both teachers and students’ information. There are some information that
are conform only to teachers type like the salary, and some information relevant to students
only like fellowships:
type
Person = Record
Name : String;
Age : 16..100;
Sex: (male, female);
BirthDate: Date;
case kind: (Teacher, Student) of
Teacher:
(Salary: 12000..100000;
HireDate: Date);
Student:
(Fellowship: [180, 240, 270];
RegistrationDate: Date);
end;

Variant records permit the reutilization of space; the same space is used by different type of
structure:

Name Age Sex BirthDate Kind -Salary -HireDate


-Fellowship -RegistrationDate

-55-
2-7 Pointers:
Pointers are used as addresses to access a location or a block of locations). They are used to
implement dynamic structures such as linked lists, trees, graphs,…
Pascal imposed the strong typing approach on pointers by declaring them as typed-pointers:
var
p: ^real;
x: real;
c: char;
begin
new(p); (*create a memory allocation and attach it to p*)
p^ := 3.14159;
c := p^; (* illegal*) (* strong typing*)
x := p^; (* legal*)
end

Pointers can also used to access other types such as records. Access to fields is done as
follows:
var
p: ^person;
begin
new(p);
p^.Name := ‘Ali’;
p^.Age := 25;
end

2-7 Structure-Equivalence vs. Name-Equivalence:


Let’s consider the following example:
var
x: record id: integer; weight : real; end;
(*implicit type t1*)
y: record id: integer; weight : real; end; (*type t2*)
begin
x := y; (* legal or illegal*)
end

-56-
In a language that adopts the structure-equivalence approach, two objects are
equivalent if and only if their structures are equivalent word by word.
In a language that adopts the name-equivalence approach, two objects are equivalent if
and only if their types have the same explicit name. However, an exception is allowed when
one object’s type is a subrange of another:
var
x: integer;
y: 1..16;
begin
x := y; (* legal*)
y := x; (* legal*)
end

Pros Cons
Name Equivalence -Secure
-Efficient and simple
compilation
Structure Equivalence -Useful in dynamic type-
checking

3- Name Structures
3-1 Constants:
PASCAL includes the ability to name constants of discrete types such as integers, characters,
and enumerated types:
Const
Size = 16;
Letter = ‘a’;
PrayerDay = Fri;
Constants play a big role in maintainability: Let’s say that we’ve wrote a program that
manipulates an array of 16 elements:

-57-
var
A: array[1..16] of real;
sum : real;
I : integer;
begin

for i:= 1 to 16 do
sum := sum + A[i];
end

Let’s say that you needed to augment the array size to 20. Then, you’ll need to alter all
the 16s that refers to the size with 20; a job which is proportionally hard.
However, if the size is declared as a constant, only one replacement is needed to shift
from one size to another:
const
SIZE= 16;
var
A: array[1..SIZE] of real;
sum : real;
I : integer;
begin

for i:= 1 to 16 do
sum := sum + A[i];
SIZE := 20; (* illegal*)
end

A language convention states that all constants should be capitalized in order to


distinguish them from variables.

-58-
3-2 Procedures and Functions:
PASCAL implemented sub programs quiet the same as Algol. Yet, Pascal uses explicitly the
keyword function to declare a function:
Function f(x : real): real;
var
i: integer;
G: real;
begin
G := 0;
for i := 1 to 3 do
G := G + pow(x, i);
f := G;
end;

Since Pascal handles recursion too, it adds the keyword forward in order to solve the
declaration problem with one-pass compilers when two procedures (or functions) are calling
each other:
procedure Q(…); forward;
procedure P(…);
begin

Q(…);

end;
procedure Q(…);
begin

P(…);

end;

-59-
3-3 Pass by Value and Pass by Reference:
The Revised Report on Pascal described two parameter passing modes: by value and by
reference. The latter replaced the complicated pass by name mechanism of Algol. By default,
a parameter is passed by value (in only); pass by reference is applied when the keyword var
is used.
procedure P(a: integer; var b: integer);
begin

end;

Pass by reference is useful when the actual parameter is to be altered (out or in-out). Pass by
reference is also efficient since the address of an object is transmitted rather than the object
itself.

3-4 Procedural Parameters:


Pascal allows procedures to be passed as parameters: Another step closer to handling
procedures as first-class citizen:
function F2(x: real): real;
begin
F2 := x * 2;
end;
function F3(x: real): real;
begin
F3 := x * 3;
end;
function PlusOne(function F(t: real): real; x: real): real;
begin
PlusOne := F(x) + 1;
end;
begin
m = PlusOne(F2, 2);
n = PlusOne(F3, 2);
write(n, m); (* n = 7 , m = 5*)
end.

-60-
Using a formal parameter procedure, we should specify its formal parameters and their types
as well in order to aid the compiler do its type checking (strong typing).

4- Control Structures
4-1 Simple Loops: The For Loop:
PASCAL simplified the mysterious Algol’s for loop, and enhance it to look even simpler than
FORTRAN’s:

for <name> := <expression> [to/downto] <expression> do <statement>

The loop can step up or down by 1 only. PASCAL has overreacted to the complexity of the
second generation’s for-loop, since non-unit steps are very common. Nevertheless, Pascal for-
loop is more efficient since the loop counter and its incrementing/decrementing can be
handled easily using micro-processor registers.

4-2 Indefinite-iteration Loops: The While and the Repeat Loops:


PASCAL provides constructs for indefinite iterations where the rhythm of altering the loop
counter, or even the condition of maintaining the loop is not known before executing the loop
itself:
while <condition of repetition> do <statement>;
repeat <statements> until <condition to stop>;

4-3 Case Statement:


PASCAL enhanced the switch statement of Algol by providing a more elegant construct: the
Case statement. This statement uses a variable of ordinary type (integer, character,
enumerated type) to decide the case to execute:
case i of
1: begin … end;
2, 3: begin… end;
4: begin…end;
end

-61-
5- Pascal’s Contributions:
- Simple and Efficient.
- Char, set, enumerated types, subranges.
- User-type definitions.
- Typed Pointers.
- Record Types.
- Secure Pass by Reference.
- Elegant and simpler Case statement.
- Passing procedures and functions as parameters.

-62-
Fourth Generation Class of Languages :
Modularity and Data Abstraction (ADA)

1- History and Motivation:


A software crisis came up in the 70’s due to the difficulty of producing large
programs. It became hard to maintain, trace errors, and enhance these programs due to the
dependability (shared sections) of their functions and procedures. It was also hard to embed
newer procedures with older ones since they were not compiled together.
The DoD (Department of defense) saw the need for a new language that satisfy the
following specifications:
- Allow embedded programs.
- Separation between utilization (user) and implementation (programmer).
- Ability to define abstract data types (values + operations).
- Management of parallel tasks (Concurrency).
- Ability to catch and handle errors at run-time.
Ada was then developed based on the above specifications; it was named in honour of
Augusta Ada (Mathematician).

2- Structural Organization:
Expressions and statements are quiet similar to Pascal and Algol. Yet, Ada enriched the
declaration section with the following classifications:
- Objects
- Types
- Subprograms
- Packages (new data types)
- Tasks (concurrency)

-63-
2-1 Packages:
Packages are modules that are used to hide information and implement abstract data types.
Here is an example of defining a new abstract data type Complex:
package ComplexPackage is
type Complex is private;
i: constant Complex;
function “+” (X, Y: Complex) return Complex;
function “-” (X, Y: Complex) return Complex;
function “*” (X, Y: Complex) return Complex;
function “/” (X: Complex) return Complex;
function Re (X: Complex) return Float;
function Im (X: Complex) return Float;
private
type Complex is
record Re, Im: Float := 0.0; end record;
i: constant Complex := (0.0, 1.0);
end ComplexPackage;

The previous Ada specification code defines a new type using the package facility: It
define a complex type with 4 overloaded operators and two functions. The keyword private
mentions that the section underneath is not accessible by users; it is used only by the compiler
since the latter needs to know the complex variable’s size and the constant’s value(i).
The corresponding Ada body package is to be declared in another module (file) and
compiled separately. The user can use the body’s object code directly with the specification
module. Here is an example of a body package:

package body ComplexPackage is


function “+” (X, Y: Complex) return Complex is
begin
return (X.Re + Y.Re, X.Im + Y.Im);
end;
function “-” (X, Y: Complex) return Complex;
begin
return (X.Re - Y.Re, X.Im - Y.Im);

-64-
end;
function “*” (X, Y: Complex) return Complex;
begin
return (X.Re*Y.Re – X.Im*Y.Im,
X.Re*Y.Im + X.Im*Y.Re);
end;
function Re(X: Complex) return Float;
begin
return X.Re;
end;
-- other definitions
end ComplexPackage;

The package body, known as the implementor, gives the definition of each name
mentioned in the specification, including local procedures, functions, types that are private;
never used by the user.
The concept of using a specification and a body was first introduced by Parnas in his
design principles:
Parnas’s Principles:
1- One must provide the intended user with all the information needed to use the module
correctly and nothing more (specification).
2- One must provide the implementor with all the information needed to complete the
module and nothing more (body).

2-2 Access by Mutual Consent:


Let’s recall that global variables in Pascal are used by every procedure, whether it
needs it or not. Ada enhanced that by introducing the mutual consent mechanism: if a module
A wants to use something in module B, then B has to export it and A has to import it.
Packages can render their objects or procedures accessible by declaring them public.
All items declared in the private section are denied to use. A user gains access to the publics
of the package with a use declaration:

-65-
declare
with ComplexPackage; use ComplexPackage;
X,Y: Complex;
Z: Complex := 1.5 + 2.5*i;
begin
X := 2.5 + 3.5*i;
Y := X + Z;
Z := Re(Z) + Im(X)*i;
X := Z.Re + X.Im*i; -- illegal
if X = Y then X := Y + Z;
else X := Y * Z;
end if;
end;

2-3 Code-Sharing and Data-Sharing


Once a package of an abstract data type is used in Ada, all variables of that type are
sharing the public procedures (code-sharing); Let’s recall that in Pascal it is not possible to
attribute a procedure to a type. Also, any variable that is declared in the package can be
accessed by many users (data-sharing). It is hence equivalent to the COMMON construct of
FORTRAN, yet more secure.

2-4 Generic Packages


Generic packages represents usually a class of regular packages. Suppose that we want to
create a stack abstract data type of variable size. Here is an example of the specification of the
generic package of stack:
generic
Length : Natural := 100;
Package Stack is
Procedure Push(X: in Integer);
Procedure Pop(X: out Integer);
function Empty return Boolean;
function full return Boolean;
Stack_Error: exception;
End Stack;

-66-
The Length parameter is initialized at 100 to mention that the size is 100 by default. Let’s
show some of the body of the stack package:
Package body Stack is
ST: array(1..Length) of Integer;
Procedure Push(X: in Integer);
begin
-- ...
End Stack;
Then, it is easy to instantiate statically different stack packages with different sizes:
package Stack1 is new Stack(70);
package Stack2 is new Stack(30);
package Stack3 is new Stack; -- size 100
Here is another example:
generic
PeriodOfStudy is range 3..5;
Package body StudentType is
Id: Integer;
Name: array(1..30) of Character;
Age: Integer;
Sex: (Male, Female);
YearlyAverage: array(1..PeriodOfStudy) of Float;
GeneralAverage: Float;
-- ...
End StudentType;
Then, it will be easy to instantiate these specific types:
package DEUA is new StudentType(3);
package LicenceLMD is new StudentType(3);
package LicenceAcademique is new StudentType(4);
package INGENIEUR is new StudentType(5);
Afterwards, we can easily declare student variables of the previous types:
A, B, C: DEUA;
D,E: LicenceLMD;
F: LicenceAcademique;
G, H, E, J: INGENIEUR;

-67-
Generic packages with parameters are difficult to compile. At compile time, the
specification and the body of each instantiated package is instantiated too; i.e., each stack
package will have its own data section and also its own code like the procedure Push. This
leads to what we call a code explosion at compile time: few line of code are written, yet a
long code is generated at compile time (no code sharing).

3- Data Structures Organisation:


3-1 Numeric Types
Integers use is similar to Pascal. A range constraint can also be used to limit the range:
A is Integer;
B is Natural; -- positive
C is Integer range -100..100
Ada goes beyond Pascal’s simple provision of real data type; two classes are provided:
floating point reals and fixed point reals:
A is Float; -- portable
B is digits 2; -- not portable
C is digits 10 range -1.0e10..1.0e10; -- not portable
Dollar is delta 0.01 range 0.00..1000000.00;
Ada provides also other types like LONG_FLOAT and SHORT_FLOAT, which reduces the
portability of their use.

3-2 Constants
Ada added the feature of declaring typed constants:
A: constant := 5; -- universal integer type
B: constant Float := 1000.00;
C: constant Integer := f(A); -- function call
LightYear : constant := 5-878-000-000-000;-- use of separators
D: constant := 2#1111-1111# -- binary number
E: constant := 16#FB09# -- hexadecimal number
F: constant := 8#377# -- Octal number

-68-
3-3 Types and Subtypes
Let’s consider the following example:
N: Integer;
type Index is range -100..100;
subtype PositiveIndex is Index range 0..100;
I: Index;
J: PositiveIndex;
Even though ADA embraces name equivalence instead of structure equivalence, yet N, I, J are
of different types, the assignments N := I, and I := J are legal if the range values are respected.
Ada solved this problem by letting the subtype inherit all the operations of its base type. Yet,
a runtime constraint check is performed to verify the range values.
I := N; -- legal; check at runtime
N:= J; -- legal; check at runtime
J := -5; -- legal; check at runtime

3-4 Derived Types


Derived types are used to instantiate new types that are different in meaning than the base
type:
N: Integer;
type Percent is new Integer range 0..100;
P: Percent;
Derived types are like subtypes in terms of inheriting all the operations of the base type. Yet,
for security purposes, you can not assign a derived type variable to a base type variable, and
vice-versa. Instead, Ada permits explicit coercions like Percent(N) and Integer(P) to
convert values.
3-5 Constraints
Subranges in Pascal are replaced by constraints in Ada. There are 4 types of constraints:
- Range constraint: Integer range 1..100
- Accuracy constraint: Float digits 10
- Discriminant constraint: Person(Student) – variant record
- Index Constraint.

-69-
3-5 Index Constraints
Recall that Pascal faced a problem with its strong typing system where the array index was
considered as part of the type. Ada solved this problem with the index constraint feature.
First, an array type with unconstrained index is declared:
type Vector is array(Integer range <>) of Float;
This means that Vector is an array of reals, regardless how many of them. Then, objects of
type Vector are declared by specifying the size only:
A: Vector(1..50);
B: Vector(-100..100);
Now, any procedure that accepts objects of type Vector can receive any object of that type,
regardless its size:
Function sum(V: Vector) return float is
...
for I in V’Range loop
Total := Total + V(I);
end loop

3-5 Arrays
Like Algol-68, Ada adopted the dynamic array facility were the size is not needed at run-time.
type FloatVector is array(1..N) of Float;
At runtime, this declaration is elaborated (not executed); the value of N is to be searched in
order to allocate memory space for the array.
Ada adds nice mechanism to allow contiguous array slices to be assigned and tested for
equality:
A, B: FloatVector;
C: array(1..100) of Float;
D: array(1..100) of Float;
A(1..10) := B(11..20);
If A(30..40) = B(50..60) then...
C(1..10) := D(11..20) -- illegal

-70-
3-6 Hidden Parameters
In Ada, some hidden parameters (type fields) are also used to enhance portability:
DayType is (Sat, Sun, Mon, Tue, Wed, Thu, Fri);
Day: DayType
for Day in DayType’Range loop ...
for Day in DayType’First..DayType’Last loop ...
Natural’First→ 0
Natural’Last→ 65535
Integer’First→ -32768
Integer’Last→ 32767
The next hidden parameters are associated with Integer type:
Integer’Image(65) → ‘A’
Integer’Value(‘A’) → 65

3-7 Access Type


In Ada, pointers are referred as access types:
type Node;
type NodePtr is access Node;
type Node is record
info: integer;
Next: NodePtr := Nil;
end record;

declare
Head: NodePtr := new Node;
Tail: NodePtr := Head;
N1: NodePtr;
begin
for I in 1..10 loop
Tail.Next := new Node;
Tail := Tail.Next;
Tail.Info := I;
end loop;

-71-
N1 := Head; -- Aliasing : N1 and Head are occupying
-- the same memory space
N1.all := Head.all -- a new node N1 is created with all
-- head’s information
end;

4- Control Structures:
Ada provides a richer set of control structures than Pascal’s structures:
- Conditional
- Iterations
- Case statement
- Subprograms
- Goto statement
- Exception Handling (Extra)
- Concurrency (Extra)

4-1 Loops
Ada provides one iteration statement which handles definite, indefinite and infinite iterations
-- indefinite iteration
while A <> B
loop
if A < B then
B := B – A;
else
A := A – B;
end if;
end loop
-- definite iteration
for I in 1..10 -- the loop counter I is defined
loop -- automatically
A(I) := I;

end loop

-72-
-- infinite iteration
I: Integer;
loop
exit when A(I) = 0;
I := I + 1;
end loop
-- infinite iteration
I: Integer;
loop
if A(I) = 0 then
exit;
I := I + 1
end loop

4-2 Exception Handlers


Recall that Ada was intended for embedded applications. Therefore, an embedded
program in a device (military aircraft) should be able to react to exceptional situations;
otherwise, the application is shut down and the device is left uncontrolled; or in the best case,
the device will be controlled incorrectly. However, exceptions are a big violation of the
structure principle since the implementer has no idea about handling his own exceptions.
Let’s observe a push-operation that raise an exception once the stack is full:
Package Stack is
Procedure Push(X: in Integer);
Procedure Pop(X: out Integer);
function Empty return Boolean;
function Full return Boolean;
Stack_Error: exception;
End Stack;

Package body Stack is


ST: array(1..100) of Integer;
Size: 0..100;
Procedure Push(X: in Integer) is
begin

-73-
if Full then raise Stack_Error;
else
begin
Size := Size + 1;
ST[Size] := X;
end;
function Full return Boolean is
begin
return (Size = 100);
end;
end Stack;
Now, suppose that the stack package is used by an application that should handle the situation
where the stack is full? How? Well it depends on the conceptor: He might ignore the error as
well as he might decide to empty the old data since the new one is more important, etc…
Here is a main program that uses the stack package and gets rid of some old data once the
stack is full:
with Stack; use Stack;
declare
Data: Integer;
begin
.
.
.
Push(Data);
.
.
exception
when Stack_Error =>
declare OldData : Integer;
begin
Pop(OldData);
Push(Data);
end;
end;

-74-
4-3 Parameter Passing Modes:
Ada’s parameter passing modes reflect the ways in which the programmer may intend to use
the parameter:
- Input (Read Only).
- Output (Write Only).
- Input-Output (Read-Write).
The in parameters (input) are like constants: never meant to be changed. Therefore, an
assignment to an in parameter generates a compiling error. By default, a parameter is passed
as in mode: procedure P(X: integer) is equivalent to procedure P(X: in
integer).
The out parameters are the opposite: they are meant to be read only. Early versions of Ada
(Ada 83) didn’t allow for reading an out parameter.
The in out parameters are meant to be read and modified inside a procedure.
The programmer specifies only one of these three modes to use. The implementation of
passing a parameter by reference, value or value-result is left to the compiler.

Copying
Compiler’s (Small
Decision parameters)
Reference
(Large
parameters)

In Out In-Out
Programmer’s Decision

-75-
4-4 Position-Independent Parameters
Ada introduced feature of randomly providing actual parameters. Suppose that we have a
procedure that needs 6 parameters:
Procedure Info(Name: array(1..30) of Character; Age: Integer;
Height: Integer; Weight: Integer; ShoeSize: Integer;
WilayaCode: Integer);
An actual call to that procedure might be like:
Info(‘Ali’, 26, 175, 75, 43, 1);
Ada make possible to deliver data arbitrarily:
Info(WilayaCode=> 1, Name=>‘Ali’, ShoeSize=>43, Height=>175,
Age=>26, Weight=>75);
Position-Independent parameters are another illustration of the Labeling Principle:
The Labeling Principle:
Avoid arbitrary sequences more than a few items long; do not require the programmer
to know the absolute position of an item in a list. Instead, associate a meaningful label (name)
with each item, and allow the items to occur in any order.

4-5 Default Parameters


Ada permits the designer of a procedure to specify the default values of the parameters; in that
way, the user needs to provide only the information that is not considered by default.
Let’s consider the previous example. In case that all students are of the same WilayaCode,
then we can specify by default that:
Procedure Info(Name: array(1..30) of Character; Age: Integer
:= 16; Height: Integer := 150; Weight: Integer := 75;
ShoeSize: Integer := 40; WilayaCode: Integer := 1);
Then we might call the info function specifying only 5 parameters (excluding the
WilayaCode):
Info(‘Ali’, 26, 175, , 43,);
Default parameters are another illustration of the Localized-Cost Principle:
The Localized Cost Principle:
Users should pay only for what they use; avoid distributed costs.

-76-
4-6 Concurrency: Non-Communicating Tasks
It is often more efficient and more convenient to do several things at the same time.
Let’s consider an example where a user is editing a file and printing another at once.
Depending on the used architecture, these two tasks can be run in parallel (multiple-
processors machine), or concurrently, i.e., in pseudo-parallel mode (single processor).
Ada provides tasking facilities to implement concurrency. Here is an example of a
procedure that runs two non-communicating tasks:
procedure WordProcessor is
task Edit; end Edit; -- Spec
task body Edit is -- Body
begin
. . . -- edit the selected file
end;

task Print; end Print;


task body Print is
begin
. . . -- print the selected file
end;
begin
-- automatically initiate Edit and Print tasks
-- and wait for their completion
end;
In fact, when the WordProcessor procedure is called, the inner tasks are initiated
or launched automatically; there is no need to explicitly calling them. The procedure
WordProcessor then waits for the completion of both tasks in order to finish and return
back.

-77-
4-7 Concurrency: Communicating Tasks
Ada provides another tasking facility for communicating tasks, such as client/server
applications. The server provides some entry points to be called. This concept is defined as a
rendezvous between the server and the client.
task Server; -- Spec
entry SearchDatabase(X: Student);
entry ModifyDatabase(Y: out Student);
end Server;

The client is allowed to “call” the entry-point specified in the server’s specifications.
However, when the client calls a server’s service, it does not block or wit for the service to be
done; instead, it carries on its task. Therefore, an entry-point call is in fact a message-sending
not a regular procedure call:
task Client; end Client; -- Spec
task body Client is -- Body
begin
. . .
SearchDatabase(Ali); -- send the request to the server
. . . -- then continue the rest of its
. . . -- tasks
end;

-78-
In the body implementation of the server, each entry point is matched with an accept
clause that run the service of that entry-point. The server blocks at the first accept until its
entry point is been called by a user. Usually, accept clauses are controlled by a select
construct in order to choose whichever entry-point has been called:
task body server is -- Body
begin
loop
select
accept SearchDatabase(X: Student) do
. . .
end SearchDatabase;
accept ModifyDatabase(Y: out Student) do
. . .
end ModifyDatabase;
end select;
end loop;
end;

What happens when more than one client are calling an entry-point? A mailbox is
attached to each entry point and the server accepts them one by one in a FIFO order.

Client 1
Client 2

SearchDatabase SearchDatabase

Server
accept SearchDatabase
SearchDatabase

SearchDatabase

Client 3

Client 4

-79-
5- Ada’s Contributions:
- Strong, rich, secure typing system.
- Package facilities.
- Implementation of true ABSTRACT DATA TYPES.
- Modularity and separation between specification and implementation.
- Exception facilities.
- Tasking facilities.
- Secure and efficient parameter passing modes.
- Powerful: dynamic arrays, unconstrained arrays, array slice assignments.
- Genericity (static).

-80-
Fifth Generation Class of Languages :
1- Functional Paradigm: LISP

1- History and Motivation:


LISP (List Processing) interpreter was developed by McCarthy in 1960. Its
first implementation was on the IBM 704. LISP was widely spread to other computers
and used as a programming language for artificial intelligence applications. McCarthy
claims that LISP is the second oldest programming language, after FORTRAN.

2- Structural Organization:
2-1 Primary Data Structure: The List.
LISP was designed to process lists of objects. These objects are either atoms
(elementary) or lists (composite):
(a b c d e f) a list of 6 atoms.
(a (b c) d (e f)) a list of 2 atoms and 2 lists.
(to be or not to be) a list of 6 atoms.
2-2 Program Representations:
In LISP, programs are also represented as lists. A function call can be easily presented
as a list:
(f par1 par2 par3…)
There are 2 famous expressions that represent function calls: The M-expression,
(Meta expression). In this mode, a function is preceded by a list of its parameters
(separated by commas). Also, an M-expression represents a regular arithmetic
operation in an infix notation (or order):
f(g(a, b) , h(c, d)) or (a – b) + (c * d)
LISP uses the S-expression (Symbolic expression) where a prefix order is applied for
both function calls and arithmetic operations:
(f (g a b) (h c d)) or (+ (- a b) (* c d))
In the S-expression, the first element of a program list is always a function;
which makes it easier for the interpreter to evaluate.

-81-
Here are some examples of commands interpretations in a LISP interpreter:
% (+ 2 5)
7
% (+ (- 4 1) 5)
8
% (- 4 7)
-3
% (set ‘x 5)
5
% x
5
% (= x 5)
t
% (> x 10)
nil

3- Data Structures:
2-1 The primitives: Atoms.
Atoms are the primitive type used in Lisp. An atom can be numeric (1, 2.0,
…) or a non-numeric name (a, to, …).
Common Lisp interpreter provides a predicate to test whether an object is an
atom or not:
% (atom 5)
t
% (atom ‘to)
t
% (atom ‘x) -- the character x
t
% (atom x) -- the variable x
Error : x not defined
% (set ‘x 5) -- defining x = 5
5

-82-
% (atom x)
t
% (set ‘x ‘(a b)) -- defining x = (a b)
(a b)
% (atom x)
nil
% (atom (+ 2 3)) -- is 5 an atom ?
t
% (atom ‘(+ 2 3))
nil
% (atom (a b c))
Error
% (atom ‘(a b c))
nil

When a list is quoted, the interpreter considers it as a list of items and never evaluates
it. Hence, Lisp distinguishes between a composite data structure and a program by the
quote: a quoted list is data; a non-quoted list is a program or a function call.

2-2 The non primitives: Lists.


Lisp provides also a data structure which represents composite objects as well as
programs: the list. Common Lisp also provides a predicate to test whether an object is
a list:
% (Listp 2)
nil
% (Listp (2))
Error
% (Listp ‘(2))
t

-83-
2-3 Data Representation.
Lisp used a very simple mechanism to represents lists: a linked-list where each item
has two pointers: The first pointer points on the first element of the list, whereas the
second points on the rest of the list:
(1 2 3)

1 2 3

((a b) c (d e) f)

c f

a b d e

()

Equivalent to

nil

The atom nil is used as a list terminator. For historical reasons, the empty list () is
considered equivalent to the atom nil:
% (eq ‘() nil)
t
% (null ‘())
t
% (null ‘(a b))
nil

-84-
% (atom nil)
t
% (listp ‘())
t
% (atom ‘())
t

3- List Manipulations:
3-1 Selector Operations:
Common Lisp provides two fundamental operations to access list items: car and cdr.
Car returns the first element of the list, no matter whether it is an atom or a list. Cdr
returns the list excluding the first item. Cdr returns always a list:
% (car ‘(a b c d))
a
% (car ‘((a b) c d))
(a b)
% (set ‘x ‘((a b) (c d)))
((a b) (c d))
% (car x)
(a b) -- the first item
% (cdr x)
((c d)) -- a list of the rest of the items
Car and cdr are pure functions: they don’t alter the accessed list. In addition, another
syntactic sugar was invented to simplify list manipulation:
(caaddadr x) is equivalent to
(car (car (cdr (cdr (car (cdr x))))))

Example :
% (set ‘AS ‘((Ali Said) 30 20000 (15 January 1970)))
((Ali Said) 30 20000 (15 January 1970))
% (cadr AS)
30 -- Age

-85-
% (caddadddr AS)
1970 -- Year of Birth

Explanation :
% (cdr ‘((Ali Said) 30 20000 (15 January 1970)))
(30 20000 (15 January 1970))
% (cdr ‘(30 20000 (15 January 1970)))
(20000 (15 January 1970))
% (cdr ‘(20000 (15 January 1970)))
((15 January 1970))
% (car ‘((15 January 1970)))
(15 January 1970)
% (cdr ‘(15 January 1970))
(January 1970)
% (cdr ‘(January 1970))
(1970)
% (car ‘(1970))
1970

3-2 Constructor Operations:


Common Lisp provides also constructing functions in order to build up a list:
A) cons <item> <list> → <list>
It constructs a list by appending an item to another list:
% (cons ‘a ‘())
(a)
% (cons ‘a ‘(b c d))
(a b c d)
% (cons ‘(a) ‘(b c d))
((a) b c d)
% (cons ‘a ‘b)
Error

-86-
B) append <list1> <list2> → <list>
It merges the items of two lists:
% (append ‘(a b) ‘(c d))
(a b c d)
% (append ‘((a b)) ‘(c d))
((a b) c d)
% (append ‘a ‘(b c d))
Error
% (append ‘(a b c) ‘d)
(a b c . d) -- weird notation: the last atom
-- is d instead of nil

C) list <item1> <item2> <item3> ...→ <list>


It constructs a list of the provided items:
% (list ‘a ‘b ‘c ‘d)
(a b c d)
% (list ‘(a b) ‘(c d))
((a b) (c d))

3-3 Destructive Operations:


Even though it is considered as an applicative or functional language, Lisp violated
the purity of functional languages by providing functions with memory side effects
like these destructives:
A) rplaca <list> <item> → <list>
It replaces the first item of a list by another:
% (rplaca ‘(a b c) ‘x)
(x b c)
% (rplaca ‘(a b c) ‘(x y))
((x y) b c)

-87-
B) rplacd <list> <list> → <list>
It appends the first item of a list with the second list:
% (rplacd ‘(a b c) ‘(x))
(a x)

Because of the list implementation using linked-lists, Lisp wasn’t quite ready to
remedy the consequences of the side effects of impure functions. Consider this
example:
% (set ‘L1 ‘(a b c d e f))
(a b c d e f)

L1

a b c d e f

% (set ‘L2 (cdr L1))


(b c d e f)

L1 L2

a b c d e f

-88-
% (rplacd L2 ‘(x y))
(b x y)

L1 L2

a b c d e f

x y

% L1
(a b x y)

3-4 User-Function Definition:


Lisp provides another impure function in order to define user functions:
(defun f (par1 par2 … parn) body)

% (defun inc (x) (+ x 1))


INC
% (inc 5)
6

One of the major contributions of Lisp is the conditional expressions. In order to


mimic the alternative of imperative languages, Lisp made it possible to define an
alternative clause using the cond function:
(cond (condition1 result1) (condition2 result2) ...)

-89-
% (defun PGCD (x y)
(cond
((= x y) x)
((> x y) (PGCD (- x y) y))
(t (PGCD x (- y x)))
)
)
PGCD
% (PGCD 6 9)
3
As we can see, Lisp has altered the iteration with recursion since every iterative
program is, theoretically, transferable to some recursion function.

3-5 Property Lists:


Even though there are no means to define structures in Lisp, users can structure their
data in a simple property list:
(p1 v1 p2 v2 ... pn vn) where pi is a property and vi is its corresponding
value:
(name (Ali Ahmed) age 45 salary 30000 hiredate (25 Jan
2008))
So, we can easily program a getprop function that extracts the value of a given
property:
% (defun getprop (prop L)
(cond
((null L) nil)
((eq (car L) prop) (cadr L))
(t (getprop prop (cddr L)))
)
)
GETPROP
% (setq AA ‘(name (Ali Ahmed) age 45 salary 30000
hiredate (25 Jan 2008))
)

-90-
AA
% (getprop ‘age AA)
45
Setq is used to avoid quoting the first argument (the variable’s name)).

-91-
3-6 Association Lists:
Some properties do not require a value. For instances, flags (true or false) are
very common in use: retired, married, … In case of property lists, we have to provide
a value for these properties:
(name (Ali Ahmed) married t age 45 salary 30000 hiredate
(25 Jan 2008) retired nil)
This problem is solved by another data structure: the association list or a-list:
((a1 v1) (a2 v2)... (an vn)), where ai is an association property and vi is
its corresponding value. The function that extracts the associated value of a property
is called assoc:
% (setq AA ‘((name (Ali Ahmed)) (married) (age 45)
(salary 30000) (hiredate (25 Jan 2008))))
AA
% (assoc ‘age AA)
45
% (assoc ‘hiredate AA)
(25 Jan 2008)
% (assoc ‘married AA)
t
% (assoc ‘retired AA)
nil

3-7 Functional arguments:


Lisp considers functions as first-class citizens: they can be passed as parameters to
other functions. One of the famous functions that accept other functions as parameter
is the mapcar function. Mapcar applies the parameter function on the items of the
provided list:
% (defun inc (x) (+ x 1))
INC
% (mapcar ‘inc ‘(1 3 5))
(2 4 6)

-92-
% (defun pos (x) (>= x 0))
POS
% (mapcar ‘pos ‘(0 2 -5 6))
(t t nil t)
% (mapcar ‘listp ‘(a (b c) ((d e)) ))
(nil t t)

-93-
Exercices :
1- Write a function Length that counts the number of top-level elements in a list:
% (Length ‘(a b c d e f))
6

2- Write a function modprop that modifies a property with the provided value:
% (modprop ‘age 35 ‘(name (Ali Ahmed) age 45))
(name (Ali Ahmed) age 35)

3- Let’s consider the following function definition:


% (defun f(L)
(cond
((null L) nil)
((< (car L) 0) (cons 0 (f (cdr L))))
(t (cons (car L) (f (cdr L))))
)
)
F
What would be the result of the following call:
% (f ‘(1 -1 -2 2 3 -4))

4- Write a function sumup that adds up the elements of a given numeric list:
% (sumup ‘(1 2 3 4))
10

5- Write a function proptoassoc that converts a property list to an association list:


% (proptoassoc ‘(x 1 y 2 z 3))
((x 1) (y 2) (z 3))

-94-
4- Lambda Expressions:
4-1 Anonymous Functions:
Let’s recall the example of mapcar that we saw previously:
% (defun inc (x) (+ x 1))
INC
% (mapcar ‘inc ‘(1 3 5))
(2 4 6)
% (defun pos (x) (>= x 0))
POS
% (mapcar ‘pos ‘(0 2 -5 6))
(t t nil t)

As we can see, it is inconvenient to give a name to a function every time we


want to pass it to mapcar, because it would clutter up the name space with short
function definitions that are used only once.
This problem can be solved easily by providing the function’s body only:
% (mapcar ‘(+ x 1) ‘(1 3 5))
(2 4 6)
% (mapcar ‘(>= x 0) ‘(0 2 -5 6))
(t t nil t)
Actually, another problem occurs when an ambiguity arises between global
variables and formal parameters:
% (setq x 0)
0
% (setq y 2)
2
% (mapcar ‘(list x y) ‘(1 3 5))
((0 1) (0 3) (0 5)) ; y is the formal
((1 2) (3 2) (5 2)) ; x is the formal

-95-
Based on the lambda calculus theory, Common Lisp provided a solution by
allowing nameless functions to be declared on the fly:
% (mapcar ‘(lambda(x) (+ x 1)) ‘(1 3 5))
(2 4 6)
% (mapcar ‘(lambda(x) (>= x 0)) ‘(0 2 -5 6))
(t t nil t)
% (setq x 0)
0
% (setq y 2)
2
% (mapcar ‘(lambda(x) (list x y)) ‘(1 3 5))
((1 2) (3 2) (5 2))
% (mapcar ‘(lambda(y) (list x y)) ‘(1 3 5))
((0 1) (0 3) (0 5))

4-2 Returning an uncompleted Functions:


Suppose that we have a binary function f that accepts two parameters x and y.
Providing one parameter to this function results in a new unary function:
For instance, g(y) = f(5, x).
Common Lisp uses the lambda expression to return functions with less
parameters. Here is an example that converts a binary function to a unary one:
% (defun bu (f x) (function (lambda(y) (f x y))))
BU
As we can see, the lambda expression is used to complete the missed
parameters of a function. Then the definition of a lesser-parameter function would be:
% ((bu ‘+ 5) 6))
11
% (defun plus5 (y) ((bu ‘+ 5) y))
PLUS5
% (plus5 6)
11

-96-
% (defun listwith0 (y) ((bu ‘list 0) y))
LISTWITH0
% (LISTWITH0 1)
(0 1)
% (defun g(x y z) (+ x y z))
G
% (defun tu (f x y) (function (lambda(z) (f x y z))))
TU
% ((tu ‘g 2 3) 4)
9

Some dialects of Lisp, including Common Lisp, force the use of funcall in order to
call a formal parameter function, or a function that returns a function:
% (defun bu (f x) (function (lambda(y) (funcall f x y))))
BU
% (defun listwith0 (y) ((funcall (bu ‘list 0)) y))
LISTWITH0
% (LISTWITH0 1)
(0 1)

-97-
% (defun inc (x) (+ x 1))
INC
% (defun cube (x) (* (* x x) x))
INC
% (P INC CUBE)
15
% (P CUBE INC)
128
% (P CARREE DEC)
26

% (defun getprop (p L)
(cond
((null L) nil)
((eq p (car L)) (car (cdr L)))
(t (getprop p (cdr (cdr L))))
)
)
GETPROP
%(defun length (L)
(cond
((null L) 0)
(t (+ 1 (length (cdr L))))
)
)
LENGTH
%(Length ‘(a b c d e f))
6

-98-
% (defun modprop (prop newvalue L)
(cond
((null L) nil)
((eq (car L) prop) (cons prop (cons newvalue
(cddr L))))
(t (modprop prop newvalue (cddr L)))
)
)
GETPROP

-99-

You might also like