label.rs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. //! The labels command parser.
  2. //!
  3. //! This can parse arbitrary input, giving the list of labels added/removed.
  4. //!
  5. //! The grammar is as follows:
  6. //!
  7. //! ```text
  8. //! Command: `labels: <label-list>.`
  9. //!
  10. //! <label-list>:
  11. //! - <label-delta>
  12. //! - <label-delta> and <label-list>
  13. //! - <label-delta>, <label-list>
  14. //! - <label-delta>, and <label-list>
  15. //!
  16. //! <label-delta>:
  17. //! - +<label>
  18. //! - -<label>
  19. //! this can start with a + or -, but then the only supported way of adding it
  20. //! is with the previous two variants of this (i.e., ++label and -+label).
  21. //! - <label>
  22. //!
  23. //! <label>: \S+
  24. //! ```
  25. #[derive(Debug, PartialEq, Eq)]
  26. pub enum LabelDelta<'a> {
  27. Add(Label<'a>),
  28. Remove(Label<'a>),
  29. }
  30. #[derive(Debug, PartialEq, Eq, Copy, Clone)]
  31. pub struct Label<'a>(&'a str);
  32. impl<'a> Label<'a> {
  33. fn parse(input: &'a str) -> Result<Label<'a>, ParseError<'a>> {
  34. if input.is_empty() {
  35. Err(ParseError::EmptyLabel)
  36. } else {
  37. Ok(Label(input))
  38. }
  39. }
  40. pub fn as_str(&self) -> &'a str {
  41. self.0
  42. }
  43. }
  44. impl<'a> std::ops::Deref for Label<'a> {
  45. type Target = str;
  46. fn deref(&self) -> &str {
  47. self.0
  48. }
  49. }
  50. impl<'a> LabelDelta<'a> {
  51. fn parse(input: &mut TokenStream<'a>) -> Result<LabelDelta<'a>, ParseError<'a>> {
  52. let delta = match input.current() {
  53. Some(Token::Word(delta)) => {
  54. input.advance()?;
  55. delta
  56. }
  57. cur => {
  58. return Err(ParseError::Unexpected {
  59. found: cur,
  60. expected: "label delta",
  61. });
  62. }
  63. };
  64. if delta.starts_with('+') {
  65. Ok(LabelDelta::Add(Label::parse(&delta[1..])?))
  66. } else if delta.starts_with('-') {
  67. Ok(LabelDelta::Remove(Label(&delta[1..])))
  68. } else {
  69. Ok(LabelDelta::Add(Label::parse(delta)?))
  70. }
  71. }
  72. }
  73. #[derive(PartialEq, Eq, Debug)]
  74. pub enum ParseError<'a> {
  75. EmptyLabel,
  76. UnexpectedEnd,
  77. Unexpected {
  78. found: Option<Token<'a>>,
  79. expected: &'static str,
  80. },
  81. }
  82. fn parse_command<'a>(input: &mut TokenStream<'a>) -> Result<Vec<LabelDelta<'a>>, ParseError<'a>> {
  83. input.eat(Token::Labels, "labels command start")?;
  84. let mut deltas = Vec::new();
  85. loop {
  86. let delta = LabelDelta::parse(input)?;
  87. deltas.push(delta);
  88. // optional `, and` separator
  89. let _ = input.eat(Token::Comma, "");
  90. let _ = input.eat(Token::And, "");
  91. if let Some(Token::Dot) = input.current() {
  92. input.advance()?;
  93. break;
  94. }
  95. }
  96. Ok(deltas)
  97. }
  98. pub fn parse<'a>(input: &'a str) -> Result<Vec<LabelDelta<'a>>, ParseError<'a>> {
  99. let mut toks = TokenStream::new(input);
  100. let mut labels = Vec::new();
  101. while !toks.at_end() {
  102. if toks.current() == Some(Token::Labels) {
  103. match parse_command(&mut toks) {
  104. Ok(deltas) => {
  105. labels.extend(deltas);
  106. }
  107. Err(err) => return Err(err),
  108. }
  109. } else {
  110. // not the labels command
  111. toks.advance()?;
  112. }
  113. }
  114. Ok(labels)
  115. }
  116. #[test]
  117. fn parse_simple() {
  118. assert_eq!(
  119. parse("labels: +T-compiler -T-lang bug."),
  120. Ok(vec![
  121. LabelDelta::Add(Label("T-compiler")),
  122. LabelDelta::Remove(Label("T-lang")),
  123. LabelDelta::Add(Label("bug")),
  124. ])
  125. );
  126. }
  127. #[test]
  128. fn parse_empty() {
  129. assert_eq!(
  130. TokenStream::new("labels:+,-,,,."),
  131. [
  132. Token::Labels,
  133. Token::Word("+"),
  134. Token::Comma,
  135. Token::Word("-"),
  136. Token::Comma,
  137. Token::Word(""),
  138. Token::Comma,
  139. Token::Word(""),
  140. Token::Comma,
  141. Token::Dot
  142. ]
  143. );
  144. assert_eq!(parse("labels:+,,."), Err(ParseError::EmptyLabel));
  145. }
  146. #[test]
  147. fn parse_no_label_paragraph() {
  148. assert_eq!(
  149. parse("Labels do in fact exist but this is not a label paragraph."),
  150. Ok(vec![]),
  151. );
  152. }
  153. #[test]
  154. fn parse_multi_label() {
  155. let para = "
  156. labels: T-compiler, T-core. However, we probably *don't* want the
  157. labels: -T-lang and -T-libs.
  158. This nolabels:bar foo should not be a label statement.
  159. ";
  160. assert_eq!(
  161. parse(para),
  162. Ok(vec![
  163. LabelDelta::Add(Label("T-compiler")),
  164. LabelDelta::Add(Label("T-core")),
  165. LabelDelta::Remove(Label("T-lang")),
  166. LabelDelta::Remove(Label("T-libs")),
  167. ])
  168. );
  169. }
  170. #[test]
  171. fn parse_no_end() {
  172. assert_eq!(
  173. parse("labels: +T-compiler -T-lang bug"),
  174. Err(ParseError::Unexpected {
  175. found: None,
  176. expected: "label delta"
  177. }),
  178. );
  179. }