*.flex 文件简介

5 阅读4分钟

翻 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用于:

  1. 定义标记类型:在.flex文件中定义语言的各类标记
  2. 生成词法分析器:通过Flex工具生成Java词法分析器类
  3. 与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的语法规则强大而灵活,通过组合这些基本元素,可以为任何编程语言定义精确的词法规则。