ZetCode

Porting C to Algol 68

last modified November 14, 2026

C and Algol 68 solve the same problems with different defaults. C indexes arrays from zero, passes arrays as addresses, and asks the programmer to manage memory; Algol 68 indexes rows from one, passes a reference explicitly, and reclaims unused cells with a garbage collector. The control flow is close enough that a C function can usually be rewritten statement by statement once the declarations are understood.

This article pairs ten small C programs with their Algol 68 equivalents. Each pair prints the same output, so a difference in the transcript would mean a mistake in the translation, and the last pair shows the one place where the languages deliberately disagree.

Printing a line

A C program starts at main and calls printf. An Algol 68 program is a unit, often a BEGIN ... END block, and its output goes through print, which takes one collateral clause holding the values to write.

hello.c
#include <stdio.h>

int main(void)
{
    printf("Hello, world\n");
    return 0;
}
hello.a68
BEGIN
  print(("Hello, world", newline))
END

The newline is a value in Algol 68, not an escape inside the string. newline is a procedure that writes a line break to the file it is given, and in a print call the current output file is supplied automatically. There is no return 0, because the end of the program is the end of the block.

$ gcc -o hello hello.c && ./hello
Hello, world
$ a68g hello.a68
Hello, world

Loops

The C for loop has three parts written by the programmer. Algol 68 has a loop clause with a range, and the range TO 100 means from 1 to 100 inclusive, because the lower bound defaults to 1.

sum.c
#include <stdio.h>

int main(void)
{
    int sum = 0;
    for (int i = 1; i <= 100; i++)
        sum += i;
    printf("%d\n", sum);
    return 0;
}
sum.a68
BEGIN
  INT sum := 0;
  FOR i TO 100 DO
    sum +:= i
  OD;
  print((whole(sum, 0), newline))
END

+:= is the compound assignment and is the counterpart of +=. The loop variable i is declared by the loop itself and is not visible after OD. The result is printed with whole(sum, 0) rather than print(sum), because print pads an integer to an eleven-character field, while whole with width 0 writes the digits alone, like %d.

$ gcc -o sum sum.c && ./sum
5050
$ a68g sum.a68
5050

Arrays

A C array starts at index 0 and its length is written by the programmer. An Algol 68 row declares its bounds, which may start anywhere, and the bounds are recoverable at run time with LWB and UPB. The declaration []INT a = (3, 1, 4, 1, 5) takes its bounds from the number of values.

max.c
#include <stdio.h>

int main(void)
{
    int a[5] = {3, 1, 4, 1, 5};
    int max = a[0];
    for (int i = 1; i < 5; i++)
        if (a[i] > max)
            max = a[i];
    printf("%d\n", max);
    return 0;
}
max.a68
BEGIN
  []INT a = (3, 1, 4, 1, 5);
  INT max := a[LWB a];
  FOR i FROM LWB a + 1 TO UPB a DO
    IF a[i] > max THEN max := a[i] FI
  OD;
  print((whole(max, 0), newline))
END

The first element is a[LWB a] and the loop starts one past it. The conditional is a clause with a closing FI rather than a statement with braces, and the assignment is written the same way as in C. Because the bounds come from the row, the loop is correct for a row of any length.

$ gcc -o max max.c && ./max
5
$ a68g max.a68
5

Pointers and references

A C function that changes its arguments receives their addresses and dereferences them. An Algol 68 procedure declares the parameters as REF modes, and the caller passes the variables without an address operator. Inside the procedure the reference is used like a value, because dereferencing is implicit.

swap.c
#include <stdio.h>

void swap(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

int main(void)
{
    int x = 1, y = 2;
    swap(&x, &y);
    printf("%d %d\n", x, y);
    return 0;
}
swap.a68
PROC swap = (REF INT a, REF INT b) VOID:
(
  INT t = a;
  a := b;
  b := t
);

BEGIN
  INT x := 1;
  INT y := 2;
  swap(x, y);
  print((whole(x, 0), " ", whole(y, 0), newline))
END

INT t = a copies the value that a refers to, and a := b stores the value of b into the cell that a refers to. The caller's &x disappears: the declaration REF INT a in the procedure header is the address type, and the argument x is checked against it.

$ gcc -o swap swap.c && ./swap
2 1
$ a68g swap.a68
2 1

Structures

A C structure is declared with struct and a typedef, and its fields are read with a dot. Algol 68 declares the mode with MODE and STRUCT, builds a value from a collateral clause, and reads a field with OF.

person.c
#include <stdio.h>

typedef struct {
    const char *name;
    int age;
} Person;

int main(void)
{
    Person p = {"Ada", 36};
    printf("%s %d\n", p.name, p.age);
    return 0;
}
person.a68
MODE PERSON = STRUCT (STRING name, INT age);

BEGIN
  PERSON p = ("Ada", 36);
  print((name OF p, " ", whole(age OF p, 0), newline))
END

MODE PERSON is both the declaration and the typedef, so no second name is needed. The fields keep their modes, and the value is built position by position in the order of the declaration. A string in a structure is a STRING, not a fixed array of characters, so it can be any length.

$ gcc -o person person.c && ./person
Ada 36
$ a68g person.a68
Ada 36

Switch and CASE

A C switch selects a label and falls through unless the programmer writes break. The Algol 68 CASE clause selects one unit by position, never falls through, and has an OUT part for the values that no unit covers.

describe.c
#include <stdio.h>

static void describe(int n)
{
    switch (n) {
    case 1: puts("one"); break;
    case 2: puts("two"); break;
    default: puts("many"); break;
    }
}

int main(void)
{
    describe(1);
    describe(2);
    describe(7);
    return 0;
}
describe.a68
PROC describe = (INT n) VOID:
  CASE n IN
    print(("one", newline)),
    print(("two", newline))
  OUT
    print(("many", newline))
  ESAC;

BEGIN
  describe(1);
  describe(2);
  describe(7)
END

The units are separated by commas and the value chooses one of them, counting from one: n = 1 runs the first unit and n = 2 the second. Every other value, including 0 and the negative numbers, runs the OUT unit. Nothing falls through, so the break statements have no counterpart.

$ gcc -o describe describe.c && ./describe
one
two
many
$ a68g describe.a68
one
two
many

Loops that test at the end

C has a loop that runs its body once before the first test. Algol 68 has no such clause, so the test moves into a flag that starts as true and is reassigned at the end of the body. The loop then reads like the C do ... while even though it is written with WHILE.

digits.c
#include <stdio.h>

int main(void)
{
    int n = 90210, digits = 0;
    do {
        digits++;
        n /= 10;
    } while (n > 0);
    printf("%d\n", digits);
    return 0;
}
digits.a68
BEGIN
  INT n := 90210;
  INT digits := 0;
  BOOL more := TRUE;
  WHILE more DO
    digits +:= 1;
    n := n OVER 10;
    more := n > 0
  OD;
  print((whole(digits, 0), newline))
END

The flag is assigned, not tested with a condition written after the body, so the two parts of the C loop appear in a different order. n /= 10 becomes n := n OVER 10, because Algol 68 spells integer division as an operator of its own. The translation is mechanical once the flag is introduced.

$ gcc -o digits digits.c && ./digits
5
$ a68g digits.a68
5

Strings and characters

A C string is an array of characters ending at a zero byte, and its length comes from strlen. An Algol 68 STRING is a flexible row of characters that carries its own bounds, and a character is a value of mode CHAR whose code is obtained with ABS.

text.c
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char *s = "hello";
    printf("%zu\n", strlen(s));
    printf("%c\n", 'a' + 2);
    return 0;
}
text.a68
BEGIN
  STRING s = "hello";
  print((whole(UPB s - LWB s + 1, 0), newline));
  print((REPR (ABS "a" + 2), newline))
END

UPB s - LWB s + 1 is the number of characters, which is 5 for this literal. Character arithmetic goes through the integer code: ABS "a" is 97, the sum is 99, and REPR turns the code back into the character c. There is no zero terminator, so a loop over a string runs from LWB to UPB and stops there.

$ gcc -o text text.c && ./text
5
c
$ a68g text.a68
5
c

Heap allocation

C asks for memory with malloc and returns it with free. Algol 68 allocates a cell with HEAP and never frees it by hand; the garbage collector reclaims it when no reference points to it. A reference is the only way to name the cell.

heap.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *p = malloc(sizeof *p);
    *p = 42;
    printf("%d\n", *p);
    free(p);
    return 0;
}
heap.a68
BEGIN
  REF INT p = HEAP INT := 42;
  print((whole(p, 0), newline))
END

HEAP INT is the allocation and := 42 stores the initial value into the new cell, so the two C statements collapse into one declaration. whole(p, 0) dereferences the reference for the conversion, and the cell is collected after the block ends. A reference that is never assigned stays uninitialised and is checked at run time like any other uninitialised value.

$ gcc -o heap heap.c && ./heap
42
$ a68g heap.a68
42

Division and modulo

The last pair does not match, and the difference is in the language rather than in the translation. C truncates integer division toward zero, while Algol 68 rounds toward negative infinity, and the remainder is defined from the division. The two languages therefore disagree whenever a negative operand is involved.

div.c
#include <stdio.h>

int main(void)
{
    printf("%d\n", -7 / 2);
    printf("%d\n", -7 % 2);
    printf("%d\n", 7 / -2);
    printf("%d\n", 7 % -2);
    return 0;
}
div.a68
BEGIN
  print((whole(-7 OVER 2, 0), newline));
  print((whole(-7 MOD 2, 0), newline));
  print((whole(7 OVER -2, 0), newline));
  print((whole(7 MOD -2, 0), newline))
END

The quotients agree, but the remainders do not: -7 MOD 2 is 1 in Algol 68 and -7 % 2 is -1 in C. The relation a = (a OVER b) * b + a MOD b holds in both languages, and it is the identity to check when a ported loop depends on the sign of a remainder.

$ gcc -o div div.c && ./div
-3
-1
-3
1
$ a68g div.a68
-3
1
-3
1

What has no direct equivalent

A few C habits do not survive the crossing. There is no ++ or --, so a counter is incremented with +:= 1. There is no address arithmetic: a reference points at one value, and a row is passed as a row with its bounds. There is no sizeof in the source, and no cast syntax, because coercions are applied by the compiler and the programmer writes the mode only in a declaration.

Declarations are also stricter. Every variable has a mode, and a variable declared with := is initialised at that point; a value read before it is assigned is a runtime error rather than undefined behaviour. The programmer gains a checked program and loses the ability to write a declaration that depends on the layout of a structure.

The variables article explains references and the heap in more detail, rows covers bounds and slicing, and structures covers the field selection that replaces the dot.

Source

Revised Report on the Algorithmic Language Algol 68

The Algol 68 Genie project

In this article we translated a greeting, a counting loop, an array scan, a swap through references, a structure, a switch, a loop that tests at the end, string and character code, heap allocation, and the division and modulo pair, and noted the one case where the two languages give different answers.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Algol 68 tutorials.