Intermediate

I/O Operations in Eiffel

Learn console output, keyboard input, and text file reading and writing in Eiffel using io and PLAIN_TEXT_FILE with Docker-ready examples

Input and output are how a program talks to the outside world - the console, the keyboard, and files on disk. In Hello World you already saw print and io.put_string. This tutorial goes further: reading what a user types, writing structured output, and creating and reading text files.

As a pure object-oriented language, Eiffel does not have free-standing I/O functions. Instead, I/O is provided through objects. The feature io (inherited by every class from ANY) is an object of type STD_FILES that represents the standard input, output, and error streams. Files are represented by the PLAIN_TEXT_FILE class from the base library. You send messages to these objects - put_string, read_line, close - rather than calling global functions.

Eiffel also keeps to its Command-Query Separation principle here. A read operation such as io.read_line is a command: it changes the state of the stream and stores what it read in a query like io.last_string. You read the result afterward instead of getting it back directly from the call. This pattern - “do the read, then ask for the last value” - is the single most important thing to understand about Eiffel I/O.

By the end of this tutorial you will be able to print formatted output to the console, prompt for and read keyboard input, and write and read back a text file.

Console Output

You have several ways to write to the console. print (from ANY) takes any object and outputs its string form. The io object offers typed routines like put_string, put_integer, and put_new_line. To turn a non-string value into text explicitly, call .out on it.

Create a file named io_operations.e:

note
    description: "Demonstrates console and file I/O in Eiffel"

class
    IO_OPERATIONS

create
    make

feature -- Initialization

    make
            -- Run all I/O demonstrations in order.
        do
            show_console_output
            write_to_file
            read_from_file
        end

feature -- Console output

    show_console_output
            -- Demonstrate several forms of console output.
        local
            count: INTEGER
        do
            count := 42

            -- print comes from ANY and accepts any object
            print ("=== Console Output ===%N")

            -- io.put_string writes a string with no trailing newline
            io.put_string ("A plain string%N")

            -- Typed output: write an integer directly
            io.put_string ("Count is: ")
            io.put_integer (count)
            io.put_new_line

            -- Convert a value to a string with .out and concatenate
            io.put_string ("Doubled: " + (count * 2).out + "%N")

            -- %T is a tab; %N is a newline
            io.put_string ("Tab%Tseparated%Tvalues%N")
        end

feature -- File output

    write_to_file
            -- Create notes.txt and write several lines to it.
        local
            output_file: PLAIN_TEXT_FILE
        do
            create output_file.make_open_write ("notes.txt")
            output_file.put_string ("First line%N")
            output_file.put_string ("Second line%N")
            output_file.put_string ("Number: ")
            output_file.put_integer (100)
            output_file.put_new_line
            output_file.close
            print ("%N=== Wrote notes.txt ===%N")
        end

feature -- File input

    read_from_file
            -- Read notes.txt back and echo each line to the console.
        local
            input_file: PLAIN_TEXT_FILE
        do
            print ("=== Reading notes.txt ===%N")
            create input_file.make_open_read ("notes.txt")
            from
                input_file.read_line
            until
                input_file.exhausted
            loop
                print (input_file.last_string + "%N")
                input_file.read_line
            end
            input_file.close
        end

end

This single class covers three concepts. show_console_output demonstrates the different output routines. write_to_file creates a file and writes to it. read_from_file opens the same file and prints its contents.

Writing to a File

create output_file.make_open_write ("notes.txt") creates a PLAIN_TEXT_FILE object and opens the file for writing, truncating it if it already exists. A file object understands the same put_string, put_integer, and put_new_line messages as io - because both STD_FILES and PLAIN_TEXT_FILE inherit them from the same file abstraction. Always close the file when you are done so buffered data is flushed to disk.

Reading from a File

create input_file.make_open_read ("notes.txt") opens the file for reading. The loop shows the Command-Query pattern in action:

  • read_line is the command - it reads the next line into last_string.
  • last_string is the query - it holds the text that was just read (without the newline).
  • exhausted becomes True once a read attempt reaches end-of-file with nothing left.

The from ... until ... loop ... end form is Eiffel’s loop. We prime the loop with one read_line before it starts, print inside the body, then read again at the bottom - a standard read-ahead pattern.

Reading Keyboard Input

Console input follows the same command-then-query rhythm. io.read_line reads a full line into io.last_string; io.read_integer reads a number into io.last_integer.

Create a file named input_demo.e:

note
    description: "Reading input from the keyboard"

class
    INPUT_DEMO

create
    make

feature -- Initialization

    make
            -- Prompt for a name and an age, then respond.
        local
            age: INTEGER
        do
            io.put_string ("What is your name? ")
            io.read_line
            io.put_string ("Hello, " + io.last_string + "!%N")

            io.put_string ("How old are you? ")
            io.read_integer
            age := io.last_integer
            io.put_string ("Next year you will be ")
            io.put_integer (age + 1)
            io.put_new_line
        end

end

Because this program waits for the user to type, it is interactive. Here is a sample session, where the lines after each prompt are what the user typed:

What is your name? Ada
Hello, Ada!
How old are you? 30
Next year you will be 31

Notice again that neither read_line nor read_integer returns the value directly. You call the command, then read io.last_string or io.last_integer. This is Command-Query Separation applied consistently: commands change state, queries report it.

The Configuration File

Eiffel compiles a whole system, not a single file, so the compiler needs an ECF (Eiffel Configuration File) that names the root class and its creation feature. The io_operations.ecf below points the root at IO_OPERATIONS and its make feature, and pulls in the base library for STRING, INTEGER, PLAIN_TEXT_FILE, and the io object.

Create a file named io_operations.ecf:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-22-0" name="io_operations" uuid="00000000-0000-0000-0000-000000000002">
    <target name="io_operations">
        <root class="IO_OPERATIONS" feature="make"/>
        <setting name="console_application" value="true"/>
        <library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
        <cluster name="root_cluster" location="."/>
    </target>
</system>

The <cluster> element with location="." tells the compiler to look in the current directory for source files, so it will find io_operations.e.

Running with Docker

With io_operations.e and io_operations.ecf in the current directory, compile and run the program:

1
2
3
4
5
# Pull the official image
docker pull eiffel/eiffel:latest

# Compile the system and run the resulting executable
docker run --rm -v $(pwd):/app -w /app eiffel/eiffel:latest sh -c 'ec -batch -config io_operations.ecf -c_compile && ./EIFGENs/io_operations/W_code/io_operations'

The compiler generates C from your Eiffel code, compiles it, and places the executable in EIFGENs/io_operations/W_code/. Running it produces the console output, writes notes.txt into your mounted directory, and reads that file back.

Expected Output

=== Console Output ===
A plain string
Count is: 42
Doubled: 84
Tab	separated	values

=== Wrote notes.txt ===
=== Reading notes.txt ===
First line
Second line
Number: 100

After the run, a new file notes.txt appears in your directory containing:

First line
Second line
Number: 100

Key Concepts

  • I/O is object-oriented - there are no global I/O functions. You send messages to the io object (of type STD_FILES) and to PLAIN_TEXT_FILE objects.
  • Command-Query Separation governs input - a read is a command (read_line, read_integer); you retrieve the result from a query afterward (last_string, last_integer). Reads never return the value directly.
  • io is inherited from ANY - every class can use io.put_string, io.put_integer, io.put_new_line, io.read_line, and io.read_integer without any import.
  • Files share the same interface as the console - put_string, put_integer, and put_new_line work identically on io and on a PLAIN_TEXT_FILE, because both inherit from the same file abstraction.
  • Open with an intent - make_open_write creates (and truncates) a file for writing; make_open_read opens it for reading. Always close a file when finished.
  • Read files with a read-ahead loop - prime with one read_line, loop until exhausted, and read again at the bottom so last_string always holds a real line.
  • Escapes use % - %N is newline and %T is tab, not the C-style \n and \t.
  • Convert values with .out - any object’s out query gives a string representation you can concatenate or print.

Running Today

All examples can be run using Docker:

docker pull eiffel/eiffel:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining