(E)BNF 如何匹配到下一个非终端规则?



>我正在尝试使用 nearley 为 RIS 格式的内容编写语法

文件示例:

TY  - JOUR
KW  - foo
KW  - bar
ER  - 

*.ris文件始终以标记TY开头,以标记ER结尾。在两者之间可以有许多其他标签,如KW(关键字(。

该规范说,单个KW语句可以跨越多行。

所以这个:

TY  - JOUR
KW  - foo
bar
baz
KW  - bat
ER  - 

相当于:

TY  - JOUR
KW  - foo bar baz
KW  - bat
ER  - 

我正在努力想出一个语法,比如:

关键字以KW开头,后跟-,后跟以下任一:

  • 直到行尾的字母
  • 尾之前的字母和下一个关键字之前的任何其他行

无论我尝试什么,最终都会"吞噬"所有其他语句,例如,第一个多行关键字捕获了它之后的其他所有内容。

你会如何写这个规则?我不一定对一个近乎具体的答案感兴趣。任何触发我"啊哈"时刻的事情都会做!

我绝对不是很擅长设计语法(你可能已经想通了(,但这触发了我的Aha时刻:

很多人向我指出,为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 文件的解决方案:

样本.ris

KW  - foo
bar
baz
KW  - bat

此文件在解析后应生成['foo bar baz', 'bat']

首先让我们安装一些东西

yarn add nearley
yarn add moo

现在让我们定义我们的词法分析

词法分析器.js

const moo = require('moo');
const lexer =
moo.compile
( { NL: {match: /[n]/, lineBreaks: true}
, KW: 'KW'
, SEPARATOR: "  - "
, CONTENT: /[a-z]+/
}
);
module.exports = lexer;

我们定义了四个令牌:

  1. 换行符NL
  2. KW关键字...关键词!
  3. 标记与其内容之间的SEPARATOR
  4. 标记的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

最新更新