diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 4b5f72d..83a787f 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -27,7 +27,7 @@ pub struct CompileOptions { /// 宿主及 compiler 集成测试的源码编译入口:按指定模式解析,再校验 AST、解析名称并生成模板。 /// source 提供文件身份与原始文本;mode 决定模块、表达式或交互根;options 声明额外宿主名称。 -/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的位置和片段,成功不执行任何 VM 指令。 +/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的来源和行列位置,成功不执行任何 VM 指令。 pub fn compile_source( source: &SourceUnit, mode: ParseMode, diff --git a/crates/frontend/src/ast.rs b/crates/frontend/src/ast.rs index b6cc395..3624a1a 100644 --- a/crates/frontend/src/ast.rs +++ b/crates/frontend/src/ast.rs @@ -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 { 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 { self.declarations .iter() + // 仅取函数声明借用,类仍保留在 declarations,调用方可另行遍历。 .filter_map(|declaration| match &declaration.kind { DeclKind::Function(function) => Some(function), DeclKind::Class(_) => None, diff --git a/crates/frontend/src/diagnostic.rs b/crates/frontend/src/diagnostic.rs index 2ae2495..82fcd87 100644 --- a/crates/frontend/src/diagnostic.rs +++ b/crates/frontend/src/diagnostic.rs @@ -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 { 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, } impl Diagnostic { + /// lexer 和词法规则共用的失败包装,统一设置 Lexical 类别并保留原文字节范围。 pub(crate) fn lexical(span: Span, message: impl Into) -> 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 diff --git a/crates/frontend/src/lexer.rs b/crates/frontend/src/lexer.rs index 41c2d5c..a4e75ae 100644 --- a/crates/frontend/src/lexer.rs +++ b/crates/frontend/src/lexer.rs @@ -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, 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, 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, Diagnostic> { + // 保存本轮起点,不能用后面已经前进的 offset 修正当前 Token 或错误。 let base = self.offset; + // PEG 返回消费字节数与 Result>;规则错误作为值保留,避免回溯改认其他 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"); diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index 4528d88..d7df9d0 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -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}; diff --git a/crates/frontend/src/parser.rs b/crates/frontend/src/parser.rs index 6722bf4..9fb29b2 100644 --- a/crates/frontend/src/parser.rs +++ b/crates/frontend/src/parser.rs @@ -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 { + // 模块/表达式复用专门入口,交互入口直接调用对应 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 { } } +/// 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 { parse(source.text()).map_err(|error| source.annotate(error)) } +/// 宿主从快照解析一个表达式时调用,与 parse_expression 共享语法,仅增加错误来源。 pub fn parse_source_expression( source: &crate::SourceUnit, ) -> Result { @@ -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 { + // 词法诊断原样传播;语法 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 { + // 词法诊断原样传播;语法 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, 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 = error.expected.tokens().map(str::to_owned).collect(); Diagnostic { kind: DiagnosticKind::Syntax, @@ -71,6 +90,7 @@ fn diagnostic(error: peg::error::ParseError, 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, 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"); diff --git a/crates/frontend/src/source.rs b/crates/frontend/src/source.rs index 29408b5..67e92f5 100644 --- a/crates/frontend/src/source.rs +++ b/crates/frontend/src/source.rs @@ -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, } 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()) } diff --git a/crates/frontend/src/token.rs b/crates/frontend/src/token.rs index 6b2a129..df2b55b 100644 --- a/crates/frontend/src/token.rs +++ b/crates/frontend/src/token.rs @@ -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, }