本文是我在开发插件的时候让 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() 方法
十、最佳实践
- 从顶层开始:先定义根规则,再逐步细化
- 合理使用私有规则:只对真正需要 PSI 的规则生成类
- 适当使用 pin:提高解析性能
- 写注释:复杂的规则一定要注释
- 先测试小例子:用 PsiViewer 验证解析结果
- 保持一致性:命名风格统一
- 考虑扩展性:为将来的语法扩展留余地