diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java b/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java index 2788300..c064476 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java @@ -6,37 +6,49 @@ import org.jcnc.snow.compiler.lexer.token.TokenType; import java.util.List; /** - * {@code TokenStream} 封装了一个 Token 列表并维护当前解析位置, - * 是语法分析器读取词法单元的核心工具类。 - *

- * 提供前瞻(peek)、消费(next)、匹配(match)、断言(expect)等常用操作, - * 支持前向查看和异常处理,适用于递归下降解析等常见语法构建策略。 - *

+ * {@code TokenStream} 封装了一个 Token 列表并维护当前解析位置,是语法分析器读取词法单元的核心工具类。 + * + *

提供前瞻(peek)、消费(next)、匹配(match)、断言(expect)等常用操作, + * 支持前向查看和异常处理,适用于递归下降解析等常见语法构建策略。

+ * */ public class TokenStream { - /** 源 Token 列表 */ + /** + * 源 Token 列表。 + */ private final List tokens; - /** 当前解析位置索引 */ + /** + * 当前解析位置索引。 + */ private int pos = 0; /** * 使用 Token 列表构造 TokenStream。 * * @param tokens 由词法分析器产生的 Token 集合 + * @throws NullPointerException 如果 tokens 为 null */ public TokenStream(List tokens) { + if (tokens == null) { + throw new NullPointerException("Token list cannot be null."); + } this.tokens = tokens; } /** * 向前查看指定偏移量处的 Token(不移动位置)。 + * 会在 offset==0 时自动跳过当前位置的所有注释(COMMENT)token。 * - * @param offset 相对当前位置的偏移量(0 表示当前) + * @param offset 相对当前位置的偏移量(0 表示当前 token) * @return 指定位置的 Token;若越界则返回自动构造的 EOF Token */ public Token peek(int offset) { + // 只在 offset==0 时跳注释,向前多步 peek 由调用方控制 + if (offset == 0) { + skipTrivia(); + } int idx = pos + offset; if (idx >= tokens.size()) { return Token.eof(tokens.size() + 1); @@ -47,28 +59,30 @@ public class TokenStream { /** * 查看当前位置的 Token,等效于 {@code peek(0)}。 * - * @return 当前 Token + * @return 当前有效 Token(已跳过注释) */ public Token peek() { + skipTrivia(); return peek(0); } /** - * 消费当前位置的 Token 并返回,位置前移。 + * 消费当前位置的 Token 并返回,位置前移。注释 token 会被自动跳过。 * - * @return 当前 Token + * @return 被消费的有效 Token(已跳过注释) */ public Token next() { - Token t = peek(); - pos++; + Token t = peek(); // peek() 已跳过注释 + pos++; // 指针指向下一个 raw token + skipTrivia(); // 立即吞掉紧随其后的注释(若有) return t; } /** - * 匹配当前 Token 的词素与指定字符串,若匹配则消费。 + * 匹配当前 Token 的词素与指定字符串,若匹配则消费该 token 并前移指针。 * - * @param lexeme 待匹配词素 - * @return 若成功匹配则返回 true + * @param lexeme 待匹配的词素字符串 + * @return 匹配成功返回 true,否则返回 false */ public boolean match(String lexeme) { if (peek().getLexeme().equals(lexeme)) { @@ -80,6 +94,7 @@ public class TokenStream { /** * 断言当前 Token 的词素与指定值相符,否则抛出 {@link ParseException}。 + * 匹配成功会消费该 token 并前移指针。 * * @param lexeme 期望的词素值 * @return 匹配成功的 Token @@ -98,6 +113,7 @@ public class TokenStream { /** * 断言当前 Token 类型为指定类型,否则抛出 {@link ParseException}。 + * 匹配成功会消费该 token 并前移指针。 * * @param type 期望的 Token 类型 * @return 匹配成功的 Token @@ -115,13 +131,37 @@ public class TokenStream { } /** - * 判断是否“已经”到达 EOF。 + * 判断是否“已经”到达文件末尾(EOF)。 * - * @return 若当前位置 Token 为 EOF,则返回 true,否则 false + * @return 若当前位置 Token 为 EOF,则返回 true,否则返回 false */ public boolean isAtEnd() { return peek().getType() == TokenType.EOF; } - -} \ No newline at end of file + /** + * 跳过所有连续的注释(COMMENT)token。 + * + *

+ * 此方法会检查当前指针 pos 所指向的 token, + * 如果其类型为 TokenType.COMMENT,则直接将指针递增, + * 直到遇到非 COMMENT 类型或到达 token 列表末尾。 + *

+ * + *

+ * 注意:此方法只会跳过注释,不会递归或调用任何 + * 会产生递归的方法(如 peek()/next()),以避免堆栈溢出。 + *

+ * + *

+ * 使用场景:词法分析产物中允许出现注释 token,语法分析时需要自动跳过它们, + * 保证 parser 只处理有效语法 token。 + *

+ */ + private void skipTrivia() { + while (pos < tokens.size() + && tokens.get(pos).getType() == TokenType.COMMENT) { + pos++; // 直接跳过 COMMENT 类型 + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java index 14dc783..7d0854f 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java @@ -17,7 +17,7 @@ public record ParserEngine(ParserContext ctx) { List errs = new ArrayList<>(); TokenStream ts = ctx.getTokens(); - while (!ts.isAtEnd()) { + while (ts.isAtEnd()) { // 跳过空行 if (ts.peek().getType() == TokenType.NEWLINE) { ts.next(); @@ -46,7 +46,7 @@ public record ParserEngine(ParserContext ctx) { * 错误同步:跳到下一行或下一个已注册顶层关键字 */ private void synchronize(TokenStream ts) { - while (!ts.isAtEnd()) { + while (ts.isAtEnd()) { if (ts.peek().getType() == TokenType.NEWLINE) { ts.next(); break; @@ -57,7 +57,7 @@ public record ParserEngine(ParserContext ctx) { ts.next(); } // 连续空行全部吃掉 - while (!ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) { + while (ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) { ts.next(); } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java index c5529d4..4f79274 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java @@ -92,7 +92,7 @@ public class PrattExpressionParser implements ExpressionParser { ExpressionNode left = prefix.parse(ctx, token); - while (!ctx.getTokens().isAtEnd() + while (ctx.getTokens().isAtEnd() && prec.ordinal() < nextPrecedence(ctx)) { String lex = ctx.getTokens().peek().getLexeme(); InfixParselet infix = infixes.get(lex);