我绝对不是很擅长设计语法(你可能已经猜到了),但这触发了我的Aha时刻:
很多人向我指出,为 Nearley 编写语法很难。问题是,一般来说,写语法是非常困难的。某些与语法相关的问题可以证明是不可判定的,这无济于事。
见https://nearley.js.org/docs/how-to-grammar-good
和:
使用分词器有很多好处。它……</p>
- …通常会使您的解析器速度提高一个数量级以上。
- …允许您编写更清晰、更易于维护的语法。
- …在某些情况下有助于避免模棱两可的语法。[...]
见https://nearley.js.org/docs/tokenizers
我知道nearley建议使用moo-lexer:
Nearley 支持并推荐 Moo,一个超快速的词法分析器。
见https://nearley.js.org/docs/tokenizers
所以我四处搜索,在 YouTube 上找到了这个很棒的教程,它绝对让我畅通无阻。非常感谢@airportyh!
起初我认为这对于我的用例来说太复杂了,但事实证明,使用词法分析器实际上使事情变得既可能又更简单!
为了简单起见,我将提供一个带有截断 RIS 文件的解决方案:
样本.ris
KW - foo
bar
baz
KW - bat
这个文件应该['foo bar baz', 'bat']
在解析后产生。
首先让我们安装一些东西
yarn add nearley
yarn add moo
现在让我们定义我们的词法分析器
词法分析器
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
标签的
接下来让我们定义我们的语法
语法网
@{% 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
您还可以compile
在您的package.json
:
{
...
"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