pomsky/diagnose/
help.rs

1use pomsky_syntax::{
2    Span,
3    diagnose::{
4        CharClassError, CharStringError, DeprecationWarning, ParseErrorKind, ParseWarningKind,
5        RepetitionError,
6    },
7};
8
9use super::{CompileErrorKind, IllegalNegationKind};
10
11pub(super) fn get_parser_help(
12    kind: &ParseErrorKind,
13    slice: &str,
14    span: &mut Span,
15) -> Option<String> {
16    match kind {
17        ParseErrorKind::LexErrorWithMessage(msg) => msg.get_help(slice),
18        ParseErrorKind::RangeIsNotIncreasing => {
19            let dash_pos = slice.find('-').unwrap();
20            let (part1, part2) = slice.split_at(dash_pos);
21            let part2 = part2.trim_start_matches('-');
22            Some(format!("Switch the numbers: {}-{}", part2.trim(), part1.trim()))
23        }
24        ParseErrorKind::RangeLeadingZeroesVariableLength => {
25            fn get_number(s: &str) -> &str {
26                let digits = s.trim_matches(|c| matches!(c, ' ' | '\'' | '"'));
27                let removed_leading = digits.trim_start_matches('0');
28                if removed_leading.is_empty() {
29                    "0"
30                } else {
31                    removed_leading
32                }
33            }
34
35            let dash_pos = slice.find('-').unwrap();
36            let (part1, part2) = slice.split_at(dash_pos);
37            let part2 = part2.trim_start_matches('-');
38
39            Some(format!(
40                "Precede with a repeated zero: '0'* range '{}'-'{}'",
41                get_number(part1),
42                get_number(part2)
43            ))
44        }
45        ParseErrorKind::CharClass(CharClassError::UnknownNamedClass {
46            extra_in_prefix: true,
47            ..
48        }) => Some("When using the `block:` or `blk:` prefix, the `In` at the beginning needs to be removed".into()),
49        #[cfg(feature = "suggestions")]
50        ParseErrorKind::CharClass(CharClassError::UnknownNamedClass {
51            similar: Some(similar),
52            ..
53        }) => Some(format!("Perhaps you meant `{similar}`")),
54        ParseErrorKind::CharClass(CharClassError::NonAscendingRange(c1, c2)) => {
55            if c1 == c2 {
56                Some(format!("Use a single character: '{c1}'"))
57            } else {
58                let dash_pos = slice.find('-').unwrap();
59                let (part1, part2) = slice.split_at(dash_pos);
60                let part2 = part2.trim_start_matches('-');
61                Some(format!("Switch the characters: {}-{}", part2.trim(), part1.trim()))
62            }
63        }
64        ParseErrorKind::CharClass(CharClassError::CaretInGroup) => {
65            Some("Use `![...]` to negate a character class".into())
66        }
67        ParseErrorKind::CharString(CharStringError::TooManyCodePoints)
68            if slice.trim_matches(&['"', '\''][..]).chars().all(|c| c.is_ascii_digit()) =>
69        {
70            Some(
71                "Try a `range` expression instead:\n\
72                https://pomsky-lang.org/docs/language-tour/ranges/"
73                    .into(),
74            )
75        }
76        ParseErrorKind::KeywordAfterLet(_) => Some("Use a different variable name".into()),
77        ParseErrorKind::UnallowedMultiNot(n) => Some(if n % 2 == 0 {
78            "The number of exclamation marks is even, so you can remove all of them".into()
79        } else {
80            "The number of exclamation marks is odd, so you can remove all of them but one".into()
81        }),
82        ParseErrorKind::LetBindingExists => Some("Use a different name".into()),
83        ParseErrorKind::MissingLetKeyword => Some(format!("Try `let {slice} ...`")),
84        ParseErrorKind::Repetition(RepetitionError::QmSuffix) => Some(
85            "If you meant to make the repetition lazy, append the `lazy` keyword instead.\n\
86                If this is intentional, consider adding parentheses around the inner repetition."
87                .into(),
88        ),
89        ParseErrorKind::Repetition(RepetitionError::Multi) => {
90            Some("Add parentheses around the first repetition.".into())
91        }
92        ParseErrorKind::LonePipe => Some("Add an empty string ('') to match nothing".into()),
93        ParseErrorKind::InvalidEscapeInStringAt(offset) => {
94            let span_start = span.range_unchecked().start;
95            *span = Span::new(span_start + offset - 1, span_start + offset + 1);
96            None
97        }
98        ParseErrorKind::MultipleStringsInTestCase => {
99            Some(r#"Use `in "some string"` to match substrings in a haystack"#.into())
100        }
101        ParseErrorKind::RecursionLimit => Some(
102            "Try a less nested expression. It helps to refactor it using variables:\n\
103                https://pomsky-lang.org/docs/language-tour/variables/"
104                .into(),
105        ),
106        _ => None,
107    }
108}
109
110pub(crate) fn get_parse_warning_help(kind: &ParseWarningKind) -> Option<String> {
111    let ParseWarningKind::Deprecation(d) = kind;
112    match d {
113        DeprecationWarning::ShorthandInRange(c) => {
114            let (desc, name) = match c {
115                '\n' => ("a line feed", "n"),
116                '\r' => ("a carriage return", "r"),
117                '\t' => ("a tab character", "t"),
118                '\u{07}' => ("an alert/bell character", "a"),
119                '\u{1b}' => ("an escape character", "e"),
120                '\u{0c}' => ("a form feed", "f"),
121                _ => return None,
122            };
123            Some(format!("This shorthand matches {desc}, not a '{name}'"))
124        }
125        _ => None,
126    }
127}
128
129pub(super) fn get_compiler_help(kind: &CompileErrorKind, _span: Span) -> Option<String> {
130    match kind {
131        CompileErrorKind::UnknownVariable { found, .. }
132            if found.starts_with('U') && found[1..].chars().all(|c| c.is_ascii_hexdigit()) =>
133        {
134            Some(format!("Perhaps you meant a code point: `U+{cp}`", cp = &found[1..]))
135        }
136
137        #[cfg(feature = "suggestions")]
138        CompileErrorKind::UnknownVariable { similar: Some(similar), .. }
139        | CompileErrorKind::UnknownReferenceName { similar: Some(similar), .. } => {
140            Some(format!("Perhaps you meant `{similar}`"))
141        }
142
143        CompileErrorKind::EmptyClassNegated { group1, group2 } => Some(format!(
144            "The group is empty because it contains both `{group1:?}` and `{group2:?}`, \
145            which together match every code point",
146        )),
147
148        CompileErrorKind::NameUsedMultipleTimes(_) => {
149            Some("Give this group a different name".into())
150        }
151
152        CompileErrorKind::UnknownReferenceNumber(0) => {
153            Some("Capturing group numbers start with 1".into())
154        }
155        CompileErrorKind::RelativeRefZero => Some(
156            "Perhaps you meant `::-1` to refer to the previous or surrounding capturing group"
157                .into(),
158        ),
159
160        CompileErrorKind::DotNetNumberedRefWithMixedGroups => Some(
161            "Use a named reference, or don't mix named and unnamed capturing groups".to_string(),
162        ),
163        CompileErrorKind::NegativeShorthandInAsciiMode | CompileErrorKind::UnicodeInAsciiMode => {
164            Some("Enable Unicode for this expression".into())
165        }
166        CompileErrorKind::IllegalNegation { kind }
167            if !matches!(kind, IllegalNegationKind::DotNetChar(_)) =>
168        {
169            Some(
170                "Only the following expressions can be negated:\n\
171                - character sets\n\
172                - string literals and alternations that match exactly one code point\n\
173                - lookarounds\n\
174                - the `%` word boundary"
175                    .to_string(),
176            )
177        }
178        CompileErrorKind::BadIntersection => Some(
179            "One character sets can be intersected.\n\
180            Parentheses may be required to clarify the parsing order."
181                .to_string(),
182        ),
183        CompileErrorKind::InfiniteRecursion => Some(
184            "A recursive expression must have a branch that \
185            doesn't reach the `recursion`, or can repeat 0 times"
186                .to_string(),
187        ),
188
189        _ => None,
190    }
191}