ANTLR4: перезапись токенов - сжатие пространства вокруг удаления - PullRequest
2 голосов
/ 05 ноября 2019

У меня есть следующий пример грамматики:

start: (comments | removes)*
comments: COMMENT;
removes: REMOVE_ME;


COMMENT: ';'~('\n'|'\r')*;
REMOVE_ME: 'remove_me';
fragment NEW_LINE: (('\n')
                  |('\r')
                  |('\r\n'));
NEW_LINES: NEW_LINE+ -> channel(HIDDEN);
OTHER: . -> channel(HIDDEN);

У меня есть следующий пример текста

; comments here
; please come closer comment!


remove_me


remove_me

; comment

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

    rewritter.delete(ctx.REMOVE_ME);

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

; comments here
; please come closer comment!
; comment

Как я могу попросить переписчика:удалить предыдущие новые строки, пока не будет найдена другая конструкция или начало файла?

1 Ответ

0 голосов
/ 15 ноября 2019

ОБНОВЛЕНИЕ Моей первой попыткой решить эту проблему было изменение грамматики. Дело в том, что грамматика имеет тенденцию игнорировать символы возврата каретки и пробелы.

Я использую вашу грамматику:

grammar Toek;

start: (comments|removes)*;
comments: COMMENT;
removes: REMOVE_ME;

COMMENT: ';'~('\n'|'\r')*;
REMOVE_ME: 'remove_me';

fragment NEW_LINE: (('\n')
                  |('\r')
                  |('\r\n'));
NEW_LINES: NEW_LINE+ -> channel(HIDDEN);
OTHER: . -> channel(HIDDEN);

Затем я написал простой тест JUNIT для анализа строки (тот, который вы написали) и я применяю свое решение. Решение основано на функциональном программировании (это просто для упрощения кода, в этом нет необходимости). Когда ANTLR заканчивает замену указанного вами правила, я беру полученную строку, разделяю ее на строки и удаляю все строки пустыми.

Тест (JUNIT) и некоторые необходимые классы:

@Test
public void testOK() throws Throwable {
        final String text = "; comments here\n" +
                "; please come closer comment!" + 
                "\n" + 
                "\n" + 
                "remove_me" + 
                "\n"+
                "\n" + 
                "remove_me" + 
                "\n" + 
                "; comment";
        ParseTreeWalker walker = new ParseTreeWalker();
        List<Triple<Token, Token, String>> replace = new ArrayList<>();     
        ToekBaseListener listener = new ToekBaseListener() {

            @Override
            public void enterRemoves(RemovesContext ctx) {
                System.out.println("-: [" + ctx.getText() + "]");
                replace.add(new Pair<Token, Token,>(ctx.start, ctx.stop));
            }
        };

        ToekLexer lexer = new ToekLexer(CharStreams.fromString(text));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ToekParser parser = new ToekParser(tokens);

        parser.removeErrorListeners();
        parser.addErrorListener(new JQLBaseErrorListener() {
            @Override
            public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
                    int charPositionInLine, String msg, RecognitionException e) {
                System.out.println(String.format("unespected char at pos %s of text '%s'", charPositionInLine, text));
            }
        });
        ParserRuleContext context = parser.start();

        walker.walk(listener, context);
        TokenStreamRewriter rewriter = new TokenStreamRewriter(tokens);

        for (Triple<Token, Token, String> item : replace) {
            rewriter.replace(item.value0, item.value1, "");
        }

        String solution=split(rewriter.getText());
        System.out.println(solution);
    }

...
public class Pair {
    public Pair(double k ,double v) {
       key=k;
       value=v;
    }

    private String key;
    private String value;  

    public String getKey() { return key; }
    public String getValue() { return value; }
}

И метод, ответственный за решение. Некоторое объяснение: взять строку, преобразовать в поток (разделение на '\ n'), отфильтровать только элемент, имеющий размер> 0, перекомпактировать все вместе.

public static String removeBlankLines(String str) {
  return Stream.of(str.split("\n"))
         .filter(elem -> elem!=null && elem.trim().length()>0)              
         .collect(Collectors.joining("\n"));
}

Вывод такой, как вы хотите:

; comments here
; please come closer comment!
; comment
...