词法分析器和解析器的 ANTLR 令牌识别错误

ANTLR Token recognition error with lexer and parser

我正在编写一个 ANTLR Lexer 和 Parser 语法,它将解析与 Java class 非常相似的文本。最终它将解析如下文本:

reference schema:"https://schema.org/";
reference dc:"https://www.dublincore.org/";

type dc:Author {
}

我正在慢慢构建 Lexer 和 Parser。我已经成功地解析了 reference,但是在解析 type 时遇到了困难。

在添加对 type 的支持之前,我能够在解析器中对 space、冒号和分号使用字符串文字,但在遇到 cannot create implicit token for string literal 错误之后。我为这些字符中的每一个都定义了一个词法分析器规则,并用该规则替换了所有出现的文字。但是,这破坏了 references.

的解析

我已经包含了我的词法分析器和解析器,它们成功地解析了下面的 references(连同示例输入和解析的抽象语法树)以及无法工作的进化版本。我没有收到任何编译错误,但有很多 token recognition errors(下面包含屏幕截图)。

处理解析的正确方法是什么?

工作

词法分析器

lexer grammar WorkingLexerGrammar;

WS: ('\t' | '\n' | '\r' )+ -> skip ;

fragment Colon : ':';
fragment SemiColon: ';';
fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;

REFERENCE_KEYWORD: 'reference' ;
TYPE_KEYWORD: 'type' ;

PREFIXED_REFERENCE: ' ' -> pushMode(PrefixedReferenceMode) ;

mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: ':' -> pushMode(IriMode);
END_IRI: ';' -> popMode;

mode IriMode;
IRI: String  -> popMode;

解析器

parser grammar WorkingParserGrammar ;

options { tokenVocab=WorkingLexerGrammar; }

document: reference* EOF ;

prefixedReference: REFERENCE_PREFIX ':' IRI;
reference: REFERENCE_KEYWORD ' ' prefixedReference ';';

输入

reference schema:"https://schema.org/";
reference dc:"https://www.dublincore.org/";

输出

进化(不工作)

词法分析器

lexer grammar NotWorkingLexerGrammar;

WS: ('\t' | '\n' | '\r' )+ -> skip ;

fragment Colon : ':';
fragment SemiColon: ';';
fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;

COLON: Colon;
SEMICOLON: SemiColon;
SPACE: ' ';

REFERENCE_KEYWORD: 'reference' ;
TYPE_KEYWORD: 'type' ;

PREFIXED_REFERENCE: SPACE -> pushMode(PrefixedReferenceMode) ;

mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: COLON -> pushMode(IriMode);
END_IRI: SEMICOLON -> popMode;

mode IriMode;
IRI: String  -> popMode;

PREFIXED_NAME: SPACE -> pushMode(PrefixedNameMode) ;

mode PrefixedNameMode;
NAME_PREFIX: Prefix;
NAME_PREFIX_SEPARATOR: COLON -> pushMode(LocalNameMode);
END_NAME: SEMICOLON -> popMode;

mode LocalNameMode;
LOCAL_NAME: (Underscores | Digits | LowercaseLetters | UppercaseLetters)+  -> popMode;

解析器

parser grammar NotWorkingParserGrammar ;

options { tokenVocab=NotWorkingLexerGrammar; }

document: reference* type* EOF ;

prefixedReference: REFERENCE_PREFIX COLON IRI;
reference: REFERENCE_KEYWORD SPACE prefixedReference SEMICOLON;

prefixedName: NAME_PREFIX SPACE LOCAL_NAME;
type: TYPE_KEYWORD SPACE prefixedName;

输出


Bart Kiers' help 之后,我对词法分析器和解析器语法进行了两次更新,取得了不同程度的成功。

第一次更新

此更改正确解析了类型定义,但前提是我删除了词法分析器规则以供参考。我认为这是因为这两个规则是相同的(即 PREFIXED_REFERENCE: SPACE -> pushMode(PrefixedReferenceMode) ; 用于参考,PREFIXED_NAME: SPACE -> pushMode(PrefixedNameMode) ; 用于类型)——它们都匹配 space。我的第二次更新尝试修复此问题,但完整的词法分析器和解析器语法如下。

词法分析器

lexer grammar NotWorkingLexerGrammar;

WS: ('\t' | '\n' | '\r' )+ -> skip ;

fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;

fragment COLON: ':';
fragment SEMICOLON: ';';
fragment SPACE: ' ';

fragment REFERENCE_KEYWORD: 'reference' ;
fragment TYPE_KEYWORD: 'type' ;

PREFIXED_REFERENCE: SPACE -> pushMode(PrefixedReferenceMode) ;

mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: COLON -> pushMode(IriMode);
END_IRI: SEMICOLON -> popMode;

mode IriMode;
IRI: String  -> popMode;

PREFIXED_NAME: SPACE -> pushMode(PrefixedNameMode) ;

mode PrefixedNameMode;
NAME_PREFIX: Prefix;
NAME_PREFIX_SEPARATOR: COLON -> pushMode(LocalNameMode);
END_NAME: SEMICOLON -> popMode;

mode LocalNameMode;
LOCAL_NAME: (Underscores | Digits | LowercaseLetters | UppercaseLetters)+  -> popMode;

解析器

parser grammar NotWorkingParserGrammar ;

options { tokenVocab=NotWorkingLexerGrammar; }

document: reference* type* EOF ;

prefixedReference: REFERENCE_PREFIX REFERENCE_PREFIX_SEPARATOR IRI;
reference: REFERENCE_KEYWORD PREFIXED_REFERENCE prefixedReference END_IRI;

prefixedName: NAME_PREFIX NAME_PREFIX_SEPARATOR LOCAL_NAME;
type: TYPE_KEYWORD PREFIXED_NAME prefixedName END_NAME;

第二次更新

为了解决这个问题,我将 referencetype 关键字移动到相应部分的 Lexer 规则,但是如果我删除所有 Lexer 规则以供参考,这只会解析类型.但是引用被正确解析。

词法分析器

lexer grammar NotWorkingLexerGrammar;

WS: ('\t' | '\n' | '\r' )+ -> skip ;

fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;

fragment COLON: ':';
fragment SEMICOLON: ';';
fragment SPACE: ' ';

fragment REFERENCE_KEYWORD: 'reference' ;
fragment TYPE_KEYWORD: 'type' ;

PREFIXED_REFERENCE: REFERENCE_KEYWORD SPACE -> pushMode(PrefixedReferenceMode) ;

mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: COLON -> pushMode(IriMode);
END_IRI: SEMICOLON -> popMode;

mode IriMode;
IRI: String  -> popMode;

TYPE_DEFINITION: TYPE_KEYWORD SPACE -> pushMode(PrefixedNameMode) ;

mode PrefixedNameMode;
NAME_PREFIX: Prefix;
NAME_PREFIX_SEPARATOR: COLON -> pushMode(LocalNameMode);
END_NAME: SEMICOLON -> popMode;

mode LocalNameMode;
LOCAL_NAME: (Underscores | Digits | LowercaseLetters | UppercaseLetters)+  -> popMode;

解析器

parser grammar NotWorkingParserGrammar ;

options { tokenVocab=NotWorkingLexerGrammar; }

document: reference* type* EOF ;

prefixedReference: REFERENCE_PREFIX REFERENCE_PREFIX_SEPARATOR IRI;
reference: PREFIXED_REFERENCE prefixedReference END_IRI;

prefixedName: NAME_PREFIX NAME_PREFIX_SEPARATOR LOCAL_NAME;
type: TYPE_DEFINITION prefixedName END_NAME;

输出

对于以下输入:

reference schema:"https://schema.org/";
reference dc:"https://www.dublincore.org/";

type dc:Author;

这是输出:

line 4:0 token recognition error at: 't'
line 4:1 token recognition error at: 'y'
line 4:2 token recognition error at: 'p'
line 4:3 token recognition error at: 'e'
line 4:4 token recognition error at: ' '
line 4:5 token recognition error at: 'd'
line 4:6 token recognition error at: 'c'
line 4:7 token recognition error at: ':'
line 4:8 token recognition error at: 'A'
line 4:9 token recognition error at: 'u'
line 4:10 token recognition error at: 't'
line 4:11 token recognition error at: 'h'
line 4:12 token recognition error at: 'o'
line 4:13 token recognition error at: 'r;'

我使用模式的理由是限制规则的范围。这是我控制的语言,但不希望对其进行重大更改。这门语言比我在这里展示的要多得多,我们已经有了一个语法(目前是一个组合语法),但它非常脆弱。我试图进行更改以防止在前缀中使用大写字符,但在本地名称中允许使用大写字符,但这个滚雪球效应和其他规则开始适用。研究表明,模式是一种处理这种情况的方法,但我对 ANTLR 不是很熟悉,所以我可能误解了它。

遇到这样的errors/warnings时:

line 4:0 token recognition error at: 't'
line 4:1 token recognition error at: 'y'
line 4:2 token recognition error at: 'p'
line 4:3 token recognition error at: 'e'
...

这意味着词法分析器无法为输入构造一个标记(在这种情况下为type ...)。在您的情况下,这意味着词法分析器无法以当时所处的模式从输入创建令牌。

I tried to make a change to prevent uppercase characters in prefixes but permit them in the local name but this snowballed and other rules started applying

解决此类问题有两种选择:

  1. 像任何普通标识符一样解析前缀(大写或小写),解析后,遍历生成的解析树并使用 ANTLR 访问者或侦听器验证前缀标识符是否真的小写(参见:https://github.com/antlr/antlr4/blob/master/doc/listeners.md)
  2. 在你的词法分析器中区分小写和大写标识符,并在你的解析器规则中相应地使用它们,这样的事情可能会起作用:
document
 : reference* type* EOF
 ;

reference
 :  K_REFERENCE LOWER_ID COL STRING SCOL
 ;

type
 : K_TYPE LOWER_ID COL id OPAR CPAR
 ;

id
 : LOWER_ID
 | ID
 ;

K_REFERENCE : 'reference';
K_TYPE      : 'type';
LOWER_ID    : [a-z_] [a-z_0-9]*;
ID          : [a-zA-Z_] [a-zA-Z_0-9]*;
STRING      : '"' ~["]* '"';
SCOL        : ';';
COL         : ':';
OPAR        : '{';
CPAR        : '}';
SPACES      : [ \t\r\n] -> skip;

模式旨在用于真正将 2 种(或更多)语言嵌入彼此的输入。例如解析 HTML 个文件:有内容(文本)和带有属性的标签。据我所知,IMO,您没有按预期使用它。