Давайте немного отойдем от JavaCC и посмотрим, какая у вас на самом деле грамматика.
parse --> ows ( body )+
body --> part sep
part --> <NAME>
part --> <NUM>
part --> <NUM> ows "-" ows <NUM>
sep --> (<EOL> | <COMMA> | <SEMICOLON> | <WHITE>)+
sep --> EOF
ows --> (<WHITE>)*
Вы должны проверить это, чтобы убедиться, что (а) я не допустил ошибок и (б)это действительно тот язык, который вы намеревались.
Мне не нравится, как вы имеете дело с EOF
.Это не совсем разделитель.Я бы предложил использовать следующую грамматику, которая практически идентична
parse --> ows body
body --> part ( sep body | <EOF> )
part --> <NAME>
part --> <NUM>
part --> <NUM> ows "-" ows <NUM>
sep --> (<EOL> | <COMMA> | <SEMICOLON> | <WHITE>)+
ows --> (<WHITE>)*
Первое решение: Синтаксический взгляд вперед
ОП сказал Было былегко, если бы был способ проверить следующий токен, не потребляя его. Есть.Он называется синтаксический взгляд .
Единственное место, где нам нужно смотреть вперед, - это различить второе и третье производство для part
.Давайте скомбинируем их.
part --> <NAME>
part --> <NUM> ( ows "-" ows <NUM> )?
Отсутствие фиксированной длины не позволяет определить, будет ли выбран необязательный путь во втором производстве.Таким образом, мы используем синтаксический взгляд следующим образом:
part --> <NAME>
part --> <NUM> ( LOOKAHEAD( ows "-" ) ows "-" ows <NUM> )?
Теперь мы закончили.Давайте вернем производство обратно в JavaCC
void parse() : { }
{
ows() body }
}
void body() : { }
{
part() ( sep() body() | <EOF> )
}
void part() : { }
{
<NAME>
|
<NUM>
( LOOKAHEAD( ows() "-")
ows() "-" ows() <NUM>
)?
}
void sep() : {}
{
(<EOL> | <COMMA> | <SEMICOLON> | <WHITE>)+
}
void ows() : {}
{
(<WHITE>)*
}
Второе решение: LL (1)
Можем ли мы решить это с помощью грамматики LL (1)?Да.Давайте вернемся к исходной грамматике, точнее к грамматике, которая выводит EOF
из цикла.
parse --> ows body
body --> part (sep body | <EOF>)
part --> <NAME>
part --> <NUM> ( ows "-" ows <NUM> )?
sep --> (<EOL> | <COMMA> | <SEMICOLON> | <WHITE>)+
ows --> (<WHITE>)*
Inline part
и введем нетерминал afternum
parse --> ows body
body --> <NAME> (sep body | <EOF>)
body --> <NUM> afternum
afternum --> ( ows "-" ows <NUM> )? (sep body | <EOF>)
sep --> (<EOL> | <COMMA> | <SEMICOLON> | <WHITE>)+
ows --> (<WHITE>)*
Теперь проблема в afternum
.
Когда мы начинаем анализ afternum
, есть 5 возможностей для рассмотрения.(i) Следующий токен - "-"
.(ii) Следующим токеном является EOL
, COMMA
или SEMICOLON
.(iii) Следующий токен - это пробел.(iv) Следующий токен - EOF
.(v) В любом другом случае мы имеем ошибку.
В случае (ii) это не может быть последней частью.В случае (iii) БЕЛЫЙ, который мы только что видели, мог быть первым символом sep
, или это могло привести к дефису.Мы создаем новый нетерминал, чтобы иметь дело с обеими возможностями.
afternum --> "-" ows <NUM> (sep body | <EOF>)
afternum --> nonwssep (sep)? body
afternum --> <WHITE> moreafternum
afternum --> EOF
moreafternum --> ows "-" ows <NUM> (sep body | EOF)
| sep? body
nonwssep --> <EOL> | <COMMA> | <SEMICOLON>
Теперь проблема в moreafternum
, поскольку, если следующий токен WHITE
, любой из вариантов является жизнеспособным.
Давайте немного манипулируем moreafternum
.Цель состоит в том, чтобы выставить токен WHITE
, чтобы мы могли его выделить.
moreafternum
= By definition
ows "-" ows <NUM> (sep body | EOF) | sep? body
= Expand the ?
ows "-" ows <NUM> (sep body | EOF)
| body
| sep body
= Expand first `ows` and split white from other cases
"-" ows <NUM> (sep body | EOF)
| WHITE ows "-" ows <NUM> (sep body | EOF)
| body
| sep body
= Expand the `sep` in the fourth case
"-" ows <NUM> (sep body | EOF)
| WHITE ows "-" ows <NUM> (sep body | EOF)
| body
| (WHITE | nonwesep) sep? body
= Split the fourth case
"-" ows <NUM> (sep body | EOF)
| WHITE ows "-" ows <NUM> (sep body | EOF)
| body
| WHITE sep? body
| nonwssep sep? body
= Duplicate the fourth choice
"-" ows <NUM> (sep body | EOF)
| WHITE ows "-" ows <NUM> (sep body | EOF)
| WHITE sep? body
| body
| WHITE sep? body
| nonwssep sep?
= Combine the second and third choices.
"-" ows <NUM> (sep body | EOF)
| WHITE ( ows "-" ows <NUM> (sep body | EOF) | sep? body )
| body
| WHITE sep? body
| nonwssep sep? body
= combine the third, fourth, and fifth choices
"-" ows <NUM> (sep body | EOF)
| WHITE ( ows "-" ows <NUM> (sep body | EOF) | sep? body)
| sep? body
= Definition of moreafternum
"-" ows <NUM> (sep body | EOF)
| WHITE moreafternum
| sep? body
Теперь мы можем переопределить moreafternum
с помощью этой рекурсивной версии
moreafternum --> "-" ows <NUM> (sep body | EOF)
| <WHITE> moreafternum
| sep? body
Если мы закодируем этоПри производстве в JavaCC все еще будет конфликт выбора между вторым и третьим выбором, когда следующий токен - БЕЛЫЙ.JavaCC предпочтет второе, а не третье, что мы и хотим.Если вам не нравится предупреждение, вы можете подавить его LOOKAHEAD.Обратите внимание, что этот LOOKAHEAD не изменит созданный код Java, он просто подавляет предупреждение.
void moreafternum() : {} {
"-" ows() <NUM> (sep() body() | <EOF>)
|
// LOOKAHEAD( <WHITE> ) // Optional lookahead to suppresss the warning
<WHITE> moreafternum()
|
( sep() )? body() }
Мы можем перейти к LL (1), еще раз посмотрев на moreafternum
.
moreafternum
= From above
"-" ows <NUM> (sep body | EOF)
| WHITE ( ows "-" ows <NUM> (sep body | EOF) | sep? body)
| body
| WHITE sep? body
| nonwssep sep? body
= Fourth choice is subsumed by the second.
"-" ows <NUM> (sep body | EOF)
| WHITE ( ows "-" ows <NUM> (sep body | EOF) | sep? body)
| body
| nonwssep sep? body
= Combine last two choices
"-" ows <NUM> (sep body | EOF)
| WHITE ( ows "-" ows <NUM> (sep body | EOF) | sep? body)
| (nonwssep sep?)? body
= Original definition of moreaftersep
"-" ows <NUM> (sep body | EOF)
| WHITE moreaftersep
| (nonwssep sep?)? body
Если сложить все вместе, мы получим
parse --> ows body
body --> <NAME> (sep body | <EOF>)
body --> <NUM> afternum
afternum --> "-" ows <NUM> (sep body | <EOF>)
afternum --> <WHITE> moreafternum
afternum --> nonwssep (sep)? body
afternum --> EOF
moreafternum --> "-" ows <NUM> (sep body | EOF)
moreafternum --> <WHITE> moreafternum
moreafternum --> ( nonwssep (sep)? )? body
nonwssep --> <EOL> | <COMMA> | <SEMICOLON>
sep --> (nonwssep | <WHITE>)+
ows --> (<WHITE>)*
Это LL (1), так что вы можете перевести его на JavaCC, не обращая внимания.