翻 IntelIJ 文档的时候,遇到了这个没见过的文件类型,遂问 AI。
网上关于这个文件的资料并不是很充分,故发。
本文知识和 java 和正则表达式的交集比较大。
简介
Flex(Fast Lexical Analyzer)文件是用于生成词法分析器的规范文件。在IntelliJ语言支持插件开发中,Flex被用来定义语言的词法规则,将源代码文本转换为标记流。
Flex文件的基本结构
一个典型的Flex文件由三个部分组成,用%%分隔:
%{
// 第一部分:用户代码区
// Java代码,会原样复制到生成的类中
import com.intellij.psi.tree.IElementType;
%}
// 第二部分:选项和声明
%class MyLexer
%implements FlexLexer
%unicode
%function advance
%type IElementType
// 宏定义
DIGIT=[0-9]
LETTER=[a-zA-Z]
%%
// 第三部分:词法规则
{WHITE_SPACE} { return TokenType.WHITE_SPACE; }
{DIGIT}+ { return MyTypes.NUMBER; }
{LETTER}+ { return MyTypes.IDENTIFIER; }
. { return TokenType.BAD_CHARACTER; }
IntelliJ插件中的Flex
在IntelliJ平台开发语言插件时,Flex用于:
- 定义标记类型:在.flex文件中定义语言的各类标记
- 生成词法分析器:通过Flex工具生成Java词法分析器类
- 与PSI集成:生成的Lexer与IntelliJ的PSI系统配合工作
典型用法:
// 定义标记类型
%{
private IElementType myType;
private MyLanguageTokenType myToken = new MyLanguageTokenType("MY_TOKEN");
%}
// 定义规则
"if" { return MyTypes.IF_KEYWORD; }
"else" { return MyTypes.ELSE_KEYWORD; }
"=" { return MyTypes.ASSIGN_OP; }
"+" { return MyTypes.PLUS_OP; }
在IntelliJ插件项目中的位置
通常Flex文件放在:
· src/main/java/com/yourplugin/lexer/ · 或 src/main/resources/ 中
构建时会通过Flex插件生成对应的Java词法分析器类。
语法详解
Flex的语法规则部分由模式和动作组成,格式为:
模式 { 动作代码 }
1. 模式(Pattern)语法
基本字符匹配
"if" // 匹配精确字符串if
'if' // 同上,单引号也可以
"if" // 双引号内的字符串按字面匹配
a // 匹配单个字符a
\n // 换行符
\t // 制表符
\\ // 反斜杠本身
\" // 双引号本身
字符类
[abc] // 匹配a、b或c中的任意一个
[a-z] // 匹配a到z之间的任意小写字母
[A-Z] // 匹配A到Z之间的任意大写字母
[0-9] // 匹配任意数字
[a-zA-Z] // 匹配任意字母
[^abc] // 匹配除a、b、c外的任意字符(否定字符类)
[^0-9] // 匹配非数字字符
[\n\t\r] // 匹配空白字符
预定义字符类
. // 匹配除换行外的任意字符
\d // 数字,等价于[0-9]
\D // 非数字,等价于[^0-9]
\w // 单词字符,等价于[a-zA-Z0-9_]
\W // 非单词字符
\s // 空白字符,等价于[ \t\n\r\f]
\S // 非空白字符
重复和数量
a* // 匹配0个或多个a
a+ // 匹配1个或多个a
a? // 匹配0个或1个a
a{3} // 匹配恰好3个a
a{3,} // 匹配至少3个a
a{3,5} // 匹配3到5个a
运算符优先级
ab // 连接:先a后b
a|b // 选择:a或b
(a|b)c // 分组:括号内的a或b,然后接c
特殊操作符
a/b // 后顾:匹配a但仅当后面跟着b(不消耗b)
<<EOF>> // 匹配文件结束
^ // 行首(在规则开始处)
$ // 行尾(在规则末尾)
2. 宏定义
在第二部分定义宏,简化规则:
DIGIT = [0-9]
LETTER = [a-zA-Z]
IDENT = {LETTER}({LETTER}|{DIGIT})*
INTEGER = {DIGIT}+
WHITE = [ \t\n\r]+
%%
{WHITE} { /* 忽略空白 */ }
{IDENT} { return IDENTIFIER; }
{INTEGER} { return INTEGER_LITERAL; }
3. 动作(Action)语法
动作是匹配模式后执行的Java代码:
基本返回值
"if" { return IF_KEYWORD; }
"else" { return ELSE_KEYWORD; }
";" { return SEMICOLON; }
{IDENT} { return IDENTIFIER; }
获取匹配文本
{IDENT} {
String text = yytext(); // 获取匹配的文本
int length = yylength(); // 获取匹配长度
System.out.println("找到标识符: " + text);
return IDENTIFIER;
}
状态相关操作
"/*" { yybegin(COMMENT); } // 切换到COMMENT状态
<COMMENT>{
"*/" { yybegin(YYINITIAL); } // 回到初始状态
[^*]+ { /* 忽略注释内容 */ }
"*"+ { /* 忽略星号 */ }
}
使用yypushState和yypopState(嵌套状态)
"(" { yypushState(NESTED); }
<NESTED>{
")" { yypopState(); }
[^()]+ { /* 处理嵌套内容 */ }
}
4. 状态(States)管理
在第二部分定义状态:
%x COMMENT // 独占状态(exclusive)
%s STRING // 共享状态(inclusive)
%%
<COMMENT>{
"*/" { yybegin(YYINITIAL); }
[^*]+ { /* 忽略 */ }
}
<STRING>{
"\"" { yybegin(YYINITIAL); return STRING_LITERAL; }
\\\" { /* 转义的引号 */ }
[^\\\"]+ { /* 字符串内容 */ }
}
· %x:独占状态,只匹配该状态下的规则 · %s:共享状态,同时匹配无状态规则和本状态规则
5. 完整示例
package com.example.lexer;
import com.intellij.psi.tree.IElementType;
import com.example.psi.MyTypes;
%%
%class MyLexer
%implements FlexLexer
%unicode
%function advance
%type IElementType
%{
private int commentDepth = 0;
private IElementType myType;
%}
// 定义状态
%x COMMENT
%x STRING
// 宏定义
DIGIT = [0-9]
LETTER = [a-zA-Z_]
WHITE = [ \t\n\r]+
IDENT = {LETTER}({LETTER}|{DIGIT})*
INTEGER = {DIGIT}+
FLOAT = {DIGIT}+"."{DIGIT}*
%%
// 关键字
"if" { return MyTypes.IF; }
"else" { return MyTypes.ELSE; }
"while" { return MyTypes.WHILE; }
"return" { return MyTypes.RETURN; }
// 字面量
{INTEGER} { return MyTypes.INTEGER; }
{FLOAT} { return MyTypes.FLOAT; }
\" { yybegin(STRING); return MyTypes.STRING_BEGIN; }
// 运算符
"+" { return MyTypes.PLUS; }
"-" { return MyTypes.MINUS; }
"*" { return MyTypes.MULTIPLY; }
"/" { return MyTypes.DIVIDE; }
"=" { return MyTypes.ASSIGN; }
"==" { return MyTypes.EQUALS; }
// 分隔符
";" { return MyTypes.SEMICOLON; }
"," { return MyTypes.COMMA; }
"." { return MyTypes.DOT; }
"(" { return MyTypes.LPAREN; }
")" { return MyTypes.RPAREN; }
"{" { return MyTypes.LBRACE; }
"}" { return MyTypes.RBRACE; }
// 标识符
{IDENT} { return MyTypes.IDENTIFIER; }
// 注释
"/*" { yybegin(COMMENT); commentDepth++; }
<COMMENT>{
"/*" { commentDepth++; }
"*/" {
commentDepth--;
if (commentDepth == 0) {
yybegin(YYINITIAL);
}
}
[^*/]+ { /* 忽略注释内容 */ }
[*/] { /* 忽略单个*或/ */ }
}
// 字符串
<STRING>{
\" { yybegin(YYINITIAL); return MyTypes.STRING_END; }
\\\" { return MyTypes.ESCAPED_QUOTE; }
[^\\\"]+ { return MyTypes.STRING_CONTENT; }
}
// 空白
{WHITE} { return TokenType.WHITE_SPACE; }
// 错误处理
. { return TokenType.BAD_CHARACTER; }
<<EOF>> { return null; }
6. 常用技巧
处理Unicode
%unicode
LETTER = [:letter:] // 匹配任何语言的字母
DIGIT = [:digit:] // 匹配任何语言的数字
向前看/向后看
"<"/[^<>]*">" // 匹配<但仅在后面有匹配的>时
{IDENT}/"(" // 匹配标识符但仅当后面跟着(
条件编译
%{
private boolean debug = false;
%}
%%
{IDENT} {
if (debug) {
System.out.println("找到: " + yytext());
}
return IDENTIFIER;
}
Flex的语法规则强大而灵活,通过组合这些基本元素,可以为任何编程语言定义精确的词法规则。