Почему возникает проблема
В JavaCC, метод по умолчанию для определения того, какую ветвь выбрать, заключается в просмотре следующего токена. Если этот токен совместим с первым вариантом, первый вариант выбран, и это решение необратимо. Возврата нет.
Так посмотрите на выбор
void Nonterminal() : {}
{
<TOKEN> <COLON> A()
|
<TOKEN> <COLON> A() <COMMA> Nonterminal()
}
и предположим, что следующий токен на входе - <TOKEN>
. Первый выбор будет сделан независимо от того, будет ли <COMMA>
позже. Другими словами
<TOKEN> <COLON> A()
| <TOKEN> <COLON> A() <COMMA> Nonterminal()
эквивалентно
<TOKEN> <COLON> A()
За исключением того, что первый выдаст предупреждающее сообщение, поскольку JavaCC видит, что написанное вами не имеет смысла.
Хорошее решение
Ответ на ваш вопрос "да". Одно из решений - сделать то, что вы сделали, и исключить общий префикс
void Nonterminal() : {}
{
<TOKEN> <COLON> A() (<COMMA> NonTerminal())?
}
Решение, которое может быть лучше в зависимости от
Если по какой-то причине вы не знаете, на что рассчитывать, вы также можете сделать следующее
void Nonterminal() : {}
{
LOOKAHEAD( <TOKEN> <COLON> A() <COMMA>)
<TOKEN> <COLON> A() <COMMA> Nonterminal()
|
<TOKEN> <COLON> A()
}
Здесь парсер будет смотреть вперед во входном потоке. Если он видит запятую, первый выбор сделан. В противном случае второй.
Это может быть полезно, если семантические действия различны в обоих случаях. Э.Г.
LOOKAHEAD( <TOKEN> <COLON> A() <COMMA>)
<TOKEN> {doSomething();} <COLON> A() <COMMA> Nonterminal()
|
<TOKEN> {doSomethingDifferent();} <COLON> A()
Нерекурсивное решение.
Третий вариант -
void Nonterminal() : {}
{
Foo() (<COMMA> Foo() )*
}
void Foo() : {}
{
<TOKEN> <COLON> A()
}