我肯定不擅长设计语法(您可能已经发现了),但这引发了我的“噢”的时刻:
很多人指出,为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
所以我在谷歌上搜索,找到了这个
令人惊叹的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
`);
最后一条提示:您还可以使用nearley-test
测试您的语法:
cat sample.ris | yarn -s nearley-test -- -q grammar.js