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.
#include <stdio.h>
int main(void)
{
printf("Hello, world\n");
return 0;
}
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.
#include <stdio.h>
int main(void)
{
int sum = 0;
for (int i = 1; i <= 100; i++)
sum += i;
printf("%d\n", sum);
return 0;
}
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.
#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;
}
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.
#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;
}
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.
#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;
}
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.
#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;
}
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.
#include <stdio.h>
int main(void)
{
int n = 90210, digits = 0;
do {
digits++;
n /= 10;
} while (n > 0);
printf("%d\n", digits);
return 0;
}
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.
#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;
}
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.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = malloc(sizeof *p);
*p = 42;
printf("%d\n", *p);
free(p);
return 0;
}
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.
#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;
}
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
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
List all Algol 68 tutorials.