ANTLR4 находит токены, но возвращает усеченное дерево разбора - PullRequest
1 голос
/ 14 апреля 2020

Я поддерживаю проект с открытым исходным кодом, и мой анализ на основе ANTLR возвращает усеченное ParseTree. Я считаю, что предоставил все необходимое для воспроизведения проблемы.

Учитывая синтаксический анализатор, созданный с использованием ANTLR 4.8-1 и настроенный следующим образом:

    public static Expressions parse(String mappingExpression) throws ParseException, IOException {

        // Expressions can include references to properties within an
        // application interface ("state"),
        // properties within an event, and various operators and functions.
       InputStream targetStream = new ByteArrayInputStream(mappingExpression.getBytes());
        CharStream input = CharStreams.fromStream(targetStream,Charset.forName("UTF-8"));

        MappingExpressionLexer lexer = new MappingExpressionLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        MappingExpressionParser parser = new MappingExpressionParser(tokens);
        ParseTree tree = null;

        BufferingErrorListener errorListener = new BufferingErrorListener();

        try {
            // remove the default error listeners which print to stderr
            parser.removeErrorListeners();
            lexer.removeErrorListeners();

            // replace with error listener that buffer errors and allow us to retrieve them
            // later
            parser.addErrorListener(errorListener);
            lexer.addErrorListener(errorListener);

            tree = parser.expr();

И я предоставляю следующую инструкцию для анализа:

results.( $y := "test"; $bta := function($x) {(     $count($x.billToAccounts) > 1       ? ($contains($join($x.billToAccounts, ','), "super") ? "Super" : "Standard")        : ($contains($x.billToAccounts[0], "super") ? "Super" : "Standard") )}; { "users": $filter($, function($v, $i, $a) { $v.status = "PROVISIONED" }) { "firstName": $.profile.firstName, "lastName": $.profile.lastName, "email": $.profile.login, "lastLogin": $.lastLogin, "id" : $.id, "userType": $bta($.profile) } } )

анализ Возвращаемое дерево содержит только токен «результата», хотя все токены проанализированы (как показано в массиве _input.tokens), и все они показывают канал 0.

Где я ожидаю, что парсер продолжит сборку _localCtx, оператор MappingExpressionParser:

_alt = getInterpreter().adaptivePredict(_input,17,_ctx);

возвращает 2, поэтому дальнейшая компоновка _localCtx не происходит, и он содержит только TerminalNodeContext с "result".

Я пытался переставить различные правила и подозреваю, что это связано с положением правила parens по отношению к правилу expr, но я что-то упустил.

Что заставляет adaptivePredict так быстро возвращать 2?

/**
 * (c) Copyright 2018, 2019 IBM Corporation
 * 1 New Orchard Road,
 * Armonk, New York, 10504-1722
 * United States
 * +1 914 499 1900
 * support: Nathaniel Mills wnm3@us.ibm.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/* Antlr grammar defining the mapping expression language */

grammar MappingExpression;

/* The start rule; begin parsing here.
   operator precedence is implied by the ordering in this list */


// =======================
// = PARSER RULES
// =======================

expr:
   ID                                                     # id
 | '*' ('.' expr)?                                        # field_values
 | DESCEND ('.' expr)?                                    # descendant
 | DOLLAR (('.' expr) | (ARR_OPEN expr ARR_CLOSE))?       # context_ref
 | ROOT ('.' expr)?                                       # root_path
 | '(' (expr (';' (expr)?)*)? ')'                         # parens
 | ARR_OPEN exprOrSeqList? ARR_CLOSE                      # array_constructor
 | OBJ_OPEN fieldList? OBJ_CLOSE                          # object_constructor
 | expr ARR_OPEN ARR_CLOSE                                # to_array
 | expr '.' expr                                          # path
 | expr ARR_OPEN expr ARR_CLOSE                           # array
 | VAR_ID (emptyValues | exprValues)                      # function_call
 | FUNCTIONID varList '{' exprList? '}'                   # function_decl
 | VAR_ID ASSIGN (expr | (FUNCTIONID varList '{' exprList? '}'))                   # var_assign
 | (FUNCTIONID varList '{' exprList? '}') exprValues                               # function_exec
 | op=(TRUE|FALSE)                                        # boolean
 | op='-' expr                                            # unary_op
 | expr op=('*'|'/'|'%') expr                             # muldiv_op
 | expr op=('+'|'-') expr                                 # addsub_op
 | expr '&' expr                                          # concat_op
 | expr 'in' expr                                         # membership
 | expr 'and' expr                                        # logand
 | expr 'or' expr                                         # logor
 | expr op=('<'|'<='|'>'|'>='|'!='|'=') expr              # comp_op
 | expr '?' expr (':' expr)?                              # conditional
 | expr CHAIN expr                                        # fct_chain
 | VAR_ID                                                 # var_recall
 | NUMBER                                                 # number
 | STRING                                                 # string
 | 'null'                                                 # null
 ;

fieldList : STRING ':' expr (',' STRING ':' expr)*;
exprList : expr (',' expr)* ;
varList : '('  (VAR_ID (',' VAR_ID)*)* ')' ;
exprValues : '(' exprList ')' ((',' exprOrSeq)* ')')?;
emptyValues : '(' ')' ;
seq : expr '..' expr ;

exprOrSeq : seq | expr ;
exprOrSeqList : exprOrSeq (',' exprOrSeq)* ;

// =======================
// = LEXER RULES
// =======================

TRUE : 'true';
FALSE : 'false';


STRING
    : '\'' (ESC | ~['\\])* '\''
    | '"'  (ESC | ~["\\])* '"'
    ;

NULL : 'null';

ARR_OPEN  : '[';
ARR_CLOSE : ']';

OBJ_OPEN  : '{';
OBJ_CLOSE : '}';

DOLLAR : '$';
ROOT : '$$' ;
DESCEND : '**';

NUMBER
    :   INT '.' [0-9]+ EXP? // 1.35, 1.35E-9, 0.3
    |   INT EXP             // 1e10 3e4
    |   INT                 // 3, 45
    ;

FUNCTIONID : 'function' ;

WS: [ \t\n]+ -> skip ;                // ignore whitespace
COMMENT:  '/*' .*? '*/' -> skip;      // allow comments

// Assign token names used in above grammar
CHAIN : '~>' ;
ASSIGN : ':=' ;
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
REM : '%' ;
EQ  : '=' ;
NOT_EQ  : '!=' ;
LT  : '<' ;
LE  : '<=' ;
GT  : '>' ;
GE  : '>=' ;
CONCAT : '&';

VAR_ID : '$' ID ;

ID
    : [a-zA-Z] [a-zA-Z0-9_]*
    | BACK_QUOTE ~[`]* BACK_QUOTE;


// =======================
// = LEXER FRAGMENTS
// =======================


fragment ESC :   '\\' (["'\\/bfnrt] | UNICODE) ;
fragment UNICODE : ([\u0080-\uFFFF] | 'u' HEX HEX HEX HEX) ;
fragment HEX : [0-9a-fA-F] ;

fragment INT :   '0' | [1-9] [0-9]* ; // no leading zeros
fragment EXP :   [Ee] [+\-]? INT ;    // \- since - means "range" inside [...]

fragment SINGLE_QUOTE : '\'';
fragment DOUBLE_QUOTE : '"';
fragment BACK_QUOTE : '`';

1 Ответ

1 голос
/ 14 апреля 2020

Хотя токены создаются для всего входного примера, не все обрабатываются парсером. Если вы запустите это:

String mappingExpression = "results.(\n" +
        "    $y := \"test\"; \n" +
        "    $bta := function($x) {\n" +
        "        (\n" +
        "            $count($x.billToAccounts) > 1 \n" +
        "              ? ($contains($join($x.billToAccounts, ','), \"super\") ? \"Super\" : \"Standard\")\n" +
        "              : ($contains($x.billToAccounts[0], \"super\") ? \"Super\" : \"Standard\") \n" +
        "        )\n" +
        "    };\n" +
        "    { \n" +
        "        \"users\": $filter($, function($v, $i, $a) { \n" +
        "            $v.status = \"PROVISIONED\" \n" +
        "        })\n" +
        "        { \n" +
        "            \"firstName\": $.profile.firstName, \n" +
        "            \"lastName\": $.profile.lastName, \n" +
        "            \"email\": $.profile.login, \n" +
        "            \"lastLogin\": $.lastLogin, \n" +
        "            \"id\" : $.id, \n" +
        "            \"userType\": $bta($.profile) \n" +
        "        }\n" +
        "    } \n" +
        ")";

InputStream targetStream = new ByteArrayInputStream(mappingExpression.getBytes());
MappingExpressionLexer lexer = new MappingExpressionLexer(CharStreams.fromStream(targetStream, StandardCharsets.UTF_8));
MappingExpressionParser parser = new MappingExpressionParser(new CommonTokenStream(lexer));
ParseTree tree = parser.expr();

System.out.println(tree.toStringTree(parser));

будет напечатано следующее:

(expr results)

Это означает, что expr успешно анализирует первый вариант, ID, а затем останавливается.

Чтобы заставить анализатор использовать все токены, введите следующее правило:

expr_to_eof
 : expr EOF
 ;

и измените:

ParseTree tree = parser.expr();

на:

ParseTree tree = parser.expr_to_eof();

Когда вы снова запустите фрагмент кода, который я опубликовал (с прослушивателями ошибок по умолчанию!), Вы увидите некоторые сообщения об ошибках на своей консоли (т. Е. Синтаксический анализатор не успешно обработал ввод).

Если я попробуйте проанализировать ввод:

results.(
    $y := "test"; 
    $bta := function($x) {
        (
            $count($x.billToAccounts) > 1 
              ? ($contains($join($x.billToAccounts, ','), "super") ? "Super" : "Standard")
              : ($contains($x.billToAccounts[0], "super") ? "Super" : "Standard")
        )
    };
    { 
        "users": $filter($, function($v, $i, $a) { 
            $v.status = "PROVISIONED" 
        })
    } 
)

, тогда у парсера с этим проблем нет. Проверка дерева:

{ 
    "users": $filter($, function($v, $i, $a) { 
        $v.status = "PROVISIONED" 
    })
} 

Я вижу, что оно распознается как OBJ_OPEN fieldList? OBJ_CLOSE, где fieldList определяется следующим образом:

fieldList : STRING ':' expr (',' STRING ':' expr)*;

т.е. список значений ключей, разделенных запятые. Поэтому, если вы передадите парсеру следующее:

{
    "users": $filter($, function($v, $i, $a) {
        $v.status = "PROVISIONED"
    })
    {
        "firstName": $.profile.firstName,
        "lastName": $.profile.lastName,
        "email": $.profile.login,
        "lastLogin": $.lastLogin,
        "id" : $.id,
        "userType": $bta($.profile)
    }
}

он не сможет его правильно проанализировать, поскольку:

{
    "firstName": $.profile.firstName,
    "lastName": $.profile.lastName,
    "email": $.profile.login,
    "lastLogin": $.lastLogin,
    "id" : $.id,
    "userType": $bta($.profile)
}

не является ключом-значением, и между ними нет запятой.

Это правильно проанализирует это:

{
    "users": $filter($, function($v, $i, $a) {
        $v.status = "PROVISIONED"
    }),
    "some-key": {
        "firstName": $.profile.firstName,
        "lastName": $.profile.lastName,
        "email": $.profile.login,
        "lastLogin": $.lastLogin,
        "id" : $.id,
        "userType": $bta($.profile)
    }
}

Или $filter($, function($v, $i, $a) { $v.status = "PROVISIONED" }) может иметь { "firstName": ... } непосредственно после него, но я не могу видеть, что это верно из вашей грамматики.

...