ANTLR4 Fortran语法中NAME与LETTERSPEC词法规则冲突求解
解决Fortran解析器中NAME与LETTERSPEC的词法冲突问题
冲突根源
ANTLR4的词法分析遵循最长匹配优先+先定义规则优先的原则。你的问题核心在于:
NAME规则LETTER (ALPHANUMERICCHARACTER)*完全包含单个字母的匹配场景(*允许0个后续字符);NAME定义在LETTERSPEC之前,且单个字母的匹配长度对两者完全一致,因此ANTLR会优先将单个字母标记为NAME,而非LETTERSPEC。
另外,原词法规则将LETTER、DIGIT等作为独立token规则,会导致单个字母同时匹配多个规则,进一步加剧冲突——建议先将这些辅助规则改为fragment(仅用于组成其他规则,不生成独立token)。
具体解决方案
方案1:使用Lexer Modes区分上下文
Fortran中LETTERSPEC仅出现在IMPLICIT语句的括号内,因此可以通过**词法模式(Lexer Modes)**在进入括号时切换规则集,确保括号内的单个字母被识别为LETTERSPEC。
修改后的词法规则:
lexer grammar FortrantTestLex; WS: [ \t\r\n]+ -> skip; // 默认模式:处理关键字、普通NAME和符号 PROGRAM: 'PROGRAM'; IMPLICIT: 'IMPLICIT'; NONE: 'NONE'; END: 'END'; LPAREN: '(' -> pushMode(IMPLICIT_PARENTHESES); // 进入括号时切换模式 COMMA: ','; MINUS: '-'; NAME: LETTER (ALPHANUMERICCHARACTER)*; // 辅助片段:不生成独立token fragment LETTER: 'A'..'Z' | 'a'..'z'; fragment DIGIT: '0'..'9'; fragment UNDERSCORE: '_'; fragment ALPHANUMERICCHARACTER: LETTER | DIGIT | UNDERSCORE; // IMPLICIT语句括号内的模式 mode IMPLICIT_PARENTHESES; LETTERSPEC: LETTER (MINUS LETTER)?; // 优先识别LETTERSPEC MODE_COMMA: ','; MODE_MINUS: '-'; MODE_RPAREN: ')' -> popMode; // 退出括号时回到默认模式 MODE_WS: [ \t\r\n]+ -> skip;
对应的解析器规则调整:
grammar FortranTest; import FortrantTestLex; programName: NAME; programStmt: PROGRAM programName; // 使用模式内的token定义letterSpecList letterSpecList: LETTERSPEC (MODE_COMMA LETTERSPEC)*; // 补充declarationTypeSpec的基础规则(示例) declarationTypeSpec: INTEGER | REAL | CHARACTER; INTEGER: 'INTEGER'; REAL: 'REAL'; CHARACTER: 'CHARACTER'; implicitSpec: declarationTypeSpec LPAREN letterSpecList MODE_RPAREN; implicitSpecList: implicitSpec (COMMA implicitSpec)*; implicitStmt: IMPLICIT implicitSpecList | IMPLICIT NONE ( LPAREN implicitNameSpecList? RPAREN )?; // 补充缺失的implicitNameSpecList规则 implicitNameSpecList: NAME (COMMA NAME)*; endProgramStmt: END (PROGRAM programName?)?; mainProgram: programStmt? endProgramStmt; programUnit: mainProgram; program: programUnit (programUnit)*;
方案2:将LETTERSPEC改为解析器规则
放弃在词法层面区分LETTERSPEC,转而在解析器中定义规则,直接处理单个字母或字母范围的语法结构。
修改后的词法规则:
lexer grammar FortrantTestLex; WS: [ \t\r\n]+ -> skip; PROGRAM: 'PROGRAM'; IMPLICIT: 'IMPLICIT'; NONE: 'NONE'; END: 'END'; LPAREN: '('; RPAREN: ')'; COMMA: ','; MINUS: '-'; INTEGER: 'INTEGER'; REAL: 'REAL'; CHARACTER: 'CHARACTER'; // 拆分单个字母和多字符NAME LETTER_TOKEN: LETTER; NAME: LETTER ALPHANUMERICCHARACTER+; // 辅助片段 fragment LETTER: 'A'..'Z' | 'a'..'z'; fragment DIGIT: '0'..'9'; fragment UNDERSCORE: '_'; fragment ALPHANUMERICCHARACTER: LETTER | DIGIT | UNDERSCORE;
对应的解析器规则:
grammar FortranTest; import FortrantTestLex; // Fortran的name可以是单个字母或多字符,因此programName兼容两种token programName: NAME | LETTER_TOKEN; programStmt: PROGRAM programName; // 解析器层面定义letterSpec规则 letterSpec: LETTER_TOKEN (MINUS LETTER_TOKEN)?; letterSpecList: letterSpec (COMMA letterSpec)*; declarationTypeSpec: INTEGER | REAL | CHARACTER; implicitSpec: declarationTypeSpec LPAREN letterSpecList RPAREN; implicitSpecList: implicitSpec (COMMA implicitSpec)*; implicitStmt: IMPLICIT implicitSpecList | IMPLICIT NONE ( LPAREN implicitNameSpecList? RPAREN )?; implicitNameSpecList: (NAME | LETTER_TOKEN) (COMMA (NAME | LETTER_TOKEN))*; endProgramStmt: END (PROGRAM programName?)?; mainProgram: programStmt? endProgramStmt; programUnit: mainProgram; program: programUnit (programUnit)*;
方案对比
- 方案1(Lexer Modes):词法层面严格区分上下文,解析器规则更简洁,但需要维护模式切换逻辑,适合语法上下文边界清晰的场景。
- 方案2(解析器规则):无需修改词法模式,通过解析器规则适配上下文,但需要拆分NAME的词法定义,适合灵活度要求较高的场景。
内容的提问来源于stack exchange,提问作者Akhil Akkapelli
相关产品推荐
相关产品推荐

