ZetCode

Algol 68 string processing

last modified November 13, 2026

A string is a row of characters, so the loops and slices of the rows article apply to text as well. What changes is the unit of work: a program that counts words, cuts a line into fields, or removes spaces thinks in runs of characters rather than in single ones.

This article counts words, splits a line into a row of words, trims a line, tests for palindromes, and reads comma-separated fields.

Counting words

A word is a run of characters that are not spaces. Counting words therefore means counting the places where such a run begins, which is a transition from a space to a non-space. The procedure is space accepts the space, the tab, and the two line breaks.

wordcount.a68
PROC is space = (CHAR c) BOOL:
  (c = " " ORF c = REPR 9) ORF (c = REPR 10 ORF c = REPR 13);

PROC word count = (STRING s) INT:
(
  INT n := 0;
  BOOL inside := FALSE;
  FOR i TO UPB s DO
    IF is space(s[i]) THEN
      inside := FALSE
    ELIF NOT inside THEN
      n +:= 1;
      inside := TRUE
    FI
  OD;
  n
);

BEGIN
  [1:3] STRING lines := (
    "the quick brown fox",
    "  spaced   out  ",
    "");
  FOR i TO UPB lines DO
    print((whole(word count(lines[i]), 0), newline))
  OD
END

inside remembers whether the previous character belonged to a word, so a run of several spaces counts once. The loop runs over the upper bound of the string, and an empty string has upper bound 0, so the loop body never runs and the answer is 0 without a special case.

The three lines hold 4, 2, and 0 words. Leading, trailing, and repeated spaces do not create empty words, because only the first character of a run starts a new one.

$ a68g wordcount.a68
4
2
0

Splitting into words

Splitting turns a line into a row of words. The elements of a row must share one mode, and words have different lengths, so the row holds references to strings on the heap: MODE WORD = REF STRING. Each element points at a copy of one word.

split.a68
MODE WORD = REF STRING;

PROC is space = (CHAR c) BOOL:
  (c = " " ORF c = REPR 9) ORF (c = REPR 10 ORF c = REPR 13);

PROC add word = (REF FLEX [] WORD ws, STRING w) VOID:
(
  INT n = UPB ws;
  FLEX [1:n + 1] WORD bigger;
  FOR i TO n DO
    bigger[i] := ws[i]
  OD;
  bigger[n + 1] := HEAP STRING := w;
  ws := bigger
);

PROC split = (STRING s) FLEX [] WORD:
(
  FLEX [1:0] WORD result;
  INT i := 1;
  WHILE i <= UPB s DO
    IF is space(s[i]) THEN
      i +:= 1
    ELSE
      INT start := i;
      WHILE i <= UPB s ANDF NOT is space(s[i]) DO
        i +:= 1
      OD;
      add word(result, s[start:i - 1])
    FI
  OD;
  result
);

BEGIN
  FLEX [1:0] WORD words := split("  one two   three  ");
  print((whole(UPB words, 0), " words", newline));
  FOR i TO UPB words DO
    print((whole(i, -2), ": ", words[i], " (", whole(UPB words[i], 0),
           " letters)", newline))
  OD
END

add word grows the flexible row by allocating one that is longer, copying the old elements, and assigning the new row to its parameter. The parameter is REF FLEX [] WORD, a reference to a flexible row, so the assignment reaches the caller's variable.

The inner loop uses ANDF, which stops at the first operand that is false. The standard AND evaluates both operands and would read past the end of the string; ANDF and ORF are extensions of Algol 68 Genie, and plain AND and OR remain the operators of the language.

The result holds three words, and each one is a heap string whose length is read with UPB. The spaces at the beginning, between, and after the words are gone, because a word starts only after a run of spaces ends.

$ a68g split.a68
3 words
 1: one (3 letters)
 2: two (3 letters)
 3: three (5 letters)

Trimming a line

Trimming removes spaces from both ends of a line and leaves the middle alone. Two indices move inwards: one from the first character while it is a space, and one from the last. The remaining part is a slice, which copies the characters into a new string.

trim.a68
PROC is space = (CHAR c) BOOL:
  (c = " " ORF c = REPR 9) ORF (c = REPR 10 ORF c = REPR 13);

PROC trim = (STRING s) STRING:
(
  INT first := 1;
  INT last := UPB s;
  WHILE first <= last ANDF is space(s[first]) DO
    first +:= 1
  OD;
  WHILE last >= first ANDF is space(s[last]) DO
    last -:= 1
  OD;
  IF first > last THEN
    ""
  ELSE
    s[first:last]
  FI
);

BEGIN
  [1:3] STRING lines := (
    "   padded   ",
    REPR 9 + "tabbed",
    "    ");
  FOR i TO UPB lines DO
    STRING t = trim(lines[i]);
    print(("[", t, "] ", whole(UPB t, 0), newline))
  OD
END

The two loops test the bounds before they read a character, because a line of nothing but spaces would otherwise walk off both ends. When the indices have crossed, the line was empty or contained only spaces, and the procedure answers the empty string.

The three lines lose their leading and trailing spaces, and the tab is removed like a space. The empty result has upper bound 0, which is the length that the last line reports.

$ a68g trim.a68
[padded] 6
[tabbed] 6
[] 0

Palindromes

A palindrome reads the same from both ends. Two indices start at the first and last characters and move towards the middle, comparing the characters they pass. A mismatch ends the test immediately.

palindrome.a68
PROC is palindrome = (STRING s) BOOL:
(
  INT i := 1;
  INT j := UPB s;
  BOOL ok := TRUE;
  WHILE ok ANDF i < j DO
    IF s[i] /= s[j] THEN
      ok := FALSE
    FI;
    i +:= 1;
    j -:= 1
  OD;
  ok
);

BEGIN
  [1:5] STRING words := (
    "racecar", "level", "algol", "", "ab");
  FOR i TO UPB words DO
    print((words[i], " -> ", is palindrome(words[i]), newline))
  OD
END

The loop condition combines the flag with the bound test using ANDF, so a mismatch stops the loop before the indices meet. A string of one character and the empty string are palindromes, because the loop body never runs for them.

The first two words read the same backwards, algol does not, the empty string is accepted, and ab fails on its first comparison. The test is case sensitive, so a mixed-case sentence would need toupper before the comparison.

$ a68g palindrome.a68
racecar -> T
level -> T
algol -> F
 -> T
ab -> F

Comma-separated fields

A line of comma-separated values splits at every comma. The program walks the line, prints the slice between the previous comma and the current one, and remembers where the next field starts.

csv.a68
PROC show fields = (STRING line) VOID:
(
  INT start := 1;
  INT n := 0;
  FOR i TO UPB line DO
    IF line[i] = "," THEN
      n +:= 1;
      print((whole(n, -2), ": [", line[start:i - 1], "]", newline));
      start := i + 1
    FI
  OD;
  n +:= 1;
  IF start <= UPB line THEN
    print((whole(n, -2), ": [", line[start:UPB line], "]", newline))
  ELSE
    print((whole(n, -2), ": []", newline))
  FI
);

BEGIN
  show fields("alpha,beta,gamma");
  print(newline);
  show fields("one,,three,")
END

A trailing comma produces a final empty field, and the last field has no comma after it, so it is printed after the loop. The guard start <= UPB line covers both cases: when the line ends with a comma, the slice would begin one character past the end, which is an error rather than an empty string.

The first line has three fields and the second has four, two of which are empty. Quoted fields that contain commas are not handled here; recognising them would need a flag that records whether the walk is inside quotes.

$ a68g csv.a68
 1: [alpha]
 2: [beta]
 3: [gamma]

 1: [one]
 2: []
 3: [three]
 4: []

More text tools

The procedures of this article are built from the two operations that a string supports: a subscript that reads one character and a slice that copies a range. Patterns need more than that, and Algol 68 Genie adds grep in string and sub in string for searching and replacing with regular expressions.

Numbers become text with whole, fixed, and float, and formatted transput aligns columns of strings and numbers in one call. A program that reads its text from a file uses the procedures of the files article, and the regular expressions article covers the pattern procedures.

Next steps

The strings article explains the subscripts and slices that every procedure here uses, and flexible rows covers the growth of the row of words. Regular expressions adds pattern matching and replacement, and formatted transput aligns the results in columns.

Source

Revised Report on the Algorithmic Language Algol 68

The Algol 68 Genie project

In this article we have counted words, split a line into a row of heap strings, trimmed spaces from both ends, tested strings for palindromes, and read comma-separated fields.

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.