7.1 KiB
AST 模型、ASDL 与跨工具接口
Endor 的 AST 节点统一由 Endor.asdl 定义。它描述节点及字段;lexer.peg 描述字符识别,grammar.peg 描述语法与节点构造。ASDL 与语法各自维护一个职责,不引入 CST 或 Python type/元类;AST 位于 frontend,运行时对象位于 objects。
生成关系
Endor.asdl → scripts/generate_ast.py
├─ src/ast/generated.rs:Rust 节点、Serde 与 Spanned
├─ src/ast/visitor.rs:Rust Visitor/walk_*
├─ editor/ast.ts:TypeScript 类型、Visitor/walk*
└─ editor/ast.schema.json:JSON Schema 2020-12
路径均相对 crates/frontend。生成器使用 Python 标准库和 rustfmt,无额外 Python 包、build.rs 或构建时网络访问。产物提交到 Git,普通 Cargo 构建直接使用它们。开发修改 schema 后,在仓库根执行:
python3 scripts/generate_ast.py --write
python3 scripts/generate_ast.py --check
这两条命令是源码生成/静态检查,可在本地使用;行为测试仍仅在指定远端执行。CI 检查生成同步,并运行生成器回归检查与工作区测试。
ASDL 支持 module、产品类型 (Type field, ...)、构造器联合 A(...) | B(...)、* 序列、? 可选字段、-- 行注释。内建 string 是拥有文本的 String,span 对应共享的 UTF-8 Span,schema_id 是交换契约标识。生成器检查重复名称、字段、未知类型和不支持的语法;这是项目使用的 ASDL 子集,不声称兼容 CPython ASDL 的全部扩展。
Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图的回边放置 Box,Vec 已经断开按值递归。增加节点后,Rust 类型、两种语言的遍历接口和 JSON Schema 由同一次生成更新。
根、声明、语句、表达式
| 层级 | 当前数据模型 |
|---|---|
| AstRoot | Module(Program)、Expression(Expr)、Interactive(InteractiveInput) |
| Program | declarations: Vec 与 span;声明种类为 Function、Class |
| Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block;Class 保存名称、可选基类、字段及方法 |
| Block | statements: Vec 与 span,不再用固定的 bindings/result 字段表示 |
| Stmt/StmtKind | Binding、Assignment、MemberAssignment、Return、Expression、Declaration;每条语句有自身范围 |
| MemberAssignment | 对象表达式、属性名称、右侧表达式与原文范围 |
| Assignment | 名称目标、右侧表达式及原文范围;独立于 let 的 Binding 节点 |
| ReturnStatement | 可选表达式与范围;当前源码中的 return 仍要求表达式 |
| Expr/ExprKind | 名称、数字原文、字符串、符号、一元/二元运算、成员和调用 |
参数与返回类型仍为 Name,只保留语法,不实施类型或名称检查。Program::functions() 提供函数迭代便捷接口。旧的 Program.functions、Block.bindings/result/return_span 字段已移除;源码构造和使用方改为声明/语句列表。ExprKind 的既有表达式结构保留。
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块要求一个或多个函数/类声明;函数体接受 let/名称赋值/属性赋值/表达式语句,以及可选的末尾带值 return。Interactive 模式接受一次函数/类声明、let 绑定、名称/属性赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、trait 等尚未实现,也不加入 FunctionType 这种 Python 专用入口。
解析与位置
parse_ast(&str, ParseMode) 和 parse_source_ast(&SourceUnit, ParseMode) 返回 AstRoot;ParseMode 包含 Module、Expression、Interactive。原有 parse/parse_expression/parse_source/parse_source_expression 保留对应的具体返回类型。带来源入口的诊断仍记录名称/URI/修订和行列。
Spanned 为有位置的节点提供 span()、source_text(&str)、location(&str)。Span::slice/location 拒绝反向、越界或截断 UTF-8 字符的范围;行列是一基 Unicode 标量列,CRLF 算一次换行。Span 不携带源码身份,调用方需关联原始 SourceUnit;AST JSON 本身不含源码、文件路径、URI 或资源修订。
遍历与分析
生成的 Rust Visitor 有每个节点/种类的 visit_* 方法、默认 walk_* 函数,以及 enter_node/leave_node 钩子。Error 为关联类型,子节点失败时默认遍历返回错误,并配对调用已进入节点的离开钩子。可在 visit_function、visit_binding、visit_expr 等位置实现名称分析或字节码生成。
覆盖 visit_* 后,调用 walk_* 可继续访问子节点;覆盖方法自身的钩子管理由实现者决定,直接调用 walk_* 不会重复调用当前节点的 visit_*。这些接口只提供遍历;独立 Compiler 实现当前模块/函数名称分析和最小字节码生成,闭包捕获分析尚未实现。TypeScript Visitor 对应编辑器使用,回调返回 false 可跳过子树。
JSON 交换
frontend 使用工作区已有的 serde/serde_json。AstDocument 是带 schema 和 root 的交换外壳,提供 new、to_json、from_json、validate、validate_source。构造/读取/写出时检查 schema 标识、位置顺序、父子范围包含关系及 JS 安全整数范围;validate_source 额外检查给定原文的长度和 UTF-8 字符边界,不比较原文与节点的程序含义。
构造器联合按 {"kind":"Variant","data":...} 编码,无字段的运算符联合使用字符串。产品类型使用字段对象;未知字段和未知构造器拒绝。可选字段可省略或为 null,标准写出为 null。数字保留源码拼写字符串,不经浮点转换。
AST_SCHEMA_ID 从 Endor.asdl 的 UTF-8 文本计算 SHA-256,包含注释与空白;生成器读取时使用 Python 的统一换行规则。它标识这套 AST 接口定义,区别于用户资源修订、编译器或 VM 版本。修改 schema 后旧标识被拒绝,尚无历史格式迁移。改变生成器的交换编码或内建位置类型契约时,也必须更新 ASDL 顶部的 Wire contract 版本说明,使标识随之变化。直接调用 Serde 是结构编解码,跨边界应使用 AstDocument 方法执行完整校验;JSON Schema 不表达所有位置约束。
CLI 可提供具体的交换输出:
lyra parse --json source.endor
lyra parse --mode expression --json -
lyra parse --mode interactive --json -
文件/stdin 阅读保持原有 UTF-8 和错误来源规则,stdin 到 EOF 后才解析。普通 lyra parse 输出 AstRoot 的 Debug 文本,稳定交换使用带 schema 的 JSON;parse 的各模式均不执行源码;run/eval 执行入口见 编译链路。命令在指定远端运行。
检查源码见 frontend/tests 和 生成器检查,边界见 验证。返回 运行时索引。