yash_syntax/parser/
while_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 while and until loops
18
19use super::core::Parser;
20use super::core::Result;
21use super::error::Error;
22use super::error::SyntaxError;
23use super::lex::Keyword::{Until, While};
24use super::lex::TokenId::Token;
25use crate::syntax::CompoundCommand;
26
27impl Parser<'_, '_> {
28    /// Parses a while loop.
29    ///
30    /// The next token must be the `while` reserved word.
31    ///
32    /// # Panics
33    ///
34    /// If the first token is not `while`.
35    pub async fn while_loop(&mut self) -> Result<CompoundCommand> {
36        let open = self.take_token_raw().await?;
37        assert_eq!(open.id, Token(Some(While)));
38
39        let condition = self.maybe_compound_list_boxed().await?;
40
41        // TODO allow empty condition if not POSIXly-correct
42        if condition.0.is_empty() {
43            let cause = SyntaxError::EmptyWhileCondition.into();
44            let location = self.take_token_raw().await?.word.location;
45            return Err(Error { cause, location });
46        }
47
48        let body = match self.do_clause().await? {
49            Some(body) => body,
50            None => {
51                let opening_location = open.word.location;
52                let cause = SyntaxError::UnclosedWhileClause { opening_location }.into();
53                let location = self.take_token_raw().await?.word.location;
54                return Err(Error { cause, location });
55            }
56        };
57
58        Ok(CompoundCommand::While { condition, body })
59    }
60
61    /// Parses an until loop.
62    ///
63    /// The next token must be the `until` reserved word.
64    ///
65    /// # Panics
66    ///
67    /// If the first token is not `until`.
68    pub async fn until_loop(&mut self) -> Result<CompoundCommand> {
69        let open = self.take_token_raw().await?;
70        assert_eq!(open.id, Token(Some(Until)));
71
72        let condition = self.maybe_compound_list_boxed().await?;
73
74        // TODO allow empty condition if not POSIXly-correct
75        if condition.0.is_empty() {
76            let cause = SyntaxError::EmptyUntilCondition.into();
77            let location = self.take_token_raw().await?.word.location;
78            return Err(Error { cause, location });
79        }
80
81        let body = match self.do_clause().await? {
82            Some(body) => body,
83            None => {
84                let opening_location = open.word.location;
85                let cause = SyntaxError::UnclosedUntilClause { opening_location }.into();
86                let location = self.take_token_raw().await?.word.location;
87                return Err(Error { cause, location });
88            }
89        };
90
91        Ok(CompoundCommand::Until { condition, body })
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::super::error::ErrorCause;
98    use super::super::lex::Lexer;
99    use super::super::lex::TokenId::EndOfInput;
100    use super::*;
101    use crate::alias::{AliasSet, HashEntry};
102    use crate::source::Location;
103    use crate::source::Source;
104    use assert_matches::assert_matches;
105    use futures_util::FutureExt;
106
107    #[test]
108    fn parser_while_loop_short() {
109        let mut lexer = Lexer::with_code("while true; do :; done");
110        let mut parser = Parser::new(&mut lexer);
111
112        let result = parser.compound_command().now_or_never().unwrap();
113        let compound_command = result.unwrap().unwrap();
114        assert_matches!(compound_command, CompoundCommand::While { condition, body } => {
115            assert_eq!(condition.to_string(), "true");
116            assert_eq!(body.to_string(), ":");
117        });
118
119        let next = parser.peek_token().now_or_never().unwrap().unwrap();
120        assert_eq!(next.id, EndOfInput);
121    }
122
123    #[test]
124    fn parser_while_loop_long() {
125        let mut lexer = Lexer::with_code("while false; true& do foo; bar& done");
126        let mut parser = Parser::new(&mut lexer);
127
128        let result = parser.compound_command().now_or_never().unwrap();
129        let compound_command = result.unwrap().unwrap();
130        assert_matches!(compound_command, CompoundCommand::While { condition, body } => {
131            assert_eq!(condition.to_string(), "false; true&");
132            assert_eq!(body.to_string(), "foo; bar&");
133        });
134
135        let next = parser.peek_token().now_or_never().unwrap().unwrap();
136        assert_eq!(next.id, EndOfInput);
137    }
138
139    #[test]
140    fn parser_while_loop_unclosed() {
141        let mut lexer = Lexer::with_code("while :");
142        let mut parser = Parser::new(&mut lexer);
143
144        let result = parser.compound_command().now_or_never().unwrap();
145        let e = result.unwrap_err();
146        assert_matches!(e.cause,
147            ErrorCause::Syntax(SyntaxError::UnclosedWhileClause { opening_location }) => {
148            assert_eq!(*opening_location.code.value.borrow(), "while :");
149            assert_eq!(opening_location.code.start_line_number.get(), 1);
150            assert_eq!(*opening_location.code.source, Source::Unknown);
151            assert_eq!(opening_location.range, 0..5);
152        });
153        assert_eq!(*e.location.code.value.borrow(), "while :");
154        assert_eq!(e.location.code.start_line_number.get(), 1);
155        assert_eq!(*e.location.code.source, Source::Unknown);
156        assert_eq!(e.location.range, 7..7);
157    }
158
159    #[test]
160    fn parser_while_loop_empty_posix() {
161        let mut lexer = Lexer::with_code(" while do :; done");
162        let mut parser = Parser::new(&mut lexer);
163
164        let result = parser.compound_command().now_or_never().unwrap();
165        let e = result.unwrap_err();
166        assert_eq!(
167            e.cause,
168            ErrorCause::Syntax(SyntaxError::EmptyWhileCondition)
169        );
170        assert_eq!(*e.location.code.value.borrow(), " while do :; done");
171        assert_eq!(e.location.code.start_line_number.get(), 1);
172        assert_eq!(*e.location.code.source, Source::Unknown);
173        assert_eq!(e.location.range, 7..9);
174    }
175
176    #[test]
177    fn parser_while_loop_aliasing() {
178        let mut lexer = Lexer::with_code(" while :; DO :; done");
179        #[allow(clippy::mutable_key_type)]
180        let mut aliases = AliasSet::new();
181        let origin = Location::dummy("");
182        aliases.insert(HashEntry::new(
183            "DO".to_string(),
184            "do".to_string(),
185            false,
186            origin.clone(),
187        ));
188        aliases.insert(HashEntry::new(
189            "while".to_string(),
190            ";;".to_string(),
191            false,
192            origin,
193        ));
194        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
195
196        let result = parser.compound_command().now_or_never().unwrap();
197        let compound_command = result.unwrap().unwrap();
198        assert_eq!(compound_command.to_string(), "while :; do :; done");
199
200        let next = parser.peek_token().now_or_never().unwrap().unwrap();
201        assert_eq!(next.id, EndOfInput);
202    }
203
204    #[test]
205    fn parser_until_loop_short() {
206        let mut lexer = Lexer::with_code("until true; do :; done");
207        let mut parser = Parser::new(&mut lexer);
208
209        let result = parser.compound_command().now_or_never().unwrap();
210        let compound_command = result.unwrap().unwrap();
211        assert_matches!(compound_command, CompoundCommand::Until { condition, body } => {
212            assert_eq!(condition.to_string(), "true");
213            assert_eq!(body.to_string(), ":");
214        });
215
216        let next = parser.peek_token().now_or_never().unwrap().unwrap();
217        assert_eq!(next.id, EndOfInput);
218    }
219
220    #[test]
221    fn parser_until_loop_long() {
222        let mut lexer = Lexer::with_code("until false; true& do foo; bar& done");
223        let mut parser = Parser::new(&mut lexer);
224
225        let result = parser.compound_command().now_or_never().unwrap();
226        let compound_command = result.unwrap().unwrap();
227        assert_matches!(compound_command, CompoundCommand::Until { condition, body } => {
228            assert_eq!(condition.to_string(), "false; true&");
229            assert_eq!(body.to_string(), "foo; bar&");
230        });
231
232        let next = parser.peek_token().now_or_never().unwrap().unwrap();
233        assert_eq!(next.id, EndOfInput);
234    }
235
236    #[test]
237    fn parser_until_loop_unclosed() {
238        let mut lexer = Lexer::with_code("until :");
239        let mut parser = Parser::new(&mut lexer);
240
241        let result = parser.compound_command().now_or_never().unwrap();
242        let e = result.unwrap_err();
243        assert_matches!(e.cause,
244            ErrorCause::Syntax(SyntaxError::UnclosedUntilClause { opening_location }) => {
245            assert_eq!(*opening_location.code.value.borrow(), "until :");
246            assert_eq!(opening_location.code.start_line_number.get(), 1);
247            assert_eq!(*opening_location.code.source, Source::Unknown);
248            assert_eq!(opening_location.range, 0..5);
249        });
250        assert_eq!(*e.location.code.value.borrow(), "until :");
251        assert_eq!(e.location.code.start_line_number.get(), 1);
252        assert_eq!(*e.location.code.source, Source::Unknown);
253        assert_eq!(e.location.range, 7..7);
254    }
255
256    #[test]
257    fn parser_until_loop_empty_posix() {
258        let mut lexer = Lexer::with_code("  until do :; done");
259        let mut parser = Parser::new(&mut lexer);
260
261        let result = parser.compound_command().now_or_never().unwrap();
262        let e = result.unwrap_err();
263        assert_eq!(
264            e.cause,
265            ErrorCause::Syntax(SyntaxError::EmptyUntilCondition)
266        );
267        assert_eq!(*e.location.code.value.borrow(), "  until do :; done");
268        assert_eq!(e.location.code.start_line_number.get(), 1);
269        assert_eq!(*e.location.code.source, Source::Unknown);
270        assert_eq!(e.location.range, 8..10);
271    }
272
273    #[test]
274    fn parser_until_loop_aliasing() {
275        let mut lexer = Lexer::with_code(" until :; DO :; done");
276        #[allow(clippy::mutable_key_type)]
277        let mut aliases = AliasSet::new();
278        let origin = Location::dummy("");
279        aliases.insert(HashEntry::new(
280            "DO".to_string(),
281            "do".to_string(),
282            false,
283            origin.clone(),
284        ));
285        aliases.insert(HashEntry::new(
286            "until".to_string(),
287            ";;".to_string(),
288            false,
289            origin,
290        ));
291        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
292
293        let result = parser.compound_command().now_or_never().unwrap();
294        let compound_command = result.unwrap().unwrap();
295        assert_eq!(compound_command.to_string(), "until :; do :; done");
296
297        let next = parser.peek_token().now_or_never().unwrap().unwrap();
298        assert_eq!(next.id, EndOfInput);
299    }
300}