fix:改进语法分析器支持顶层语句

This commit is contained in:
Luke 2025-05-16 15:52:58 +08:00
parent e023b576c1
commit d4d2b3b87c
7 changed files with 102 additions and 86 deletions

View File

@ -27,8 +27,6 @@ SCompiler 项目将源代码依次经过多个阶段处理,最终生成字节
扫描过程采用 **多通道扫描**Lexer按字符顺序读取输入对每个字符依次尝试上述扫描器哪个扫描器的`canHandle()`方法返回true就交由其`handle()`处理。每个扫描器从`LexerContext`获取当前字符流状态,消费相应字符序列并通过`TokenFactory`创建Token加入结果列表。例如IdentifierScanner读取字母序列后由TokenFactory判断是标识符还是关键字。Lexer会跳过空白和注释不生成多余Token。扫描循环持续直到输入结尾然后显式追加一个EOF(TokenType.EOF)作为结束标记。整个词法分析的输出是有序的Token列表可供语法分析器消费。 扫描过程采用 **多通道扫描**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`以及表达式调用等。 **语法分析器(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源文件级模块作为根。如果语法有误解析器会抛出带错误位置和原因的异常停止编译。 * **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. 帮我解决以上两个问题,确保修改后和后面模块兼容,给我修改后的代码和新增的代码
## 语义分析模块 ## 语义分析模块

View File

@ -115,11 +115,13 @@ public class TokenStream {
} }
/** /**
* 判断是否尚未到达 EOF * 判断是否已经到达 EOF
* *
* @return 若当前位置 Token EOF则返回 true * @return 若当前位置 Token EOF则返回 true否则 false
*/ */
public boolean isAtEnd() { public boolean isAtEnd() {
return peek().getType() != TokenType.EOF; return peek().getType() == TokenType.EOF; // 修正逻辑
} }
} }

View File

@ -10,62 +10,59 @@ import org.jcnc.snow.compiler.parser.ast.base.Node;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* {@code ParserEngine} 是语法分析阶段的主控引擎
* 负责驱动顶层语法结构如模块定义导入语句函数定义等的解析流程
* <p>
* 它通过不断读取 TokenStream 中的标记动态选择合适的解析器由工厂提供
* 并构建对应的抽象语法树AST节点
* 同时具备跳过空行处理非法标记等基本容错能力
* </p>
*/
public class ParserEngine { public class ParserEngine {
/** 解析上下文,封装 TokenStream 与语法状态 */
private final ParserContext ctx; private final ParserContext ctx;
/**
* 构造一个 {@code ParserEngine} 实例
*
* @param ctx 提供语法分析所需的上下文信息 Token
*/
public ParserEngine(ParserContext ctx) { public ParserEngine(ParserContext ctx) {
this.ctx = ctx; this.ctx = ctx;
} }
/**
* 启动语法解析流程提取所有顶层 AST 节点
* <p>
* 该方法会循环调用 {@link TopLevelParserFactory} 选择合适解析器
* 并将解析结果存入返回列表直到遇到文件结束符EOF
* </p>
*
* @return 顶层 AST 节点列表如模块导入函数等
* @throws IllegalStateException 若遇到无法识别的顶层标记
*/
public List<Node> parse() { public List<Node> parse() {
List<Node> nodes = new ArrayList<>(); List<Node> nodes = new ArrayList<>();
List<String> errs = new ArrayList<>();
TokenStream ts = ctx.getTokens(); TokenStream ts = ctx.getTokens();
while (ts.isAtEnd()) { while (!ts.isAtEnd()) { // 取反
// 跳过 NEWLINE 以增强容错性 // 跳过空行
if (ts.peek().getType() == TokenType.NEWLINE) { if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next(); ts.next();
continue; continue;
} }
// 当前词素作为解析关键字 module, import TopLevelParser parser = TopLevelParserFactory.get(ts.peek().getLexeme());
String lex = ts.peek().getLexeme();
TopLevelParser parser = TopLevelParserFactory.get(lex); try {
if (parser == null) {
throw new IllegalStateException("意外的顶级标记: " + lex);
}
// 执行对应解析器
nodes.add(parser.parse(ctx)); nodes.add(parser.parse(ctx));
} catch (Exception ex) {
errs.add(ex.getMessage());
synchronize(ts); // 错误恢复
}
} }
if (!errs.isEmpty()) {
throw new IllegalStateException("解析过程中检测到 "
+ errs.size() + " 处错误:\n - "
+ String.join("\n - ", errs));
}
return nodes; 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();
}
}
} }

View File

@ -27,10 +27,14 @@ import java.util.Map;
*/ */
public class PrattExpressionParser implements ExpressionParser { public class PrattExpressionParser implements ExpressionParser {
/** 前缀解析器注册表:按 Token 类型映射 */ /**
* 前缀解析器注册表 Token 类型映射
*/
private static final Map<String, PrefixParselet> prefixes = new HashMap<>(); 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 { static {
@ -83,14 +87,13 @@ public class PrattExpressionParser implements ExpressionParser {
ExpressionNode left = prefix.parse(ctx, token); 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(); String lex = ctx.getTokens().peek().getLexeme();
InfixParselet infix = infixes.get(lex); InfixParselet infix = infixes.get(lex);
if (infix == null) break; if (infix == null) break;
left = infix.parse(ctx, left); left = infix.parse(ctx, left);
} }
return left; return left;
} }

View File

@ -1,42 +1,29 @@
package org.jcnc.snow.compiler.parser.factory; package org.jcnc.snow.compiler.parser.factory;
import org.jcnc.snow.compiler.parser.base.TopLevelParser; 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.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.Map;
import java.util.HashMap; import java.util.HashMap;
/**
* {@code TopLevelParserFactory} 是一个顶层结构解析器工厂类
* 用于根据源文件起始关键字 {@code module}动态选择相应的 {@link TopLevelParser} 实现
*
* <p>
* 每种顶层结构如模块导入等应实现 {@link TopLevelParser} 接口并在本工厂中静态注册
* 从而在语法分析阶段由 {@link ParserEngine} 根据关键字调用对应的解析器
* </p>
*
* <p>
* 本类采用静态注册表机制在类加载时将支持的关键字与其解析器一一映射并缓存于内部 Map
* 若调用时传入的关键字未注册则返回 {@code null}调用方应自行处理此情况
* </p>
*/
public class TopLevelParserFactory { public class TopLevelParserFactory {
/** 顶层关键字 -> 顶层结构解析器 的映射表 */
private static final Map<String, TopLevelParser> registry = new HashMap<>(); private static final Map<String, TopLevelParser> registry = new HashMap<>();
private static final TopLevelParser DEFAULT = new ScriptTopLevelParser(); // 默认解析器
static { 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) { public static TopLevelParser get(String keyword) {
return registry.get(keyword); return registry.getOrDefault(keyword, DEFAULT);
} }
} }

View File

@ -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
}
}

2
test
View File

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