否定其他有效匹配项的 Xtext 规则

Xtext rule that negates other valid matches

我对 Xtext 还很陌生,所以我可能问错了问题,或者使用了不正确的术语。请在您的回复中牢记这一点。

我正尝试在 Xtext 中从头开始实施 JBehave EBNF Spec 作为学习练习。 JBehave 是一种非常 "wordy" 的语法,类似于我需要能够维护的语法,因此我将需要了解如何在不同的上下文中处理各种类型的 "words"。

作为起点,我已经能够让这个测试用例通过。

@Test
def void loadModel() {

    // Multi-line
    var story = parseHelper.parse('''
        The quick brown fox
        Jumps over the lazy dog
    ''')

    assertThat(story, notNullValue())
    assertThat(
        story.description,
        equalTo('''
            The quick brown fox
            Jumps over the lazy dog
        ''')
    )

    // Single-line description
    story = parseHelper.parse('''
        The quick brown fox
    ''')
    assertThat(
        story.description,
        equalTo("The quick brown fox\n")
    )
}

使用这个语法定义...

grammar org.example.jbehave.JBehave hidden (WS)

import "http://www.eclipse.org/emf/2002/Ecore" as ecore
generate jbehave "http://www.example.org/jbehave"

// The story describes a feature via description, narrative and a set of scenarios
// Story := Description? Meta? Narrative? GivenStories? Lifecycle? Scenario+ ;
Story:
    description=Description?
;

// The Description is expressed by any sequence of words that must not contain any keywords at start of lines.
// Description := (Word Space?)* ;
Description:
    ((WORD) (WORD)* EOL+)+
//  ((NON_KEYWORD) (WORD)* EOL+)+
;

// Key Words
////

// TODO: parser fails when uncommented
//terminal NON_KEYWORD: 
//  !(IN_ORDER_TO
//      | AS_A
//      | I_WANT_TO
//      | SO_THAT
//      | SCENARIO
//      | GIVEN_STORIES
//      | GIVEN
//      | THEN
//      | WHEN
//      | AND
//  )
//; 

terminal fragment IN_ORDER_TO: "In order to";
terminal fragment AS_A: "As a";
terminal fragment I_WANT_TO: "I want to";
terminal fragment SO_THAT: "So that";
terminal fragment SCENARIO: "Scenario:";
terminal fragment GIVEN_STORIES: "GivenStories:";
terminal fragment GIVEN: "Given";
terminal fragment WHEN: "When";
terminal fragment THEN: "Then";
terminal fragment AND: "And";

// Common Terminals
////

terminal WORD: ('a'..'z'|'A'..'Z'|'_')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;

terminal WS: (' '|'\t')+;

terminal EOL: NL;
terminal fragment NL: ('\r'? '\n');

我 运行 遇到的问题已在评论中列出。

  1. 当我取消注释终端 NON_KEYWORD 时,测试失败并显示

    Expected: "The quick brown fox\nJumps over the lazy dog\n" but: was "The"

  2. 如果我随后替换 Description 中注释掉的行,测试将完全无法解析

    Expected: not null but: was null

我隐约明白这里发生的事情。我在 WORD 之前定义的标记也是有效的单词,因此它会丢弃解析器。因此我的问题如下。

  1. 在 Xtext 文档(或其他来源)的何处可以找到描述此处受影响的基本原则的内容。到目前为止,我已经多次阅读 Xtext 文档,但我所能找到的只是关于终端语句顺序依赖性的简短说明。

  2. 调试解析器如何解释我的语法的好方法是什么?是否有类似于将 IFormattableDocument 转储到控制台的内容,但对于 lexer/parser/whatever?

  3. 最后,从 Xtext 的角度来看,解决这个问题的最佳方法是什么。我应该研究自定义数据类型,还是可以用纯 Xtext 表达?

我正在寻求了解基本原则。

更新

嗯,这确实很奇怪。我现在试图跳过这个并实现规范的下一部分。

; The narrative is identified by keyword "Narrative:" (or equivalent in I18n-ed locale),
; It is followed by the narrative elements
Narrative:= "Narrative:" ( InOrderTo AsA IWantTo | AsA IWantTo SoThat ) ;

我实际上无法让它自己工作。然而,当我取消原始代码的注释并一起尝试时,它起作用了!

    @Test
    def void narrativeOnly() {
        var story = _th.parse('''
            Narrative:
            In order check reports
            As a Developer
            I want to workin with todos using examples
        ''')
        assertThat(story, notNullValue())
    }

    @Test
    def void descriptionOnly() {

        // Multi-line
        var story = _th.parse('''
            The quick brown fox
            Jumps over the lazy dog
        ''')

        assertThat(story, notNullValue())
        assertThat(
            story.description,
            equalTo('''
                The quick brown fox
                Jumps over the lazy dog
            ''')
        )

        // Single-line description
        story = _th.parse('''
            The quick brown fox
        ''')
        assertThat(
            story.description,
            equalTo("The quick brown fox\n")
        )
    }
grammar org.agileware.natural.jbehave.JBehave hidden (WS)

import "http://www.eclipse.org/emf/2002/Ecore" as ecore
generate jbehave "http://www.agileware.org/natural/jbehave"

// Story
////

// The story describes a feature via description, narrative and a set of scenarios
// Story := Description? Meta? Narrative? GivenStories? Lifecycle? Scenario+ ;
Story:
    description=Description?
    narrative=Narrative?
;

// Narrative
////

// The narrative is identified by keyword "Narrative:" (or equivalent in I18n-ed locale),
// It is followed by the narrative elements
// Narrative:= "Narrative:" ( InOrderTo AsA IWantTo | AsA IWantTo SoThat ) ;

// The narrative element content is any sequence of characters that do not match a narrative starting word
// NarrativeElementContent := ? Any sequence of NarrativeCharacter that does not match NarrativeStartingWord ? ;

Narrative:
    'Narrative:'
    inOrderTo=InOrderTo
    asA=AsA
    wantTo=IWantTo
;

// InOrderTo:= "In order to" NarrativeElementContent ;
InOrderTo:
    IN_ORDER_TO (WORD) (WORD)* EOL+;

// AsA:= "As a" NarrativeElementContent ;
AsA:
    AS_A (WORD) (WORD)* EOL+;

// IWantTo:= "I want to" NarrativeElementContent ;
IWantTo:
    I_WANT_TO (WORD) (WORD)* EOL+;

// SoThat:= "So that" NarrativeElementContent ;
SoThat:
    SO_THAT (WORD) (WORD)* EOL+;

// The Description is expressed by any sequence of words that must not contain any keywords at start of lines.
// Description := (Word Space?)* ;
Description:
    ((WORD) (WORD)* EOL+)+
;

// Key Words
////

//terminal NON_KEYWORD: 
//  !(IN_ORDER_TO
//      | AS_A
//      | I_WANT_TO
//      | SO_THAT
//      | SCENARIO
//      | GIVEN_STORIES
//      | GIVEN
//      | THEN
//      | WHEN
//      | AND
//  )
//; 

terminal IN_ORDER_TO: "In order to";
terminal AS_A: "As a";
terminal I_WANT_TO: "I want to";
terminal SO_THAT: "So that";
//terminal SCENARIO: "Scenario:";
//terminal GIVEN_STORIES: "GivenStories:";
//terminal GIVEN: "Given";
//terminal WHEN: "When";
//terminal THEN: "Then";
//terminal AND: "And";

// Common Terminals
////

terminal WORD: (LETTER)(LETTER|DIGIT)*;

terminal fragment LETTER: ('a'..'z'|'A'..'Z');

terminal fragment DIGIT: ('0'..'9');

terminal WS: (' '|'\t')+;

terminal EOL: NL;
terminal fragment NL: ('\r'? '\n');

我想这解决了#3,但偶然到达那里有点违背了目的。我现在将接受任何可以指出或向我描述导致我所描述的行为的基本原则的答案。

为什么我不能随机匹配一组单词?在 Story 中定义 narrative 赋值和 description 赋值如何改变解析器解释语法的方式?

我已经能够使用 ANTLRWorks 回答我的所有 3 个问题,这是一个可执行 jar 形式的 gui 工具,其明确目的是调试、可视化和帮助人们理解解析器。

要使其与 Xtext 一起使用,需要添加以下 mwe2 生成器:

language = StandardLanguage {
    // ...

    parserGenerator = {
        debugGrammar = true
    }
}

然后在 ANTLRWorks 工具中打开生成的调试文件并点击 "Bug"(调试)图标。此文件应位于 src-gen/*/parser/antlr/internal/DebugInternal*.g

来源:https://blogs.itemis.com/en/debugging-xtext-grammars-what-to-do-when-your-language-is-ambiguous