Грамматика ANTLR для предварительной обработки исходных файлов при сохранении форматирования белого пространства - PullRequest
1 голос
/ 16 сентября 2011

Я пытаюсь предварительно обработать исходные файлы C ++ с помощью ANTLR. Я хотел бы вывести входной файл с сохранением всех форматов пробелов исходного исходного файла, в то же время вставив несколько новых собственных исходных кодов в соответствующие места.

Я знаю, что для сохранения WS требуется следующее правило лексера:

WS: (' '|'\n'| '\r'|'\t'|'\f' )+ {$channel=HIDDEN;};

При этом мои правила синтаксического анализа будут иметь атрибут $ text, содержащий все скрытые WS. Но проблема в том, что для любого правила синтаксического анализатора его атрибут $ text включает в себя только тот входной текст, который начинается с позиции, соответствующей первому токену правила. Например, если это мой ввод (обратите внимание на форматирование WS до и между токенами):

line   1;     line   2;

И, если у меня есть 2 отдельных правила синтаксического анализа, соответствующих

"line   1;" 

и

"line   2;" 

выше отдельно, но не вся строка:

"    line   1;     line   2;"

, тогда ведущий WS и те WS между "line 1" и "line 2" теряются (недоступно ни по одному из моих правил).

Что я должен сделать, чтобы сохранить ВСЕ БЕЛЫЕ ПРОСТРАНСТВА, позволяя правилам моего синтаксического анализатора определять, когда добавлять новые коды в соответствующих местах?

EDIT

Скажем, всякий раз, когда мой код содержит вызов функции (1) , используя 1 в качестве параметра, но не что-то еще, он добавляет extraFunction () перед ним:

void myFunction() {
   function();
   function(1);
}

становится:

void myFunction() {
   function();
   extraFunction();
   function(1);
}

Этот предварительно обработанный вывод должен оставаться читаемым человеком, поскольку люди будут продолжать его кодировать. Для этого простого примера текстовый редактор может справиться с этим. Но есть более сложные случаи, которые оправдывают использование ANTLR.

Ответы [ 3 ]

2 голосов
/ 16 сентября 2011

Другое решение, но, возможно, также не очень практичное (?): Вы можете собрать все пробелы в обратном направлении, что-то вроде этого непроверенного псевдокода:

grammar T;

@members {
    public printWhitespaceBetweenRules(Token start) {
        int index = start.getTokenIndex() - 1;

        while(index >= 0) {
            Token token = input.get(index);
            if(token.getChannel() != Token.HIDDEN_CHANNEL) break;
            System.out.print(token.getText());
            index--;
        }
    }
}

line1: 'line' '1' {printWhitespaceBetweenRules($start); };
line2: 'line' '2' {printWhitespaceBetweenRules($start); };
WS: (' '|'\n'| '\r'|'\t'|'\f' )+ {$channel=HIDDEN;};

Но вам все равно придется изменить каждое правило.*

1 голос
/ 16 сентября 2011

Вот еще один способ ее решения (по крайней мере, пример, который вы разместили).

Итак, вы хотите заменить ...function(1) на ...extraFunction();\nfunction(1), где точки - это отступы, а \n - разрыв строки.

То, что вы можете сделать, это совпадение:

Function1
  :  Spaces 'function' Spaces '(' Spaces '1' Spaces ')' 
  ;

fragment Spaces
  :  (' ' | '\t')*
  ;

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

'function()'

(без параметра 1 в качестве параметра)

или

'    x...'

(отступы не , за которыми следуют f из функция )

Итак, вам нужно "разветвляться" в своем правиле Function1 и убедиться, что вы заменяете только правильное вхождение.

Вы также должны позаботиться о вхождениях function(1) внутри строковых литералов и комментариев, предполагая, что вы не хотите, чтобы они были предварительно добавлены с extraFunction();\n.

Небольшая демонстрация:

grammar T;

parse
  :  (t=. {System.out.print($t.text);})* EOF
  ;

Function1
  :  indent=Spaces 
     ( 'function' Spaces '(' Spaces ( '1' Spaces ')' {setText($indent.text + "extraFunction();\n" + $text);}
                                    | ~'1' // do nothing if something other than `1` occurs
                                    )
     | '"' ~('"' | '\r' | '\n')* '"'       // do nothing in case of a string literal
     | '/*' .* '*/'                        // do nothing in case of a multi-line comment
     | '//' ~('\r' | '\n')*                // do nothing in case of a single-line comment
     | ~'f'                                // do nothing in case of a char other than 'f' is seen
     )
  ;

OtherChar
  :  . // a "fall-through" rule: it will match anything if none of the above matched
  ;

fragment Spaces
  :  (' ' | '\t')* // fragment rules are only used inside other lexer rules
  ;

Вы можете проверить это с помощью следующего класса:

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String source = 
        "/*                      \n" +
        "  function(1)           \n" +
        "*/                      \n" +
        "void myFunction() {     \n" +
        "   s = \"function(1)\"; \n" + 
        "   function();          \n" + 
        "   function(1);         \n" + 
        "}                       \n";
    System.out.println(source);
    System.out.println("---------------------------------");
    TLexer lexer = new TLexer(new ANTLRStringStream(source));
    TParser parser = new TParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}

И если вы запустите этот основной класс, вы увидите следующее, выводимое на консоль:

bart@hades:~/Programming/ANTLR/Demos/T$ java -cp antlr-3.3.jar org.antlr.Tool T.g
bart@hades:~/Programming/ANTLR/Demos/T$ javac -cp antlr-3.3.jar *.java
bart@hades:~/Programming/ANTLR/Demos/T$ java -cp .:antlr-3.3.jar Main

/*                      
  function(1)           
*/                      
void myFunction() {     
   s = "function(1)"; 
   function();          
   function(1);         
}                       

---------------------------------
/*                      
  function(1)           
*/                      
void myFunction() {     
   s = "function(1)"; 
   function();          
   extraFunction();
   function(1);         
}                       

Я уверен, что это не защищает от ошибок (например, я не учел букв-символов), но это может стать началом, IMO.

1 голос
/ 16 сентября 2011

Полагаю, одним из решений является сохранение токенов WS в том же канале путем удаления $channel = HIDDEN;.Это позволит вам получить доступ к информации о токене WS в вашем парсере.

...