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:
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 |
Fred | X25 | Jo_Blow | hours_per_week |
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 |
0.000123 | 1.23E-4 | .123e-3 | 123.E-6 |
'%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.
end --if |
The following are the basic types:
integer | an integer, such as 123 |
real | a real number, such as 1.23e4 |
double | a real number with double-length precision |
boolean | a Boolean value, true or false |
character | a character, such as 'a' |
- 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 |
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 |
!!p1.make(15,20) |
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 |
p1.translate (10, 10) |
The assignment
p2 := clone (p1) |
p2.copy(p1) |
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 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 next step is to create the array, giving it the required size:
A.make (1,5) | -- specify the bounds |
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) |
val := A.item( i ) |
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 |
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) |
io.read_integer | |
n := io.last_integer |
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 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) |
io.read_line | |
s := io.last_line |
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
5.1.99
Macquarie University