
大家晚上好。
在node.js上编写沉浸式CLI时出现问题。 以前为此目的使用了vorpal 。 这次我想做的是没有不必要的依赖关系,此外,我考虑了采用不同的命令参数的可能性。
使用vorpal时,命令编写如下:
setValue -s 1 -v 0
同意,每次写-s
都不是很方便。
最后,团队转变为以下团队:
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_out函数,该函数在输出到控制台后调用readline输入行。
解析器
似乎可以将字符串分成多个空格,分离各个部分并进行处理。 但是这样就不可能传递包含空格的字符串参数。 在任何情况下,都必须删除多余的空格和制表符。
最初,他计划自己实现解析器,将赫伯特·希尔尔德(Herbert Schildt)关于C语言的书中递归的递归解析器重写为JS。在执行过程中,决定简化该解析器,但最终却无法实现,因为 在编写过程中,我找到了ebnf软件包,并且由于对BNF / EBNF语法定义系统感兴趣并熟悉了它,因此决定在应用程序中使用它。
文法
我们在语法文件中描述命令和参数。
首先,定义以下内容:
- 表达式由一行组成。 我们不需要处理两个以上的行。
- 表达式的开头是命令标识符。 进一步的论点。
- 由于命令数量有限,因此每个命令都写在语法文件中。
入口点如下:
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对象的实例会发出线事件的信号,该事件由以下功能处理:
let parseCmd = line => { let res = parser.getAST(line.trim()); if (res.type === "command") { let cmdObject = res.children[0]; return processCmd(cmdObject); } };
如果正确编写了命令,则解析器将返回一棵树,其中的每个元素都有一个类型,子字段和文本字段。 type字段采用当前元素的类型值。 即 如果我们将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)中(例如),您需要在输入字段中输入一个组地址,然后使用鼠标按钮(或几个TAB)发送请求。
在通过vorpal实现的界面中,命令如下:
readValue -s 1
或者:
readValues -s "1, 3"
使用解析器,可以避免不必要的“ -s”元素和引号。
read 1 3
连结
- https://github.com/bobaoskit/bobaos.tool-项目存储库。 您可以看一下代码。
- http://menduz.com/ebnf-highlighter/-您可以即时编辑和检查语法。