refactor: IdentifierTokenScanner 重构为状态机

This commit is contained in:
Luke 2025-07-01 17:02:01 +08:00
parent ded31578d7
commit b43245b1f5

View File

@ -1,30 +1,34 @@
package org.jcnc.snow.compiler.lexer.scanners; package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext; import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.core.LexicalException;
import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenFactory; import org.jcnc.snow.compiler.lexer.token.TokenFactory;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/** /**
* 标识符扫描器处理标识符的识别如变量名函数名等 * {@code IdentifierTokenScanner} 标识符扫描器负责识别源代码中的标识符如变量名函数名等
* <p> *
* 识别规则如下 * <p>标识符的识别遵循以下规则</p>
* <ul> * <ul>
* <li>必须以字母或下划线_开头</li> * <li>标识符必须以字母A-Za-z或下划线_开头</li>
* <li>后续字符可以是字母数字或下划线</li> * <li>标识符的后续字符可以是字母数字0-9或下划线</li>
* </ul> * </ul>
* <p> *
* 扫描完成后会调用 {@link TokenFactory} 自动判断是否为关键字 * <p>在扫描过程中标识符会被处理为一个 {@link Token} 对象如果该标识符是一个关键字
* 并返回对应类型的 {@link Token} * 扫描器会通过 {@link TokenFactory} 自动识别并返回相应的 {@link TokenType}</p>
*
* <p>本扫描器实现了一个有限状态机FSM它能够在不同状态之间转换确保标识符的正确识别</p>
*/ */
public class IdentifierTokenScanner extends AbstractTokenScanner { public class IdentifierTokenScanner extends AbstractTokenScanner {
/** /**
* 判断是否可以处理当前位置的字符 * 判断当前字符是否可以作为标识符的起始字符
* <p>如果字符为字母或下划线则认为是标识符的</p> * <p>如果字符为字母或下划线则认为是标识符的</p>
* *
* @param c 当前字符 * @param c 当前字符
* @param ctx 当前词法上下文 * @param ctx 当前词法上下文
* @return 如果是标识符起始字符则返回 true * @return 如果字符是标识符起始字符则返回 {@code true}否则返回 {@code false}
*/ */
@Override @Override
public boolean canHandle(char c, LexerContext ctx) { public boolean canHandle(char c, LexerContext ctx) {
@ -32,17 +36,57 @@ public class IdentifierTokenScanner extends AbstractTokenScanner {
} }
/** /**
* 执行标识符的扫描逻辑 * 执行标识符扫描
* <p>连续读取满足标识符规则的字符序列交由 {@code TokenFactory} 创建对应的 Token</p> * <p>使用状态机模式扫描标识符首先从初始状态开始读取标识符的起始字符字母或下划线
* 然后进入标识符状态继续读取标识符字符字母数字或下划线一旦遇到不符合标识符规则的字符
* 标识符扫描结束返回一个 {@link Token}</p>
* *
* @param ctx 词法上下文 * @param ctx 词法上下文用于获取字符流
* @param line 当前行号 * @param line 当前行号1
* @param col 当前列号 * @param col 当前列号1
* @return 标识符或关键字类型的 Token * @return 返回一个包含标识符或关键字的 {@link Token} 对象
* @throws LexicalException 如果标识符以非法字符如点号开头则抛出异常
*/ */
@Override @Override
protected Token scanToken(LexerContext ctx, int line, int col) { protected Token scanToken(LexerContext ctx, int line, int col) {
String lexeme = readWhile(ctx, ch -> Character.isLetterOrDigit(ch) || ch == '_'); StringBuilder lexeme = new StringBuilder(); // 用于构建标识符的字符串
return TokenFactory.create(lexeme, line, col); State currentState = State.INITIAL; // 初始状态
// 遍历字符流直到遇到不合法的字符或流结束
while (!ctx.isAtEnd()) {
char currentChar = ctx.peek(); // 获取当前字符
switch (currentState) {
case INITIAL:
// 初始状态标识符开始
if (Character.isLetter(currentChar) || currentChar == '_') {
lexeme.append(ctx.advance()); // 接受当前字符
currentState = State.IDENTIFIER; // 进入标识符状态
} else {
return null; // 当前字符不符合标识符的规则返回 null
}
break;
case IDENTIFIER:
// 标识符状态继续读取合法标识符字符
if (Character.isLetterOrDigit(currentChar) || currentChar == '_') {
lexeme.append(ctx.advance()); // 继续接受合法字符
} else {
// 当前字符不符合标识符的规则标识符结束返回 token
return TokenFactory.create(lexeme.toString(), line, col);
}
break;
}
}
// 如果字符流结束返回标识符 token
return TokenFactory.create(lexeme.toString(), line, col);
}
/**
* 枚举类型表示标识符扫描的状态
*/
private enum State {
INITIAL, // 初始状态等待标识符的开始
IDENTIFIER // 标识符状态继续读取标识符字符
} }
} }