DevelopmentAll versions
On this page

ANSI Finite-State Machine & CSI Dispatch Specification

This document describes parser routing and collection contracts. Protocol support is tracked in the feature map; the classes described here are internal implementation details.

1. The State Machine Architecture

TerminalParser coordinates two routes: printable UTF-8 decoding and the ANSI state machine. A pending printable UTF-8 scalar takes priority. Once resolved, normal byte routing resumes, including bounded replay of a non-continuation byte.

The ANSI route separates three responsibilities:

  1. ByteClass maps 0x00..0x7f into 16 control/ASCII classes and 0x80..0xff into the UTF8_PAYLOAD routing class. Raw C1 bytes are not 8-bit control introducers.
  2. AnsiStateMachine performs a flat IntArray lookup for the current state and routing class. Each entry packs an action in the low eight bits and the next state above it. There are 18 states and 32 reserved columns per state, with 17 active routing classes. Lookup uses (state shl 5) or byteClass.
  3. ActionEngine updates reusable sequence state and delegates semantic operations to the dispatchers or printable processor.

Non-ASCII bytes are printable ingress only in GROUND. OSC/DCS bodies collect them as opaque bytes; graphics APC rejects them and ignored strings discard them. In active ESC/CSI grammar, they terminate the incomplete sequence without dispatching a stale final byte.

The matrix lookup does fixed work per byte. That does not make an entire parser call allocation-free: completed metadata commands may decode strings, and explicitly permitted larger OSC bodies may grow collection storage.

2. CSI Command Signatures

CSI dispatch distinguishes the final byte, private marker, and ordered intermediates. CsiSignature packs them into a primitive Long:

Bits Meaning
0..7 Final byte
8..15 Private marker, or zero
16..47 Up to four intermediate bytes, low to high
48..51 Intermediate count
52..63 Zero

Parameters are separate from this key. A reusable IntArray holds up to 32 fields; -1 represents an omitted field. A bit mask records fields opened by a colon. Opening a 33rd field rejects the entire CSI before dispatch. Decimal overflow saturates the retained value to Int.MAX_VALUE and records saturation for dispatchers that require exact values. The intermediate collector retains the first four bytes; additional intermediate bytes are dropped.

3. Dispatch table

GeneratedCsiDispatchTable stores sorted structural signatures in a LongArray and corresponding command IDs in an IntArray. Binary search selects the command; the dispatcher then interprets its parameters and emits semantic sink operations. An unmatched key does not fall back to a final-byte-only command.

Structural actions flush pending printable text before command dispatch. This preserves callback order and prevents later text from extending a grapheme across a structural operation.

4. Parser Recovery and Security Safeguards

OSC ends with BEL or ESC \; DCS and APC end with ESC \. CAN/SUB cancel collected strings without dispatch. Ordinary C0 controls are ignored in OSC; in a DCS body they are retained as payload rather than executed. DEL is ignored in OSC/DCS. Graphics APC rejects ordinary C0 and DEL as invalid header or Base64 data. String escape states handle a split ESC \ without confusing it with a standalone ESC command.

ControlStringPolicy sets collection limits before decoding. Limits count stored bytes, including family headers and separators, excluding introducers, terminators, and controls ignored by that string's rules:

Collection Limit
Ordinary OSC envelopes and DCS +q 4096 bytes
OSC dynamic colors 10, 11, 12 256 bytes
DCS $q 64 bytes
Custom OSC 4096 bytes by default, independently configurable
Eligible OSC 52 writes Ordinary bound or a larger checked limit from the host's decoded-byte budget

Unsupported families stop collecting their bodies after identification. Overflow stops further storage and rejects the completed command; OSC 8 also ends an existing hyperlink to avoid retaining stale link state. Eligible larger OSC collection grows on demand. Temporary storage is released on completion, cancellation, overflow, reset, EOF, and custom callback failure.

Collection limits do not authorize clipboard changes or terminal replies. The sink's owning layers validate complete requests and apply current permissions. endOfInput discards unfinished OSC/DCS/APC commands and chunked graphics uploads. reset discards parser-owned state without emitting sink commands.

Graphics APC collection

APC_STRING and APC_ESCAPE route ESC _ strings separately from ignored SOS/PM. The first payload byte selects graphics (G) or draining. A lazy GraphicsParser owns the bounded header, incremental Base64 state and one upload spanning several APC strings. Its header/chunk/transfer limits are separate from ControlStringPolicy; see TerminalGraphicsLimits.

APC_END validates a chunk at ST; APC_ABORT cancels on CAN/SUB. An unexpected escape suffix uses APC_INVALID to reject and drain the current string rather than execute its tail as terminal input. The sink's invalidation token prevents an incomplete upload from surviving a reset or effective screen switch. Completed controls and decoded bytes are borrowed synchronously to applyGraphicsCommand. See the graphics contract for continuation and reply semantics.

Validation

AnsiStateMachineTest checks routing, and ActionEngineTest checks collection and mutation invariants. ControlStringPayloadPolicyTest, CustomOscHandlerTest, CustomOscLimitTest, GraphicsParserTest, and TerminalParserTest cover exact bounds, chunking, cancellation, ordering, and recovery. Run ./gradlew :ketraterm-parser:test from the repository root; semantic changes also require the affected host tests.