On this page:
3.1 Data model
3.2 Expressions
3.3 Control flow
3.3.1 NEXT and RESUME
3.3.2 FORGET
3.3.3 COME FROM
3.3.4 TRY AGAIN and GIVE UP
3.4 Statement state and self-modification
3.4.1 ABSTAIN and REINSTATE
3.4.2 NOT, DON’T, ONCE, and AGAIN
3.4.3 IGNORE and REMEMBER
3.4.4 STASH and RETRIEVE
3.5 Runtime I/  O support

3 The INTERCAL Model🔗ℹ

This implementation is easiest to understand if you treat an INTERCAL program as a labeled state machine over mutable variables.

Each source line has:

  • an optional numeric label such as (100).

  • a core operation such as assignment, NEXT, or READ OUT.

  • optional modifiers such as PLEASE, NOT, ONCE, or a chance prefix like %50.

Execution starts at the first runtime line and normally falls through to the next runtime line. Labels are entry points for control flow, not separate blocks in the Racket sense.

3.1 Data model🔗ℹ

The frontend accepts the standard INTERCAL variable classes:

  • .name: onespot scalar values.

  • :name: twospot scalar values.

  • ,name: onespot arrays.

  • ;name: twospot arrays.

  • *name: frontend-supported array identifiers that are treated as onespot-valued by the runtime.

Unlike Racket’s arbitrary-precision integers, INTERCAL values are fixed-width machine integers. The runtime enforces the following bounds:

  • Onespot (.) targets: unsigned 16-bit (0..65535).

  • Twospot (:) targets: unsigned 32-bit (0..4294967295).

  • Overflow Protection: Storing a twospot-sized value into a onespot target will not truncate or "wrap around"; it triggers a runtime INTERCAL error.

Arrays are dimensioned with BY and indexed with SUB. A declaration such as

DO ,1 <- #10 BY #20

creates a two-dimensional array, and a use such as

DO ,1 SUB #3 SUB #4 <- #99

stores into a particular cell.

3.2 Expressions🔗ℹ

The supported expression language is intentionally small:

  • integer literals like 5.

  • mesh literals like #5.

  • variables and subscripted array references.

  • $ for MINGLE.

  • ~ for SELECT.

  • unary &, V, and ?.

  • single-quote and double-quote grouping.

The important semantic point is that these are bit-structured operations, not ordinary arithmetic operators:

  • a $ b interleaves the bits of a and b.

  • a ~ b selects the bits of a at positions where b has ones, then packs them together.

  • &, V, and ? combine a value with a one-bit rotation of itself.

That is why this implementation routes ordinary arithmetic through "syslib.i" rather than trying to express everything directly.

3.3 Control flow🔗ℹ

The control-flow model is the heart of INTERCAL. The core operations are NEXT, RESUME, FORGET, COME FROM, TRY AGAIN, and GIVE UP.

3.3.1 NEXT and RESUME🔗ℹ

NEXT acts like a subroutine call. Executing

DO (200) NEXT

pushes the following runtime line onto the NEXT stack and transfers control to label (200).

RESUME unwinds that stack. A statement such as

DO RESUME #2

removes two saved continuations and jumps to the last one removed.

The important edge cases are:

  • RESUME #1 returns to the most recent NEXT continuation.

  • RESUME #2 skips one saved continuation and returns to the next one below it.

  • RESUME #0 is an error.

  • Resuming past the end of the NEXT stack raises the standard stack rupture error.

This implementation has explicit regression tests for these cases in "tests/sick-test.rkt".

3.3.2 FORGET🔗ℹ

FORGET discards continuations without transferring control. For example,

DO FORGET #1

removes one saved NEXT entry. This implementation follows C-INTERCAL here: forgetting more entries than are currently present saturates instead of raising an error.

3.3.3 COME FROM🔗ℹ

COME FROM is the inverse of a goto-like jump. A line such as

(500) DO COME FROM (100)

registers line (500) as a hijacker for transfers that reach label (100). Operationally:

  • the target line still executes.

  • after the target line completes, control may be redirected to the matching COME FROM line.

  • the runtime keeps explicit label-to-hijacker tables to implement this.

In the presence of NEXT, the timing matters. This implementation models the delayed behavior needed by real programs: a COME FROM attached to a saved NEXT entry does not fire until that entry is actually resumed.

3.3.4 TRY AGAIN and GIVE UP🔗ℹ

GIVE UP terminates the program. TRY AGAIN restarts execution from the first runtime line. In practice, TRY AGAIN is usually used as an explicit loop reset and GIVE UP is the normal program exit.

3.4 Statement state and self-modification🔗ℹ

Unlike Racket’s static execution, INTERCAL permits dynamic, line-level toggling of statement activity. This implementation supports that behavior by generating explicit runtime lookup tables—rather than straight-line Racket code—to verify a statement’s status immediately before execution. This centralized state tracking is essential for handling modifiers that disable or enable logic (ABSTAIN, NOT, REINSTATE, DON’T), limit execution frequency (ONCE, AGAIN), or manipulate variable accessibility and history (IGNORE, REMEMBER, STASH, RETRIEVE).

3.4.1 ABSTAIN and REINSTATE🔗ℹ

ABSTAIN disables statements; REINSTATE re-enables them.

This implementation supports both styles:

  • Targeting a particular labeled line, as in DO ABSTAIN FROM (100)

  • Targeting gerunds, as in DO ABSTAIN FROM STASHING + RETRIEVING.

The runtime represents abstention with counts, not booleans. That matters because:

  • multiple abstentions compose abstaining twice increments the count twice.

  • the line stays inactive while the count is positive.

  • each reinstatement removes one layer.

3.4.2 NOT, DON’T, ONCE, and AGAIN🔗ℹ

The parser accepts both NOT and upstream-style DONT. The lexer normalizes DONT to DO NOT, and the runtime interprets that prefix as an initial abstention state for the line.

Postfix ONCE and AGAIN are local state modifiers on that line:

  • ONCE updates the line’s abstention state after it is encountered once

  • AGAIN updates that local state on later encounters.

  • This implementation tracks this with per-line state tables, not with source rewriting.

The short version is that these modifiers make a line stateful. A line can change whether it will run the next time control reaches it.

3.4.3 IGNORE and REMEMBER🔗ℹ

IGNORE and REMEMBER control whether assignments to a variable take effect.

  • If a variable is ignored, writes to it are suppressed.

  • REMEMBER restores normal write behavior.

  • The compiler only emits ignore-table checks for variables that can actually be ignored, which is one of the conservative optimizations in "sick.rkt".

3.4.4 STASH and RETRIEVE🔗ℹ

STASH saves variable values on per-variable stacks, and RETRIEVE restores them. For subscripted array expressions, this implementation stashes the array object rather than a single indexed element, which matches the way INTERCAL uses these commands operationally.

3.5 Runtime I/O support🔗ℹ

This implementation supports both standard numeric INTERCAL I/O and the tape style used by C-INTERCAL string-oriented programs.

  • Scalar WRITE IN reads spelled numbers such as ONE TWO THREE and accepts OH for zero.

  • Scalar READ OUT writes Roman numerals.

  • Array WRITE IN and READ OUT use the C-INTERCAL tape encoding, which is required for programs such as "pit/hello.i" and "pit/unlambda.i".

For a Racket programmer, the practical implication is simple: strings are not primitive values in INTERCAL. Real text I/O is done through arrays and the tape encoding.