Список синтаксического анализа для ‘вырезать’

Я новичок в Rust и учусь, реализуя свою собственную версию cut. Это фрагмент, который анализирует <list> диапазонов, необходимых для -f, -b, или же -c опции. В соответствующем разделе спецификации говорится:

Приложение должно гарантировать, что список аргументов-опций (см. Параметры -b, -c и -f ниже) является разделенным списком или разделенным списком положительных чисел и диапазонов. Диапазоны могут быть трех видов. Первое — это два положительных числа, разделенных знаком (от низкого до высокого), которые представляют все поля от первого до второго числа. Второе — положительное число, которому предшествует (- высокий), который представляет все поля от номера 1 до этого номера. Третье — положительное число, за которым следует (low-), которое представляет это число до последнего поля включительно. Элементы в списке могут повторяться, перекрываться и указываться в любом порядке, но выбранные байты, символы или поля должны быть записаны в порядке входных данных. Если элемент появляется в списке выбора более одного раза, он должен быть записан ровно один раз.

Меня интересуют советы по написанию более идиоматического Rust (особенно обработка ошибок) и любые другие советы для начинающих программистов на Rust. Спасибо!

use std::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::iter::FromIterator;
use std::num::ParseIntError;

pub type Result<T> = std::result::Result<T, RangeError>;

#[derive(Debug, Eq, PartialEq)]
pub enum RangeError {
    MalformedRangSpec,
    Parse(ParseIntError),
}

impl Display for RangeError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        use RangeError::*;
        let msg = match self {
            MalformedRangSpec => format!("Invalid range spec"),
            Parse(e) => e.to_string(),
        };
        write!(f, "Error: {}", msg)
    }
}

impl From<ParseIntError> for RangeError {
    fn from(e: ParseIntError) -> Self {
        RangeError::Parse(e)
    }
}

impl Error for RangeError {}

#[derive(Debug, Eq, PartialEq, Hash)]
pub enum Range {
    From(usize),
    To(usize),
    Inclusive(usize, usize),
    Singleton(usize),
}

#[derive(Debug, Eq, PartialEq)]
pub struct RangeSet {
    ranges: Vec<Range>,
}

impl RangeSet {
    pub fn from<I: IntoIterator<Item = Range>>(iter: I) -> RangeSet {
        RangeSet {
            ranges: Vec::from_iter(iter),
        }
    }

    pub fn from_spec<T: AsRef<str>>(spec: T) -> Result<RangeSet> {
        // "-5,10,14-17,20-"
        let tuples = spec
            .as_ref()
            .split(|c| c == ',' || c == ' ') // e.g. ["-5", "10", "14-17", "20-"]
            // [[None, Some(5)], [Some(10)], [Some(14), Some(17)], [Some(20), None]]
            .map(|element| {
                element
                    .split('-') // e.g. first iter: ["", 5]
                    .map(|bound| match bound {
                        // e.g. [None, Some("5")]
                        "" => Ok(None),
                        s => {
                            let n: usize = s.parse()?;
                            Ok(Some(n))
                        }
                    })
                    .collect::<Result<Vec<_>>>()
            })
            .collect::<Result<Vec<_>>>()?;

        let ranges: Vec<Range> = tuples
            .iter()
            .map(|range| match range.as_slice() {
                [Some(n)] => Ok(Range::Singleton(*n)),
                [Some(s), Some(e)] => Ok(Range::Inclusive(*s, *e)),
                [Some(s), None] => Ok(Range::From(*s)),
                [None, Some(e)] => Ok(Range::To(*e)),
                _ => Err(RangeError::MalformedRangSpec),
            })
            .collect::<Result<Vec<_>>>()?;

        Ok(RangeSet::from(ranges))
    }

    pub fn contains(&self, n: usize) -> bool {
        if n == 0 {
            // range defined to start at 1
            return false;
        }

        self.ranges.iter().any(|range| match range {
            Range::From(from) => (*from..).contains(&n),
            Range::To(to) => (1..=*to).contains(&n),
            Range::Inclusive(from, to) => (*from..=*to).contains(&n),
            Range::Singleton(s) => s == &n,
        })
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn contains() {
        let r = RangeSet::from(vec![
            Range::From(100),
            Range::Inclusive(50, 60),
            Range::Singleton(40),
            Range::To(10),
        ]);

        for n in 0..1000 {
            match n {
                1..=10 | 40..=40 | 50..=60 | 100..=1000 => {
                    assert!(r.contains(n), "should contain {}", n)
                }
                _ => assert!(!r.contains(n), "shouldn't contain {}", n),
            }
        }
    }

    #[test]
    fn from_spec() {
        let r1 = RangeSet::from(vec![Range::Singleton(1)]);
        let r2 = RangeSet::from_spec("1");
        assert_eq!(Ok(r1), r2);

        let r1 = RangeSet::from(vec![
            Range::To(10),
            Range::Singleton(40),
            Range::Inclusive(50, 60),
            Range::From(100),
        ]);

        let r2 = RangeSet::from_spec("-10,40,50-60,100-");

        assert_eq!(Ok(r1), r2);
    }

    #[test]
    fn from_spec_bad() {
        assert!(RangeSet::from_spec("b").is_err());
        assert!(RangeSet::from_spec("4-5-6").is_err());
    }
}

1 ответ
1

Хорошее использование пользовательских типов RangeError, Range, и RangeSet. Некоторые отзывы:

  • Самая большая проблема с вашим кодом, которую я вижу, заключается в том, что RangeSet::from_spec функция довольно грубая. При осмотре мой диагноз проблемы заключается в том, что вы реализуете функциональность в RangeSet это было бы лучше реализовать на Range. Вот такой рефакторинг:

    impl FromStr for Range {
        type Err = RangeError;
    
        fn from_str(s: &str) -> Result<Range> {
            let bounds = s
                .split('-')
                .map(|bound| match bound {
                    "" => Ok(None),
                    s => Ok(Some(s.parse()?))
                })
                .collect::<Result<Vec<_>>>()?;
            match bounds.as_slice() {
                [Some(n)] => Ok(Range::Singleton(*n)),
                [Some(s), Some(e)] => Ok(Range::Inclusive(*s, *e)),
                [Some(s), None] => Ok(Range::From(*s)),
                [None, Some(e)] => Ok(Range::To(*e)),
                _ => Err(RangeError::MalformedRangSpec),
            }
        }
    }
    impl Range {
        pub fn contains(&self, n: usize) -> bool {
            if n == 0 {
                return false;
            }
            match self {
                Range::From(from) => (*from..).contains(&n),
                Range::To(to) => (1..=*to).contains(&n),
                Range::Inclusive(from, to) => (*from..=*to).contains(&n),
                Range::Singleton(s) => s == &n,
            }
        }
    }
    
  • В match заявления, вы также можете убрать все эти посторонние *s путем сопоставления с образцом на match *x вместо match x, вот как это выглядит:

    impl Range {
        pub fn contains(&self, n: usize) -> bool {
            if n == 0 {
                return false;
            }
            match *self {
                Range::From(from) => (from..).contains(&n),
                Range::To(to) => (1..=to).contains(&n),
                Range::Inclusive(from, to) => (from..=to).contains(&n),
                Range::Singleton(s) => s == n,
            }
        }
    }
    

    и аналогично в fn from_str: match *bounds.as_slice() и [Some(n)] => Ok(Range::Singleton(n)) и Т. Д.

  • в &[Some(s), Some(e)] => Ok(Range::Inclusive(s, e)) случай, возможно, вы хотите проверить, что диапазон нетривиален:

            [Some(s), Some(e)] => {
                if s < e {
                    Ok(Range::Inclusive(s, e))
                } else {
                    Err(RangeError::MalformedRangSpec)
                }
            },
    
  • С этими реализациями на Range, RangeSet становится намного проще:

    impl RangeSet {
        pub fn from<I: IntoIterator<Item = Range>>(iter: I) -> RangeSet {
            RangeSet {
                ranges: Vec::from_iter(iter),
            }
        }
    
        pub fn from_spec<T: AsRef<str>>(spec: T) -> RangeResult<RangeSet> {
            // "-5,10,14-17,20-"
            let ranges = spec
                .as_ref()
                .split(|c| c == ',' || c == ' ')
                // e.g. ["-5", "10", "14-17", "20-"]
                .map(|element| element.parse())
                .collect::<RangeResult<Vec<_>>>()?;
    
            Ok(RangeSet::from(ranges))
        }
    
        pub fn contains(&self, n: usize) -> bool {
            self.ranges.iter().any(|range| range.contains(n))
        }
    }
    
  • В вашей реализации в настоящее время отсутствует интерфейс командной строки. Для этого вы, вероятно, захотите использовать structopt ящик. Стоит учесть, что это может сделать некоторые из ваших функций, в частности RangeSet::from_spec ненужный. В частности, если вы определяете аргумент командной строки типа Vec<Range> StructOpt автоматически определит разумный способ разбора строки на вектор диапазонов (это зависит от Range реализация FromStr что мы и сделали выше).

  • В RangeError реализация — это идиоматический способ обработки ошибок, и он хорошо выглядит. Однако я лично предпочел бы не переопределять стандартные типы библиотек, такие как pub type Result<T> = std::result::Result<T, RangeError>, поскольку это приводит к нестандартным сигнатурам функций, которые нельзя сразу прочитать. Тип ошибки является частью поведения функции, поэтому лучше явно возвращать Result<T, RangeError> в ваших функциях. В качестве альтернативы вы можете просто использовать новое имя:

    pub type RangeResult<T> = Result<Range, RangeError>
    

    (FWIW, я тоже думаю std::fmt::Result и std::io::Result<T> были ошибки.)

Окончательный код:

use std::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::iter::FromIterator;
use std::num::ParseIntError;
use std::str::FromStr;

pub type RangeResult<T> = std::result::Result<T, RangeError>;

#[derive(Debug, Eq, PartialEq)]
pub enum RangeError {
    MalformedRangSpec,
    Parse(ParseIntError),
}

impl Display for RangeError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        use RangeError::*;
        let msg = match self {
            MalformedRangSpec => "Invalid range spec".to_string(),
            Parse(e) => e.to_string(),
        };
        write!(f, "Error: {}", msg)
    }
}

impl From<ParseIntError> for RangeError {
    fn from(e: ParseIntError) -> Self {
        RangeError::Parse(e)
    }
}

impl Error for RangeError {}

#[derive(Debug, Eq, PartialEq, Hash)]
pub enum Range {
    From(usize),
    To(usize),
    Inclusive(usize, usize),
    Singleton(usize),
}

// Added
impl FromStr for Range {
    type Err = RangeError;

    fn from_str(s: &str) -> RangeResult<Range> {
        let bounds = s
            .split('-')
            .map(|bound| match bound {
                "" => Ok(None),
                s => Ok(Some(s.parse()?))
            })
            .collect::<RangeResult<Vec<_>>>()?;
        match *bounds.as_slice() {
            [Some(n)] => Ok(Range::Singleton(n)),
            [Some(s), Some(e)] => {
                if s < e {
                    Ok(Range::Inclusive(s, e))
                } else {
                    Err(RangeError::MalformedRangSpec)
                }
            },
            [Some(s), None] => Ok(Range::From(s)),
            [None, Some(e)] => Ok(Range::To(e)),
            _ => Err(RangeError::MalformedRangSpec),
        }
    }
}
impl Range {
    pub fn contains(&self, n: usize) -> bool {
        if n == 0 {
            return false;
        }
        match *self {
            Range::From(from) => (from..).contains(&n),
            Range::To(to) => (1..=to).contains(&n),
            Range::Inclusive(from, to) => (from..=to).contains(&n),
            Range::Singleton(s) => s == n,
        }
    }
}

#[derive(Debug, Eq, PartialEq)]
pub struct RangeSet {
    ranges: Vec<Range>,
}

impl RangeSet {
    pub fn from<I: IntoIterator<Item = Range>>(iter: I) -> RangeSet {
        RangeSet {
            ranges: Vec::from_iter(iter),
        }
    }

    pub fn from_spec<T: AsRef<str>>(spec: T) -> RangeResult<RangeSet> {
        // "-5,10,14-17,20-"
        let ranges = spec
            .as_ref()
            .split(|c| c == ',' || c == ' ')
            // e.g. ["-5", "10", "14-17", "20-"]
            .map(|element| element.parse())
            .collect::<RangeResult<Vec<_>>>()?;

        Ok(RangeSet::from(ranges))
    }

    pub fn contains(&self, n: usize) -> bool {
        self.ranges.iter().any(|range| range.contains(n))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn contains() {
        let r = RangeSet::from(vec![
            Range::From(100),
            Range::Inclusive(50, 60),
            Range::Singleton(40),
            Range::To(10),
        ]);

        for n in 0..1000 {
            match n {
                1..=10 | 40..=40 | 50..=60 | 100..=1000 => {
                    assert!(r.contains(n), "should contain {}", n)
                }
                _ => assert!(!r.contains(n), "shouldn't contain {}", n),
            }
        }
    }

    #[test]
    fn from_spec() {
        let r1 = RangeSet::from(vec![Range::Singleton(1)]);
        let r2 = RangeSet::from_spec("1");
        assert_eq!(Ok(r1), r2);

        let r1 = RangeSet::from(vec![
            Range::To(10),
            Range::Singleton(40),
            Range::Inclusive(50, 60),
            Range::From(100),
        ]);

        let r2 = RangeSet::from_spec("-10,40,50-60,100-");

        assert_eq!(Ok(r1), r2);
    }

    #[test]
    fn from_spec_bad() {
        assert!(RangeSet::from_spec("b").is_err());
        assert!(RangeSet::from_spec("4-5-6").is_err());
    }
}

  • 1

    Спасибо за понимание! «(FWIW, я также считаю, что std :: fmt :: Result и std :: io :: Result были ошибками.)« Я тоже подумал, что это сбивает с толку, но, похоже, это было условностью. Мне больше нравится ваше предложение.

    — слебарон


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *