Macquarie University
Division of Information and Communication Systems

COMP125 FUNDAMENTALS OF COMPUTER SCIENCE


NOTES ON EIFFEL


1. Introduction

These notes provide a brief introduction to Eiffel and its related environment for the benefit of students who have learnt some Pascal in COMP124. Their aim is to assist with the transition and so they do not cover the more distinctive features of Eiffel, such as the philosophy of 'design by contract'. They are based on the PC version of Eiffel 4, but most points should be applicable to other platforrns as well.

For further reading, see the EiffelBench tutorial (provided separately) and the book Object-Oriented Programming in Eiffel, 2nd edition, by Thomas and Weedon (Addison Wesley, 1995). For a useful web site, try the Eiffel Liberty Journal at http://www.elj.com.

2. Environment

An Eiffel program consists of a set of classes which, for our purposes, will all be in the same directory. Each class is stored in a file whose name must end with '.e'. If you want to store class FRED in the file joe.e, no-one will stop you. For your own sanity, though, it is best to store it in fred.e.

To run EiffelBench, execute ebench. You will be asked to select a directory: find the required one and click OK.

If this is a new project, you will need to nominate or build an Ace file (Assembly of Classes in Eiffel). You can either modify an existing one or create one from scratch. Click on the System button (the second one on the toolbar) and choose Browse to select an existing one, which you can then modify, or Build to create a new one. If you choose Build, you will be asked to enter:

  1. The system name. This is the name of the file in which the executable will be stored. Choose an appropriate name. (In practice, you are unlikely to use the name, or even to see it; execution is achieved by clicking on 'Run'.)
  2. The root class name. This names the class that provides what is, in effect, your main program. It helps if the name describes the application.
  3. The creation procedure name. This names the main program itself, which must be a creation procedure of the root class -- typically, one called make.
You can then select various options:
  1. Precompiled libraries. If you need the Windows Eiffel Library, tick the WEL box.
  2. Default options. Usually OK.
  3. Default assertion checking. Why not go for them all?
Clicking on 'Create Project' will create the Ace file, giving it the default name Ace.ace. If a file for your root class does not yet exist, it creates one for you (which is generally not very helpful). It displays a message saying 'Welcome to Eiffel'.

To create, view and modify class files, click on the Class button (the third from the left). For an existing file, you then need to click on Open and select the one you want. Editing facilities are standard for Windows. On completion, click on Save.

To compile, click on Melt. The diagnostics are quite helpful: read them carefully. To execute, click on Run. To find out more, try the Eiffelbench tutorial.

3. Basic elements

Comments start with two minus signs and continue to the end of the line. Thus:
n := 0 -- initialise n
An identifier is a sequence of letters and digits, beginning with a letter and with underscores as optional separators. For example:

Fred X25 Jo_Blow hours_per_week
The identifiers are not case sensitive: they do not distinguish between upper case and lower case. Thus the names FRED, Fred and fred are all equivalent.

The following identifiers are reserved as keywords of the language and cannot be used for other purposes:
alias all and as check
class creation debug deferred do
else elseif end ensure expanded
export external false feature from
frozen if implies indexing infix
inherit inspect invariant is like
local loop not obsolete old
once or prefix redefine rename
require rescue retry select separate
strip then true undefine unique
until variant when xor
An integer constant is a sequence of digits and is assumed to be decimal. A floating constant must include a decimal point and may include an exponent. Examples:
0.000123 1.23E-4 .123e-3 123.E-6
A character constant is a character enclosed in single quotes and is case sensitive. Thus 'x' and X are different. Special characters are represented by using '%' as an 'escape' character. The more useful ones are:

	'%N'		newline
	'%T'		tab
	'%B'		backspace
	'%R'		carriage return
	'%F'		form feed
	'%V'		|
	'%H'		\
	'%L'		~
	'%''		'
	'%"'		"
	'%%'		%
	'%/ddd/'	ASCII code ddd
	'%U'		the null character (='%/000/')

A string is a sequence of characters enclosed in double quotes. For example, "this is a string%N".

4. Style

In writing Eiffel programs, you are expected to follow certain style conventions.

  1. Every file should start with a comment that gives a brief description of the class it contains. The comment should conclude with your name and the date when you wrote it.
  2. The main sections of each class should be separated by one or more blank lines.
  3. Identifiers should be 'meaningful'. They should describe the entities that they name. Occasionally it may be more appropriate to use a very simple name, such as x or i. Use your discretion.
  4. Most Eiffel programs adopt the curious convention that the names of types are written in upper case. My own view is that this is a hangover from the 1950s, if not from the ancient Romans, and that it should be abolished. As a concession to current practice, I shall continue to use upper case for class names but shall nott use it for the basic types, namely integer, real, double, boolean and character. The upper case names can then remind us that class objects behave in a radically different way from the basic types.
  5. You must follow the standard conventions for indentation. However, do not use the tab character for this purpose. Tabs may look good on the screen, but when you print them out they usually take up eight spaces, which is far too much. Research has shown that three or four spaces are the most readable.
  6. Comments should be used to explain what the code is for. However, do not clutter up the code with too many insertions and do not state the obvious. Major comments may be included as separate lines prior to each block of code. Minor comments may be added on the right, preferably lined up with each other.
  7. The end symbol can end many different things. As an aid to understanding, a common convention is to add a brief comment to indicate what is ending at that point. For example, at the end of an if instruction write:
  8. end --if
5. Basic types

The following are the basic types:

The arithmetic operators for integers are:
- i negation
i + j addition
i - j subtraction
i * j multiplication
i // j quotient on division
i \\ j remainder on division

If i and/or j is negative, the effects of the division need to be carefully checked.

For floating types the arithmetic operators are:
- x negation
x + y addition
x - y subtraction
x * y multiplication
x / y division
x ^ y x to the power of y

Mixed modes are allowed. So, for example, the expression x + 1 is allowed, with 1 being converted to real. Similarly, x ^ 3 and even 2 ^ 3 are allowed, their results in both cases being real. (On some versions 2 ^ 3 is integer, which seems more reasonable).

For all the basic types, the usual relational operators are defined:
x = y equality
x /= y inequality
x < y less than
x <= y less than or equal
x > y greater than
x >= y greater than or equal

Their results are the Boolean values false or true. Equality and inequality are also defined for class objects; but their definition is a bit complicated and will not be considered here.

Conditions can be combined by the following Boolean operators:
not p logical not
p or q inclusive or
p and q logical and
p implies q (not p) or q
The or and and operators have 'short circuit' forms:
p or else q conditional or
p and then q conditional and

If p is true, the or else returns true without bothering to evaluate q. Similarly, if p is false, the and then returns false without bothering to evaluate q.

The precedence rules give highest precedence to the unary operators:
not + -

Next come the infix operators in the following order:

	^
	*   /    //   \\
	+   -
	=   /=
	<   <=   >    >=
	and   and then
	or    or else
	implies

These all associate to the left. For example, x / y * z means (x / y) * z.

6. Declarations

Variables can be declared, either as features of a class or as local variables of a routine (function or procedure):
i, j : integer
B : BOARD_ARRAY

The declarations may be separated by semicolons; but, as always in Eiffel, these are optional.

The variables have default initial values, depending on their types:
integer 0
real 0.0
double 0.0
boolean false
character the null character
all other types void

An initial value may be specified, but in that case the declaration creates a named constant:
max_mark : integer is 100
conversion_factor : real is 0.133

The values must be 'manifest' constants (known at compile time) and cannot be changed.

Values can be enumerated automatically:
green, orange, red : integer is unique

The values will be positive and consecutive, and the usual relations apply, such as green < orange.

Declarations of routines (functions and procedures) are described later.

7. Instructions

Instructions tell the computer to do something (and are called 'statements' in most other languages).

One of the simplest is the assignment:
variable := expression

This tells the computer to evaluate the expression and to assign its value to the variable. Another form of the assignment uses the operator '?=' and has a technical meaning that relates to the types of subclasses.

The conditional statement has the form:

	if condition then
	   compound
	elseif condition then			-- optional part(s)
	   compound
	else					-- optional part
	   compound
	end

Indentation should follow the style illustrated, using the number of spaces suggested in Section 4.

The if clause introduces the first condition to be tested followed by the compound that is to be executed if the condition is true. The compound is an arbitrary sequence of instructions, optionally separated by semicolons. Next come an arbitrary number of elseif clauses, which are tested in turn until a true one is found. If the conditions are all false, the optional else clause acts as a catch-all.

The inspect instruction provides an alternative conditional form:

	inspect
	   expression
	when list of value then
	   compound
	when list of value then			-- optional part(s)
	   compound
	else
	   compound
	end

The expression is evaluated and its value must be either an integer or a character. Each list of values is a list of constants and/or constant ranges of that type, separated by commas. These values must be known at compile time and no value may be included more than once. If the value of the expression occurs in one of the lists, the corresponding compound is executed. If it does not occur, the else part (if any) is executed; but if there is no else part, the run-time system raises an exception, signalling that something has probably gone wrong. For example:

	inspect
	   spots
	when 5, 9 then
	   wins := wins + 1
	when 2, 6..8, 12 then
	   losses := losses + 1
	else
	   -- null
	end -- inspect

The else part shows that a compound can be empty. The comment makes the intention more explicit.

The loop statement has the basic form:

	from
	   compound
	until
	   condition
	loop
	   compound
	end

The first compound initialises the loop; if there is no initiallisation, it can be left empty . The main loop then repeatedly tests the condition and, if it is false, executes the second compound. When it finds that the condition is true, the loop terminates. Eiffel also allows two other clauses to be included, called the variant and the invariant. These are used for verifying that the loop satisfies its required properties.

For example, here is a loop that executes P(i) for each value of i from 1 to n:

	from
	   i := 1
	until
	   i > n
	loop
	   P(i)
	   i := i + 1
	end -- loop

There are no other kinds of loop in Eiffel.

8. Routines

A routine is either a function or a procedure. A function returns a result but should have no other effect. A procedure carries out some actions.

To declare a routine, the basic form is:

	name ( formal arguments ) : type is
	local
	   variable declarations
	do
	   compound
	end

If there are no formal arguments, the whole of '(formal arguments)' may be omitted. Similarly, if there are no local declarations, the whole of ' local declarations ' may be omitted.

The formal arguments (if any) have the same form as an ordinary list of declarations except that the semicolons must be included. All arguments are passed 'by value' and so there is no need to specify a mode (such as var). Inside the body of the routine, assignments to the formal arguments are not allowed.

If the phrase ':type' is included, the routine is a function that returns a value of the specified type. It implicitly declares a variable of that type called Result. When the compound has been executed, the final value of Result is returned as the value of the function. Functions are called as components of expressions.

If the phrase ':type' is not included, the routine is a procedure. It executes the compound but does not return a result. Routines are called as instructions.

Variables may be declared local to the routine, but not constants. The variables are initialised each time the routine is called and are destroyed when the call is complete.

9. Classes and Objects

Classes lie at the heart of object-oriented programming. Every object belongs to a class and its properties are those specified by the class definition. The framework for the simplest kind of class definition is:

	class
	   class name
	inherit
	   parent list
	creation
	   list of creation procedure names
	feature { clients }
	   feature declarations
	feature { clients }
	   ...
	end

A class has a name and may optionally inherit features from one or more parents. In most cases it will then nominate one or more creation procedures, which are used for creating objects of the class. It then declares its own features, which may be constants, variables or routines. These must include the creation procedures nominated earlier. The features are accessible to the nominated clients. The client ANY means any client of the class and is the default if '{clients}' is omitted. NONE means no clients: in other words, the features are private to the class itself. Note that clients may only access variables in read-only mode: they cannot change them.

To create objects of a specific class, POINT say, it is first necessary to declare one or more variables with the class name as their type:
p1, p2 : POINT

At this point p1 and p2 are void: they have no associated objects. To create an object, we can simply do:
!!p1
which creates an object of type POINT, with default initial values, and attaches it to p1. However, it is much more usual to initialise the object by calling one of the creation procedures, typically called make. So if make requires two integer arguments for the coordinates of the point, we might create a point by doing:
!!p1.make(15,20)
This creates an object of type POINT, attaches it to p1, and then executes the make procedure.

Once p1 has been attached to an object, its features can be accessed by the 'dot' notation. For example, p1.distance might return its distance from the origin. The feature distance could be either a variable or a function: Eiffel leaves it deliberately ambiguous from the user's point of view.

It is important to appreciate the effect of an assignment on an object. For example, consider:
p2 := p1
This does not assign to p2 a copy of p1. Instead, it 'attaches' p2 to the same object as p1. They are said to share the object. Anything that happens to p1's object will also happen to p2's. For example, if class POINT has a feature translate(x,y) that moves a point x units to the right and y units up, the effect of
p1.translate (10, 10)
will be felt by p2 as well as by p1.

The assignment
p2 := clone (p1)
does make a copy of p1's object. It creates a new object with exactly the same values. Apart from the use of '!!', it is the only way of making a new object. By contrast,
p2.copy(p1)
copies the values of p1's object into p2's object (and so p2 had better not be void). Note that it is an updating operation, not an assignment.

10. Arrays

An array is a collection of n objects (n >= 0) numbered 1, 2, ... n, or possibly 0, 1, ... n-1, or more generally b, b+1, ... b+n-1. The objects all belong to the same class, called the base class of the array.

In mathematical notation, the numbers would be written as subscripts:

A1 A2 A3 ... An
In computing, the numbers are called indices and are usually enclosed in brackets of some kind. Pascal, for example, uses the notation A[1], A[2], etc. More generally, b .. b+n-1 is called the index range, with b being the lower bound and b+n-1 the upper bound.

In Eiffel, arrays are provided by means of a standard class called ARRAY. Here, for example, are the steps needed to create an array called A containing the five numbers 2.6, 1.1, 8.0, 7.2 and 3.5. The first step is to declare A, giving it the required type:
A : ARRAY[real] -- A is initially void
The class ARRAY is generic, meaning that it can be of many different kinds. The clause '[real]' provides a generic parameter, indicating that its base class is to be real. Any other type could be used, including any class type. Note that Eiffel's use of square brackets is quite different from Pascal's.

The next step is to create the array, giving it the required size:
A.make (1,5) -- specify the bounds
The lower bound is 1 and the upper bound is 5. These values could be specified by any integer expressions. For example, they could be 0 and n-1.

Finally, we assign the values:
A.put (2.6, 1) -- assign 2.6 to position 1
A.put (1.1, 2)
A.put (8.0, 3)
A.put (7.2, 4)
A.put (3.5, 5)

These are not conventional assignments as found in Pascal, etc. Instead, they use the put procedure which is part of the class definition. The inverse operation is the function item, which retrieves the item in a specified position. For example.
val := A.item( i )
The reason for this notation is to make it consistent with all the other 'container' classes, such as sets and queues, that are in the library.

The full definition of class ARRAY contains several other features including lower, upper, and capacity (which is upper - lower + 1). For example, here is a loop that adds 1 to all the elements in an array B:

	from i := B.lower until i > B.upper loop
	   x := B.item(i)
	   B.put (x+1, i)
	   i := i + 1
	end -- loop

The operations put (x,i) and item (i) have the precondition:
require lower <= i and i <= upper
If i lies outside the bounds, an exception is raised.

For two-dimensional arrays there is a similar generic class ARRAY2 with the creation procedure make (m,n). This creates an m x n array with rows numbered 1..m and columns numbered 1..n. In contrast to ARRAY, the lower bounds have to be 1. The upper bounds m and n are retained as the attributes height and width. To access the item in row i and column j, you use the procedure put (x,i,j) and the function item (i,j).

11. Input/Output

Input/output is not part of the Eiffel language but is provided by its large library of classes. In particular, there is a class called STD_FILES and a related object called 'io' that provides typical text I/0 facilities. For example, to write an integer n do:
io.write_integer (n)

Reading requires more work:
io.read_integer
n := io.last_integer
The procedure read_integer reads an integer and stores it in the io object itself. The function last_integer then returns the one most recently read. Eiffel uses this two-step process because of its strict separation of procedures (taking actions) from functions (returning results).

There are similar features for character, STRING, real, double and boolean. Note the gaps:
put_character read_character last_character
put_string --- last_string
put_real read_real last_real
put_double read_double last_double
put_integer read_integer last_integer
put_boolean --- ---

To output numbers in a particular format, you have to convert them to strings using the classes FORMAT_INTEGER and FORMAT_DOUBLE (not covered here).

To read a string there are three instructions:
read_line read to the end of the line
read_stream(n) read n characters or to the end of the file, whichever is first
read_word read the next word (string of characters other than the 'white space' characters -- space, tab and newline)
All these leave their results in last_string. As the upper case reminds us, the STRING version has a pitfall. Following
io.read_line
s := io.last_line
the variable s is attached to last_string and so the next call of io.read_line will overwrite it. It is therefore safer to do:
s := clone (io.last_line)

The remaining features of io are:
next_line move to the start of the next input line
new_line move to the start of the next output line

All these names have abbreviated versions (see Wiener, pp78-80). However, as a matter of good style, which places readability before writing convenience, you should use the full names.

NB Note carefully. The PC version of Eiffel has serious bugs in its 'read' routines. In particular, they cannot reliably read more than one item per line. Furthermore, blank lines can produce some very surprising results. The only safe strategy is to restrict input to one item per line and to make sure that there are no blank lines.
NB You have been warned!!

12. Sample class

The following code shows what a simple class looks like. It states that SQUARE objects have attributes that give their position and their contents, and three routines -- the creation procedure make, a put procedure for supplying the contents, and a paint procedure for displaying it in a window.

	class SQUARE

	inherit
	   WEL_STANDARD_COLORS

	creation
	   make

	feature

	   x1, y1, x2, y2 : integer			-- its positon
	   ob : OBJECT					-- its contents

	   make (px1, py1, px2, py2 : integer) is
	   do
	      x1 := px1
	      y1 := py1
	      x2 := px2
	      y2 := py2
	   end --make

	   put (v : OBJECT) is
	      -- supply an object
	   do
	      ob := v
	   end --put

	   paint (paint_dc : WEL_PAINT_DC; palette : RAINBOW) is
	      -- display the square
	   local
	      brush : WEL_BRUSH				-- for colouring
	   do
	      paint_dc.rectangle (x1, y1, x2, y2)
	      !!brush.make_solid (palette.colour(ob.fun))
	      paint_dc.select_brush (brush)
	      paint_dc.flood_fill_border (x1+1, y1+1, black)
	   end --paint

	end --SQUARE

Jan Hext
Macquarie University

5.1.99


Back To COMP125 Page.
Back To Nozzle's Homepage.