好的,我们来详细展开讲解 第三步:定义"单词"(词法规则 / Lexer Rules)。这是整个语法解析的基础,决定了如何将原始的字符流转换成有意义的语言单元。
1. 词法规则的核心概念
什么是词法规则?
词法规则定义了如何将输入的字符序列分解成一系列的词法符号(Tokens)。这个过程称为词法分析或标记化。
简单比喻:词法分析就像阅读时的"认字"过程——将连续的笔画识别成一个个独立的汉字/单词。
词法规则的基本结构
TokenName : pattern ;- TokenName:词法符号的名称,必须大写字母开头
- ::分隔符
- pattern:匹配模式(正则表达式)
- ;:结束符
2. 词法规则的命名规范
大小写约定
| 类型 | 命名规范 | 示例 |
|---|---|---|
| 词法规则 | 首字母大写,驼峰式 | INT, Identifier, StringLiteral |
| 语法规则 | 首字母小写,驼峰式 | expression, ifStatement, variableDeclaration |
常用命名模式
// 1. 关键字:全大写,描述性
IF : 'if';
ELSE : 'else';
WHILE : 'while';
RETURN : 'return';
// 2. 标识符:描述性名称
IDENTIFIER : [a-zA-Z_][a-zA-Z_0-9]*;
// 3. 字面量:类型_LITERAL 格式
INT_LITERAL : [0-9]+;
FLOAT_LITERAL : [0-9]+ '.' [0-9]*;
STRING_LITERAL : '"' .*? '"';
// 4. 运算符:简洁明确
ASSIGN : '=';
PLUS : '+';
EQ : '==';
// 5. 分隔符
LPAREN : '(';
RPAREN : ')';
LBRACE : '{';
RBRACE : '}';
SEMI : ';';
COMMA : ',';3. 模式(Pattern)的语法详解
3.1 基本字符匹配
// 字面量字符
PLUS : '+';
MINUS : '-';
ASSIGN : '=';
// 字符序列
IF : 'if';
FOR : 'for';3.2 字符类(Character Classes)
// 单个字符范围
DIGIT : [0-9]; // 0到9的数字
LETTER : [a-zA-Z]; // 所有字母
HEX_DIGIT : [0-9a-fA-F]; // 十六进制数字
// 字符组合
OPERATOR : [+-*/]; // 四个运算符中的任意一个
BINARY : [01]; // 二进制数字
// 取反字符类
NON_ZERO_DIGIT : [1-9]; // 非零数字
NON_DIGIT : ~[0-9]; // 非数字字符
NON_QUOTE : ~["]; // 非引号字符3.3 量词(Quantifiers)
// ? : 0次或1次(可选)
OPTIONAL_MINUS : '-'? DIGIT+;
// * : 0次或多次
DIGITS : DIGIT*; // 零个或多个数字
WHITESPACE : [ \t]*; // 零个或多个空格/tab
// + : 1次或多次(至少一个)
NUMBER : DIGIT+; // 一个或多个数字
IDENTIFIER : [a-zA-Z_]+; // 至少一个字母/下划线
// {n,m} : n到m次
PRECISE_DIGITS : DIGIT{3,5}; // 3到5个数字
AT_LEAST_TWO : DIGIT{2,}; // 至少2个数字
EXACTLY_THREE : DIGIT{3}; // 正好3个数字3.4 转义字符
// 特殊字符需要转义
LPAREN : '('; // 正常字符,不需要转义
// LBRACKET : '['; // 错误!'[' 是元字符,需要转义
LBRACKET : '\\['; // 正确:转义方括号
ESCAPED_QUOTE : '\\"'; // 转义引号
NEWLINE : '\\n'; // 换行符
TAB : '\\t'; // 制表符
BACKSLASH : '\\\\'; // 反斜杠本身4. 高级模式技巧
4.1 选择运算符(|)
// 匹配多种模式之一
OPERATOR : '+' | '-' | '*' | '/';
COMPARISON : '<' | '>' | '<=' | '>=' | '==' | '!=';
BOOLEAN : 'true' | 'false';4.2 通配符和范围
ANY_CHAR : . ; // 匹配任意单个字符(除换行符)
ANY_CHAR_INCL_NL : .? ; // 匹配任意字符包括换行符(非贪婪)
// 复杂范围
WORD_CHAR : [a-zA-Z0-9_]; // 单词字符
NON_SPACE : ~[ \t\r\n]; // 非空白字符4.3 非贪婪匹配
// .*? : 非贪婪匹配(最短匹配)
STRING : '"' .*? '"'; // 匹配两个引号之间的最短内容
COMMENT : '/*' .*? '*/'; // 匹配注释内容(遇到第一个*/就结束)
// 对比贪婪匹配(错误示例)
// BAD_STRING : '"' .* '"'; // 错误!会匹配到最后一个引号5. 片段规则(Fragments)
片段规则是可重用的词法规则组件,本身不生成 Token,只被其他词法规则引用。
为什么要用片段规则?
- 提高可读性:将复杂模式分解
- 促进重用:避免重复定义
- 便于维护:修改一处,影响全局
片段规则示例
// 定义片段(不生成独立Token)
fragment DIGIT : [0-9];
fragment LETTER : [a-zA-Z];
fragment HEX_DIGIT : [0-9a-fA-F];
fragment ESCAPED_CHAR : '\\\\' [btnfr"'\\]; // 转义字符
// 使用片段构建完整规则
INT : DIGIT+;
FLOAT : DIGIT+ '.' DIGIT*;
IDENTIFIER : LETTER (LETTER | DIGIT)*;
HEX_NUMBER : '0' [xX] HEX_DIGIT+;
STRING : '"' (ESCAPED_CHAR | ~["\\])* '"';6. 词法规则指令(Lexer Commands)
词法规则指令用 -> 符号指定,控制匹配后的行为。
6.1 跳过指令(skip)
// 最常见的指令:跳过匹配的内容
WS : [ \t\r\n]+ -> skip; // 跳过空白字符
COMMENT : '//' ~[\r\n]* -> skip; // 跳过单行注释
MULTILINE_COMMENT : '/*' .*? '*/' -> skip; // 跳过多行注释6.2 通道指令(channel)
// 将Token发送到特定通道(不跳过,但语法规则通常忽略)
WHITESPACE : [ \t\r\n]+ -> channel(HIDDEN); // 隐藏通道
COMMENTS : '//' ~[\r\n]* -> channel(HIDDEN); // 注释也隐藏6.3 模式指令(mode)
用于处理复杂的分词状态(如字符串内的转义字符),属于高级特性。
7. 完整的词法规则示例
让我们看一个完整的编程语言词法规则示例:
lexer grammar CommonTokens;
// ===== 关键字 =====
IF : 'if';
ELSE : 'else';
WHILE : 'while';
FOR : 'for';
RETURN : 'return';
VAR : 'var';
FUNCTION : 'function';
// ===== 类型关键字 =====
INT_TYPE : 'int';
FLOAT_TYPE : 'float';
STRING_TYPE : 'string';
BOOLEAN_TYPE : 'boolean';
// ===== 字面量 =====
fragment DIGIT : [0-9];
fragment LETTER : [a-zA-Z_];
fragment ESCAPE : '\\\\' [btnfr"'\\];
INT_LITERAL : DIGIT+;
FLOAT_LITERAL : DIGIT+ '.' DIGIT*;
STRING_LITERAL : '"' (ESCAPE | ~["\\])* '"';
BOOLEAN_LITERAL : 'true' | 'false';
// ===== 标识符 =====
IDENTIFIER : LETTER (LETTER | DIGIT)*;
// ===== 运算符 =====
ASSIGN : '=';
PLUS : '+';
MINUS : '-';
MUL : '*';
DIV : '/';
MOD : '%';
EQ : '==';
NEQ : '!=';
LT : '<';
GT : '>';
LTE : '<=';
GTE : '>=';
// ===== 分隔符 =====
LPAREN : '(';
RPAREN : ')';
LBRACE : '{';
RBRACE : '}';
LBRACKET : '[';
RBRACKET : ']';
SEMICOLON : ';';
COMMA : ',';
DOT : '.';
// ===== 需要跳过的内容 =====
WHITESPACE : [ \t\r\n]+ -> skip;
LINE_COMMENT : '//' ~[\r\n]* -> skip;
BLOCK_COMMENT : '/*' .*? '*/' -> skip;8. 词法规则的优先级和冲突解决
8.1 优先级原则
ANTLR 词法规则的两个重要原则:
- 最长匹配原则:优先匹配最长的字符串
- 最先定义原则:如果多个规则匹配相同长度,选择最先定义的
8.2 常见冲突和解决方案
冲突1:关键字 vs 标识符
// 正确:关键字必须先定义
IF : 'if';
ELSE : 'else';
IDENTIFIER : [a-zA-Z_][a-zA-Z_0-9]*; // 后定义,不会覆盖关键字
// 错误:如果IDENTIFIER先定义,会匹配到'if'等关键字冲突2:运算符的歧义
// 注意顺序:更具体的规则要先定义
LTE : '<='; // 必须先于LT
LT : '<'; // 后定义
GTE : '>=';
GT : '>';
EQ : '==';
ASSIGN : '=';8.3 测试优先级
输入 <= 的匹配过程:
- 尝试匹配
LTE:成功(2个字符) - 尝试匹配
LT:成功(1个字符),但比LTE短 - 结果:选择
LTE(最长匹配)
9. 调试技巧和最佳实践
9.1 调试词法规则
// 临时添加调试规则
DEBUG_TOKEN : 'DEBUG_' .*? 'END' {System.out.println("Debug: " + getText());};9.2 最佳实践
- 关键字优先:总是先定义关键字,再定义标识符
- 具体优先:更具体的模式要先定义
- 使用片段:复杂模式分解为片段规则
- 充分测试:为边界情况编写测试用例
- 处理所有字符:确保语法能处理所有可能的输入字符
9.3 错误模式示例
// ❌ 错误:关键字被标识符覆盖
IDENTIFIER : [a-zA-Z]+; // 这个会匹配到'if'
IF : 'if'; // 永远不会被匹配到!
// ✅ 正确:关键字先定义
IF : 'if';
IDENTIFIER : [a-zA-Z]+; // 不会匹配'if',因为IF规则优先10. 完整示例:简单计算器的词法规则
grammar SimpleCalculator;
// === 词法规则 ===
// 数字
INT : [0-9]+;
FLOAT : [0-9]+ '.' [0-9]*;
// 运算符
PLUS : '+';
MINUS : '-';
MULTIPLY : '*';
DIVIDE : '/';
POWER : '^';
MODULO : '%';
// 括号
LPAREN : '(';
RPAREN : ')';
// 函数和常量
SQRT : 'sqrt';
SIN : 'sin';
COS : 'cos';
PI : 'pi';
E : 'e';
// 空白字符(跳过)
WS : [ \t\r\n]+ -> skip;
// === 语法规则(下一节详细讲解)===
expression : term ( (PLUS | MINUS) term )* ;
term : factor ( (MULTIPLY | DIVIDE) factor )* ;
factor : (PLUS | MINUS)? (INT | FLOAT | function | LPAREN expression RPAREN) ;
function : (SQRT | SIN | COS) LPAREN expression RPAREN ;总结
词法规则是 ANTLR4 语法的基础,它负责:
- ✅ 识别基本单元:将字符流转换为 Token 流
- ✅ 处理空白注释:通过
skip指令忽略无关内容 - ✅ 定义词汇表:建立语言的"单词表"
- ✅ 处理优先级:通过规则顺序解决歧义
掌握了词法规则,你就为后续的语法规则(如何将这些"单词"组成"句子")打下了坚实基础。下一步,我们将进入语法规则的学习。
本文由 jxxxy 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。