I/O Operations in Modula-2
Read and write console and file data in Modula-2 with gm2 using StrIO, NumberIO, StdIO, and the FIO file module - formatted output, input, and file handling.
Input and output are where a program meets the outside world. As an imperative, procedural language, Modula-2 treats I/O as a set of library procedures rather than built-in statements — you import exactly the procedures you need and call them in sequence. This keeps the core language small while still giving you full control over terminals and files.
Modula-2 has no single “print” keyword. Instead, the standard library splits I/O across focused modules, each with a clear responsibility. In the GNU Modula-2 (gm2) base libraries used throughout this series, StrIO handles strings, NumberIO handles integers and cardinals, StdIO handles single characters, and FIO provides low-level file access with explicit File handles. Because imports are qualified and explicit, any reader of your code can see precisely which I/O operations a module performs.
In this tutorial you’ll go beyond the single WriteString call from Hello World. You’ll produce formatted, aligned console output, read values typed at the keyboard, and write and read text files with FIO. Along the way you’ll see how Modula-2’s strong typing shapes I/O: cardinals and integers have distinct output procedures, strings are character arrays, and file handles are opaque values you pass between calls.
Formatted Console Output
StrIO.WriteString prints text and NumberIO.WriteInt / NumberIO.WriteCard print numbers. The numeric procedures take a second argument — a field width — that right-justifies the value in a column, which makes building aligned tables straightforward. StdIO.Write emits a single character, useful for drawing or character-by-character output.
Create a file named formatted_output.mod:
| |
The second argument to WriteInt and WriteCard is the minimum field width. WriteInt(5, 6) pads 5 with five leading spaces; WriteInt(-100, 6) needs four characters for -100, so it gets two leading spaces. Passing 0 (as in WriteCard(65535, 0)) means “no padding” — print the digits and nothing more.
Reading Console Input
Reading input mirrors output: StrIO.ReadString reads a line of text into a character array, and NumberIO.ReadCard reads an unsigned number. ReadString reads up to the end of the line and consumes the trailing newline, so a following ReadCard starts cleanly on the next value.
Create a file named console_input.mod:
| |
Notice that name is declared as ARRAY [0..49] OF CHAR — Modula-2 strings are fixed-size character arrays, so you choose the capacity up front. ReadString stops at the newline (or when the array is full) and NUL-terminates the result, so WriteString(name) prints exactly what was typed.
Writing and Reading Files
For files, the gm2 base library provides the FIO module. Because FIO exports procedures named WriteString and ReadString that clash with the string procedures in StrIO, the cleanest approach is a qualified import — IMPORT FIO; — so every file call is written as FIO.OpenToWrite, FIO.WriteString, and so on. This makes it obvious which calls touch the terminal and which touch a file.
A file is opened with FIO.OpenToWrite or FIO.OpenToRead, which return a FIO.File handle. You pass that handle to every read or write, then release it with FIO.Close. FIO.WriteLine writes a newline to the file.
Create a file named file_io.mod:
| |
FIO.ReadString reads one line at a time into the line array, discarding the newline. Here we read exactly the three lines we wrote. For files of unknown length, FIO also provides FIO.EOF(f), which returns TRUE once you read past the end — the natural condition for a WHILE NOT FIO.EOF(f) DO ... END loop. You can check that a file opened successfully with FIO.IsNoError(f) before reading or writing.
Running with Docker
| |
The -i flag on the input example keeps STDIN open so the piped text reaches the running program. The volume mount (-v $(pwd):/app) means report.txt written by file_io appears in your current directory after the container exits.
Expected Output
formatted_output:
Formatted output in Modula-2
Right-justified integers:
5
42
-100
Cardinal value: 65535
Squares table:
1 -> 1
2 -> 4
3 -> 9
4 -> 16
5 -> 25
Row of stars: **********
file_io:
Wrote 3 lines to report.txt
Contents of report.txt:
Modula-2 was created in 1978.
It introduced true modules.
gm2 is part of GCC.
console_input (with Ada and 30 piped in — the prompts print before each read, so they appear consecutively):
Enter your name: Enter your age:
Hello, Ada!
Next year you will be 31
Key Concepts
- I/O is library procedures, not keywords — Modula-2 keeps the core language small; you import
StrIO,NumberIO,StdIO, andFIOprocedures explicitly for the I/O you need. - Type-specific output — cardinals (
WriteCard) and integers (WriteInt) have separate procedures, reflecting Modula-2’s strong distinction between signed and unsigned numbers. - Field widths give alignment for free — the second argument to
WriteInt/WriteCardright-justifies values in a column; pass0for no padding. - Strings are fixed-size character arrays — declare
ARRAY [0..N] OF CHARwith enough capacity;ReadStringNUL-terminates the result andWriteStringprints up to that terminator. - Files use explicit
FIO.Filehandles — open withFIO.OpenToWrite/FIO.OpenToRead, pass the handle to every operation, and alwaysFIO.Closewhen done. - Qualified imports avoid name clashes — importing
FIOunqualified (IMPORT FIO;) keeps itsWriteString/ReadStringdistinct from theStrIOversions and makes file access visible at every call site. FIO.EOFandFIO.IsNoErrorhandle the edges — loop withWHILE NOT FIO.EOF(f)for files of unknown length, and checkFIO.IsNoError(f)after opening to detect failures.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/modula-2:latest
Comments
Loading comments...
Leave a Comment