EscapeSequenceParser.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. //
  2. // EscapeSequenceParser.swift
  3. // SwiftTerm
  4. //
  5. // Created by Miguel de Icaza on 3/28/19.
  6. // Copyright © 2019 Miguel de Icaza. All rights reserved.
  7. //
  8. // The state machien has been extended to allow ":" in the CSI Param state to initiate a new
  9. // parameter value. This is strictly not part of the spec, but necessary to parse the
  10. // color scheme CSI [ 48:2:R:G:B m sequence which uses ":" instead of the more common ";"
  11. //
  12. // Alternative approaches:
  13. // * only allow ":" as a CsiParam if the first param is a 48/38.
  14. // * create an additiona "ignoredBuffer" that is passed to functions interested in those,
  15. // and this could be one of those. Would be a little stricter, and probably better
  16. import Foundation
  17. enum ParserState : UInt8 {
  18. case ground = 0
  19. case escape
  20. case escapeIntermediate
  21. case csiEntry
  22. case csiParam
  23. case csiIntermediate
  24. case csiIgnore
  25. case sosPmApcString
  26. case oscString
  27. case dcsEntry
  28. case dcsParam
  29. case dcsIgnore
  30. case dcsIntermediate
  31. case dcsPassthrough
  32. }
  33. typealias cstring = [UInt8]
  34. class ParsingState {
  35. var position: Int
  36. var code: UInt8
  37. var currentState: ParserState
  38. var print: Int
  39. var dcs: Int
  40. var osc: cstring
  41. var collect: cstring
  42. var parameters: [Int32]
  43. var abort: Bool
  44. init ()
  45. {
  46. position = 0
  47. code = 0
  48. currentState = .ground
  49. print = 0
  50. dcs = 0
  51. osc = []
  52. collect = []
  53. parameters = []
  54. abort = false
  55. }
  56. }
  57. enum ParserAction : UInt8 {
  58. case ignore = 0
  59. case error
  60. case print
  61. case execute
  62. case oscStart
  63. case oscPut
  64. case oscEnd
  65. case csiDispatch
  66. case param
  67. case collect
  68. case escDispatch
  69. case clear
  70. case dcsHook
  71. case dcsPut
  72. case dcsUnhook
  73. }
  74. class TransitionTable {
  75. // data is packed like this:
  76. // currentState << 8 | characterCode --> action << 4 | nextState
  77. var table: [UInt8]
  78. init (len: Int)
  79. {
  80. table = Array.init (repeating: 0, count: len)
  81. }
  82. func add (code: UInt8, state: ParserState, action: ParserAction, next: ParserState)
  83. {
  84. let v = (UInt8 (action.rawValue) << 4) | next.rawValue
  85. table [(Int (state.rawValue) << 8) | Int(code)] = v
  86. }
  87. func add (codes: [UInt8], state: ParserState, action: ParserAction, next: ParserState)
  88. {
  89. for c in codes {
  90. add (code: c, state: state, action: action, next: next)
  91. }
  92. }
  93. subscript (idx: Int) -> UInt8 {
  94. get {
  95. return table [idx]
  96. }
  97. }
  98. }
  99. protocol DcsHandler {
  100. func hook (collect: cstring, parameters: [Int], flag: UInt8)
  101. func put (data : ArraySlice<UInt8>)
  102. func unhook ()
  103. }
  104. class EscapeSequenceParser {
  105. static func r (low: UInt8, high: UInt8) -> [UInt8]
  106. {
  107. let c = high-low
  108. var ret = [UInt8]()
  109. for x in 0..<c {
  110. ret.append(low + x)
  111. }
  112. return ret;
  113. }
  114. static func rinclusive (low: ParserState, high: ParserState)-> [ParserState]
  115. {
  116. let c = high.rawValue-low.rawValue
  117. var ret = [ParserState]()
  118. for x in 0...c {
  119. ret.append(ParserState (rawValue: low.rawValue + x)!)
  120. }
  121. return ret;
  122. }
  123. static let NonAsciiPrintable : UInt8 = 0xa0
  124. static func buildVt500TransitionTable () -> TransitionTable
  125. {
  126. let table = TransitionTable(len: 4095)
  127. let states = rinclusive(low: .ground, high: .dcsPassthrough)
  128. // table with default transition
  129. for state in states {
  130. for code in 0...NonAsciiPrintable {
  131. table.add(code: code, state: state, action: .error, next: .ground)
  132. }
  133. }
  134. // printables
  135. let printables = r (low: 0x20, high: 0x7f)
  136. let executables = r (low: 0x00, high: 0x19) + r (low: 0x1c, high: 0x20)
  137. table.add (codes: printables, state: .ground, action: .print, next: .ground)
  138. // global anywhere rules
  139. for state in states {
  140. table.add (codes: [0x18, 0x1a, 0x99, 0x9a], state: state, action: .execute, next: .ground)
  141. table.add (codes: r (low: 0x80, high: 0x90), state: state, action: .execute, next: .ground)
  142. table.add (codes: r (low: 0x90, high: 0x98), state: state, action: .execute, next: .ground)
  143. table.add (code: 0x9c, state: state, action: .ignore, next: .ground) // ST as terminator
  144. table.add (code: 0x1b, state: state, action: .clear, next: .escape) // ESC
  145. table.add (code: 0x9d, state: state, action: .oscStart, next: .oscString) // OSC
  146. table.add (codes: [0x98, 0x9e, 0x9f], state: state, action: .ignore, next: .sosPmApcString)
  147. table.add (code: 0x9b, state: state, action: .clear, next: .csiEntry) // CSI
  148. table.add (code: 0x90, state: state, action: .clear, next: .dcsEntry) // DCS
  149. }
  150. // rules for executable and 0x7f
  151. table.add (codes: executables, state: .ground, action: .execute, next: .ground)
  152. table.add (codes: executables, state: .escape, action: .execute, next: .escape)
  153. table.add (code: 0x7f, state: .escape, action: .ignore, next: .escape)
  154. table.add (codes: executables, state: .oscString, action: .ignore, next: .oscString)
  155. table.add (codes: executables, state: .csiEntry, action: .execute, next: .csiEntry)
  156. table.add (code: 0x7f, state: .csiEntry, action: .ignore, next: .csiEntry)
  157. table.add (codes: executables, state: .csiParam, action: .execute, next: .csiParam)
  158. table.add (code: 0x7f, state: .csiParam, action: .ignore, next: .csiParam)
  159. table.add (codes: executables, state: .csiIgnore, action: .execute, next: .csiIgnore)
  160. table.add (codes: executables, state: .csiIntermediate, action: .execute, next: .csiIntermediate)
  161. table.add (code: 0x7f, state: .csiIntermediate, action: .ignore, next: .csiIntermediate)
  162. table.add (codes: executables, state: .escapeIntermediate, action: .execute, next: .escapeIntermediate)
  163. table.add (code: 0x7f, state: .escapeIntermediate, action: .ignore, next: .escapeIntermediate)
  164. // osc
  165. table.add (code: 0x5d, state: .escape, action: .oscStart, next: .oscString)
  166. table.add (codes: printables, state: .oscString, action: .oscPut, next: .oscString)
  167. table.add (code: 0x7f, state: .oscString, action: .oscPut, next: .oscString)
  168. table.add (codes: [0x9c, 0x1b, 0x18, 0x1a, 0x07], state: .oscString, action: .oscEnd, next: .ground)
  169. table.add (codes: r (low: 0x1c, high: 0x20), state: .oscString, action: .ignore, next: .oscString)
  170. // sos/pm/apc does nothing
  171. table.add (codes: [0x58, 0x5e, 0x5f], state: .escape, action: .ignore, next: .sosPmApcString)
  172. table.add (codes: printables, state: .sosPmApcString, action: .ignore, next: .sosPmApcString)
  173. table.add (codes: executables, state: .sosPmApcString, action: .ignore, next: .sosPmApcString)
  174. table.add (code: 0x9c, state: .sosPmApcString, action: .ignore, next: .ground)
  175. table.add (code: 0x7f, state: .sosPmApcString, action: .ignore, next: .sosPmApcString)
  176. // csi entries
  177. table.add (code: 0x5b, state: .escape, action: .clear, next: .csiEntry)
  178. table.add (codes: r (low: 0x40, high: 0x7f), state: .csiEntry, action: .csiDispatch, next: .ground)
  179. table.add (codes: r (low: 0x30, high: 0x3a), state: .csiEntry, action: .param, next: .csiParam)
  180. table.add (code: 0x3b, state: .csiEntry, action: .param, next: .csiParam)
  181. table.add (codes: [0x3c, 0x3d, 0x3e, 0x3f], state: .csiEntry, action: .collect, next: .csiParam)
  182. table.add (codes: r (low: 0x30, high: 0x3a), state: .csiParam, action: .param, next: .csiParam)
  183. table.add (code: 0x3b, state: .csiParam, action: .param, next: .csiParam)
  184. table.add (codes: r (low: 0x40, high: 0x7f), state: .csiParam, action: .csiDispatch, next: .ground)
  185. table.add (codes: [0x3c, 0x3d, 0x3e, 0x3f], state: .csiParam, action: .ignore, next: .csiIgnore)
  186. // csi for ":"
  187. table.add (code: 0x3a, state: .csiParam, action: .param, next: .csiParam)
  188. table.add (codes: r (low: 0x20, high: 0x40), state: .csiIgnore, action: .ignore, next: .csiIgnore)
  189. table.add (code: 0x7f, state: .csiIgnore, action: .ignore, next: .csiIgnore)
  190. table.add (codes: r (low: 0x40, high: 0x7f), state: .csiIgnore, action: .ignore, next: .ground)
  191. //table.Add (code: 0x3a, state: .CsiEntry, action: .Ignore, next: .CsiIgnore)
  192. table.add (codes: r (low: 0x20, high: 0x30), state: .csiEntry, action: .collect, next: .csiIntermediate)
  193. table.add (codes: r (low: 0x20, high: 0x30), state: .csiIntermediate, action: .collect, next: .csiIntermediate)
  194. table.add (codes: r (low: 0x30, high: 0x40), state: .csiIntermediate, action: .ignore, next: .csiIgnore)
  195. table.add (codes: r (low: 0x40, high: 0x7f), state: .csiIntermediate, action: .csiDispatch, next: .ground)
  196. table.add (codes: r (low: 0x20, high: 0x30), state: .csiParam, action: .collect, next: .csiIntermediate)
  197. // escIntermediate
  198. table.add (codes: r (low: 0x20, high: 0x30), state: .escape, action: .collect, next: .escapeIntermediate)
  199. table.add (codes: r (low: 0x20, high: 0x30), state: .escapeIntermediate, action: .collect, next: .escapeIntermediate)
  200. table.add (codes: r (low: 0x30, high: 0x7f), state: .escapeIntermediate, action: .escDispatch, next: .ground)
  201. table.add (codes: r (low: 0x30, high: 0x50), state: .escape, action: .escDispatch, next: .ground)
  202. table.add (codes: r (low: 0x51, high: 0x58), state: .escape, action: .escDispatch, next: .ground)
  203. table.add (codes: [0x59, 0x5a, 0x5c], state: .escape, action: .escDispatch, next: .ground)
  204. table.add (codes: r (low: 0x60, high: 0x7f), state: .escape, action: .escDispatch, next: .ground)
  205. // dcs entry
  206. table.add (code: 0x50, state: .escape, action: .clear, next: .dcsEntry)
  207. table.add (codes: executables, state: .dcsEntry, action: .ignore, next: .dcsEntry)
  208. table.add (code: 0x7f, state: .dcsEntry, action: .ignore, next: .dcsEntry)
  209. table.add (codes: r (low: 0x1c, high: 0x20), state: .dcsEntry, action: .ignore, next: .dcsEntry)
  210. table.add (codes: r (low: 0x20, high: 0x30), state: .dcsEntry, action: .collect, next: .dcsIntermediate)
  211. table.add (code: 0x3a, state: .dcsEntry, action: .ignore, next: .dcsIgnore)
  212. table.add (codes: r (low: 0x30, high: 0x3a), state: .dcsEntry, action: .param, next: .dcsParam)
  213. table.add (code: 0x3b, state: .dcsEntry, action: .param, next: .dcsParam)
  214. table.add (codes: [0x3c, 0x3d, 0x3e, 0x3f], state: .dcsEntry, action: .collect, next: .dcsParam)
  215. table.add (codes: executables, state: .dcsIgnore, action: .ignore, next: .dcsIgnore)
  216. table.add (codes: r (low: 0x20, high: 0x80), state: .dcsIgnore, action: .ignore, next: .dcsIgnore)
  217. table.add (codes: r (low: 0x1c, high: 0x20), state: .dcsIgnore, action: .ignore, next: .dcsIgnore)
  218. table.add (codes: executables, state: .dcsParam, action: .ignore, next: .dcsParam)
  219. table.add (code: 0x7f, state: .dcsParam, action: .ignore, next: .dcsParam)
  220. table.add (codes: r (low: 0x1c, high: 0x20), state: .dcsParam, action: .ignore, next: .dcsParam)
  221. table.add (codes: r (low: 0x30, high: 0x3a), state: .dcsParam, action: .param, next: .dcsParam)
  222. table.add (code: 0x3b, state: .dcsParam, action: .param, next: .dcsParam)
  223. table.add (codes: [0x3a, 0x3c, 0x3d, 0x3e, 0x3f], state: .dcsParam, action: .ignore, next: .dcsIgnore)
  224. table.add (codes: r (low: 0x20, high: 0x30), state: .dcsParam, action: .collect, next: .dcsIntermediate)
  225. table.add (codes: executables, state: .dcsIntermediate, action: .ignore, next: .dcsIntermediate)
  226. table.add (code: 0x7f, state: .dcsIntermediate, action: .ignore, next: .dcsIntermediate)
  227. table.add (codes: r (low: 0x1c, high: 0x20), state: .dcsIntermediate, action: .ignore, next: .dcsIntermediate)
  228. table.add (codes: r (low: 0x20, high: 0x30), state: .dcsIntermediate, action: .collect, next: .dcsIntermediate)
  229. table.add (codes: r (low: 0x30, high: 0x40), state: .dcsIntermediate, action: .ignore, next: .dcsIgnore)
  230. table.add (codes: r (low: 0x40, high: 0x7f), state: .dcsIntermediate, action: .dcsHook, next: .dcsPassthrough)
  231. table.add (codes: r (low: 0x40, high: 0x7f), state: .dcsParam, action: .dcsHook, next: .dcsPassthrough)
  232. table.add (codes: r (low: 0x40, high: 0x7f), state: .dcsEntry, action: .dcsHook, next: .dcsPassthrough)
  233. table.add (codes: executables, state: .dcsPassthrough, action: .dcsPut, next: .dcsPassthrough)
  234. table.add (codes: printables, state: .dcsPassthrough, action: .dcsPut, next: .dcsPassthrough)
  235. table.add (code: 0x7f, state: .dcsPassthrough, action: .ignore, next: .dcsPassthrough)
  236. table.add (codes: [0x1b, 0x9c], state: .dcsPassthrough, action: .dcsUnhook, next: .ground)
  237. table.add (code: NonAsciiPrintable, state: .oscString, action: .oscPut, next: .oscString)
  238. return table
  239. }
  240. // Array of parameters, and "collect" string
  241. typealias CsiHandler = ([Int],cstring) -> ()
  242. typealias CsiHandlerFallback = ([Int],cstring,UInt8) -> ()
  243. // String with payload
  244. typealias OscHandler = (ArraySlice<UInt8>) -> ()
  245. typealias OscHandlerFallback = (Int) -> ()
  246. typealias DscHandlerFallback = (UInt8, [Int]) -> ()
  247. // Collect + flag
  248. typealias EscHandler = (cstring, UInt8) -> ()
  249. typealias EscHandlerFallback = (cstring, UInt8) -> ()
  250. // Range of bytes to print out
  251. typealias PrintHandler = (ArraySlice<UInt8>) -> ()
  252. typealias ExecuteHandler = () -> ()
  253. // Handlers
  254. var csiHandlers: [UInt8:CsiHandler] = [:]
  255. var oscHandlers: [Int:OscHandler] = [:]
  256. var executeHandlers: [UInt8:ExecuteHandler] = [:]
  257. var escHandlers: [cstring:EscHandler] = [:]
  258. var dcsHandlers: [cstring:DcsHandler] = [:]
  259. var activeDcsHandler: DcsHandler? = nil
  260. var errorHandler: (ParsingState) -> ParsingState = { (state : ParsingState) -> ParsingState in return state; }
  261. var initialState: ParserState = .ground
  262. var currentState: ParserState = .ground
  263. // buffers over several calls
  264. var _osc: cstring
  265. var _pars: [Int]
  266. var _parsTxt: [UInt8]
  267. var _collect: cstring
  268. var printHandler: PrintHandler = { (slice : ArraySlice<UInt8>) -> () in }
  269. var printStateReset: () -> () = { }
  270. var table: TransitionTable
  271. init ()
  272. {
  273. table = EscapeSequenceParser.buildVt500TransitionTable()
  274. _osc = []
  275. _pars = [0]
  276. _parsTxt = []
  277. _collect = []
  278. // "\"
  279. setEscHandler("\\", { collect, flag in })
  280. }
  281. var escHandlerFallback: EscHandlerFallback = { (collect: cstring, flag: UInt8) in
  282. }
  283. func setEscHandler (_ flag: String, _ callback: @escaping EscHandler)
  284. {
  285. escHandlers [Array (flag.utf8)] = callback
  286. }
  287. func setCsiHandler (_ flag: String, _ callback: @escaping CsiHandler)
  288. {
  289. csiHandlers [flag.first!.asciiValue!] = callback
  290. }
  291. func setDcsHandler (_ flag: String, _ callback: DcsHandler)
  292. {
  293. dcsHandlers [Array (flag.utf8)] = callback
  294. }
  295. var dscHandlerFallback: DscHandlerFallback = { code, pars in }
  296. var executeHandlerFallback : ExecuteHandler = { () -> () in
  297. }
  298. var csiHandlerFallback : CsiHandlerFallback = { (pars: [Int], collect: cstring, code: UInt8) -> () in
  299. print ("Cannot handle ESC-\(code)")
  300. }
  301. var oscHandlerFallback: OscHandlerFallback = { (code: Int) -> () in
  302. }
  303. func reset ()
  304. {
  305. currentState = initialState
  306. _osc = []
  307. _pars = [0]
  308. _collect = []
  309. activeDcsHandler = nil
  310. printStateReset()
  311. }
  312. var logFileCounter = 1
  313. func dump (_ data: ArraySlice<UInt8>)
  314. {
  315. let dir = "/tmp"
  316. let path = dir + "/log-\(logFileCounter)"
  317. do {
  318. let dataCopy = Data (data)
  319. try dataCopy.write(to: URL.init(fileURLWithPath: path))
  320. logFileCounter += 1
  321. } catch {
  322. // Ignore write error
  323. //print ("Got error while logging data dump to \(path)")
  324. }
  325. }
  326. func parse (data: ArraySlice<UInt8>)
  327. {
  328. var code : UInt8 = 0
  329. var transition : UInt8 = 0
  330. var error = false
  331. var currentState = self.currentState
  332. var print = -1
  333. var dcs = -1
  334. var osc = self._osc
  335. var collect = self._collect
  336. var pars = self._pars
  337. var parsTxt = self._parsTxt
  338. var dcsHandler = activeDcsHandler
  339. //dump (data)
  340. // process input string
  341. var i = data.startIndex
  342. // let len = data.count
  343. let end = data.endIndex
  344. while i < end {
  345. code = data [i]
  346. // 1f..80 are printable ascii characters
  347. // c2..f3 are valid utf8 beginning of sequence elements, and most importantly,
  348. // does not cover 0x90 which is the DCS initiator in 8 bit mode.
  349. // The nice code is commented out, because this ends up consuming valid utf8 code when
  350. // we are in the middle of things (force a small reading buffer to see more easily)
  351. if currentState == .ground && code > 0x1f { // }(code > 0x1f && code < 0x80 || (code > 0xc2 && code < 0xf3)) {
  352. print = (~print != 0) ? print : i
  353. repeat {
  354. i += 1
  355. } while i < end && data [i] > 0x1f
  356. continue;
  357. }
  358. // shortcut for CSI params
  359. if currentState == .csiParam && (code > 0x2f && code < 0x39) {
  360. let newV = pars [pars.count - 1] * 10 + Int(code) - 48
  361. // Prevent attempts at overflowing - crash
  362. let willOverflow = newV > ((Int.max/10)-10)
  363. pars [pars.count - 1] = willOverflow ? 0 : newV
  364. parsTxt.append(code)
  365. i += 1
  366. continue
  367. }
  368. // Normal transition and action loop
  369. transition = table [(Int(currentState.rawValue) << 8) | Int (UInt8 ((code < 0xa0 ? code : EscapeSequenceParser.NonAsciiPrintable)))]
  370. let action = ParserAction (rawValue: transition >> 4)!
  371. switch action {
  372. case .print:
  373. print = (~print != 0) ? print : i
  374. case .execute:
  375. if ~print != 0 {
  376. printHandler (data [print..<i])
  377. print = -1
  378. }
  379. if let callback = executeHandlers [code] {
  380. callback ()
  381. } else {
  382. // executeHandlerFallback (code)
  383. }
  384. case .ignore:
  385. // handle leftover print or dcs chars
  386. if ~print != 0 {
  387. printHandler (data [print..<i])
  388. print = -1
  389. } else if ~dcs != 0 {
  390. dcsHandler?.put (data: data [dcs..<i])
  391. dcs = -1
  392. }
  393. case .error:
  394. // chars higher than 0x9f are handled by this action
  395. // to keep the transition table small
  396. if code > 0x9f {
  397. switch (currentState) {
  398. case .ground:
  399. print = (~print != 0) ? print : i;
  400. case .csiIgnore:
  401. transition |= ParserState.csiIgnore.rawValue;
  402. case .dcsIgnore:
  403. transition |= ParserState.dcsIgnore.rawValue;
  404. case .dcsPassthrough:
  405. dcs = (~dcs != 0) ? dcs : i;
  406. transition |= ParserState.dcsPassthrough.rawValue;
  407. break;
  408. default:
  409. error = true;
  410. break;
  411. }
  412. } else {
  413. error = true;
  414. }
  415. // if we end up here a real error happened
  416. if error {
  417. let state = ParsingState ()
  418. state.position = i
  419. state.code = code
  420. state.currentState = currentState
  421. state.print = print
  422. state.dcs = dcs
  423. state.osc = osc
  424. state.collect = collect
  425. let inject = errorHandler (state)
  426. if inject.abort {
  427. return;
  428. }
  429. error = false;
  430. }
  431. case .csiDispatch:
  432. // Trigger CSI handler
  433. if let handler = csiHandlers [code] {
  434. _parsTxt = parsTxt
  435. handler (pars, collect)
  436. } else {
  437. csiHandlerFallback (pars, collect, code)
  438. }
  439. case .param:
  440. parsTxt.append(code)
  441. if code == 0x3b || code == 0x3a {
  442. pars.append (0)
  443. } else {
  444. let newV = pars [pars.count - 1] * 10 + Int(code) - 48
  445. // Prevent attempts at overflowing - crash
  446. let willOverflow = newV > ((Int.max/10)-10)
  447. pars [pars.count - 1] = willOverflow ? 0 : newV
  448. }
  449. case .escDispatch:
  450. if let handler = escHandlers [collect + [code]] {
  451. handler (collect, code)
  452. } else {
  453. escHandlerFallback(collect, code)
  454. }
  455. case .collect:
  456. collect.append (code)
  457. case .clear:
  458. if ~print != 0 {
  459. printHandler (data [print..<i])
  460. print = -1
  461. }
  462. osc = []
  463. pars = [0]
  464. parsTxt = []
  465. collect = []
  466. dcs = -1
  467. printStateReset()
  468. case .dcsHook:
  469. if let dcs = dcsHandlers [collect + [code]] {
  470. dcsHandler = dcs
  471. dcs.hook (collect: collect, parameters: pars, flag: code)
  472. }
  473. // FIXME: perhaps have a fallback?
  474. break
  475. case .dcsPut:
  476. dcs = (~dcs != 0) ? dcs : i
  477. case .dcsUnhook:
  478. if let d = dcsHandler {
  479. if ~dcs != 0 {
  480. d.put (data: data[dcs..<i])
  481. d.unhook ()
  482. dcsHandler = nil
  483. }
  484. }
  485. if code == 0x1b {
  486. transition |= ParserState.escape.rawValue
  487. }
  488. osc = []
  489. pars = [0]
  490. parsTxt = []
  491. collect = []
  492. dcs = -1
  493. printStateReset()
  494. case .oscStart:
  495. if ~print != 0 {
  496. printHandler (data[print..<i])
  497. print = -1
  498. }
  499. osc = []
  500. case .oscPut:
  501. var j = i
  502. while j < end {
  503. let c = data [j]
  504. if c == ControlCodes.BEL || c == ControlCodes.CAN || c == ControlCodes.ESC {
  505. break
  506. } else if c >= 0x20 {
  507. osc.append (c)
  508. }
  509. j += 1
  510. }
  511. i = j - 1
  512. case .oscEnd:
  513. if osc.count != 0 && code != ControlCodes.CAN && code != ControlCodes.SUB {
  514. // NOTE: OSC subparsing is not part of the original parser
  515. // we do basic identifier parsing here to offer a jump table for OSC as well
  516. var oscCode : Int
  517. var content : ArraySlice<UInt8>
  518. let semiColonAscii = 59 // ';'
  519. if let idx = osc.firstIndex (of: UInt8(semiColonAscii)){
  520. oscCode = EscapeSequenceParser.parseInt (osc [0..<idx])
  521. content = osc [(idx+1)...]
  522. } else {
  523. oscCode = EscapeSequenceParser.parseInt (osc[0...])
  524. content = []
  525. }
  526. if let handler = oscHandlers [oscCode] {
  527. handler (content)
  528. } else {
  529. oscHandlerFallback (oscCode)
  530. }
  531. }
  532. if code == 0x1b {
  533. transition |= ParserState.escape.rawValue
  534. }
  535. osc = []
  536. pars = [0]
  537. parsTxt = []
  538. collect = []
  539. dcs = -1
  540. printStateReset()
  541. }
  542. currentState = ParserState (rawValue: transition & 15)!
  543. i += 1
  544. }
  545. // push leftover pushable buffers to terminal
  546. if currentState == .ground && (~print != 0) {
  547. printHandler (data [print..<end])
  548. } else if currentState == .dcsPassthrough && (~dcs != 0) && dcsHandler != nil {
  549. dcsHandler!.put (data: data [dcs..<end])
  550. }
  551. // save non pushable buffers
  552. _osc = osc
  553. _collect = collect
  554. _pars = pars
  555. _parsTxt = parsTxt
  556. // save active dcs handler reference
  557. activeDcsHandler = dcsHandler
  558. // save state
  559. self.currentState = currentState
  560. }
  561. static func parseInt (_ str: ArraySlice<UInt8>) -> Int
  562. {
  563. var result = 0
  564. for x in str {
  565. if x < 48 || x > 57 {
  566. return result
  567. }
  568. let newV = result * 10 + Int ((x - 48))
  569. let willOverflow = newV > ((Int.max/10)-10)
  570. if willOverflow {
  571. return 0
  572. }
  573. result = newV
  574. }
  575. return result
  576. }
  577. }