1use super::super::Error;
20use super::super::attr::AttrChar;
21use super::super::attr::Origin;
22use super::Env;
23use super::Expand;
24use super::Phrase;
25use yash_syntax::syntax::Unquote as _;
26use yash_syntax::syntax::Word;
27use yash_syntax::syntax::WordUnit::{self, *};
28
29const SINGLE_QUOTE: AttrChar = AttrChar {
30 value: '\'',
31 origin: Origin::Literal,
32 is_quoted: false,
33 is_quoting: true,
34};
35
36fn single_quote(value: &str) -> Phrase {
38 let mut field = Vec::with_capacity(value.chars().count() + 2);
39 field.push(SINGLE_QUOTE);
40 field.extend(value.chars().map(|c| AttrChar {
41 value: c,
42 origin: Origin::Literal,
43 is_quoted: true,
44 is_quoting: false,
45 }));
46 field.push(SINGLE_QUOTE);
47 Phrase::Field(field)
48}
49
50fn dollar_single_quote(s: &str) -> Phrase {
52 const DOLLAR: AttrChar = AttrChar {
53 value: '$',
54 origin: Origin::Literal,
55 is_quoted: false,
56 is_quoting: true,
57 };
58 let mut field = Vec::with_capacity(s.chars().count() + 3);
59 field.push(DOLLAR);
60 field.push(SINGLE_QUOTE);
61 field.extend(s.chars().map(|c| AttrChar {
62 value: c,
63 origin: Origin::Literal,
64 is_quoted: true,
65 is_quoting: false,
66 }));
67 field.push(SINGLE_QUOTE);
68 Phrase::Field(field)
69}
70
71fn double_quote(phrase: &mut Phrase) {
75 const QUOTE: AttrChar = AttrChar {
76 value: '"',
77 origin: Origin::Literal,
78 is_quoted: false,
79 is_quoting: true,
80 };
81
82 fn quote_field(chars: &mut Vec<AttrChar>) {
83 for c in chars.iter_mut() {
84 c.is_quoted = true;
85 }
86 chars.reserve_exact(2);
87 chars.insert(0, QUOTE);
88 chars.push(QUOTE);
89 }
90
91 match phrase {
92 Phrase::Char(c) => {
93 let is_quoted = true;
94 let c = AttrChar { is_quoted, ..*c };
95 *phrase = Phrase::Field(vec![QUOTE, c, QUOTE]);
96 }
97 Phrase::Field(chars) => quote_field(chars),
98 Phrase::Full(fields) => fields.iter_mut().for_each(quote_field),
99 }
100}
101
102impl Expand for WordUnit {
134 async fn expand(&self, env: &mut Env<'_>) -> Result<Phrase, Error> {
135 match self {
136 Unquoted(text_unit) => text_unit.expand(env).await,
137 SingleQuote(value) => Ok(single_quote(value)),
138 DoubleQuote(text) => {
139 let would_split = std::mem::replace(&mut env.will_split, false);
140 let result = text.expand(env).await;
141 env.will_split = would_split;
142
143 let mut phrase = result?;
144 double_quote(&mut phrase);
145 Ok(phrase)
146 }
147 DollarSingleQuote(string) => Ok(dollar_single_quote(&string.unquote().0)),
148 Tilde {
149 name,
150 followed_by_slash,
151 } => Ok(super::tilde::expand(name, *followed_by_slash, env.inner).into()),
152 }
153 }
154}
155
156impl Expand for Word {
160 #[inline]
161 async fn expand(&self, env: &mut Env<'_>) -> Result<Phrase, Error> {
162 self.units.expand(env).await
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::super::param::tests::braced_param;
169 use super::super::param::tests::env_with_positional_params_and_ifs;
170 use super::*;
171 use futures_util::FutureExt;
172 use yash_syntax::syntax::SpecialParam;
173 use yash_syntax::syntax::Text;
174 use yash_syntax::syntax::TextUnit;
175
176 #[test]
177 fn double_quote_char() {
178 let mut phrase = Phrase::Char(AttrChar {
179 value: 'C',
180 origin: Origin::SoftExpansion,
181 is_quoted: false,
182 is_quoting: false,
183 });
184 double_quote(&mut phrase);
185 let quote = AttrChar {
186 value: '"',
187 origin: Origin::Literal,
188 is_quoted: false,
189 is_quoting: true,
190 };
191 let c = AttrChar {
192 value: 'C',
193 origin: Origin::SoftExpansion,
194 is_quoted: true,
195 is_quoting: false,
196 };
197 assert_eq!(phrase, Phrase::Field(vec![quote, c, quote]));
198 }
199
200 #[test]
201 fn double_quote_field() {
202 let mut phrase = Phrase::Field(vec![]);
203 double_quote(&mut phrase);
204 let quote = AttrChar {
205 value: '"',
206 origin: Origin::Literal,
207 is_quoted: false,
208 is_quoting: true,
209 };
210 assert_eq!(phrase, Phrase::Field(vec![quote, quote]));
211
212 let i = AttrChar {
213 value: 'i',
214 origin: Origin::SoftExpansion,
215 is_quoted: false,
216 is_quoting: false,
217 };
218 let f = AttrChar {
219 value: 'f',
220 origin: Origin::Literal,
221 is_quoted: false,
222 is_quoting: false,
223 };
224 phrase = Phrase::Field(vec![i, f]);
225 double_quote(&mut phrase);
226 let is_quoted = true;
227 let i = AttrChar { is_quoted, ..i };
228 let f = AttrChar { is_quoted, ..f };
229 assert_eq!(phrase, Phrase::Field(vec![quote, i, f, quote]));
230 }
231
232 #[test]
233 fn double_quote_full() {
234 let mut phrase = Phrase::Full(vec![]);
235 double_quote(&mut phrase);
236 assert_eq!(phrase, Phrase::zero_fields());
237
238 let a = AttrChar {
239 value: 'a',
240 origin: Origin::HardExpansion,
241 is_quoted: false,
242 is_quoting: false,
243 };
244 let b = AttrChar { value: 'b', ..a };
245 phrase = Phrase::Full(vec![vec![a], vec![b]]);
246 double_quote(&mut phrase);
247 let quote = AttrChar {
248 value: '"',
249 origin: Origin::Literal,
250 is_quoted: false,
251 is_quoting: true,
252 };
253 let is_quoted = true;
254 let a = AttrChar { is_quoted, ..a };
255 let b = AttrChar { is_quoted, ..b };
256 assert_eq!(
257 phrase,
258 Phrase::Full(vec![vec![quote, a, quote], vec![quote, b, quote]])
259 );
260 }
261
262 #[test]
263 fn unquoted() {
264 let mut env = yash_env::Env::new_virtual();
265 let mut env = Env::new(&mut env);
266 let unit: WordUnit = "x".parse().unwrap();
267 let result = unit.expand(&mut env).now_or_never().unwrap();
268
269 let c = AttrChar {
270 value: 'x',
271 origin: Origin::Literal,
272 is_quoted: false,
273 is_quoting: false,
274 };
275 assert_eq!(result, Ok(Phrase::Char(c)));
276 }
277
278 #[test]
279 fn empty_single_quote() {
280 let result = single_quote("");
281 let q = AttrChar {
282 value: '\'',
283 origin: Origin::Literal,
284 is_quoted: false,
285 is_quoting: true,
286 };
287 assert_eq!(result, Phrase::Field(vec![q, q]));
288 }
289
290 #[test]
291 fn non_empty_single_quote() {
292 let result = single_quote("do");
293 let q = AttrChar {
294 value: '\'',
295 origin: Origin::Literal,
296 is_quoted: false,
297 is_quoting: true,
298 };
299 let d = AttrChar {
300 value: 'd',
301 origin: Origin::Literal,
302 is_quoted: true,
303 is_quoting: false,
304 };
305 let o = AttrChar { value: 'o', ..d };
306 assert_eq!(result, Phrase::Field(vec![q, d, o, q]));
307 }
308
309 #[test]
310 fn expand_dollar_single_quote() {
311 let mut env = yash_env::Env::new_virtual();
312 let mut env = Env::new(&mut env);
313 let unit = DollarSingleQuote(r"\\\n".parse().unwrap());
314 let result = unit.expand(&mut env).now_or_never().unwrap();
315
316 let dollar = AttrChar {
317 value: '$',
318 origin: Origin::Literal,
319 is_quoted: false,
320 is_quoting: true,
321 };
322 let quote = AttrChar {
323 value: '\'',
324 origin: Origin::Literal,
325 is_quoted: false,
326 is_quoting: true,
327 };
328 let backslash = AttrChar {
329 value: '\\',
330 origin: Origin::Literal,
331 is_quoted: true,
332 is_quoting: false,
333 };
334 let newline = AttrChar {
335 value: '\n',
336 origin: Origin::Literal,
337 is_quoted: true,
338 is_quoting: false,
339 };
340 assert_eq!(
341 result,
342 Ok(Phrase::Field(vec![
343 dollar, quote, backslash, newline, quote
344 ]))
345 );
346 }
347
348 #[test]
349 fn expand_double_quote() {
350 let mut env = yash_env::Env::new_virtual();
351 let mut env = Env::new(&mut env);
352 let unit = DoubleQuote(Text(vec![TextUnit::Literal('X')]));
353 let result = unit.expand(&mut env).now_or_never().unwrap();
354
355 let quote = AttrChar {
356 value: '"',
357 origin: Origin::Literal,
358 is_quoted: false,
359 is_quoting: true,
360 };
361 let x = AttrChar {
362 value: 'X',
363 origin: Origin::Literal,
364 is_quoted: true,
365 is_quoting: false,
366 };
367 assert_eq!(result, Ok(Phrase::Field(vec![quote, x, quote])));
368 }
369
370 #[test]
371 fn inside_double_quote_is_non_splitting_context() {
372 let mut env = env_with_positional_params_and_ifs();
373 let mut env = Env::new(&mut env);
374 let unit = TextUnit::BracedParam(braced_param(SpecialParam::Asterisk));
375 let unit = DoubleQuote(Text(vec![unit]));
376 let result = unit.expand(&mut env).now_or_never().unwrap();
377
378 assert!(env.will_split);
379 let quote = AttrChar {
380 value: '"',
381 origin: Origin::Literal,
382 is_quoted: false,
383 is_quoting: true,
384 };
385 let a = AttrChar {
386 value: 'a',
387 origin: Origin::SoftExpansion,
388 is_quoted: true,
389 is_quoting: false,
390 };
391 let amp = AttrChar { value: '&', ..a };
392 let c = AttrChar { value: 'c', ..a };
393 assert_eq!(result, Ok(Phrase::Field(vec![quote, a, amp, c, quote])));
394 }
395}