Skip to main content

omgkit_io/
error.rs

1//! 解析错误 —— 带精确位置。
2//!
3//! 一条只说"语法错误"的报错,在几十个字符的 SMILES 上就已经很难定位了。
4//! 精确到列的位置是手写解析器相对生成式解析器的主要收益之一,所以错误类型
5//! 一开始就带位置和可打印的插字号视图。
6
7use core::fmt;
8
9/// 解析失败的原因。
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum ParseErrorKind {
13    /// 空输入
14    Empty,
15    /// 出现了不该出现的字符
16    UnexpectedChar(char),
17    /// 输入提前结束(如方括号未闭合)
18    UnexpectedEnd,
19    /// 元素符号无法识别
20    UnknownElement(String),
21    /// 方括号原子语法错误
22    BadBracketAtom(&'static str),
23    /// 分支括号不匹配
24    UnbalancedParen,
25    /// 分支为空,如 `C()`
26    EmptyBranch,
27    /// 环闭合标号始终未配对
28    UnclosedRingBond(u32),
29    /// 环闭合两端是同一个原子,如 `C11`
30    RingBondToSelf(u32),
31    /// 同一对原子间重复的环闭合键
32    DuplicateRingBond(u32),
33    /// 环闭合两端指定了互相冲突的键级
34    ConflictingRingBondOrder(u32),
35    /// 键符号后面没有原子
36    DanglingBond,
37    /// 数字超出可表示范围
38    NumberOverflow,
39    /// 立体标记的排列序号超出该几何的取值范围,如 `@TB21`
40    StereoPermOutOfRange {
41        /// 几何的书写形式(`TH` / `AL` / `SP` / `TB` / `OH`)
42        geometry: &'static str,
43        /// 写出来的序号
44        got: u32,
45        /// 该几何允许的最大序号
46        max: u32,
47    },
48}
49
50impl fmt::Display for ParseErrorKind {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::Empty => write!(f, "空的 SMILES"),
54            Self::UnexpectedChar(c) => write!(f, "意外的字符 {c:?}"),
55            Self::UnexpectedEnd => write!(f, "输入意外结束"),
56            Self::UnknownElement(s) => write!(f, "无法识别的元素符号 {s:?}"),
57            Self::BadBracketAtom(why) => write!(f, "方括号原子语法错误:{why}"),
58            Self::UnbalancedParen => write!(f, "括号不匹配"),
59            Self::EmptyBranch => write!(f, "空的分支"),
60            Self::UnclosedRingBond(n) => write!(f, "环闭合标号 {n} 未配对"),
61            Self::RingBondToSelf(n) => write!(f, "环闭合标号 {n} 的两端是同一个原子"),
62            Self::DuplicateRingBond(n) => write!(f, "环闭合标号 {n} 在同一对原子间重复"),
63            Self::ConflictingRingBondOrder(n) => {
64                write!(f, "环闭合标号 {n} 的两端指定了冲突的键级")
65            }
66            Self::DanglingBond => write!(f, "键符号后面缺少原子"),
67            Self::NumberOverflow => write!(f, "数值超出范围"),
68            Self::StereoPermOutOfRange { geometry, got, max } => write!(
69                f,
70                "立体标记 @{geometry}{got} 的序号超出范围,@{geometry} 最大为 {max}"
71            ),
72        }
73    }
74}
75
76/// 一次解析失败,携带出错位置。
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct ParseError {
79    /// 失败原因
80    pub kind: ParseErrorKind,
81    /// 出错处的字节偏移(0 起)
82    pub pos: usize,
83    /// 出错时的完整输入,用于渲染上下文
84    pub input: String,
85}
86
87impl ParseError {
88    pub(crate) fn new(kind: ParseErrorKind, pos: usize, input: &[u8]) -> Self {
89        Self {
90            kind,
91            pos,
92            input: String::from_utf8_lossy(input).into_owned(),
93        }
94    }
95
96    /// 渲染成带插字号的两行视图:
97    ///
98    /// ```text
99    /// CC(C)C1CC
100    ///         ^ 环闭合标号 1 未配对
101    /// ```
102    #[must_use]
103    pub fn render(&self) -> String {
104        // 插字号按字符数而非字节数缩进,含中日韩字符时才不会错位
105        let cols = self.input[..self.pos.min(self.input.len())].chars().count();
106        format!("{}\n{}^ {}", self.input, " ".repeat(cols), self.kind)
107    }
108}
109
110impl fmt::Display for ParseError {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(f, "第 {} 字符处:{}", self.pos, self.kind)
113    }
114}
115
116impl std::error::Error for ParseError {}
117
118/// 解析结果。
119pub type Result<T> = core::result::Result<T, ParseError>;