Antlr:Самый простой способ распознать даты и цифры?
Вопрос
Каков самый простой (с наименьшим количеством правил и без предупреждений) способ анализа как допустимых дат, так и чисел в одной грамматике?Моя проблема в том, что правило лексера, соответствующее действительному месяцу (1-12), будет соответствовать любому вхождению 1-12.Поэтому, если я просто хочу сопоставить число, мне нужно правило синтаксического анализа, например:
number: (MONTH|INT);
Это становится только сложнее, когда я добавляю правила лексера для дня и года.Мне нужно правило синтаксического анализа для даты, подобное этому:
date: month '/' day ( '/' year )? -> ^('DATE' year month day);
Мне все равно, являются ли месяц, день и год правилами синтаксического анализа или лексера, главное, чтобы в итоге я получал ту же древовидную структуру.Мне также нужно уметь распознавать цифры в другом месте, например:
foo: STRING OP number -> ^(OP STRING number);
STRING: ('a'..'z')+;
OP: ('<'|'>');
Решение
Проблема в том, что вы, похоже, хотите выполнить как синтаксическую, так и семантическую проверку в вашем лексере и / или вашем синтаксическом анализаторе.Это распространенная ошибка, которая возможна только на очень простых языках.
Что вам действительно нужно сделать, так это принять более широкий подход к лексеру и синтаксическому анализатору, а затем выполнить семантические проверки.Насколько строго вы используете лексику, зависит от вас, но у вас есть два основных варианта, в зависимости от того, нужно ли вам принимать нули, предшествующие вашим дням месяца:1) Будьте действительно приемлемы для ваших целых чисел, 2) определите DATENUM, чтобы принимать только те токены, которые являются действительными днями, но не действительными целыми числами.Я рекомендую второй вариант, потому что позже в коде потребуется меньше семантических проверок (поскольку тогда целые числа будут проверяемы на уровне синтаксиса, и вам нужно будет выполнять семантические проверки только для ваших дат.Первый подход:
INT: '0'..'9'+;
Второй подход:
DATENUM: '0' '1'..'9';
INT: '0' | SIGN? '1'..'9' '0'..'9'*;
После принятия использования этих правил в lexer ваше поле даты будет либо:
date: INT '/' INT ( '/' INT )?
или:
date: (INT | DATENUM) '/' (INT | DATENUM) ('/' (INT | DATENUM) )?
После этого вы должны выполнить семантическую проверку вашего AST, чтобы убедиться, что ваши даты действительны.
Однако, если вы решительно настроены на выполнение семантических проверок в своей грамматике, ANTLR допускает семантические предикаты в анализаторе, поэтому вы могли бы создать поле даты, которое проверяет значения следующим образом:
date: month=INT '/' day=INT ( year='/' INT )? { year==null ? (/* First check /*) : (/* Second check */)}
Однако, когда вы делаете это, вы встраиваете в свою грамматику код, специфичный для конкретного языка, и он не будет переносимым между целевыми объектами.
Другие советы
Используя ANTLR4, вот простая комбинированная грамматика, которую я использовал.Он использует лексер для сопоставления только простых токенов, оставляя правила синтаксического анализа для интерпретации дат и чисел.
// parser rules
date
: INT SEPARATOR month SEPARATOR INT
| INT SEPARATOR month SEPARATOR INT4
| INT SEPARATOR INT SEPARATOR INT4;
month : JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC ;
number : FLOAT | INT | INT4 ;
// lexer rules
FLOAT : DIGIT+ '.' DIGIT+ ;
INT4 : DIGIT DIGIT DIGIT DIGIT;
INT : DIGIT+;
JAN : [Jj][Aa][Nn] ;
FEB : [Ff][Ee][Bb] ;
MAR : [Mm][Aa][Rr] ;
APR : [Aa][Pp][Rr] ;
MAY : [Mm][Aa][Yy] ;
JUN : [Jj][Uu][Nn] ;
JUL : [Jj][Uu][Ll] ;
AUG : [Aa][Uu][Gg] ;
SEP : [Ss][Ee][Pp] ;
OCT : [Oo][Cc][Tt] ;
NOV : [Nn][Oo][Vv] ;
DEC : [Dd][Ee][Cc] ;
SEPARATOR : [/\\\-] ;
fragment DIGIT : [0-9];