Intermediate

I/O Operations in ALGOL 68

Console output, reading input, formatted output with printf, and real file I/O through channels in ALGOL 68 - all runnable with Algol 68 Genie and Docker

Introduction

Where ALGOL 60 deliberately left input and output undefined, ALGOL 68 went the opposite way and built a complete, carefully specified transput model directly into the language (the ALGOL 68 Report uses the word transput for the combined idea of transput = transfer + in/output). This model is unusually rich for a language of its era: it defines files, channels that connect files to devices, automatic formatting of every mode, and a printf-style format facility - all standardised rather than left to each implementation.

In the Hello World tutorial you used print to send text to the screen and saw printf in passing. This tutorial uses the full toolkit. You will print values of every basic mode with print, turn numbers into neat strings with the whole and fixed formatting functions, read values typed at the terminal with read, lay out reports with printf and its $...$ format patterns, and finally write and read a genuine named file through ALGOL 68’s channel system - something ALGOL 60 never offered.

Because ALGOL 68 is an imperative, procedural language, transput reads as a clear sequence of procedure calls. What makes it distinctive is the orthogonality: the same print, put, and get procedures work uniformly whether the value is an INT, a REAL, a BOOL, a CHAR, or a STRING, and whether the destination is the screen or a file.

Console Output for Every Mode

print takes a single value, or a structure of values written in parentheses, and sends each to standard output. The catch is that printing a number with bare print uses a default field width and a leading sign, which is rarely what you want for tidy output. The fix is the formatting functions whole(value, width) for integers and fixed(value, width, after) for reals: with a width of 0 they return the shortest faithful string, with no padding and no + sign.

Create a file named io_output.a68:

BEGIN
  # print sends any mode to standard output; whole and fixed turn      #
  # numbers into clean, padding-free strings (width 0 = shortest form) #
  INT    count  = 42;
  REAL   pi     = 3.14159;
  BOOL   active = TRUE;
  CHAR   grade  = "A";

  print(("ALGOL 68 transput", new line));
  print(("Count : ", whole(count, 0), new line));
  print(("Pi    : ", fixed(pi, 0, 4), new line));

  # A BOOL prints as T or F by default, so map it to readable text #
  print(("Active: ", (active | "yes" | "no"), new line));
  print(("Grade : ", grade, new line))
END

The conditional expression (active | "yes" | "no") is ALGOL 68’s compact if-then-else: it yields "yes" when active is TRUE. Note how fixed(pi, 0, 4) rounds to exactly four decimal places, giving you the decimal control that ALGOL 60’s shortest-form output could never provide.

Reading Input

Input mirrors output. read takes a structure of variables (not values) and fills each one from standard input. Reading an INT skips leading spaces and newlines, then consumes digits; reading a STRING consumes the rest of the current line; and the new line item inside a read list advances past the line terminator so the next read starts cleanly.

Create a file named io_input.a68:

BEGIN
  # read fills variables from standard input. STRING reads to end of   #
  # line; new line in the list then steps past the line terminator     #
  print(("Enter your name: "));
  STRING name;
  read((name, new line));

  print(("Enter your age:  "));
  INT age;
  read((age, new line));

  print(("Hello, ", name, "!", new line));
  print(("Next year you will be ", whole(age + 1, 0), new line))
END

Because the program reads from standard input, you supply the values by piping them in (shown in the Docker section). Piped input is not echoed back, so each prompt sits on the same line as the response the program prints next.

Formatted Output with printf

For report-style output, printf pairs a format - the $...$ pattern - with the values to fill it. Format patterns use single-letter items: g inserts a value in its general form, l emits a newline, and literal text in quotes is printed verbatim. A clean, predictable approach is to pre-format numbers with whole and fixed, then drop the resulting strings into g placeholders.

Create a file named io_printf.a68:

BEGIN
  # printf marries a $...$ format with values. g inserts a value,      #
  # l is a newline. Pre-format numbers with whole/fixed for control    #
  INT  items = 7;
  REAL price = 19.99;
  REAL total = items * price;

  printf(($g" units at $"g" each"l$,
          whole(items, 0), fixed(price, 0, 2)));
  printf(($"Total cost: $"gl$,
          fixed(total, 0, 2)));

  # printf can also take several values against repeated format items  #
  printf(($"Line "g": "gl$, whole(1, 0), "first"));
  printf(($"Line "g": "gl$, whole(2, 0), "second"))
END

Multiplying the INT items by the REAL price widens the integer to a real automatically - one of ALGOL 68’s coercions - so total is a REAL and fixed can round it to two decimal places.

File I/O Through Channels

This is where ALGOL 68 pulls decisively ahead of ALGOL 60. A FILE is a variable that represents an open stream, and you attach it to a named disk file through a channel that says what kind of access you want. Creating a brand-new file for writing uses establish with the stand out channel; opening an existing file for reading uses open with the stand in channel. (open expects the file to already exist, which is why writing goes through establish.) Both return an INT that is 0 on success. Once attached, the same put and get procedures mirror print and read, but aimed at the file. Always close the file when done.

The program below writes a small report to a real file and then reopens and reads it back, so a single run is fully self-contained.

Create a file named io_files.a68:

BEGIN
  # ----- Write a report to a named file ----- #
  # establish creates a new file for writing; open (used below) #
  # only attaches to a file that already exists                 #
  FILE out file;
  IF establish(out file, "report.txt", stand out channel) /= 0 THEN
    print(("Cannot open report.txt for writing", new line));
    stop
  FI;
  put(out file, ("Sales Report", new line));
  put(out file, ("Widgets sold: ", whole(150, 0), new line));
  put(out file, ("Revenue: $", fixed(2998.50, 0, 2), new line));
  close(out file);
  print(("Wrote 3 lines to report.txt", new line));

  # ----- Reopen the same file and read it back ----- #
  FILE in file;
  IF open(in file, "report.txt", stand in channel) /= 0 THEN
    print(("Cannot open report.txt for reading", new line));
    stop
  FI;
  STRING line;
  TO 3 DO
    get(in file, (line, new line));
    print(("> ", line, new line))
  OD;
  close(in file)
END

get(in file, (line, new line)) reads one line into the flexible STRING line, then steps past the line break, and the TO 3 DO ... OD loop repeats that three times - once per line we wrote. The result file, report.txt, is left on disk in your working directory because the -v $(pwd):/app mount maps the container’s /app back to your host folder.

Running with Docker

Use the same Algol 68 Genie image as the rest of this series. Pull it once:

1
docker pull codearchaeology/algol68:latest

Output-only programs run exactly like Hello World:

1
2
3
4
5
6
7
8
# Console output for every mode
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest io_output.a68

# Formatted output with printf
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest io_printf.a68

# File I/O: writes report.txt, then reads it back
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest io_files.a68

The input program needs the -i flag so Docker keeps standard input connected. Pipe two lines in - a name then an age:

1
2
# Reading input: pipe a name and an age into standard input
printf 'Ada\n35\n' | docker run --rm -i -v $(pwd):/app -w /app codearchaeology/algol68:latest io_input.a68

Expected Output

io_output.a68:

ALGOL 68 transput
Count : 42
Pi    : 3.1416
Active: yes
Grade : A

io_printf.a68:

7 units at $19.99 each
Total cost: $139.93
Line 1: first
Line 2: second

io_input.a68, given the piped input Ada then 35:

Enter your name: Hello, Ada!
Enter your age:  Next year you will be 36

io_files.a68:

Wrote 3 lines to report.txt
> Sales Report
> Widgets sold: 150
> Revenue: $2998.50

After the file example runs, report.txt remains in your working directory containing the three written lines.

Key Concepts

  • Transput is built in. Unlike ALGOL 60, ALGOL 68 standardised input and output - called transput - directly in the language, so the same code behaves consistently across conforming implementations like Algol 68 Genie.
  • One set of verbs, every mode. print and read handle INT, REAL, BOOL, CHAR, and STRING uniformly; you pass a structure of values in parentheses, e.g. print(("x = ", whole(x, 0), new line)).
  • Format numbers with whole and fixed. whole(n, 0) gives an integer as its shortest string and fixed(x, 0, d) gives a real rounded to d decimals - both with no padding or + sign at width 0.
  • read takes variables, not values. read((name, new line)) fills name and then steps past the line terminator; numeric reads skip leading whitespace automatically.
  • printf joins a format to values. The $...$ pattern uses g to insert a value and l for a newline; pre-formatting numbers with whole/fixed keeps output predictable.
  • Files go through channels. establish(file, "name", stand out channel) creates a new file for writing and open(file, "name", stand in channel) attaches to an existing one for reading - both return 0 on success; then put/get mirror print/read, and close finishes - genuine named-file I/O that ALGOL 60 lacked entirely.
  • Coercion widens automatically. Multiplying an INT by a REAL promotes the integer, so arithmetic mixing the two modes yields a REAL without an explicit cast.
  • Conditional expressions are values. (flag | "yes" | "no") is ALGOL 68’s compact if-then-else and can be dropped straight into a print list.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/algol68:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining