语法同时解析多行注释和字符串文字

时间:2011-09-23 09:05:25

标签: parsing antlr grammar lexer

我正在尝试解析C ++ / Java样式的源文件,并希望将注释,字符串文字和空格隔离为标记。

对于空格和注释,通常建议的解决方案是(使用ANTLR语法):

// WS comments*****************************
WS: (' '|'\n'| '\r'|'\t'|'\f' )+ {$channel=HIDDEN;};
ML_COMMENT: '/*' (options {greedy=false;}: .)* '*/' {$channel=HIDDEN;};
SL_COMMENT: '//' (options {greedy=false;}: .)* '\r'? '\n' {$channel=HIDDEN;};

但问题是我的源文件也包含字符串文字,例如

printf("   /* something looks like comment and whitespace \n");
printf("    something looks like comment and whitespace */ \n");

“”中的整个内容应该被视为单个令牌,但我的ANTLR词法分析器显然会将它们视为ML_COMMENT令牌:

    /* something looks like comment and whitespace \n");
printf("    something looks like comment and whitespace */

但是我无法创建另一个词法分析器规则来将一个标记定义为一对“(假设”转义序列被正确处理)中的内容,因为这将被错误地视为字符串标记:

/*  comment...."comment that looks */   /*like a string literal"...more comment */

简而言之,2对/ ** /和“”将相互干扰,因为每个都可以包含另一对的开头作为其有效内容。那么我们应该如何定义词法分析器语法来处理这两种情况?

2 个答案:

答案 0 :(得分:4)

  

JavaMan写道:

     

我正在尝试解析C ++ / Java样式的源文件,并希望将注释,字符串文字和空格隔离为标记。

你不应该匹配char文字吗?考虑:

char c = '"';

双引号不应被视为字符串文字的开头!

  

JavaMan写道:

     

简而言之,2对/ ** /和“”会相互干扰。

呃,不。如果首先“看到”/*,它将一直消耗到第一个*/。输入如:

/*  comment...."comment that looks like a string literal"...more comment */

这意味着双引号也被消耗掉了。对于字符串文字也是如此:当首先看到双引号时,/*和/或*/将被消耗,直到遇到下一个(未转义的)"为止。

还是我误解了?

请注意,您可以在options {greedy=false;}:.*之前删除语法中的.+,默认情况下不合适。

这是一种方式:

grammar T;

parse
  :  (t=. 
       {
         if($t.type != OTHER) {
           System.out.printf("\%-10s >\%s<\n", tokenNames[$t.type], $t.text);
         }
       }
     )+
     EOF
  ;

ML_COMMENT
  :  '/*' .* '*/'
  ;

SL_COMMENT
  :  '//' ~('\r' | '\n')*
  ;

STRING
  :  '"' (STR_ESC | ~('\\' | '"' | '\r' | '\n'))* '"'
  ;

CHAR
  :  '\'' (CH_ESC | ~('\\' | '\'' | '\r' | '\n')) '\''
  ;

SPACE
  :  (' ' | '\t' | '\r' | '\n')+
  ;

OTHER
  :  . // fall-through rule: matches any char if none of the above matched
  ;

fragment STR_ESC
  :  '\\' ('\\' | '"' | 't' | 'n' | 'r') // add more:  Unicode esapes, ...
  ;

fragment CH_ESC
  :  '\\' ('\\' | '\'' | 't' | 'n' | 'r') // add more: Unicode esapes, Octal, ...
  ;

可以用以下方法测试:

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String source = 
        "String s = \" foo \\t /* bar */ baz\";\n" +
        "char c = '\"'; // comment /* here\n" +
        "/* multi \"no string\"\n" +
        "   line */";
    System.out.println(source + "\n-------------------------");
    TLexer lexer = new TLexer(new ANTLRStringStream(source));
    TParser parser = new TParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}

如果您运行上面的课程,则会在控制台上打印以下内容:

String s = " foo \t /* bar */ baz";
char c = '"'; // comment /* here
/* multi "no string"
   line */
-------------------------
SPACE      > <
SPACE      > <
SPACE      > <
STRING     >" foo \t /* bar */ baz"<
SPACE      >
<
SPACE      > <
SPACE      > <
SPACE      > <
CHAR       >'"'<
SPACE      > <
SL_COMMENT >// comment /* here<
SPACE      >
<
ML_COMMENT >/* multi "no string"
   line */<

答案 1 :(得分:-3)

基本上你的问题是:在字符串文字中,必须忽略注释(/ *和//),反之亦然。 IMO只能通过顺序阅读来解决。在逐个字符的基础上遍历源文件时,您可以将其作为具有状态Text,BlockComment,LineComment,StringLiteral的状态机来处理。

尝试使用正则表达式甚至是语法来解决这个问题。

请注意,任何C / C ++ / C#/ Java词法分析器也需要处理这个完全相同的问题。我很确定它采用类似状态机的解决方案。所以我建议的是,如果可以的话,以这种方式定制你的词法分析器。