ANTLR Бесконечная рекурсия без предупреждения пытается реализовать грамматику синтаксического анализа макросов

Antlr не будет показывать никаких предупреждений при компиляции этой грамматики, но при вызове с входным файлом он будет постоянно повторяться в org.antlr.v4.runtime.atn.LexerATNSimulator.closure. Есть идеи, как создать грамматики парсинга макросов, которые должны работать с лексерами с любыми символами, кроме специальных?

Лексер:

lexer grammar e8pp_lexer;

NEWLINE: '\r' '\n' | '\n' | '\r';
CONTENT: NEWLINE CONTENTF+;
MACRODEF : NEWLINE 'MACRO' -> pushMode(MACRO_DEF);
MACROUSE : NEWLINE '`' -> mode (MACRO_USE); 
fragment CONTENTF: .* ;

mode MACRO_DEF;

MACRONAME: LETTERS+ ;
MACROPARAMSTART: '[' ;
MACROPARAM: LETTERS+ ;
MACROSEP: ',';
MACROPARAMEND : ']' ;
MACRODEF_DEF : ':' -> pushMode(MACRO_MODE);
WHITESPACE: WS+ -> skip;

fragment WS: [ \t\r\n\u000C];
fragment LETTERS : [a-zA-Z];

mode MACRO_MODE;

NEWLINE_CONTENT: '\r' '\n' | '\n' | '\r';
ENDMACRO : NEWLINE_CONTENT 'ENDMACRO' -> popMode;
MACRO_CONTENT : NEWLINE_CONTENT MACRO_CONTENTF+;
fragment MACRO_CONTENTF: .* ;

mode MACRO_USE;

MU_ID: LETTERS+ ;
MU_PARAMSTART: '(';
MU_PARAM: LETTERS+;
MU_PARAMSEP : ',';
MU_PARAM_END: ')' -> popMode;

fragment MU_WS: [ \t\r\n\u000C];
fragment MU_LETTERS : [a-zA-Z];

Парсер

grammar e8pp_parser;

options { tokenVocab=e8pp_lexer; }

content:
    contentElement+ EOF
    ;

contentElement:
    data | macro
    ;

data:
    contentData | macroUse
    ;

contentData:
    CONTENT
    ;

macroUse:
    MACROUSE macroId MU_PARAMSTART macroUseParams? MU_PARAM_END
    ;

macroId:
    MU_ID
    ;

macroUseParams:
    macroUseParam (MU_PARAMSEP macroUseParam)*
    ;

macroUseParam:
    MU_PARAM
    ;


macro:
    MACRODEF macroName 
        MACROPARAMSTART macroParams? MACROPARAMEND MACRODEF_DEF
    macroBody?
    NEWLINE_CONTENT ENDMACRO
    ;

macroName:
    MACRONAME
    ;

macroParams:
    macroParam (MACROSEP macroParam)*
    ;

macroParam:
    MACROPARAM
    ;

macroBody:
    MACRO_CONTENT+
    ; 

Идея состоит в том, что при этом специальные блоки будут анализироваться с помощью макросов, а все остальное будет возвращено в виде данных. Использование Java:

private ByteArrayInputStream parseMacros() throws Exception {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    e8pp_lexer lexer = new e8pp_lexer(new ANTLRFileStream(srcFile.getAbsolutePath(), "utf-8"));
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    e8pp_parserParser parser = new e8pp_parserParser(tokens);

    ContentContext cc = parser.content();

    Map<String, MacroDef> macros = new HashMap<String, MacroDef>();

    for (ContentElementContext ctx : cc.contentElement()) {
        parseMacroElement(ctx, bos, macros);
    }

    return new ByteArrayInputStream(bos.toByteArray());
}

private void parseMacroElement(ContentElementContext ctx, ByteArrayOutputStream bos, Map<String, MacroDef> macros) throws Exception {
    if (ctx.data() != null) {
        if (ctx.data().contentData() != null) {
            bos.write(ctx.data().contentData().getText().getBytes(Charset.forName("UTF-8")));
        } else {
            resolveMacro(ctx.data().macroUse(), bos, macros);
        }
    } else {
        defineMacro(ctx.macro(), macros);
    }
}

private void defineMacro(MacroContext macro, Map<String, MacroDef> macros) {
    String name = macro.macroName().getText();
    if (macros.containsKey(name)) {
        throw new AssemblyException("Duplicate macro name " + name);
    }

    MacroDef md = new MacroDef();
    md.content = macro.macroBody().getText();
    md.argNames = parseMacroArgs(macro.macroParams());

}

private List<String> parseMacroArgs(MacroParamsContext ctx) {
    List<String> params = new ArrayList<String>();

    for (MacroParamContext p : ctx.macroParam()) {
        params.add(p.getText());
    }

    return params;
}

private void resolveMacro(MacroUseContext ctx, ByteArrayOutputStream bos, Map<String, MacroDef> macros) throws Exception {
    String macroName = ctx.macroId().getText();
    if (!macros.containsKey(macroName)) {
        throw new AssemblyException("Unknown macro " + macroName);
    }

    MacroDef md = macros.get(macroName);
    List<String> values = parseMacroParams(ctx.macroUseParams());

    if (values.size() != md.argNames.size()) {
        throw new AssemblyException("Bad macro param count for macro " + macroName);
    }

    String replaced = md.content;
    for (int i=0; i<md.argNames.size(); i++) {
        String paramName = md.argNames.get(i);
        String replaceValue = values.get(i);
        replaced = replaced.replaceAll(Pattern.quote(paramName), replaceValue);
    }

    bos.write(replaced.getBytes(Charset.forName("UTF-8")));
}

private List<String> parseMacroParams(MacroUseParamsContext ctx) {
    List<String> params = new ArrayList<String>();

    for (MacroUseParamContext p : ctx.macroUseParam()) {
        params.add(p.getText());
    }

    return params;
}

Я не могу помочь тебе с твоей проблемой. Но если вы хотите создать собственный язык + грамматика, вам следует обязательно взглянуть на xtext: eclipse.org/Xtext Он основан на antlr и, на мой взгляд, его проще использовать как простой antlr.

Andreas Hauschild 19.09.2018 13:35

Во-первых, похоже, что вы используете старую версию Antlr4. Лучше всего обновить до последней только потому, что. Во-вторых, похоже, есть проблемы с вашим лексером: фрагмент правила fragment CONTENTF: .* ; будет потреблять контент для EOF! Выполните отладку лексера независимо, сбросив поток токенов и убедившись, что грамматика лексера правильно идентифицирует токены.

GRosenberg 19.09.2018 20:33
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
2
33
0

Другие вопросы по теме