1use crate::error::{ParseError, Reason};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum Token<'a> {
7 Key(&'a str),
9 Value(&'a str),
11 Equals,
13 All,
15 Any,
17 Not,
19 OpenParen,
21 CloseParen,
23 Comma,
25}
26
27impl std::fmt::Display for Token<'_> {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 std::fmt::Debug::fmt(self, f)
30 }
31}
32
33impl Token<'_> {
34 #[inline]
35 fn len(&self) -> usize {
36 match self {
37 Token::Key(s) => s.len(),
38 Token::Value(s) => s.len() + 2,
39 Token::Equals | Token::OpenParen | Token::CloseParen | Token::Comma => 1,
40 Token::All | Token::Any | Token::Not => 3,
41 }
42 }
43}
44
45pub struct Lexer<'a> {
51 pub(super) inner: &'a str,
52 original: &'a str,
53 offset: usize,
54}
55
56impl<'a> Lexer<'a> {
57 pub fn new(text: &'a str) -> Self {
60 let text = if text.starts_with("cfg(") && text.ends_with(')') {
61 &text[4..text.len() - 1]
62 } else {
63 text
64 };
65
66 Self {
67 inner: text,
68 original: text,
69 offset: 0,
70 }
71 }
72}
73
74#[derive(Debug)]
77pub struct LexerToken<'a> {
78 pub token: Token<'a>,
80 pub span: std::ops::Range<usize>,
82}
83
84impl<'a> Iterator for Lexer<'a> {
85 type Item = Result<LexerToken<'a>, ParseError>;
86
87 fn next(&mut self) -> Option<Self::Item> {
88 let non_whitespace_index = match self.inner.find(|c: char| !c.is_whitespace()) {
90 Some(idx) => idx,
91 None => self.inner.len(),
92 };
93
94 self.inner = &self.inner[non_whitespace_index..];
95 self.offset += non_whitespace_index;
96
97 #[inline]
98 fn is_ident_start(ch: char) -> bool {
99 ch == '_' || ch.is_ascii_lowercase() || ch.is_ascii_uppercase()
100 }
101
102 #[inline]
103 fn is_ident_rest(ch: char) -> bool {
104 is_ident_start(ch) || ch.is_ascii_digit()
105 }
106
107 match self.inner.chars().next() {
108 None => None,
109 Some('=') => Some(Ok(Token::Equals)),
110 Some('(') => Some(Ok(Token::OpenParen)),
111 Some(')') => Some(Ok(Token::CloseParen)),
112 Some(',') => Some(Ok(Token::Comma)),
113 Some(c) => {
114 if c == '"' {
115 match self.inner[1..].find('"') {
116 Some(ind) => Some(Ok(Token::Value(&self.inner[1..=ind]))),
117 None => Some(Err(ParseError {
118 original: self.original.to_owned(),
119 span: self.offset..self.original.len(),
120 reason: Reason::UnclosedQuotes,
121 })),
122 }
123 } else if is_ident_start(c) {
124 let substr = match self.inner[1..].find(|c: char| !is_ident_rest(c)) {
125 Some(ind) => &self.inner[..=ind],
126 None => self.inner,
127 };
128
129 match substr {
130 "all" => Some(Ok(Token::All)),
131 "any" => Some(Ok(Token::Any)),
132 "not" => Some(Ok(Token::Not)),
133 other => Some(Ok(Token::Key(other))),
134 }
135 } else {
136 #[allow(clippy::range_plus_one)]
139 Some(Err(ParseError {
140 original: self.original.to_owned(),
141 span: self.offset..self.offset + 1,
142 reason: Reason::Unexpected(&["<key>", "all", "any", "not"]),
143 }))
144 }
145 }
146 }
147 .map(|tok| {
148 tok.map(|tok| {
149 let len = tok.len();
150
151 let start = self.offset;
152 self.inner = &self.inner[len..];
153 self.offset += len;
154
155 LexerToken {
156 token: tok,
157 span: start..self.offset,
158 }
159 })
160 })
161 }
162}