質問
同じ文法で有効な日付と数値の両方を解析する最も簡単な(最短、最小のルール、警告なし)方法は何ですか?私の問題は、有効な月(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: ('<'|'>');
解決
問題は、レクサーやパーサーで構文とセマンティックの両方のチェックを実行したいように見えることです。これはよくある間違いであり、非常に単純な言語でのみ可能なことです。
本当に必要なのは、レクサーとパーサーでより広く受け入れてから、セマンティックチェックを実行することです。字句解析の厳格さはあなた次第ですが、月の前にゼロを受け入れる必要があるかどうかに応じて、2つの基本的なオプションがあります。1)INTを本当に受け入れ、2)にDATENUMを定義する有効な日であるが、有効なINTではないトークンのみを受け入れます。コードの後半で必要なセマンティックチェックが少なくなるため、2番目をお勧めします(INTは構文レベルで検証可能になり、日付に対してセマンティックチェックのみを実行する必要があるためです。最初のアプローチ:
INT: '0'..'9'+;
2番目のアプローチ:
DATENUM: '0' '1'..'9';
INT: '0' | SIGN? '1'..'9' '0'..'9'*;
レクサーでこれらのルールの使用を受け入れた後、日付フィールドは次のいずれかになります。
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];