【问题标题】:(E)BNF How to match until next non-terminal rule?(E)BNF 如何匹配直到下一个非终结规则?
【发布时间】:2020-10-11 15:24:06
【问题描述】:

我正在尝试使用RIS format 中的内容编写语法

文件示例:

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 开头,后跟 -,然后是:

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

无论我尝试什么最终都会“吞下”所有其他陈述,例如第一个多行关键字捕获其后的所有内容。

你会如何写这条规则?我不一定对 的具体答案感兴趣。任何能触发我“啊哈”时刻的事情都可以!

【问题讨论】:

    标签: nearley nearley grammar bnf ebnf earley-parser nearley


    【解决方案1】:

    我绝对不是很擅长设计语法(你可能已经猜到了)但这触发了我的啊哈时刻:

    很多人向我指出,为 Nearley 编写语法很难。问题是,一般来说,写语法是非常困难的。某些与语法相关的问题可证明是不可判定的,这无济于事。

    https://nearley.js.org/docs/how-to-grammar-good

    还有:

    使用分词器有很多好处。它……

    • ...通常会使您的解析器速度提高一个数量级以上。
    • ...允许您编写更简洁、更易于维护的语法。
    • ...在某些情况下有助于避免语法不明确。 [...]

    https://nearley.js.org/docs/tokenizers

    我知道 推荐使用

    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;
    

    我们定义了四个标记:

    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
    

    【讨论】:

      猜你喜欢
      • 2012-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-02
      • 2023-03-13
      • 2013-12-06
      • 2010-10-09
      相关资源
      最近更新 更多