Skip to content

ISO Lexer Edge Cases

Clausal Prolog's reader follows the escape, quoted-character and number rules of ISO/IEC 13211-1 §6.4, including in places where many Prolog systems are more lenient. Until October 2026 our lexer was lenient too. It accepted bare-exponent floats, uppercase radix prefixes, unknown escapes, octal and hex escapes without their closing backslash, raw control characters inside quotes, and a few other forms. We tightened it to match the standard, and if the standard is ever relaxed in these areas, we will relax with it.

This page records each tightened case: what ISO says, what we used to do, what two other systems do, and the reason for the rule as we understand it. Two reasons for keeping such a record:

  • Some of these rules are widely ignored, which may mean the standard is stricter than practice needs. Making the cases visible helps that discussion.
  • Where a rule protects against an ambiguity with another part of the grammar, documenting that link is useful to other implementers and helps test the standard itself.

The "reason" column gives our reading of why each rule exists. It is not a quotation from the standard or its committee. The row numbers are from Ulrich Neumerkel's Conformity Testing I: Syntax page (see ISO Prolog Conformity).

How the other systems were checked

Each case was read as t :- X = <case>, writeq(X), nl. from a file by Scryer Prolog v0.10.0-137-g9acba77a and Trealla Prolog v2.92.3, on 2026-10-16. "error" means the clause was refused with a syntax error.

Numbers

Case ISO Clausal before Scryer Trealla Ulrich row
1e9 not a float float 1.0e9 error error —
1E9 not a float float 1.0e9 error error #47
1.0e9, 1.0E9 float float float float —
0X1F, 0O17, 0B101 not a number 31, 15, 5 error error #43, #272

Bare exponents (1e9). In ISO 6.4.5 a float is an integer, a fraction and an optional exponent, and the fraction is not optional. Without that rule, 1e9 would be both a float and the integer 1 followed by the name e9. Ulrich's rows #49 and #50 show this matters: after op(9, xf, e9), 1e9 = e9(1) must succeed, and after op(9, xf, e), 1e-9 reads as -(e(1), 9). Requiring the fraction keeps a digit run followed by a letter from turning into a number depending on which letter it is. Accepting 1e9 is probably the most common extension in practice, which makes it the strongest candidate on this page for relaxing the standard.

Uppercase exponent char. E is allowed: ISO 6.4.5's exponent char is e or E, and Scryer's lexer spells the same production that way. Only the missing fraction makes 1E9 invalid. (Our first tightening also dropped the E; that was a mistake, corrected before landing.)

Uppercase radix prefixes (0X1F). ISO 6.4.4 writes 0x, 0o and 0b in lowercase only. With that rule, an uppercase letter after a 0 always starts a new token, here a variable (X1F). Accepting 0X would make 0X1F mean a number in one reading and 0 followed by the variable X1F in another.

Quoted atoms and strings

Case ISO Clausal before Scryer Trealla Ulrich row
'\e' error ESC character error ESC character #17, #19
'\s' error space error space —
'\d', '\z', '\ca', '\N', '\9', '\u1' error kept the backslash error '\d' kept, '\z' error #16, #21, #22, #104, #105, #229, #241
'\ ', '\<TAB>' (a backslash before a space or tab) error kept the backslash error error #11, #12, #193
'\141' (no closing \) error a error error #102
'\141\' a a a a —
a raw tab inside quotes error accepted error error #5
a raw newline inside quotes error accepted error error #6
another raw control character (U+0001, DEL) error accepted error U+0001 error, DEL accepted —

A closed set of escapes. ISO 6.4.2.1 lists exactly which characters may follow a backslash: the symbolic escapes a b f n r t v \ ' " `, the octal and hex escapes, and backslash-newline as a line continuation. Anything else is an error. A closed set means '\e' reads the same everywhere or is refused everywhere. It does not depend on which extensions a system happens to have. The two other systems already disagree: Trealla reads '\e' as ESC and '\s' as a space, and Scryer refuses both. \e (escape) and \s (space) are popular enough that adding them to the standard may be worth discussing.

The closing backslash on octal and hex escapes. Without the closing \, an escape like '\1411' is ambiguous between octal 141 followed by 1 and octal 1411. The terminator makes the end of the escape explicit, so the escape does not need a fixed width.

No raw control characters inside quotes. A literal tab, newline or any other control character (Unicode category Cc: U+0000 to U+001F, DEL, and U+0080 to U+009F) inside quotes is an error, but a space is allowed. The rule makes quoted text visible on the page: a tab that looks like spaces cannot slip into an atom. It also makes an unclosed quote fail on its own line instead of swallowing the rest of the file. A newline that belongs in the text is written \n, and a quoted atom can continue onto the next line with backslash-newline.

Clausal still reads some characters that Scryer refuses. These are the non-ASCII space and separator characters, such as U+00A0 (no-break space), U+2003 (em space) and U+2028 (line separator), and the lone-surrogate escape '\xD800\'. ISO leaves characters outside its own character set to the implementation, so we have not tightened them.

Character codes (0')

Case ISO Clausal before Scryer Trealla Ulrich row
0'' not a char code 39 error error #117, #120, #197
0''' 39 39 39 39 #115
0'\' 39 39 39 39 #114
0' followed by a raw tab error 9 error error #177
0'\141, 0'\x41 (no closing \) error 97, 65 error error —
0'\e, 0'\s error 27, 32 error 27, 32 —
0'\141\, 0'\x41\ 97, 65 97, 65 97, 65 97, 65 —

A quote after 0' must be doubled, just as inside a quoted atom (0'''), or escaped (0'\'). A single quote is not a valid character there, so 0'' reads as the integer 0 followed by the empty atom ''. Ulrich's rows #120 and #197 depend on that reading: with '' declared as an operator, 0''1 reads as ''(0, 1), and with '' as a postfix operator, 0'' reads as ''(0).

No raw control character after 0', for the same reason as inside quotes: 0' followed by a tab looks like 0' followed by spaces. A space itself is allowed, so 0' is 32.

The same escapes as inside quotes. After 0'\ comes one escape from the same closed set, and an octal or hex escape needs its closing backslash: 0'\141\ is 97, and 0'\141, 0'\e and 0'\s are errors, just as '\141', '\e' and '\s' are.