Skip to main content

fmt_lang/
error.rs

1//! Error types: [`RuleError`] for rules that cannot be compiled, and
2//! [`FormatError`] for trees that do not match their source.
3
4use alloc::string::String;
5use core::fmt;
6
7/// Why [`Rules::compile`](crate::Rules::compile) refused a set of rules.
8///
9/// Every variant names the offending kind, so the message can point at the
10/// right line of a sketch.
11///
12/// # Examples
13///
14/// ```
15/// use fmt_lang::{NodeRule, RuleError, Rules};
16///
17/// let err = Rules::new().node(NodeRule::new("objekt")).compile(|_| None::<u8>).unwrap_err();
18/// assert_eq!(err, RuleError::UnknownKind { name: "objekt".into() });
19/// ```
20#[derive(Clone, Debug, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum RuleError {
23    /// A rule names a kind the language does not have. Fix the spelling, or
24    /// mark the rule [`optional`](crate::TokenRule::optional) if it comes from
25    /// a preset meant for several languages.
26    UnknownKind {
27        /// The name as written in the rule.
28        name: String,
29    },
30    /// Two node rules name the same kind. Merge them into one rule.
31    DuplicateNode {
32        /// The kind both rules name.
33        name: String,
34    },
35    /// The indentation step is wider than
36    /// [`MAX_INDENT_STEP`](crate::MAX_INDENT_STEP) columns.
37    IndentTooWide {
38        /// The step that was asked for.
39        columns: u8,
40    },
41    /// A node's separator is also one of its delimiters, so the formatter
42    /// cannot tell the two roles apart. Use distinct kinds.
43    SeparatorIsDelimiter {
44        /// The node rule.
45        node: String,
46    },
47    /// A node rule sets a trailing-separator policy other than
48    /// [`Trailing::Preserve`](crate::Trailing::Preserve) but no delimiters.
49    /// The policy needs the closing delimiter to know where the list ends;
50    /// add [`delimiters`](crate::NodeRule::delimiters) or keep `Preserve`.
51    TrailingNeedsDelimiters {
52        /// The node rule.
53        node: String,
54    },
55}
56
57impl fmt::Display for RuleError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Self::UnknownKind { name } => {
61                write!(f, "format rule names unknown kind `{name}`")
62            }
63            Self::DuplicateNode { name } => {
64                write!(f, "two format rules for node kind `{name}`; merge them")
65            }
66            Self::IndentTooWide { columns } => write!(
67                f,
68                "indentation step of {columns} columns is wider than the maximum of {}",
69                crate::MAX_INDENT_STEP
70            ),
71            Self::SeparatorIsDelimiter { node } => write!(
72                f,
73                "the rule for `{node}` uses the same kind as separator and delimiter"
74            ),
75            Self::TrailingNeedsDelimiters { node } => write!(
76                f,
77                "the rule for `{node}` sets a trailing-separator policy without delimiters"
78            ),
79        }
80    }
81}
82
83impl core::error::Error for RuleError {}
84
85/// Why [`format()`](crate::format) could not format a tree.
86///
87/// The formatter accepts any tree shape, error nodes included; it refuses
88/// only a tree whose tokens do not describe `source`, because formatting such a
89/// tree would lose or invent text.
90///
91/// # Examples
92///
93/// ```
94/// use fmt_lang::syntax_lang::{Element, Node, Span, Token, TokenKind};
95/// use fmt_lang::{format, FormatError, Style};
96///
97/// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
98/// struct Word;
99/// impl TokenKind for Word {}
100///
101/// // A token reaching past the end of the source.
102/// let tree = Node::new(Word, vec![Element::Token(Token::new(Word, Span::new(0, 9)))]);
103/// let err = format(&tree, "short", &Style::default(), 80).unwrap_err();
104/// assert_eq!(err, FormatError::OutOfBounds { start: 0, end: 9, len: 5 });
105/// ```
106#[derive(Clone, Debug, PartialEq, Eq)]
107#[non_exhaustive]
108pub enum FormatError {
109    /// A token's span reaches past the end of the source. The tree was built
110    /// from different text; parse `source` again.
111    OutOfBounds {
112        /// Start of the token's span.
113        start: u32,
114        /// End of the token's span.
115        end: u32,
116        /// Length of the source in bytes.
117        len: usize,
118    },
119    /// A token boundary falls inside a multi-byte character. The tree was
120    /// built from different text; parse `source` again.
121    NotCharBoundary {
122        /// The offending byte offset.
123        offset: u32,
124    },
125    /// Consecutive tokens leave a gap or overlap, so the tree is not lossless:
126    /// text between them would be lost or written twice. Every byte of the
127    /// tree's range must belong to exactly one token (trivia included).
128    NotContiguous {
129        /// Where the next token should have started (the previous token's end).
130        expected: u32,
131        /// Where it actually started.
132        found: u32,
133    },
134}
135
136impl fmt::Display for FormatError {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Self::OutOfBounds { start, end, len } => write!(
140                f,
141                "token span {start}..{end} lies outside the {len}-byte source"
142            ),
143            Self::NotCharBoundary { offset } => write!(
144                f,
145                "token boundary at byte {offset} is inside a multi-byte character"
146            ),
147            Self::NotContiguous { expected, found } => write!(
148                f,
149                "tree is not lossless: a token starts at byte {found}, but the previous one ended at byte {expected}"
150            ),
151        }
152    }
153}
154
155impl core::error::Error for FormatError {}