fix:token修复

This commit is contained in:
Luke 2025-05-16 13:12:33 +08:00
parent dc81131add
commit e023b576c1
5 changed files with 146 additions and 58 deletions

View File

@ -1,15 +1,19 @@
package org.jcnc.snow.compiler.cli; package org.jcnc.snow.compiler.cli;
import org.jcnc.snow.compiler.backend.*; import org.jcnc.snow.compiler.backend.RegisterAllocator;
import org.jcnc.snow.compiler.backend.VMCodeGenerator;
import org.jcnc.snow.compiler.backend.VMProgramBuilder;
import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder; import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder;
import org.jcnc.snow.compiler.ir.core.*; import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRProgram;
import org.jcnc.snow.compiler.lexer.core.LexerEngine; import org.jcnc.snow.compiler.lexer.core.LexerEngine;
import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.parser.ast.base.Node; import org.jcnc.snow.compiler.parser.ast.base.Node;
import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.core.ParserEngine; import org.jcnc.snow.compiler.parser.core.ParserEngine;
import org.jcnc.snow.compiler.semantic.core.SemanticAnalyzerRunner; import org.jcnc.snow.compiler.semantic.core.SemanticAnalyzerRunner;
import org.jcnc.snow.vm.engine.*; import org.jcnc.snow.vm.engine.VMMode;
import org.jcnc.snow.vm.engine.VirtualMachineEngine;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -36,12 +40,12 @@ public class SnowCompiler {
String source = Files.readString(srcPath, StandardCharsets.UTF_8); String source = Files.readString(srcPath, StandardCharsets.UTF_8);
/* 1. 词法分析 */ /* 1. 词法分析 */
LexerEngine lexer = new LexerEngine(source); LexerEngine lexer = new LexerEngine(source, srcPath.toString());
List<Token> tokens = lexer.getAllTokens(); List<Token> tokens = lexer.getAllTokens();
/* 2. 语法分析 */ /* 2. 语法分析 */
ParserContext ctx = new ParserContext(tokens); ParserContext ctx = new ParserContext(tokens);
List<Node> ast = new ParserEngine(ctx).parse(); List<Node> ast = new ParserEngine(ctx).parse();
/* 3. 语义分析 */ /* 3. 语义分析 */
SemanticAnalyzerRunner.runSemanticAnalysis(ast, false); SemanticAnalyzerRunner.runSemanticAnalysis(ast, false);

View File

@ -10,78 +10,102 @@ import java.util.List;
/** /**
* {@code LexerEngine} 是编译器前端的词法分析器核心实现 * {@code LexerEngine} 是编译器前端的词法分析器核心实现
* <p> * <p>
* 负责将源代码字符串按顺序扫描并转换为一系列 {@link Token} 实例 * 负责将源代码字符串按顺序扫描并转换为一系列 {@link Token} 实例
* 每个 Token 表示语法上可识别的最小单位如标识符关键字常量运算符等 * 每个 Token 表示语法上可识别的最小单位如标识符关键字常量运算符等
* <p> * <p>
* 分析流程通过注册多个 {@link TokenScanner} 扫描器实现类型识别 * 分析流程通过注册多个 {@link TokenScanner} 扫描器实现类型识别
* 并由 {@link LexerContext} 提供字符流与位置信息支持 * 并由 {@link LexerContext} 提供字符流与位置信息支持
* 支持文件名传递遇到非法字符时会以文件名:::错误信息输出简洁诊断
* </p> * </p>
*/ */
public class LexerEngine { public class LexerEngine {
/**
/** 扫描生成的 Token 序列(含 EOF */ * 扫描生成的 Token 序列包含文件结束符 EOF
*/
private final List<Token> tokens = new ArrayList<>(); private final List<Token> tokens = new ArrayList<>();
/** 词法上下文,提供字符流读取与位置信息 */ /**
* 词法上下文提供字符流读取与位置信息
*/
private final LexerContext context; private final LexerContext context;
/** Token 扫描器集合,按优先级顺序组织,用于识别不同类别的 Token */ /**
* Token 扫描器集合按优先级顺序组织用于识别不同类别的 Token
*/
private final List<TokenScanner> scanners; private final List<TokenScanner> scanners;
/** /**
* 构造一个 {@code LexerEngine} 实例并初始化内部扫描器与上下文 * 构造词法分析器假定输入源自标准输入文件名默认为 <stdin>
* 调用构造函数时即开始词法扫描生成完整 Token 序列
* *
* @param source 原始源代码文本 * @param source 源代码文本
*/ */
public LexerEngine(String source) { public LexerEngine(String source) {
this.context = new LexerContext(source); this(source, "<stdin>");
}
// 按优先级注册所有支持的 Token 扫描器 /**
* 构造词法分析器并指定源文件名用于诊断信息
* 构造时立即进行全量扫描
*
* @param source 源代码文本
* @param sourceName 文件名或来源描述"main.snow"
*/
public LexerEngine(String source, String sourceName) {
this.context = new LexerContext(source);
this.scanners = List.of( this.scanners = List.of(
new WhitespaceTokenScanner(), // 跳过空格制表符等 new WhitespaceTokenScanner(), // 跳过空格制表符等
new NewlineTokenScanner(), // 处理换行符生成 NEWLINE Token new NewlineTokenScanner(), // 处理换行符生成 NEWLINE Token
new CommentTokenScanner(), // 处理单行/多行注释 new CommentTokenScanner(), // 处理单行/多行注释
new NumberTokenScanner(), // 识别整数与浮点数 new NumberTokenScanner(), // 识别整数与浮点数字面量
new IdentifierTokenScanner(), // 识别标识符与关键字 new IdentifierTokenScanner(), // 识别标识符关键字
new StringTokenScanner(), // 处理字符串常量 new StringTokenScanner(), // 处理字符串常量
new OperatorTokenScanner(), // 处理运算符 new OperatorTokenScanner(), // 识别运算符
new SymbolTokenScanner(), // 处理括号分号等符号 new SymbolTokenScanner(), // 识别括号分号等符号
new UnknownTokenScanner() // 捕捉无法识别的字符 new UnknownTokenScanner() // 捕捉无法识别的字符最后兜底
); );
scanAllTokens(); // 主扫描流程遇到非法字符立即输出错误并终止进程
try {
scanAllTokens();
} catch (LexicalException le) {
// 输出文件名::: 错误信息简洁明了
System.err.printf(
"%s:%d:%d: %s%n",
sourceName,
le.getLine(), // 获取出错行号
le.getColumn(), // 获取出错列号
le.getMessage() // 错误描述
);
System.exit(65); // 65 = EX_DATAERR标准数据错误退出码
}
} }
/** /**
* 执行主扫描流程将整个源代码转换为 Token 序列 * 主扫描循环将源代码转为 Token 序列
* <p> * 依次尝试每个扫描器直到找到可处理当前字符的扫描器为止
* 每次扫描尝试依次使用各个 {@link TokenScanner}直到某一扫描器能够处理当前字符 * 扫描到结尾后补充 EOF Token
* 若无匹配扫描器交由 {@code UnknownTokenScanner} 处理
* 扫描结束后自动附加一个 EOF文件结束Token
* </p>
*/ */
private void scanAllTokens() { private void scanAllTokens() {
while (!context.isAtEnd()) { while (!context.isAtEnd()) {
char currentChar = context.peek(); char currentChar = context.peek();
// 依次查找能处理当前字符的扫描器
for (TokenScanner scanner : scanners) { for (TokenScanner scanner : scanners) {
if (scanner.canHandle(currentChar, context)) { if (scanner.canHandle(currentChar, context)) {
scanner.handle(context, tokens); scanner.handle(context, tokens);
break; break; // 已处理跳到下一个字符
} }
} }
} }
// 末尾补一个 EOF 标记
tokens.add(Token.eof(context.getLine())); tokens.add(Token.eof(context.getLine()));
} }
/** /**
* 返回词法分析生成的所有 Token EOF * 获取全部 Token包含 EOF返回只读列表
* *
* @return Token 的不可变副本列表 * @return 词法分析结果 Token 列表
*/ */
public List<Token> getAllTokens() { public List<Token> getAllTokens() {
return List.copyOf(tokens); return List.copyOf(tokens);
} }
} }

View File

@ -0,0 +1,54 @@
package org.jcnc.snow.compiler.lexer.core;
/**
* 词法异常LexicalException
* <p>
* {@link org.jcnc.snow.compiler.lexer.core.LexerEngine} 在扫描过程中遇到
* 非法或无法识别的字符序列时抛出该异常
* <ul>
* <li>异常消息仅包含一行简明错误信息包含行号与列号</li>
* <li>完全禁止 Java 堆栈信息输出使命令行输出保持整洁</li>
* </ul>
* <pre>
*
* main.s:2:19: Lexical error: Illegal character sequence '@' at 2:19
* </pre>
*/
public class LexicalException extends RuntimeException {
/** 错误发生的行号从1开始 */
private final int line;
/** 错误发生的列号从1开始 */
private final int column;
/**
* 构造词法异常
* @param reason 错误原因非法字符描述
* @param line 出错行号
* @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);
this.line = line;
this.column = column;
}
/**
* 屏蔽异常堆栈填充始终不打印堆栈信息
*/
@Override
public synchronized Throwable fillInStackTrace() { return this; }
/**
* 获取出错的行号
* @return 行号
*/
public int getLine() { return line; }
/**
* 获取出错的列号
* @return 列号
*/
public int getColumn() { return column; }
}

View File

@ -1,25 +1,28 @@
package org.jcnc.snow.compiler.lexer.scanners; package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext; import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.core.LexicalException;
import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/** /**
* 未知符号扫描器兜底处理无法被其他扫描器识别的字符 * 未知 Token 扫描器UnknownTokenScanner
* <p> * <p>
* 用于捕捉非法或未定义的符号序列生成 {@code UNKNOWN} 类型的 Token * 作为所有扫描器的兜底处理器当前字符若不被任何其他扫描器识别
* 由本类处理并抛出 {@link LexicalException}终止词法分析流程
* </p>
* <p> * <p>
* 它会连续读取一段既不是字母数字空白符也不属于常规符号 ;{"、:、,、(、)、.、+、-、*)的字符序列。 * 主要作用保证所有非法不可识别的字符@$等不会被静默跳过或误当作合法 Token
* 而是在词法阶段立刻定位并报错有助于尽早发现源代码问题
* </p>
*/ */
public class UnknownTokenScanner extends AbstractTokenScanner { public class UnknownTokenScanner extends AbstractTokenScanner {
/** /**
* 始终返回 true作为所有扫描器中的兜底处理器 * 判断是否可以处理当前字符
* <p>当没有其他扫描器能够处理当前字符时使用本扫描器</p> * 对于 UnknownTokenScanner始终返回 true兜底扫描器必须排在扫描器链末尾
* * @param c 当前待处理字符
* @param c 当前字符 * @param ctx 词法上下文
* @param ctx 当前词法上下文 * @return 是否处理该字符始终为 true
* @return 总是返回 true
*/ */
@Override @Override
public boolean canHandle(char c, LexerContext ctx) { public boolean canHandle(char c, LexerContext ctx) {
@ -27,23 +30,26 @@ public class UnknownTokenScanner extends AbstractTokenScanner {
} }
/** /**
* 扫描未知或非法的字符序列 * 实际处理非法字符序列的方法
* <p>跳过字母数字空白和已知符号仅捕获无法识别的符号块</p> * 连续读取所有无法被其他扫描器识别的字符组成错误片段并抛出异常
* * @param ctx 词法上下文
* @param ctx 词法上下文 * @param line 错误发生行号
* @param line 当前行 * @param col 错误发生列
* @param col 当前列号 * @return 不会返回Token始终抛异常
* @return {@code UNKNOWN} 类型的 Token包含无法识别的字符内容 * @throws LexicalException 非法字符导致的词法错误
*/ */
@Override @Override
protected Token scanToken(LexerContext ctx, int line, int col) { protected Token scanToken(LexerContext ctx, int line, int col) {
String lexeme = readWhile(ctx, c -> // 读取一段非法字符既不是字母数字也不是常见符号
!Character.isLetterOrDigit(c) && String lexeme = readWhile(ctx, ch ->
!Character.isWhitespace(c) && !Character.isLetterOrDigit(ch) &&
c != ';' && c != '{' && c != '"' && !Character.isWhitespace(ch) &&
":,().+-*".indexOf(c) < 0 ":,().+-*{};\"".indexOf(ch) < 0
); );
// 如果没读到任何字符则把当前字符单独作为非法片段
return new Token(TokenType.UNKNOWN, lexeme, line, col); if (lexeme.isEmpty())
lexeme = String.valueOf(ctx.advance());
// 抛出词法异常并带上错误片段与具体位置
throw new LexicalException("Illegal character sequence '" + lexeme + "'", line, col);
} }
} }

2
test
View File

@ -1,5 +1,5 @@
module: CommonTasks module: CommonTasks
function: main function: main@
parameter: parameter:
return_type:int return_type:int