SmartEiffel: a short course

Hugh Gibbons and Colm Ó Dúnlaing

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

return to table of contents





Classes

The following is an outline class definition:
            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 program

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

                        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: Here are some (not all) features of the deferred class GBN_ROTUPLE[G]:
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].

return to table of contents

(End of notes.)