mirror of
https://github.com/nuintun/command-manager.git
synced 2025-06-08 12:14:03 +08:00
468 lines
12 KiB
JavaScript
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;
|
|
};
|