You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

语法报错误显EOF为违规符号:如何判断EOF Token并获取前序Token?

问题描述

我实现了一个重写syntaxError方法的CustomErrorListener。当紧邻EOF的最后一个Token存在语法错误时,syntaxError方法会将EOF报告为违规符号。

我的顶层语法规则如下:

program: statementSeparator* version statements statementSeparator* EOF;

类似如下结尾的程序:

h q[0, 4

会在4之后(即EOF所在位置)报告错误,但实际违规符号应为缺少闭合]的q[0, 4。

现咨询:

  1. 是否因语法规则问题,导致EOF的前序Token未被报告为违规符号?
  2. 若语法规则无问题:
    a) 有无规范方法判断EOF Token?
    b) 是否可获取EOF的前序Token?

词法分析器(Lexer)

lexer grammar CqasmLexer;

// 空白字符和注释会被跳过,不传递给解析器
WHITE_SPACE: [ \t]+ -> skip;
SINGLE_LINE_COMMENT: '//' ~[\r\n]* -> skip;
MULTI_LINE_COMMENT: '/*' .*? '*/' -> skip;

// 符号
NEW_LINE: '\r'?'\n';
SEMICOLON: ';';
COLON: ':';
COMMA: ',';
DOT: '.';
EQUALS: '=';
OPEN_BRACKET: '[';
CLOSE_BRACKET: ']';
OPEN_BRACE: '{';
CLOSE_BRACE: '}';
OPEN_PARENS: '(';
CLOSE_PARENS: ')';
PLUS: '+';  // 该Token被UNARY_PLUS_OP和PLUS_OP共享
MINUS: '-';  // 该Token被UNARY_MINUS_OP和MINUS_OP共享

// 运算符
BITWISE_NOT_OP: '~';
LOGICAL_NOT_OP: '!';
POWER_OP: '**';
PRODUCT_OP: '*';
DIVISION_OP: '/';
MODULO_OP: '%';
SHL_OP: '<<';
SHR_OP: '>>';
CMP_GT_OP: '>';
CMP_LT_OP: '<';
CMP_GE_OP: '>=';
CMP_LE_OP: '<=';
CMP_EQ_OP: '==';
CMP_NE_OP: '!=';
BITWISE_AND_OP: '&';
BITWISE_XOR_OP: '^';
BITWISE_OR_OP: '|';
LOGICAL_AND_OP: '&&';
LOGICAL_XOR_OP: '^^';
LOGICAL_OR_OP: '||';
TERNARY_CONDITIONAL_OP: '?';

// 关键字
VERSION: 'version' -> pushMode(VERSION_STATEMENT);
MEASURE: 'measure';
QUBIT_TYPE: 'qubit';
BIT_TYPE: 'bit';
AXIS_TYPE: 'axis';
BOOL_TYPE: 'bool';
INT_TYPE: 'int';
FLOAT_TYPE: 'float';

// 数值字面量
BOOLEAN_LITERAL: 'true' | 'false';
INTEGER_LITERAL: Digit+;
FLOAT_LITERAL:
    Digit+ '.' Digit+ Exponent?
    | Digit+ '.' Exponent?  // 浮点字面量可以以点结尾
    | '.' Digit+ Exponent?;  // 或仅以点开头
fragment Digit: [0-9];
fragment Exponent: [eE][-+]?Digit+;

// 标识符
IDENTIFIER: Letter (Letter | Digit)*;
fragment Letter: [a-zA-Z_];

// 版本模式
// 遇到'version' Token时,进入版本模式
// 在版本模式中,'3.0'这类序列会被视为版本号,而非浮点字面量
mode VERSION_STATEMENT;
VERSION_WHITESPACE: [ \t]+ -> skip;
VERSION_NUMBER: Digit+ ('.' Digit+)? -> popMode;

语法分析器(Parser)

parser grammar CqasmParser;

options {
    tokenVocab = CqasmLexer;
}

program: statementSeparator* version statements statementSeparator* EOF;

version: VERSION VERSION_NUMBER;

statements: (statementSeparator+ statement)*;

statementSeparator: NEW_LINE | SEMICOLON;

statement:
    QUBIT_TYPE arraySizeDeclaration? IDENTIFIER  # qubitTypeDeclaration
    | BIT_TYPE arraySizeDeclaration? IDENTIFIER  # bitTypeDeclaration
    | AXIS_TYPE IDENTIFIER (EQUALS expression)?  # axisTypeDeclaration
    | BOOL_TYPE arraySizeDeclaration? IDENTIFIER (EQUALS expression)?  # boolTypeDeclaration
    | INT_TYPE arraySizeDeclaration? IDENTIFIER (EQUALS expression)?  # intTypeDeclaration
    | FLOAT_TYPE arraySizeDeclaration? IDENTIFIER (EQUALS expression)?  # floatTypeDeclaration
    | expression EQUALS MEASURE expression  # measureInstruction
    | IDENTIFIER expressionList  # instruction
    ;

arraySizeDeclaration: OPEN_BRACKET INTEGER_LITERAL CLOSE_BRACKET;

expressionList: expression (COMMA expression)*;

indexList: indexEntry (COMMA indexEntry)*;

indexEntry:
    expression  # indexItem
    | expression COLON expression  # indexRange
    ;

expression:
    OPEN_PARENS expression CLOSE_PARENS  # parensExpression
    | <assoc=right> (PLUS | MINUS) expression  # unaryPlusMinusExpression
    | <assoc=right> BITWISE_NOT_OP expression  # bitwiseNotExpression
    | <assoc=right> LOGICAL_NOT_OP expression  # logicalNotExpression
    | <assoc=right> expression POWER_OP expression  # powerExpression
    | expression (PRODUCT_OP | DIVISION_OP | MODULO_OP) expression  # productExpression
    | expression (PLUS | MINUS) expression  # additionExpression
    | expression (SHL_OP | SHR_OP) expression  # shiftExpression
    | expression (CMP_GT_OP | CMP_LT_OP | CMP_GE_OP | CMP_LE_OP) expression  # comparisonExpression
    | expression (CMP_EQ_OP | CMP_NE_OP) expression  # equalityExpression
    | expression BITWISE_AND_OP expression  # bitwiseAndExpression
    | expression BITWISE_XOR_OP expression  # bitwiseXorExpression
    | expression BITWISE_OR_OP expression  # bitwiseOrExpression
    | expression LOGICAL_AND_OP expression  # logicalAndExpression
    | expression LOGICAL_XOR_OP expression  # logicalXorExpression
    | expression LOGICAL_OR_OP expression  # logicalOrExpression
    | <assoc=right> expression TERNARY_CONDITIONAL_OP expression COLON expression  # ternaryConditionalExpression
    | IDENTIFIER OPEN_PARENS expressionList? CLOSE_PARENS  # functionCall
    | IDENTIFIER OPEN_BRACKET indexList CLOSE_BRACKET  # index
    | IDENTIFIER  # identifier
    | OPEN_BRACKET expression COMMA expression COMMA expression CLOSE_BRACKET  # axisInitializationList
    | OPEN_BRACE expressionList CLOSE_BRACE  # initializationList
    | BOOLEAN_LITERAL  # booleanLiteral
    | INTEGER_LITERAL  # integerLiteral
    | FLOAT_LITERAL  # floatLiteral
    ;

解答

1. 语法规则是否导致问题?

不是。你的语法规则本身没问题,问题出在ANTLR的错误恢复机制:当解析器遇到无法匹配的输入(比如缺少]的q[0,4),它会尝试跳过Token直到找到能继续匹配的点。在你的案例中,解析器一直读到EOF才触发错误,因为没有后续Token能匹配语法规则里的闭合括号或语句分隔符。

2. 语法规则无问题时的解决方案

a) 规范判断EOF Token的方法

ANTLR中,EOF Token的类型固定为Token.EOF(值为-1)。你可以在CustomErrorListener的syntaxError方法中直接判断:

if (offendingToken.getType() == Token.EOF) {
    // 处理EOF作为违规符号的情况
}

b) 获取EOF的前序Token

可以通过解析器的TokenStream获取。在你的CustomErrorListener中,你可以持有TokenStream的引用,或者通过解析器实例获取:

// 假设你有TokenStream的引用tokenStream
int eofIndex = tokenStream.index();
Token previousToken = tokenStream.get(eofIndex - 1);

注意要处理边界情况(比如输入为空时,eofIndex为0,此时没有前序Token)。

另外,你也可以在syntaxError方法中,通过recognizer(即解析器实例)获取TokenStream:

TokenStream tokenStream = ((Parser) recognizer).getInputStream();
int currentIndex = tokenStream.index();
if (currentIndex > 0) {
    Token previousToken = tokenStream.get(currentIndex - 1);
}

内容的提问来源于stack exchange,提问作者rturrado

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 11:44:54