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

JavaCC自定义语法解析问题:末尾单个空行触发ParseException

解决JavaCC解析器末尾单个空行报错的问题

我来帮你分析一下问题的根源:

你的解析器在遇到末尾单个空行时抛出异常,核心原因是单个空行的换行符被错误地纳入了Body()规则的匹配逻辑,而Body()期望换行后跟着非空的<STRING> Token,但此时已经到了EOF,无法匹配,从而触发错误。

具体来说:

  1. 在DEFAULT状态下,你定义了"\n" : INBODY,意味着任何换行符都会切换到INBODY状态,尝试匹配<STRING>。
  2. 你的Body()规则是<STRING> ("\n" <STRING>)*,它会不断尝试匹配“换行+字符串”的组合。当末尾只有单个空行时,换行符触发状态切换后,解析器试图匹配<STRING>,但此时已经是EOF,且<STRING>默认不允许空匹配(JavaCC默认禁止空Token),因此抛出ParseException。
  3. 你之前修改S()规则时去掉了["\n"],但并没有解决单个空行被Body()规则捕获的问题。

解决方案

我们需要调整Token定义和语法规则,将“单元结束后的空行”与“Body内部的换行”区分开:

1. 添加空行Token并调整匹配优先级

首先,在DEFAULT状态下定义专门匹配空行的Token,并确保它在单独的换行符之前被匹配,避免空行触发状态切换到INBODY:

<DEFAULT> TOKEN: {
  "(" | ")" | 
  <ID: (["a"-"z","A"-"Z","0"-"9","-","_"])+ > |
  <#RAND: (" " | "\t" | "\r")* > |
  // 匹配单个空行(前后可含空白字符)
  <EMPTY_LINE: <RAND> "\n" <RAND> > |
  // 非空行的换行,切换到INBODY状态
  "\n" : INBODY
}

同时移除原来的<END> Token定义,我们用<EMPTY_LINE>*来表示任意数量的空行。

2. 修正<STRING>的定义(确保匹配非空行)

为了符合你的目标语言(Body行都是非空的),将<STRING>改为匹配至少一个非换行字符:

<INBODY> TOKEN: {
  // 匹配至少一个非换行/回车字符,匹配完成后切回DEFAULT状态
  <STRING: (~["\n", "\r"])+> : DEFAULT
}

3. 重新设计S()规则

让每个Signature()+Body()块之后可以跟任意数量(包括0)的空行,最后以EOF结束:

void S(): {} {
  (Signature() "\n" Body() <EMPTY_LINE>* )+ <EOF>
}

4. (可选)添加OPTIONS配置

如果你的JavaCC版本需要显式允许空Token(比如<RAND>允许空匹配),可以在PARSER_BEGIN之前添加:

OPTIONS {
  ALLOW_EMPTY_TOKENS = true;
}

完整修改后的代码

OPTIONS {
  ALLOW_EMPTY_TOKENS = true;
}

PARSER_BEGIN(Compiler)
public class Compiler {
    public static void main(String[] args) {
        try {
            (new Compiler(new java.io.BufferedReader(new java.io.FileReader(args[0])))).S();
            System.out.println("Syntax is correct");
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}
PARSER_END(Compiler)

<DEFAULT, INBODY> SKIP: { " " | "\t" | "\r" }

<DEFAULT> TOKEN: {
  "(" | ")" | 
  <ID: (["a"-"z","A"-"Z","0"-"9","-","_"])+ > |
  <#RAND: (" " | "\t" | "\r")* > |
  <EMPTY_LINE: <RAND> "\n" <RAND> > |
  "\n" : INBODY
}

<INBODY> TOKEN: {
  <STRING: (~["\n", "\r"])+> : DEFAULT
}

void S(): {} {
  (Signature() "\n" Body() <EMPTY_LINE>* )+ <EOF>
}

void Signature(): {} {
  "(" <ID> <ID> ")"
}

void Body(): {} {
  <STRING> ("\n" <STRING> )*
}

验证

现在你的解析器可以正确处理以下场景:

  • 末尾无空行
  • 末尾有单个空行
  • 末尾有多个空行
  • 单元之间有任意数量的空行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:04:05