//! 宿主及前端测试在词法处理前调用的无状态读取适配器;统一验证 UTF-8,原样保留字节解码后的文本与来源。 use std::error::Error; use std::fmt; use std::fs::File; use std::io::{self, Read}; use std::path::Path; use std::string::FromUtf8Error; use crate::{SourceOrigin, SourceUnit}; /// The host resolves names, authorizes access and selects a revision before /// returning its source bytes. No URI path layout or hash algorithm is assumed. /// 宿主实现的 endor URI 解析协议;Reader 不内置仓库、网络、路径布局或版本算法。 pub trait EndorSourceProvider { /// EndorReader::from_endor 在基础 URI 检查后调用;宿主负责授权、定位版本并返回该版本原始字节。 fn load(&self, uri: &str) -> io::Result; } #[derive(Debug, Clone, PartialEq, Eq)] /// provider.load 的拥有型结果;Reader 验证非空版本标签及 UTF-8 后移入 SourceUnit。 pub struct ResolvedEndorSource { // 所选版本的原始标签,前端仅检查 trim 后非空,不规范化或验证哈希。 pub revision: String, // 该版本的源码字节,尚未验证 UTF-8,也未进行换行或编码转换。 pub bytes: Vec, } /// Stateless source adapters, before lexing. Streams are read to EOF; session /// prompting and incomplete REPL input detection belong to the interactive host. #[derive(Debug, Clone, Copy, Default)] /// 宿主/测试共用的读取门面;每次调用独立产生快照,流读到 EOF,不保存 REPL 会话状态。 pub struct EndorReader; impl EndorReader { /// 宿主和编译测试提供现成 UTF-8 文本时调用;String 类型已保证编码有效,只包装来源与原文。 pub fn from_text(name: impl Into, text: impl Into) -> SourceUnit { SourceUnit::new(SourceOrigin::Text { name: name.into() }, text.into()) } /// 宿主/测试把 Read 流读成一个快照;name 仅用于来源显示,读取失败保留流来源名。 pub fn from_stream( name: impl Into, reader: impl Read, ) -> Result { Self::read(SourceOrigin::Stream { name: name.into() }, reader) } /// 宿主/文件测试调用,先保存提供的路径身份再打开文件;打开/读取/解码错误均带相同来源。 pub fn from_file(path: impl AsRef) -> Result { // 借用调用方传入路径用于打开;来源另存拥有副本,不要求先规范化或转为绝对路径。 let path = path.as_ref(); // 在打开之前保存来源,连文件不存在或权限错误也能指明请求的路径。 let origin = SourceOrigin::File { path: path.to_owned(), }; // 打开失败属于 IO 错误;成功后与流读取共用 read 和 decode。 let file = File::open(path).map_err(|error| SourceReadError { origin: origin.to_string(), kind: SourceReadErrorKind::Io(error), })?; Self::read(origin, file) } /// 宿主/URI 适配测试调用;前端做基础定位符检查,宿主 provider 选择版本,成功后才解码源码。 pub fn from_endor( uri: &str, provider: &P, ) -> Result { // Only recognize the scheme and reject empty/control-filled locators. // Namespace syntax, revision selectors and percent decoding are host policy. // 协议大小写不敏感,冒号后不能为空,整串不能含空白/控制字符;其余语义交给宿主。 let valid = uri.split_once(':').is_some_and(|(scheme, address)| { scheme.eq_ignore_ascii_case("endor") && !address.is_empty() }) && !uri .chars() .any(|character| character.is_whitespace() || character.is_control()); // 无效定位符先拒绝,不能调用 provider 触发不必要的外部访问。 if !valid { return Err(SourceReadError { origin: uri.into(), kind: SourceReadErrorKind::InvalidEndorUri, }); } // 基础检查通过后委托宿主,宿主解析失败统一包装为带原 URI 的 IO 错误。 let resolved = provider.load(uri).map_err(|error| SourceReadError { origin: uri.into(), kind: SourceReadErrorKind::Io(error), })?; // 没有解析后的版本身份就不能构成稳定来源;仅检查,不把原标签改成 trim 后内容。 if resolved.revision.trim().is_empty() { return Err(SourceReadError { origin: uri.into(), kind: SourceReadErrorKind::MissingRevision, }); } // 保存原 URI 和 provider 返回的版本标签,诊断及生成代码文件名沿用它们。 let origin = SourceOrigin::Endor { uri: uri.into(), revision: resolved.revision, }; Self::decode(origin, resolved.bytes) } /// from_stream/from_file 共用;完整读到 EOF 才尝试 UTF-8 解码,中途失败不返回部分源码快照。 fn read(origin: SourceOrigin, mut reader: impl Read) -> Result { // 收集原始字节,不过滤 BOM、换行、空白或 NUL;编码和后续语法各按自己的阶段处理。 let mut bytes = Vec::new(); reader .read_to_end(&mut bytes) .map_err(|error| SourceReadError { origin: origin.to_string(), kind: SourceReadErrorKind::Io(error), })?; Self::decode(origin, bytes) } /// read/from_endor 共用;只接受严格 UTF-8,不做有损替换,成功原样构造拥有快照。 fn decode(origin: SourceOrigin, bytes: Vec) -> Result { // String::from_utf8 失败保留 FromUtf8Error,来源显示中包含文件/流/已解析版本身份。 let text = String::from_utf8(bytes).map_err(|error| SourceReadError { origin: origin.to_string(), kind: SourceReadErrorKind::InvalidUtf8(error), })?; Ok(SourceUnit::new(origin, text)) } } #[derive(Debug)] /// Reader 的失败分类,区别外部 IO、文本编码及 URI/版本协议问题,尚未进入词法阶段。 pub enum SourceReadErrorKind { // 文件打开、流读取或 provider.load 返回的底层 IO 原因。 Io(io::Error), // 严格 UTF-8 解码失败,保留有效字节前缀位置及原始错误对象。 InvalidUtf8(FromUtf8Error), // Reader 的基础 endor URI 检查不通过,没有调用 provider。 InvalidEndorUri, // provider 成功返回字节但未提供非空版本标签,不能创建已解析来源。 MissingRevision, } #[derive(Debug)] /// Reader 公开返回给宿主的错误,拥有来源显示文本与具体原因,可沿标准 Error 链追查底层失败。 pub struct SourceReadError { // 错误发生时固化的来源字符串,不依赖失败的输入流或 SourceUnit 仍存在。 pub origin: String, // 分类及可能的底层错误对象,供宿主程序化判断。 pub kind: SourceReadErrorKind, } impl fmt::Display for SourceReadError { /// 宿主显示错误时先输出来源,再按类别显示底层说明、无效 UTF-8 字节位置或协议错误。 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "{}: ", self.origin)?; // IO/编码错误保留底层原因;URI 和缺版本是 Reader 自身协议错误。 match &self.kind { SourceReadErrorKind::Io(error) => write!(formatter, "{error}"), SourceReadErrorKind::InvalidUtf8(error) => write!( formatter, "invalid UTF-8 at byte {}", error.utf8_error().valid_up_to() ), SourceReadErrorKind::InvalidEndorUri => formatter.write_str( "expected a nonempty endor URI without whitespace or control characters", ), SourceReadErrorKind::MissingRevision => { formatter.write_str("source provider returned no resolved revision") } } } } impl Error for SourceReadError { /// 标准错误链查询入口;IO 与编码错误返回原始原因,Reader 协议错误没有下层异常。 fn source(&self) -> Option<&(dyn Error + 'static)> { // IO/编码错误保留底层原因;URI 和缺版本是 Reader 自身协议错误。 match &self.kind { SourceReadErrorKind::Io(error) => Some(error), SourceReadErrorKind::InvalidUtf8(error) => Some(error), _ => None, } } }