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