Comment analyser une date partielle dans ANTLR?

Sep 30 2020

Je fais mes premiers pas pour utiliser antlr4 et j'essaye d'analyser une date partielle au format européen DD.MM.YYYY.

Je veux reconnaître une date normale comme 15.05.2020ou 7.5.20mais aussi des dates qui ne contiennent que le mois et l'année comme 05.2020ou 5.20et en plus de ces dates qui ne contiennent qu'une année comme 2020ou 20. Dans mon application, je souhaite avoir accès à toutes les parties d'une date (jour, mois et année) à laquelle certaines parties peuvent être vides / nulles.

Voici ma grammaire pour l'instant.

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      :  [ \t\r\n\u000C]+ -> skip;

Cette grammaire fonctionne avec une seule année ( 2020) mais ne reconnaît pas une combinaison mois-année ( 05.2020). grun -tokensm'a dit ce qui suit.

[@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>

Donc, avec ma poignée, j'ai pensé que la règle de l'analyseur dateétait le problème et je l'ai réécrit pour

date : (
          (DAY DOT)?      
          MONTH DOT     
       )?
       YEAR               
     ;

Mais j'ai toujours la même erreur. Ensuite, j'ai pensé que je devais peut-être réorganiser les règles du lexer. Donc au lieu d'ANNÉE -> MOIS -> JOUR, je leur ai écrit JOUR -> MOIS -> ANNÉE. Mais grunme l'a dit.

[@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

J'ai également essayé de changer l'ordre des alternatives or'ed dans la règle de l'analyseur, datemais cela n'a pas fonctionné non plus. Ensuite, j'ai essayé de changer les règles du lexer DAY, MONTH, YEAR pour en faire des règles d'analyseur (jour, mois, année). Après avoir obtenu des erreurs parce qu'apparemment la notation [0-9] n'est pas autorisée dans les règles de l'analyseur, j'ai changé la grammaire en ceci.

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];

C'était aussi une déception. grunm'a dit.

[@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'

Pour autant que je comprends, la langue que je recherche est une langue ordinaire. Et chaque entrée est sans ambiguïté. J'ai donc essayé d'introduire toute la «logique» dans le lexer et j'ai réussi avec la grammaire suivante.

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      :  [ \t\r\n\u000C]+ -> skip;

Il analyse chaque entrée que je lui donne. Mais le problème est que chaque entrée n'est qu'un fichier DT.

[@0,0:6='05.2020',<DT>,1:0]
[@1,9:8='<EOF>',<EOF>,2:0]

Je ne peux pas faire la distinction entre le jour, le mois et l'année chez un visiteur / auditeur car les étiquettes ne sont pas autorisées dans les règles lexer.


Ma question est donc de savoir où est le problème avec la première grammaire donnée et que dois-je changer pour que cela fonctionne?

En regardant la sortie du jeton de grun, je pense que je pourrais saisir le problème que chaque entrée pour un jour, un mois et / ou une année peut être ambiguë, mais dans son ensemble, elle ne devrait pas être en conjonction avec les points. Comment puis-je dire ça à Antlr?

Réponses

1 BartKiers Sep 30 2020 at 15:02

Ma question est donc de savoir où est le problème avec la première grammaire donnée et que dois-je changer pour que cela fonctionne?

Le problème est que le lexer n'est pas piloté par l'analyseur. Cela signifie que lorsque l'analyseur essaie de faire correspondre les jetons DAY DOT MONTHet que l'entrée est 01.01, le lexer ne créera pas un DAYet un MONTHpour ces deux 01, mais deux MONTHjetons. Voici comment fonctionne le lexer d'ANTLR: essayez de saisir autant de caractères pour un jeton, et quand il y a 2 jetons ou plus qui correspondent aux mêmes caractères (comme 01peuvent être mis en correspondance par les deux DAYet MONTH), laissez le jeton défini en premier est le MONTHjeton). Il n'y a aucun moyen de contourner cela.

Ce que vous pourriez faire est quelque chose comme ceci (non testé):

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];