Files
endorphin-aster-vm/docs/runtime/frontend.md

105 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 源码词法与 PEG 语法前端
frontend 包提供 [Endor 阅读器](reader.md)、rust-peg 生成的字符 lexer 与 token parser。宿主通过 SourceUnit 调用带来源的词法/语法入口,原有 lex、parse、parse_expression 保留;CLI 提供 parse 文件/stdin 命令;结果是 AST;独立 [Compiler](compiler.md) 已接入名称/作用域分析、最小字节码生成和 CLI run/eval 执行,frontend 自身不执行代码或检查类型。Core 仅依赖本地 Objects,不依赖 frontend 或 peg;Objects 保持零依赖。
首版以 Endorphin-Language 设计稿的基础词法、表达式与函数章节为起点,语法可以继续修改;设计稿中的未完成部分不作为阻塞前端实现的前置条件,也不被整体视作已经实现的语言能力。这里维护当前解析契约;源码位置见 [frontend](../../crates/frontend/README.md)。
## 词法与语法文件
两份规则复用现有 peg 0.8.6 依赖,分别处理字符和 Token:
| 文件 | 输入与职责 | Rust 接口 |
| --- | --- | --- |
| [lexer.peg](../../crates/frontend/src/lexer.peg) | str 字符输入,识别名称、数字、字符串/转义、标点、空白/注释及词法错误,生成 Token | [lexer.rs](../../crates/frontend/src/lexer.rs) 保留 lex/lex_source、游标、位置换算和字符串解码 |
| [grammar.peg](../../crates/frontend/src/grammar.peg) | [Token] 输入,匹配函数/绑定/return、表达式优先级并构造 AST | [parser.rs](../../crates/frontend/src/parser.rs) 保留解析入口、诊断转换和 AST 辅助函数 |
lexer.rs 与 parser.rs 分别通过 include! 在编译期引入对应规则;两份 .peg 文件保留 peg::parser! 外层声明,直接参与编译,自身不需要 build.rs 或生成脚本。AST 类型另由 Endor.asdl 离线生成,交换接口使用 serde/serde_json,见 [AST 模型](ast.md)。远端检查脚本单独对两份规则执行 rustfmt。
字符 PEG 的内部 next 规则使用 #[no_eof] 每次识别一个前缀,跳过空白/注释并返回一个 Token 或输入结束。Lexer 保存 source/offset,循环调用该规则,直到全输入结束或首个错误;公开 lex 仍扫描整份输入,不是增量/可恢复输入接口。规则中的 position! 相对本次输入切片,lexer.rs 将 Token 和诊断位置统一换算为原始源码 UTF-8 字节范围。
已识别的非法数字后缀、字符串转义、裸换行/NUL、未闭合字符串和未知字符,以规则返回的 Diagnostic 值传递,不作为允许 PEG 改试其他 Token 种类的匹配失败。识别到词法错误即停止;词法规则不修改外部游标或 Token 容器,避免回溯副作用。前缀规则及动作约定见 [peg 官方文档](https://docs.rs/peg/0.8.6/peg/#end-of-file-handling)。
## 入口和所有权
| API | 输入与结果 |
| --- | --- |
| lex_source(&SourceUnit) | 对阅读器输出分词,错误携带来源、修订及行列 |
| parse_source(&SourceUnit) | 对阅读器输出解析基础函数 Program,错误携带来源信息 |
| parse_source_expression(&SourceUnit) | 对阅读器输出解析一个完整表达式,错误携带来源信息 |
| lex(&str) | 产生 Vec<Token>;每个 token 只有种类和原始源码的字节范围,不复制文本 |
| parse(&str) | 解析一个或多个完整函数定义,返回 Program;空输入和未消费的尾部内容报错 |
| parse_expression(&str) | 解析一个完整表达式,不能忽略尾部 token |
| parse_ast(&str, ParseMode) | 返回 Module/Expression/Interactive 三类 AstRoot,模式独立于输入来源 |
| parse_source_ast(&SourceUnit, ParseMode) | 同一统一入口,失败保留来源、修订和行列 |
AST 结构及 Visitor、Spanned 和跨工具 JSON 契约见 [AST 模型](ast.md)。Program 使用 declarations,Block 使用 statements;ExprKind 保留既有表达式形状。AST 的名称、数字原文和解码后的字符串均由 AST 自己持有,可以在释放输入后继续使用。Token/AST 的 Span 均为原始 UTF-8 源码的半开字节区间 start..end;节点跨度包含首尾 token 之间的空白及注释,不包含外侧 trivia。分组表达式的范围包括括号,Program 范围从第一个函数到最后一个函数。
词法动作只返回 Token/诊断,语法动作只构造 AST;两者均不做 I/O、VM 分配或其他外部写入。PEG 回溯不会执行语言函数或产生宿主业务副作用。
## 词法规则
- lexer 输入是有效 UTF-8 的 &str。阅读器将字符串、流、文件或宿主 URI provider 的结果转换为不可变 SourceUnit;CLI 通过阅读器严格解码字节。其他编码由宿主先显式解码,不修改全局 locale。
- 名称首字符为下划线或 Rust char::is_alphabetic,后续为下划线或 char::is_alphanumeric。这是当前明确采用的分类,不承诺 XID/NFKC 规则;没有自动规范化。中文、日文可用于名称,emoji 可以用于字符串。
- 名称统一产生 Name token。function、let、return 只在相应语法位置按拼写识别,不预先固定全局保留字表;class、agent 等也仍可作为名称。
- 数字为 ASCII 十进制数字,可带两边都有数字的小数部分;原文保留,不转换为 f64。负号单独分词;科学计数法、数字后缀、下划线分隔、前导正号、.5 和 1. 形式不在当前表达式语法中。
- 双引号字符串允许 Unicode 文本,支持 `\n`、`\t`、`\"`、`\\`。token 范围包含引号,构造 AST 时解码转义。未闭合字符串、未知转义、裸换行和 NUL 报错;没有单引号、多行或插值字符串。
- 空格、Tab、LF、CR 和 CRLF 分隔 token;缩进和换行不形成语句或代码块。`//` 到行末为注释,字符串内的 `//` 保持文本。没有块注释。
- 支持 `(`、`)`、`{`、`}`、`:`、`,`、`.`、`=`、`+`、`-`、`*`、`/`;不接受分号和其他未支持字符。
## 当前语法
```text
program = function+
function = "function" name "(" parameters? ")" ":" name block
parameter = name ":" name
block = "{" (binding | assignment | expression)* ("return" expression)? "}"
binding = "let" name "=" expression
assignment = name "=" expression
```
parameters 和调用参数列表使用逗号分隔,不接受尾随逗号。函数体支持独立表达式语句(包括方法调用)和可选的末尾 return;省略 return 时编译器生成 Nil 结果。函数体支持属性赋值,尚不支持嵌套声明或默认参数源码写法。Interactive 解析模式接受一次函数/类声明、let 绑定、名称/属性赋值或表达式,完整消费输入,但不创建 REPL 或执行代码。参数和返回类型目前只是名称节点;并不据此创建语言 type 对象或检查类型,省略 return 不代表已实施 Unit 类型检查。
表达式包括名称、数字、字符串、`.name` 符号、括号分组、成员访问与位置参数调用、一元负号以及四则运算。优先级从高到低为:
1. 成员访问与调用,可任意连续组合,例如 factory(1).build(.title)("日文")。
2. 一元负号,可嵌套。
3. 乘除,从左到右结合。
4. 加减,从左到右结合。
符号字面量、名称与成员分别保留为独立 AST 结构。true、false、Nil 等词目前只是普通名称,没有额外的源码字面量或内建值绑定。
use、namespace、trait、agent、完整类型语法、布尔运算、break/continue和闭包声明等尚未加入首版。当前语法可修改;扩展时同步 AST、PEG 规则、样例和测试,不将旧草案视作不可修改的 ABI。
## 诊断和命令
词法和语法错误分别标记为 Lexical/Syntax。语法错误将 PEG 的 token 下标转换成原始源码 Span,保留期望项目列表;EOF 错误使用 source.len() 处的空区间。Diagnostic::location 返回一基行列,列按 Unicode 标量计数,CRLF 只算一次换行,不按字节列或字形簇计数。
`lyra parse <file>` 与 `lyra parse -` 成功时输出 Rust Debug 格式的 AST,失败时在 stderr 输出来源/行/列和诊断并返回非零退出码;`-` 表示从 stdin 读到 EOF,不是 REPL。阅读与来源包装规则见 [阅读器](reader.md)。可用 --mode 选择 module/expression/interactive,用 --json 输出带 schema 标识的 AstDocument;普通输出为 AstRoot Debug 文本,稳定交换格式见 [AST 模型](ast.md)。命令不执行源码、不初始化 VM、不连接数据库;具体运行与本地禁测约定见 [运行](../operations/README.md) 和 [验证](../operations/validation.md)。
当前 Compiler 将 AST 经模块/函数名称分析转换为现有字节码,再交给 VM 解释执行。这是解释器内部的编译/执行准备步骤,不要求使用者先构建程序,见 [执行与版本模型](../architecture/execution-and-versions.md)。MakeFunction 已接入源码函数编译,四则运算、比较和条件/循环已可执行;符号值仍会给出明确编译诊断,见 [编译链路](compiler.md)。源码闭包尚未实现。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。
裸赋值 `name = expression` 创建或更新当前作用域中的名称;它不是表达式,不能链式赋值,属性目标使用独立 MemberAssignment 节点;索引目标尚不支持。`let` 仍表示声明,保留重复声明检查;名称与初始化规则见 [Compiler](compiler.md)。两种源码写法产生独立的 Binding/Assignment 节点,宿主不改写输入。
源码类实现子集采用 `class Name: Base { field: TypeName function method(self: Name): ResultType { ... } }`;基类可省略,执行环境须提供 Object。成员函数沿用既有 function 语法,接收者 self 为显式首参数。普通类字段按基类字段、再本类字段的顺序接收位置构造实参。这里记录实现范围,不确认语言文档中的全部候选构造语法;详细限制见[名义类型](types.md)。
`await expression` 解析为 ExprKind::Await(operand),优先级与前缀负号同级,成员/调用绑定更紧;例如await task.result()等待该调用的返回对象,(await task).field先等待task。await按表达式前缀位置识别,不引入async函数声明。执行是否允许等待由调用上下文决定,见字节码VM的Await章节。
## 条件与循环
```endor
function sum(limit: Number): Number {
i = 0
total = 0
while i < limit {
if i < 10 { total = total + i } else { total = total - i }
i = i + 1
}
return total
}
```
比较包括 `== != < <= > >=`,优先级低于四则运算;比较链暂按左结合解析,不提供 Python 式链式比较。条件使用运行时真值;字面量 true/false、and/or/not、for、break/continue 和 else-if 简写暂未加入。可使用比较结果、宿主 Bool,或 0/非零数值。if/while 可出现在函数体与单条 Interactive 输入中;Module 仍只收声明,Interactive 仍只接受一个完整语句。else 接一个花括号块,嵌套 if 写在块内。