123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- //! The labels command parser.
- //!
- //! This can parse arbitrary input, giving the list of labels added/removed.
- //!
- //! The grammar is as follows:
- //!
- //! ```text
- //! Command: `labels: <label-list>.`
- //!
- //! <label-list>:
- //! - <label-delta>
- //! - <label-delta> and <label-list>
- //! - <label-delta>, <label-list>
- //! - <label-delta>, and <label-list>
- //!
- //! <label-delta>:
- //! - +<label>
- //! - -<label>
- //! this can start with a + or -, but then the only supported way of adding it
- //! is with the previous two variants of this (i.e., ++label and -+label).
- //! - <label>
- //!
- //! <label>: \S+
- //! ```
- #[derive(Debug, PartialEq, Eq)]
- pub enum LabelDelta<'a> {
- Add(Label<'a>),
- Remove(Label<'a>),
- }
- #[derive(Debug, PartialEq, Eq, Copy, Clone)]
- pub struct Label<'a>(&'a str);
- impl<'a> Label<'a> {
- fn parse(input: &'a str) -> Result<Label<'a>, ParseError<'a>> {
- if input.is_empty() {
- Err(ParseError::EmptyLabel)
- } else {
- Ok(Label(input))
- }
- }
- pub fn as_str(&self) -> &'a str {
- self.0
- }
- }
- impl<'a> std::ops::Deref for Label<'a> {
- type Target = str;
- fn deref(&self) -> &str {
- self.0
- }
- }
- impl<'a> LabelDelta<'a> {
- fn parse(input: &mut TokenStream<'a>) -> Result<LabelDelta<'a>, ParseError<'a>> {
- let delta = match input.current() {
- Some(Token::Word(delta)) => {
- input.advance()?;
- delta
- }
- cur => {
- return Err(ParseError::Unexpected {
- found: cur,
- expected: "label delta",
- });
- }
- };
- if delta.starts_with('+') {
- Ok(LabelDelta::Add(Label::parse(&delta[1..])?))
- } else if delta.starts_with('-') {
- Ok(LabelDelta::Remove(Label(&delta[1..])))
- } else {
- Ok(LabelDelta::Add(Label::parse(delta)?))
- }
- }
- }
- #[derive(PartialEq, Eq, Debug)]
- pub enum ParseError<'a> {
- EmptyLabel,
- UnexpectedEnd,
- Unexpected {
- found: Option<Token<'a>>,
- expected: &'static str,
- },
- }
- fn parse_command<'a>(input: &mut TokenStream<'a>) -> Result<Vec<LabelDelta<'a>>, ParseError<'a>> {
- input.eat(Token::Labels, "labels command start")?;
- let mut deltas = Vec::new();
- loop {
- let delta = LabelDelta::parse(input)?;
- deltas.push(delta);
- // optional `, and` separator
- let _ = input.eat(Token::Comma, "");
- let _ = input.eat(Token::And, "");
- if let Some(Token::Dot) = input.current() {
- input.advance()?;
- break;
- }
- }
- Ok(deltas)
- }
- pub fn parse<'a>(input: &'a str) -> Result<Vec<LabelDelta<'a>>, ParseError<'a>> {
- let mut toks = TokenStream::new(input);
- let mut labels = Vec::new();
- while !toks.at_end() {
- if toks.current() == Some(Token::Labels) {
- match parse_command(&mut toks) {
- Ok(deltas) => {
- labels.extend(deltas);
- }
- Err(err) => return Err(err),
- }
- } else {
- // not the labels command
- toks.advance()?;
- }
- }
- Ok(labels)
- }
- #[test]
- fn parse_simple() {
- assert_eq!(
- parse("labels: +T-compiler -T-lang bug."),
- Ok(vec![
- LabelDelta::Add(Label("T-compiler")),
- LabelDelta::Remove(Label("T-lang")),
- LabelDelta::Add(Label("bug")),
- ])
- );
- }
- #[test]
- fn parse_empty() {
- assert_eq!(
- TokenStream::new("labels:+,-,,,."),
- [
- Token::Labels,
- Token::Word("+"),
- Token::Comma,
- Token::Word("-"),
- Token::Comma,
- Token::Word(""),
- Token::Comma,
- Token::Word(""),
- Token::Comma,
- Token::Dot
- ]
- );
- assert_eq!(parse("labels:+,,."), Err(ParseError::EmptyLabel));
- }
- #[test]
- fn parse_no_label_paragraph() {
- assert_eq!(
- parse("Labels do in fact exist but this is not a label paragraph."),
- Ok(vec![]),
- );
- }
- #[test]
- fn parse_multi_label() {
- let para = "
- labels: T-compiler, T-core. However, we probably *don't* want the
- labels: -T-lang and -T-libs.
- This nolabels:bar foo should not be a label statement.
- ";
- assert_eq!(
- parse(para),
- Ok(vec![
- LabelDelta::Add(Label("T-compiler")),
- LabelDelta::Add(Label("T-core")),
- LabelDelta::Remove(Label("T-lang")),
- LabelDelta::Remove(Label("T-libs")),
- ])
- );
- }
- #[test]
- fn parse_no_end() {
- assert_eq!(
- parse("labels: +T-compiler -T-lang bug"),
- Err(ParseError::Unexpected {
- found: None,
- expected: "label delta"
- }),
- );
- }
|