mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
Merge pull request #110 from mitchellh/vt-parse
Properly implement anywhere transitions in VT parse table
This commit is contained in:
@ -15,7 +15,6 @@ const log = std.log.scoped(.parser);
|
|||||||
|
|
||||||
/// States for the state machine
|
/// States for the state machine
|
||||||
pub const State = enum {
|
pub const State = enum {
|
||||||
anywhere,
|
|
||||||
ground,
|
ground,
|
||||||
escape,
|
escape,
|
||||||
escape_intermediate,
|
escape_intermediate,
|
||||||
@ -225,14 +224,7 @@ pub fn next(self: *Parser, c: u8) [3]?Action {
|
|||||||
return .{ self.next_utf8(c), null, null };
|
return .{ self.next_utf8(c), null, null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const effect = effect: {
|
const effect = table[c][@enumToInt(self.state)];
|
||||||
// First look up the transition in the anywhere table.
|
|
||||||
const anywhere = table[c][@enumToInt(State.anywhere)];
|
|
||||||
if (anywhere.state != .anywhere) break :effect anywhere;
|
|
||||||
|
|
||||||
// If we don't have any transition from anywhere, use our state.
|
|
||||||
break :effect table[c][@enumToInt(self.state)];
|
|
||||||
};
|
|
||||||
|
|
||||||
// log.info("next: {x}", .{c});
|
// log.info("next: {x}", .{c});
|
||||||
|
|
||||||
@ -242,16 +234,6 @@ pub fn next(self: *Parser, c: u8) [3]?Action {
|
|||||||
// After generating the actions, we set our next state.
|
// After generating the actions, we set our next state.
|
||||||
defer self.state = next_state;
|
defer self.state = next_state;
|
||||||
|
|
||||||
// In debug mode, we log bad state transitions.
|
|
||||||
if (builtin.mode == .Debug) {
|
|
||||||
if (next_state == .anywhere) {
|
|
||||||
log.debug(
|
|
||||||
"state transition to 'anywhere' from '{}', likely binary input: {x}",
|
|
||||||
.{ self.state, c },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When going from one state to another, the actions take place in this order:
|
// When going from one state to another, the actions take place in this order:
|
||||||
//
|
//
|
||||||
// 1. exit action from old state
|
// 1. exit action from old state
|
||||||
|
@ -25,7 +25,11 @@ pub const table = genTable();
|
|||||||
|
|
||||||
/// Table is the type of the state table. This is dynamically (comptime)
|
/// Table is the type of the state table. This is dynamically (comptime)
|
||||||
/// generated to be exactly sized.
|
/// generated to be exactly sized.
|
||||||
pub const Table = genTableType();
|
pub const Table = genTableType(false);
|
||||||
|
|
||||||
|
/// OptionalTable is private to this file. We use this to accumulate and
|
||||||
|
/// detect invalid transitions created.
|
||||||
|
const OptionalTable = genTableType(true);
|
||||||
|
|
||||||
// Transition is the transition to take within the table
|
// Transition is the transition to take within the table
|
||||||
pub const Transition = struct {
|
pub const Transition = struct {
|
||||||
@ -34,36 +38,62 @@ pub const Transition = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Table is the type of the state transition table.
|
/// Table is the type of the state transition table.
|
||||||
fn genTableType() type {
|
fn genTableType(comptime optional: bool) type {
|
||||||
const max_u8 = std.math.maxInt(u8);
|
const max_u8 = std.math.maxInt(u8);
|
||||||
const stateInfo = @typeInfo(State);
|
const stateInfo = @typeInfo(State);
|
||||||
const max_state = stateInfo.Enum.fields.len;
|
const max_state = stateInfo.Enum.fields.len;
|
||||||
return [max_u8 + 1][max_state]Transition;
|
const Elem = if (optional) ?Transition else Transition;
|
||||||
|
return [max_u8 + 1][max_state]Elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function to generate the full state transition table for VT emulation.
|
/// Function to generate the full state transition table for VT emulation.
|
||||||
fn genTable() Table {
|
fn genTable() Table {
|
||||||
@setEvalBranchQuota(20000);
|
@setEvalBranchQuota(20000);
|
||||||
var result: Table = undefined;
|
|
||||||
|
|
||||||
// Initialize everything so every state transition exists
|
// We accumulate using an "optional" table so we can detect duplicates.
|
||||||
var i: usize = 0;
|
var result: OptionalTable = undefined;
|
||||||
while (i < result.len) : (i += 1) {
|
for (0..result.len) |i| {
|
||||||
var j: usize = 0;
|
for (0..result[0].len) |j| {
|
||||||
while (j < result[0].len) : (j += 1) {
|
result[i][j] = null;
|
||||||
result[i][j] = transition(.anywhere, .none);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// anywhere transitions
|
||||||
|
const stateInfo = @typeInfo(State);
|
||||||
|
inline for (stateInfo.Enum.fields) |field| {
|
||||||
|
const source = @intToEnum(State, field.value);
|
||||||
|
|
||||||
|
// anywhere => ground
|
||||||
|
single(&result, 0x18, source, .ground, .execute);
|
||||||
|
single(&result, 0x1A, source, .ground, .execute);
|
||||||
|
range(&result, 0x80, 0x8F, source, .ground, .execute);
|
||||||
|
range(&result, 0x91, 0x97, source, .ground, .execute);
|
||||||
|
single(&result, 0x99, source, .ground, .execute);
|
||||||
|
single(&result, 0x9A, source, .ground, .execute);
|
||||||
|
single(&result, 0x9C, source, .ground, .none);
|
||||||
|
|
||||||
|
// anywhere => escape
|
||||||
|
single(&result, 0x1B, source, .escape, .none);
|
||||||
|
|
||||||
|
// anywhere => sos_pm_apc_string
|
||||||
|
single(&result, 0x98, source, .sos_pm_apc_string, .none);
|
||||||
|
single(&result, 0x9E, source, .sos_pm_apc_string, .none);
|
||||||
|
single(&result, 0x9F, source, .sos_pm_apc_string, .none);
|
||||||
|
|
||||||
|
// anywhere => csi_entry
|
||||||
|
single(&result, 0x9B, source, .csi_entry, .none);
|
||||||
|
|
||||||
|
// anywhere => dcs_entry
|
||||||
|
single(&result, 0x90, source, .dcs_entry, .none);
|
||||||
|
|
||||||
|
// anywhere => osc_string
|
||||||
|
single(&result, 0x9D, source, .osc_string, .none);
|
||||||
|
}
|
||||||
|
|
||||||
// ground
|
// ground
|
||||||
{
|
{
|
||||||
const source = State.ground;
|
const source = State.ground;
|
||||||
|
|
||||||
// anywhere =>
|
|
||||||
single(&result, 0x18, .anywhere, .ground, .execute);
|
|
||||||
single(&result, 0x1A, .anywhere, .ground, .execute);
|
|
||||||
single(&result, 0x9C, .anywhere, .ground, .none);
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
single(&result, 0x19, .ground, .ground, .execute);
|
single(&result, 0x19, .ground, .ground, .execute);
|
||||||
range(&result, 0, 0x17, .ground, .ground, .execute);
|
range(&result, 0, 0x17, .ground, .ground, .execute);
|
||||||
@ -94,28 +124,17 @@ fn genTable() Table {
|
|||||||
{
|
{
|
||||||
const source = State.sos_pm_apc_string;
|
const source = State.sos_pm_apc_string;
|
||||||
|
|
||||||
// anywhere =>
|
|
||||||
single(&result, 0x98, .anywhere, source, .none);
|
|
||||||
single(&result, 0x9E, .anywhere, source, .none);
|
|
||||||
single(&result, 0x9F, .anywhere, source, .none);
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
single(&result, 0x19, source, source, .ignore);
|
single(&result, 0x19, source, source, .ignore);
|
||||||
range(&result, 0, 0x17, source, source, .ignore);
|
range(&result, 0, 0x17, source, source, .ignore);
|
||||||
range(&result, 0x1C, 0x1F, source, source, .ignore);
|
range(&result, 0x1C, 0x1F, source, source, .ignore);
|
||||||
range(&result, 0x20, 0x7F, source, source, .ignore);
|
range(&result, 0x20, 0x7F, source, source, .ignore);
|
||||||
|
|
||||||
// => ground
|
|
||||||
single(&result, 0x9C, source, .ground, .none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// escape
|
// escape
|
||||||
{
|
{
|
||||||
const source = State.escape;
|
const source = State.escape;
|
||||||
|
|
||||||
// anywhere =>
|
|
||||||
single(&result, 0x1B, .anywhere, source, .none);
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
single(&result, 0x19, source, source, .execute);
|
single(&result, 0x19, source, source, .execute);
|
||||||
range(&result, 0, 0x17, source, source, .execute);
|
range(&result, 0, 0x17, source, source, .execute);
|
||||||
@ -152,9 +171,6 @@ fn genTable() Table {
|
|||||||
{
|
{
|
||||||
const source = State.dcs_entry;
|
const source = State.dcs_entry;
|
||||||
|
|
||||||
// anywhere =>
|
|
||||||
single(&result, 0x90, .anywhere, source, .ignore);
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
single(&result, 0x19, source, source, .ignore);
|
single(&result, 0x19, source, source, .ignore);
|
||||||
range(&result, 0, 0x17, source, source, .ignore);
|
range(&result, 0, 0x17, source, source, .ignore);
|
||||||
@ -202,9 +218,6 @@ fn genTable() Table {
|
|||||||
single(&result, 0x19, source, source, .ignore);
|
single(&result, 0x19, source, source, .ignore);
|
||||||
range(&result, 0, 0x17, source, source, .ignore);
|
range(&result, 0, 0x17, source, source, .ignore);
|
||||||
range(&result, 0x1C, 0x1F, source, source, .ignore);
|
range(&result, 0x1C, 0x1F, source, source, .ignore);
|
||||||
|
|
||||||
// => ground
|
|
||||||
single(&result, 0x9C, source, .ground, .none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dcs_param
|
// dcs_param
|
||||||
@ -240,9 +253,6 @@ fn genTable() Table {
|
|||||||
range(&result, 0x1C, 0x1F, source, source, .put);
|
range(&result, 0x1C, 0x1F, source, source, .put);
|
||||||
range(&result, 0x20, 0x7E, source, source, .put);
|
range(&result, 0x20, 0x7E, source, source, .put);
|
||||||
single(&result, 0x7F, source, source, .ignore);
|
single(&result, 0x7F, source, source, .ignore);
|
||||||
|
|
||||||
// => ground
|
|
||||||
single(&result, 0x9C, source, .ground, .none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// csi_param
|
// csi_param
|
||||||
@ -305,9 +315,6 @@ fn genTable() Table {
|
|||||||
{
|
{
|
||||||
const source = State.csi_entry;
|
const source = State.csi_entry;
|
||||||
|
|
||||||
// anywhere =>
|
|
||||||
single(&result, 0x9B, .anywhere, source, .none);
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
single(&result, 0x19, source, source, .execute);
|
single(&result, 0x19, source, source, .execute);
|
||||||
range(&result, 0, 0x17, source, source, .execute);
|
range(&result, 0, 0x17, source, source, .execute);
|
||||||
@ -333,41 +340,40 @@ fn genTable() Table {
|
|||||||
{
|
{
|
||||||
const source = State.osc_string;
|
const source = State.osc_string;
|
||||||
|
|
||||||
// anywhere =>
|
|
||||||
single(&result, 0x9D, .anywhere, source, .none);
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
single(&result, 0x19, source, source, .ignore);
|
single(&result, 0x19, source, source, .ignore);
|
||||||
range(&result, 0, 0x06, source, source, .ignore);
|
range(&result, 0, 0x17, source, source, .ignore);
|
||||||
range(&result, 0x08, 0x17, source, source, .ignore);
|
|
||||||
range(&result, 0x1C, 0x1F, source, source, .ignore);
|
range(&result, 0x1C, 0x1F, source, source, .ignore);
|
||||||
range(&result, 0x20, 0x7F, source, source, .osc_put);
|
range(&result, 0x20, 0x7F, source, source, .osc_put);
|
||||||
|
|
||||||
// => ground
|
|
||||||
single(&result, 0x07, source, .ground, .none);
|
|
||||||
single(&result, 0x9C, source, .ground, .none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
// Create our immutable version
|
||||||
|
var final: Table = undefined;
|
||||||
|
for (0..final.len) |i| {
|
||||||
|
for (0..final[0].len) |j| {
|
||||||
|
final[i][j] = result[i][j] orelse transition(@intToEnum(State, j), .none);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return final;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn single(t: *Table, c: u8, s0: State, s1: State, a: Action) void {
|
fn single(t: *OptionalTable, c: u8, s0: State, s1: State, a: Action) void {
|
||||||
// In debug mode, we want to verify that every state is marked
|
const s0_int = @enumToInt(s0);
|
||||||
// exactly once.
|
|
||||||
if (builtin.mode == .Debug) {
|
|
||||||
const existing = t[c][@enumToInt(s0)];
|
|
||||||
if (existing.state != .anywhere) {
|
|
||||||
std.debug.print("transition set multiple times c={} s0={} existing={}", .{
|
|
||||||
c, s0, existing,
|
|
||||||
});
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t[c][@enumToInt(s0)] = transition(s1, a);
|
// TODO: enable this but it thinks we're in runtime right now
|
||||||
|
// if (t[c][s0_int]) |existing| {
|
||||||
|
// @compileLog(c);
|
||||||
|
// @compileLog(s0);
|
||||||
|
// @compileLog(s1);
|
||||||
|
// @compileLog(existing);
|
||||||
|
// @compileError("transition set multiple times");
|
||||||
|
// }
|
||||||
|
|
||||||
|
t[c][s0_int] = transition(s1, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn range(t: *Table, from: u8, to: u8, s0: State, s1: State, a: Action) void {
|
fn range(t: *OptionalTable, from: u8, to: u8, s0: State, s1: State, a: Action) void {
|
||||||
var i = from;
|
var i = from;
|
||||||
while (i <= to) : (i += 1) single(t, i, s0, s1, a);
|
while (i <= to) : (i += 1) single(t, i, s0, s1, a);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user