VTパーサー
from エスケープシーケンス
Paul Williamsの状態遷移図
VTパーサーの定番実装は Paul Williams の VT500 状態遷移図 に基づく。この状態遷移モデルは、Alacrittyの vte クレートなど、多くの実装の基盤となっている。
状態一覧
code:_
┌─────────────────┬─────────────────────────────────────────────────┐
│ 状態 │ 説明 │
├─────────────────┼─────────────────────────────────────────────────┤
│ Ground │ 初期状態。通常の文字を出力する │
│ Escape │ ESC (0x1B) を受信した直後 │
│ EscapeIntermed │ ESC後に中間バイト(0x20-0x2F)を受信 │
│ CsiEntry │ CSI (ESC [) を受信した直後 │
│ CsiParam │ CSIパラメータバイト(0x30-0x3F)を受信中 │
│ CsiIntermediate │ CSI中間バイト(0x20-0x2F)を受信中 │
│ CsiIgnore │ CSI解析エラー、最終バイトまで無視 │
│ DcsEntry │ DCS (ESC P) を受信した直後 │
│ DcsParam │ DCSパラメータバイト受信中 │
│ DcsIntermediate │ DCS中間バイト受信中 │
│ DcsPassthrough │ DCSデータ受信中 (STまで) │
│ DcsIgnore │ DCS解析エラー、STまで無視 │
│ OscString │ OSCデータ受信中 (ST/BELまで) │
│ SosPmApcString │ SOS/PM/APCデータ受信中 (STまで) │
└─────────────────┴─────────────────────────────────────────────────┘
状態遷移の全体像
code:_
ESC
┌──────────────────────┐
│ │
▼ │
┌──────┐ │
┌──▶│Ground│◀─ (完了/エラー) ─┘
│ └──┬───┘
│ │ 0x1B (ESC)
│ ▼
│ ┌──────┐
│ │Escape│
│ └──┬───┘
│ │
│ ├─ '[' → CsiEntry → CsiParam → (最終バイト) → Ground
│ │ ↘ CsiIntermediate → Ground
│ │ ↘ CsiIgnore → Ground
│ │
│ ├─ ']' → OscString → (ST/BEL) → Ground
│ │
│ ├─ 'P' → DcsEntry → DcsParam → DcsPassthrough → (ST) → Ground
│ │
│ ├─ 'O' → (SS3) 次の1バイトを処理 → Ground
│ ├─ 'N' → (SS2) 次の1バイトを処理 → Ground
│ │
│ └─ その他 → ESCシーケンスとして処理 → Ground
│
└── (C0制御文字はほぼ全ての状態で処理可能)
CSI解析の詳細な状態遷移
code:_
CsiEntry ─── パラメータバイト (0-9, ;) ──▶ CsiParam
│ │
│ ├── パラメータバイト → CsiParam (ループ)
│ │
├── 最終バイト (0x40-0x7E) ──▶ CSI実行 ──▶ Ground
│ │
│ ├── 最終バイト → CSI実行 → Ground
│ │
│ ├── 中間バイト (0x20-0x2F) → CsiIntermediate
│ │ └── 最終バイト → CSI実行 → Ground
│ │
│ └── 不正なバイト → CsiIgnore
│ └── 最終バイト → Ground (無視)
│
└── '?' / '>' / '<' (0x3C-0x3F) → プライベートマーカーとして記録 → CsiParam
パーサーの実装例
code:rust
// Rust: 最小限のVTパーサー骨格
#derive(Debug, Clone, Copy, PartialEq)
enum State {
Ground,
Escape,
CsiEntry,
CsiParam,
CsiIntermediate,
CsiIgnore,
OscString,
DcsEntry,
DcsParam,
DcsPassthrough,
}
struct VtParser {
state: State,
params: Vec<u16>,
current_param: u16,
intermediates: Vec<u8>,
osc_data: Vec<u8>,
private_marker: Option<u8>,
}
impl VtParser {
fn advance(&mut self, byte: u8, handler: &mut impl Handler) {
// C0制御文字はほぼ全ての状態で即時処理
if byte < 0x20 && byte != 0x1B {
match self.state {
State::OscString | State::DcsPassthrough => {
if byte == 0x07 && self.state == State::OscString {
// BELでOSC終了
handler.osc_dispatch(&self.osc_data);
self.state = State::Ground;
return;
}
// DcsPassthrough内のC0は通過させる
if self.state == State::DcsPassthrough {
handler.dcs_put(byte);
return;
}
}
_ => {}
}
handler.execute_c0(byte);
return;
}
match self.state {
State::Ground => {
if byte == 0x1B {
self.state = State::Escape;
} else if byte >= 0x20 {
handler.print(byte as char);
}
}
State::Escape => match byte {
b'[' => {
self.reset_params();
self.state = State::CsiEntry;
}
b']' => {
self.osc_data.clear();
self.state = State::OscString;
}
b'P' => {
self.reset_params();
self.state = State::DcsEntry;
}
b'O' => {
// SS3: 次のバイトと組み合わせて処理
// (簡略化: 実際はSS3状態が必要)
handler.ss3_dispatch(byte);
self.state = State::Ground;
}
b'D' => { handler.index(); self.state = State::Ground; }
b'E' => { handler.newline(); self.state = State::Ground; }
b'M' => { handler.reverse_index(); self.state = State::Ground; }
b'\\' => { self.state = State::Ground; } // ST
_ => {
handler.esc_dispatch(byte);
self.state = State::Ground;
}
},
State::CsiEntry | State::CsiParam => {
match byte {
b'0'..=b'9' => {
self.current_param = self.current_param
.saturating_mul(10)
.saturating_add((byte - b'0') as u16);
self.state = State::CsiParam;
}
b';' => {
self.params.push(self.current_param);
self.current_param = 0;
self.state = State::CsiParam;
}
b'?' | b'>' | b'<' => {
self.private_marker = Some(byte);
self.state = State::CsiParam;
}
0x20..=0x2F => {
self.intermediates.push(byte);
self.state = State::CsiIntermediate;
}
0x40..=0x7E => {
// 最終バイト: CSIシーケンスを実行
self.params.push(self.current_param);
handler.csi_dispatch(
&self.params,
&self.intermediates,
self.private_marker,
byte as char,
);
self.state = State::Ground;
}
_ => {
self.state = State::CsiIgnore;
}
}
}
State::OscString => {
if byte == 0x1B {
// 次のバイトが'\\'ならST
// (簡略化: 実際はESC受信後の状態遷移が必要)
handler.osc_dispatch(&self.osc_data);
self.state = State::Escape;
} else {
self.osc_data.push(byte);
}
}
// ... DcsEntry, DcsParam, DcsPassthrough, CsiIntermediate,
// CsiIgnore の処理も同様に実装
_ => {}
}
}
fn reset_params(&mut self) {
self.params.clear();
self.current_param = 0;
self.intermediates.clear();
self.private_marker = None;
}
}
重要な実装上の注意点
1. C0制御文字のインターセプト: CSIパラメータ解析中でもBSやCR等のC0制御文字は即時処理する必要がある(ESCを除く)。これはPaul Williamsの図で "anywhere" として定義されている。
2. ESCの再エントリ: どの状態にいても ESC (0x1B) を受信したらEscape状態に遷移する。不正なシーケンスの途中でも新しいシーケンスが始まれば前のものは破棄される。
3. パラメータのデフォルト値: CSIパラメータが省略された場合のデフォルト値はコマンドごとに異なる。多くは0または1だが、仕様書を確認する必要がある。
4. パラメータのオーバーフロー対策: 悪意のある入力に備え、パラメータ数や値の上限を設ける。一般的には最大16パラメータ、値は最大65535とする。