
Boa noite a todos.
Ocorreu um problema ao gravar sua CLI imersiva em node.js. Vorpal usado anteriormente para esse fim. Dessa vez, queria passar sem dependências desnecessárias e, além disso, considerei a possibilidade de assumir argumentos de comando de maneira diferente.
Com vorpal, os comandos foram escritos da seguinte maneira:
setValue -s 1 -v 0
Concordo, escrever sempre não é muito conveniente.
No final, a equipe se transformou no seguinte:
set 1: 0
Como pode ser implementado - sob o corte
- Além disso, um bom bônus é a transferência de vários argumentos na forma de uma lista de valores, separados por um espaço e na forma de uma matriz.
entrada de texto
Eu uso o readline
para inserir texto. Da seguinte maneira, criamos uma interface com suporte para preenchimento automático:
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 conforme o esperado, ou seja, exibe o texto na linha atual e quebra a linha e, se chamado em algum evento externo independente da entrada de texto, os dados serão exibidos na linha de entrada. Portanto, usamos a função console_out, que, após saída para o console, chama a linha de entrada readline.
analisador
Parece que você pode dividir a sequência em espaços, separar as partes individuais e processá-la. Mas será impossível passar parâmetros de string contendo um espaço; e, em qualquer caso, será necessário remover espaços e guias extras.
Inicialmente, ele planejou implementar o analisador, reescrevendo o analisador recursivo descendente do livro de Herbert Schildt sobre a linguagem C. Em JS. Durante a execução, foi decidido simplificar o analisador, mas no final não foi possível implementá-lo, porque no processo de redação, encontrei o pacote ebnf e, tendo me interessado e familiarizado com os sistemas de definição de sintaxe BNF / EBNF, decidi usá-lo em meu aplicativo.
gramática
Nós descrevemos os comandos e argumentos no arquivo de gramática.
Para começar, defina o seguinte:
- A expressão consiste em uma linha. Não precisamos processar mais de duas linhas.
- No início da expressão está o identificador de comando. Mais argumentos.
- Há um número limitado de comandos, portanto cada um deles é gravado no arquivo de gramática.
O ponto de entrada é o seguinte:
command ::= (set|get|stored|read|description|getbyte|watch|unwatch|ping|state|reset|getitem|progmode|help) WS*
WS * significa espaço em branco - caracteres de espaço ou tabulação. É descrito da seguinte maneira:
WS ::= [#x20#x09#x0A#x0D]+
O que significa que o espaço de caracteres, tabulação ou quebra de linha ocorre uma e mais vezes.
Vamos para as equipes.
O mais simples, sem argumentos:
ping ::= "ping" WS* state ::= "state" WS* reset ::= "reset" WS* help ::= "help" WS*
Além disso, os comandos que levam uma lista de números naturais separados por um espaço ou uma 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 )
Portanto, os seguintes exemplos estão corretos para o comando get:
get 1 get 1 2 3 5 get [1, 2, 3, 5, 10]
Em seguida, o comando set, que recebe um ID do par de entrada: value ou uma 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 )
Portanto, para o comando set, os seguintes formulários de notação estão corretos:
set 1: true set 2: 255 set 3: 21.42 set [1: false, 999: "hello, friend"]
processo em js
Lemos o arquivo, criamos o objeto analisador.
const grammar = fs.readFileSync(`${__dirname}/grammar`, "utf8"); const parser = new Grammars.W3C.Parser(grammar);
Além disso, ao inserir dados, uma instância do objeto readline sinaliza o evento de linha, que é processado pela seguinte função:
let parseCmd = line => { let res = parser.getAST(line.trim()); if (res.type === "command") { let cmdObject = res.children[0]; return processCmd(cmdObject); } };
Se o comando foi escrito corretamente, o analisador retornará uma árvore, na qual cada elemento possui um tipo, um campo filho e um campo de texto. O campo de tipo assume o valor de tipo do elemento atual. I.e. se passarmos o comando ping para o analisador, a árvore parecerá um rastreio. caminho:
{ "type": "command", "text": "ping", "children": [{ "type": "ping", "text": "ping", "children": [] }] }
Escrevemos no formulário:
command ping Text = "ping"
Para o comando "obter 1 2 3",
command get UIntList uint Text = "1" uint Text = "2" uint Text = "3"
Em seguida, processamos cada comando, executamos as ações necessárias e exibimos o resultado no console.
O resultado é uma interface muito conveniente que acelera o trabalho com um mínimo de dependências. Vou explicar:
na interface gráfica (ETS) para ler endereços de grupo (por exemplo), é necessário inserir um endereço de grupo no campo de entrada e clicar em (ou várias TABs) para enviar uma solicitação.
Na interface implementada através do vorpal, o comando é o seguinte:
readValue -s 1
Ou:
readValues -s "1, 3"
Usando o analisador, você pode evitar elementos "-s" e aspas desnecessários.
read 1 3
ligações
- https://github.com/bobaoskit/bobaos.tool - repositório do projeto. Você pode olhar para o código.
- http://menduz.com/ebnf-highlighter/ - você pode editar e verificar a gramática em tempo real.