diff --git a/playground/main.snow b/playground/Main.snow similarity index 100% rename from playground/main.snow rename to playground/Main.snow diff --git a/playground/test.snow b/playground/Math.snow similarity index 86% rename from playground/test.snow rename to playground/Math.snow index 161b7b2..e79d078 100644 --- a/playground/test.snow +++ b/playground/Math.snow @@ -2,7 +2,7 @@ module: Math function: factorial parameter: declare n1: long - declare n2: long + declare n2: int return_type: long body: return n1+n2 diff --git a/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java b/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java index 6c2b335..fa0831a 100644 --- a/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java +++ b/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java @@ -59,7 +59,7 @@ public class SnowCompiler { System.out.println(code); LexerEngine lexer = new LexerEngine(code, p.toString()); - ParserContext ctx = new ParserContext(lexer.getAllTokens()); + ParserContext ctx = new ParserContext(lexer.getAllTokens(), p.toString()); allAst.addAll(new ParserEngine(ctx).parse()); } diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java index 819fa07..4d06999 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java @@ -48,7 +48,7 @@ public class LexerEngine { * 构造时立即进行全量扫描。 * * @param source 源代码文本 - * @param sourceName 文件名或来源描述(如"main.snow") + * @param sourceName 文件名或来源描述(如"Main.snow") */ public LexerEngine(String source, String sourceName) { this.context = new LexerContext(source); diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/CallExpressionNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/CallExpressionNode.java index 593658e..ac36871 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/ast/CallExpressionNode.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/CallExpressionNode.java @@ -19,7 +19,8 @@ public record CallExpressionNode( ExpressionNode callee, // 被调用的表达式节点,表示函数或方法名 List arguments, // 函数调用的参数表达式列表 int line, // 当前节点所在的行号 - int column // 当前节点所在的列号 + int column, // 当前节点所在的列号 + String file // 当前节点所在的文件 ) implements ExpressionNode { /** @@ -59,4 +60,11 @@ public record CallExpressionNode( public int column() { return column; } + + /** + * 获取当前表达式所在的文件名。 + * + * @return 当前表达式所在的文件名。 + */ + public String file() { return file; } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/ParserContext.java b/src/main/java/org/jcnc/snow/compiler/parser/context/ParserContext.java index bbf2b2e..5ff228a 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/context/ParserContext.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/ParserContext.java @@ -1,6 +1,8 @@ package org.jcnc.snow.compiler.parser.context; import org.jcnc.snow.compiler.lexer.token.Token; + +import java.nio.file.Paths; import java.util.List; /** @@ -16,15 +18,27 @@ public class ParserContext { /** 当前语法分析所使用的 Token 流 */ private final TokenStream tokens; + /** 当前语法分析所使用的资源文件名 */ + private final String sourceName; + + /** - * 使用词法分析得到的 Token 列表构造上下文。 + * 构造一个新的 {@code ParserContext} 实例,用于在语法分析阶段传递上下文信息。 + *

+ * 本构造方法接收词法分析得到的 {@link Token} 列表以及当前源文件名,
+ * 并将 Token 列表包装为 {@link TokenStream} 以便后续遍历与分析。
+ * 源文件名通常用于错误定位、调试和报错信息中指明具体文件。 + *

* - * @param tokens 词法分析器生成的 Token 集合 + * @param tokens 词法分析器生成的 Token 集合,表示待解析的完整源代码流 + * @param sourceName 当前正在解析的源文件名(或文件路径),用于错误报告和调试定位 */ - public ParserContext(List tokens) { + public ParserContext(List tokens, String sourceName) { this.tokens = new TokenStream(tokens); + this.sourceName = Paths.get(sourceName).toAbsolutePath().toString(); } + /** * 获取封装的 Token 流,用于驱动语法分析过程。 * @@ -33,4 +47,13 @@ public class ParserContext { public TokenStream getTokens() { return tokens; } + + /** + * 获取资源文件名,用于发现错误后展示文件名。 + * + * @return 当前语法分析所使用的资源文件名 + */ + public String getSourceName() { + return sourceName; + } } \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/CallParselet.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/CallParselet.java index eb47b86..9499f6d 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/expression/CallParselet.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/CallParselet.java @@ -43,8 +43,9 @@ public class CallParselet implements InfixParselet { ctx.getTokens().expect(")"); // 消费并验证 ")" - // 创建 CallExpressionNode 并传递位置信息 - return new CallExpressionNode(left, args, line, column); + // 创建 CallExpressionNode 并传递位置信息,文件名称 + String file = ctx.getSourceName(); + return new CallExpressionNode(left, args, line, column, file); } /** diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java b/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java index 121d0eb..113777e 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java @@ -1,32 +1,65 @@ package org.jcnc.snow.compiler.semantic.error; - import org.jcnc.snow.compiler.parser.ast.base.Node; /** - * 表示一次语义错误。
+ * 表示一次语义错误(Semantic Error)。 + *

+ * 本类用于在语义分析阶段记录出错的 AST 节点及对应的错误信息,
+ * 便于后续错误报告、调试和 IDE 集成等多种用途。 + *

*
    - *
  • 记录对应 {@link Node} 及出错信息;
  • - *
  • 重写 {@link #toString()},以 行 X, 列 Y: message 格式输出,
  • - *
  • 避免默认的 Node@hash 形式。
  • + *
  • 通过关联的 {@link Node} 提供出错的具体位置(文件、行号、列号等)信息;
  • + *
  • 支持格式化错误输出,友好展示错误发生的上下文;
  • + *
  • 避免直接输出 AST 节点的默认 toString() 形式。
  • *
+ * + *

示例输出:

+ *
+ *   D:\Devs\IdeaProjects\Snow\playground\Main.snow: 行 7, 列 28: 参数类型不匹配 (位置 1): 期望 int, 实际 long
+ * 
+ * + * @param node 指向发生语义错误的 AST 节点,可用于获取详细的位置信息(文件名、行号、列号等) + * @param message 描述该语义错误的详细信息,通常为人类可读的解释或修正建议 + * */ public record SemanticError(Node node, String message) { + /** + * 返回该语义错误的字符串描述,格式如下: + *
+     * [文件绝对路径: ]行 X, 列 Y: [错误信息]
+     * 
+ * 若节点未能提供有效位置,则输出“未知位置”。 + * + * @return 适合用户阅读的语义错误描述字符串 + */ @Override public String toString() { // Node 假定提供 line() / column() 方法;如无则返回 -1 int line = -1; int col = -1; + String file = null; + if (node != null) { try { line = (int) node.getClass().getMethod("line").invoke(node); + } catch (Exception ignored) { + } + try { col = (int) node.getClass().getMethod("column").invoke(node); - } catch (ReflectiveOperationException ignored) { - // 若 Node 未提供 line/column 方法则保持 -1 + } catch (Exception ignored) { + } + try { + file = (String) node.getClass().getMethod("file").invoke(node); + } catch (Exception ignored) { } } - String pos = (line >= 0 && col >= 0) ? ("行 " + line + ", 列 " + col) : "未知位置"; - return pos + ": " + message; + + StringBuilder sb = new StringBuilder(); + if (file != null && !file.isBlank()) sb.append(file).append(": "); + sb.append((line >= 0 && col >= 0) ? "行 " + line + ", 列 " + col : "未知位置"); + sb.append(": ").append(message); + return sb.toString(); } }