1use core::{fmt, ops, str};
4
5use compile_fmt::{Ascii, compile_panic};
6
7#[derive(Debug)]
9#[non_exhaustive]
10pub enum HexColorError {
11 NoHash,
13 InvalidLen,
15 InvalidHexDigit,
17}
18
19impl fmt::Display for HexColorError {
20 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21 formatter.write_str(self.as_str())
22 }
23}
24
25impl HexColorError {
26 const fn as_str(&self) -> &'static str {
27 match self {
28 Self::NoHash => "color string doesn't start with a hash `#`",
29 Self::InvalidLen => "color string has unexpected length (not 4 or 7)",
30 Self::InvalidHexDigit => "color string contains an invalid hex digit",
31 }
32 }
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for HexColorError {}
37
38#[derive(Debug)]
40#[non_exhaustive]
41pub enum ParseErrorKind {
42 UnfinishedStyle,
44 UnsupportedStyle,
46 HexColor(HexColorError),
48 UnfinishedColor,
50 InvalidIndexColor,
52 UnfinishedBackground,
54 BogusDelimiter,
56 NonInitialCopy,
58 NonIsolatedClear,
60 UnsupportedEffect,
62 NegationWithoutCopy,
64 DuplicateSpecifier,
66 RedundantNegation,
68 EscapeInText,
70
71 #[doc(hidden)] SpanOverflow,
73 #[doc(hidden)] TextOverflow,
75}
76
77impl ParseErrorKind {
78 pub(crate) const fn with_pos(self, pos: ops::Range<usize>) -> ParseError {
79 ParseError { kind: self, pos }
80 }
81
82 const fn as_str(&self) -> &'static str {
83 match self {
84 Self::UnfinishedStyle => "unfinished style definition",
85 Self::UnsupportedStyle => "unsupported style specifier",
86 Self::HexColor(err) => err.as_str(),
87 Self::UnfinishedColor => "unfinished color spec",
88 Self::InvalidIndexColor => "invalid indexed color",
89 Self::UnfinishedBackground => "no background specified after `on` keyword",
90 Self::BogusDelimiter => "bogus delimiter",
91 Self::NonInitialCopy => "* (copy) specifier must come first",
92 Self::NonIsolatedClear => "/ (clear) specifier must be the only token",
93 Self::UnsupportedEffect => "unsupported effect",
94 Self::NegationWithoutCopy => "negation without * (copy) specifier",
95 Self::DuplicateSpecifier => "duplicate specifier",
96 Self::RedundantNegation => "redundant negation",
97 Self::EscapeInText => "ANSI escape char 0x1b encountered in text",
98 Self::SpanOverflow => "too many spans",
99 Self::TextOverflow => "too much text",
100 }
101 }
102
103 const fn as_ascii_str(&self) -> Ascii<'static> {
104 Ascii::new(self.as_str())
105 }
106}
107
108impl fmt::Display for ParseErrorKind {
109 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110 formatter.write_str(self.as_str())
111 }
112}
113
114#[derive(Debug)]
116pub struct ParseError {
117 kind: ParseErrorKind,
118 pos: ops::Range<usize>,
119}
120
121impl ParseError {
122 pub const fn kind(&self) -> &ParseErrorKind {
124 &self.kind
125 }
126
127 pub fn pos(&self) -> ops::Range<usize> {
129 self.pos.clone()
130 }
131
132 #[track_caller]
133 pub(crate) const fn compile_panic(self, raw: &str) -> ! {
134 let (_, hl) = raw.as_bytes().split_at(self.pos.start);
135 let (hl, _) = hl.split_at(self.pos.end - self.pos.start);
136 let Ok(hl) = str::from_utf8(hl) else {
137 panic!("internal error: invalid error range");
138 };
139
140 compile_panic!(
141 "invalid styled string at ",
142 self.pos.start => compile_fmt::fmt::<usize>(), "..", self.pos.end => compile_fmt::fmt::<usize>(),
143 " ('", hl => compile_fmt::clip(64, "…"),
144 "'): ", self.kind.as_ascii_str() => compile_fmt::clip_ascii(42, "")
145 );
146 }
147}
148
149impl fmt::Display for ParseError {
150 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(
152 formatter,
153 "invalid styled string at {:?}: {}",
154 self.pos, self.kind
155 )
156 }
157}
158
159#[cfg(feature = "std")]
160impl std::error::Error for ParseError {}