Beginner

Hello World in Modula-2

Your first Modula-2 program - the classic Hello World example with Docker setup using GNU Modula-2 (gm2)

Every programming journey starts with Hello World. Let’s write our first Modula-2 program using the GNU Modula-2 compiler (gm2), which is now part of GCC.

The Code

Create a file named hello.mod:

1
2
3
4
5
6
7
8
MODULE Hello;

FROM StrIO IMPORT WriteString, WriteLn;

BEGIN
  WriteString("Hello, World!");
  WriteLn
END Hello.

Understanding the Code

Modula-2 programs are clean, modular, and structured. Let’s break down each part:

  • MODULE Hello; - Declares a module named Hello (the program entry point)
  • FROM StrIO IMPORT WriteString, WriteLn; - Imports specific procedures from the StrIO library
  • BEGIN - Marks the start of the module body (executable statements)
  • WriteString("Hello, World!"); - Outputs the text string
  • WriteLn - Outputs a newline character
  • END Hello. - Closes the module (must match the module name and end with a period)

The Import Statement

Modula-2 uses qualified imports to control the namespace:

1
FROM StrIO IMPORT WriteString, WriteLn;

This imports only WriteString and WriteLn from the StrIO module, making them available directly. You could also write:

1
2
3
4
5
6
IMPORT StrIO;

BEGIN
  StrIO.WriteString("Hello, World!");
  StrIO.WriteLn
END Hello.

This keeps the module prefix, making the code origin explicit.

Module Structure

A Modula-2 program module has this structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MODULE Name;

(* Import declarations *)
FROM Module IMPORT Procedure;

(* Type, constant, variable declarations *)
(* Procedure declarations *)

BEGIN
  (* Module initialization code *)
END Name.

Notice the period after Name - program modules end with . while definition and implementation modules end with just the module name.

Case Sensitivity

Unlike Pascal, Modula-2 is case-sensitive:

  • MODULE is different from module (use uppercase for keywords)
  • Hello is different from hello
  • Convention: Keywords in uppercase, identifiers in MixedCase

Comments

Modula-2 uses Pascal-style comments:

1
2
3
4
5
6
(* This is a single-line comment *)

(*
   This is a
   multi-line comment
*)

Running with Docker

The easiest way to run Modula-2 without installing gm2 locally uses GCC (which includes gm2 since version 13) in a Docker container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Create the source file
cat > hello.mod << 'EOF'
MODULE Hello;

FROM StrIO IMPORT WriteString, WriteLn;

BEGIN
  WriteString("Hello, World!");
  WriteLn
END Hello.
EOF

# Compile and run with gm2
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -static -o hello hello.mod && ./hello'

Understanding the Docker Command

  • gm2 - The GNU Modula-2 compiler (part of GCC)
  • -o hello - Output executable named hello
  • hello.mod - Input source file (.mod = Modula-2 module)
  • ./hello - Run the compiled executable

The gm2 command automatically:

  1. Compiles the source to object code
  2. Links with the Modula-2 runtime library
  3. Produces an executable

Running Locally

If you have GCC 13+ with gm2 installed:

1
2
3
4
5
# Compile and link
gm2 -o hello hello.mod

# Run
./hello

Installing gm2

macOS (Homebrew):

1
2
3
4
5
6
7
# gm2 is part of GCC 13+
brew install gcc

# Verify installation (may be named gm2-13 or similar)
gm2 --version
# or
gm2-13 --version

Ubuntu/Debian (GCC 13+):

1
2
3
4
5
6
7
# Add GCC 13 repository if needed
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update
sudo apt install gcc-13 gm2-13

# Use gm2-13
gm2-13 -o hello hello.mod

Fedora/RHEL:

1
sudo dnf install gcc gm2

From Source:

1
2
# gm2 is included in GCC 13+
# Build from GCC source with --enable-languages=m2

Expected Output

Hello, World!

Clean and simple - exactly what you’d expect!

Alternative Approaches

Modula-2’s standard library offers several ways to output text:

Using Individual Character Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
MODULE Hello;

FROM InOut IMPORT Write, WriteLn;

VAR i: CARDINAL;
    msg: ARRAY [0..12] OF CHAR;

BEGIN
  msg := "Hello, World!";
  FOR i := 0 TO 12 DO
    Write(msg[i])
  END;
  WriteLn
END Hello.

This demonstrates arrays and loops, but WriteString is much simpler.

Using Write Procedures

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
MODULE Hello;

FROM InOut IMPORT WriteString, Write, WriteLn;

BEGIN
  Write('H');
  Write('e');
  Write('l');
  Write('l');
  Write('o');
  WriteString(", World!");
  WriteLn
END Hello.

Write outputs a single character, WriteString outputs a string.

With Constants

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MODULE Hello;

FROM InOut IMPORT WriteString, WriteLn;

CONST
  Message = "Hello, World!";

BEGIN
  WriteString(Message);
  WriteLn
END Hello.

Constants declared between imports and BEGIN.

With Variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
MODULE Hello;

FROM InOut IMPORT WriteString, WriteLn;

VAR
  greeting: ARRAY [0..12] OF CHAR;

BEGIN
  greeting := "Hello, World!";
  WriteString(greeting);
  WriteLn
END Hello.

Variables declared in the VAR section.

The .mod and .def Files

Modula-2 programs use two file types:

.mod (Module Implementation)

Contains:

  • Program modules (like our Hello World)
  • Implementation modules (code for definition modules)
  • Module initialization code

.def (Definition Module)

Contains:

  • Module interfaces (exported types, procedures, constants)
  • Type declarations visible to clients
  • Procedure signatures (no implementation)

Our simple Hello World only needs a .mod file since it’s a standalone program module. As programs grow, you’ll create libraries with separate definition (.def) and implementation (.mod) files.

Program Structure

A minimal Modula-2 program requires:

  1. MODULE declaration - Define the program name
  2. Optional IMPORT clauses - Bring in needed modules
  3. Optional declarations - Constants, types, variables, procedures
  4. BEGIN keyword - Start module body
  5. Statements - Actual program logic
  6. END clause - Close the module with name and period

Example with Declarations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
MODULE Greeting;

FROM InOut IMPORT WriteString, WriteLn;

CONST
  Year = 1815;

VAR
  name: ARRAY [0..20] OF CHAR;

BEGIN
  name := "Ada Lovelace";
  WriteString("Hello from ");
  WriteString(name);
  WriteLn;
  WriteString("Born in ");
  (* Would need WriteInt to print Year *)
  WriteLn
END Greeting.

Notice:

  • Constants declared in CONST section
  • Variables declared in VAR section
  • Strings are character arrays
  • Each section appears before BEGIN

Compilation Process

When you run gm2 -o hello hello.mod, several things happen:

hello.mod → hello.o (Object file)
          → hello (Executable)

Separate Compilation Steps

You can also compile manually:

1
2
3
4
5
6
7
8
# Compile to object
gm2 -c hello.mod

# Link to executable
gm2 -o hello hello.o

# Or let gm2 handle everything
gm2 -o hello hello.mod

With Multiple Modules

For larger programs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Compile all modules
gm2 -c Utils.def
gm2 -c Utils.mod
gm2 -c Main.mod

# Link together
gm2 -o program Main.o Utils.o

# Or let gm2 figure it out
gm2 -o program Main.mod Utils.mod

Common Beginner Mistakes

Missing Period After Module Name

1
2
END Hello   (* ❌ Missing period *)
END Hello.  (* ✅ Correct for program module *)

Wrong Import Syntax

1
2
IMPORT WriteString FROM InOut;  (* ❌ Wrong order *)
FROM InOut IMPORT WriteString;  (* ✅ Correct *)

Mismatched Module Name

1
2
3
4
5
6
7
8
9
MODULE Hello;
BEGIN
  WriteString("Hello, World!")
END Goodbye.  (* ❌ Must match module name *)

MODULE Hello;
BEGIN
  WriteString("Hello, World!")
END Hello.    (* ✅ Correct *)

Forgetting Semicolons

1
2
FROM InOut IMPORT WriteString, WriteLn  (* ❌ Missing semicolon *)
FROM InOut IMPORT WriteString, WriteLn; (* ✅ Correct *)

Case Errors

1
2
3
4
5
module Hello;           (* ❌ Keywords must be uppercase *)
MODULE Hello;           (* ✅ Correct *)

from InOut import ...   (* ❌ Keywords must be uppercase *)
FROM InOut IMPORT ...   (* ✅ Correct *)

Wrong File Extension

Modula-2 requires specific extensions:

  • hello.m2 - ❌ Not standard
  • hello.m - ❌ Not recognized by gm2
  • hello.mod - ✅ Correct for modules
  • hello.def - ✅ Correct for definitions

Compiler Options

Useful gm2 compilation flags:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Verbose output
gm2 -v -o hello hello.mod

# Enable all warnings
gm2 -Wall -o hello hello.mod

# Optimize for speed
gm2 -O2 -o hello hello.mod

# Generate debugging information
gm2 -g -o hello hello.mod

# Specify PIM dialect (default)
gm2 -fpim -o hello hello.mod

# Use ISO standard
gm2 -fiso -o hello hello.mod

# Show search paths
gm2 -fiso --print-search-dirs

Dialect Selection

gm2 supports multiple Modula-2 dialects:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# PIM-2 (Programming in Modula-2, 2nd edition)
gm2 -fpim2 -o hello hello.mod

# PIM-3 (3rd edition - most common)
gm2 -fpim3 -o hello hello.mod

# PIM-4 (4th edition with SYSTEM)
gm2 -fpim4 -o hello hello.mod

# ISO standard
gm2 -fiso -o hello hello.mod

Our Hello World works with all dialects.

Standard Library Modules

Common Modula-2 library modules:

InOut

Input and output for basic types:

  • WriteString() - Output string
  • WriteInt() - Output integer
  • WriteLn - Output newline
  • ReadString() - Input string
  • ReadInt() - Input integer

Storage

Dynamic memory allocation:

  • ALLOCATE() - Allocate memory
  • DEALLOCATE() - Free memory

Strings

String manipulation:

  • Length() - String length
  • Concat() - Concatenate strings
  • Compare() - Compare strings

MathLib0

Mathematical functions:

  • sqrt() - Square root
  • sin(), cos() - Trigonometric
  • exp(), ln() - Exponential/logarithm

Why Modula-2 for Hello World?

Even in this simple example, you see Modula-2’s philosophy:

  1. Explicit Imports - No hidden dependencies or globals
  2. Clear Structure - MODULE declaration, BEGIN/END blocks
  3. Module Name Redundancy - END Hello. catches copy-paste errors
  4. Qualified Access - InOut.WriteString shows exactly where functions come from
  5. Strong Typing - Even strings have explicit types (character arrays)

These features become invaluable in large systems where Modula-2 was designed to shine.

A Bit of History

Modula-2 was created by Niklaus Wirth (1934-2024), who also designed Pascal. The name “Modula” comes from modular programming - the language’s central concept.

In 1978, when Modula-2 emerged, most systems programming was done in assembly or C. Modula-2 proved you could write operating systems, compilers, and device drivers in a high-level language with strong type safety. The entire Lilith workstation OS was written in Modula-2.

Today, Modula-2’s module system lives on in Go (designed by Wirth’s student), Rust, and even modern JavaScript/TypeScript modules.

Performance Note

Modula-2 compiles to efficient machine code. The gm2 compiler uses GCC’s backend, producing optimized executables comparable to C. The strong typing and safety checks happen at compile time, not runtime - Modula-2 programs run at full speed.

Standard Output Libraries

Different Modula-2 implementations have different I/O modules:

PIM Style (our example)

1
FROM InOut IMPORT WriteString, WriteLn;

ISO Style

1
2
FROM STextIO IMPORT WriteString;
FROM SWholeIO IMPORT WriteInt;

Terminal Module

1
FROM Terminal IMPORT WriteString;

gm2 supports all these - use -fpim or -fiso to select the dialect.

Next Steps

Now that you’ve written your first Modula-2 program, you can explore:

  • Variables and Data Types - Learn about Modula-2’s strong type system
  • Module System - Create reusable definition and implementation modules
  • Pointers and Dynamic Memory - Work with dynamic data structures
  • Procedures and Functions - Build modular, reusable code

Key Takeaways

  1. Modula-2 uses explicit imports via FROM module IMPORT clauses
  2. Programs are modules with MODULE/BEGIN/END structure
  3. Module names must match in the END clause with a period
  4. Case sensitive - keywords in uppercase, identifiers in MixedCase
  5. gm2 is part of GCC and available in modern GCC distributions
  6. .mod files contain program and implementation modules
  7. .def files contain definition modules (interfaces)
  8. Comments use (* ... *) delimiters

Troubleshooting

“gm2: command not found”

Your GCC version is older than 13. Either:

  • Install GCC 13+ from your package manager
  • Build GCC from source with --enable-languages=m2
  • Use Docker with gcc:latest image

“cannot find module ‘InOut’”

Specify the PIM dialect:

1
gm2 -fpim -o hello hello.mod

“syntax error”

Check:

  • Keywords are UPPERCASE (MODULE, not module)
  • Module name matches in END Hello.
  • Period after module name in END clause
  • Semicolons after IMPORT and before BEGIN

Strange compilation errors

Try explicitly setting the dialect:

1
2
gm2 -fpim3 -o hello hello.mod  # For PIM-3
gm2 -fiso -o hello hello.mod   # For ISO

Running Today

All examples can be run using Docker:

docker pull codearchaeology/modula-2:latest
Last updated: