2015-11-30 14:16:33 +08:00

468 lines
12 KiB
JavaScript

/**
* Created by nuintun on 2015/11/30.
*/
'use strict';
/**
* range function for numbers [a, .., b-1]
*
* @param {number} a
* @param {number} b
* @return {Array}
*/
function r(a, b){
var c = b - a;
var arr = new Array(c);
while (c--) {
arr[c] = --b;
}
return arr;
}
/**
* Add a transition to the transition table.
*
* @param table - table to add transition
* @param {number} inp - input character code
* @param {number} state - current state
* @param {number=} action - action to be taken
* @param {number=} next - next state
*/
function add(table, inp, state, action, next){
table[state << 8 | inp] = ((action | 0) << 4) | ((next === undefined) ? state : next);
}
/**
* Add multiple transitions to the transition table.
*
* @param table - table to add transition
* @param {Array} inps - array of input character codes
* @param {number} state - current state
* @param {number=} action - action to be taken
* @param {number=} next - next state
*/
function add_list(table, inps, state, action, next){
for (var i = 0; i < inps.length; i++)
add(table, inps[i], state, action, next);
}
/** global definition of printables and executables */
var PRINTABLES = r(0x20, 0x7f);
var EXECUTABLES = r(0x00, 0x18);
EXECUTABLES.push(0x19);
EXECUTABLES.concat(r(0x1c, 0x20));
/**
* create the standard transition table - used by all parser instances
*
* table[state << 8 | character code] = action << 4 | next state
*
* - states are numbers from 0 to 13
* - control character codes defined from 0 to 159 (C0 and C1)
* - actions are numbers from 0 to 14
* - any higher character than 159 is handled by the 'error' action
*
* state replacements (14 states):
* 'GROUND' -> 0
* 'ESCAPE' -> 1
* 'ESCAPE_INTERMEDIATE' -> 2
* 'CSI_ENTRY' -> 3
* 'CSI_PARAM' -> 4
* 'CSI_INTERMEDIATE' -> 5
* 'CSI_IGNORE' -> 6
* 'SOS_PM_APC_STRING' -> 7
* 'OSC_STRING' -> 8
* 'DCS_ENTRY' -> 9
* 'DCS_PARAM' -> 10
* 'DCS_IGNORE' -> 11
* 'DCS_INTERMEDIATE' -> 12
* 'DCS_PASSTHROUGH' -> 13
*
* action replacements (15 actions):
* 'no action' -> 0
* 'error' -> 1
* 'print' -> 2
* 'execute' -> 3
* 'osc_start' -> 4
* 'osc_put' -> 5
* 'osc_end' -> 6
* 'csi_dispatch' -> 7
* 'param' -> 8
* 'collect' -> 9
* 'esc_dispatch' -> 10
* 'clear' -> 11
* 'dcs_hook' -> 12
* 'dcs_put' -> 13
* 'dcs_unhook' -> 14
*/
var TRANSITION_TABLE = (function (){
//var table = [];
var table = new Uint8Array(4095);
// table with default transition [any] --> [error, GROUND]
for (var state = 0; state < 14; ++state) {
for (var code = 0; code < 160; ++code) {
table[state << 8 | code] = 16;
}
}
// apply transitions
// printables
add_list(table, PRINTABLES, 0, 2);
// global anywhere rules
for (state = 0; state < 14; ++state) {
add_list(table, [0x18, 0x1a, 0x99, 0x9a], state, 3, 0);
add_list(table, r(0x80, 0x90), state, 3, 0);
add_list(table, r(0x90, 0x98), state, 3, 0);
add(table, 0x9c, state, 0, 0); // ST as terminator
add(table, 0x1b, state, 11, 1); // ESC
add(table, 0x9d, state, 4, 8); // OSC
add_list(table, [0x98, 0x9e, 0x9f], state, 0, 7);
add(table, 0x9b, state, 11, 3); // CSI
add(table, 0x90, state, 11, 9); // DCS
}
// rules for executables and 7f
add_list(table, EXECUTABLES, 0, 3);
add_list(table, EXECUTABLES, 1, 3);
add(table, 0x7f, 1);
add_list(table, EXECUTABLES, 8);
add_list(table, EXECUTABLES, 3, 3);
add(table, 0x7f, 3);
add_list(table, EXECUTABLES, 4, 3);
add(table, 0x7f, 4);
add_list(table, EXECUTABLES, 6, 3);
add_list(table, EXECUTABLES, 5, 3);
add(table, 0x7f, 5);
add_list(table, EXECUTABLES, 2, 3);
add(table, 0x7f, 2);
// osc
add(table, 0x5d, 1, 4, 8);
add_list(table, PRINTABLES, 8, 5);
add(table, 0x7f, 8, 5);
add_list(table, [0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0);
add_list(table, r(0x1c, 0x20), 8, 0);
// sos/pm/apc does nothing
add_list(table, [0x58, 0x5e, 0x5f], 1, 0, 7);
add_list(table, PRINTABLES, 7);
add_list(table, EXECUTABLES, 7);
add(table, 0x9c, 7, 0, 0);
// csi entries
add(table, 0x5b, 1, 11, 3);
add_list(table, r(0x40, 0x7f), 3, 7, 0);
add_list(table, r(0x30, 0x3a), 3, 8, 4);
add(table, 0x3b, 3, 8, 4);
add_list(table, [0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4);
add_list(table, r(0x30, 0x3a), 4, 8);
add(table, 0x3b, 4, 8);
add_list(table, r(0x40, 0x7f), 4, 7, 0);
add_list(table, [0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6);
add_list(table, r(0x20, 0x40), 6);
add(table, 0x7f, 6);
add_list(table, r(0x40, 0x7f), 6, 0, 0);
add(table, 0x3a, 3, 0, 6);
add_list(table, r(0x20, 0x30), 3, 9, 5);
add_list(table, r(0x20, 0x30), 5, 9);
add_list(table, r(0x30, 0x40), 5, 0, 6);
add_list(table, r(0x40, 0x7f), 5, 7, 0);
add_list(table, r(0x20, 0x30), 4, 9, 5);
// esc_intermediate
add_list(table, r(0x20, 0x30), 1, 9, 2);
add_list(table, r(0x20, 0x30), 2, 9);
add_list(table, r(0x30, 0x7f), 2, 10, 0);
add_list(table, r(0x30, 0x50), 1, 10, 0);
add_list(table, [0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0);
add_list(table, r(0x60, 0x7f), 1, 10, 0);
// dcs entry
add(table, 0x50, 1, 11, 9);
add_list(table, EXECUTABLES, 9);
add(table, 0x7f, 9);
add_list(table, r(0x1c, 0x20), 9);
add_list(table, r(0x20, 0x30), 9, 9, 12);
add(table, 0x3a, 9, 0, 11);
add_list(table, r(0x30, 0x3a), 9, 8, 10);
add(table, 0x3b, 9, 8, 10);
add_list(table, [0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10);
add_list(table, EXECUTABLES, 11);
add_list(table, r(0x20, 0x80), 11);
add_list(table, r(0x1c, 0x20), 11);
add_list(table, EXECUTABLES, 10);
add(table, 0x7f, 10);
add_list(table, r(0x1c, 0x20), 10);
add_list(table, r(0x30, 0x3a), 10, 8);
add(table, 0x3b, 10, 8);
add_list(table, [0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11);
add_list(table, r(0x20, 0x30), 10, 9, 12);
add_list(table, EXECUTABLES, 12);
add(table, 0x7f, 12);
add_list(table, r(0x1c, 0x20), 12);
add_list(table, r(0x20, 0x30), 12, 9);
add_list(table, r(0x30, 0x40), 12, 0, 11);
add_list(table, r(0x40, 0x7f), 12, 12, 13);
add_list(table, r(0x40, 0x7f), 10, 12, 13);
add_list(table, r(0x40, 0x7f), 9, 12, 13);
add_list(table, EXECUTABLES, 13, 13);
add_list(table, PRINTABLES, 13, 13);
add(table, 0x7f, 13);
add_list(table, [0x1b, 0x9c], 13, 14, 0);
return table;
})();
/**
* helper for param conversion
* @param {string} params - params string with ;
* @return {Array}
*/
function parse_params(params){
// params are separated by ';'
// empty defaults to 0
var p = params.split(';');
for (var i = 0; i < p.length; ++i) {
p[i] = Number(p[i]);
}
return p;
}
/**
* AnsiParser - Parser for ANSI terminal escape sequences.
*
* @param {Object=} terminal
* @constructor
*/
function AnsiParser(terminal){
this.initial_state = 0; // 'GROUND' is default
this.current_state = this.initial_state;
// global non pushable buffers for multiple parse invocations
this.osc = '';
this.params = '';
this.collected = '';
// back reference to terminal
this.term = terminal || {};
var instructions = [
'inst_p', 'inst_o', 'inst_x', 'inst_c',
'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E'
];
for (var i = 0; i < instructions.length; ++i) {
if (!(instructions[i] in this.term)) {
this.term[instructions[i]] = function (){};
}
}
}
/**
* Reset the parser.
*/
AnsiParser.prototype.reset = function (){
this.current_state = this.initial_state;
this.osc = '';
this.params = '';
this.collected = '';
};
/**
* Parse string s.
* @param {string} s
*/
AnsiParser.prototype.parse = function (s){
var error = false;
var c, code, transition;
var current_state = this.current_state;
// local buffers
var printed = '';
var dcs = '';
var osc = this.osc;
var collected = this.collected;
var params = this.params;
// process input string
for (var i = 0; i < s.length; ++i) {
c = s.charAt(i);
code = c.charCodeAt(0);
if (code < 0xa0) {
transition = TRANSITION_TABLE[current_state << 8 | code];
} else {
transition = 16;
}
switch (transition >> 4) {
case 0:
// no action
break;
case 1:
// error
// NOTE: real error recovery is not implemented
// handle unicode chars in write buffers w'o state change
if (code > 0x9f) {
switch (current_state) {
case 0:
// GROUND -> add char to print string
printed += c;
break;
case 8:
// OSC_STRING -> add char to osc string
osc += c;
transition |= 8;
break;
case 6:
// CSI_IGNORE -> ignore char
transition |= 6;
break;
case 11:
// DCS_IGNORE -> ignore char
transition |= 11;
break;
case 13:
// DCS_PASSTHROUGH -> add char to dcs
dcs += c;
transition |= 13;
break;
default:
error = true;
}
} else {
error = true;
}
if (error) {
if (
this.term.inst_E({
pos: i, // position in parse string
character: c, // wrong character
state: current_state, // in state
print: printed, // print buffer
dcs: dcs, // dcs buffer
osc: osc, // osc buffer
collect: collected, // collect buffer
params: params // params buffer
})
) {
return;
}
error = false;
}
break;
case 2:
// print
printed += c;
break;
case 3:
// execute
if (printed) {
this.term.inst_p(printed);
}
printed = '';
this.term.inst_x(c);
break;
case 7:
// csi_dispatch
this.term.inst_c(collected, parse_params(params), c);
break;
case 8:
// param
params += c;
break;
case 9:
// collect
collected += c;
break;
case 10:
// esc_dispatch
this.term.inst_e(collected, c);
break;
case 11:
// clear
if (printed) {
this.term.inst_p(printed);
}
printed = '';
osc = '';
params = '';
collected = '';
dcs = '';
break;
case 4:
// osc_start
if (printed) {
this.term.inst_p(printed);
}
printed = '';
osc = '';
break;
case 5:
// osc_put
osc += c;
break;
case 6:
// osc_end
if (osc && code !== 0x18 && code !== 0x1a) {
this.term.inst_o(osc);
}
if (code === 0x1b) {
transition |= 1;
}
osc = '';
params = '';
collected = '';
dcs = '';
break;
case 12: // dcs_hook
this.term.inst_H(collected, parse_params(params), c);
break;
case 13: // dcs_put
dcs += c;
break;
case 14: // dcs_unhook
if (dcs) {
this.term.inst_P(dcs);
}
this.term.inst_U();
if (code === 0x1b) {
transition |= 1;
}
osc = '';
params = '';
collected = '';
dcs = '';
break;
}
current_state = transition & 15;
}
// push leftover pushable buffers to terminal
if (!current_state && printed) {
this.term.inst_p(printed);
} else if (current_state === 13 && dcs) {
this.term.inst_P(dcs);
}
// save non pushable buffers
this.osc = osc;
this.collected = collected;
this.params = params;
// save state
this.current_state = current_state;
};