use crate::discrete_range_set::{DiscreteRange, DiscreteRangeSet};
pub type Error = Box<dyn std::error::Error>;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CharRange(char, char);
impl Into<DiscreteRange<u32>> for CharRange {
fn into(self) -> DiscreteRange<u32> {
DiscreteRange::new(self.0 as u32, self.1 as u32).unwrap()
}
}
impl CharRange {
pub fn new(start: char, end: char) -> Result<Self> {
if start > end {
return Err("Invalid char range (negative size)".into());
}
Ok(CharRange(start, end))
}
pub fn len(&self) -> u32 {
self.1 as u32 - self.0 as u32 + 1
}
pub fn contains(&self, value: char) -> bool {
value >= self.0 && value <= self.1
}
pub fn try_merge(&self, other: &Self) -> Result<Self> {
if self.1 as u32 + 1 < other.0 as u32 || other.1 as u32 + 1 < self.0 as u32 {
return Err("Disjoint char ranges cannot be merged".into());
}
Ok(CharRange(self.0.min(other.0), self.1.max(other.1)))
}
pub fn char(c: char) -> Self {
CharRange(c, c)
}
pub fn lowercase() -> Self {
CharRange('a', 'z')
}
pub fn uppercase() -> Self {
CharRange('A', 'Z')
}
pub fn digits() -> Self {
CharRange('0', '9')
}
pub fn all() -> Self {
CharRange('\u{0000}', '\u{10FFFF}')
}
}
impl From<(char, char)> for CharRange {
fn from(tuple: (char, char)) -> Self {
CharRange::new(tuple.0, tuple.1).unwrap()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CharRangeSet(DiscreteRangeSet<u32>);
impl CharRangeSet {
pub fn new() -> Self {
CharRangeSet(DiscreteRangeSet::new())
}
pub fn add_range<R: Into<CharRange>>(&mut self, range: R) {
self.0.add_range(range.into());
}
pub fn contains(&self, value: char) -> bool {
self.0.contains(value as u32)
}
pub fn contains_any<I: IntoIterator<Item = char>>(&self, values: I) -> bool {
for v in values {
if self.contains(v) {
return true;
}
}
false
}
pub fn contains_all<I: IntoIterator<Item = char>>(&self, values: I) -> bool {
for v in values {
if !self.contains(v) {
return false;
}
}
true
}
pub fn with_range<R: Into<CharRange>>(mut self, range: R) -> Self {
self.add_range(range);
self
}
pub fn with_lowercase(mut self) -> Self {
self.add_range(CharRange::lowercase());
self
}
pub fn with_uppercase(mut self) -> Self {
self.add_range(CharRange::uppercase());
self
}
pub fn with_digits(mut self) -> Self {
self.add_range(CharRange::digits());
self
}
pub fn with_all(mut self) -> Self {
self.add_range(CharRange::all());
self
}
pub fn with_alpha(mut self) -> Self {
self.add_range(CharRange::lowercase());
self.add_range(CharRange::uppercase());
self
}
pub fn with_alphanumeric(mut self) -> Self {
self.add_range(CharRange::lowercase());
self.add_range(CharRange::uppercase());
self.add_range(CharRange::digits());
self
}
pub fn with_chars<I: IntoIterator<Item = char>>(mut self, chars: I) -> Self {
for c in chars {
self.add_range(CharRange::char(c));
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_range() {
let range = CharRange::new('a', 'd').unwrap();
assert_eq!(range.len(), 4);
assert!(range.contains('b'));
assert!(!range.contains('e'));
let range2 = CharRange::new('c', 'f').unwrap();
let merged = range.try_merge(&range2).unwrap();
assert_eq!(merged, CharRange::new('a', 'f').unwrap());
}
#[test]
fn test_char_range_set() {
let mut set = CharRangeSet::new();
set.add_range(CharRange::new('a', 'd').unwrap());
set.add_range(CharRange::new('f', 'h').unwrap());
set.add_range(CharRange::new('c', 'g').unwrap());
assert!(set.contains('b'));
assert!(set.contains('e'));
assert!(!set.contains('i'));
}
#[test]
fn test_char_range_set_builder() {
let set = CharRangeSet::new()
.with_lowercase()
.with_uppercase()
.with_digits()
.with_chars(vec!['_', '-']);
assert!(set.contains('a'));
assert!(set.contains('Z'));
assert!(set.contains('5'));
assert!(set.contains('_'));
assert!(!set.contains('@'));
assert!(!set.contains(' '));
}
}