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 arraywill 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 HELLOThis 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 hellowhich produces a `short form' of the program. To compile it use
compile helloThis produces an executable file
a.outwhich writes the message `Hello world' on the terminal and stops.
compile -o hello hellodoes 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 makeallows 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 makeCompile has the general format
compile (-oThe compile program leaves various unwanted C files lying around. To clean up your directory after compiling hello, use the clean utility:) (main file) (main routine)
clean hello
class CLASS_NAME -- anything after two hyphens is a comment creation <creation features> feature <various features> feature <various features> ... end -- class CLASS_NAMEFeatures 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> endThe 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 HELLO2The 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> endThe 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 typeFunction 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 \\ 3means x modulo 3.
Creation statements are like
!! x or create x or !! x.make or create x.makeThese 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 FRACTIONAll 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" ) endreturn 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 endreturn 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 endreturn 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 endreturn 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) + "return to start of this program..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 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
ioall 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 endIn 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.)