
Buenas tardes a todos.
Hubo un problema al escribir su CLI inmersiva en node.js. Vorpal utilizado anteriormente para este propósito. Esta vez quise prescindir de dependencias innecesarias y, además, consideré la posibilidad de tomar argumentos de comando de manera diferente.
Con vorpal, los comandos se escribieron de la siguiente manera:
setValue -s 1 -v 0
De acuerdo, escribir siempre no es muy conveniente.
Al final, el equipo se transformó en lo siguiente:
set 1: 0
Cómo se puede implementar: bajo el corte
- Además, una buena ventaja es la transferencia de varios argumentos en forma de una lista de valores, separados por un espacio y en forma de matriz.
entrada de texto
Yo uso readline
para ingresar texto. De la siguiente manera creamos una interfaz con soporte para autocompletar:
let commandlist = []; commandlist.push("set", "get", "stored", "read", "description"); commandlist.push("watch", "unwatch"); commandlist.push("getbyte", "getitem", "progmode"); commandlist.push("ping", "state", "reset", "help"); function completer(line) { const hits = commandlist.filter(c => c.startsWith(line)); // show all completions if none found return [hits.length ? hits : commandlist, line]; } /// init repl const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "bobaos> ", completer: completer }); const console_out = msg => { process.stdout.clearLine(); process.stdout.cursorTo(0); console.log(msg); rl.prompt(true); };
console.log
funciona como se esperaba, es decir muestra el texto en la línea actual y ajusta la línea, y si se desencadena por un evento externo que es independiente de la entrada de texto, los datos se mostrarán en la línea de entrada. Por lo tanto, usamos la función console_out, que, después de la salida a la consola, llama a la línea de entrada readline.
analizador
Parece que puede dividir la cadena en espacios, separar las partes individuales y procesarla. Pero entonces será imposible pasar parámetros de cadena que contengan un espacio; y, en cualquier caso, será necesario eliminar espacios y pestañas adicionales.
Inicialmente, planeó implementar el analizador él mismo, reescribiendo el analizador recursivo descendente del libro de Herbert Schildt sobre el lenguaje C en JS. Durante la ejecución, se decidió simplificar el analizador, pero al final no fue posible implementarlo, porque En el proceso de redacción, encontré el paquete ebnf y, después de interesarme y familiarizarme con los sistemas de definición de sintaxis BNF / EBNF, decidí usarlo en mi aplicación.
gramática
Describimos los comandos y argumentos en el archivo de gramática.
Para comenzar, defina lo siguiente:
- La expresión consta de una línea. No necesitamos procesar más de dos líneas.
- Al comienzo de la expresión está el identificador de comando. Otros argumentos
- Hay un número limitado de comandos, por lo que cada uno de ellos está escrito en el archivo de gramática.
El punto de entrada es el siguiente:
command ::= (set|get|stored|read|description|getbyte|watch|unwatch|ping|state|reset|getitem|progmode|help) WS*
WS * significa espacio en blanco: espacio o caracteres de tabulación. Se describe de la siguiente manera:
WS ::= [#x20#x09#x0A#x0D]+
Lo que significa el espacio de caracteres, tabulación o salto de línea, que ocurre una vez y más.
Pasemos a los equipos.
El más simple, sin argumentos:
ping ::= "ping" WS* state ::= "state" WS* reset ::= "reset" WS* help ::= "help" WS*
Además, los comandos que toman una lista de números naturales separados por un espacio o una matriz.
BEGIN_ARRAY ::= WS* #x5B WS* END_ARRAY ::= WS* #x5D WS* COMMA ::= WS* #x2C WS* uint ::= [0-9]* UIntArray ::= BEGIN_ARRAY (uint WS* (COMMA uint)*) END_ARRAY UIntList ::= (uint WS*)* get ::= "get" WS* ( UIntList | UIntArray )
Por lo tanto, los siguientes ejemplos son correctos para el comando get:
get 1 get 1 2 3 5 get [1, 2, 3, 5, 10]
A continuación, el comando set, que toma un id de par de entrada: valor o una matriz de valores.
COLON ::= WS* ":" WS* Number ::= "-"? ("0" | [1-9] [0-9]*) ("." [0-9]+)? (("e" | "E") ( "-" | "+" )? ("0" | [1-9] [0-9]*))? String ::= '"' [^"]* '"' | "'" [^']* "'" Null ::= "null" Bool ::= "true" | "false" Value ::= Number | String | Null | Bool DatapointValue ::= uint COLON Value DatapointValueArray ::= BEGIN_ARRAY (DatapointValue WS* (COMMA DatapointValue)*)? END_ARRAY set ::= "set" WS* ( DatapointValue | DatapointValueArray )
Por lo tanto, para el comando set, las siguientes formas de notación son correctas:
set 1: true set 2: 255 set 3: 21.42 set [1: false, 999: "hello, friend"]
proceso en js
Leemos el archivo, creamos el objeto analizador.
const grammar = fs.readFileSync(`${__dirname}/grammar`, "utf8"); const parser = new Grammars.W3C.Parser(grammar);
Además, al ingresar datos, una instancia del objeto readline señala el evento de línea, que es procesado por la siguiente función:
let parseCmd = line => { let res = parser.getAST(line.trim()); if (res.type === "command") { let cmdObject = res.children[0]; return processCmd(cmdObject); } };
Si el comando se escribió correctamente, el analizador devuelve un árbol, donde cada elemento tiene un tipo, un campo secundario y un campo de texto. El campo de tipo toma el valor de tipo del elemento actual. Es decir Si pasamos el comando ping al analizador, el árbol se verá como un rastro. manera:
{ "type": "command", "text": "ping", "children": [{ "type": "ping", "text": "ping", "children": [] }] }
Escribimos en la forma:
command ping Text = "ping"
Para el comando "obtener 1 2 3",
command get UIntList uint Text = "1" uint Text = "2" uint Text = "3"
A continuación, procesamos cada comando, realizamos las acciones necesarias y mostramos el resultado en la consola.
El resultado es una interfaz muy conveniente que acelera el trabajo con un mínimo de dependencias. Explicaré:
en la interfaz gráfica (ETS) para leer direcciones de grupo (por ejemplo), debe ingresar una dirección de grupo en el campo de entrada, luego hacer clic (o varias TAB) para enviar una solicitud.
En la interfaz implementada a través de vorpal, el comando es el siguiente:
readValue -s 1
O:
readValues -s "1, 3"
Usando el analizador, puede evitar elementos "-s" innecesarios y comillas.
read 1 3
enlaces
- https://github.com/bobaoskit/bobaos.tool - repositorio de proyectos. Puedes mirar el código.
- http://menduz.com/ebnf-highlighter/ : puede editar y verificar la gramática sobre la marcha.