Я новичок в Xtext, поэтому, возможно, я задаю неправильный вопрос или использую неправильную терминологию. Помните об этом в своих ответах.
Я пытаюсь реализовать JBehave EBNF Spe c с нуля в Xtext в качестве обучающего упражнения. JBehave - очень «многословная» грамматика, похожая на ту, которую мне нужно будет поддерживать, поэтому мне нужно будет понять, как обрабатывать различные типы «слов» в разном контексте.
Я был возможность пройти этот тестовый пример в качестве отправной точки.
@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');
Проблемы, с которыми я сталкиваюсь, описаны в комментариях.
- Когда я раскомментирую терминал
NON_KEYWORD
, тест не проходит с Ожидается: «Быстрая коричневая лиса \ nПрыгает через ленивую собаку \ n», но: было «The»
- Если я затем заменю строку, закомментированную в
Description
, тест вообще не сможет выполнить синтаксический анализ с помощью Ожидается: не null, но: было null
Я как бы понимаю, что здесь происходит, в некотором смысле. Токены, которые я определяю перед WORD, также являются допустимыми словами, поэтому анализатор сбрасывает их. Поэтому мои вопросы заключаются в следующем.
Где я могу найти в документации Xtext (или других источниках), которые описывают основные принципы, которые здесь затрагиваются. К настоящему времени я прочитал документы Xtext много раз, но все, что я смог найти, это краткое замечание о зависимости от порядка терминальных операторов.
Какой хороший способ отладить, как парсер интерпретирует мою грамматику? Есть ли что-то похожее на выгрузку IFormattableDocument
в консоль, но для лексера / парсера / чего угодно?
И, наконец, как лучше всего решить эту проблему с точки зрения Xtext . Следует ли мне изучать настраиваемые типы данных или это можно выразить в чистом Xtext?
Я пытаюсь понять основные принципы.
Обновление
Ну это конечно странно. Я попытался пока обойтись без этого и реализовать следующую часть spe c.
; 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, но попадание туда случайно не дает цели. Теперь я приму любой ответ, который может указать или описать мне основные принципы, которые вызывают описанное мной поведение.
Почему я не могу просто сопоставить случайную группу слов? Как определение присваивания narrative
вместе с присваиванием description
в Story
изменяет то, как синтаксический анализатор интерпретирует грамматику?