docs(frontend): explain source readers and PEG grammar rules
This commit is contained in:
@@ -1,79 +1,110 @@
|
||||
// parser.rs 编译时引入,生成 syntax 的 Token 解析规则;source 是这些 Token 对应的原始 UTF-8 文本。
|
||||
// 规则只构造拥有文本及原文跨度的 AST,不解析名称/类型,也不执行函数或操作 VM。
|
||||
peg::parser! {
|
||||
// parse/parse_expression/parse_ast 的交互分支调用相应公开根,默认要求完整消费 Token 输入。
|
||||
grammar syntax(source: &str) for [Token] {
|
||||
// 各语法上下文按原文精确匹配 Name;词法器不把这些词全局保留为独立关键字种类。
|
||||
rule keyword(word: &str) -> Span
|
||||
= [Token { kind: TokenKind::Name, span } if &source[span.start..span.end] == word] { span }
|
||||
|
||||
// function 规则的开头;quiet 隐藏内部 Token 细节,失败期望显示可读的 function。
|
||||
rule function_keyword() -> Span
|
||||
= quiet!{keyword("function")} / expected!("'function'")
|
||||
// binding 的开头,同样提供稳定可读的期望项。
|
||||
rule let_keyword() -> Span
|
||||
= quiet!{keyword("let")} / expected!("'let'")
|
||||
// return_statement 的开头及 local_statement 的负向前瞻,避免 return 被普通表达式语句消费。
|
||||
rule return_keyword() -> Span
|
||||
= quiet!{keyword("return")} / expected!("'return'")
|
||||
|
||||
// 声明、类型名及表达式引用共用;从 Token 范围复制名称文本,AST 不借用原源码缓冲。
|
||||
rule name() -> Name
|
||||
= span:quiet!{[Token { kind: TokenKind::Name, span }] { span }} {
|
||||
Name { text: source[span.start..span.end].into(), span }
|
||||
}
|
||||
/ expected!("identifier")
|
||||
|
||||
// 函数参数、调用及括号表达式复用左括号匹配,返回范围供组合节点使用。
|
||||
rule lparen() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::LeftParen, span }] { span }} / expected!("'('")
|
||||
// 参数/调用/括号表达式复用右括号匹配,其终点可成为完整表达式终点。
|
||||
rule rparen() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::RightParen, span }] { span }} / expected!("')'")
|
||||
// 类和 block 的左边界,缺失时报告可读的左花括号期望项。
|
||||
rule lbrace() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::LeftBrace, span }] { span }} / expected!("'{'")
|
||||
// 类和 block 的右边界,节点跨度包含这个闭合括号。
|
||||
rule rbrace() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::RightBrace, span }] { span }} / expected!("'}'")
|
||||
// 类型注解和类继承共用冒号,不额外构造 AST 节点。
|
||||
rule colon()
|
||||
= quiet!{[Token { kind: TokenKind::Colon, .. }]} / expected!("':'")
|
||||
// 参数/实参列表分隔;列表组合器决定空列表及分隔规则。
|
||||
rule comma()
|
||||
= quiet!{[Token { kind: TokenKind::Comma, .. }]} / expected!("','")
|
||||
// 绑定和赋值共用单等号 Token,不接受相等比较 Token。
|
||||
rule equal()
|
||||
= quiet!{[Token { kind: TokenKind::Equal, .. }]} / expected!("'='")
|
||||
// 成员后缀和符号字面量共用点,返回其范围以保留符号起点。
|
||||
rule dot() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::Dot, span }] { span }} / expected!("'.'")
|
||||
// 加法优先级层消费 Plus Token。
|
||||
rule plus()
|
||||
= [Token { kind: TokenKind::Plus, .. }]
|
||||
// 减法及一元取负共用 Minus,前缀负号需要它的跨度作为节点起点。
|
||||
rule minus() -> Span
|
||||
= [Token { kind: TokenKind::Minus, span }] { span }
|
||||
// 乘法优先级层消费 Star Token。
|
||||
rule star()
|
||||
= [Token { kind: TokenKind::Star, .. }]
|
||||
// 除法优先级层消费 Slash,注释已在词法 trivia 中跳过。
|
||||
rule slash()
|
||||
= [Token { kind: TokenKind::Slash, .. }]
|
||||
|
||||
// parse 的模块入口;至少一个顶层函数或类声明,默认拒绝剩余 Token。
|
||||
pub rule program() -> Program
|
||||
= declarations:declaration()+ {
|
||||
// 声明列表由 + 保证非空,根范围从首声明起点延伸到末声明终点。
|
||||
let span = declarations[0].span.through(declarations.last().unwrap().span);
|
||||
Program { declarations, span }
|
||||
}
|
||||
|
||||
// program/interactive_statement 的声明分支;函数直接包装,类则收集基类及成员。
|
||||
rule declaration() -> Decl
|
||||
= function:function() {
|
||||
Decl { span: function.span, kind: DeclKind::Function(function) }
|
||||
}
|
||||
/ start:keyword("class") name:name() base:(colon() base:name() { base })?
|
||||
lbrace() members:class_member()* end:rbrace() {
|
||||
// 按字段出现顺序收集字段参数结构,类的布局稍后由编译器及 VM 解释。
|
||||
let mut fields = Vec::new();
|
||||
// 按方法出现顺序收集函数结构,与字段分开存储但不改变各自顺序。
|
||||
let mut methods = Vec::new();
|
||||
// 解析阶段先统一取得成员,再按中间分类分发到两份 AST 列表。
|
||||
for member in members {
|
||||
// 字段和方法保留各自原节点及跨度,不在这里检查成员重名或 self。
|
||||
match member {
|
||||
ClassMember::Field(field) => fields.push(field),
|
||||
ClassMember::Method(method) => methods.push(method),
|
||||
}
|
||||
}
|
||||
// 类范围包括 class 开头与闭合花括号,中间空白/注释仍属于原文范围。
|
||||
let span = start.through(end);
|
||||
Decl { span, kind: DeclKind::Class(Class { name, base, fields, methods, span }) }
|
||||
}
|
||||
|
||||
// declaration 的类体调用,先尝试完整方法,再尝试名称加类型的字段。
|
||||
rule class_member() -> ClassMember
|
||||
= function:function() { ClassMember::Method(function) }
|
||||
/ field:parameter() { ClassMember::Field(field) }
|
||||
|
||||
// parse_ast 的交互入口;恰好一个完整语句包装为列表,不管理多次提交的环境。
|
||||
pub rule interactive() -> InteractiveInput
|
||||
= statement:interactive_statement() {
|
||||
InteractiveInput { span: statement.span, statements: vec![statement] }
|
||||
}
|
||||
|
||||
// 交互允许声明、控制流、let、属性/普通赋值及表达式,不提供根级 return。
|
||||
rule interactive_statement() -> Stmt
|
||||
= declaration:declaration() {
|
||||
Stmt { span: declaration.span, kind: StmtKind::Declaration(declaration) }
|
||||
@@ -86,24 +117,30 @@ peg::parser! {
|
||||
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
|
||||
}
|
||||
|
||||
// 顶层和类方法共用函数语法,显式参数类型、返回类型及函数体;self 合法性留给分析。
|
||||
rule function() -> Function
|
||||
= start:function_keyword() name:name() lparen()
|
||||
parameters:(parameter() ** comma()) rparen() colon() return_type:name() body:block() {
|
||||
Function { span: start.through(body.span), name, parameters, return_type, body }
|
||||
}
|
||||
|
||||
// 函数参数或类字段共用名称/类型名节点,语法只保留注解文本而不解析类型身份。
|
||||
rule parameter() -> Parameter
|
||||
= name:name() colon() type_name:name() {
|
||||
Parameter { span: name.span.through(type_name.span), name, type_name }
|
||||
}
|
||||
|
||||
// function 和控制流共用块语法,普通语句后可接一个末尾 return;return 当前要求表达式。
|
||||
rule block() -> Block
|
||||
= start:lbrace() locals:local_statement()* returned:return_statement()? end:rbrace() {
|
||||
// 先保留普通语句序列,下面仅在存在末尾 return 时追加,保持源码顺序。
|
||||
let mut statements = locals;
|
||||
// 可选 return 不会被当作普通表达式消费,也不能后接其他块内语句。
|
||||
if let Some(returned) = returned { statements.push(returned); }
|
||||
Block { statements, span: start.through(end) }
|
||||
}
|
||||
|
||||
// 块内语句分派;成员赋值先于普通赋值,最后表达式分支显式排除 return 前缀。
|
||||
rule local_statement() -> Stmt
|
||||
= control:control_statement() { control }
|
||||
/ binding:binding() { binding_statement(binding) }
|
||||
@@ -113,26 +150,34 @@ peg::parser! {
|
||||
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
|
||||
}
|
||||
|
||||
// 交互或块内解析 if/else、while,条件和嵌套 block 都保留为语法树,尚不推断初始化。
|
||||
rule control_statement() -> Stmt
|
||||
= start:keyword("if") condition:expression() body:block()
|
||||
otherwise:(keyword("else") body:block() { body })? {
|
||||
// if 范围有 else 时延伸到 else 块末尾,否则只延伸到 then 块末尾。
|
||||
let span=start.through(otherwise.as_ref().map_or(body.span,|b|b.span));
|
||||
Stmt {span,kind:StmtKind::If(IfStatement {condition,body,otherwise,span})}
|
||||
}
|
||||
/ start:keyword("while") condition:expression() body:block() {
|
||||
// while 范围覆盖关键字、条件及整个循环体。
|
||||
let span=start.through(body.span);
|
||||
Stmt {span,kind:StmtKind::While(WhileStatement {condition,body,span})}
|
||||
}
|
||||
|
||||
// 交互或局部普通赋值;允许语法上未声明目标,具体全局/局部规则由 compiler::analyze 决定。
|
||||
rule assignment() -> Stmt
|
||||
= name:name() equal() value:expression() {
|
||||
// 赋值语句的原文范围从目标名延伸到右值末尾。
|
||||
let span = name.span.through(value.span);
|
||||
Stmt { span, kind: StmtKind::Assignment(Assignment { name, value, span }) }
|
||||
}
|
||||
|
||||
// 先解析后缀目标再检查其最外层确为 Member;错误目标使本规则失败,不执行对象写入。
|
||||
rule member_assignment() -> Stmt
|
||||
= target:postfix_expression() equal() value:expression() {?
|
||||
// 属性赋值保留整个接收对象/属性链及右值的原文范围。
|
||||
let span = target.span.through(value.span);
|
||||
// 只有成员访问可拆成接收对象与字段名,裸名字留给普通赋值分支识别。
|
||||
match target.kind {
|
||||
ExprKind::Member { object, name } => Ok(Stmt {
|
||||
span, kind: StmtKind::MemberAssignment(MemberAssignment { object: *object, name, value, span }),
|
||||
@@ -141,18 +186,23 @@ peg::parser! {
|
||||
}
|
||||
}
|
||||
|
||||
// 交互/局部 let 绑定,范围包括 let 关键字;重复名和右值初始化检查在后续语义分析。
|
||||
rule binding() -> Binding
|
||||
= start:let_keyword() name:name() equal() value:expression() {
|
||||
Binding { span: start.through(value.span), name, value }
|
||||
}
|
||||
|
||||
// block 的可选末尾返回,语法要求一个表达式,构造 ReturnStatement 的 Some 值。
|
||||
rule return_statement() -> Stmt
|
||||
= start:return_keyword() value:expression() {
|
||||
// return 范围从关键字到返回表达式末尾,包括其间空白。
|
||||
let span = start.through(value.span);
|
||||
Stmt { span, kind: StmtKind::Return(ReturnStatement { value: Some(value), span }) }
|
||||
}
|
||||
|
||||
// parse_expression 公开入口及各子表达式共用;precedence 层由低到高组织,二元操作在同层左结合。
|
||||
pub rule expression() -> Expr = precedence! {
|
||||
// 比较层优先级最低;六种比较均复用 binary 包装左右节点和范围。
|
||||
left:(@) [Token {kind:TokenKind::EqualEqual,..}] right:@ {binary(left,BinaryOp::Equal,right)}
|
||||
left:(@) [Token {kind:TokenKind::NotEqual,..}] right:@ {binary(left,BinaryOp::NotEqual,right)}
|
||||
left:(@) [Token {kind:TokenKind::Less,..}] right:@ {binary(left,BinaryOp::Less,right)}
|
||||
@@ -160,42 +210,55 @@ peg::parser! {
|
||||
left:(@) [Token {kind:TokenKind::Greater,..}] right:@ {binary(left,BinaryOp::Greater,right)}
|
||||
left:(@) [Token {kind:TokenKind::GreaterEqual,..}] right:@ {binary(left,BinaryOp::GreaterEqual,right)}
|
||||
--
|
||||
// 加减层高于比较、低于乘除,同层按左结合嵌套。
|
||||
left:(@) plus() right:@ { binary(left, BinaryOp::Add, right) }
|
||||
left:(@) minus() right:@ { binary(left, BinaryOp::Subtract, right) }
|
||||
--
|
||||
// 乘除层先构造节点,再交给外围的加减/比较层。
|
||||
left:(@) star() right:@ { binary(left, BinaryOp::Multiply, right) }
|
||||
left:(@) slash() right:@ { binary(left, BinaryOp::Divide, right) }
|
||||
--
|
||||
// 前缀 await 构造等待节点,是否允许停放由执行上下文决定。
|
||||
start:keyword("await") operand:@ {
|
||||
Expr { span: start.through(operand.span), kind: ExprKind::Await(Box::new(operand)) }
|
||||
}
|
||||
// 前缀取负包住当前高优先级操作数,不在语法阶段计算数值。
|
||||
start:minus() operand:@ {
|
||||
Expr { span: start.through(operand.span), kind: ExprKind::Unary { operation: UnaryOp::Negate, operand: Box::new(operand) } }
|
||||
}
|
||||
--
|
||||
// 后缀表达式位于最高优先级层,先形成调用/成员链,再被一元或二元节点包裹。
|
||||
expression:postfix_expression() { expression }
|
||||
}
|
||||
|
||||
// 优先级最高的 primary 后接零或多个成员/调用后缀,从左到右 fold 保持链式求值结构。
|
||||
rule postfix_expression() -> Expr
|
||||
= expression:primary() suffixes:suffix()* {
|
||||
// parser::postfix 每次把先前结果作为接收对象或被调用表达式,构造 a.b(c).d 一类链。
|
||||
suffixes.into_iter().fold(expression, postfix)
|
||||
}
|
||||
|
||||
// postfix_expression 共用后缀;点后取 Name,调用保存参数列表及右括号范围。
|
||||
rule suffix() -> Suffix
|
||||
= dot() name:name() { Suffix::Member(name) }
|
||||
/ lparen() arguments:(expression() ** comma()) end:rparen() { Suffix::Call(arguments, end) }
|
||||
|
||||
// 原子表达式:括号、符号、数字、字符串或名称;AST 文本均从匹配 Token 原文复制/解码。
|
||||
rule primary() -> Expr
|
||||
= start:lparen() expression:expression() end:rparen() {
|
||||
// 括号不新增语义节点种类,只把子表达式范围扩大到两侧括号。
|
||||
Expr { span: start.through(end), ..expression }
|
||||
}
|
||||
/ start:dot() name:name() {
|
||||
// 点开头的符号与成员后缀不同;前端保留它,编译器当前尚不支持生成该运行时值。
|
||||
Expr { span: start.through(name.span), kind: ExprKind::Symbol(name) }
|
||||
}
|
||||
/ [Token { kind: TokenKind::Number, span }] {
|
||||
// 数值保存原始十进制拼写,精确解析与负号处理留给 codegen。
|
||||
Expr { span, kind: ExprKind::Number(source[span.start..span.end].into()) }
|
||||
}
|
||||
/ [Token { kind: TokenKind::String, span }] {
|
||||
// Token 跨度含引号,去掉引号后调用已验证转义的 decode_string,AST 保存解码结果。
|
||||
Expr { span, kind: ExprKind::String(decode_string(&source[span.start + 1..span.end - 1])) }
|
||||
}
|
||||
/ name:name() { Expr { span: name.span, kind: ExprKind::Name(name.text) } }
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
// lexer.rs 编译时引入,生成 lexical::next;输入为本轮尚未消费的 UTF-8 后缀,所有 position! 都相对这个后缀。
|
||||
peg::parser! {
|
||||
// Lexer::next 调用的字符串扫描规则;每轮消费 trivia 后至多一个 Token。
|
||||
grammar lexical() for str {
|
||||
// next 首先跳过 ASCII 空白和双斜杠行注释,不产生 Token;注释在 CR/LF 前结束。
|
||||
rule trivia()
|
||||
= ([' ' | '\t' | '\r' | '\n'] / "//" [^'\r' | '\n']*)*
|
||||
|
||||
// name 首字符允许下划线或 Unicode 字母,不允许数字开头。
|
||||
rule name_start()
|
||||
= [character if character == '_' || character.is_alphabetic()]
|
||||
|
||||
// name 后续字符允许下划线或 Unicode 字母/数字;number 也用它识别非法黏连后缀。
|
||||
rule name_continue()
|
||||
= [character if character == '_' || character.is_alphanumeric()]
|
||||
|
||||
// kind 的名称分支;关键字也返回 Name,具体语法上下文稍后才识别。
|
||||
rule name() -> Result<TokenKind, Diagnostic>
|
||||
= name_start() name_continue()* { Ok(TokenKind::Name) }
|
||||
|
||||
// kind 的十进制数分支;无符号整数后可接点和至少一位小数,负号留给单独操作符。
|
||||
rule number() -> Result<TokenKind, Diagnostic>
|
||||
= start:position!() ['0'..='9']+ ("." ['0'..='9']+)?
|
||||
suffix:$(name_continue()*) end:position!() {
|
||||
// 数字后没有名称字符才是完整数 Token;否则将整段作为词法错误,避免误拆成两个合法 Token。
|
||||
if suffix.is_empty() {
|
||||
Ok(TokenKind::Number)
|
||||
} else {
|
||||
@@ -25,32 +33,42 @@ peg::parser! {
|
||||
}
|
||||
}
|
||||
|
||||
// string 只接受换行、制表、双引号、反斜杠四种反斜杠转义,解码在 parser 的 decode_string 中进行。
|
||||
rule escape()
|
||||
= "\\" ['n' | 't' | '"' | '\\']
|
||||
|
||||
// string 的普通字符不能是引号、反斜杠、原始换行或 NUL;其他 Unicode 字符原样消费。
|
||||
rule string_character()
|
||||
= [^'"' | '\\' | '\r' | '\n' | '\0']
|
||||
|
||||
// string 主体停止后检查闭引号或报告具体错误;start 是开引号位置,供未闭合字符串定位。
|
||||
rule string_end(start: usize) -> Result<(), Diagnostic>
|
||||
= "\"" { Ok(()) }
|
||||
// 主体已消费所有合法转义,剩余反斜杠加字符必是未支持的转义。
|
||||
/ position:position!() "\\" [_] end:position!() {
|
||||
Err(Diagnostic::lexical(Span::new(position, end), "unsupported string escape"))
|
||||
}
|
||||
// 反斜杠后直接 EOF,按从开引号到 EOF 的未闭合范围报告。
|
||||
/ "\\" end:position!() ![_] {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unterminated string"))
|
||||
}
|
||||
// 原始换行/NUL 不可出现在字符串体内,定位具体违规字符。
|
||||
/ position:position!() ['\r' | '\n' | '\0'] end:position!() {
|
||||
Err(Diagnostic::lexical(Span::new(position, end), "unescaped newline or NUL in string"))
|
||||
}
|
||||
// 普通 EOF 也表示缺少闭引号,范围从开引号覆盖到输入结尾。
|
||||
/ end:position!() ![_] {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unterminated string"))
|
||||
}
|
||||
|
||||
// kind 的字符串分支;扫描已验证主体及终结情况,Token 跨度仍包含引号并保持未解码原文。
|
||||
rule string() -> Result<TokenKind, Diagnostic>
|
||||
= start:position!() "\"" (escape() / string_character())* end:string_end(start) {
|
||||
// 闭引号验证通过才确认 String 种类,错误按值继续向上传递。
|
||||
end.map(|()| TokenKind::String)
|
||||
}
|
||||
|
||||
// kind 的操作符/分隔符分支;共享前缀的双字符比较必须放在单字符比较/赋值之前。
|
||||
rule punctuation() -> TokenKind
|
||||
= "(" { TokenKind::LeftParen }
|
||||
/ ")" { TokenKind::RightParen }
|
||||
@@ -71,6 +89,7 @@ peg::parser! {
|
||||
/ "*" { TokenKind::Star }
|
||||
/ "/" { TokenKind::Slash }
|
||||
|
||||
// token 按顺序尝试名称、数字、字符串、标点;未知字符作为错误值返回,避免吞掉后重试别种 Token。
|
||||
rule kind() -> Result<TokenKind, Diagnostic>
|
||||
= name()
|
||||
/ number()
|
||||
@@ -80,6 +99,7 @@ peg::parser! {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unexpected source character"))
|
||||
}
|
||||
|
||||
// next 用此包装单词种类及相对字节范围;只在 kind 成功时生成 Token。
|
||||
rule token() -> Result<Token, Diagnostic>
|
||||
= start:position!() kind:kind() end:position!() {
|
||||
kind.map(|kind| Token { kind, span: Span::new(start, end) })
|
||||
@@ -87,9 +107,12 @@ peg::parser! {
|
||||
|
||||
// Scan only one prefix. Errors are returned as values, so a matched
|
||||
// malformed number/string cannot backtrack into another token kind.
|
||||
// 禁用默认整段消费检查,才能逐个前缀扫描;最终 EOF 由规则内的 ![_] 判断。
|
||||
#[no_eof]
|
||||
// Lexer::next 的唯一公开扫描入口;允许剩余后缀尚有内容,返回本轮消费字节数和 Token/EOF/诊断。
|
||||
pub rule next() -> (usize, Result<Option<Token>, Diagnostic>)
|
||||
= trivia() result:(token:token() { token.map(Some) } / ![_] { Ok(None) }) end:position!() {
|
||||
// end 包括前导 trivia 与当前 Token;Lexer 用它推进游标,再把结果跨度加回本轮原文起点。
|
||||
(end, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! 宿主及前端测试在词法处理前调用的无状态读取适配器;统一验证 UTF-8,原样保留字节解码后的文本与来源。
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
@@ -9,26 +10,34 @@ 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<ResolvedEndorSource>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// provider.load 的拥有型结果;Reader 验证非空版本标签及 UTF-8 后移入 SourceUnit。
|
||||
pub struct ResolvedEndorSource {
|
||||
// 所选版本的原始标签,前端仅检查 trim 后非空,不规范化或验证哈希。
|
||||
pub revision: String,
|
||||
// 该版本的源码字节,尚未验证 UTF-8,也未进行换行或编码转换。
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// 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<String>, text: impl Into<String>) -> SourceUnit {
|
||||
SourceUnit::new(SourceOrigin::Text { name: name.into() }, text.into())
|
||||
}
|
||||
|
||||
/// 宿主/测试把 Read 流读成一个快照;name 仅用于来源显示,读取失败保留流来源名。
|
||||
pub fn from_stream(
|
||||
name: impl Into<String>,
|
||||
reader: impl Read,
|
||||
@@ -36,11 +45,15 @@ impl EndorReader {
|
||||
Self::read(SourceOrigin::Stream { name: name.into() }, reader)
|
||||
}
|
||||
|
||||
/// 宿主/文件测试调用,先保存提供的路径身份再打开文件;打开/读取/解码错误均带相同来源。
|
||||
pub fn from_file(path: impl AsRef<Path>) -> Result<SourceUnit, SourceReadError> {
|
||||
// 借用调用方传入路径用于打开;来源另存拥有副本,不要求先规范化或转为绝对路径。
|
||||
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),
|
||||
@@ -48,33 +61,39 @@ impl EndorReader {
|
||||
Self::read(origin, file)
|
||||
}
|
||||
|
||||
/// 宿主/URI 适配测试调用;前端做基础定位符检查,宿主 provider 选择版本,成功后才解码源码。
|
||||
pub fn from_endor<P: EndorSourceProvider + ?Sized>(
|
||||
uri: &str,
|
||||
provider: &P,
|
||||
) -> Result<SourceUnit, SourceReadError> {
|
||||
// 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,
|
||||
@@ -82,7 +101,9 @@ impl EndorReader {
|
||||
Self::decode(origin, resolved.bytes)
|
||||
}
|
||||
|
||||
/// from_stream/from_file 共用;完整读到 EOF 才尝试 UTF-8 解码,中途失败不返回部分源码快照。
|
||||
fn read(origin: SourceOrigin, mut reader: impl Read) -> Result<SourceUnit, SourceReadError> {
|
||||
// 收集原始字节,不过滤 BOM、换行、空白或 NUL;编码和后续语法各按自己的阶段处理。
|
||||
let mut bytes = Vec::new();
|
||||
reader
|
||||
.read_to_end(&mut bytes)
|
||||
@@ -93,7 +114,9 @@ impl EndorReader {
|
||||
Self::decode(origin, bytes)
|
||||
}
|
||||
|
||||
/// read/from_endor 共用;只接受严格 UTF-8,不做有损替换,成功原样构造拥有快照。
|
||||
fn decode(origin: SourceOrigin, bytes: Vec<u8>) -> Result<SourceUnit, SourceReadError> {
|
||||
// String::from_utf8 失败保留 FromUtf8Error,来源显示中包含文件/流/已解析版本身份。
|
||||
let text = String::from_utf8(bytes).map_err(|error| SourceReadError {
|
||||
origin: origin.to_string(),
|
||||
kind: SourceReadErrorKind::InvalidUtf8(error),
|
||||
@@ -103,22 +126,32 @@ impl EndorReader {
|
||||
}
|
||||
|
||||
#[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!(
|
||||
@@ -137,7 +170,9 @@ impl fmt::Display for SourceReadError {
|
||||
}
|
||||
|
||||
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),
|
||||
|
||||
Reference in New Issue
Block a user