use std::rc::Rc;
use std::str::FromStr;
use crate::utilities::FormatError;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct UnicodeRange {
single: UnicodeRangeSegment,
segments: Option<Rc<[UnicodeRangeSegment]>>,
}
impl UnicodeRange {
pub fn new(start: i32, end: i32) -> Self {
Self { single: UnicodeRangeSegment::new(start, end), segments: None }
}
pub fn from_segment(single: UnicodeRangeSegment) -> Self {
Self { single, segments: None }
}
pub fn from_segments(segments: Vec<UnicodeRangeSegment>) -> Self {
if segments.is_empty() {
panic!("segments must not be empty");
}
Self { single: segments[0], segments: Some(Rc::from(segments)) }
}
pub fn default_range() -> Self {
Self::new(0, 0x10FFFD)
}
#[allow(dead_code)] pub(crate) fn single(&self) -> UnicodeRangeSegment {
self.single
}
#[allow(dead_code)] pub(crate) fn segments(&self) -> Option<&[UnicodeRangeSegment]> {
self.segments.as_deref()
}
pub fn is_in_range(&self, value: i32) -> bool {
match &self.segments {
None => self.single.is_in_range(value),
Some(segments) => segments.iter().any(|segment| segment.is_in_range(value)),
}
}
pub fn parse(s: &str) -> Result<UnicodeRange, FormatError> {
if s.is_empty() {
return Err(FormatError::new("Could not parse specified Unicode range."));
}
let mut parts = s.split(',');
if !s.contains(',') {
return Ok(UnicodeRange::from_segment(UnicodeRangeSegment::parse(s)?));
}
let segments = parts.by_ref().map(|part| UnicodeRangeSegment::parse(part.trim())).collect::<Result<Vec<_>, _>>()?;
Ok(UnicodeRange::from_segments(segments))
}
}
impl Default for UnicodeRange {
fn default() -> Self {
Self::default_range()
}
}
impl FromStr for UnicodeRange {
type Err = FormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct UnicodeRangeSegment {
start: i32,
end: i32,
}
impl UnicodeRangeSegment {
pub const fn new(start: i32, end: i32) -> Self {
Self { start, end }
}
pub const fn start(&self) -> i32 {
self.start
}
pub const fn end(&self) -> i32 {
self.end
}
pub const fn is_in_range(&self, value: i32) -> bool {
self.start <= value && value <= self.end
}
fn match_value(s: &str) -> Option<&str> {
let value = s.strip_prefix("u+").or_else(|| s.strip_prefix("U+")).unwrap_or(s);
let bytes = value.as_bytes();
if bytes.is_empty() || bytes.len() > 6 || !bytes[0].is_ascii_hexdigit() {
return None;
}
if bytes[1..].iter().all(|b| b.is_ascii_hexdigit() || *b == b'?') {
Some(value)
} else {
None
}
}
fn parse_hex(value: &str) -> Result<i32, FormatError> {
i32::from_str_radix(value, 16)
.map_err(|_| FormatError::new("Could not parse specified Unicode range segment."))
}
pub fn parse(s: &str) -> Result<UnicodeRangeSegment, FormatError> {
const ERROR: FormatError = FormatError::new("Could not parse specified Unicode range segment.");
if s.is_empty() {
return Err(ERROR);
}
let mut parts = s.split('-');
let first = parts.next().unwrap_or("");
let second = parts.next();
if parts.next().is_some() {
return Err(ERROR);
}
match second {
None => {
let single = Self::match_value(first).ok_or(ERROR)?;
if !single.contains('?') {
let start = Self::parse_hex(single)?;
Ok(UnicodeRangeSegment::new(start, start))
} else {
let start = Self::parse_hex(&single.replace('?', "0"))?;
let end = Self::parse_hex(&single.replace('?', "F"))?;
Ok(UnicodeRangeSegment::new(start, end))
}
}
Some(second) => {
let first = Self::match_value(first).ok_or(ERROR)?;
let second = Self::match_value(second).ok_or(ERROR)?;
Ok(UnicodeRangeSegment::new(Self::parse_hex(first)?, Self::parse_hex(second)?))
}
}
}
}
impl FromStr for UnicodeRangeSegment {
type Err = FormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_parse_segments() {
let range = UnicodeRange::parse("U+0, U+1, U+2, U+3").unwrap();
let segments = range.segments().expect("the range has segments");
assert_eq!(segments.iter().map(UnicodeRangeSegment::start).collect::<Vec<_>>(), [0, 1, 2, 3]);
}
#[test]
fn should_parse() {
for (s, expected_start, expected_end) in
[("u+00-FF", 0, 255), ("U+00-FF", 0, 255), ("U+00-U+FF", 0, 255), ("U+AB??", 43776, 44031)]
{
let segment = UnicodeRangeSegment::parse(s).unwrap();
assert_eq!(segment.start(), expected_start, "{s}");
assert_eq!(segment.end(), expected_end, "{s}");
}
}
#[test]
fn in_range_should_return_false_for_values_outside_range() {
let segment = UnicodeRangeSegment::new(20, 25);
for value in [0, 19, 26, 100] {
assert!(!segment.is_in_range(value));
}
}
#[test]
fn in_range_should_return_true_for_values_within_range() {
let segment = UnicodeRangeSegment::new(20, 22);
for value in [20, 21, 22] {
assert!(segment.is_in_range(value));
}
}
#[test]
fn malformed_ranges_are_errors() {
for s in ["", "U+", "U+G", "U+1-2-3", "U+1234567", "x"] {
assert!(UnicodeRangeSegment::parse(s).is_err(), "{s}");
}
assert!(UnicodeRange::parse("").is_err());
assert!(UnicodeRange::parse("U+1, x").is_err());
assert!(UnicodeRange::default().is_in_range(0x10FFFD));
assert!(UnicodeRange::parse("U+41").unwrap().is_in_range(0x41));
}
}