diff --git a/doc/Snow语言文档.md b/doc/Snow语言文档.md index dab887d..36c5105 100644 --- a/doc/Snow语言文档.md +++ b/doc/Snow语言文档.md @@ -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. 帮我解决以上两个问题,确保修改后和后面模块兼容,给我修改后的代码和新增的代码 ## 语义分析模块 diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java b/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java index f4a1cc4..b16a395 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java @@ -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; // ← 修正逻辑 } + + } \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java index 8567eb2..df71c8d 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java @@ -10,62 +10,59 @@ import org.jcnc.snow.compiler.parser.ast.base.Node; import java.util.ArrayList; import java.util.List; -/** - * {@code ParserEngine} 是语法分析阶段的主控引擎, - * 负责驱动顶层语法结构(如模块定义、导入语句、函数定义等)的解析流程。 - *

- * 它通过不断读取 TokenStream 中的标记,动态选择合适的解析器(由工厂提供), - * 并构建对应的抽象语法树(AST)节点。 - * 同时具备跳过空行、处理非法标记等基本容错能力。 - *

- */ public class ParserEngine { - /** 解析上下文,封装 TokenStream 与语法状态 */ private final ParserContext ctx; - /** - * 构造一个 {@code ParserEngine} 实例。 - * - * @param ctx 提供语法分析所需的上下文信息(如 Token 流) - */ public ParserEngine(ParserContext ctx) { this.ctx = ctx; } - /** - * 启动语法解析流程,提取所有顶层 AST 节点。 - *

- * 该方法会循环调用 {@link TopLevelParserFactory} 选择合适解析器, - * 并将解析结果存入返回列表,直到遇到文件结束符(EOF)。 - *

- * - * @return 顶层 AST 节点列表(如模块、导入、函数等) - * @throws IllegalStateException 若遇到无法识别的顶层标记 - */ public List parse() { - List nodes = new ArrayList<>(); - TokenStream ts = ctx.getTokens(); + List nodes = new ArrayList<>(); + List 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; } -} \ No newline at end of file + + /** 错误同步:跳到下一行或下一个已注册顶层关键字 */ + 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(); + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java index cae7e8c..c2a818b 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java @@ -27,33 +27,37 @@ import java.util.Map; */ public class PrattExpressionParser implements ExpressionParser { - /** 前缀解析器注册表:按 Token 类型映射 */ + /** + * 前缀解析器注册表:按 Token 类型映射 + */ private static final Map prefixes = new HashMap<>(); - /** 中缀解析器注册表:按运算符词素映射 */ - private static final Map infixes = new HashMap<>(); + /** + * 中缀解析器注册表:按运算符词素映射 + */ + private static final Map 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; } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java b/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java index 3521d31..7fa779c 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java @@ -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} 实现。 - * - *

- * 每种顶层结构(如模块、导入等)应实现 {@link TopLevelParser} 接口,并在本工厂中静态注册, - * 从而在语法分析阶段由 {@link ParserEngine} 根据关键字调用对应的解析器。 - *

- * - *

- * 本类采用静态注册表机制,在类加载时将支持的关键字与其解析器一一映射并缓存于内部 Map 中。 - * 若调用时传入的关键字未注册,则返回 {@code null},调用方应自行处理此情况。 - *

- */ public class TopLevelParserFactory { - /** 顶层关键字 -> 顶层结构解析器 的映射表 */ + private static final Map 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); } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java b/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java new file mode 100644 index 0000000..2190423 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java @@ -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 + } +} diff --git a/test b/test index 92297d4..1f3471f 100644 --- a/test +++ b/test @@ -1,5 +1,5 @@ module: CommonTasks - function: main@ + function: main parameter: return_type:int