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);