Грамматика для одновременного анализа многострочных комментариев и строковых литералов - PullRequest
4 голосов
/ 23 сентября 2011

Я пытаюсь проанализировать исходные файлы в стиле 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 */

Но я не могу создать другое правило лексера, чтобы определять токен как нечто внутри пары "(при условии, что escape-последовательность \" обрабатывается правильно), потому что этобудет ошибочно рассматриваться как строковый токен:

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

Короче говоря, две пары / ** / и "" будут мешать друг другу, поскольку каждая из них может содержать начало другого в качестве допустимого содержимого.Так как же нам определить грамматику лексера для обоих случаев?

Ответы [ 2 ]

4 голосов
/ 23 сентября 2011

JavaMan писал:

Я пытаюсь проанализировать исходные файлы в стиле C ++ / Java и хотел бы выделить комментарий, строковый литерал и пробел в качестве токенов.

Разве вы не должны соответствовать буквам литерала? Рассмотрим:

char c = '"';

Двойная кавычка не должна рассматриваться как начало строкового литерала!

JavaMan писал:

Короче говоря, две пары / ** / и "" будут мешать друг другу.

Ошибка, нет. Если /* «виден» первым, он будет потреблять весь путь до первого */. Для ввода, как:

/*  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 */<
0 голосов
/ 13 июля 2012

В основном ваша проблема такова: внутри строкового литерала комментарии (/ * и //) должны игнорироваться, и наоборот. ИМО это можно решить только путем последовательного чтения. когда вы просматриваете исходный файл посимвольно, вы можете использовать его как конечный автомат с состояниями Text, BlockComment, LineComment, StringLiteral.

Это сложная проблема, которую нужно решить с помощью регулярных выражений или даже грамматики.

Имейте в виду, что любой лексер C / C ++ / C # / Java также должен решить эту проблему. Я вполне уверен, что он использует решение, подобное конечному автомату. Так что я предлагаю, если вы можете, настроить свой лексер таким образом.

...