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:
- ByteClass
maps
0x00..0x7finto 16 control/ASCII classes and0x80..0xffinto theUTF8_PAYLOADrouting class. Raw C1 bytes are not 8-bit control introducers. - AnsiStateMachine
performs a flat
IntArraylookup 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. - 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.