ANTLR4,用词法模式匹配较短的字符序列
ANTLR4, Matching shorter character sequence with Lexical Modes
工程软件的结果文件由许多页面组成,每页有 header 行和一些数据行。
每 header 行包括:
- 行首的字符“1”
- 部分字母数字字符(一般数据不解析)
- 第 122 行的字符串 'PAGE'
- 数字字符串(页码)
- 荷兰
这样的 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
时,你推HEADER_MODE
- 当在
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>)
(我在上面的输出中添加了一些换行符)
工程软件的结果文件由许多页面组成,每页有 header 行和一些数据行。 每 header 行包括:
- 行首的字符“1”
- 部分字母数字字符(一般数据不解析)
- 第 122 行的字符串 'PAGE'
- 数字字符串(页码)
- 荷兰
这样的 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
时,你推HEADER_MODE
- 当在
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>)
(我在上面的输出中添加了一些换行符)