#parsing #antlr #antlr4
#синтаксический анализ #antlr #antlr4
Вопрос:
Я делаю первые шаги по использованию antlr4 и пытаюсь проанализировать частичную дату в европейском формате DD.MM.YYYY
.
Я хочу распознать обычную дату, подобную 15.05.2020
or 7.5.20
, но также и даты, которые содержат только месяц и год, подобные 05.2020
or 5.20
, и в дополнение к этим датам, которые содержат только год, подобный 2020
or 20
. В моем приложении я хочу иметь доступ ко всем частям даты (день, месяц и год), в которых некоторые части могут быть пустыми / нулевыми.
Вот моя грамматика на данный момент.
grammar LogicalDateExpressions;
stmt : date EOF
;
date : (YEAR)
| (MONTH DOT YEAR)
| (DAY DOT MONTH DOT YEAR)
;
YEAR : ([12] [0-9] [0-9] [0-9])
| ([0-9] [0-9])
;
MONTH : ('0'? [1-9])
| ('1' [012])
;
DAY : ('0'? [1-9])
| ([12][0-9])
| ('3'[01])
;
DOT : '.';
WS : [ trnu000C] -> skip;
Эта грамматика работает с одним годом ( 2020
), но не распознает комбинацию месяц-год ( 05.2020
). grun -tokens
сказал мне следующее.
[@0,0:1='05',<YEAR>,1:0]
[@1,2:2='.',<'.'>,1:2]
[@2,3:6='2020',<YEAR>,1:3]
[@3,9:8='<EOF>',<EOF>,2:0]
line 1:2 mismatched input '.' expecting <EOF>
Итак, с моим небольшим разбором я решил, что проблема в правиле синтаксического date
анализа, и я переписал его на
date : (
(DAY DOT)?
MONTH DOT
)?
YEAR
;
Но я все равно получил ту же ошибку. Затем я подумал, что, возможно, мне нужно изменить порядок правил лексера. Итак, вместо ГОДА -> МЕСЯЦА -> ДЕНЬ, я написал их ДЕНЬ -> МЕСЯЦ -> ГОД. Но grun
сказал мне.
[@0,0:1='05',<DAY>,1:0]
[@1,2:2='.',<'.'>,1:2]
[@2,3:6='2020',<YEAR>,1:3]
[@3,9:8='<EOF>',<EOF>,2:0]
line 1:3 mismatched input '2020' expecting MONTH
Я также попытался изменить порядок or’ed альтернатив в правиле синтаксического date
анализа, но это тоже не сработало. Затем я попытался изменить правила лексера ДЕНЬ, МЕСЯЦ, ГОД, чтобы сделать их правилами синтаксического анализа (день, месяц, год). После получения некоторых ошибок, потому что, по-видимому, обозначение [0-9] не разрешено в правилах синтаксического анализа, я изменил грамматику на это.
date : (year)
| (month DOT year)
| (day DOT month DOT year)
;
[...]
year : (('1'|'2') DIGIT DIGIT DIGIT)
| (DIGIT DIGIT)
;
month : ('0'? DIGIT_NO_ZERO)
| ('1' ('0'|'1'|'2'))
;
day : ('0'? DIGIT_NO_ZERO)
| (('1'|'2') DIGIT)
| ('3' ('0'|'1'))
;
[...]
DIGIT : [0-9];
DIGIT_NO_ZERO : [1-9];
Это тоже был облом. grun
сказал мне.
[@0,0:0='0',<'0'>,1:0]
[@1,1:1='5',<DIGIT>,1:1]
[@2,2:2='.',<'.'>,1:2]
[@3,3:3='2',<'2'>,1:3]
[@4,4:4='0',<'0'>,1:4]
[@5,5:5='2',<'2'>,1:5]
[@6,6:6='0',<'0'>,1:6]
[@7,9:8='<EOF>',<EOF>,2:0]
line 1:1 no viable alternative at input '05'
Насколько я понимаю, язык, который я ищу, является обычным. И каждый ввод однозначен. Итак, я попытался поместить всю «логику» в лексер, и мне это удалось со следующей грамматикой.
grammar LogicalDateExpressions;
stmt : date EOF
;
date : DT
;
DT : (
((('0'? [1-9])|([12][0-9])|('3'[01])) DOT)? // Day
(('0'? [1-9])|('1' [012])) DOT // Month
)?
((DIGIT DIGIT DIGIT DIGIT)|(DIGIT DIGIT)) // Year
;
DIGIT : [0-9];
DOT : '.';
WS : [ trnu000C] -> skip;
Он анализирует каждый ввод, который я ему даю. Но проблема в том, что каждый ввод — это просто DT
.
[@0,0:6='05.2020',<DT>,1:0]
[@1,9:8='<EOF>',<EOF>,2:0]
Я не могу различить день, месяц и год в посетителе / слушателе, потому что метки не разрешены в правилах lexer.
Итак, мой вопрос в том, в чем проблема с первой заданной грамматикой и что мне нужно изменить, чтобы она работала?
Взглянув на вывод токена из grun, я думаю, что могу понять проблему, каждый ввод за день, месяц и / или год может быть неоднозначным, но в целом ввод в сочетании с точками не должен быть. Как я могу сообщить об этом antlr?
Ответ №1:
Итак, мой вопрос в том, в чем проблема с первой заданной грамматикой и что мне нужно изменить, чтобы она работала?
Проблема в том, что лексер не управляется синтаксическим анализатором. Это означает, что когда анализатор пытается сопоставить токены DAY DOT MONTH
и входные 01.01
данные, лексер создаст не a DAY
и a MONTH
для этих двух 01
, а два MONTH
токена. Вот как работает лексер ANTLR: попробуйте захватить как можно больше символов для токена, и когда есть 2 или более токенов, которые соответствуют одним и тем же символам (например 01
, могут совпадать оба DAY
и MONTH
), пусть сначала «выиграет» определенный токен (который является MONTH
токеном). Обойти это невозможно.
Что вы могли бы сделать, это что-то вроде этого (непроверенный):
date
: year
| month DOT year
| day DOT month DOT year
;
day
: N_01_12
| N_13_31
;
month
: N_01_12
;
year
: N_01_12
| N_13_31
| N_32_99
| N_1000_2999
;
N_01_12
: '0'? D // 01-09
| '1' [0-2] // 10-12
;
N_13_31
: '1' [3-9] // 13-19
| '2' D // 20-29
| '3' [01] // 30-31
;
N_32_99
: '3' [2-9] // 32-39
| [4-9] D // 40-99
;
N_1000_2999
: [12] D D D // 1000-2999
;
fragment D : [0-9];
Комментарии:
1. спасибо за объяснение и код, я думаю, что понимаю. Кстати. код работает отлично. В любом случае, правильно ли это с технической точки зрения, если я скажу, что лексер в моем примере неоднозначен?
2. Я бы так это не назвал, хотя я бы понял, что люди так думают. Из-за того, как работает лексер ANTLR (совпадение жадное, и в случае ничьей выигрывает первое правило), двусмысленности на самом деле нет. И, конечно, пожалуйста!