みなさんこんばんは。
node.jsで没入型CLIを作成する際に問題が発生しました。 この目的で以前に使用されたvorpal 。 今回は、不必要な依存関係なしでやりたいと思っていました。さらに、コマンド引数を別の方法で取る可能性を考えました。
vorpalでは、コマンドは次のように記述されました。
setValue -s 1 -v 0
同意する-毎回書くことはあまり便利ではありません。
最終的に、チームは次のように変わりました。
set 1: 0
実装方法-カットの下で
- また、良いボーナスは、スペースで区切られた値のリストの形式で、配列の形式でいくつかの引数を転送することです。
テキスト入力
readline
を使用してテキストを入力します。 次の方法で、自動補完をサポートするインターフェイスを作成します。
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
は期待どおりに機能しconsole.log
つまり、 現在の行にテキストを表示し、行を折り返します。テキスト入力に依存しない外部イベントによってトリガーされた場合、データは入力行に表示されます。 したがって、console_out関数を使用します。この関数は、コンソールへの出力後、readline入力行を呼び出します。
パーサー
文字列をスペースに分割し、個々の部分を分離して処理できるように思われます。 ただし、スペースを含む文字列パラメーターを渡すことはできません。 とにかく、余分なスペースとタブを削除する必要があります。
当初、彼はパーサーを自分で実装することを計画し、C言語に関するHerbert Schildtの本から降順の再帰的パーサーをJSに書き換えました。実行中に、パーサーを単純化することが決定されました 執筆の過程でebnfパッケージを見つけ、BNF / EBNF構文定義システムに興味を持ち慣れてきたので、アプリケーションで使用することにしました。
文法
文法ファイルでコマンドと引数について説明します。
開始するには、次を定義します。
- 式は1行で構成されます。 3行以上を処理する必要はありません。
- 式の先頭はコマンド識別子です。 さらなる議論。
- コマンドの数は限られているため、それぞれが文法ファイルに書き込まれます。
エントリポイントは次のとおりです。
command ::= (set|get|stored|read|description|getbyte|watch|unwatch|ping|state|reset|getitem|progmode|help) WS*
WS *は空白-スペースまたはタブ文字を意味します。 次のように説明されています。
WS ::= [#x20#x09#x0A#x0D]+
これは、文字スペース、タブ、または改行が何度も発生することを意味します。
チームに移りましょう。
引数なしの最も単純な:
ping ::= "ping" WS* state ::= "state" WS* reset ::= "reset" WS* help ::= "help" WS*
さらに、スペースまたは配列で区切られた自然数のリストを取得するコマンド。
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 )
したがって、次の例はgetコマンドに適しています。
get 1 get 1 2 3 5 get [1, 2, 3, 5, 10]
次に、setコマンド。入力ペアid:valueまたは値の配列を受け取ります。
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 )
したがって、setコマンドの場合、次の表記形式が正しいです。
set 1: true set 2: 255 set 3: 21.42 set [1: false, 999: "hello, friend"]
jsでの処理
ファイルを読み取り、パーサーオブジェクトを作成します。
const grammar = fs.readFileSync(`${__dirname}/grammar`, "utf8"); const parser = new Grammars.W3C.Parser(grammar);
さらに、データを入力すると、readlineオブジェクトのインスタンスがlineイベントで通知し、次の関数によって処理されます。
let parseCmd = line => { let res = parser.getAST(line.trim()); if (res.type === "command") { let cmdObject = res.children[0]; return processCmd(cmdObject); } };
コマンドが正しく記述されていれば、パーサーはツリーを返します。各要素には、タイプ、子フィールド、およびテキストフィールドがあります。 タイプフィールドは、現在の要素のタイプ値を受け入れます。 つまり パーサーにpingコマンドを渡すと、ツリーはトレースのようになります。 方法:
{ "type": "command", "text": "ping", "children": [{ "type": "ping", "text": "ping", "children": [] }] }
次の形式で書き込みます。
command ping Text = "ping"
コマンド「get 1 2 3」の場合、
command get UIntList uint Text = "1" uint Text = "2" uint Text = "3"
次に、各コマンドを処理し、必要なアクションを実行して、結果をコンソールに出力します。
その結果、最小限の依存関係で作業を高速化する非常に便利なインターフェイスが実現します。 私が説明します:
(たとえば)グループアドレスを読み取るためのグラフィカルインターフェイス(ETS)では、入力フィールドに1つのグループアドレスを入力し、(または複数のTAB)をクリックして要求を送信する必要があります。
vorpalを介して実装されたインターフェイスでは、コマンドは次のとおりです。
readValue -s 1
または:
readValues -s "1, 3"
パーサーを使用すると、不要な「-s」要素と引用符を回避できます。
read 1 3
リンク
- https://github.com/bobaoskit/bobaos.tool-プロジェクトリポジトリ。 コードを見ることができます。
- http://menduz.com/ebnf-highlighter/-文法をその場で編集および確認できます。