ANTLR4,用词法模式匹配较短的字符序列

ANTLR4, Matching shorter character sequence with Lexical Modes

工程软件的结果文件由许多页面组成,每页有 header 行和一些数据行。 每 header 行包括:

  1. 行首的字符“1”
  2. 部分字母数字字符(一般数据不解析)
  3. 第 122 行的字符串 'PAGE'
  4. 数字字符串(页码)
  5. 荷兰

这样的 header 行的一个例子是:

软件分配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 时激活,但这并没有发生,词法分析器仍然会生成 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,而不是在找到 PAGE 后更改为 PAGENUM 模式,如以下示例及其 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

您可以使用多种词法分析器模式:

  1. 当你在行首遇到1时,你推HEADER_MODE
  2. 当在 HEADER_MODE 中遇到 PAGE 时,你按下 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>)

(我在上面的输出中添加了一些换行符)