#parsing #lexer #bnf #javacc
Вопрос:
Я недавно начал изучать синтаксический анализатор javacc. Меня попросили написать синтаксический анализатор, в котором токен принимает числа от 1 до многих, и другой токен, который принимает числа от 2 до многих, поэтому я придумал что-то вроде этого:
TOKEN : {
<NUM1: <NUM> (<NUM>)* > //for one or more
<NUM2: (<NUM>) > //for two or more
<NUM :(["0"-"9"]) >
// and in the function
void calc():
{}
{
(
(<NUM1>)
(<NUM2>)
)* <EOF>
}
Однако, даже если я передам текстовое значение БЕЗ цифр, оно будет успешно передано. Что я делаю не так в этом?
Ответ №1:
Синтаксис JavaCC для лексических маркеров позволяет использовать повторения элементов, заключенных в область ()
, за которой следует один из:
? - zero or one time
* - zero or more times
- one or more times
В вашем случае вам понадобятся два жетона:
TOKEN:
{
<NUM2: ["0"-"9"] (["0"-"9"]) > // for two or more
<NUM1: (["0"-"9"]) > // for one or more
}
Вы читаете это как:
- названный токен
NUM1
соответствует от одного до бесконечного числа цифр - маркер с именем
NUM2
соответствует от двух до бесконечного числа цифр
Лексический механизм в JavaCC использует один символ из входного потока символов и пытается распознать токен. Эти два автомата выглядят следующим образом:
Лексер прогрессирует одновременно в обоих автоматах для обоих токенов. После того, как дальнейший прогресс невозможен, распознается последний найденный токен. Если возможно более одного типа токена, то распознается тот, который был объявлен первым. По этой причине NUM2
заявлено ранее NUM1
. Это означает, что для ввода 1
токен NUM2
не будет распознан, потому что для него требуется более одной цифры. В этом случае NUM1
будет только один тип токена, соответствующий этому входу. Для ввода 12
оба типа токенов будут принимать его, но NUM2
будут распознаны, потому что он объявлен первым. Это означает, что если вы закажете их NUM1
первыми, то NUM2
вы никогда не получите NUM2
токен, потому NUM1
что всегда будете «выигрывать» с его наивысшим приоритетом.
Чтобы использовать их, у вас могут быть две функции синтаксического анализа, подобные этим:
void match_one_to_many_numbers() : {} { <NUM1> (" " <NUM1>)* <EOF> }
void match_two_to_many_numbers() : {} { <NUM2> (" " <NUM2>)* <EOF> }
Вы читаете это как:
- функция
match_one_to_many_numbers
принимает от одного до бесконечного числа токеновNUM1
, разделенных пробелом, а затем входной поток должен заканчиватьсяEOF
системным токеном - функция
match_two_to_many_numbers
та же, только из токенаNUM2
Поскольку оба токена принимают бесконечное количество цифр, вы не можете иметь последовательность этих токенов без разделителя, который не является цифрой.
Комментарии:
1. Слава богу, это работает!!!!
2. Поскольку
<NUM1>
совпадают только токены, содержащие одну цифру, вы могли бы написать просто<NUM1: (["0"-"9"])>
. Если вы хотите сопоставить токен, содержащий 1 или более цифр, вы можете написать(<NUM1> | <NUM2>)
в синтаксическом анализаторе.3. Использование
" "
в синтаксическом анализаторе имеет смысл только в том случае, если у вас есть такоеTOKEN
объявление, какTOKEN:{ <SPACE : " ">}
. Если вы пропускаете пробел, скажем,SKIP:{< " ">}
с помощью,, то вам не нужно и не следует использовать «» в грамматике. Если для вас нет объявления ТОКЕНА или ПРОПУСКА" "
, и вы используете" "
правила грамматики, то JavaCC выведетTOKEN
для вас объявление, которое может вам не понадобиться. Прочтите документацию и часто задаваемые вопросы для получения полного объяснения.4. @TheodoreNorvell Первый комментарий неверен, второй не имеет отношения к вопросу (ничто не мешает использовать встроенные объявления токенов в грамматике синтаксического анализатора, это документированная функция, я сохранил два типа токенов, как указано в вопросе, без других нерелевантных токенов).
5. Первый комментарий содержал три утверждения. Что не соответствует действительности? (a) Строки из более чем одной цифры не приведут к появлению токенов NUM1. (b) Таким образом, определение NUM1 может быть упрощено. (c) Вы можете сопоставлять строки из 1 или более цифр, как показано на рисунке. Второй комментарий был предназначен для того, чтобы разъяснить ваше использование «» для спрашивающего. В его вопросе не было ничего, что указывало бы на то, что он хотел, чтобы «» было знаком. Поэтому я подумал, что стоит отметить, что ваша грамматика делает «» символом, что может быть неочевидно для новичка.