Skip to content

Commit ecf1a79

Browse files
feat: port parser EXE and CSI fast-paths from upstream xterm.js
Add two fast-paths to the main parse loop that bypass the transition table for common sequences, matching upstream xterm.js commits 5d120de3, 6fd7ecc8, b28b5d0c, eb65910c: - EXE fast-path: control bytes 0x00-0x17 in non-payload states (state <= CSI_IGNORE) dispatch directly from the executeHandlers array without a table lookup. - CSI fast-path: when ESC [ is detected in a non-string state, params and the final byte are parsed in a tight loop. Sequences with intermediates fall back to the table-driven CSI_PARAM state. Fixes #25 Co-authored-by: Ona <no-reply@ona.com>
1 parent 69a3e0e commit ecf1a79

2 files changed

Lines changed: 481 additions & 0 deletions

File tree

parser.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,72 @@ func (p *EscapeSequenceParser) Parse(data []uint32, length int) {
343343
for i := 0; i < length; i++ {
344344
code = data[i]
345345

346+
// EXE fast-path: common control bytes (0x00-0x17) in non-payload states
347+
// bypass the transition table entirely and dispatch directly.
348+
if code < 0x18 && p.currentState <= ParserStateCSIIgnore {
349+
if p.executeHandlers[code] != nil {
350+
p.executeHandlers[code]()
351+
} else {
352+
p.executeHandlerFb(code)
353+
}
354+
p.precedingJoinState = 0
355+
continue
356+
}
357+
358+
// CSI fast-path: when ESC [ is detected in a non-string state, parse
359+
// params and final byte in a tight loop without table lookups.
360+
if code == 0x1b && p.currentState < ParserStateOSCString &&
361+
i+2 < length && data[i+1] == 0x5b {
362+
p.params.ResetZdm()
363+
p.collect = 0
364+
k := i + 2
365+
ch := data[k]
366+
// Prefix byte (< = > ?)
367+
if ch >= 0x3c && ch <= 0x3f {
368+
p.collect = int(ch)
369+
k++
370+
}
371+
csiDone := false
372+
for ; k < length; k++ {
373+
ch = data[k]
374+
if ch >= 0x30 && ch <= 0x39 {
375+
p.params.AddDigit(int32(ch) - 48)
376+
} else if ch == 0x3b {
377+
p.params.AddParam(0) // ZDM
378+
} else if ch == 0x3a {
379+
p.params.AddSubParam(-1)
380+
} else if ch >= 0x40 && ch <= 0x7e {
381+
// Final byte — dispatch CSI handler
382+
ident := p.collect<<8 | int(ch)
383+
handlers := p.csiHandlers[ident]
384+
j := len(handlers) - 1
385+
for ; j >= 0; j-- {
386+
if handlers[j](p.params) {
387+
break
388+
}
389+
}
390+
if j < 0 {
391+
p.csiHandlerFb(ident, p.params)
392+
}
393+
p.precedingJoinState = 0
394+
i = k
395+
p.currentState = ParserStateGround
396+
csiDone = true
397+
break
398+
} else {
399+
// Intermediate byte or unexpected — fall back to table-driven path
400+
break
401+
}
402+
}
403+
if !csiDone {
404+
// Ran out of data or hit an intermediate; let the table-driven
405+
// path continue from the CSI_PARAM state.
406+
i = k - 1
407+
p.currentState = ParserStateCSIParam
408+
}
409+
continue
410+
}
411+
346412
// Map non-ASCII printable to the 0xA0 slot
347413
if code < 0xa0 {
348414
transition = table[int(p.currentState)<<tableIndexStateShift|int(code)]

0 commit comments

Comments
 (0)