Intermediate

I/O Operations in Icon

Learn console and file I/O in Icon - reading input, writing files, formatted output, and how failure drives the end of a file

Input and output are where Icon’s goal-directed evaluation stops being an abstract curiosity and starts doing real work. In most languages you check for end-of-file with a special return value or an exception. In Icon, read() simply fails when there is nothing left to read - and because failure is a first-class part of expression evaluation, that single fact is enough to drive an entire read loop.

The same idea runs through the whole I/O system. open() fails (rather than throwing) when it cannot open a file, so you guard it with the | (alternation) operator. A file value is itself a generator that produces its lines, so !f yields each line in turn. This tutorial covers console output beyond the write() you saw in Hello World, reading from standard input, writing and reading files, and formatting output into aligned columns.

Because Icon converts between strings and numbers automatically, you rarely parse input by hand - a line read as a string participates in arithmetic directly. We will lean on that throughout.

Console Output in Detail

You already met write(). Here are the variations worth knowing: writes() omits the trailing newline, write() concatenates all of its arguments, and &errout is the standard error stream.

Create a file named io_output.icn:

procedure main()
    # write adds a newline; writes does not
    writes("No newline here... ")
    write("but this line ends.")

    # Multiple arguments are concatenated with no separator
    write("Sum: ", 2 + 3, " and text together")

    # Numbers are converted to strings automatically
    count := 7
    write("Count is " || count)

    # Direct output to the standard error stream
    write(&errout, "This goes to stderr")
end

Passing &errout as the first argument to write() redirects that line to standard error instead of standard output - useful for diagnostics that should not pollute a program’s real output.

Reading Standard Input

read() with no argument reads a single line from standard input (&input), returning it as a string without the newline. When the input is exhausted, read() fails, so the assignment name := read() simply does not happen and the variable stays &null. The /name test succeeds only when name is null, giving us a clean “no input” guard.

Create a file named io_input.icn:

procedure main()
    writes("Enter your name: ")
    name := read()
    if /name then
        stop("No input provided")
    write("Hello, ", name, "!")

    writes("Enter a number: ")
    n := read()
    # n is a string, but * converts it to a number automatically
    write("Double is ", 2 * n)
end

Notice we never explicitly convert n from a string to an integer. Because * is an arithmetic operator, Icon coerces the string "21" into the integer 21 on demand. stop() writes its arguments to &errout and terminates the program.

Writing to a File

open(filename, mode) returns a file value or fails if it cannot open the file. The idiom open(...) | stop(...) uses alternation: if the left side fails, the right side runs. Mode "w" opens for writing (truncating any existing file), "a" appends, and "r" reads.

Create a file named io_write_file.icn:

procedure main()
    out := open("fruits.txt", "w") | stop("Cannot open fruits.txt for writing")

    # every drives the generator !list, writing each element on its own line
    every write(out, !["apple", "banana", "cherry"])
    write(out, "Total: 3 fruits")

    close(out)
    write("Wrote fruits.txt")
end

When the first argument to write() is a file, output goes to that file. Always close() a file you opened for writing so buffered data is flushed to disk.

Reading a File

Reading pairs naturally with failure. read(in) returns each line until end-of-file, then fails - which ends the while loop with no explicit EOF check. Run this after io_write_file so that fruits.txt exists.

Create a file named io_read_file.icn:

procedure main()
    in := open("fruits.txt", "r") | stop("Cannot open fruits.txt for reading")

    count := 0
    while line := read(in) do {
        count +:= 1
        write(count, ": ", line)
    }
    close(in)
    write("Read ", count, " lines")
end

The +:= operator is augmented assignment (like += elsewhere). A file is also a generator of its lines, so the whole loop could be written as every write(!in) when you do not need the running count - !in produces each line in turn.

Formatted Output

Icon has no printf built into the core language, but the string functions left(), right(), and center() pad a string into a fixed-width field, which is all you need for aligned columns.

Create a file named io_format.icn:

procedure main()
    # left() left-justifies, right() right-justifies within a field width
    write(left("Name", 10), right("Score", 8))
    write(left("Alice", 10), right("95", 8))
    write(left("Bob", 10), right("7", 8))

    # center() centers text within the field
    write("[", center("title", 11), "]")

    # Right-justify a value in a fixed-width column
    write("Value:", right("3.14", 8))
end

Each function takes a string and a field width, padding with spaces so that columns line up regardless of how long the individual values are.

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Pull the official image
docker pull codearchaeology/icon:latest

# Console output
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont io_output.icn && ./io_output"

# Console input - pipe two lines in and keep stdin open with -i
printf 'Ada\n21\n' | docker run --rm -i -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont io_input.icn && ./io_input"

# Write a file, then read it back
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont io_write_file.icn && ./io_write_file"
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont io_read_file.icn && ./io_read_file"

# Formatted output
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont io_format.icn && ./io_format"

Expected Output

io_output (the final line is written to standard error):

No newline here... but this line ends.
Sum: 5 and text together
Count is 7
This goes to stderr

io_input (with Ada and 21 piped in; the prompts and replies share a line because writes omits the newline):

Enter your name: Hello, Ada!
Enter a number: Double is 42

io_write_file:

Wrote fruits.txt

…and the resulting fruits.txt contains:

apple
banana
cherry
Total: 3 fruits

io_read_file:

1: apple
2: banana
3: cherry
4: Total: 3 fruits
Read 4 lines

io_format:

Name         Score
Alice           95
Bob              7
[   title   ]
Value:    3.14

Key Concepts

  • Failure signals end-of-file - read() fails when input is exhausted, so while line := read(f) needs no explicit EOF test
  • open() fails, it does not throw - guard it with alternation: open(name, mode) | stop("message")
  • Files are generators - !f produces each line of a file, so every write(!f) echoes an entire file
  • Redirect by first argument - write(f, ...) writes to file f; write(&errout, ...) writes to standard error
  • writes() vs write() - writes() omits the trailing newline, handy for prompts before read()
  • Automatic conversion - a line read as a string participates in arithmetic directly (2 * n), no manual parsing needed
  • Always close() written files - closing flushes buffered output so it actually reaches disk
  • left(), right(), center() provide printf-style column alignment without a format language

Running Today

All examples can be run using Docker:

docker pull codearchaeology/icon:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining