diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/StringTokenScanner.java b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/StringTokenScanner.java index 8c696e8..e4c441c 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/StringTokenScanner.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/StringTokenScanner.java @@ -5,91 +5,118 @@ import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.TokenType; /** - * 字符串扫描器: 处理双引号包裹的字符串字面量,支持基本的转义字符。 + * 字符串扫描器(StringTokenScanner)用于处理由双引号包裹的字符串字面量, + * 并支持常见转义字符的解析。该扫描器采用有限状态机(状态机)实现, + * 保证对各类字符串格式进行准确的词法分析。 *
- * 支持格式示例: + * 主要支持如下字符串样式: *
- * 扫描器会保留原始字符串的形式(包含双引号和转义符), - * 并生成 {@code STRING_LITERAL} 类型的 Token。 + * 扫描器会保留字符串的原始形式(含双引号和转义符), + * 并返回{@link TokenType#STRING_LITERAL}类型的Token。 + *
+ *+ * 状态机说明: + *
当字符为双引号(")时,认为是字符串字面量的开始。
+ * 判断当前位置的字符是否为字符串起始符号。 + *+ * 只有遇到双引号(")时,才由本扫描器处理。 + *
* - * @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 == '"'; // 只处理双引号起始 } /** - * 执行字符串的扫描逻辑。 - *从当前位置开始,读取直到匹配结束的双引号。 - * 支持转义字符(如 \"、\\n 等),不会中断字符串扫描。
+ * 执行字符串字面量的具体扫描过程。采用有限状态机处理逻辑, + * 从起始的双引号开始,逐字符处理字符串内容,支持基本转义序列(如 \\n、\\t、\\\" 等)。 + *+ * 扫描遇到未转义的结尾双引号时即结束,并立即返回Token。 + * 如果遇到换行或文件结束但未遇到结尾引号,视为字符串未闭合,仍返回已扫描内容。 + *
* - * @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, } }