1use super::core::Parser;
20use super::core::Rec;
21use super::core::Result;
22use super::error::Error;
23use super::error::SyntaxError;
24use super::lex::Keyword::{Do, For, In};
25use super::lex::Operator::{Newline, Semicolon};
26use super::lex::TokenId::{EndOfInput, IoLocation, IoNumber, Operator, Token};
27use super::lex::is_portable_name;
28use crate::source::Location;
29use crate::syntax::CompoundCommand;
30use crate::syntax::List;
31use crate::syntax::MaybeLiteral as _;
32use crate::syntax::Word;
33
34impl Parser<'_, '_> {
35 async fn for_loop_name(&mut self) -> Result<Word> {
37 let name = self.take_token_auto(&[]).await?;
38
39 match name.id {
41 EndOfInput | Operator(Newline) | Operator(Semicolon) => {
42 let cause = SyntaxError::MissingForName.into();
43 let location = name.word.location;
44 return Err(Error { cause, location });
45 }
46 Operator(_) => {
47 let cause = SyntaxError::InvalidForName.into();
48 let location = name.word.location;
49 return Err(Error { cause, location });
50 }
51 Token(_) | IoNumber | IoLocation => (),
52 }
53
54 if self.mode().portable
55 && !name
56 .word
57 .to_string_if_literal()
58 .is_some_and(|s| is_portable_name(&s))
59 {
60 let cause = SyntaxError::NonPortableForName.into();
61 let location = name.word.location;
62 return Err(Error { cause, location });
63 }
64
65 Ok(name.word)
66 }
67
68 async fn for_loop_values(
76 &mut self,
77 opening_location: Location,
78 ) -> Result<(Option<Vec<Word>>, Location)> {
79 let mut first_line = true;
81 loop {
82 match self.peek_token().await?.id {
83 Operator(Semicolon) if first_line => {
84 self.take_token_raw().await?;
85 return Ok((None, opening_location));
86 }
87 Token(Some(Do)) => {
88 return Ok((None, opening_location));
89 }
90 Operator(Newline) => {
91 assert!(self.newline_and_here_doc_contents().await?);
92 first_line = false;
93 }
94 Token(Some(In)) => {
95 self.take_token_raw().await?;
96 break;
97 }
98 _ => match self.take_token_manual(false).await? {
99 Rec::AliasSubstituted => (),
100 Rec::Parsed(token) => {
101 let cause = SyntaxError::MissingForBody { opening_location }.into();
102 let location = token.word.location;
103 return Err(Error { cause, location });
104 }
105 },
106 }
107 }
108
109 let mut values = Vec::new();
111 loop {
112 let next = self.take_token_auto(&[]).await?;
113 match next.id {
114 Token(_) | IoNumber | IoLocation => {
115 values.push(next.word);
116 }
117 Operator(Semicolon) | Operator(Newline) | EndOfInput => {
118 return Ok((Some(values), opening_location));
119 }
120 Operator(_) => {
121 let cause = SyntaxError::InvalidForValue.into();
122 let location = next.word.location;
123 return Err(Error { cause, location });
124 }
125 }
126 }
127 }
128
129 async fn for_loop_body(&mut self, opening_location: Location) -> Result<List> {
131 loop {
132 while self.newline_and_here_doc_contents().await? {}
133
134 if let Some(body) = self.do_clause().await? {
135 return Ok(body);
136 }
137
138 match self.take_token_manual(false).await? {
139 Rec::AliasSubstituted => (),
140 Rec::Parsed(token) => {
141 let cause = SyntaxError::MissingForBody { opening_location }.into();
142 let location = token.word.location;
143 return Err(Error { cause, location });
144 }
145 }
146 }
147 }
148
149 pub async fn for_loop(&mut self) -> Result<CompoundCommand> {
157 let open = self.take_token_raw().await?;
158 assert_eq!(open.id, Token(Some(For)));
159 let opening_location = open.word.location;
160
161 let name = self.for_loop_name().await?;
162 let (values, opening_location) = self.for_loop_values(opening_location).await?;
163 let body = self.for_loop_body(opening_location).await?;
164 Ok(CompoundCommand::For { name, values, body })
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::super::error::ErrorCause;
171 use super::super::lex::Lexer;
172 use super::*;
173 use crate::alias::{AliasSet, HashEntry};
174 use crate::source::Source;
175 use assert_matches::assert_matches;
176 use futures_util::FutureExt as _;
177
178 #[test]
179 fn parser_for_loop_short() {
180 let mut lexer = Lexer::with_code("for A do :; done");
181 let mut parser = Parser::new(&mut lexer);
182
183 let result = parser.compound_command().now_or_never().unwrap();
184 let compound_command = result.unwrap().unwrap();
185 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
186 assert_eq!(name.to_string(), "A");
187 assert_eq!(values, None);
188 assert_eq!(body.to_string(), ":");
189 });
190
191 let next = parser.peek_token().now_or_never().unwrap().unwrap();
192 assert_eq!(next.id, EndOfInput);
193 }
194
195 #[test]
196 fn parser_for_loop_with_semicolon_before_do() {
197 let mut lexer = Lexer::with_code("for B ; do :; done");
198 let mut parser = Parser::new(&mut lexer);
199
200 let result = parser.compound_command().now_or_never().unwrap();
201 let compound_command = result.unwrap().unwrap();
202 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
203 assert_eq!(name.to_string(), "B");
204 assert_eq!(values, None);
205 assert_eq!(body.to_string(), ":");
206 });
207
208 let next = parser.peek_token().now_or_never().unwrap().unwrap();
209 assert_eq!(next.id, EndOfInput);
210 }
211
212 #[test]
213 fn parser_for_loop_with_semicolon_and_newlines_before_do() {
214 let mut lexer = Lexer::with_code("for B ; \n\t\n do :; done");
215 let mut parser = Parser::new(&mut lexer);
216
217 let result = parser.compound_command().now_or_never().unwrap();
218 let compound_command = result.unwrap().unwrap();
219 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
220 assert_eq!(name.to_string(), "B");
221 assert_eq!(values, None);
222 assert_eq!(body.to_string(), ":");
223 });
224
225 let next = parser.peek_token().now_or_never().unwrap().unwrap();
226 assert_eq!(next.id, EndOfInput);
227 }
228
229 #[test]
230 fn parser_for_loop_with_newlines_before_do() {
231 let mut lexer = Lexer::with_code("for B \n \\\n \n do :; done");
232 let mut parser = Parser::new(&mut lexer);
233
234 let result = parser.compound_command().now_or_never().unwrap();
235 let compound_command = result.unwrap().unwrap();
236 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
237 assert_eq!(name.to_string(), "B");
238 assert_eq!(values, None);
239 assert_eq!(body.to_string(), ":");
240 });
241
242 let next = parser.peek_token().now_or_never().unwrap().unwrap();
243 assert_eq!(next.id, EndOfInput);
244 }
245
246 #[test]
247 fn parser_for_loop_with_zero_values_delimited_by_semicolon() {
248 let mut lexer = Lexer::with_code("for foo in; do :; done");
249 let mut parser = Parser::new(&mut lexer);
250
251 let result = parser.compound_command().now_or_never().unwrap();
252 let compound_command = result.unwrap().unwrap();
253 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
254 assert_eq!(name.to_string(), "foo");
255 assert_eq!(values, Some(vec![]));
256 assert_eq!(body.to_string(), ":");
257 });
258
259 let next = parser.peek_token().now_or_never().unwrap().unwrap();
260 assert_eq!(next.id, EndOfInput);
261 }
262
263 #[test]
264 fn parser_for_loop_with_one_value_delimited_by_semicolon_and_newlines() {
265 let mut lexer = Lexer::with_code("for foo in bar; \n \n do :; done");
266 let mut parser = Parser::new(&mut lexer);
267
268 let result = parser.compound_command().now_or_never().unwrap();
269 let compound_command = result.unwrap().unwrap();
270 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
271 assert_eq!(name.to_string(), "foo");
272 let values = values
273 .unwrap()
274 .iter()
275 .map(ToString::to_string)
276 .collect::<Vec<String>>();
277 assert_eq!(values, vec!["bar"]);
278 assert_eq!(body.to_string(), ":");
279 });
280
281 let next = parser.peek_token().now_or_never().unwrap().unwrap();
282 assert_eq!(next.id, EndOfInput);
283 }
284
285 #[test]
286 fn parser_for_loop_with_many_values_delimited_by_one_newline() {
287 let mut lexer = Lexer::with_code("for in in in a b c\ndo :; done");
288 let mut parser = Parser::new(&mut lexer);
289
290 let result = parser.compound_command().now_or_never().unwrap();
291 let compound_command = result.unwrap().unwrap();
292 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
293 assert_eq!(name.to_string(), "in");
294 let values = values
295 .unwrap()
296 .iter()
297 .map(ToString::to_string)
298 .collect::<Vec<String>>();
299 assert_eq!(values, vec!["in", "a", "b", "c"]);
300 assert_eq!(body.to_string(), ":");
301 });
302
303 let next = parser.peek_token().now_or_never().unwrap().unwrap();
304 assert_eq!(next.id, EndOfInput);
305 }
306
307 #[test]
308 fn parser_for_loop_with_zero_values_delimited_by_many_newlines() {
309 let mut lexer = Lexer::with_code("for foo in \n \n \n do :; done");
310 let mut parser = Parser::new(&mut lexer);
311
312 let result = parser.compound_command().now_or_never().unwrap();
313 let compound_command = result.unwrap().unwrap();
314 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
315 assert_eq!(name.to_string(), "foo");
316 assert_eq!(values, Some(vec![]));
317 assert_eq!(body.to_string(), ":");
318 });
319
320 let next = parser.peek_token().now_or_never().unwrap().unwrap();
321 assert_eq!(next.id, EndOfInput);
322 }
323
324 #[test]
325 fn parser_for_loop_newlines_before_in() {
326 let mut lexer = Lexer::with_code("for foo\n \n\nin\ndo :; done");
327 let mut parser = Parser::new(&mut lexer);
328
329 let result = parser.compound_command().now_or_never().unwrap();
330 let compound_command = result.unwrap().unwrap();
331 assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
332 assert_eq!(name.to_string(), "foo");
333 assert_eq!(values, Some(vec![]));
334 assert_eq!(body.to_string(), ":");
335 });
336
337 let next = parser.peek_token().now_or_never().unwrap().unwrap();
338 assert_eq!(next.id, EndOfInput);
339 }
340
341 #[test]
342 fn parser_for_loop_aliasing_on_semicolon() {
343 let mut lexer = Lexer::with_code(" FOR_A if :; done");
344 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
345 let mut aliases = AliasSet::new();
346 let origin = Location::dummy("");
347 aliases.insert(HashEntry::new(
348 "if".to_string(),
349 " ;\n\ndo".to_string(),
350 false,
351 origin.clone(),
352 ));
353 aliases.insert(HashEntry::new(
354 "FOR_A".to_string(),
355 "for A ".to_string(),
356 false,
357 origin,
358 ));
359 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
360
361 let result = parser.take_token_manual(true).now_or_never().unwrap();
362 assert_matches!(result, Ok(Rec::AliasSubstituted));
363
364 let result = parser.compound_command().now_or_never().unwrap();
365 let compound_command = result.unwrap().unwrap();
366 assert_eq!(compound_command.to_string(), "for A do :; done");
367
368 let next = parser.peek_token().now_or_never().unwrap().unwrap();
369 assert_eq!(next.id, EndOfInput);
370 }
371
372 #[test]
373 fn parser_for_loop_aliasing_on_do() {
374 let mut lexer = Lexer::with_code(" FOR_A if :; done");
375 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
376 let mut aliases = AliasSet::new();
377 let origin = Location::dummy("");
378 aliases.insert(HashEntry::new(
379 "if".to_string(),
380 "\ndo".to_string(),
381 false,
382 origin.clone(),
383 ));
384 aliases.insert(HashEntry::new(
385 "FOR_A".to_string(),
386 "for A ".to_string(),
387 false,
388 origin,
389 ));
390 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
391
392 let result = parser.take_token_manual(true).now_or_never().unwrap();
393 assert_matches!(result, Ok(Rec::AliasSubstituted));
394
395 let result = parser.compound_command().now_or_never().unwrap();
396 let compound_command = result.unwrap().unwrap();
397 assert_eq!(compound_command.to_string(), "for A do :; done");
398
399 let next = parser.peek_token().now_or_never().unwrap().unwrap();
400 assert_eq!(next.id, EndOfInput);
401 }
402
403 #[test]
404 fn parser_for_loop_missing_name_eof() {
405 let mut lexer = Lexer::with_code(" for ");
406 let mut parser = Parser::new(&mut lexer);
407
408 let result = parser.compound_command().now_or_never().unwrap();
409 let e = result.unwrap_err();
410 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
411 assert_eq!(*e.location.code.value.borrow(), " for ");
412 assert_eq!(e.location.code.start_line_number.get(), 1);
413 assert_eq!(*e.location.code.source, Source::Unknown);
414 assert_eq!(e.location.range, 5..5);
415 }
416
417 #[test]
418 fn parser_for_loop_missing_name_newline() {
419 let mut lexer = Lexer::with_code(" for\ndo :; done");
420 let mut parser = Parser::new(&mut lexer);
421
422 let result = parser.compound_command().now_or_never().unwrap();
423 let e = result.unwrap_err();
424 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
425 assert_eq!(*e.location.code.value.borrow(), " for\n");
426 assert_eq!(e.location.code.start_line_number.get(), 1);
427 assert_eq!(*e.location.code.source, Source::Unknown);
428 assert_eq!(e.location.range, 4..5);
429 }
430
431 #[test]
432 fn parser_for_loop_missing_name_semicolon() {
433 let mut lexer = Lexer::with_code("for; do :; done");
434 let mut parser = Parser::new(&mut lexer);
435
436 let result = parser.compound_command().now_or_never().unwrap();
437 let e = result.unwrap_err();
438 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
439 assert_eq!(*e.location.code.value.borrow(), "for; do :; done");
440 assert_eq!(e.location.code.start_line_number.get(), 1);
441 assert_eq!(*e.location.code.source, Source::Unknown);
442 assert_eq!(e.location.range, 3..4);
443 }
444
445 #[test]
446 fn parser_for_loop_invalid_name() {
447 let mut lexer = Lexer::with_code("FOR if do :; done");
449 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
450 let mut aliases = AliasSet::new();
451 let origin = Location::dummy("");
452 aliases.insert(HashEntry::new(
453 "FOR".to_string(),
454 "for ".to_string(),
455 false,
456 origin.clone(),
457 ));
458 aliases.insert(HashEntry::new(
459 "if".to_string(),
460 "&".to_string(),
461 false,
462 origin,
463 ));
464 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
465
466 let result = parser.take_token_manual(true).now_or_never().unwrap();
467 assert_matches!(result, Ok(Rec::AliasSubstituted));
468
469 let result = parser.compound_command().now_or_never().unwrap();
470 let e = result.unwrap_err();
471 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidForName));
472 assert_eq!(*e.location.code.value.borrow(), "&");
473 assert_eq!(e.location.code.start_line_number.get(), 1);
474 assert_eq!(e.location.range, 0..1);
475 assert_matches!(&*e.location.code.source, Source::Alias { original, alias } => {
476 assert_eq!(*original.code.value.borrow(), "FOR if do :; done");
477 assert_eq!(original.code.start_line_number.get(), 1);
478 assert_eq!(*original.code.source, Source::Unknown);
479 assert_eq!(original.range, 4..6);
480 assert_eq!(alias.name, "if");
481 });
482 }
483
484 #[test]
485 fn parser_for_loop_semicolon_after_newline() {
486 let mut lexer = Lexer::with_code("for X\n; do :; done");
487 let mut parser = Parser::new(&mut lexer);
488
489 let result = parser.compound_command().now_or_never().unwrap();
490 let e = result.unwrap_err();
491 assert_matches!(&e.cause,
492 ErrorCause::Syntax(SyntaxError::MissingForBody { opening_location }) => {
493 assert_eq!(*opening_location.code.value.borrow(), "for X\n; do :; done");
494 assert_eq!(opening_location.code.start_line_number.get(), 1);
495 assert_eq!(*opening_location.code.source, Source::Unknown);
496 assert_eq!(opening_location.range, 0..3);
497 });
498 assert_eq!(*e.location.code.value.borrow(), "for X\n; do :; done");
499 assert_eq!(e.location.code.start_line_number.get(), 1);
500 assert_eq!(*e.location.code.source, Source::Unknown);
501 assert_eq!(e.location.range, 6..7);
502 }
503
504 #[test]
505 fn parser_for_loop_invalid_values_delimiter() {
506 let mut lexer = Lexer::with_code("for_A_in_a_b if c; do :; done");
508 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
509 let mut aliases = AliasSet::new();
510 let origin = Location::dummy("");
511 aliases.insert(HashEntry::new(
512 "for_A_in_a_b".to_string(),
513 "for A in a b ".to_string(),
514 false,
515 origin.clone(),
516 ));
517 aliases.insert(HashEntry::new(
518 "if".to_string(),
519 "&".to_string(),
520 false,
521 origin,
522 ));
523 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
524
525 let result = parser.take_token_manual(true).now_or_never().unwrap();
526 assert_matches!(result, Ok(Rec::AliasSubstituted));
527
528 let result = parser.compound_command().now_or_never().unwrap();
529 let e = result.unwrap_err();
530 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidForValue));
531 assert_eq!(*e.location.code.value.borrow(), "&");
532 assert_eq!(e.location.code.start_line_number.get(), 1);
533 assert_eq!(e.location.range, 0..1);
534 assert_matches!(&*e.location.code.source, Source::Alias { original, alias } => {
535 assert_eq!(*original.code.value.borrow(), "for_A_in_a_b if c; do :; done");
536 assert_eq!(original.code.start_line_number.get(), 1);
537 assert_eq!(*original.code.source, Source::Unknown);
538 assert_eq!(original.range, 13..15);
539 assert_eq!(alias.name, "if");
540 });
541 }
542
543 #[test]
544 fn parser_for_loop_invalid_token_after_semicolon() {
545 let mut lexer = Lexer::with_code(" for X; ! do :; done");
546 let mut parser = Parser::new(&mut lexer);
547
548 let result = parser.compound_command().now_or_never().unwrap();
549 let e = result.unwrap_err();
550 assert_matches!(&e.cause,
551 ErrorCause::Syntax(SyntaxError::MissingForBody { opening_location }) => {
552 assert_eq!(*opening_location.code.value.borrow(), " for X; ! do :; done");
553 assert_eq!(opening_location.code.start_line_number.get(), 1);
554 assert_eq!(*opening_location.code.source, Source::Unknown);
555 assert_eq!(opening_location.range, 1..4);
556 });
557 assert_eq!(*e.location.code.value.borrow(), " for X; ! do :; done");
558 assert_eq!(e.location.code.start_line_number.get(), 1);
559 assert_eq!(*e.location.code.source, Source::Unknown);
560 assert_eq!(e.location.range, 8..9);
561 }
562
563 fn portable_mode() -> yash_env::parser::Mode {
564 let mut mode = yash_env::parser::Mode::default();
565 mode.portable = true;
566 mode
567 }
568
569 #[test]
570 fn parser_for_loop_name_starting_with_digit_rejected_in_portable_mode() {
571 let mut lexer = Lexer::with_code("for 1a do :; done");
572 lexer.set_mode(portable_mode());
573 let mut parser = Parser::new(&mut lexer);
574
575 let result = parser.compound_command().now_or_never().unwrap();
576 let e = result.unwrap_err();
577 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::NonPortableForName));
578 assert_eq!(*e.location.code.value.borrow(), "for 1a do :; done");
579 assert_eq!(e.location.code.start_line_number.get(), 1);
580 assert_eq!(*e.location.code.source, Source::Unknown);
581 assert_eq!(e.location.range, 4..6);
582 }
583
584 #[test]
585 fn parser_for_loop_quoted_name_rejected_in_portable_mode() {
586 let mut lexer = Lexer::with_code("for 'A' do :; done");
587 lexer.set_mode(portable_mode());
588 let mut parser = Parser::new(&mut lexer);
589
590 let result = parser.compound_command().now_or_never().unwrap();
591 let e = result.unwrap_err();
592 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::NonPortableForName));
593 assert_eq!(e.location.range, 4..7);
594 }
595
596 #[test]
597 fn parser_for_loop_name_with_expansion_rejected_in_portable_mode() {
598 let mut lexer = Lexer::with_code("for $A do :; done");
599 lexer.set_mode(portable_mode());
600 let mut parser = Parser::new(&mut lexer);
601
602 let result = parser.compound_command().now_or_never().unwrap();
603 let e = result.unwrap_err();
604 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::NonPortableForName));
605 assert_eq!(e.location.range, 4..6);
606 }
607
608 #[test]
609 fn parser_for_loop_portable_name_allowed_in_portable_mode() {
610 let mut lexer = Lexer::with_code("for _Az9 do :; done");
611 lexer.set_mode(portable_mode());
612 let mut parser = Parser::new(&mut lexer);
613
614 let result = parser.compound_command().now_or_never().unwrap();
615 let compound_command = result.unwrap().unwrap();
616 assert_matches!(compound_command, CompoundCommand::For { name, .. } => {
617 assert_eq!(name.to_string(), "_Az9");
618 });
619 }
620
621 #[test]
622 fn parser_for_loop_non_portable_name_allowed_without_portable() {
623 let mut lexer = Lexer::with_code("for 1a do :; done");
624 let mut parser = Parser::new(&mut lexer);
625
626 let result = parser.compound_command().now_or_never().unwrap();
627 let compound_command = result.unwrap().unwrap();
628 assert_matches!(compound_command, CompoundCommand::For { name, .. } => {
629 assert_eq!(name.to_string(), "1a");
630 });
631 }
632}