• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pomsky-lang / pomsky / 12076846628

29 Nov 2024 12:11AM UTC coverage: 81.241% (-1.6%) from 82.811%
12076846628

push

github

Aloso
chore: try linking PCRE2 statically

4296 of 5288 relevant lines covered (81.24%)

406362.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.68
/pomsky-syntax/src/error.rs
1
//! Module containing all the errors that can occur during parsing
2

3
use std::{
4
    fmt,
5
    num::{IntErrorKind, ParseIntError},
6
};
7

8
use crate::{lexer::Token, Span};
9

10
pub use crate::lexer::LexErrorMsg;
11

12
/// An error than can occur only during parsing
13
#[derive(Debug, Clone, PartialEq, Eq)]
14
pub struct ParseError {
15
    pub kind: ParseErrorKind,
16
    pub span: Span,
17
}
18

19
impl core::fmt::Display for ParseError {
20
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
21
        if let Some(std::ops::Range { start, end }) = self.span.range() {
×
22
            write!(f, "{}\n  at {start}..{end}", self.kind)
×
23
        } else {
24
            self.kind.fmt(f)
×
25
        }
26
    }
×
27
}
28

29
/// An error kind (without a span) than can occur only during parsing
30
#[derive(Debug, Clone, PartialEq, Eq)]
31
#[non_exhaustive]
32
pub enum ParseErrorKind {
33
    UnknownToken,
34
    LexErrorWithMessage(LexErrorMsg),
35
    KeywordAfterLet(String),
36
    KeywordAfterColon(String),
37
    NonAsciiIdentAfterColon(char),
38
    GroupNameTooLong(usize),
39
    UnexpectedKeyword(String),
40

41
    Deprecated(DeprecationError),
42

43
    Expected(&'static str),
44
    LeftoverTokens,
45
    ExpectedToken(Token),
46
    RangeIsNotIncreasing,
47
    RangeLeadingZeroesVariableLength,
48
    UnallowedNot,
49
    UnallowedMultiNot(usize),
50
    LonePipe,
51
    LetBindingExists,
52
    MissingLetKeyword,
53
    InvalidEscapeInStringAt(usize),
54
    CharString(CharStringError),
55
    CharClass(CharClassError),
56
    InvalidCodePoint,
57
    Number(NumberError),
58
    Repetition(RepetitionError),
59
    MultipleStringsInTestCase,
60

61
    RecursionLimit,
62
}
63

64
impl ParseErrorKind {
65
    /// Creates a [`ParseError`] from this error kind, and a [`Span`] indicating
66
    /// where the error occurred.
67
    pub fn at(self, span: Span) -> ParseError {
127✔
68
        ParseError { kind: self, span }
127✔
69
    }
127✔
70
}
71

72
impl From<RepetitionError> for ParseErrorKind {
73
    fn from(e: RepetitionError) -> Self {
×
74
        ParseErrorKind::Repetition(e)
×
75
    }
×
76
}
77

78
impl From<CharClassError> for ParseErrorKind {
79
    fn from(e: CharClassError) -> Self {
3✔
80
        ParseErrorKind::CharClass(e)
3✔
81
    }
3✔
82
}
83

84
impl From<DeprecationError> for ParseErrorKind {
85
    fn from(e: DeprecationError) -> Self {
×
86
        ParseErrorKind::Deprecated(e)
×
87
    }
×
88
}
89

90
impl From<NumberError> for ParseErrorKind {
91
    fn from(e: NumberError) -> Self {
×
92
        ParseErrorKind::Number(e)
×
93
    }
×
94
}
95

96
impl std::error::Error for ParseErrorKind {}
97

98
impl core::fmt::Display for ParseErrorKind {
99
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122✔
100
        match self {
122✔
101
            ParseErrorKind::UnknownToken => write!(f, "Unknown token"),
8✔
102
            ParseErrorKind::LexErrorWithMessage(msg) => msg.fmt(f),
48✔
103
            ParseErrorKind::KeywordAfterLet(keyword)
1✔
104
            | ParseErrorKind::UnexpectedKeyword(keyword)
2✔
105
            | ParseErrorKind::KeywordAfterColon(keyword) => {
1✔
106
                write!(f, "Unexpected keyword `{keyword}`")
4✔
107
            }
108
            &ParseErrorKind::NonAsciiIdentAfterColon(char) => {
1✔
109
                let num = char as u32;
1✔
110
                write!(f, "Group name contains illegal code point `{char}` (U+{num:04X}). Group names must be ASCII only.")
1✔
111
            }
112
            &ParseErrorKind::GroupNameTooLong(len) => {
1✔
113
                write!(f, "Group name is too long. It is {len} code points long, but must be at most 32 code points.")
1✔
114
            }
115

116
            ParseErrorKind::Deprecated(deprecation) => deprecation.fmt(f),
×
117

118
            ParseErrorKind::Expected(expected) => write!(f, "Expected {expected}"),
7✔
119
            ParseErrorKind::LeftoverTokens => {
120
                write!(f, "There are leftover tokens that couldn't be parsed")
3✔
121
            }
122
            ParseErrorKind::ExpectedToken(token) => write!(f, "Expected {token}"),
10✔
123
            ParseErrorKind::RangeIsNotIncreasing => {
124
                write!(f, "The first number in a range must be smaller than the second")
1✔
125
            }
126
            ParseErrorKind::RangeLeadingZeroesVariableLength => write!(
1✔
127
                f,
1✔
128
                "Leading zeroes are not allowed, unless both numbers have the same number of digits"
1✔
129
            ),
1✔
130
            ParseErrorKind::UnallowedNot => write!(f, "This code point or range can't be negated"),
1✔
131
            ParseErrorKind::UnallowedMultiNot(_) => {
132
                write!(f, "A shorthand character class can't be negated more than once")
1✔
133
            }
134
            ParseErrorKind::LonePipe => write!(f, "A pipe must be followed by an expression"),
6✔
135
            ParseErrorKind::LetBindingExists => {
136
                write!(f, "A variable with the same name already exists in this scope")
1✔
137
            }
138
            ParseErrorKind::MissingLetKeyword => {
139
                write!(f, "A variable declaration must start with the `let` keyword")
1✔
140
            }
141
            ParseErrorKind::InvalidEscapeInStringAt(_) => {
142
                write!(f, "Unsupported escape sequence in string")
1✔
143
            }
144
            ParseErrorKind::InvalidCodePoint => {
145
                write!(f, "This code point is outside the allowed range")
3✔
146
            }
147
            ParseErrorKind::MultipleStringsInTestCase => {
148
                write!(f, "Test cases can't have multiple strings")
1✔
149
            }
150
            ParseErrorKind::CharString(error) => error.fmt(f),
5✔
151
            ParseErrorKind::CharClass(error) => error.fmt(f),
7✔
152
            ParseErrorKind::Number(error) => error.fmt(f),
2✔
153
            ParseErrorKind::Repetition(error) => error.fmt(f),
7✔
154

155
            ParseErrorKind::RecursionLimit => write!(f, "Recursion limit reached"),
2✔
156
        }
157
    }
122✔
158
}
159

160
/// An error that is returned when a deprecated feature is used
161
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162
pub enum DeprecationError {}
163

164
impl std::error::Error for DeprecationError {}
165

166
impl core::fmt::Display for DeprecationError {
167
    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
168
        match *self {}
169
    }
170
}
171

172
/// An error that relates to a character string
173
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174
#[non_exhaustive]
175
pub enum CharStringError {
176
    /// Empty string in a code point range within a character class, e.g.
177
    /// `[''-'z']`
178
    Empty,
179
    /// String in a code point range within a character class that contains
180
    /// multiple code points, e.g. `['abc'-'z']`
181
    TooManyCodePoints,
182
}
183

184
impl std::error::Error for CharStringError {}
185

186
impl core::fmt::Display for CharStringError {
187
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5✔
188
        let error = match self {
5✔
189
            CharStringError::Empty => "Strings used in ranges can't be empty",
1✔
190
            CharStringError::TooManyCodePoints => {
191
                "Strings used in ranges can only contain 1 code point"
4✔
192
            }
193
        };
194

195
        f.write_str(error)
5✔
196
    }
5✔
197
}
198

199
/// An error that relates to a character class
200
#[derive(Debug, Clone, PartialEq, Eq)]
201
#[non_exhaustive]
202
pub enum CharClassError {
203
    /// Empty character class, i.e. `[]`
204
    Empty,
205
    /// This error is created when `[^` is encountered. This is a negated
206
    /// character class in a regex, but pomsky instead uses the `![` syntax.
207
    CaretInGroup,
208
    /// Non-ascending code point range, e.g. `['z'-'a']`
209
    NonAscendingRange(char, char),
210
    /// Invalid token within a character class
211
    Invalid,
212
    /// Character class contains incompatible shorthands, e.g. `[. codepoint]`
213
    Unallowed,
214
    /// Unknown shorthand character class or Unicode property
215
    UnknownNamedClass {
216
        found: Box<str>,
217
        extra_in_prefix: bool,
218
        #[cfg(feature = "suggestions")]
219
        similar: Option<Box<str>>,
220
    },
221
    /// A character class that can't be negated, e.g. `[!ascii]`
222
    Negative,
223
    /// The character class has a prefix where none is expected, e.g. `[scx:w]`
224
    UnexpectedPrefix,
225
    /// The character class has the wrong prefix, e.g. `[sc:Basic_Latin]` (the correct prefix would be `block:`)
226
    WrongPrefix { expected: &'static str, has_in_prefix: bool },
227
}
228

229
impl std::error::Error for CharClassError {}
230

231
impl core::fmt::Display for CharClassError {
232
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
7✔
233
        match self {
7✔
234
            CharClassError::Empty => write!(f, "This character class is empty"),
1✔
235
            CharClassError::CaretInGroup => write!(f, "`^` is not allowed here"),
2✔
236
            &CharClassError::NonAscendingRange(a, b) => write!(
1✔
237
                f,
1✔
238
                "Character range must be in increasing order, but it is U+{:04X?} - U+{:04X?}",
1✔
239
                a as u32, b as u32
1✔
240
            ),
1✔
241
            CharClassError::Invalid => {
242
                write!(f, "Expected string, range, code point or named character class")
×
243
            }
244
            CharClassError::Unallowed => {
245
                write!(f, "This combination of character classes is not allowed")
×
246
            }
247
            &CharClassError::UnknownNamedClass { ref found, extra_in_prefix, .. } => {
1✔
248
                if extra_in_prefix {
1✔
249
                    write!(f, "Unknown character class `{}`", found.replacen("In", "blk:", 1))
×
250
                } else {
251
                    write!(f, "Unknown character class `{found}`")
1✔
252
                }
253
            }
254
            CharClassError::Negative => write!(f, "This character class can't be negated"),
×
255
            CharClassError::UnexpectedPrefix => {
256
                write!(f, "This character class cannot have a prefix")
1✔
257
            }
258
            &CharClassError::WrongPrefix { expected, has_in_prefix } => {
1✔
259
                if has_in_prefix {
1✔
260
                    write!(
1✔
261
                        f,
1✔
262
                        "This character class has the wrong prefix; it should be {expected},\n\
1✔
263
                        and the `In` at the start should be removed"
1✔
264
                    )
1✔
265
                } else {
266
                    write!(f, "This character class has the wrong prefix; it should be {expected}")
×
267
                }
268
            }
269
        }
270
    }
7✔
271
}
272

273
/// An error that relates to parsing a number
274
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
275
#[non_exhaustive]
276
pub enum NumberError {
277
    /// The parsed string is empty
278
    Empty,
279
    /// The parsed string contains a character that isn't a digit
280
    InvalidDigit,
281
    /// The number is too large to fit in the target integer type
282
    TooLarge,
283
    /// The number is too small to fit in the target integer type
284
    TooSmall,
285
    /// The number is zero, but the target number type can't be zero
286
    Zero,
287
}
288

289
impl From<ParseIntError> for NumberError {
290
    fn from(e: ParseIntError) -> Self {
×
291
        match e.kind() {
×
292
            IntErrorKind::Empty => NumberError::Empty,
×
293
            IntErrorKind::InvalidDigit => NumberError::InvalidDigit,
×
294
            IntErrorKind::PosOverflow => NumberError::TooLarge,
×
295
            IntErrorKind::NegOverflow => NumberError::TooSmall,
×
296
            IntErrorKind::Zero => NumberError::Zero,
×
297
            _ => unimplemented!(),
×
298
        }
299
    }
×
300
}
301

302
impl std::error::Error for NumberError {}
303

304
impl core::fmt::Display for NumberError {
305
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2✔
306
        let error = match self {
2✔
307
            NumberError::Empty => "cannot parse integer from empty string",
1✔
308
            NumberError::InvalidDigit => "invalid digit found in string",
×
309
            NumberError::TooLarge => "number too large",
1✔
310
            NumberError::TooSmall => "number too small",
×
311
            NumberError::Zero => "number would be zero for non-zero type",
×
312
        };
313

314
        f.write_str(error)
2✔
315
    }
2✔
316
}
317

318
/// An error indicating an invalid repetition, e.g. `x{4,2}`
319
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320
pub enum RepetitionError {
321
    /// The second number in the repetition is greater than the first
322
    NotAscending,
323
    /// Question mark after a repetition, e.g. `x{3}?`
324
    QmSuffix,
325
    /// Multiple consecutive repetitions
326
    Multi,
327
}
328

329
impl std::error::Error for RepetitionError {}
330

331
impl core::fmt::Display for RepetitionError {
332
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7✔
333
        let error = match self {
7✔
334
            RepetitionError::NotAscending => "Lower bound can't be greater than the upper bound",
1✔
335
            RepetitionError::QmSuffix => "Unexpected `?` following a repetition",
2✔
336
            RepetitionError::Multi => "Only one repetition allowed",
4✔
337
        };
338

339
        f.write_str(error)
7✔
340
    }
7✔
341
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc