空白文字のスキップ
引き続き、ASTは、タダじゃ作ってくれない。 - 設計と実装の狭間で。を添削。
予想以上に大変。
普通のやり方
Lexerのルールで、スキップしたいトークンに対して "{ $channel = HIDDEN; }" を指定すればOK。
WHITE_SPACE : ( ' ' | '\t' | '\r' | '\n' )+ { $channel = HIDDEN; } ;
これはLexerからParserにトークンが渡される際に乗せるチャネルを選ぶ機能で、HIDDENという傍流のチャネルにトークンを乗せればParserからは自動的に無視される。
しかし、これがヤヴァイ。
linecomment : C_LN (IDENT)+ (LT|EOF) ; ... LT : '\n' // Line feed. | '\r' // Carriage return. | '\u2028' // Line separator. | '\u2029' // Paragraph separator. ;
$channel = HIDDEN;なやり方だとLTがどうしてもParserに渡されないので、終端が分からなくなる。
Semantic Predicate
ANTLRにはSemantic Predicateという機能があって、要はLexerやらParserのルールを外側から捻じ曲げてあげることができます。
使い方は、ルールの中で "{
ということで、行コメントの中にいる場合のみLTをHIDDENにしないようにする。
predicateしてみる
まず、行コメントのなかかどうかを表すフラグをLexerに追加。
@lexer::members { private boolean inLineComment = false; }
次に、行コメントの開始でこのフラグをセット。
LINE_COMMENT_START : '--' { inLineComment = true; } ;
最後に、Semantic Predicateをうまく利用して、行コメントの終了とスキップするルールを作る。
行コメントが終了したら、フラグはリセットしておく。
WHITE_SPACE : ( ' ' | '\t' )+ { $channel = HIDDEN; } ; NEW_LINE : {!inLineComment}? ( '\r' | '\n' )+ { $channel = HIDDEN; } ; LINE_COMMENT_END : {inLineComment}? ( '\r\n' | '\r' | '\n' ) { inLineComment = false; } ;
さんぷる
読むというより、Parse Treeを眺めていただければと。
サンプル入力は↓。意図的に添削先とは文法を似せつつ趣旨を変えてます。
/* PRT 0 */ if (/* 100 */ hoge) -- IF a print 200; -- ELSE print /* 300 + 2 * 3 */ foo; else print /* 400 + 500 */ bar;
ちょっと力入れすぎた。
grammar SimpleTwoway; @lexer::members { private boolean inComment = false; private boolean inLineComment = false; } //// Way-1 program : w1_statement EOF ; w1_statement : w2_statement_decorator? ( w1_print_statement | w1_if_statement | w1_empty_statement | w2_statement ) ; w1_print_statement : 'print' w1_expression ';' ; w1_if_statement : 'if' '(' w1_expression ')' w1_statement ( 'else' w1_statement | /* empty */ ) ; w1_empty_statement : ';' ; w1_expression : w1_term ( '+' w1_term | '-' w1_term )* ; w1_term : w1_unary ( '*' w1_unary | '/' w1_unary | '%' w1_unary )* ; w1_unary : w1_factor | '-' w1_factor | '+' w1_factor ; w1_factor : w2_expression_decorator? ( ident | number | '(' w1_expression ')' ) ; ident : IDENT ; number : NUMBER ; // Way-2 w2_statement : ( BLOCK_COMMENT_START 'IF' w2_expression BLOCK_COMMENT_END | LINE_COMMENT_START 'IF' w2_expression LINE_COMMENT_END ) w1_statement ( ( BLOCK_COMMENT_START 'ELSE' 'IF' w2_expression BLOCK_COMMENT_END | LINE_COMMENT_START 'ELSE' 'IF' w2_expression LINE_COMMENT_END ) w1_statement )* ( BLOCK_COMMENT_START 'ELSE' BLOCK_COMMENT_END | LINE_COMMENT_START 'ELSE' LINE_COMMENT_END ) w1_statement ; w2_statement_decorator : w2_print_statement ; w2_expression_decorator : BLOCK_COMMENT_START w2_expression BLOCK_COMMENT_END ; w2_print_statement : BLOCK_COMMENT_START 'PRT' w2_expression BLOCK_COMMENT_END | LINE_COMMENT_START 'PRT' w2_expression LINE_COMMENT_END ; w2_expression : w2_term ( '+' w2_term | '-' w2_term )* ; w2_term : w2_unary ( '*' w2_unary | '/' w2_unary | '%' w2_unary )* ; w2_unary : w2_factor | '-' w2_factor | '+' w2_factor ; w2_factor : ident | number | '(' w2_expression ')' ; //// Common tokens IDENT : IDENT_START ( IDENT_START | DIGIT )* ; NUMBER : '0' | NONZERO_DIGIT DIGIT* ; fragment IDENT_START : 'A'..'Z' | 'a'..'z' | '_' ; fragment DIGIT : '0' | NONZERO_DIGIT ; fragment NONZERO_DIGIT : '1'..'9' ; //// comment bounds // cannot be declared in other comments BLOCK_COMMENT_START : {!inComment}? '/*' { inComment = true; } ; // cannot be declared in line comments BLOCK_COMMENT_END : {!inLineComment}? '*/' { inComment = false; } ; // cannot be declared in other comments LINE_COMMENT_START : {!inComment}? '--' { inLineComment = true; inComment = true; } ; // cannot be declared in other line comments LINE_COMMENT_END : {inLineComment}? ( '\r\n' | '\r' | '\n' | EOF ) { inLineComment = false; inComment = false; } ; //// whitespaces WHITE_SPACE : ( ' ' | '\t' )+ { $channel = HIDDEN; } ; NEW_LINE : {!inLineComment}? ( '\r' | '\n' )+ { $channel = HIDDEN; } ;