From 766fe57bf1b86254b7f54050b8d78345e8cdc1f3 Mon Sep 17 00:00:00 2001 From: Marcin Kulik Date: Fri, 2 Mar 2012 20:54:43 +0100 Subject: [PATCH] New parser --- app/assets/javascripts/player/vt.js.coffee | 678 ++++++++++++++------- 1 file changed, 457 insertions(+), 221 deletions(-) diff --git a/app/assets/javascripts/player/vt.js.coffee b/app/assets/javascripts/player/vt.js.coffee index 09791ee..dbf4034 100644 --- a/app/assets/javascripts/player/vt.js.coffee +++ b/app/assets/javascripts/player/vt.js.coffee @@ -5,257 +5,477 @@ class AsciiIo.VT @data = '' @resetTerminal() @render() - @compilePatterns() + # @compilePatterns() + + handleData: (data) -> + if data.match(/^\x1b[\x00-\x1f]/) + @handleControlCharacter(data[1]) + return 2 + + else if match = data.match(/^(\x1b\x5d|\x9d).*?(\x1b\\\\|\x9c|\x07)/) + # OSC seq + return match[0].length + + else if match = data.match(/^(\x1b[PX_^]|[\x90\x98\x9e\x9f]).*?(\x1b\\\\|\x9c)/) + # DCS/SOS/PM/APC seq + return match[0].length + + else if match = data.match(/^(?:\x1b\x5b|\x9b)([\x30-\x3f]*?)[\x20-\x2f]*?[\x40-\x7e]/) + # Control sequences + @handleControlSequence(match[0], match[1], match) + return match[0].length + + else if match = data.match(/^\x1b[\x20-\x2f]*?[\x30-\x3f]/) + @handlePrivateEscSeq(match[0]) + return match[0].length + + else if match = data.match(/^\x1b[\x20-\x2f]*?[\x40-\x5a\x5c-\x7e]/) # excluding \x5b "[" + @handleStandardEscSeq(match[0]) + return match[0].length + + else if data.match(/^\x1b\x7f/) # DELETE + return 2 + + else if data.match(/^[\x00-\x1a\x1c-\x1f]/) # excluding \x1b "ESC" + @handleControlCharacter(data[0]) + return 1 + + else if match = data.match(/^([\x20-\x7e]|\xe2..|[\xa1-\xfe])+/) + @handlePrintableCharacters(match[0]) + return match[0].length + + else if data[0] is "\x7f" + # DELETE, always and everywhere ignored + return 1 + + else if data.match(/^[\x80-\x9f]/) + @handleControlCharacter(data[0]) + return 1 + + else if data[0] is "\xa0" + # Same as SPACE (\x20) + @handlePrintableCharacters(' ') + return 1 + + else if data[0] is "\xff" + # Same as DELETE (\x7f) + return 1 + + else + return 0 + + handleControlCharacter: (char) -> + switch char + when "\x07" + @bell() + when "\x08" + @backspace() + when "\x09" + @buffer.goToNextHorizontalTabStop() + # @tab() + when "\x0a" + @lineFeed() + when "\x0b" + @verticalTab() + when "\x0c" + @formFeed() + when "\x0d" + @carriageReturn() + + when "\x84" + @index() # "D" + when "\x85" + @newLine() # "E" + when "\x88" + @setHorizontalTabStop() # "H" + when "\x8d" + @reverseIndex() # "M" - noop: -> - - # handleC0ControlSet: (data) -> - # console.log 'handling C0' handlePrintableCharacters: (text) -> + @buffer.print text + + handleStandardEscSeq: (data) -> + last = data[data.length - 1] + + if last.match(/[\x40-\x5f]/) + # convert to C1 + char = String.fromCharCode(last.charCodeAt(0) + 0x40) + @handleControlCharacter(char) + else + switch last + when "c" + @resetTerminal() + + handlePrivateEscSeq: (data) -> + last = data[data.length - 1] + + switch last + when "7" + @saveTerminalState() + when "8" + @restoreTerminalState() + + handleControlSequence: (data, params, match) -> + if params and params.match(/^[\x3c-\x3f]/) + @handlePrivateControlSequence(data, params) + else + @handleStandardControlSequence(data, params) + + handleStandardControlSequence: (data, params) -> + term = data[data.length - 1] + + numbers = @parseParams(params) + n = numbers[0] + m = numbers[1] - # handleC1ControlSet: (data) -> - - # handleControlSequence: (data) -> - - _C0_PATTERNS: - # C0 set of 7-bit control characters - "[\x00-\x1f]": - # bell - "\x07": (data) -> @bell() - - # backspace - "\x08": (data) -> @backspace() - - # Move the cursor to the next tab stop - "\x09": (data) -> - - "\x0a": (data) -> @lineFeed() - - "\x0d": (data) -> @carriageReturn() - - "\x0e": (data) -> - - "\x0f": (data) -> - - # Reserved (?) - "\x82": (data) -> - - # Cancel Character, ignore previous character - "\x94": (data) -> - - # Escape sequence - "\x1b": _.extend({}, @_C0_PATTERNS, { - - # Control sequence - "\x1b\\[": @_CS_PATTERNS - }) - - _CS_PATTERNS: - "sth": 1 - - _PATTERNS: _.extend({}, @_C0_PATTERNS, { - # Printable characters - "([\x20-\x7e])+": @handlePrintableCharacters - - # "Delete", always and everywhere ignored - "[\x7f\xff]": @noop - - # C1 control set - "[\x80-\x9f]": - - # Control sequence - "\x9b": @_CS_PATTERNS - - # G1 Displayable, 94 additional displayable characters - "[\xa1-\xfe]": @handlePrintableCharacters - - # Always and everywhere a blank space - "\xa0": -> @handlePrintableCharacters(' ') - - }) - - PATTERNS: - "\x00": (data) -> - "\x07": (data) -> @bell() - "\x08": (data) -> @backspace() - "\x09": (data) -> @buffer.goToNextHorizontalTabStop() - "\x0a": (data) -> @lineFeed() - "\x0b": (data) -> @verticalTab() - "\x0c": (data) -> @formFeed() - "\x0d": (data) -> @carriageReturn() - "\x0e": (data) -> - "\x0f": (data) -> - "\x82": (data) -> # Reserved (?) - "\x85": (data) -> @setHorizontalTabStop() - "\x94": (data) -> # Cancel Character, ignore previous character - - # 20 - 7e - "([\x20-\x7e]|\xe2..|[\xc2\xc4\xc5].)+": (data, match) -> - @buffer.print match[0] - - "\x1b\\(B": (data) -> # SCS (Set G0 Character SET) - - "\x1b\\[([0-9;]*)([\x40-\x7e])": (data, match) -> - if match[1].length == 0 - @params = [] - else - @params = _(match[1].split(';')).map (n) -> if n is '' then undefined else parseInt(n) - - @n = @params[0] - @m = @params[1] - @handleCS match[2] - - # private standards - "\x1b\\[\\?([\x30-\x3f]+)([hlsr])": (data, match) -> - # h = Sets DEC/xterm specific mode (http://ttssh2.sourceforge.jp/manual/en/about/ctrlseq.html#decmode) - # l = Resets mode (http://ttssh2.sourceforge.jp/manual/en/about/ctrlseq.html#mode) - # 1001 + s = ? - # 1001 + r = ? - modes = match[1].split(";") - action = match[2] - mode = undefined - - for mode in modes - if mode is "1" - # 1 + h / l = cursor keys stuff - else if mode is "5" - # Reverse/normal video - ignoring - else if mode is "7" - # Enables/disables autowrap mode - else if mode is "12" - if action is "h" - # blinking cursor - else action is "l" - # steady cursor - else if mode is "25" - if action is "h" - @showCursor() - else if action is "l" - @hideCursor() - else if mode is "47" - if action is "h" - @switchToAlternateBuffer() - else if action is "l" - @switchToNormalBuffer() - else if mode is "1000" - # Enables/disables normal mouse tracking - else if mode is "1001" - # pbly sth with mouse/keys... - else if mode is "1002" - # 2002 + h / l = mouse tracking stuff - else if mode is "1049" - if action is "h" - # Save cursor position, switch to alternate screen buffer, and clear screen. - @switchToAlternateBuffer() - @clearScreen() - else if action is "l" - # Clear screen, switch to normal screen buffer, and restore cursor position. - @clearScreen() - @switchToNormalBuffer() - else - throw "unknown mode: " + mode + action - - "\x1b\x3d": (data) -> # DECKPAM - Set keypad to applications mode (ESCape instead of digits) - - "\x1b\x3e": (data) -> # DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq) - - "\x1b\\\\": -> # String Terminator (VT125 exits graphics) - - "\x1b\x5d[012]\x3b.*?\x07": (data, match) -> # OSC - Operating System Command (terminal title) - - "\x1b\\[>c": (data) -> # Secondary Device Attribute request (?) - - "\x1bc": -> @resetTerminal() - "\x1bP([^\\\\])*?\\\\": (data) -> # DCS, Device Control String - "\x1bD": -> @index() - "\x1bE": -> @newLine() - "\x1bH": -> @setHorizontalTabStop() - "\x1bM": -> @reverseIndex() - "\x1bk": -> # NAPLPS lock-shift G1 to GR - - "\x1b7": (data) -> # save cursor pos and char attrs - @saveTerminalState() - - "\x1b8": (data) -> # restore cursor pos and char attrs - @restoreTerminalState() - - handleCS: (term) -> switch term when "@" - @buffer.insertCharacters @n + @buffer.insertCharacters n when "A" - @buffer.priorRow @n + @buffer.priorRow n when "B" - @buffer.nextRow @n + @buffer.nextRow n when "C" - @buffer.nextColumn @n + @buffer.nextColumn n when "D" - @buffer.priorColumn @n + @buffer.priorColumn n when "E" - @buffer.nextRowFirstColumn @n + @buffer.nextRowFirstColumn n when "F" - @buffer.priorRowFirstColumn @n + @buffer.priorRowFirstColumn n when "G" - @buffer.goToColumn @n + @buffer.goToColumn n when "H" - @buffer.goToRowAndColumn @n, @m + @buffer.goToRowAndColumn n, m when "I" - @buffer.goToNextHorizontalTabStop @n + @buffer.goToNextHorizontalTabStop n when "J" - if @n is 2 + if n is 2 @buffer.eraseScreen() - else if @n is 1 + else if n is 1 @buffer.eraseFromScreenStart() else @buffer.eraseToScreenEnd() when "K" - if @n is 2 + if n is 2 @buffer.eraseRow() - else if @n is 1 + else if n is 1 @buffer.eraseFromRowStart() else @buffer.eraseToRowEnd() when "L" - @buffer.insertLine @n or 1 + @buffer.insertLine n or 1 when "M" - @buffer.deleteLine @n or 1 + @buffer.deleteLine n or 1 when "P" # DCH - Delete Character, from current position to end of field - @buffer.deleteCharacters @n or 1 + @buffer.deleteCharacters n or 1 when "S" - @buffer.scrollUp @n + @buffer.scrollUp n when "T" - @buffer.scrollDown @n + @buffer.scrollDown n when "X" - @buffer.eraseCharacters @n + @buffer.eraseCharacters n when "Z" - @buffer.goToPriorHorizontalTabStop @n + @buffer.goToPriorHorizontalTabStop n when "b" - @buffer.repeatLastCharacter @n + @buffer.repeatLastCharacter n when "d" # VPA - Vertical Position Absolute - @buffer.goToRow @n + @buffer.goToRow n when "f" - @buffer.goToRowAndColumn @n, @m + @buffer.goToRowAndColumn n, m when "g" - if !@n or @n is 0 + if !n or n is 0 @buffer.clearHorizontalTabStop() - else if @n is 3 + else if n is 3 @buffer.clearAllHorizontalTabStops() when "l" # l, Reset mode - console.log "(TODO) reset: " + @n + console.log "(TODO) reset: " + n when "m" - @handleSGR @params + @handleSGR numbers when "n" @reportRowAndColumn() when "r" # Set top and bottom margins (scroll region on VT100) - @setScrollRegion @n or 0, @m or @lines - 1 - when "^" - # reserved - # Privacy Message (password verification), terminated by ST - # TODO + @setScrollRegion n or 0, m or @lines - 1 else throw "no handler for CSI term: " + term + + handlePrivateControlSequence: (data, params) -> + action = data[data.length - 1] + modes = @parseParams(params) + + console.log "private: #{@formattedData(data)}, action: #{action}, modes: #{modes}" + for mode in modes + if mode is 25 + if action is "h" + @showCursor() + else if action is "l" + @hideCursor() + else if mode is 47 + if action is "h" + @switchToAlternateBuffer() + else if action is "l" + @switchToNormalBuffer() + else if mode is 1049 + if action is "h" + # Save cursor position, switch to alternate screen buffer, and clear screen. + console.log 'switching' + @switchToAlternateBuffer() + @clearScreen() + else if action is "l" + # Clear screen, switch to normal screen buffer, and restore cursor position. + console.log 'switching back' + @clearScreen() + @switchToNormalBuffer() + # else + # throw "unknown mode: " + mode + action + + parseParams: (params) -> + if params.length is 0 + numbers = [] + else + numbers = _(params.replace(/[^0-9;]/, '').split(';')).map (n) -> if n is '' then undefined else parseInt(n) + + numbers + +# _C0_PATTERNS: +# default: (data) -> +# console.log "no handler for: #{@formattedData(data)}" + +# # C0 set of 7-bit control characters +# "\x1b?[\x00-\x1f]": +# # bell +# "\x07": (data) -> @bell() + +# # backspace +# "\x08": (data) -> @backspace() + +# # Move the cursor to the next tab stop +# "\x09": (data) -> + +# "\x0a": (data) -> @lineFeed() + +# "\x0d": (data) -> @carriageReturn() + +# "\x0e": (data) -> + +# "\x0f": (data) -> + +# # Reserved (?) +# "\x82": (data) -> + +# # Cancel Character, ignore previous character +# "\x94": (data) -> + +# # # Escape sequence +# # "\x1b": _.extend({}, @_C0_PATTERNS, { + +# # Control sequence +# "\x1b\\[": @_CS_PATTERNS + +# _CS_PATTERNS: +# "sth": 1 + +# _PATTERNS: _.extend({}, @_C0_PATTERNS, { +# # Printable characters +# "([\x20-\x7e])+": @handlePrintableCharacters + +# # "Delete", always and everywhere ignored +# "[\x7f\xff]": @noop + +# # C1 control set +# "[\x80-\x9f]": + +# # Control sequence +# "\x9b": @_CS_PATTERNS + +# # G1 Displayable, 94 additional displayable characters +# "[\xa1-\xfe]": @handlePrintableCharacters + +# # Always and everywhere a blank space +# "\xa0": -> @handlePrintableCharacters(' ') + +# }) + + # PATTERNS: + # "\x00": (data) -> + # "\x07": (data) -> @bell() + # "\x08": (data) -> @backspace() + # "\x09": (data) -> @buffer.goToNextHorizontalTabStop() + # "\x0a": (data) -> @lineFeed() + # "\x0b": (data) -> @verticalTab() + # "\x0c": (data) -> @formFeed() + # "\x0d": (data) -> @carriageReturn() + # "\x0e": (data) -> + # "\x0f": (data) -> + + # # 20 - 7e + # "([\x20-\x7e]|\xe2..|[\xc2\xc4\xc5].)+": (data, match) -> + # @buffer.print match[0] + + # "\x1b\\(B": (data) -> # SCS (Set G0 Character SET) + + # "\x1b\\[([0-9;]*)([\x40-\x7e])": (data, match) -> + # if match[1].length == 0 + # @params = [] + # else + # @params = _(match[1].split(';')).map (n) -> if n is '' then undefined else parseInt(n) + + # @n = @params[0] + # @m = @params[1] + # @handleCS match[2] + +# # private standards +# "\x1b\\[\\?([\x30-\x3f]+)([hlsr])": (data, match) -> +# # h = Sets DEC/xterm specific mode (http://ttssh2.sourceforge.jp/manual/en/about/ctrlseq.html#decmode) +# # l = Resets mode (http://ttssh2.sourceforge.jp/manual/en/about/ctrlseq.html#mode) +# # 1001 + s = ? +# # 1001 + r = ? +# modes = match[1].split(";") +# action = match[2] +# mode = undefined + +# for mode in modes +# if mode is "1" +# # 1 + h / l = cursor keys stuff +# else if mode is "5" +# # Reverse/normal video - ignoring +# else if mode is "7" +# # Enables/disables autowrap mode +# else if mode is "12" +# if action is "h" +# # blinking cursor +# else action is "l" +# # steady cursor +# else if mode is "25" +# if action is "h" +# @showCursor() +# else if action is "l" +# @hideCursor() +# else if mode is "47" +# if action is "h" +# @switchToAlternateBuffer() +# else if action is "l" +# @switchToNormalBuffer() +# else if mode is "1000" +# # Enables/disables normal mouse tracking +# else if mode is "1001" +# # pbly sth with mouse/keys... +# else if mode is "1002" +# # 2002 + h / l = mouse tracking stuff +# else if mode is "1049" +# if action is "h" +# # Save cursor position, switch to alternate screen buffer, and clear screen. +# @switchToAlternateBuffer() +# @clearScreen() +# else if action is "l" +# # Clear screen, switch to normal screen buffer, and restore cursor position. +# @clearScreen() +# @switchToNormalBuffer() +# else +# throw "unknown mode: " + mode + action + + # "\x1b\x3d": (data) -> # DECKPAM - Set keypad to applications mode (ESCape instead of digits) + + # "\x1b\x3e": (data) -> # DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq) + + # "\x1b\\\\": -> # String Terminator (VT125 exits graphics) + + # "\x1b\x5d[012]\x3b.*?\x07": (data, match) -> # OSC - Operating System Command (terminal title) + + # "\x1b\\[>c": (data) -> # Secondary Device Attribute request (?) + + # "\x1bc": -> @resetTerminal() + # "\x1bP([^\\\\])*?\\\\": (data) -> # DCS, Device Control String + # "\x1bD": -> @index() + # "\x1bE": -> @newLine() + # "\x1bH": -> @setHorizontalTabStop() + # "\x1bM": -> @reverseIndex() + # "\x1bk": -> # NAPLPS lock-shift G1 to GR + + # "\x1b7": (data) -> # save cursor pos and char attrs + # @saveTerminalState() + + # "\x1b8": (data) -> # restore cursor pos and char attrs + # @restoreTerminalState() + + # handleCS: (term) -> + # switch term + # when "@" + # @buffer.insertCharacters @n + # when "A" + # @buffer.priorRow @n + # when "B" + # @buffer.nextRow @n + # when "C" + # @buffer.nextColumn @n + # when "D" + # @buffer.priorColumn @n + # when "E" + # @buffer.nextRowFirstColumn @n + # when "F" + # @buffer.priorRowFirstColumn @n + # when "G" + # @buffer.goToColumn @n + # when "H" + # @buffer.goToRowAndColumn @n, @m + # when "I" + # @buffer.goToNextHorizontalTabStop @n + # when "J" + # if @n is 2 + # @buffer.eraseScreen() + # else if @n is 1 + # @buffer.eraseFromScreenStart() + # else + # @buffer.eraseToScreenEnd() + # when "K" + # if @n is 2 + # @buffer.eraseRow() + # else if @n is 1 + # @buffer.eraseFromRowStart() + # else + # @buffer.eraseToRowEnd() + # when "L" + # @buffer.insertLine @n or 1 + # when "M" + # @buffer.deleteLine @n or 1 + # when "P" # DCH - Delete Character, from current position to end of field + # @buffer.deleteCharacters @n or 1 + # when "S" + # @buffer.scrollUp @n + # when "T" + # @buffer.scrollDown @n + # when "X" + # @buffer.eraseCharacters @n + # when "Z" + # @buffer.goToPriorHorizontalTabStop @n + # when "b" + # @buffer.repeatLastCharacter @n + # when "d" # VPA - Vertical Position Absolute + # @buffer.goToRow @n + # when "f" + # @buffer.goToRowAndColumn @n, @m + # when "g" + # if !@n or @n is 0 + # @buffer.clearHorizontalTabStop() + # else if @n is 3 + # @buffer.clearAllHorizontalTabStops() + # when "l" # l, Reset mode + # console.log "(TODO) reset: " + @n + # when "m" + # @handleSGR @params + # when "n" + # @reportRowAndColumn() + # when "r" # Set top and bottom margins (scroll region on VT100) + # @setScrollRegion @n or 0, @m or @lines - 1 + # when "^" + # # reserved + # # Privacy Message (password verification), terminated by ST + # # TODO + # else + # throw "no handler for CSI term: " + term + handleSGR: (numbers) -> @buffer.setBrush @sgrInterpreter.buildBrush(@buffer.brush, numbers) @@ -263,35 +483,50 @@ class AsciiIo.VT @view.visualBell() # @trigger('bell') - compilePatterns: -> - @COMPILED_PATTERNS = ([new RegExp("^" + re), f] for re, f of @PATTERNS) + # compilePatterns: -> + # @COMPILED_PATTERNS = ([new RegExp("^" + re), f] for re, f of @PATTERNS) feed: (data) -> @data += data while @data.length > 0 - match = null + processed = @handleData(@data) - for pattern in @COMPILED_PATTERNS - match = pattern[0].exec(@data) + if processed is 0 + # console.log "no kurwa: #{@formattedData(@data)}" + break + # throw 'up' - if match - handler = pattern[1] - handler.call(this, @data, match) - @data = @data.slice(match[0].length) - break + @data = @data.slice(processed) - break unless match + # match = null - @render() + # for pattern in @COMPILED_PATTERNS + # match = pattern[0].exec(@data) - if @data.length > 500 - head = @data.slice(0, 100) - hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head) - console.log "failed matching: '" + Utf8.decode(head) + "' (" + hex.join() + ")" # [pos: " + (@dataIndex - count) + "]" + # if match + # handler = pattern[1] + # handler.call(this, @data, match) + # @data = @data.slice(match[0].length) + # break + + # break unless match + + @render() @data.length is 0 + # aaa: -> + # if @data.length > 500 + # head = @data.slice(0, 100) + # hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head) + # console.log "failed matching: '" + Utf8.decode(head) + "' (" + hex.join() + ")" # [pos: " + (@dataIndex - count) + "]" + + formattedData: (data) -> + head = data.slice(0, 100) + hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head) + Utf8.decode(head) + " (" + hex.join() + ")" + render: -> @view.render(@buffer.changes(), @buffer.cursorX, @buffer.cursorY) @buffer.clearChanges() @@ -391,3 +626,4 @@ class AsciiIo.VT reportRowAndColumn: -> # http://www.shaels.net/index.php/propterm/documents +# http://manpages.ubuntu.com/manpages/lucid/man7/urxvt.7.html