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}