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按字符顺序读取输入,对每个字符依次尝试上述扫描器,哪个扫描器的`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. 帮我解决以上两个问题,确保修改后和后面模块兼容,给我修改后的代码和新增的代码
|
||||||
|
|
||||||
## 语义分析模块
|
## 语义分析模块
|
||||||
|
|
||||||
|
|||||||
@ -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; // ← 修正逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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<>();
|
||||||
TokenStream ts = ctx.getTokens();
|
List<String> errs = new ArrayList<>();
|
||||||
|
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) {
|
nodes.add(parser.parse(ctx));
|
||||||
throw new IllegalStateException("意外的顶级标记: " + lex);
|
} 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;
|
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 {
|
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 {
|
||||||
// 注册前缀解析器
|
// 注册前缀解析器
|
||||||
prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet());
|
prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet());
|
||||||
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
|
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
|
||||||
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
|
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
|
||||||
prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet());
|
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.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.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 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 CallParselet());
|
||||||
infixes.put(".", new MemberParselet());
|
infixes.put(".", new MemberParselet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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