fix: 数字字面量与位宽符号之间不允许有空白符
This commit is contained in:
parent
6a247f456c
commit
3eacdf6d39
@ -1,7 +1,7 @@
|
||||
function: main
|
||||
return_type: int
|
||||
body:
|
||||
3 L
|
||||
declare num1 :int = 3.1 G
|
||||
return 65537
|
||||
end body
|
||||
end function
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Token> tokens = new ArrayList<>();
|
||||
|
||||
private final String absPath;
|
||||
/**
|
||||
* 词法上下文,提供字符流读取与位置信息
|
||||
*/
|
||||
@ -34,6 +36,8 @@ public class LexerEngine {
|
||||
*/
|
||||
private final List<TokenScanner> scanners;
|
||||
|
||||
private final List<LexicalError> errors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 构造词法分析器(假定输入源自标准输入,文件名默认为 <stdin>)
|
||||
*
|
||||
@ -43,6 +47,7 @@ public class LexerEngine {
|
||||
this(source, "<stdin>");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造词法分析器,并指定源文件名(用于诊断信息)。
|
||||
* 构造时立即进行全量扫描。
|
||||
@ -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<LexicalError> 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)) {
|
||||
try {
|
||||
scanner.handle(context, tokens);
|
||||
break; // 已处理,跳到下一个字符
|
||||
} 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<Token> getAllTokens() {
|
||||
return List.copyOf(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回全部词法错误
|
||||
*/
|
||||
public List<LexicalError> getErrors() {
|
||||
return List.copyOf(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user