Contents
Compiling SmartEiffel
Hello world
Classes
Day of week example
Example: a class of fractions
Example: find all files containing words
Generic classes, deferred classes, and inheritance
End of notes
Compiling SmartEiffel
First, the SmartEiffel package used be called
SmallEiffel, and we use the older SmallEiffel
package out of caution.
The most important Small (or Smart) Eiffel programs are
compile, short, and clean.
There are others. To learn about all of them, use man smalleiffel
The short utility displays a short form of an Eiffel class.
short array
will summarise the class ARRAY.
This lists the `public features' of the array. If you
wish to see all the features, public and private, use
short -client none array
The programs short, compile, clean, are stored in a SmallEiffel subdirectory. You need to make them accessible. The Eiffel library (containing arrays, strings, etcetera) needs to be made accessible also.
Ask for help if you need to set this up.
return to table of contents
Hello world
Create a file hello.e containing the following
class HELLO
creation
make
feature
make is
do
io . put_string ("Hello world\%N")
end
end -- class HELLO
This file must be named hello.e, because SmartEiffel
always expects a class HELLO to be in a file hello.e.
This is how SmartEiffel finds class definitions.
Input/output: the object io
The object io is of type
STD_INPUT_OUTPUT and can be used like both stdin and stdout
in C. The '%N' of Eiffel means end-of-line, like '\n' in C.
Information about classes such as STD_INPUT_OUTPUT (TEXT_INPUT_OUTPUT in SmartEiffel) is accessible through the Eiffel web page www/~odunlain/eiffel. The `short' utility is also useful.
To see the general features of your hello program, use
short hello
which produces a `short form' of the program. To compile it use
compile hello
This produces an executable file
a.out
which writes the
message `Hello world' on the terminal and stops.
compile -o hello hello
does the same thing, except the executable file is now called
`hello.'
Eiffel expects the main routine, `main()' in C,
to be called `make.' The clause
creation
make
allows the routine make to be accessed as a main
routine. Therefore
the following compile command produces the same effect as above:
compile -o hello hello make
Compile has the general format
compile (-o ) (main file) (main routine)
The compile program leaves various unwanted C files lying around.
To clean up your directory after compiling hello, use the
clean utility:
clean hello
class CLASS_NAME
-- anything after two hyphens is a comment
creation
<creation features>
feature
<various features>
feature
<various features>
...
end -- class CLASS_NAME
Features are variables, routines, etcetera.
Features need not be `exported' to all
classes. For example,
feature {TREE}
left_child ...
would say that left_child is a feature only available
to objects of type TREE.
The creation features are routines which can be called when the object is created.
Certain classes, like the class HELLO in hello.e above, are meant to be run as programs, and actually resemble PASCAL programs.
Each feature is either a variable, a constant, a procedure, or a function.
A variable declaration looks like
message : STRING
count : INTEGER
A constant declaration looks like
count : INTEGER is 5
Types of variables are
CHARACTER, BOOLEAN, INTEGER, REAL, DOUBLE,
and any type constructed out of Eiffel classes, such as
ARRAY [ STRING ]
Types like ARRAY[STRING] are called reference types.
Every variable is initialised. Numbers are initialised to zero, characters to blanks or nulls (compilers are inonsistent here), booleans to false, and reference to Void, which corresponds to NULL.
Flow control is through if, loop, and inspect.
Format of an if statement is
if <condition> then
<group of statements>
elseif <condition> then
<group of statements>
elseif ...
else
<group of statements>
end
The elseif part is optional, and there can be several.
The else part is also optional.
Format of a loop statement is
from
<initialisation statements>
until
<termination condition>
loop
<group of statements>
end
The inspect statement corresponds to the switch statement in C.
inspect <integer expression>
when <integer value> then ...
...
when <integer value> then ...
default ...
end
Example of a loop statement
class HELLO2
creation
go
feature
count : INTEGER is 5
go is
local
i : INTEGER
do
from
until
i > count
loop
io . put_integer (i)
io . put_string ( " Hello world%N" )
i := i+1
end
end
end -- class HELLO2
The main routine (creation feature)
in this program is called go, so the program
must be compiled as
compile hello2 go
Semicolons. Notice that the above program
has no semicolons. Semicolons as statement separators
are optional in Eiffel.
Conditions in if and loop statements. These involve the usual binary relations
= < > <= >= /=
(The last is `not equals.')
Logical connectives are ``not, and, or, and then, or else, implies.'' The last three are semistrict.
In other words, in evaluating `A and then B,' if A is false then false is returned without evaluating B, and if A is true then B is evaluated.
Here is the general form of a routine definition
the_routine ( arguments )
is
require
<preconditions>
local
<local variables>
do
<routine body>
ensure
<postconditions etcetera>
end
The require and ensure clauses are about
preconditions and postconditions and will be discussed by
Hugh Gibbons later this week.
They are optional. Only `is, do,' and `end,' are required.
A function has the same layout, except for the header:
the_function ( arguments ) : result type
Function values are returned through the reserved word
Result.
Upper and lowercase. Eiffel is officially case-insensitive, but there are certain conventions.
That is, class names are uppercase, keywords are lowercase, and reserved words such as Result, Current, and Void, are capitalised as shown.
Simple statements. There are three kinds: assignment, creation, and routine calls.
Assignment statements are like
x := <expression>
x is a variable which can only be a feature of
the class or local variables.
It is impossible to assign to routine arguments or to features of another object.
Arithmetic expressions are almost the same as in other languages. The arithmetic operators are
+ - * / // \\
The double slash means integer division, and the
double backslash means integer remainder,
i.e.,
x \\ 3
means x modulo 3.
Creation statements are like
!! x
or
create x
or
!! x.make
or
create x.make
These create objects whose type is that of x.
In !!x, all fields in x are initialised according to
the Eiffel conventions.
The form !!x.make uses a creation procedure
to initialise x. A useful example is
x : ARRAY [ INTEGER ]
...
!! x . make ( 1, 100 )
which creates an array of size 100 (initialised to zero).
To learn about the class ARRAY you need to use the short
utility or the web page entry.
return to table of contents
Day of week example
class DAY
-- Derive day of week from date (in 2003)
-- Required input is dd mm
-- day and month
creation
go
feature
month_offset (mm: INTEGER) : INTEGER is
local
months : ARRAY [INTEGER]
i : INTEGER
do
-- return total length of months up to
-- but not including mm.
months :=
<<31,28,31,30,31,30,31,31,30,31,30,31>>
-- This is a very useful Eiffel construction,
-- called a `manifest array.' It makes it
-- very easy to create constant arrays.
from
i := 1
until
i = mm
loop
Result := Result + months . item (i)
i := i+1
end
end
weekday (i : INTEGER) : STRING is
do
inspect i
when 0 then Result := "Sunday"
when 1 then Result := "Monday"
when 2 then Result := "Tuesday"
when 3 then Result := "Wednesday"
when 4 then Result := "Thursday"
when 5 then Result := "Friday"
when 6 then Result := "Saturday"
end
end
go is
local
finished : BOOLEAN
day, month, k : INTEGER
do
from
io.put_string ("Enter date: ")
until
finished
loop
io . read_integer
day := io . last_integer
-- Reading from the input stream is
-- always in two steps.
if day <= 0 then
finished := true
else
io . read_integer
month := io . last_integer
k := 2 + day + month_offset ( month )
k := k \\ 7
io.put_string ("Day of week is ")
io.put_string ( weekday (k) )
io.put_string ("%NEnter date: ")
end
end
end
end -- class DAY
return to table of contents
Example: a class of fractions
A fraction is, of course, a number
p/q where p,q are integers and
q is not zero.
We should be able to add, subtract, multiply, and divide fractions. We should also be able to access the numerator and denominator of a fraction. Our class will include the following features:
class FRACTION is
creation
make
feature
numerator, denominator : INTEGER
sum ( other : like Current ) : FRACTION
difference ( other : FRACTION ) : FRACTION
product ( other : FRACTION ) : FRACTION
quotient ( other : FRACTION ) : FRACTION
inverse : FRACTION
feature {NONE}
make ( n, d : INTEGER )
reduce
-- reduce to simplest form
end -- class FRACTION
All the operations are functions. They don't modify
fractions, they create new ones. This is inefficient as it
puts a strain on the garbage collector, but it is more secure,
since fractions never change.Notice that the creation routine is listed under
feature {NONE}
This
means that no other object can call make except
as a creation routine.
The reduce procedure reduces the fraction to its simplest
form.
We shall also write an `out' feature.
This produces a printable form of the fraction.
Finally, `syntactic sugar.' It is possible to define infix forms of routine names, allowing us to use the usual +,-,*,/ with fractions. We shall do this, so what was called sum, difference, etcetera will now be called +,-, etcetera.
Here is the full listing.
class FRACTION
inherit ANY
redefine out
end
-- needed because the default 'out' routine is
-- rewritten.
creation
make
feature
numerator, denominator: INTEGER
infix "+" ( other : like Current ) : like Current is
local
n,d : INTEGER
do
n := denominator * other.numerator + numerator * other.denominator
d := denominator * other . denominator
!! Result . make ( n, d )
end
-- type `like Current' is equivalent
-- to FRACTION. It means `the same
-- type as the current object's.'
infix "-" ( other : like Current ) : like Current is
infix "*" ( other : like Current ) : like Current is
-- similar
infix "/" ( other : like Current ) : like Current is
require
no_zero_divide : other . numerator /= 0
-- similar
inverse : like Current is
require
nonzero : numerator /= 0
-- similar
out: STRING is
do
Result := numerator . out
if denominator /= 1 then
Result . extend ( '/' )
Result . append ( denominator . out )
end
end
feature {NONE}
-- these features are visible to NONE. It
-- means no other object can call the routine
-- reduce, and another object can only call
-- make as a creation routine.
reduce is
-- ensures fraction is in its simplest form
make (n, d: INTEGER ) is
-- etcetera
end -- class FRACTION
The program below exhibits the class FRACTION:
class FRAC_TEST
creation
go
feature
go is
local
a,b,c : FRACTION
do
!! a . make ( 2 , 4 )
!! b . make ( 3 , -4 )
!! c . make ( 5, 6)
io . put_string ( "a = " + a . out + "%N" )
io . put_string ( "b = " + b . out + "%N" )
io . put_string ( "c = " + c . out + "%N" )
io . put_string ( "a + b = " + (a+b).out + "%N" )
io . put_string ( "a - c = " + (a-c).out + "%N" )
io . put_string ( "a * c = " + (a*c).out + "%N" )
io . put_string ( "a / c = " + (a/c).out + "%N" )
end
end -- class FRAC_TEST
return to table of contents
find_words example program
match_in_file
fname_has_extension
scan
go
class FIND_WORDS
creation
go
feature
extension : STRING
-- default equivalent -ext e (alternatively, -ext c, -ext tex, etc
-- That is, only searches files with this extension, and
-- the default extension is e (eiffel files). This
-- string begins with a fullstop.
automaton : KMP
display_overlap_map ( kmp : KMP ) is
local
triple : GBN_TRIPLE [ STRING, STRING, STRING ]
do
triple := kmp . overlap_out
io . put_string ("kmp overlap map%N")
io . put_string ( triple.item_1 + "%N" )
io . put_string ( triple.item_2 + "%N" )
io . put_string ( triple.item_3 + "%N" )
end
return to start of this program
match_in_file ( fname : STRING; pad: STRING ) is
require
automaton_exists : automaton /= Void
local
file : TEXT_FILE_READ
head_written : BOOLEAN
do
if not file_exists ( fname ) then
std_error.put_string ( pad + fname + " unreadable%N")
else
from
!! file.connect_to ( fname )
until
file . end_of_input
loop
file . read_line
automaton.install_text_string ( file.last_string )
automaton.find_match
if automaton.match_found then
if not head_written then
io.put_string ( pad + fname + ":%N" )
head_written := true
end
io.put_string ( io.last_string + "%N" )
end
end
end
end
return to start of this program
fname_has_extension ( fname : STRING ) : BOOLEAN is
-- verifies that fname ends with correct
-- extension.
local
i : INTEGER
do
if fname.count >= extension.count then
from
i := 0
Result := true
until
i >= extension.count
loop
if fname.item(fname.count-i) =
extension.item(extension.count-i)
then
i := i+1
else
Result := false
i := extension.count
end
end
end
end
return to start of this program
scan ( a_dir: STRING; pad: STRING ) is
-- scans the directory named a_dir, searching
-- files which match the extension, and
-- recursing into subdirectories.
local
dir, subdir : DIRECTORY
fname : STRING
i : INTEGER
do
!! dir.make
!! subdir.make
dir.scan ( a_dir )
from
i := 1
until
i > dir.count
loop
fname := dir.item(i)
if not fname.is_equal(".") and then not fname.is_equal("..")
then
subdir.scan ( a_dir + "/" + fname )
if subdir.last_scan_status then
scan ( a_dir + "/" + fname, pad + " " )
elseif fname_has_extension (fname)
then
match_in_file ( a_dir + "/" + fname, pad )
end
end
i := i+1
end
end
return to start of this program
go is
local
file : TEXT_FILE_READ
keys : ARRAY[STRING]
i,j : INTEGER
do
!! extension.copy ( ".e" )
------------------------------------------------------
-- awful code checking the command line arguments
------------------------------------------------------
if argument_count < 2 then
std_error.put_string("find_words expects at least 2 args%N")
std_error.put_string("format " + argument(0) +
" ..pattern[s].. [-ext ]%N" )
die_with_code ( exit_failure_code )
end
if not file_exists (argument(1)) then
std_error.put_string ( argument(1) + " unreadable%N")
die_with_code ( exit_failure_code )
end
if argument(2).is_equal ("-f") then
if argument_count = 5
then
if argument(4).is_equal("-ext")
then
!!extension.copy( "." + argument(5))
else
std_error.put_string("4th arg expected -ext%N")
die_with_code ( exit_failure_code )
end
elseif argument_count /= 3 then
std_error.put_string( argument(0) +
" -f [-ext ]%N" )
die_with_code ( exit_failure_code )
end
if not file_exists (argument(3)) then
std_error.put_string ( argument(3) + " unreadable%N")
die_with_code ( exit_failure_code )
else
!! file.connect_to ( argument(3) )
end
end
------------------------------------------------------
-- end of awful code checking the command line arguments.
-- Next read many keys from the command line but only
-- use the first.
------------------------------------------------------
if file = Void then
from
!! keys.make (1, 0 )
i := 2
j := 0
until
i > argument_count
loop
if argument(i).is_equal("-ext") and
i < argument_count
then
!! extension.copy ( "."+argument(i+1) )
i := i+2
else
j := j+1
keys.force ( argument(i), j )
i := i+1
end
end
else
from
!! keys.make ( 1, 0 )
i := 1
until
file.end_of_input
loop
file.read_line
keys.force ( file.last_string, i )
end
end
!! automaton.make ( keys . item(1) )
-- display_overlap_map ( automaton )
-- debugging
scan ( argument(1), "" )
end
end -- class FIND_WORDS
return to start of this programreturn to table of contents
Generic classes, deferred classes, and inheritance
A generic class is one which requires one or
more type parameters. The class ARRAY is a generic
class.
Its definition begins
class ARRAY[E]so objects are arrays of items of type E. You can therefore use
a : ARRAY [INTEGER]
b : ARRAY [REAL]
c : ARRAY [STRING]
etcetera.
Class inheritance is where one class inherits all features of another. In fact there is a system-wide class ANY containing various useful features which are automatically inherited by all classes.
For example, we use a feature
io
all the time.
Yet we never see it declared. Why? Because it is a feature
of the class ANY and automatically inherited by every class.
Class inheritance is important in connection with deferred classes. A deferred class is one which lists certain features without filling in all their details.
For example, in the Gobán library, currently under development, there is a class GBN_ITERATOR which is both a generic class and a deferred class. Its definition begins
deferred class GBN_ITERATOR [G]
-- iterator in the Goban library
feature
finished: BOOLEAN
-- has iteration finished?
is
deferred
end
-- There are three other features, all deferred.
-- forth: next item in iteration
-- stop: stop the iteration
-- item: the item currently being inspected.
end -- deferred class GBN_ITERATOR [G]
This is a deferred class because the details
of how the features are implemented differ
from class to class. But iterators can always be
used in exactly the same way, such as
local
it : GBN_ITERATOR [STRING]
...
from
it := a . iterator
until
it . finished
loop
io . put_string ( it . item )
io . put_new_line
end
In the Gobán library there is an important class
representing a finite sequence or `tuple.'
It is defined in stages, using inheritance:
GBN_ROTUPLE [G] count: INTEGER is_empty: BOOLEAN first_place: GBN_PLACE last_place: like first_place rank (p: like first_place): INTEGER pred (p: like first_place): like first_place succ (p: like first_place): like first_place item (p: like first_place): G first_item: G last_item: G iterator: GBN_TUPLE_ITERATOR[G] reverse_iterator: like iterator printout (f: OUTPUT_STREAM)And of GBN_TUPLE [G]:
GBN_TUPLE [G]: all features of GBN_ROTUPLE [G], and wipe_out add_first (x: G) add_last (x: G) add_before (x: G; p: like first_place) add_after (x: G; p: like first_place) remove (p: like first_place) absorb_before (other: like Current; p: like first_place) absorb_after (other: like Current; p: like first_place)All of these features are implemented quite differently in GBN_DLIST and GBN_RBTLIST, but they behave identically.
Assignment statements and types.
In an assignment
x := <expression>
the type of x must be generally the same
type as in <expression>. However, the class to
which x belongs can be an ancestor of that for the
<expression>. For example, if x is of
type GBN_ITERATOR[INTEGER] and a is of type
GBN_RBTLIST[INTEGER],
then
x := a . iterator
is valid, even though the right-hand side is actually of
type GBN_TUPLE_ITERATOR[INTEGER]. This is because
although its type is not GBN_ITERATOR[INTEGER], its type
is a descendant of GBN_ITERATOR[INTEGER].
(End of notes.)