*.bnf 文件简介(范畴:IntellIJ 插件开发)

4 阅读5分钟

本文是我在开发插件的时候让 AI 生成的一个解释。


一、BNF 基础概念

BNF (Backus-Naur Form) 是用来定义编程语言语法的元语言。在 IntelliJ 插件开发中,我们用 BNF 文件来定义语言的语法规则,Grammar-Kit 会根据 BNF 文件生成解析器和 PSI 类。

二、BNF 文件整体结构

{
  // ========== 1. 解析器配置 ==========
  parserClass="org.intellij.sdk.language.parser.LoreParser"  // 生成的解析器类
  
  // ========== 2. PSI 元素配置 ==========
  extends="com.intellij.extapi.psi.ASTWrapperPsiElement"      // PSI 元素基类
  
  psiClassPrefix="Lore"        // PSI 类名前缀
  psiImplClassSuffix="Impl"    // PSI 实现类后缀
  
  psiPackage="org.intellij.sdk.language.psi"        // PSI 接口包
  psiImplPackage="org.intellij.sdk.language.psi.impl" // PSI 实现包
  
  // ========== 3. Token 类型类配置 ==========
  elementTypeHolderClass="org.intellij.sdk.language.psi.LoreTypes"  // token 类型持有类
  elementTypeClass="org.intellij.sdk.language.psi.LoreElementType"  // 元素类型类
  tokenTypeClass="org.intellij.sdk.language.psi.LoreTokenType"      // token 类型类
  
  // ========== 4. Token 定义 ==========
  tokens = [
    // 关键字(直接写字符串)
    IF='if'
    ELSE='else'
    WHILE='while'
    FOR='for'
    RETURN='return'
    
    // 运算符
    PLUS='+'
    MINUS='-'
    MUL='*'
    DIV='/'
    ASSIGN='='
    EQ='=='
    NE='!='
    LT='<'
    GT='>'
    LE='<='
    GE='>='
    
    // 分隔符
    LPAREN='('
    RPAREN=')'
    LBRACE='{'
    RBRACE='}'
    LBRACK='['
    RBRACK=']'
    SEMICOLON=';'
    COMMA=','
    DOT='.'
    
    // 字面量(正则表达式)
    INTEGER='regexp:\d+'
    FLOAT='regexp:\d+\.\d*'
    STRING='regexp:"[^"]*"'
    CHAR='regexp:\'.\''
    IDENTIFIER='regexp:[a-zA-Z_][a-zA-Z0-9_]*'
    COMMENT='regexp://.*'
    BLOCK_COMMENT='regexp:/\*([^*]|\*[^/])*\*/'
    WHITE_SPACE='regexp:\s+'
    CRLF='regexp:\n'
  ]
  
  // ========== 5. 命名 token(可选)==========
  // 给正则表达式 token 起个好记的名字
  name(".*_expr") = "expression"
  name(".*_statement") = "statement"
}

// ========== 6. 语法规则 ==========
// 根规则
loreFile ::= statement*

// 语句规则
private statement ::= 
    let_statement 
  | if_statement 
  | while_statement 
  | for_statement 
  | return_statement 
  | expression_statement
  | block
  | COMMENT
  | CRLF

// 具体的规则定义...

三、语法规则详解

1. 规则定义语法

// 基本形式:规则名 ::= 定义
规则名 ::= 元素1 元素2 元素3 ...

// 元素可以是:
// - token(如 'if'、IDENTIFIER)
// - 其他规则
// - 正则表达式
// - 组合

2. 组合操作符

// 1. 或操作符 |
if_statement ::= 'if' '(' expression ')' statement 
               | 'if' '(' expression ')' statement 'else' statement

// 2. 可选操作符 ?
optional_semicolon ::= ';'?

// 3. 重复操作符 *(0次或多次)
statement_list ::= statement*

// 4. 重复操作符 +(1次或多次)
non_empty_list ::= item+

// 5. 分组操作符 ()
expression ::= ('+' | '-')? term

3. 规则类型

// 1. 公开规则(默认)→ 会生成 PSI 类
public rule ::= ...
// 或者显式声明
rule ::= ... { }

// 2. 私有规则 → 不生成 PSI 类,只用于组织语法
private helper_rule ::= ...

// 3. 外部规则 → 手动实现解析逻辑
external rule ::= com.example.MyParser.parseRule

四、常用语法模式

1. 表达式优先级

// 经典的表达式优先级定义(从低到高)
expression ::= assignment_expr

assignment_expr ::= conditional_expr (('=' | '+=' | '-=') assignment_expr)?

conditional_expr ::= logical_or_expr ('?' expression ':' conditional_expr)?

logical_or_expr ::= logical_and_expr ('||' logical_and_expr)*

logical_and_expr ::= bitwise_or_expr ('&&' bitwise_or_expr)*

bitwise_or_expr ::= bitwise_xor_expr ('|' bitwise_xor_expr)*

bitwise_xor_expr ::= bitwise_and_expr ('^' bitwise_and_expr)*

bitwise_and_expr ::= equality_expr ('&' equality_expr)*

equality_expr ::= relational_expr (('==' | '!=') relational_expr)*

relational_expr ::= shift_expr (('<' | '>' | '<=' | '>=') shift_expr)*

shift_expr ::= additive_expr (('<<' | '>>' | '>>>') additive_expr)*

additive_expr ::= multiplicative_expr (('+' | '-') multiplicative_expr)*

multiplicative_expr ::= unary_expr (('*' | '/' | '%') unary_expr)*

unary_expr ::= ('+' | '-' | '++' | '--' | '!' | '~')? primary

primary ::= literal | identifier | '(' expression ')' | call_expr

2. 语句类型

// 块语句
block ::= '{' statement* '}'

// 变量声明
let_statement ::= 'let' identifier (':' type)? ('=' expression)? ';'

// 函数声明
function_declaration ::= 'function' identifier '(' parameter_list? ')' (':' type)? block

parameter_list ::= parameter (',' parameter)*
parameter ::= identifier ':' type

// 类型定义
type ::= 'int' | 'float' | 'string' | 'boolean' | identifier | array_type
array_type ::= type '[' ']'

// 函数调用
call_expr ::= identifier '(' argument_list? ')'
argument_list ::= expression (',' expression)*

3. 列表和分隔符

// 用逗号分隔的列表
comma_separated_list ::= item (',' item)*

// 可选尾部逗号
trailing_comma_list ::= item (',' item)* ','?

// 用分号分隔的列表
semicolon_separated_list ::= item (';' item)* ';'?

五、属性配置详解

1. pin 属性

rule ::= 'if' '(' expression ')' block {
  pin=1  // 看到 'if' 就确定这是 if 语句
}

// 复杂情况的 pin
rule ::= prefix middle suffix {
  pin("prefix")=1     // 只有 prefix 规则需要 pin
  pin("middle|suffix")=2  // middle 或 suffix 在第2个元素时 pin
  pin=1               // 全局默认
}

pin 的作用

  • 告诉解析器:"到这个元素就确定当前规则了"
  • 提高解析性能
  • 更好的错误恢复

2. recoverWhile 属性

statement_list ::= statement* {
  recoverWhile="statement_recover"
}

private statement_recover ::= !('}' | ';' | keyword)

// 更复杂的恢复
block ::= '{' statement* '}' {
  pin=1
  recoverWhile="block_recover"
}

private block_recover ::= !('}' | <<eof>>)

recoverWhile 的作用

  • 错误恢复:解析出错时继续消费 token 直到遇到指定集合
  • 避免错误扩散

3. methods 属性

let_statement ::= 'let' identifier '=' expression ';' {
  methods=[
    getIdentifier     // 生成 getIdentifier() 方法
    getExpression     // 生成 getExpression() 方法
    isConstant        // 生成 isConstant() 方法
  ]
}

// 自定义方法名
binary_expr ::= left_expr operator right_expr {
  methods=[
    left="getLeftOperand"
    right="getRightOperand"
    operator="getOperator"
  ]
}

4. mixin 和 implements

// 混入已有的类
identifier_expr ::= identifier {
  mixin="org.intellij.sdk.language.psi.impl.IdentifierExprMixin"
}

// 实现接口
call_expr ::= identifier '(' argument_list? ')' {
  implements="org.intellij.sdk.language.psi.CallExpression"
}

六、高级特性

1. 外部规则

// 手动实现复杂的解析逻辑
complex_rule ::= external com.example.MyParser.parseComplexRule param1 param2

// 在 Java 中实现
public class MyParser {
  public static boolean parseComplexRule(PsiBuilder builder, int level, String param) {
    // 手动实现解析逻辑
    if (!builder.getTokenText().equals("expected")) {
      return false;
    }
    builder.advanceLexer();
    return true;
  }
}

2. 内联 Java 代码

// 在规则中嵌入 Java 代码
rule ::= element {
  // 在生成的 PSI 类中添加字段
  extraField="private int myExtraField;"
  
  // 添加构造方法
  extraConstructor="public MyRule(@NotNull ASTNode node) { super(node); myExtraField = 0; }"
  
  // 添加方法
  extraMethod="public int getExtraField() { return myExtraField; }"
}

3. 条件解析

// 根据上下文条件解析
rule ::= element {
  // 只在特定条件下生成
  generate=[ 
    psi=false  // 不生成 PSI 类
    parser=true  // 只生成解析器
  ]
}

// 使用 <<eof>> 检查文件结束
file ::= element* <<eof>>

七、实际案例:JSON 语法

{
  tokens = [
    LBRACE='{'
    RBRACE='}'
    LBRACK='['
    RBRACK=']'
    COLON=':'
    COMMA=','
    STRING='regexp:"([^"\\]|\\.)*"'
    NUMBER='regexp:-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?'
    TRUE='true'
    FALSE='false'
    NULL='null'
    WHITE_SPACE='regexp:\s+'
  ]
  
  extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
  psiClassPrefix="Json"
  psiImplClassSuffix="Impl"
  psiPackage="org.intellij.sdk.language.psi"
  psiImplPackage="org.intellij.sdk.language.psi.impl"
  elementTypeHolderClass="org.intellij.sdk.language.psi.JsonTypes"
}

json ::= value

private value ::= 
    object 
  | array 
  | STRING 
  | NUMBER 
  | TRUE 
  | FALSE 
  | NULL
  | WHITE_SPACE?

object ::= '{' object_members? '}' {
  pin=1
}

private object_members ::= object_member (',' object_member)* ','?

object_member ::= STRING ':' value {
  pin=2
  methods=[getKey="getString" getValue]
}

array ::= '[' array_values? ']' {
  pin=1
}

private array_values ::= value (',' value)* ','?

八、调试技巧

1. 使用 name 属性调试

{
  name(".*_expr") = "expression"
  name(".*_statement") = "statement"
}

// 在 PsiViewer 中会显示友好的名称

2. 使用 console 输出

rule ::= element {
  // 在解析时输出调试信息
  on="recover"  // 在错误恢复时触发
  console="Error recovering rule"
}

3. 常见错误和解决方案

// 错误:左递归
expr ::= expr '+' term  // 无限递归!

// 正确:改成迭代
expr ::= term ('+' term)*

// 错误:歧义
stmt ::= IF '(' expr ')' stmt
       | IF '(' expr ')' stmt ELSE stmt

// 正确:明确优先级
stmt ::= IF '(' expr ')' stmt else_clause?
else_clause ::= ELSE stmt

// 错误:过度使用私有规则
// 可能丢失重要的 PSI 信息
private important_rule ::= ...  // 如果后面需要引用,不要私有

九、生成代码的对应关系

// BNF 规则                 →   生成的 PSI 类
let_statement ::= ...      →   LetStatement.java (接口)
                           →   LetStatementImpl.java (实现)

// Token 定义              →   Token 类型
IF='if'                    →   Types.IF
IDENTIFIER='regexp:...'    →   Types.IDENTIFIER

// 属性配置                →   生成的方法
methods=[getFoo]           →   getFoo() 方法

十、最佳实践

  1. 从顶层开始:先定义根规则,再逐步细化
  2. 合理使用私有规则:只对真正需要 PSI 的规则生成类
  3. 适当使用 pin:提高解析性能
  4. 写注释:复杂的规则一定要注释
  5. 先测试小例子:用 PsiViewer 验证解析结果
  6. 保持一致性:命名风格统一
  7. 考虑扩展性:为将来的语法扩展留余地