docs(frontend): explain parsing entry points and source diagnostics
This commit is contained in:
@@ -27,7 +27,7 @@ pub struct CompileOptions {
|
||||
|
||||
/// 宿主及 compiler 集成测试的源码编译入口:按指定模式解析,再校验 AST、解析名称并生成模板。
|
||||
/// source 提供文件身份与原始文本;mode 决定模块、表达式或交互根;options 声明额外宿主名称。
|
||||
/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的位置和片段,成功不执行任何 VM 指令。
|
||||
/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的来源和行列位置,成功不执行任何 VM 指令。
|
||||
pub fn compile_source(
|
||||
source: &SourceUnit,
|
||||
mode: ParseMode,
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
//! compiler 和宿主共用的语法树门面:生成类型、可定制访问器、版本化 JSON 文档及源码位置辅助接口。
|
||||
//! AST types and walkers generated from the single Endor.asdl schema.
|
||||
//! Runtime objects remain in the objects crate.
|
||||
|
||||
// 类型及 Spanned 实现来自 Endor.asdl 对应生成器,避免前端与编辑器各维护一套结构。
|
||||
mod generated;
|
||||
// 公开借用节点的遍历协议,文档校验和宿主工具可覆盖访问并积累自己的状态。
|
||||
pub mod visitor;
|
||||
// JSON 编解码、schema 和跨度/父子范围校验通过此模块实现。
|
||||
mod wire;
|
||||
|
||||
// 宿主和编译器从 ast 直接引用 Program/Expr/Stmt 等生成类型。
|
||||
pub use generated::*;
|
||||
// 文档验证失败使用统一错误类型,不等同于 VM 执行错误。
|
||||
pub use wire::AstError;
|
||||
|
||||
use crate::{SourceLocation, Span};
|
||||
|
||||
/// A syntax node's half-open byte range in its original UTF-8 source.
|
||||
/// 生成 AST 节点实现的原文定位协议,编译分析、编辑器和测试用它取得字节范围或源片段。
|
||||
pub trait Spanned {
|
||||
/// 具体节点实现返回存储的半开字节范围,不自行读取源码。
|
||||
fn span(&self) -> Span;
|
||||
|
||||
/// 宿主/测试按节点范围借用原文;无效 UTF-8 边界或越界返回 None,不分配字符串。
|
||||
fn source_text<'a>(&self, source: &'a str) -> Option<&'a str> {
|
||||
self.span().slice(source)
|
||||
}
|
||||
|
||||
/// 宿主/测试从对应原文计算起点行列;范围不合法时返回 None。
|
||||
fn location(&self, source: &str) -> Option<SourceLocation> {
|
||||
self.span().location(source)
|
||||
}
|
||||
@@ -25,9 +35,11 @@ pub trait Spanned {
|
||||
|
||||
impl Program {
|
||||
/// Iterate the currently supported top-level function declarations.
|
||||
/// 前端 AST 测试或宿主只需顶层函数时使用;保持声明顺序,类及其方法不进入此迭代器。
|
||||
pub fn functions(&self) -> impl Iterator<Item = &Function> {
|
||||
self.declarations
|
||||
.iter()
|
||||
// 仅取函数声明借用,类仍保留在 declarations,调用方可另行遍历。
|
||||
.filter_map(|declaration| match &declaration.kind {
|
||||
DeclKind::Function(function) => Some(function),
|
||||
DeclKind::Class(_) => None,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! lexer、parser、AST 与 compiler 共用的位置/错误协议;字节跨度与用户显示的 Unicode 行列分开保存。
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
@@ -5,55 +6,76 @@ use std::fmt;
|
||||
/// the first and last syntax token. EOF has an empty range at source.len().
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
/// 语法节点及 Token 的半开原文字节范围 [start,end),EOF 使用 source.len() 处的空区间。
|
||||
pub struct Span {
|
||||
// 包含的起始字节偏移,应落在 UTF-8 字符边界。
|
||||
pub start: usize,
|
||||
// 不包含的结束字节偏移,包含两端 Token 之间的注释/空白。
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// 词法/语法规则及测试构造范围,仅保存偏移;合法性由 slice 或 AST 校验验证。
|
||||
pub const fn new(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
/// Safely read this range, rejecting reversed, out-of-bounds or split UTF-8 ranges.
|
||||
/// AST Spanned::source_text 及文档校验调用;安全拒绝倒序、越界或切断 UTF-8 字符的范围。
|
||||
pub fn slice(self, source: &str) -> Option<&str> {
|
||||
source.get(self.start..self.end)
|
||||
}
|
||||
|
||||
/// AST Spanned::location 和编译器调用,先验证整个范围,再计算起点的显示行列。
|
||||
pub fn location(self, source: &str) -> Option<SourceLocation> {
|
||||
self.slice(source)?;
|
||||
Some(source_location(source, self.start))
|
||||
}
|
||||
|
||||
/// parser 组合左右节点及后缀范围时调用;沿用第一个起点和最后一个终点,不另做顺序检查。
|
||||
pub(crate) fn through(self, last: Self) -> Self {
|
||||
Self::new(self.start, last.end)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// 阶段分类供宿主和测试区分错误发生于分词、语法、名称分析还是生成。
|
||||
pub enum DiagnosticKind {
|
||||
// 词法规则无法接受字符或字符串转义等输入。
|
||||
Lexical,
|
||||
// Token 已合法,但无法匹配指定根的语法。
|
||||
Syntax,
|
||||
// 编译名称/控制流分析拒绝重复、未知或未初始化读取。
|
||||
Semantic,
|
||||
// AST 校验或指令生成/验证失败,尚未执行 VM。
|
||||
Compilation,
|
||||
}
|
||||
|
||||
/// Lines and Unicode scalar columns are one-based. CRLF counts as one newline.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// source_location 输出的一基位置;列按 Unicode 标量计数,CRLF 合并为一个换行。
|
||||
pub struct SourceLocation {
|
||||
// 从一开始的逻辑行号,独立 CR 和 LF 均换行,CRLF 只换一次。
|
||||
pub line: usize,
|
||||
// 从一开始的 Unicode 标量列,不是字节数、字素簇数或终端显示宽度。
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 各阶段产生的不带来源快照的错误,SourceUnit::annotate 可补来源和行列。
|
||||
pub struct Diagnostic {
|
||||
// 原始失败阶段,便于测试和宿主按类别处理。
|
||||
pub kind: DiagnosticKind,
|
||||
// 原源码字节位置,Display 直接显示这个范围。
|
||||
pub span: Span,
|
||||
// 面向用户的说明,不含来源名及行列前缀。
|
||||
pub message: String,
|
||||
// 语法解析器报告的期望项;词法和编译器辅助构造通常留空。
|
||||
pub expected: Vec<String>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// lexer 和词法规则共用的失败包装,统一设置 Lexical 类别并保留原文字节范围。
|
||||
pub(crate) fn lexical(span: Span, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: DiagnosticKind::Lexical,
|
||||
@@ -63,12 +85,14 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// SourceUnit::annotate 及宿主根据起始偏移计算位置;本入口不校验整个 span,调用方应传对应原文。
|
||||
pub fn location(&self, source: &str) -> SourceLocation {
|
||||
source_location(source, self.span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Diagnostic {
|
||||
/// 未绑定来源的错误显示说明及字节区间;来源行列由 SourceDiagnostic 的 Display 提供。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
@@ -80,25 +104,36 @@ impl fmt::Display for Diagnostic {
|
||||
|
||||
impl Error for Diagnostic {}
|
||||
|
||||
/// Span::location 和 Diagnostic::location 共用的纯文本扫描,计算 position 前缀的行列,不执行 IO。
|
||||
/// 调用方应提供有效字节边界;此辅助函数只扫描到偏移或 EOF,不负责校验范围。
|
||||
fn source_location(source: &str, position: usize) -> SourceLocation {
|
||||
// 空前缀位于第一行第一列,每消费一个普通 Unicode 标量才前进一列。
|
||||
let mut location = SourceLocation { line: 1, column: 1 };
|
||||
// 记录上一个已消费字符是否 CR,使紧随其后的 LF 不再次增加行号。
|
||||
let mut previous_cr = false;
|
||||
// char_indices 同时提供字节边界和完整字符,避免把多字节字符算成多列。
|
||||
for (offset, character) in source.char_indices() {
|
||||
// 当前字符起点已到目标位置,不能把目标字符本身算进前缀。
|
||||
if offset >= position {
|
||||
break;
|
||||
}
|
||||
// 换行重置列;普通字符(包括 tab)仅按一个 Unicode 标量计列。
|
||||
match character {
|
||||
// CR 自身就是换行,可能随后与 LF 配成一个换行。
|
||||
'\r' => {
|
||||
location.line += 1;
|
||||
location.column = 1;
|
||||
}
|
||||
// CR 已完成换行,这个 LF 不再增加行号。
|
||||
'\n' if previous_cr => {}
|
||||
// 独立 LF 换到下一行第一列。
|
||||
'\n' => {
|
||||
location.line += 1;
|
||||
location.column = 1;
|
||||
}
|
||||
_ => location.column += 1,
|
||||
}
|
||||
// 每轮都更新,只有紧邻 CR 的 LF 才会被合并。
|
||||
previous_cr = character == '\r';
|
||||
}
|
||||
location
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
//! parser 及宿主分词入口;每次交给 PEG 一个剩余源码前缀,再把相对跨度换回原文绝对字节位置。
|
||||
use crate::{Diagnostic, Span, Token, TokenKind};
|
||||
|
||||
/// Lex a reader snapshot and preserve provenance in any diagnostic.
|
||||
/// 宿主对 SourceUnit 分词时调用;失败以快照补来源及行列,成功 Token 仍仅存原文字节跨度。
|
||||
pub fn lex_source(source: &crate::SourceUnit) -> Result<Vec<Token>, crate::SourceDiagnostic> {
|
||||
lex(source.text()).map_err(|error| source.annotate(error))
|
||||
}
|
||||
|
||||
/// Lex the basic Endorphin draft syntax without reading files or changing
|
||||
/// locale. Positions always refer to bytes in this exact UTF-8 source.
|
||||
/// parser 的各语法入口及词法测试调用;只消费传入文本,Token 不持有源码字符串。
|
||||
pub fn lex(source: &str) -> Result<Vec<Token>, Diagnostic> {
|
||||
// 游标从原文零偏移开始,PEG 的每次扫描都在 offset 之后的后缀上进行。
|
||||
let mut lexer = Lexer { source, offset: 0 };
|
||||
// 按原输入顺序保存有效 Token,空白与行注释由词法规则跳过。
|
||||
let mut tokens = Vec::new();
|
||||
// next 返回 None 表示跳过末尾 trivia 后到 EOF;任一诊断立即中止整个分词。
|
||||
while let Some(token) = lexer.next()? {
|
||||
tokens.push(token);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// lex 内部的借用游标,原文保持不变,offset 始终对应已消费前缀的字节数。
|
||||
struct Lexer<'source> {
|
||||
// 原始完整 UTF-8 文本,供后缀扫描和全局错误位置计算。
|
||||
source: &'source str,
|
||||
// 下次扫描的原文起点,包含此前已跳过的空白/注释。
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl Lexer<'_> {
|
||||
/// lex 循环调用;分别处理 PEG 自身失败和词法动作返回的 Diagnostic,二者位置都换回全局偏移。
|
||||
fn next(&mut self) -> Result<Option<Token>, Diagnostic> {
|
||||
// 保存本轮起点,不能用后面已经前进的 offset 修正当前 Token 或错误。
|
||||
let base = self.offset;
|
||||
// PEG 返回消费字节数与 Result<Option<Token>>;规则错误作为值保留,避免回溯改认其他 Token。
|
||||
let (consumed, token) = lexical::next(&self.source[base..]).map_err(|error| {
|
||||
// PEG 自身报错位置相对于后缀,加 base 得到完整原文偏移。
|
||||
let start = base + error.location.offset;
|
||||
// 覆盖当前一个完整 UTF-8 字符;EOF 没有字符,使用空范围。
|
||||
let end = start
|
||||
+ self.source[start..]
|
||||
.chars()
|
||||
@@ -33,8 +47,10 @@ impl Lexer<'_> {
|
||||
.map_or(0, char::len_utf8);
|
||||
Diagnostic::lexical(Span::new(start, end), "unexpected source character")
|
||||
})?;
|
||||
// consumed 包括本轮 trivia 和 Token;词法错误随后返回给 lex,不继续扫描。
|
||||
self.offset += consumed;
|
||||
token
|
||||
// 成功且非 EOF 的 Token 才需要把两个端点加回原文起点。
|
||||
.map(|token| {
|
||||
token.map(|mut token| {
|
||||
token.span.start += base;
|
||||
@@ -42,6 +58,7 @@ impl Lexer<'_> {
|
||||
token
|
||||
})
|
||||
})
|
||||
// 词法动作返回的错误同样使用后缀坐标;转换后与 Token 遵循同一位置协议。
|
||||
.map_err(|mut error| {
|
||||
error.span.start += base;
|
||||
error.span.end += base;
|
||||
@@ -50,11 +67,17 @@ impl Lexer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 的字符串 primary 调用,raw 已去掉引号且转义由 lexer.peg 验证,只解码四种受支持转义。
|
||||
pub(crate) fn decode_string(raw: &str) -> String {
|
||||
// AST 持有解码文本,和 Token 所指向的带引号原文分开保存。
|
||||
let mut result = String::new();
|
||||
// 按 Unicode 字符遍历,普通多字节字符不做额外转换。
|
||||
let mut characters = raw.chars();
|
||||
// 遇反斜杠消耗下一个转义字符,其他字符原样追加。
|
||||
while let Some(character) = characters.next() {
|
||||
// lexer 已保证反斜杠后有合法转义;缺字符或未知字符表示内部阶段契约被破坏。
|
||||
if character == '\\' {
|
||||
// 只支持换行、制表、双引号和反斜杠,不解释额外 Unicode/数字转义。
|
||||
result.push(match characters.next().expect("lexer validated escape") {
|
||||
'n' => '\n',
|
||||
't' => '\t',
|
||||
@@ -69,4 +92,5 @@ pub(crate) fn decode_string(raw: &str) -> String {
|
||||
result
|
||||
}
|
||||
|
||||
// 编译时引入 lexical::next 的 rust-peg 规则实现;运行时由 Lexer::next 调用。
|
||||
include!("lexer.peg");
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
//! 宿主、编译器与测试共用的前端门面:读取不可变源码快照,分词并构造拥有文本的 AST。
|
||||
//! UTF-8 source reader and rust-peg rules for lexing and token-based parsing.
|
||||
//! Produces syntax only; it does not resolve types, generate code or execute VM
|
||||
//! operations. AST data owns its text and does not borrow the source buffer.
|
||||
|
||||
// 公开语法树、访问器及 JSON 文档接口,供编译器分析和编辑器传输使用。
|
||||
pub mod ast;
|
||||
// 统一维护半开字节跨度、Unicode 行列及阶段错误,避免词法/语法各自计算位置。
|
||||
mod diagnostic;
|
||||
// 把 UTF-8 文本转成 Token;rust-peg 词法规则由该模块引入。
|
||||
mod lexer;
|
||||
// 把 Token 序列转成模块、表达式或交互根,不执行类型解析和 VM 操作。
|
||||
mod parser;
|
||||
// 从文本、流、文件或宿主提供的 endor URI 读取原样 UTF-8 快照。
|
||||
mod reader;
|
||||
// 保存源码来源/版本标签及文本,错误附加来源和已经计算的行列位置。
|
||||
mod source;
|
||||
// Token 只存种类与原文跨度,具体文本由解析器从源码读取。
|
||||
mod token;
|
||||
|
||||
// 宿主和编译器可沿用同一套位置与诊断数据结构。
|
||||
pub use diagnostic::{Diagnostic, DiagnosticKind, SourceLocation, Span};
|
||||
// 同时公开裸文本和保留 SourceUnit 来源的分词入口。
|
||||
pub use lexer::{lex, lex_source};
|
||||
// 公开各语法根及来源包装入口;ParseMode 选择语法单位,不维护 REPL 会话。
|
||||
pub use parser::{
|
||||
ParseMode, parse, parse_ast, parse_expression, parse_source, parse_source_ast,
|
||||
parse_source_expression,
|
||||
};
|
||||
// 宿主实现 EndorSourceProvider 掌握 URI 授权与版本选择,前端只消费返回字节。
|
||||
pub use reader::{
|
||||
EndorReader, EndorSourceProvider, ResolvedEndorSource, SourceReadError, SourceReadErrorKind,
|
||||
};
|
||||
// 源码快照和带来源诊断供跨读取/解析/编译阶段传递。
|
||||
pub use source::{SourceDiagnostic, SourceOrigin, SourceUnit};
|
||||
// 公开词法输出,供解析测试及直接 Token 使用者检查种类与字节位置。
|
||||
pub use token::{Token, TokenKind};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! compiler 与宿主使用的 Token 解析门面;grammar.peg 构造拥有文本的 AST,类型名及名称绑定留待编译分析。
|
||||
use crate::ast::{
|
||||
Assignment, AstRoot, BinaryOp, Binding, Block, Class, Decl, DeclKind, Expr, ExprKind, Function,
|
||||
IfStatement, InteractiveInput, MemberAssignment, Name, Parameter, Program, ReturnStatement,
|
||||
@@ -8,17 +9,25 @@ use crate::{Diagnostic, DiagnosticKind, Span, Token, TokenKind, lex};
|
||||
|
||||
/// Select the syntax root without implying any execution or REPL session.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// parse_ast/parse_source_ast 调用方选择的语法根,不表示开启执行器或 REPL 状态。
|
||||
pub enum ParseMode {
|
||||
// 一个或多个顶层函数/类声明。
|
||||
Module,
|
||||
// 恰好一个完整表达式,不能附带其他语句。
|
||||
Expression,
|
||||
// 一个完整的交互语句,可为声明、绑定、赋值、控制流或表达式。
|
||||
Interactive,
|
||||
}
|
||||
|
||||
/// compile_source 和语法测试调用,按 mode 返回 AstRoot;词法失败先返回,语法失败转换为原文跨度诊断。
|
||||
pub fn parse_ast(source: &str, mode: ParseMode) -> Result<AstRoot, Diagnostic> {
|
||||
// 模块/表达式复用专门入口,交互入口直接调用对应 PEG 根规则。
|
||||
match mode {
|
||||
ParseMode::Module => parse(source).map(AstRoot::Module),
|
||||
ParseMode::Expression => parse_expression(source).map(AstRoot::Expression),
|
||||
// 语法只收一个完整提交,不负责多次提交共享环境或未完成输入提示。
|
||||
ParseMode::Interactive => {
|
||||
// 先分词,保留 Token 的原文跨度,供语法错误从 Token 索引映射回字节。
|
||||
let tokens = lex(source)?;
|
||||
syntax::interactive(&tokens, source)
|
||||
.map(AstRoot::Interactive)
|
||||
@@ -27,6 +36,7 @@ pub fn parse_ast(source: &str, mode: ParseMode) -> Result<AstRoot, Diagnostic> {
|
||||
}
|
||||
}
|
||||
|
||||
/// compiler 与宿主的快照入口;语法根由 mode 决定,错误增加该快照的来源和行列。
|
||||
pub fn parse_source_ast(
|
||||
source: &crate::SourceUnit,
|
||||
mode: ParseMode,
|
||||
@@ -35,10 +45,12 @@ pub fn parse_source_ast(
|
||||
}
|
||||
|
||||
/// Parse a reader snapshot, retaining its origin and resolved revision on error.
|
||||
/// 宿主从 Reader 快照解析模块时调用,保留来源及 Endor 版本身份到错误包装。
|
||||
pub fn parse_source(source: &crate::SourceUnit) -> Result<Program, crate::SourceDiagnostic> {
|
||||
parse(source.text()).map_err(|error| source.annotate(error))
|
||||
}
|
||||
|
||||
/// 宿主从快照解析一个表达式时调用,与 parse_expression 共享语法,仅增加错误来源。
|
||||
pub fn parse_source_expression(
|
||||
source: &crate::SourceUnit,
|
||||
) -> Result<Expr, crate::SourceDiagnostic> {
|
||||
@@ -47,21 +59,28 @@ pub fn parse_source_expression(
|
||||
|
||||
/// Parse one or more typed function or class declarations. This is a syntax
|
||||
/// operation: type names and name references are retained without resolution.
|
||||
/// parse_ast 的 Module 分支、parse_source 和语法测试调用;只构造声明树,不注册 VM 全局值。
|
||||
pub fn parse(source: &str) -> Result<Program, Diagnostic> {
|
||||
// 词法诊断原样传播;语法 PEG 处理的是 Token 序列,不直接按 UTF-8 字节推进。
|
||||
let tokens = lex(source)?;
|
||||
syntax::program(&tokens, source).map_err(|error| diagnostic(error, &tokens, source.len()))
|
||||
}
|
||||
|
||||
/// Parse exactly one complete expression, useful independently of declarations.
|
||||
/// parse_ast 的 Expression 分支、快照包装及表达式测试调用;要求整份 Token 输入被消费。
|
||||
pub fn parse_expression(source: &str) -> Result<Expr, Diagnostic> {
|
||||
// 词法诊断原样传播;语法 PEG 处理的是 Token 序列,不直接按 UTF-8 字节推进。
|
||||
let tokens = lex(source)?;
|
||||
syntax::expression(&tokens, source).map_err(|error| diagnostic(error, &tokens, source.len()))
|
||||
}
|
||||
|
||||
/// 各 PEG 语法根的 map_err 调用,把失败 Token 索引映射为原文范围,并保留期望项列表。
|
||||
fn diagnostic(error: peg::error::ParseError<usize>, tokens: &[Token], end: usize) -> Diagnostic {
|
||||
// 命中 Token 则定位该 Token,EOF 或越界索引使用原文尾部空范围。
|
||||
let span = tokens
|
||||
.get(error.location)
|
||||
.map_or(Span::new(end, end), |token| token.span);
|
||||
// PEG 期望项转成拥有字符串,既写入可读消息,也保留供宿主程序化显示。
|
||||
let expected: Vec<String> = error.expected.tokens().map(str::to_owned).collect();
|
||||
Diagnostic {
|
||||
kind: DiagnosticKind::Syntax,
|
||||
@@ -71,6 +90,7 @@ fn diagnostic(error: peg::error::ParseError<usize>, tokens: &[Token], end: usize
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 的交互绑定/局部绑定规则调用,复用 Binding 自身范围包装成语句节点。
|
||||
fn binding_statement(binding: Binding) -> Stmt {
|
||||
Stmt {
|
||||
span: binding.span,
|
||||
@@ -78,6 +98,7 @@ fn binding_statement(binding: Binding) -> Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 的优先级规则调用,把已解析左右操作数包装为二元节点;跨度覆盖两端及中间原文。
|
||||
fn binary(left: Expr, operation: BinaryOp, right: Expr) -> Expr {
|
||||
Expr {
|
||||
span: left.span.through(right.span),
|
||||
@@ -89,17 +110,25 @@ fn binary(left: Expr, operation: BinaryOp, right: Expr) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 暂存一个后缀,postfix 按源码从左到右把链式成员/调用折叠成 AST。
|
||||
enum Suffix {
|
||||
// 点后属性名节点,自身含原文字节跨度。
|
||||
Member(Name),
|
||||
// 参数表达式与右括号范围,后者用于延长整个调用节点的跨度。
|
||||
Call(Vec<Expr>, Span),
|
||||
}
|
||||
|
||||
/// grammar.peg 的 class_member 返回中间分类,再由 class 规则分别收集字段和方法。
|
||||
enum ClassMember {
|
||||
// 字段使用名称加类型名结构,与 Parameter 共用形状。
|
||||
Field(Parameter),
|
||||
// 类方法沿用函数 AST;显式 self 等约束由 compiler::analyze 检查。
|
||||
Method(Function),
|
||||
}
|
||||
|
||||
/// grammar.peg 的 postfix_expression fold 调用;每步把前一个结果作为新的接收对象或被调表达式。
|
||||
fn postfix(expression: Expr, suffix: Suffix) -> Expr {
|
||||
// 成员节点延长到属性名末尾,调用节点延长到右括号,保持连续后缀完整跨度。
|
||||
match suffix {
|
||||
Suffix::Member(name) => Expr {
|
||||
span: expression.span.through(name.span),
|
||||
@@ -118,4 +147,5 @@ fn postfix(expression: Expr, suffix: Suffix) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// 编译时引入 syntax 根规则;上述公开入口与 AST 构造辅助函数在规则中互相调用。
|
||||
include!("grammar.peg");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! Reader 产生 SourceUnit,lexer/parser/compiler 借用其文本;错误在 annotate 时固化来源及行列。
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
@@ -7,15 +8,22 @@ use crate::{Diagnostic, SourceLocation};
|
||||
/// Source provenance, independent of filesystem layout or VM heap handles.
|
||||
/// Endor revisions are opaque labels supplied by the host, not verified hashes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Reader 选择的来源身份,诊断和代码文件名显示使用;Endor revision 是宿主标签,不代表已验证哈希。
|
||||
pub enum SourceOrigin {
|
||||
// 内存文本的宿主显示名,不表示磁盘路径。
|
||||
Text { name: String },
|
||||
// 已读到 EOF 的字节流显示名,流本身不再存于快照。
|
||||
Stream { name: String },
|
||||
// 实际读取时保存的路径,不要求规范化为绝对路径。
|
||||
File { path: PathBuf },
|
||||
// 经宿主解析的 URI 与所选版本身份,前端不推断其命名空间或哈希算法。
|
||||
Endor { uri: String, revision: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for SourceOrigin {
|
||||
/// 诊断与生成代码文件名调用 Display,按来源种类生成可读身份文本。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 文本/流显示名称,文件显示路径,Endor 来源附带版本标签。
|
||||
match self {
|
||||
Self::Text { name } | Self::Stream { name } => formatter.write_str(name),
|
||||
Self::File { path } => write!(formatter, "{}", path.display()),
|
||||
@@ -27,25 +35,33 @@ impl fmt::Display for SourceOrigin {
|
||||
/// An owned, immutable UTF-8 snapshot. Reader adapters preserve the text exactly
|
||||
/// so token byte offsets still refer to the original decoded source.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// EndorReader 构造的拥有型 UTF-8 快照;原文不规范化,以保持 Token 和 AST 字节跨度对应原输入。
|
||||
pub struct SourceUnit {
|
||||
// 来源身份与文本一起保存,供错误和代码元数据使用。
|
||||
origin: SourceOrigin,
|
||||
// 已验证 UTF-8 的原始文本,解析阶段只借用它,不修改换行或空白。
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl SourceUnit {
|
||||
/// Reader 适配器完成解码后调用;将来源及文本移入快照,不再读取外部数据。
|
||||
pub(crate) fn new(origin: SourceOrigin, text: String) -> Self {
|
||||
Self { origin, text }
|
||||
}
|
||||
|
||||
/// 编译器生成文件名、宿主检查来源时借用身份,保留 Endor 的已解析版本。
|
||||
pub fn origin(&self) -> &SourceOrigin {
|
||||
&self.origin
|
||||
}
|
||||
|
||||
/// lexer/parser/compiler 及位置校验借用 UTF-8 文本,返回生命周期依附当前快照。
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// Bind a diagnostic to this snapshot before discarding its source buffer.
|
||||
/// lex_source、parse_source 系列及 compile_source 在失败时调用,把字节诊断绑定到此来源和行列。
|
||||
/// 返回值保存来源、位置及原诊断,不保存源码全文;后续无需再次读源即可显示基本错误。
|
||||
pub fn annotate(&self, diagnostic: Diagnostic) -> SourceDiagnostic {
|
||||
SourceDiagnostic {
|
||||
location: diagnostic.location(&self.text),
|
||||
@@ -56,13 +72,18 @@ impl SourceUnit {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 带来源及预计算位置的阶段错误,由 SourceUnit::annotate 创建,交给宿主显示或追查底层 Diagnostic。
|
||||
pub struct SourceDiagnostic {
|
||||
// 错误对应的来源,独立于原快照生命周期。
|
||||
pub origin: SourceOrigin,
|
||||
// annotate 当时根据原文计算的一基行列,后续 Display 不重新计算。
|
||||
pub location: SourceLocation,
|
||||
// 原始字节跨度、阶段、说明及期望项,保留给程序化处理与 Error::source。
|
||||
pub diagnostic: Box<Diagnostic>,
|
||||
}
|
||||
|
||||
impl fmt::Display for SourceDiagnostic {
|
||||
/// 宿主显示错误时输出“来源:行:列:说明”,跨度及期望项仍可从 diagnostic 单独读取。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
@@ -73,6 +94,7 @@ impl fmt::Display for SourceDiagnostic {
|
||||
}
|
||||
|
||||
impl Error for SourceDiagnostic {
|
||||
/// 标准错误链使用者调用,暴露原始阶段诊断作为本错误的直接原因。
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self.diagnostic.as_ref())
|
||||
}
|
||||
|
||||
@@ -1,35 +1,61 @@
|
||||
//! lexer 产生、parser 消费的轻量 Token;文本通过 span 从原 SourceUnit 借取,不在 Token 中复制。
|
||||
use crate::Span;
|
||||
|
||||
/// Text is located by span in the original source. Names stay contextual: the
|
||||
/// lexer does not turn syntax words into globally reserved keywords.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// lexer.peg 输出的种类;语法关键字仍是 Name,由 grammar.peg 按原文上下文识别。
|
||||
pub enum TokenKind {
|
||||
// Unicode 名称,包含上下文关键字;这里只区分词形,不决定语义用途。
|
||||
Name,
|
||||
// 无符号十进制整数/小数拼写;负号是单独 Token,精确值由编译器构造。
|
||||
Number,
|
||||
/// Span includes quotes. Escapes have been checked, not decoded.
|
||||
// 含引号的字符串范围,转义已校验但未解码;解析时再生成拥有文本。
|
||||
String,
|
||||
// 左圆括号,用于参数、调用或括号表达式。
|
||||
LeftParen,
|
||||
// 右圆括号,调用范围在它之后结束。
|
||||
RightParen,
|
||||
// 左花括号,开始类或语句块。
|
||||
LeftBrace,
|
||||
// 右花括号,结束类或语句块。
|
||||
RightBrace,
|
||||
// 类型注解及继承声明的冒号。
|
||||
Colon,
|
||||
// 参数和实参间的分隔符。
|
||||
Comma,
|
||||
// 成员访问或符号字面量的点。
|
||||
Dot,
|
||||
// 单等号赋值,不与双等号比较混淆。
|
||||
Equal,
|
||||
// 双等号相等比较。
|
||||
EqualEqual,
|
||||
// 不等比较。
|
||||
NotEqual,
|
||||
// 小于比较。
|
||||
Less,
|
||||
// 小于等于比较。
|
||||
LessEqual,
|
||||
// 大于比较。
|
||||
Greater,
|
||||
// 大于等于比较。
|
||||
GreaterEqual,
|
||||
// 加法操作符。
|
||||
Plus,
|
||||
// 减法或一元取负,由语法上下文区分。
|
||||
Minus,
|
||||
// 乘法操作符。
|
||||
Star,
|
||||
// 除法操作符;双斜杠行注释在 trivia 阶段跳过。
|
||||
Slash,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// 一次词法输出;Token 可复制,但只保存种类和字节范围,调用方须保留匹配的原文以读取内容。
|
||||
pub struct Token {
|
||||
// parser 按此选择语法分支,名称关键字额外检查原文。
|
||||
pub kind: TokenKind,
|
||||
// 原始 UTF-8 输入的半开范围,不含前面被跳过的空白/注释。
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user