1 | use crate::error::{ParseError, ParseErrorKind::*}; |
2 | use std::fmt; |
3 | use std::iter; |
4 | use std::str::{self, FromStr}; |
5 | |
6 | /// A cfg expression. |
7 | #[derive (Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] |
8 | pub enum CfgExpr { |
9 | Not(Box<CfgExpr>), |
10 | All(Vec<CfgExpr>), |
11 | Any(Vec<CfgExpr>), |
12 | Value(Cfg), |
13 | } |
14 | |
15 | /// A cfg value. |
16 | #[derive (Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] |
17 | pub enum Cfg { |
18 | /// A named cfg value, like `unix`. |
19 | Name(String), |
20 | /// A key/value cfg pair, like `target_os = "linux"`. |
21 | KeyPair(String, String), |
22 | } |
23 | |
24 | #[derive (PartialEq)] |
25 | enum Token<'a> { |
26 | LeftParen, |
27 | RightParen, |
28 | Ident(&'a str), |
29 | Comma, |
30 | Equals, |
31 | String(&'a str), |
32 | } |
33 | |
34 | #[derive (Clone)] |
35 | struct Tokenizer<'a> { |
36 | s: iter::Peekable<str::CharIndices<'a>>, |
37 | orig: &'a str, |
38 | } |
39 | |
40 | struct Parser<'a> { |
41 | t: Tokenizer<'a>, |
42 | } |
43 | |
44 | impl FromStr for Cfg { |
45 | type Err = ParseError; |
46 | |
47 | fn from_str(s: &str) -> Result<Cfg, Self::Err> { |
48 | let mut p: Parser<'_> = Parser::new(s); |
49 | let e: Cfg = p.cfg()?; |
50 | if let Some(rest: &str) = p.rest() { |
51 | return Err(ParseError::new( |
52 | p.t.orig, |
53 | kind:UnterminatedExpression(rest.to_string()), |
54 | )); |
55 | } |
56 | Ok(e) |
57 | } |
58 | } |
59 | |
60 | impl fmt::Display for Cfg { |
61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
62 | match *self { |
63 | Cfg::Name(ref s: &String) => s.fmt(f), |
64 | Cfg::KeyPair(ref k: &String, ref v: &String) => write!(f, " {} = \"{}\"" , k, v), |
65 | } |
66 | } |
67 | } |
68 | |
69 | impl CfgExpr { |
70 | /// Utility function to check if the key, "cfg(..)" matches the `target_cfg` |
71 | pub fn matches_key(key: &str, target_cfg: &[Cfg]) -> bool { |
72 | if key.starts_with("cfg(" ) && key.ends_with(')' ) { |
73 | let cfg: &str = &key[4..key.len() - 1]; |
74 | |
75 | CfgExpr::from_str(cfg) |
76 | .ok() |
77 | .map(|ce| ce.matches(target_cfg)) |
78 | .unwrap_or(default:false) |
79 | } else { |
80 | false |
81 | } |
82 | } |
83 | |
84 | pub fn matches(&self, cfg: &[Cfg]) -> bool { |
85 | match *self { |
86 | CfgExpr::Not(ref e: &Box) => !e.matches(cfg), |
87 | CfgExpr::All(ref e: &Vec) => e.iter().all(|e: &CfgExpr| e.matches(cfg)), |
88 | CfgExpr::Any(ref e: &Vec) => e.iter().any(|e: &CfgExpr| e.matches(cfg)), |
89 | CfgExpr::Value(ref e: &Cfg) => cfg.contains(e), |
90 | } |
91 | } |
92 | } |
93 | |
94 | impl FromStr for CfgExpr { |
95 | type Err = ParseError; |
96 | |
97 | fn from_str(s: &str) -> Result<CfgExpr, Self::Err> { |
98 | let mut p: Parser<'_> = Parser::new(s); |
99 | let e: CfgExpr = p.expr()?; |
100 | if let Some(rest: &str) = p.rest() { |
101 | return Err(ParseError::new( |
102 | p.t.orig, |
103 | kind:UnterminatedExpression(rest.to_string()), |
104 | )); |
105 | } |
106 | Ok(e) |
107 | } |
108 | } |
109 | |
110 | impl fmt::Display for CfgExpr { |
111 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
112 | match *self { |
113 | CfgExpr::Not(ref e: &Box) => write!(f, "not( {})" , e), |
114 | CfgExpr::All(ref e: &Vec) => write!(f, "all( {})" , CommaSep(e)), |
115 | CfgExpr::Any(ref e: &Vec) => write!(f, "any( {})" , CommaSep(e)), |
116 | CfgExpr::Value(ref e: &Cfg) => write!(f, " {}" , e), |
117 | } |
118 | } |
119 | } |
120 | |
121 | struct CommaSep<'a, T>(&'a [T]); |
122 | |
123 | impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> { |
124 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
125 | for (i: usize, v: &T) in self.0.iter().enumerate() { |
126 | if i > 0 { |
127 | write!(f, ", " )?; |
128 | } |
129 | write!(f, " {}" , v)?; |
130 | } |
131 | Ok(()) |
132 | } |
133 | } |
134 | |
135 | impl<'a> Parser<'a> { |
136 | fn new(s: &'a str) -> Parser<'a> { |
137 | Parser { |
138 | t: Tokenizer { |
139 | s: s.char_indices().peekable(), |
140 | orig: s, |
141 | }, |
142 | } |
143 | } |
144 | |
145 | fn expr(&mut self) -> Result<CfgExpr, ParseError> { |
146 | match self.peek() { |
147 | Some(Ok(Token::Ident(op @ "all" ))) | Some(Ok(Token::Ident(op @ "any" ))) => { |
148 | self.t.next(); |
149 | let mut e = Vec::new(); |
150 | self.eat(&Token::LeftParen)?; |
151 | while !self.r#try(&Token::RightParen) { |
152 | e.push(self.expr()?); |
153 | if !self.r#try(&Token::Comma) { |
154 | self.eat(&Token::RightParen)?; |
155 | break; |
156 | } |
157 | } |
158 | if op == "all" { |
159 | Ok(CfgExpr::All(e)) |
160 | } else { |
161 | Ok(CfgExpr::Any(e)) |
162 | } |
163 | } |
164 | Some(Ok(Token::Ident("not" ))) => { |
165 | self.t.next(); |
166 | self.eat(&Token::LeftParen)?; |
167 | let e = self.expr()?; |
168 | self.eat(&Token::RightParen)?; |
169 | Ok(CfgExpr::Not(Box::new(e))) |
170 | } |
171 | Some(Ok(..)) => self.cfg().map(CfgExpr::Value), |
172 | Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()), |
173 | None => Err(ParseError::new( |
174 | self.t.orig, |
175 | IncompleteExpr("start of a cfg expression" ), |
176 | )), |
177 | } |
178 | } |
179 | |
180 | fn cfg(&mut self) -> Result<Cfg, ParseError> { |
181 | match self.t.next() { |
182 | Some(Ok(Token::Ident(name))) => { |
183 | let e = if self.r#try(&Token::Equals) { |
184 | let val = match self.t.next() { |
185 | Some(Ok(Token::String(s))) => s, |
186 | Some(Ok(t)) => { |
187 | return Err(ParseError::new( |
188 | self.t.orig, |
189 | UnexpectedToken { |
190 | expected: "a string" , |
191 | found: t.classify(), |
192 | }, |
193 | )) |
194 | } |
195 | Some(Err(e)) => return Err(e), |
196 | None => { |
197 | return Err(ParseError::new(self.t.orig, IncompleteExpr("a string" ))) |
198 | } |
199 | }; |
200 | Cfg::KeyPair(name.to_string(), val.to_string()) |
201 | } else { |
202 | Cfg::Name(name.to_string()) |
203 | }; |
204 | Ok(e) |
205 | } |
206 | Some(Ok(t)) => Err(ParseError::new( |
207 | self.t.orig, |
208 | UnexpectedToken { |
209 | expected: "identifier" , |
210 | found: t.classify(), |
211 | }, |
212 | )), |
213 | Some(Err(e)) => Err(e), |
214 | None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier" ))), |
215 | } |
216 | } |
217 | |
218 | fn peek(&mut self) -> Option<Result<Token<'a>, ParseError>> { |
219 | self.t.clone().next() |
220 | } |
221 | |
222 | fn r#try(&mut self, token: &Token<'a>) -> bool { |
223 | match self.peek() { |
224 | Some(Ok(ref t)) if token == t => {} |
225 | _ => return false, |
226 | } |
227 | self.t.next(); |
228 | true |
229 | } |
230 | |
231 | fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> { |
232 | match self.t.next() { |
233 | Some(Ok(ref t)) if token == t => Ok(()), |
234 | Some(Ok(t)) => Err(ParseError::new( |
235 | self.t.orig, |
236 | UnexpectedToken { |
237 | expected: token.classify(), |
238 | found: t.classify(), |
239 | }, |
240 | )), |
241 | Some(Err(e)) => Err(e), |
242 | None => Err(ParseError::new( |
243 | self.t.orig, |
244 | IncompleteExpr(token.classify()), |
245 | )), |
246 | } |
247 | } |
248 | |
249 | /// Returns the rest of the input from the current location. |
250 | fn rest(&self) -> Option<&str> { |
251 | let mut s = self.t.s.clone(); |
252 | loop { |
253 | match s.next() { |
254 | Some((_, ' ' )) => {} |
255 | Some((start, _ch)) => return Some(&self.t.orig[start..]), |
256 | None => return None, |
257 | } |
258 | } |
259 | } |
260 | } |
261 | |
262 | impl<'a> Iterator for Tokenizer<'a> { |
263 | type Item = Result<Token<'a>, ParseError>; |
264 | |
265 | fn next(&mut self) -> Option<Result<Token<'a>, ParseError>> { |
266 | loop { |
267 | match self.s.next() { |
268 | Some((_, ' ' )) => {} |
269 | Some((_, '(' )) => return Some(Ok(Token::LeftParen)), |
270 | Some((_, ')' )) => return Some(Ok(Token::RightParen)), |
271 | Some((_, ',' )) => return Some(Ok(Token::Comma)), |
272 | Some((_, '=' )) => return Some(Ok(Token::Equals)), |
273 | Some((start, '"' )) => { |
274 | while let Some((end, ch)) = self.s.next() { |
275 | if ch == '"' { |
276 | return Some(Ok(Token::String(&self.orig[start + 1..end]))); |
277 | } |
278 | } |
279 | return Some(Err(ParseError::new(self.orig, UnterminatedString))); |
280 | } |
281 | Some((start, ch)) if is_ident_start(ch) => { |
282 | while let Some(&(end, ch)) = self.s.peek() { |
283 | if !is_ident_rest(ch) { |
284 | return Some(Ok(Token::Ident(&self.orig[start..end]))); |
285 | } else { |
286 | self.s.next(); |
287 | } |
288 | } |
289 | return Some(Ok(Token::Ident(&self.orig[start..]))); |
290 | } |
291 | Some((_, ch)) => { |
292 | return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch)))); |
293 | } |
294 | None => return None, |
295 | } |
296 | } |
297 | } |
298 | } |
299 | |
300 | fn is_ident_start(ch: char) -> bool { |
301 | ch == '_' || ch.is_ascii_alphabetic() |
302 | } |
303 | |
304 | fn is_ident_rest(ch: char) -> bool { |
305 | is_ident_start(ch) || ch.is_ascii_digit() |
306 | } |
307 | |
308 | impl<'a> Token<'a> { |
309 | fn classify(&self) -> &'static str { |
310 | match *self { |
311 | Token::LeftParen => "`(`" , |
312 | Token::RightParen => "`)`" , |
313 | Token::Ident(..) => "an identifier" , |
314 | Token::Comma => "`,`" , |
315 | Token::Equals => "`=`" , |
316 | Token::String(..) => "a string" , |
317 | } |
318 | } |
319 | } |
320 | |