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 {}