yash_syntax/parser/
for_loop.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2020 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Syntax parser for for loop
18
19use 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 crate::source::Location;
28use crate::syntax::CompoundCommand;
29use crate::syntax::List;
30use crate::syntax::Word;
31
32impl Parser<'_, '_> {
33    /// Parses the name of a for loop.
34    async fn for_loop_name(&mut self) -> Result<Word> {
35        let name = self.take_token_auto(&[]).await?;
36
37        // Validate the token type
38        match name.id {
39            EndOfInput | Operator(Newline) | Operator(Semicolon) => {
40                let cause = SyntaxError::MissingForName.into();
41                let location = name.word.location;
42                return Err(Error { cause, location });
43            }
44            Operator(_) => {
45                let cause = SyntaxError::InvalidForName.into();
46                let location = name.word.location;
47                return Err(Error { cause, location });
48            }
49            Token(_) | IoNumber | IoLocation => (),
50        }
51
52        // TODO reject non-portable names in POSIXly-correct mode
53
54        Ok(name.word)
55    }
56
57    /// Parses the values of a for loop.
58    ///
59    /// For the values to be parsed, the first token needs to be `in`. Otherwise,
60    /// the result will be `None`.
61    ///
62    /// If successful, `opening_location` is returned intact as the second value
63    /// of the tuple.
64    async fn for_loop_values(
65        &mut self,
66        opening_location: Location,
67    ) -> Result<(Option<Vec<Word>>, Location)> {
68        // Parse the `in`
69        let mut first_line = true;
70        loop {
71            match self.peek_token().await?.id {
72                Operator(Semicolon) if first_line => {
73                    self.take_token_raw().await?;
74                    return Ok((None, opening_location));
75                }
76                Token(Some(Do)) => {
77                    return Ok((None, opening_location));
78                }
79                Operator(Newline) => {
80                    assert!(self.newline_and_here_doc_contents().await?);
81                    first_line = false;
82                }
83                Token(Some(In)) => {
84                    self.take_token_raw().await?;
85                    break;
86                }
87                _ => match self.take_token_manual(false).await? {
88                    Rec::AliasSubstituted => (),
89                    Rec::Parsed(token) => {
90                        let cause = SyntaxError::MissingForBody { opening_location }.into();
91                        let location = token.word.location;
92                        return Err(Error { cause, location });
93                    }
94                },
95            }
96        }
97
98        // Parse values until a delimiter is found
99        let mut values = Vec::new();
100        loop {
101            let next = self.take_token_auto(&[]).await?;
102            match next.id {
103                Token(_) | IoNumber | IoLocation => {
104                    values.push(next.word);
105                }
106                Operator(Semicolon) | Operator(Newline) | EndOfInput => {
107                    return Ok((Some(values), opening_location));
108                }
109                Operator(_) => {
110                    let cause = SyntaxError::InvalidForValue.into();
111                    let location = next.word.location;
112                    return Err(Error { cause, location });
113                }
114            }
115        }
116    }
117
118    /// Parses the body of a for loop, possibly preceded by newlines.
119    async fn for_loop_body(&mut self, opening_location: Location) -> Result<List> {
120        loop {
121            while self.newline_and_here_doc_contents().await? {}
122
123            if let Some(body) = self.do_clause().await? {
124                return Ok(body);
125            }
126
127            match self.take_token_manual(false).await? {
128                Rec::AliasSubstituted => (),
129                Rec::Parsed(token) => {
130                    let cause = SyntaxError::MissingForBody { opening_location }.into();
131                    let location = token.word.location;
132                    return Err(Error { cause, location });
133                }
134            }
135        }
136    }
137
138    /// Parses a for loop.
139    ///
140    /// The next token must be the `for` reserved word.
141    ///
142    /// # Panics
143    ///
144    /// If the first token is not `for`.
145    pub async fn for_loop(&mut self) -> Result<CompoundCommand> {
146        let open = self.take_token_raw().await?;
147        assert_eq!(open.id, Token(Some(For)));
148        let opening_location = open.word.location;
149
150        let name = self.for_loop_name().await?;
151        let (values, opening_location) = self.for_loop_values(opening_location).await?;
152        let body = self.for_loop_body(opening_location).await?;
153        Ok(CompoundCommand::For { name, values, body })
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::super::error::ErrorCause;
160    use super::super::lex::Lexer;
161    use super::*;
162    use crate::alias::{AliasSet, HashEntry};
163    use crate::source::Source;
164    use assert_matches::assert_matches;
165    use futures_util::FutureExt;
166
167    #[test]
168    fn parser_for_loop_short() {
169        let mut lexer = Lexer::with_code("for A do :; done");
170        let mut parser = Parser::new(&mut lexer);
171
172        let result = parser.compound_command().now_or_never().unwrap();
173        let compound_command = result.unwrap().unwrap();
174        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
175            assert_eq!(name.to_string(), "A");
176            assert_eq!(values, None);
177            assert_eq!(body.to_string(), ":");
178        });
179
180        let next = parser.peek_token().now_or_never().unwrap().unwrap();
181        assert_eq!(next.id, EndOfInput);
182    }
183
184    #[test]
185    fn parser_for_loop_with_semicolon_before_do() {
186        let mut lexer = Lexer::with_code("for B ; do :; done");
187        let mut parser = Parser::new(&mut lexer);
188
189        let result = parser.compound_command().now_or_never().unwrap();
190        let compound_command = result.unwrap().unwrap();
191        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
192            assert_eq!(name.to_string(), "B");
193            assert_eq!(values, None);
194            assert_eq!(body.to_string(), ":");
195        });
196
197        let next = parser.peek_token().now_or_never().unwrap().unwrap();
198        assert_eq!(next.id, EndOfInput);
199    }
200
201    #[test]
202    fn parser_for_loop_with_semicolon_and_newlines_before_do() {
203        let mut lexer = Lexer::with_code("for B ; \n\t\n do :; done");
204        let mut parser = Parser::new(&mut lexer);
205
206        let result = parser.compound_command().now_or_never().unwrap();
207        let compound_command = result.unwrap().unwrap();
208        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
209            assert_eq!(name.to_string(), "B");
210            assert_eq!(values, None);
211            assert_eq!(body.to_string(), ":");
212        });
213
214        let next = parser.peek_token().now_or_never().unwrap().unwrap();
215        assert_eq!(next.id, EndOfInput);
216    }
217
218    #[test]
219    fn parser_for_loop_with_newlines_before_do() {
220        let mut lexer = Lexer::with_code("for B \n \\\n \n do :; done");
221        let mut parser = Parser::new(&mut lexer);
222
223        let result = parser.compound_command().now_or_never().unwrap();
224        let compound_command = result.unwrap().unwrap();
225        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
226            assert_eq!(name.to_string(), "B");
227            assert_eq!(values, None);
228            assert_eq!(body.to_string(), ":");
229        });
230
231        let next = parser.peek_token().now_or_never().unwrap().unwrap();
232        assert_eq!(next.id, EndOfInput);
233    }
234
235    #[test]
236    fn parser_for_loop_with_zero_values_delimited_by_semicolon() {
237        let mut lexer = Lexer::with_code("for foo in; do :; done");
238        let mut parser = Parser::new(&mut lexer);
239
240        let result = parser.compound_command().now_or_never().unwrap();
241        let compound_command = result.unwrap().unwrap();
242        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
243            assert_eq!(name.to_string(), "foo");
244            assert_eq!(values, Some(vec![]));
245            assert_eq!(body.to_string(), ":");
246        });
247
248        let next = parser.peek_token().now_or_never().unwrap().unwrap();
249        assert_eq!(next.id, EndOfInput);
250    }
251
252    #[test]
253    fn parser_for_loop_with_one_value_delimited_by_semicolon_and_newlines() {
254        let mut lexer = Lexer::with_code("for foo in bar; \n \n do :; done");
255        let mut parser = Parser::new(&mut lexer);
256
257        let result = parser.compound_command().now_or_never().unwrap();
258        let compound_command = result.unwrap().unwrap();
259        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
260            assert_eq!(name.to_string(), "foo");
261            let values = values
262                .unwrap()
263                .iter()
264                .map(ToString::to_string)
265                .collect::<Vec<String>>();
266            assert_eq!(values, vec!["bar"]);
267            assert_eq!(body.to_string(), ":");
268        });
269
270        let next = parser.peek_token().now_or_never().unwrap().unwrap();
271        assert_eq!(next.id, EndOfInput);
272    }
273
274    #[test]
275    fn parser_for_loop_with_many_values_delimited_by_one_newline() {
276        let mut lexer = Lexer::with_code("for in in in a b c\ndo :; done");
277        let mut parser = Parser::new(&mut lexer);
278
279        let result = parser.compound_command().now_or_never().unwrap();
280        let compound_command = result.unwrap().unwrap();
281        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
282            assert_eq!(name.to_string(), "in");
283            let values = values
284                .unwrap()
285                .iter()
286                .map(ToString::to_string)
287                .collect::<Vec<String>>();
288            assert_eq!(values, vec!["in", "a", "b", "c"]);
289            assert_eq!(body.to_string(), ":");
290        });
291
292        let next = parser.peek_token().now_or_never().unwrap().unwrap();
293        assert_eq!(next.id, EndOfInput);
294    }
295
296    #[test]
297    fn parser_for_loop_with_zero_values_delimited_by_many_newlines() {
298        let mut lexer = Lexer::with_code("for foo in \n \n \n do :; done");
299        let mut parser = Parser::new(&mut lexer);
300
301        let result = parser.compound_command().now_or_never().unwrap();
302        let compound_command = result.unwrap().unwrap();
303        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
304            assert_eq!(name.to_string(), "foo");
305            assert_eq!(values, Some(vec![]));
306            assert_eq!(body.to_string(), ":");
307        });
308
309        let next = parser.peek_token().now_or_never().unwrap().unwrap();
310        assert_eq!(next.id, EndOfInput);
311    }
312
313    #[test]
314    fn parser_for_loop_newlines_before_in() {
315        let mut lexer = Lexer::with_code("for foo\n \n\nin\ndo :; done");
316        let mut parser = Parser::new(&mut lexer);
317
318        let result = parser.compound_command().now_or_never().unwrap();
319        let compound_command = result.unwrap().unwrap();
320        assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
321            assert_eq!(name.to_string(), "foo");
322            assert_eq!(values, Some(vec![]));
323            assert_eq!(body.to_string(), ":");
324        });
325
326        let next = parser.peek_token().now_or_never().unwrap().unwrap();
327        assert_eq!(next.id, EndOfInput);
328    }
329
330    #[test]
331    fn parser_for_loop_aliasing_on_semicolon() {
332        let mut lexer = Lexer::with_code(" FOR_A if :; done");
333        #[allow(clippy::mutable_key_type)]
334        let mut aliases = AliasSet::new();
335        let origin = Location::dummy("");
336        aliases.insert(HashEntry::new(
337            "if".to_string(),
338            " ;\n\ndo".to_string(),
339            false,
340            origin.clone(),
341        ));
342        aliases.insert(HashEntry::new(
343            "FOR_A".to_string(),
344            "for A ".to_string(),
345            false,
346            origin,
347        ));
348        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
349
350        let result = parser.take_token_manual(true).now_or_never().unwrap();
351        assert_matches!(result, Ok(Rec::AliasSubstituted));
352
353        let result = parser.compound_command().now_or_never().unwrap();
354        let compound_command = result.unwrap().unwrap();
355        assert_eq!(compound_command.to_string(), "for A do :; done");
356
357        let next = parser.peek_token().now_or_never().unwrap().unwrap();
358        assert_eq!(next.id, EndOfInput);
359    }
360
361    #[test]
362    fn parser_for_loop_aliasing_on_do() {
363        let mut lexer = Lexer::with_code(" FOR_A if :; done");
364        #[allow(clippy::mutable_key_type)]
365        let mut aliases = AliasSet::new();
366        let origin = Location::dummy("");
367        aliases.insert(HashEntry::new(
368            "if".to_string(),
369            "\ndo".to_string(),
370            false,
371            origin.clone(),
372        ));
373        aliases.insert(HashEntry::new(
374            "FOR_A".to_string(),
375            "for A ".to_string(),
376            false,
377            origin,
378        ));
379        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
380
381        let result = parser.take_token_manual(true).now_or_never().unwrap();
382        assert_matches!(result, Ok(Rec::AliasSubstituted));
383
384        let result = parser.compound_command().now_or_never().unwrap();
385        let compound_command = result.unwrap().unwrap();
386        assert_eq!(compound_command.to_string(), "for A do :; done");
387
388        let next = parser.peek_token().now_or_never().unwrap().unwrap();
389        assert_eq!(next.id, EndOfInput);
390    }
391
392    #[test]
393    fn parser_for_loop_missing_name_eof() {
394        let mut lexer = Lexer::with_code(" for ");
395        let mut parser = Parser::new(&mut lexer);
396
397        let result = parser.compound_command().now_or_never().unwrap();
398        let e = result.unwrap_err();
399        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
400        assert_eq!(*e.location.code.value.borrow(), " for ");
401        assert_eq!(e.location.code.start_line_number.get(), 1);
402        assert_eq!(*e.location.code.source, Source::Unknown);
403        assert_eq!(e.location.range, 5..5);
404    }
405
406    #[test]
407    fn parser_for_loop_missing_name_newline() {
408        let mut lexer = Lexer::with_code(" for\ndo :; done");
409        let mut parser = Parser::new(&mut lexer);
410
411        let result = parser.compound_command().now_or_never().unwrap();
412        let e = result.unwrap_err();
413        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
414        assert_eq!(*e.location.code.value.borrow(), " for\n");
415        assert_eq!(e.location.code.start_line_number.get(), 1);
416        assert_eq!(*e.location.code.source, Source::Unknown);
417        assert_eq!(e.location.range, 4..5);
418    }
419
420    #[test]
421    fn parser_for_loop_missing_name_semicolon() {
422        let mut lexer = Lexer::with_code("for; do :; done");
423        let mut parser = Parser::new(&mut lexer);
424
425        let result = parser.compound_command().now_or_never().unwrap();
426        let e = result.unwrap_err();
427        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
428        assert_eq!(*e.location.code.value.borrow(), "for; do :; done");
429        assert_eq!(e.location.code.start_line_number.get(), 1);
430        assert_eq!(*e.location.code.source, Source::Unknown);
431        assert_eq!(e.location.range, 3..4);
432    }
433
434    #[test]
435    fn parser_for_loop_invalid_name() {
436        // Alias substitution results in "for & do :; done"
437        let mut lexer = Lexer::with_code("FOR if do :; done");
438        #[allow(clippy::mutable_key_type)]
439        let mut aliases = AliasSet::new();
440        let origin = Location::dummy("");
441        aliases.insert(HashEntry::new(
442            "FOR".to_string(),
443            "for ".to_string(),
444            false,
445            origin.clone(),
446        ));
447        aliases.insert(HashEntry::new(
448            "if".to_string(),
449            "&".to_string(),
450            false,
451            origin,
452        ));
453        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
454
455        let result = parser.take_token_manual(true).now_or_never().unwrap();
456        assert_matches!(result, Ok(Rec::AliasSubstituted));
457
458        let result = parser.compound_command().now_or_never().unwrap();
459        let e = result.unwrap_err();
460        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidForName));
461        assert_eq!(*e.location.code.value.borrow(), "&");
462        assert_eq!(e.location.code.start_line_number.get(), 1);
463        assert_eq!(e.location.range, 0..1);
464        assert_matches!(&*e.location.code.source, Source::Alias { original, alias } => {
465            assert_eq!(*original.code.value.borrow(), "FOR if do :; done");
466            assert_eq!(original.code.start_line_number.get(), 1);
467            assert_eq!(*original.code.source, Source::Unknown);
468            assert_eq!(original.range, 4..6);
469            assert_eq!(alias.name, "if");
470        });
471    }
472
473    #[test]
474    fn parser_for_loop_semicolon_after_newline() {
475        let mut lexer = Lexer::with_code("for X\n; do :; done");
476        let mut parser = Parser::new(&mut lexer);
477
478        let result = parser.compound_command().now_or_never().unwrap();
479        let e = result.unwrap_err();
480        assert_matches!(&e.cause,
481            ErrorCause::Syntax(SyntaxError::MissingForBody { opening_location }) => {
482            assert_eq!(*opening_location.code.value.borrow(), "for X\n; do :; done");
483            assert_eq!(opening_location.code.start_line_number.get(), 1);
484            assert_eq!(*opening_location.code.source, Source::Unknown);
485            assert_eq!(opening_location.range, 0..3);
486        });
487        assert_eq!(*e.location.code.value.borrow(), "for X\n; do :; done");
488        assert_eq!(e.location.code.start_line_number.get(), 1);
489        assert_eq!(*e.location.code.source, Source::Unknown);
490        assert_eq!(e.location.range, 6..7);
491    }
492
493    #[test]
494    fn parser_for_loop_invalid_values_delimiter() {
495        // Alias substitution results in "for A in a b & c; do :; done"
496        let mut lexer = Lexer::with_code("for_A_in_a_b if c; do :; done");
497        #[allow(clippy::mutable_key_type)]
498        let mut aliases = AliasSet::new();
499        let origin = Location::dummy("");
500        aliases.insert(HashEntry::new(
501            "for_A_in_a_b".to_string(),
502            "for A in a b ".to_string(),
503            false,
504            origin.clone(),
505        ));
506        aliases.insert(HashEntry::new(
507            "if".to_string(),
508            "&".to_string(),
509            false,
510            origin,
511        ));
512        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
513
514        let result = parser.take_token_manual(true).now_or_never().unwrap();
515        assert_matches!(result, Ok(Rec::AliasSubstituted));
516
517        let result = parser.compound_command().now_or_never().unwrap();
518        let e = result.unwrap_err();
519        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidForValue));
520        assert_eq!(*e.location.code.value.borrow(), "&");
521        assert_eq!(e.location.code.start_line_number.get(), 1);
522        assert_eq!(e.location.range, 0..1);
523        assert_matches!(&*e.location.code.source, Source::Alias { original, alias } => {
524            assert_eq!(*original.code.value.borrow(), "for_A_in_a_b if c; do :; done");
525            assert_eq!(original.code.start_line_number.get(), 1);
526            assert_eq!(*original.code.source, Source::Unknown);
527            assert_eq!(original.range, 13..15);
528            assert_eq!(alias.name, "if");
529        });
530    }
531
532    #[test]
533    fn parser_for_loop_invalid_token_after_semicolon() {
534        let mut lexer = Lexer::with_code(" for X; ! do :; done");
535        let mut parser = Parser::new(&mut lexer);
536
537        let result = parser.compound_command().now_or_never().unwrap();
538        let e = result.unwrap_err();
539        assert_matches!(&e.cause,
540            ErrorCause::Syntax(SyntaxError::MissingForBody { opening_location }) => {
541            assert_eq!(*opening_location.code.value.borrow(), " for X; ! do :; done");
542            assert_eq!(opening_location.code.start_line_number.get(), 1);
543            assert_eq!(*opening_location.code.source, Source::Unknown);
544            assert_eq!(opening_location.range, 1..4);
545        });
546        assert_eq!(*e.location.code.value.borrow(), " for X; ! do :; done");
547        assert_eq!(e.location.code.start_line_number.get(), 1);
548        assert_eq!(*e.location.code.source, Source::Unknown);
549        assert_eq!(e.location.range, 8..9);
550    }
551}