refactor: 重构字符串扫描器实现

- 优化了 StringTokenScanner 类的文档注释,增加了状态机说明
-重新组织了代码结构,提高了可读性和可维护性
- 添加了对未闭合字符串的处理逻辑,增强了健壮性
- 优化了状态机实现,保证了字符串解析的准确性
This commit is contained in:
Luke 2025-07-17 15:24:46 +08:00
parent 5f0931155d
commit e6ad4ff282

View File

@ -5,91 +5,118 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 字符串扫描器: 处理双引号包裹的字符串字面量支持基本的转义字符
* 字符串扫描器StringTokenScanner用于处理由双引号包裹的字符串字面量
* 并支持常见转义字符的解析该扫描器采用有限状态机状态机实现
* 保证对各类字符串格式进行准确的词法分析
* <p>
* 支持格式示例:
* 主要支持如下字符串样式:
* <ul>
* <li>"hello"</li>
* <li>"line\\nbreak"</li>
* <li>"escaped \\\" quote"</li>
* </ul>
* <p>
* 扫描器会保留原始字符串的形式包含双引号和转义符
* 并生成 {@code STRING_LITERAL} 类型的 Token
* 扫描器会保留字符串的原始形式含双引号和转义符
* 并返回{@link TokenType#STRING_LITERAL}类型的Token
* </p>
* <p>
* 状态机说明
* <ul>
* <li>START起始状态准备扫描第一个双引号</li>
* <li>STRING扫描字符串内容</li>
* <li>ESCAPE处理转义字符</li>
* </ul>
*
* @author 你的名字
* @since 2024
*/
public class StringTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>当字符为双引号")时,认为是字符串字面量的开始。</p>
* 判断当前位置的字符是否为字符串起始符号
* <p>
* 只有遇到双引号")时,才由本扫描器处理。
* </p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果为字符串起始符则返回 true
* @param c 当前待扫描字符
* @param ctx 当前词法分析上下文
* @return 如果是字符串起始符号返回 true否则返回 false
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return c == '"'; // 只处理字符串开始符号
return c == '"'; // 只处理双引号起始
}
/**
* 执行字符串的扫描逻辑
* <p>从当前位置开始读取直到匹配结束的双引号
* 支持转义字符 \"\\n 等),不会中断字符串扫描。</p>
* 执行字符串字面量的具体扫描过程采用有限状态机处理逻辑
* 从起始的双引号开始逐字符处理字符串内容支持基本转义序列 \\n\\t\\\" 等)。
* <p>
* 扫描遇到未转义的结尾双引号时即结束并立即返回Token
* 如果遇到换行或文件结束但未遇到结尾引号视为字符串未闭合仍返回已扫描内容
* </p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 字符串字面量类型 Token
* @param ctx 词法分析上下文支持字符遍历与回退等操作
* @param line 字符串开始行号用于错误定位
* @param col 字符串开始列号用于错误定位
* @return 解析得到的字符串字面量类型Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
StringBuilder sb = new StringBuilder();
// 当前状态
State currentState = State.START; // 初始状态为开始扫描字符串
StringBuilder sb = new StringBuilder(); // 用于收集字符串原文
State currentState = State.START; // 初始状态为START
// 开始扫描字符串
// 主循环直到文件结束或状态机中止
while (!ctx.isAtEnd()) {
char c = ctx.advance();
char c = ctx.advance(); // 消耗当前字符
sb.append(c);
switch (currentState) {
case START:
// 开始状态遇到第一个双引号
// 第一个双引号状态切换到STRING
currentState = State.STRING;
break;
case STRING:
if (c == '\\') {
// 遇到转义字符进入 ESCAPE 状态
// 遇到转义切换到ESCAPE状态
currentState = State.ESCAPE;
} else if (c == '"') {
// 遇到结束的双引号结束扫描
currentState = State.END;
// 遇到结束双引号立即返回Token字符串扫描完毕
return new Token(TokenType.STRING_LITERAL, sb.toString(), line, col);
} else if (c == '\n' || c == '\r') {
// 若字符串未闭合且遇到换行提前返回可根据需要抛异常或报错
return new Token(TokenType.STRING_LITERAL, sb.toString(), line, col);
}
// 其他字符保持在STRING状态继续扫描
break;
case ESCAPE:
// 在转义状态下处理转义字符
sb.append(ctx.advance()); // 加入转义字符后的字符
currentState = State.STRING; // 返回字符串状态
// ESCAPE状态下一个字符会作为转义内容无论是"、n、t等
// 注意advance已经处理所以不需要再append
currentState = State.STRING;
break;
case END:
// 结束状态字符串扫描完成
return new Token(TokenType.STRING_LITERAL, sb.toString(), line, col);
}
}
// 如果没有结束的双引号则表示错误或者未正确处理
// 若扫描到文件尾仍未遇到结尾引号则返回当前内容
return new Token(TokenType.STRING_LITERAL, sb.toString(), line, col);
}
// 定义状态枚举
/**
* 状态机枚举类型表示当前字符串解析所处的状态
*/
private enum State {
START, // 开始状态寻找字符串的开始双引号
STRING, // 字符串扫描状态处理字符串中的字符
ESCAPE, // 处理转义字符状态
END // 字符串结束状态
/**
* 起始状态等待遇到第一个双引号
*/
START,
/**
* 字符串内容状态处理实际字符串及普通字符
*/
STRING,
/**
* 处理转义序列状态遇到'\\'后切换到此状态
*/
ESCAPE,
}
}