From 05135460d2408b9bf8262a77fc7fc2fb937ed8eb Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Mon, 6 Nov 2023 02:00:53 +0100 Subject: [PATCH] fix: forbid single-element char ranges --- pomsky-lib/src/diagnose/diagnostic_code.rs | 4 ++-- pomsky-lib/src/diagnose/help.rs | 14 +++++++++----- pomsky-syntax/src/error.rs | 6 +++--- pomsky-syntax/src/exprs/char_class/char_group.rs | 2 +- pomsky-syntax/src/parse/parser_impl.rs | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pomsky-lib/src/diagnose/diagnostic_code.rs b/pomsky-lib/src/diagnose/diagnostic_code.rs index 8fb99a6..32583a2 100644 --- a/pomsky-lib/src/diagnose/diagnostic_code.rs +++ b/pomsky-lib/src/diagnose/diagnostic_code.rs @@ -61,7 +61,7 @@ diagnostic_code! { RepetitionChain = 112, CharRangeStringEmpty = 113, CharRangeTooManyCodePoints = 114, - CharClassHasDescendingRange = 115, + CharClassHasNonAscendingRange = 115, CharClassUnknownShorthand = 116, CharClassIllegalNegation = 117, CharClassUnallowedCombination = 118, @@ -152,7 +152,7 @@ impl<'a> From<&'a CharClassError> for DiagnosticCode { match value { E::Empty => Self::EmptyClass, E::CaretInGroup | E::Invalid => Self::UnexpectedToken, - E::DescendingRange(_, _) => Self::CharClassHasDescendingRange, + E::NonAscendingRange(_, _) => Self::CharClassHasNonAscendingRange, E::Unallowed => Self::CharClassUnallowedCombination, E::UnknownNamedClass { .. } => Self::CharClassUnknownShorthand, E::Negative => Self::CharClassIllegalNegation, diff --git a/pomsky-lib/src/diagnose/help.rs b/pomsky-lib/src/diagnose/help.rs index 5e0f425..1ff915a 100644 --- a/pomsky-lib/src/diagnose/help.rs +++ b/pomsky-lib/src/diagnose/help.rs @@ -47,11 +47,15 @@ pub(super) fn get_parser_help( similar: Some(ref similar), .. }) => Some(format!("Perhaps you meant `{similar}`")), - ParseErrorKind::CharClass(CharClassError::DescendingRange(..)) => { - let dash_pos = slice.find('-').unwrap(); - let (part1, part2) = slice.split_at(dash_pos); - let part2 = part2.trim_start_matches('-'); - Some(format!("Switch the characters: {}-{}", part2.trim(), part1.trim())) + ParseErrorKind::CharClass(CharClassError::NonAscendingRange(c1, c2)) => { + if c1 == c2 { + Some(format!("Use a single character: '{c1}'")) + } else { + let dash_pos = slice.find('-').unwrap(); + let (part1, part2) = slice.split_at(dash_pos); + let part2 = part2.trim_start_matches('-'); + Some(format!("Switch the characters: {}-{}", part2.trim(), part1.trim())) + } } ParseErrorKind::CharClass(CharClassError::CaretInGroup) => { Some("Use `![...]` to negate a character class".into()) diff --git a/pomsky-syntax/src/error.rs b/pomsky-syntax/src/error.rs index 035828f..6ac0459 100644 --- a/pomsky-syntax/src/error.rs +++ b/pomsky-syntax/src/error.rs @@ -212,8 +212,8 @@ pub enum CharClassError { /// This error is created when `[^` is encountered. This is a negated /// character class in a regex, but pomsky instead uses the `![` syntax. CaretInGroup, - /// Descending code point range, e.g. `['z'-'a']` - DescendingRange(char, char), + /// Non-ascending code point range, e.g. `['z'-'a']` + NonAscendingRange(char, char), /// Invalid token within a character class Invalid, /// Character class contains incompatible shorthands, e.g. `[. codepoint]` @@ -235,7 +235,7 @@ impl core::fmt::Display for CharClassError { match self { CharClassError::Empty => write!(f, "This character class is empty"), CharClassError::CaretInGroup => write!(f, "`^` is not allowed here"), - &CharClassError::DescendingRange(a, b) => write!( + &CharClassError::NonAscendingRange(a, b) => write!( f, "Character range must be in increasing order, but it is U+{:04X?} - U+{:04X?}", a as u32, b as u32 diff --git a/pomsky-syntax/src/exprs/char_class/char_group.rs b/pomsky-syntax/src/exprs/char_class/char_group.rs index d189ad9..a9791d8 100644 --- a/pomsky-syntax/src/exprs/char_class/char_group.rs +++ b/pomsky-syntax/src/exprs/char_class/char_group.rs @@ -23,7 +23,7 @@ impl CharGroup { /// Tries to create a `CharGroup` from a range of characters (inclusive). /// Returns `None` if `last` is lower than `first`. pub(crate) fn try_from_range(first: char, last: char) -> Option> { - if first <= last { + if first < last { Some(vec![GroupItem::Range { first, last }]) } else { None diff --git a/pomsky-syntax/src/parse/parser_impl.rs b/pomsky-syntax/src/parse/parser_impl.rs index d09ac0d..3e36f9c 100644 --- a/pomsky-syntax/src/parse/parser_impl.rs +++ b/pomsky-syntax/src/parse/parser_impl.rs @@ -588,7 +588,7 @@ impl<'i> Parser<'i> { let last = last.to_char().map_err(|e| e.at(span2))?; let group = CharGroup::try_from_range(first, last).ok_or_else(|| { - PEK::CharClass(CharClassError::DescendingRange(first, last)).at(span1.join(span2)) + PEK::CharClass(CharClassError::NonAscendingRange(first, last)).at(span1.join(span2)) })?; Ok(Some(group)) } else {