diff --git a/playground/BugFarm/Bug1/Main.snow b/playground/BugFarm/Bug1/Main.snow index d9a3e6e..3dae6e6 100644 --- a/playground/BugFarm/Bug1/Main.snow +++ b/playground/BugFarm/Bug1/Main.snow @@ -1,7 +1,7 @@ function: main return_type: int body: - 3 L - return 65537 + declare num1 :int = 3.1 G + return 65537 end body end function \ No newline at end of file diff --git a/playground/Demo1/Math.snow b/playground/Demo1/Math.snow index bfe8605..973f7c9 100644 --- a/playground/Demo1/Math.snow +++ b/playground/Demo1/Math.snow @@ -1,8 +1,8 @@ module: Math function: factorial parameter: - declare n1: long - declare n2: long + declare n1: int + declare n2: int return_type: long body: return n1+n2 diff --git a/src/main/java/org/jcnc/snow/cli/SnowCLI.java b/src/main/java/org/jcnc/snow/cli/SnowCLI.java index 03a4391..0afa4e4 100644 --- a/src/main/java/org/jcnc/snow/cli/SnowCLI.java +++ b/src/main/java/org/jcnc/snow/cli/SnowCLI.java @@ -91,7 +91,7 @@ public class SnowCLI { System.exit(exitCode); } catch (Exception e) { // 捕获命令执行过程中的异常并打印错误消息 - System.err.println("Error: " + e.getMessage()); +// System.err.println("Error: " + e.getMessage()); System.exit(1); } } 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 4d06999..060d3c3 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 @@ -4,6 +4,7 @@ import org.jcnc.snow.compiler.lexer.base.TokenScanner; import org.jcnc.snow.compiler.lexer.scanners.*; import org.jcnc.snow.compiler.lexer.token.Token; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -24,6 +25,7 @@ public class LexerEngine { */ private final List tokens = new ArrayList<>(); + private final String absPath; /** * 词法上下文,提供字符流读取与位置信息 */ @@ -34,6 +36,8 @@ public class LexerEngine { */ private final List scanners; + private final List errors = new ArrayList<>(); + /** * 构造词法分析器(假定输入源自标准输入,文件名默认为 ) * @@ -43,6 +47,7 @@ public class LexerEngine { this(source, ""); } + /** * 构造词法分析器,并指定源文件名(用于诊断信息)。 * 构造时立即进行全量扫描。 @@ -51,6 +56,7 @@ public class LexerEngine { * @param sourceName 文件名或来源描述(如"Main.snow") */ public LexerEngine(String source, String sourceName) { + this.absPath = new File(sourceName).getAbsolutePath(); this.context = new LexerContext(source); this.scanners = List.of( new WhitespaceTokenScanner(), // 跳过空格、制表符等 @@ -68,15 +74,28 @@ public class LexerEngine { try { scanAllTokens(); } catch (LexicalException le) { - // 输出:文件名:行:列: 错误信息,简洁明了 + // 输出:绝对路径: 行 x, 列 y: 错误信息 System.err.printf( - "%s:%d:%d: %s%n", - sourceName, - le.getLine(), // 获取出错行号 - le.getColumn(), // 获取出错列号 - le.getMessage() // 错误描述 + "%s: 行 %d, 列 %d: %s%n", + absPath, + le.getLine(), + le.getColumn(), + le.getReason() ); - System.exit(65); // 65 = EX_DATAERR,标准数据错误退出码 + System.exit(65); // 65 = EX_DATAERR + } + LexerEngine.report(this.getErrors()); + } + + /** + * 静态报告方法 + */ + public static void report(List errors) { + if (errors != null && !errors.isEmpty()) { + System.err.println("\n词法分析发现 " + errors.size() + " 个错误:"); + errors.forEach(err -> System.err.println(" " + err)); + } else { + System.out.println("## 词法分析通过,没有发现错误\n"); } } @@ -88,15 +107,28 @@ public class LexerEngine { private void scanAllTokens() { while (!context.isAtEnd()) { char currentChar = context.peek(); - // 依次查找能处理当前字符的扫描器 + boolean handled = false; for (TokenScanner scanner : scanners) { if (scanner.canHandle(currentChar, context)) { - scanner.handle(context, tokens); - break; // 已处理,跳到下一个字符 + try { + scanner.handle(context, tokens); + } catch (LexicalException le) { + // 收集词法错误,不直接退出 + errors.add(new LexicalError( + absPath, le.getLine(), le.getColumn(), le.getReason() + )); + // 跳过当前字符,防止死循环 + context.advance(); + } + handled = true; + break; } } + if (!handled) { + // 万一没有任何扫描器能处理,跳过一个字符防止死循环 + context.advance(); + } } - // 末尾补一个 EOF 标记 tokens.add(Token.eof(context.getLine())); } @@ -108,4 +140,11 @@ public class LexerEngine { public List getAllTokens() { return List.copyOf(tokens); } + + /** + * 返回全部词法错误 + */ + public List getErrors() { + return List.copyOf(errors); + } } diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalError.java b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalError.java new file mode 100644 index 0000000..81ac9ee --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalError.java @@ -0,0 +1,20 @@ +package org.jcnc.snow.compiler.lexer.core; + +public class LexicalError { + private final String file; + private final int line; + private final int column; + private final String message; + + public LexicalError(String file, int line, int column, String message) { + this.file = file; + this.line = line; + this.column = column; + this.message = message; + } + + @Override + public String toString() { + return file + ": 行 " + line + ", 列 " + column + ": " + message; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalException.java b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalException.java index 96cfc0c..cdd01cb 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalException.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexicalException.java @@ -19,6 +19,8 @@ public class LexicalException extends RuntimeException { private final int line; /** 错误发生的列号(从1开始) */ private final int column; + /** 错误原因 */ + private final String reason; /** * 构造词法异常 @@ -27,13 +29,14 @@ public class LexicalException extends RuntimeException { * @param column 出错列号 */ public LexicalException(String reason, int line, int column) { - // 构造出错消息,并禁止异常堆栈打印 - super(String.format("Lexical error: %s at %d:%d", reason, line, column), - null, false, false); + // 错误描述直接为 reason,禁止异常堆栈打印 + super(reason, null, false, false); + this.reason = reason; this.line = line; this.column = column; } + /** * 屏蔽异常堆栈填充(始终不打印堆栈信息) */ @@ -51,4 +54,10 @@ public class LexicalException extends RuntimeException { * @return 列号 */ public int getColumn() { return column; } + + /** + * 获取出错的描述 + * @return 出错描述 + */ + public String getReason() { return reason; } } diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java index ac598de..084c8f7 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java @@ -35,51 +35,56 @@ import org.jcnc.snow.compiler.lexer.token.TokenType; */ public class NumberTokenScanner extends AbstractTokenScanner { - /** 合法类型后缀字符集合 */ + /** 合法类型后缀字符集合(单字符,大小写均可) */ private static final String SUFFIX_CHARS = "bslfdBSLFD"; @Override public boolean canHandle(char c, LexerContext ctx) { + // 仅当遇到数字时,本扫描器才处理 return Character.isDigit(c); } @Override protected Token scanToken(LexerContext ctx, int line, int col) { StringBuilder literal = new StringBuilder(); - boolean hasDot = false; // 是否已遇到小数点 + boolean hasDot = false; // 标记是否已出现过小数点 - /* 1. 读取数字主体(整数 / 小数) */ + /* 1. 读取数字主体部分(包括整数、小数) */ while (!ctx.isAtEnd()) { char c = ctx.peek(); if (c == '.' && !hasDot) { + // 遇到第一个小数点 hasDot = true; literal.append(ctx.advance()); } else if (Character.isDigit(c)) { + // 吸收数字字符 literal.append(ctx.advance()); } else { + // 非数字/非小数点,终止主体读取 break; } } - /* 2. 处理后缀或非法跟随字符 */ + /* 2. 检查数字字面量后的字符,决定是否继续吸收或抛出异常 */ if (!ctx.isAtEnd()) { char next = ctx.peek(); - /* 2-A: 合法类型后缀,直接吸收 */ + /* 2-A: 合法类型后缀,直接吸收(如 42L、3.0F) */ if (SUFFIX_CHARS.indexOf(next) >= 0) { literal.append(ctx.advance()); } - /* 2-B: 未知字母紧邻 → 抛异常 */ + /* 2-B: 若紧跟未知字母(如 42X),抛出词法异常 */ else if (Character.isLetter(next)) { throw new LexicalException( - "Unknown numeric suffix '" + next + "'", + "未知的数字类型后缀 '" + next + "'", line, col ); } - /* 2-C: 数字后空白(非换行)→ 若空白后跟字母,抛异常 */ + /* 2-C: 若数字后有空白,且空白后紧跟字母(如 3 L),也为非法 */ else if (Character.isWhitespace(next) && next != '\n') { int off = 1; char look; + // 跳过所有空白字符,找到第一个非空白字符 do { look = ctx.peekAhead(off); if (look == '\n' || look == '\0') break; @@ -88,20 +93,21 @@ public class NumberTokenScanner extends AbstractTokenScanner { } while (true); if (Character.isLetter(look)) { + // 抛出:数字字面量与位宽符号之间不允许有空白符 throw new LexicalException( - "Whitespace between numeric literal and an alphabetic character is not allowed", + "数字字面量与位宽符号之间不允许有空白符", line, col ); } } - /* 2-D: 紧邻字符为 '/' → 抛异常以避免死循环 */ + /* 2-D: 若紧跟 '/',抛出异常防止死循环 */ else if (next == '/') { throw new LexicalException( - "Unexpected '/' after numeric literal", + "数字字面量后不允许直接出现 '/'", line, col ); } - /* 其余字符(运算符、分隔符等)留给后续扫描器处理 */ + // 其余情况(如分号、括号、运算符),交由其他扫描器处理 } /* 3. 返回 NUMBER_LITERAL Token */ diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/utils/SemanticAnalysisReporter.java b/src/main/java/org/jcnc/snow/compiler/semantic/utils/SemanticAnalysisReporter.java index dc2b336..91b4487 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/utils/SemanticAnalysisReporter.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/utils/SemanticAnalysisReporter.java @@ -28,7 +28,7 @@ public final class SemanticAnalysisReporter { System.err.println("语义分析发现 " + errors.size() + " 个错误:"); errors.forEach(err -> System.err.println(" " + err)); } else { -// System.out.println("## 语义分析通过,没有发现错误\n"); + System.out.println("\n## 语义分析通过,没有发现错误\n"); } }