fix:token修复
This commit is contained in:
parent
dc81131add
commit
e023b576c1
@ -1,15 +1,19 @@
|
||||
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.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.token.Token;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.Node;
|
||||
import org.jcnc.snow.compiler.parser.context.ParserContext;
|
||||
import org.jcnc.snow.compiler.parser.core.ParserEngine;
|
||||
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.nio.charset.StandardCharsets;
|
||||
@ -36,12 +40,12 @@ public class SnowCompiler {
|
||||
String source = Files.readString(srcPath, StandardCharsets.UTF_8);
|
||||
|
||||
/* 1. 词法分析 */
|
||||
LexerEngine lexer = new LexerEngine(source);
|
||||
LexerEngine lexer = new LexerEngine(source, srcPath.toString());
|
||||
List<Token> tokens = lexer.getAllTokens();
|
||||
|
||||
/* 2. 语法分析 */
|
||||
ParserContext ctx = new ParserContext(tokens);
|
||||
List<Node> ast = new ParserEngine(ctx).parse();
|
||||
List<Node> ast = new ParserEngine(ctx).parse();
|
||||
|
||||
/* 3. 语义分析 */
|
||||
SemanticAnalyzerRunner.runSemanticAnalysis(ast, false);
|
||||
|
||||
@ -10,78 +10,102 @@ import java.util.List;
|
||||
/**
|
||||
* {@code LexerEngine} 是编译器前端的词法分析器核心实现。
|
||||
* <p>
|
||||
* 它负责将源代码字符串按顺序扫描并转换为一系列 {@link Token} 实例,
|
||||
* 负责将源代码字符串按顺序扫描并转换为一系列 {@link Token} 实例,
|
||||
* 每个 Token 表示语法上可识别的最小单位(如标识符、关键字、常量、运算符等)。
|
||||
* <p>
|
||||
* 分析流程通过注册多个 {@link TokenScanner} 扫描器实现类型识别,
|
||||
* 并由 {@link LexerContext} 提供字符流与位置信息支持。
|
||||
* 支持文件名传递,遇到非法字符时会以“文件名:行:列:错误信息”输出简洁诊断。
|
||||
* </p>
|
||||
*/
|
||||
public class LexerEngine {
|
||||
|
||||
/** 扫描生成的 Token 序列(含 EOF) */
|
||||
/**
|
||||
* 扫描生成的 Token 序列(包含文件结束符 EOF)
|
||||
*/
|
||||
private final List<Token> tokens = new ArrayList<>();
|
||||
|
||||
/** 词法上下文,提供字符流读取与位置信息 */
|
||||
/**
|
||||
* 词法上下文,提供字符流读取与位置信息
|
||||
*/
|
||||
private final LexerContext context;
|
||||
|
||||
/** Token 扫描器集合,按优先级顺序组织,用于识别不同类别的 Token */
|
||||
/**
|
||||
* Token 扫描器集合,按优先级顺序组织,用于识别不同类别的 Token
|
||||
*/
|
||||
private final List<TokenScanner> scanners;
|
||||
|
||||
/**
|
||||
* 构造一个 {@code LexerEngine} 实例,并初始化内部扫描器与上下文。
|
||||
* 调用构造函数时即开始词法扫描,生成完整 Token 序列。
|
||||
* 构造词法分析器(假定输入源自标准输入,文件名默认为 <stdin>)
|
||||
*
|
||||
* @param source 原始源代码文本
|
||||
* @param 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(
|
||||
new WhitespaceTokenScanner(), // 跳过空格、制表符等
|
||||
new NewlineTokenScanner(), // 处理换行符,生成 NEWLINE Token
|
||||
new CommentTokenScanner(), // 处理单行/多行注释
|
||||
new NumberTokenScanner(), // 识别整数与浮点数
|
||||
new IdentifierTokenScanner(), // 识别标识符与关键字
|
||||
new NumberTokenScanner(), // 识别整数与浮点数字面量
|
||||
new IdentifierTokenScanner(), // 识别标识符和关键字
|
||||
new StringTokenScanner(), // 处理字符串常量
|
||||
new OperatorTokenScanner(), // 处理运算符
|
||||
new SymbolTokenScanner(), // 处理括号、分号等符号
|
||||
new UnknownTokenScanner() // 捕捉无法识别的字符
|
||||
new OperatorTokenScanner(), // 识别运算符
|
||||
new SymbolTokenScanner(), // 识别括号、分号等符号
|
||||
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 序列。
|
||||
* <p>
|
||||
* 每次扫描尝试依次使用各个 {@link TokenScanner},直到某一扫描器能够处理当前字符。
|
||||
* 若无匹配扫描器,交由 {@code UnknownTokenScanner} 处理。
|
||||
* 扫描结束后自动附加一个 EOF(文件结束)Token。
|
||||
* </p>
|
||||
* 主扫描循环,将源代码转为 Token 序列
|
||||
* 依次尝试每个扫描器,直到找到可处理当前字符的扫描器为止
|
||||
* 扫描到结尾后补充 EOF Token
|
||||
*/
|
||||
private void scanAllTokens() {
|
||||
while (!context.isAtEnd()) {
|
||||
char currentChar = context.peek();
|
||||
|
||||
// 依次查找能处理当前字符的扫描器
|
||||
for (TokenScanner scanner : scanners) {
|
||||
if (scanner.canHandle(currentChar, context)) {
|
||||
scanner.handle(context, tokens);
|
||||
break;
|
||||
break; // 已处理,跳到下一个字符
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 末尾补一个 EOF 标记
|
||||
tokens.add(Token.eof(context.getLine()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回词法分析生成的所有 Token(含 EOF)。
|
||||
* 获取全部 Token(包含 EOF),返回只读列表
|
||||
*
|
||||
* @return Token 的不可变副本列表
|
||||
* @return 词法分析结果 Token 列表
|
||||
*/
|
||||
public List<Token> getAllTokens() {
|
||||
return List.copyOf(tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -1,25 +1,28 @@
|
||||
package org.jcnc.snow.compiler.lexer.scanners;
|
||||
|
||||
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.TokenType;
|
||||
|
||||
/**
|
||||
* 未知符号扫描器:兜底处理无法被其他扫描器识别的字符。
|
||||
* 未知 Token 扫描器(UnknownTokenScanner)。
|
||||
* <p>
|
||||
* 用于捕捉非法或未定义的符号序列,生成 {@code UNKNOWN} 类型的 Token。
|
||||
* 作为所有扫描器的兜底处理器。当前字符若不被任何其他扫描器识别,
|
||||
* 由本类处理并抛出 {@link LexicalException},终止词法分析流程。
|
||||
* </p>
|
||||
* <p>
|
||||
* 它会连续读取一段既不是字母、数字、空白符,也不属于常规符号(如 ;、{、"、:、,、(、)、.、+、-、*)的字符序列。
|
||||
* 主要作用:保证所有非法、不可识别的字符(如@、$等)不会被静默跳过或误当作合法 Token,
|
||||
* 而是在词法阶段立刻定位并报错,有助于尽早发现源代码问题。
|
||||
* </p>
|
||||
*/
|
||||
public class UnknownTokenScanner extends AbstractTokenScanner {
|
||||
|
||||
/**
|
||||
* 始终返回 true,作为所有扫描器中的兜底处理器。
|
||||
* <p>当没有其他扫描器能够处理当前字符时,使用本扫描器。</p>
|
||||
*
|
||||
* @param c 当前字符
|
||||
* @param ctx 当前词法上下文
|
||||
* @return 总是返回 true
|
||||
* 判断是否可以处理当前字符。
|
||||
* 对于 UnknownTokenScanner,始终返回 true(兜底扫描器,必须排在扫描器链末尾)。
|
||||
* @param c 当前待处理字符
|
||||
* @param ctx 词法上下文
|
||||
* @return 是否处理该字符(始终为 true)
|
||||
*/
|
||||
@Override
|
||||
public boolean canHandle(char c, LexerContext ctx) {
|
||||
@ -27,23 +30,26 @@ public class UnknownTokenScanner extends AbstractTokenScanner {
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描未知或非法的字符序列。
|
||||
* <p>跳过字母、数字、空白和已知符号,仅捕获无法识别的符号块。</p>
|
||||
*
|
||||
* @param ctx 词法上下文
|
||||
* @param line 当前行号
|
||||
* @param col 当前列号
|
||||
* @return {@code UNKNOWN} 类型的 Token,包含无法识别的字符内容
|
||||
* 实际处理非法字符序列的方法。
|
||||
* 连续读取所有无法被其他扫描器识别的字符,组成错误片段并抛出异常。
|
||||
* @param ctx 词法上下文
|
||||
* @param line 错误发生行号
|
||||
* @param col 错误发生列号
|
||||
* @return 不会返回Token(始终抛异常)
|
||||
* @throws LexicalException 非法字符导致的词法错误
|
||||
*/
|
||||
@Override
|
||||
protected Token scanToken(LexerContext ctx, int line, int col) {
|
||||
String lexeme = readWhile(ctx, c ->
|
||||
!Character.isLetterOrDigit(c) &&
|
||||
!Character.isWhitespace(c) &&
|
||||
c != ';' && c != '{' && c != '"' &&
|
||||
":,().+-*".indexOf(c) < 0
|
||||
// 读取一段非法字符(既不是字母数字、也不是常见符号)
|
||||
String lexeme = readWhile(ctx, ch ->
|
||||
!Character.isLetterOrDigit(ch) &&
|
||||
!Character.isWhitespace(ch) &&
|
||||
":,().+-*{};\"".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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user