refactor: 重构为基于有限状态机(FSM)的注释解析器

This commit is contained in:
Luke 2025-07-01 00:15:14 +08:00
parent 30b89c0f3d
commit c88404fada

View File

@ -1,29 +1,31 @@
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.TokenType; import org.jcnc.snow.compiler.lexer.token.TokenType;
/** /**
* 注释扫描器处理源代码中的注释部分包括 * {@code CommentTokenScanner} 注释解析器基于有限状态机FSM
*
* <p>负责将源码中的两种注释形式切分为 {@link TokenType#COMMENT COMMENT} token</p>
* <ol>
* <li>单行注释 {@code //} 开头直至行尾或文件末尾</li>
* 多行注释 {@code /*} 开头 <code>*&#47;</code> 结束可跨多行
* </ol>
*
* <p>本扫描器遵循发现即捕获原则
* 注释文本被完整保留在 Token 供后续的文档提取源映射等分析使用</p>
*
* <p>错误处理策略</p>
* <ul> * <ul>
* <li>单行注释 "//" 开头直到行尾</li> * <li>未终止的多行注释若文件结束时仍未遇到 <code>*&#47;</code>抛出 {@link LexicalException}</li>
* <li>多行注释 "/*" 开头 "*&#47;" 结尾</li>
* </ul> * </ul>
* <p>
* 本扫描器会识别注释并生成 {@code TokenType.COMMENT} 类型的 Token
* 不会丢弃注释内容而是将完整注释文本保留在 Token 便于后续分析如文档提取保留注释等场景
* </p>
*/ */
public class CommentTokenScanner extends AbstractTokenScanner { public class CommentTokenScanner extends AbstractTokenScanner {
/** /**
* 判断是否可以处理当前位置的字符 * 仅当当前字符为 {@code '/'} 且下一个字符为 {@code '/'} {@code '*'} 由本扫描器处理
* <p>当当前位置字符为 '/' 且下一个字符为 '/' '*' 表示可能是注释的起始</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果是注释的起始符则返回 true
*/ */
@Override @Override
public boolean canHandle(char c, LexerContext ctx) { public boolean canHandle(char c, LexerContext ctx) {
@ -31,44 +33,54 @@ public class CommentTokenScanner extends AbstractTokenScanner {
} }
/** /**
* 实现注释的扫描逻辑 * 执行注释扫描生成 {@code COMMENT} Token
* <p>支持两种注释格式</p>
* <ul>
* <li><b>单行注释</b> "//" 开头直到遇到换行符</li>
* <li><b>多行注释</b> "/*" 开头直到遇到 "*&#47;" 结束</li>
* </ul>
* *
* @param ctx 词法上下文 * @param ctx 词法上下文
* @param line 当前行号用于 Token 位置信息 * @param line 起始行号1
* @param col 当前列号用于 Token 位置信息 * @param col 起始列号1
* @return 包含完整注释内容的 COMMENT 类型 Token * @return 包含完整注释文本的 Token
* @throws LexicalException 若遇到未终止的多行注释
*/ */
@Override @Override
protected Token scanToken(LexerContext ctx, int line, int col) { protected Token scanToken(LexerContext ctx, int line, int col) {
// 消费第一个 '/' 字符 StringBuilder literal = new StringBuilder();
ctx.advance();
StringBuilder sb = new StringBuilder("/");
// 处理单行注释 // /*
* 1. 读取注释起始符
* - 已由 canHandle 保证当前位置一定是 '/'
*/
literal.append(ctx.advance()); // 消费首个 '/'
// -------- 单行注释 (//) --------
if (ctx.match('/')) { if (ctx.match('/')) {
sb.append('/'); literal.append('/');
while (!ctx.isAtEnd() && ctx.peek() != '\n') { while (!ctx.isAtEnd() && ctx.peek() != '\n') {
sb.append(ctx.advance()); literal.append(ctx.advance());
} }
// 行尾或文件尾时退出换行符留给上层扫描器处理
} }
// 处理多行注释 /* ... */ // -------- 多行注释 (/* ... */) --------
else if (ctx.match('*')) { else if (ctx.match('*')) {
sb.append('*'); literal.append('*');
boolean terminated = false;
while (!ctx.isAtEnd()) { while (!ctx.isAtEnd()) {
char ch = ctx.advance(); char ch = ctx.advance();
sb.append(ch); literal.append(ch);
if (ch == '*' && ctx.peek() == '/') { if (ch == '*' && ctx.peek() == '/') {
sb.append(ctx.advance()); // 消费 '/' literal.append(ctx.advance()); // 追加 '/'
terminated = true;
break; break;
} }
} }
if (!terminated) {
// 文件结束仍未闭合 LexicalException
throw new LexicalException("未终止的多行注释", line, col);
}
} }
return new Token(TokenType.COMMENT, sb.toString(), line, col); /*
* 2. 生成并返回 Token
*/
return new Token(TokenType.COMMENT, literal.toString(), line, col);
} }
} }