ANTLR4, сопоставление короткой последовательности символов с лексическими режимами - PullRequest
1 голос
/ 08 апреля 2020

Файл результатов из программного обеспечения Engineering состоит из множества страниц со строкой заголовка и несколькими строками данных на странице. Каждая строка заголовка состоит из:

  1. Символ '1' в первой позиции строки
  2. Некоторые алфавитные символы c символов (общие данные не анализируются)
  3. Строка 'PAGE' в позиции 122 строки
  4. Numeri c строка (номер страницы)
  5. NL

Пример такой строки заголовка: :

HEADER LINE

Программное обеспечение назначает следующие шесть символов после PAGE для нумерации страниц.

Парсер работает нормально, за исключением документов с более чем 99999 страниц, для которых программа выводит строки типа PAGE123456 без пробелов между PAGE и номером страницы (да, некоторые программы генерируют такой огромный объем данных).

Первая грамматика, которую я попробовал:

grammar F06Reader01;
readF06: dataBlock+ EOF;
dataBlock: pageLine row+;
pageLine: ONE_AT_FIRST_POS ALPNUM* PAGEATPOS ALPNUM NL;
row: ALPNUM* NL ;
PAGEATPOS: P_ATPOS A_ATPOS G_ATPOS E_ATPOS;
P_ATPOS             :   'P'          {getCharPositionInLine() == 119}?;
A_ATPOS             :   'A'          {getCharPositionInLine() == 120}?;
G_ATPOS             :   'G'          {getCharPositionInLine() == 121}?;
E_ATPOS             :   'E'          {getCharPositionInLine() == 122}?;
ONE_AT_FIRST_POS    :   '1'          {getCharPositionInLine() == 1}?;
ALPNUM : (LETTER | DIGIT)+;
DIGIT: [0-9] ;
LETTER: ~[ \t\n\r\u0030-\u0039]; //everything but DIGITS, NL or WL
NL: '\r'? '\n';
WS : [ \t]+ ->skip;

Сгенерированные токены определяют PAGE231236 как ALPNUM, так как он находит его больше, чем PAGE.

После обнаружения этой проблемы я изменил файл g4, добавив лексический режим (PAGENUM), чтобы он активировался, когда лексер находит PAGE. но этого не произошло s и все же лексер создает токены ALPNUM.

Ниже приведен файл лексера:

lexer grammar ModeTest01Lexer;
PAGEATPOS: P_ATPOS A_ATPOS G_ATPOS E_ATPOS -> mode(PAGENUM);
P_ATPOS             :   'P'          {getCharPositionInLine() == 119}?;
A_ATPOS             :   'A'          {getCharPositionInLine() == 120}?;
G_ATPOS             :   'G'          {getCharPositionInLine() == 121}?;
E_ATPOS             :   'E'          {getCharPositionInLine() == 122}?;
ONE_AT_FIRST_POS    :   '1'          {getCharPositionInLine() == 1}?;
ALPNUM : (LETTER | DIGIT)+;
DIGIT: [0-9] ;
LETTER: ~[ \t\n\r\u0030-\u0039]; //everything but DIGITS, NL or WL
NL: '\r'? '\n';
WS : [ \t]+ ->skip;

mode PAGENUM;
NUM : [0-9]+;
WS2 : [ \t]+ ->skip;
NL2: '\r'? '\n' -> mode(DEFAULT_MODE);

и анализатор:

parser grammar ModeTest01;
options { tokenVocab=ModeTest01Lexer; }
modeTest: dataBlock+ EOF;
dataBlock: pageLine row+;
pageLine: ONE_AT_FIRST_POS ALPNUM* PAGEATPOS NUM NL2;
row: ALPNUM* NL ;

Этот код по-прежнему использует PAGE123456 в качестве ALPNUM вместо перехода в режим PAGENUM после того, как PAGE найден, как показано в следующем примере и его AST:

1    MSC.NASTRAN JOB                                                          MARCH  12, 2020  MSC Nastran 11/27/13   PAGE992306
     LC01 row
1    MSC.NASTRAN JOB                                                          MARCH  12, 2020  MSC Nastran 11/27/13   PAGE  2306
      another row of data

example AST

1 Ответ

2 голосов
/ 08 апреля 2020

Вы можете использовать несколько режимов лексера:

  1. , когда вы сталкиваетесь с 1 в начале строки, вы можете sh HEADER_MODE
  2. в HEADER_MODE и вы встречаете PAGE, вы пу sh PAGE_NUMBER_MODE (каждый (один) другой символ, который вы пропускаете в этом режиме)

Примерно так:

lexer grammar NastranLexer;

ONE_AT_FIRST_POS
 : {getCharPositionInLine() == 0}? '1' -> pushMode(HEADER_MODE)
 ;

NL
 : '\r'? '\n'
 ;

OTHER
 : .
 ;

mode HEADER_MODE;

  HEADER_MODE_PAGE
   : 'PAGE' -> pushMode(PAGE_NUMBER_MODE)
   ;

  HEADER_MODE_ANY
   : . -> skip
   ;

mode PAGE_NUMBER_MODE;

  PAGE_NUMBER_MODE_NUMBER
   : [0-9]+ -> mode(DEFAULT_MODE)
   ;

  PAGE_NUMBER_MODE_SPACE
   : [ \t] -> skip
   ;

грамматика синтаксического анализатора может выглядеть так:

parser grammar NastranParser;

options {
  tokenVocab=NastranLexer;
}

read
 : page* EOF
 ;

page
 : header NL row+
 ;

header
 : ONE_AT_FIRST_POS HEADER_MODE_PAGE PAGE_NUMBER_MODE_NUMBER
 ;

row
 : OTHER* NL
 ;

И когда вы запустите это:

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;

public class Main {

    public static void main(String[] args) {

        String source = "1    MSC.NASTRAN JOB                                                          MARCH  12, 2020  MSC Nastran 11/27/13   PAGE  2306\n" +
                "some data\n" +
                "1    MSC.NASTRAN JOB                                                          MARCH  12, 2020  MSC Nastran 11/27/13   PAGE  2307\n" +
                "some more data\n";

        NastranLexer lexer = new NastranLexer(CharStreams.fromString(source));
        NastranParser parser = new NastranParser(new CommonTokenStream(lexer));

        ParseTree parseTree = parser.read();
        System.out.println(parseTree.toStringTree(parser));
    }
}

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

(read
  (page
    (header 1 PAGE 2306) \n
    (row s o m e   d a t a \n))
  (page
    (header 1 PAGE 2307) \n
    (row s o m e   m o r e   d a t a \n)) <EOF>)

(я добавил некоторые разрывы строк в выводе выше)

...