我绝对不是很擅长设计语法(你可能已经猜到了)但这触发了我的啊哈时刻:
很多人向我指出,为 Nearley 编写语法很难。问题是,一般来说,写语法是非常困难的。某些与语法相关的问题可证明是不可判定的,这无济于事。
见https://nearley.js.org/docs/how-to-grammar-good
还有:
使用分词器有很多好处。它……
- ...通常会使您的解析器速度提高一个数量级以上。
- ...允许您编写更简洁、更易于维护的语法。
-
...在某些情况下有助于避免语法不明确。 [...]
见https://nearley.js.org/docs/tokenizers
我知道nearley 推荐使用moo-lexer:
nearley 支持并推荐 Moo,一个超快速的词法分析器。
见https://nearley.js.org/docs/tokenizers
所以我四处搜索并找到了这个amazing tutorial on YouTube,这绝对让我畅通无阻。非常感谢@airportyh!
起初我认为这对于我的用例来说方式太复杂了,但事实证明,使用词法分析器实际上使事情变得既可能又更简单!
为了简单起见,我将提供一个带有截断 RIS 文件的解决方案:
sample.ris
KW - foo
bar
baz
KW - bat
这个文件在解析后应该会产生['foo bar baz', 'bat']。
首先让我们安装一些东西
yarn add nearley
yarn add moo
现在让我们定义词法分析器
lexer.js
const moo = require('moo');
const lexer =
moo.compile
( { NL: {match: /[\n]/, lineBreaks: true}
, KW: 'KW'
, SEPARATOR: " - "
, CONTENT: /[a-z]+/
}
);
module.exports = lexer;
我们定义了四个标记:
- 换行符
NL
-
KW 关键字...关键字!
- 标签与其内容之间的
SEPARATOR
- 标签的
CONTENT
接下来让我们定义语法
grammar.ne
@{% const lexer = require('./lexer.js'); %}
@lexer lexer
@builtin "whitespace.ne"
RECORD -> _ KW:+ {% ([, keywords]) => [].concat(...keywords) %}
KW -> %KW %SEPARATOR LINE:+ {% ([,,lines]) => lines.join(' ') %}
LINE -> %CONTENT __ {% ([{value}]) => value %}
注意:看看我们如何通过前缀 %! 来引用词法分析器中定义的标记!
现在我们需要编译我们的语法
Nearley 附带编译器:
yarn -s nearleyc grammar.ne > grammar.js
您还可以在package.json 中定义compile 脚本:
{
...
"scripts": {
"compile": "nearleyc grammar.ne > grammar.js",
}
...
}
最后让我们构建一个解析器并使用它!
const nearley = require('nearley');
const grammar = require('./grammar.js');
module.exports =
str => {
const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar));
parser.feed(str);
return parser.results[0];
};
注意:这需要编译语法,即grammar.js
让我们给它一些文字:
const parser = require('./parser.js');
parser(`
KW - foo
bar
baz
KW - bat
`);
//=> [ 'foo bar baz', 'bat' ]
最后提示:您还可以使用nearley-test 测试您的语法:
cat sample.ris | yarn -s nearley-test -- -q grammar.js