fix:改进语法分析器支持顶层语句
This commit is contained in:
parent
e023b576c1
commit
d4d2b3b87c
@ -27,8 +27,6 @@ SCompiler 项目将源代码依次经过多个阶段处理,最终生成字节
|
||||
|
||||
扫描过程采用 **多通道扫描**:Lexer按字符顺序读取输入,对每个字符依次尝试上述扫描器,哪个扫描器的`canHandle()`方法返回true就交由其`handle()`处理。每个扫描器从`LexerContext`获取当前字符流状态,消费相应字符序列并通过`TokenFactory`创建Token加入结果列表。例如,IdentifierScanner读取字母序列后由TokenFactory判断是标识符还是关键字。Lexer会跳过空白和注释,不生成多余Token。扫描循环持续直到输入结尾,然后显式追加一个EOF(TokenType.EOF)作为结束标记。整个词法分析的输出是有序的Token列表,可供语法分析器消费。
|
||||
|
||||
*健壮性*: 当前Lexer对未知字符不会抛异常,而是生成类型为UNKNOWN的Token并继续。这在后续解析中会导致错误。可改进之处包括:当出现非法字符时Lexer直接报错或记录错误信息,而不是依赖Parser再处理。
|
||||
|
||||
## 语法分析模块
|
||||
|
||||
**语法分析器(Parser)** 将Token序列按照语言文法规则还原为抽象语法树(AST)。SCompiler语言的文法大致为:*模块*由`module 模块名:`及缩进的多个函数定义组成,模块以`end module`结束;*函数定义*以`function 函数名(参数列表):`开始,内部缩进块包含若干语句,以`end function`结束。语句包括变量声明、赋值、条件`if/else`、循环`loop`、返回`return`以及表达式调用等。
|
||||
@ -53,9 +51,13 @@ Parser采用**递归下降**和**运算符优先解析**相结合的方法:顶
|
||||
|
||||
* **AST节点** – 抽象语法树采用面向对象节点类层次表示。基类`Node`定义了通用接口,各子类对应不同语法成分。主要节点类例如:ModuleNode(模块,含名称和函数列表)、FunctionNode(函数定义,含名称、参数列表、返回类型、函数体)、ImportNode(导入声明)、ParameterNode(形参定义,含名和类型)、Block结构节点如IfNode、LoopNode(包含条件和内部语句块),以及表达式节点如 IdentifierNode、NumberLiteralNode、BinaryExpressionNode 等。AST节点在解析时被实例化,语义分析阶段可能会在节点上附加类型等注释信息。整个Parser输出一个AST节点列表,一般情况下包含一个ModuleNode(源文件级模块)作为根。如果语法有误,解析器会抛出带错误位置和原因的异常停止编译。
|
||||
|
||||
*健壮性*: 语法分析目前采用严格的期望匹配(`expect()`方法)和显式的`end`结束符,能够检测出缩进层级错误或缺失`end`等问题并报错。但由于没有维护一个专门的缩进栈,缩进不正确主要通过`end`不匹配来发现。设计上原本计划基于缩进来确定作用域(类似Python),但实际实现中通过`:`和成对的`end`显式标记块级范围,这种混合风格稍显冗余。改进方向可以是统一语法风格,例如完全采用缩进+换行而无`end`关键字,或采用显式花括号/`end`而无冒号。
|
||||
上面是Parser相关介绍
|
||||
|
||||
另一个问题是Parser对顶层非模块内容的支持不完善:当前TopLevelParserFactory只注册了模块解析。如果源码未以`module`开头(比如仅有独立函数定义或语句),ParserEngine将无法识别顶层结构而报错。这在设计上可能考虑过支持**脚本模式**(无模块包裹的代码),从IR生成器看也有相应处理逻辑(将顶层Statement封装进`_start`函数),但由于顶层解析未实现直接处理Statement,实际使用中需要至少有一个模块声明。为提高灵活性,可扩展Parser支持隐式模块包装或允许顶层函数定义。最后,Parser目前对错误恢复支持有限,一旦遇到语法错误通常终止分析;将来可考虑在捕获错误后跳过一定Token继续分析,收集多个错误再统一报告。
|
||||
## 需要你完成的
|
||||
1. 解压然后读取项目里面全部相关代码,然后解决下面的问题
|
||||
2. 目前问题是Parser对顶层非模块内容的支持不完善:当前TopLevelParserFactory只注册了模块解析。如果源码未以`module`开头(比如仅有独立函数定义或语句),ParserEngine将无法识别顶层结构而报错。这在设计上可能考虑过支持**脚本模式**(无模块包裹的代码),从IR生成器看也有相应处理逻辑(将顶层Statement封装进`_start`函数),但由于顶层解析未实现直接处理Statement,实际使用中需要至少有一个模块声明。目标:为提高灵活性,扩展Parser支持隐式模块包装或允许顶层函数定义。
|
||||
3. Parser目前对错误恢复支持有限,一旦遇到语法错误通常终止分析;实现在捕获错误后跳过一定Token继续分析,收集多个错误再统一报告。
|
||||
4. 帮我解决以上两个问题,确保修改后和后面模块兼容,给我修改后的代码和新增的代码
|
||||
|
||||
## 语义分析模块
|
||||
|
||||
|
||||
@ -115,11 +115,13 @@ public class TokenStream {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否尚未到达 EOF。
|
||||
* 判断是否“已经”到达 EOF。
|
||||
*
|
||||
* @return 若当前位置 Token 非 EOF,则返回 true
|
||||
* @return 若当前位置 Token 为 EOF,则返回 true,否则 false
|
||||
*/
|
||||
public boolean isAtEnd() {
|
||||
return peek().getType() != TokenType.EOF;
|
||||
return peek().getType() == TokenType.EOF; // ← 修正逻辑
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -10,62 +10,59 @@ import org.jcnc.snow.compiler.parser.ast.base.Node;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@code ParserEngine} 是语法分析阶段的主控引擎,
|
||||
* 负责驱动顶层语法结构(如模块定义、导入语句、函数定义等)的解析流程。
|
||||
* <p>
|
||||
* 它通过不断读取 TokenStream 中的标记,动态选择合适的解析器(由工厂提供),
|
||||
* 并构建对应的抽象语法树(AST)节点。
|
||||
* 同时具备跳过空行、处理非法标记等基本容错能力。
|
||||
* </p>
|
||||
*/
|
||||
public class ParserEngine {
|
||||
|
||||
/** 解析上下文,封装 TokenStream 与语法状态 */
|
||||
private final ParserContext ctx;
|
||||
|
||||
/**
|
||||
* 构造一个 {@code ParserEngine} 实例。
|
||||
*
|
||||
* @param ctx 提供语法分析所需的上下文信息(如 Token 流)
|
||||
*/
|
||||
public ParserEngine(ParserContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动语法解析流程,提取所有顶层 AST 节点。
|
||||
* <p>
|
||||
* 该方法会循环调用 {@link TopLevelParserFactory} 选择合适解析器,
|
||||
* 并将解析结果存入返回列表,直到遇到文件结束符(EOF)。
|
||||
* </p>
|
||||
*
|
||||
* @return 顶层 AST 节点列表(如模块、导入、函数等)
|
||||
* @throws IllegalStateException 若遇到无法识别的顶层标记
|
||||
*/
|
||||
public List<Node> parse() {
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
TokenStream ts = ctx.getTokens();
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
List<String> errs = new ArrayList<>();
|
||||
TokenStream ts = ctx.getTokens();
|
||||
|
||||
while (ts.isAtEnd()) {
|
||||
// 跳过 NEWLINE 以增强容错性
|
||||
while (!ts.isAtEnd()) { // ← 取反
|
||||
// 跳过空行
|
||||
if (ts.peek().getType() == TokenType.NEWLINE) {
|
||||
ts.next();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 当前词素作为解析关键字(如 module, import)
|
||||
String lex = ts.peek().getLexeme();
|
||||
TopLevelParser parser = TopLevelParserFactory.get(ts.peek().getLexeme());
|
||||
|
||||
TopLevelParser parser = TopLevelParserFactory.get(lex);
|
||||
if (parser == null) {
|
||||
throw new IllegalStateException("意外的顶级标记: " + lex);
|
||||
try {
|
||||
nodes.add(parser.parse(ctx));
|
||||
} catch (Exception ex) {
|
||||
errs.add(ex.getMessage());
|
||||
synchronize(ts); // 错误恢复
|
||||
}
|
||||
|
||||
// 执行对应解析器
|
||||
nodes.add(parser.parse(ctx));
|
||||
}
|
||||
|
||||
if (!errs.isEmpty()) {
|
||||
throw new IllegalStateException("解析过程中检测到 "
|
||||
+ errs.size() + " 处错误:\n - "
|
||||
+ String.join("\n - ", errs));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/** 错误同步:跳到下一行或下一个已注册顶层关键字 */
|
||||
private void synchronize(TokenStream ts) {
|
||||
while (!ts.isAtEnd()) {
|
||||
if (ts.peek().getType() == TokenType.NEWLINE) {
|
||||
ts.next();
|
||||
break;
|
||||
}
|
||||
if (TopLevelParserFactory.get(ts.peek().getLexeme()) != null) {
|
||||
break;
|
||||
}
|
||||
ts.next();
|
||||
}
|
||||
// 连续空行全部吃掉
|
||||
while (!ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) {
|
||||
ts.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,33 +27,37 @@ import java.util.Map;
|
||||
*/
|
||||
public class PrattExpressionParser implements ExpressionParser {
|
||||
|
||||
/** 前缀解析器注册表:按 Token 类型映射 */
|
||||
/**
|
||||
* 前缀解析器注册表:按 Token 类型映射
|
||||
*/
|
||||
private static final Map<String, PrefixParselet> prefixes = new HashMap<>();
|
||||
|
||||
/** 中缀解析器注册表:按运算符词素映射 */
|
||||
private static final Map<String, InfixParselet> infixes = new HashMap<>();
|
||||
/**
|
||||
* 中缀解析器注册表:按运算符词素映射
|
||||
*/
|
||||
private static final Map<String, InfixParselet> infixes = new HashMap<>();
|
||||
|
||||
static {
|
||||
// 注册前缀解析器
|
||||
prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet());
|
||||
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
|
||||
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
|
||||
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
|
||||
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
|
||||
prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet());
|
||||
|
||||
// 注册中缀解析器
|
||||
infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true));
|
||||
infixes.put("/", new BinaryOperatorParselet(Precedence.PRODUCT, true));
|
||||
infixes.put("%", new BinaryOperatorParselet(Precedence.PRODUCT, true));
|
||||
infixes.put(">", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("<", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true));
|
||||
infixes.put("/", new BinaryOperatorParselet(Precedence.PRODUCT, true));
|
||||
infixes.put("%", new BinaryOperatorParselet(Precedence.PRODUCT, true));
|
||||
infixes.put(">", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("<", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("==", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("!=", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put(">=", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("<=", new BinaryOperatorParselet(Precedence.SUM, true));
|
||||
infixes.put("(", new CallParselet());
|
||||
infixes.put(".", new MemberParselet());
|
||||
infixes.put("(", new CallParselet());
|
||||
infixes.put(".", new MemberParselet());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,14 +87,13 @@ public class PrattExpressionParser implements ExpressionParser {
|
||||
|
||||
ExpressionNode left = prefix.parse(ctx, token);
|
||||
|
||||
while (ctx.getTokens().isAtEnd() && prec.ordinal() < nextPrecedence(ctx)) {
|
||||
while (!ctx.getTokens().isAtEnd()
|
||||
&& prec.ordinal() < nextPrecedence(ctx)) {
|
||||
String lex = ctx.getTokens().peek().getLexeme();
|
||||
InfixParselet infix = infixes.get(lex);
|
||||
if (infix == null) break;
|
||||
|
||||
left = infix.parse(ctx, left);
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
|
||||
@ -1,42 +1,29 @@
|
||||
package org.jcnc.snow.compiler.parser.factory;
|
||||
|
||||
import org.jcnc.snow.compiler.parser.base.TopLevelParser;
|
||||
import org.jcnc.snow.compiler.parser.core.ParserEngine;
|
||||
import org.jcnc.snow.compiler.parser.module.ModuleParser;
|
||||
import org.jcnc.snow.compiler.parser.function.FunctionParser;
|
||||
import org.jcnc.snow.compiler.parser.top.ScriptTopLevelParser;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* {@code TopLevelParserFactory} 是一个顶层结构解析器工厂类,
|
||||
* 用于根据源文件起始关键字(如 {@code module})动态选择相应的 {@link TopLevelParser} 实现。
|
||||
*
|
||||
* <p>
|
||||
* 每种顶层结构(如模块、导入等)应实现 {@link TopLevelParser} 接口,并在本工厂中静态注册,
|
||||
* 从而在语法分析阶段由 {@link ParserEngine} 根据关键字调用对应的解析器。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 本类采用静态注册表机制,在类加载时将支持的关键字与其解析器一一映射并缓存于内部 Map 中。
|
||||
* 若调用时传入的关键字未注册,则返回 {@code null},调用方应自行处理此情况。
|
||||
* </p>
|
||||
*/
|
||||
public class TopLevelParserFactory {
|
||||
/** 顶层关键字 -> 顶层结构解析器 的映射表 */
|
||||
|
||||
private static final Map<String, TopLevelParser> registry = new HashMap<>();
|
||||
private static final TopLevelParser DEFAULT = new ScriptTopLevelParser(); // ← 默认解析器
|
||||
|
||||
static {
|
||||
// 注册顶层结构解析器
|
||||
registry.put("module", new ModuleParser());
|
||||
// 顶层结构解析器
|
||||
registry.put("module", new ModuleParser());
|
||||
registry.put("function", new FunctionParser());
|
||||
// 也可按需继续注册其它关键字
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定关键字获取对应的顶层结构解析器。
|
||||
*
|
||||
* @param keyword 顶层结构关键字(例如 "module")。
|
||||
* @return 对应的 {@link TopLevelParser} 实例;若关键字未注册,则返回 {@code null}。
|
||||
* 根据关键字获取解析器;若未注册,回退到脚本语句解析。
|
||||
*/
|
||||
public static TopLevelParser get(String keyword) {
|
||||
return registry.get(keyword);
|
||||
return registry.getOrDefault(keyword, DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package org.jcnc.snow.compiler.parser.top;
|
||||
|
||||
import org.jcnc.snow.compiler.parser.base.TopLevelParser;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.Node;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
|
||||
import org.jcnc.snow.compiler.parser.context.ParserContext;
|
||||
import org.jcnc.snow.compiler.parser.factory.StatementParserFactory;
|
||||
import org.jcnc.snow.compiler.parser.statement.StatementParser;
|
||||
|
||||
/**
|
||||
* {@code ScriptTopLevelParser} 允许在无 module 包裹的情况下
|
||||
* 直接解析单条顶层语句(脚本模式)。
|
||||
*
|
||||
* 解析得到的 {@link StatementNode} 将在 IR 阶段被封装成 _start 函数。
|
||||
*/
|
||||
public class ScriptTopLevelParser implements TopLevelParser {
|
||||
|
||||
@Override
|
||||
public Node parse(ParserContext ctx) {
|
||||
String first = ctx.getTokens().peek().getLexeme();
|
||||
StatementParser sp = StatementParserFactory.get(first);
|
||||
StatementNode stmt = sp.parse(ctx);
|
||||
return stmt; // StatementNode 亦是 Node
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user