yash_syntax/parser/
if.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 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 if command
18
19use super::core::Parser;
20use super::core::Result;
21use super::error::Error;
22use super::error::SyntaxError;
23use super::lex::Keyword::{Elif, Else, Fi, If, Then};
24use super::lex::TokenId::Token;
25use crate::syntax::CompoundCommand;
26use crate::syntax::ElifThen;
27
28impl Parser<'_, '_> {
29    /// Parses an elif-then clause.
30    ///
31    /// Returns `Ok(None)` if the next token is not `elif`.
32    async fn elif_then_clause(&mut self) -> Result<Option<ElifThen>> {
33        if self.peek_token().await?.id != Token(Some(Elif)) {
34            return Ok(None);
35        }
36
37        let elif = self.take_token_raw().await?;
38
39        let condition = self.maybe_compound_list_boxed().await?;
40        let then = self.take_token_raw().await?;
41
42        // TODO allow empty condition if not POSIXly-correct
43        if condition.0.is_empty() {
44            let cause = SyntaxError::EmptyElifCondition.into();
45            let location = then.word.location;
46            return Err(Error { cause, location });
47        }
48        if then.id != Token(Some(Then)) {
49            let elif_location = elif.word.location;
50            let cause = SyntaxError::ElifMissingThen { elif_location }.into();
51            let location = then.word.location;
52            return Err(Error { cause, location });
53        }
54
55        let body = self.maybe_compound_list_boxed().await?;
56        // TODO allow empty body if not POSIXly-correct
57        if body.0.is_empty() {
58            let cause = SyntaxError::EmptyElifBody.into();
59            let location = self.take_token_raw().await?.word.location;
60            return Err(Error { cause, location });
61        }
62
63        Ok(Some(ElifThen { condition, body }))
64    }
65
66    /// Parses an if conditional construct.
67    ///
68    /// The next token must be the `if` reserved word.
69    ///
70    /// # Panics
71    ///
72    /// If the first token is not `if`.
73    pub async fn if_command(&mut self) -> Result<CompoundCommand> {
74        let open = self.take_token_raw().await?;
75        assert_eq!(open.id, Token(Some(If)));
76
77        let condition = self.maybe_compound_list_boxed().await?;
78        let then = self.take_token_raw().await?;
79
80        // TODO allow empty condition if not POSIXly-correct
81        if condition.0.is_empty() {
82            let cause = SyntaxError::EmptyIfCondition.into();
83            let location = then.word.location;
84            return Err(Error { cause, location });
85        }
86        if then.id != Token(Some(Then)) {
87            let if_location = open.word.location;
88            let cause = SyntaxError::IfMissingThen { if_location }.into();
89            let location = then.word.location;
90            return Err(Error { cause, location });
91        }
92
93        let body = self.maybe_compound_list_boxed().await?;
94        // TODO allow empty body if not POSIXly-correct
95        if body.0.is_empty() {
96            let cause = SyntaxError::EmptyIfBody.into();
97            let location = self.take_token_raw().await?.word.location;
98            return Err(Error { cause, location });
99        }
100
101        let mut elifs = Vec::new();
102        while let Some(elif) = self.elif_then_clause().await? {
103            elifs.push(elif);
104        }
105
106        let r#else = if self.peek_token().await?.id == Token(Some(Else)) {
107            self.take_token_raw().await?;
108            let content = self.maybe_compound_list_boxed().await?;
109            // TODO allow empty else if not POSIXly-correct
110            if content.0.is_empty() {
111                let cause = SyntaxError::EmptyElse.into();
112                let location = self.take_token_raw().await?.word.location;
113                return Err(Error { cause, location });
114            }
115            Some(content)
116        } else {
117            None
118        };
119
120        let fi = self.take_token_raw().await?;
121        if fi.id != Token(Some(Fi)) {
122            let opening_location = open.word.location;
123            let cause = SyntaxError::UnclosedIf { opening_location }.into();
124            let location = fi.word.location;
125            return Err(Error { cause, location });
126        }
127
128        Ok(CompoundCommand::If {
129            condition,
130            body,
131            elifs,
132            r#else,
133        })
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::super::error::ErrorCause;
140    use super::super::lex::Lexer;
141    use super::super::lex::TokenId::EndOfInput;
142    use super::*;
143    use crate::alias::EmptyGlossary;
144    use crate::source::Source;
145    use assert_matches::assert_matches;
146    use futures_util::FutureExt;
147
148    #[test]
149    fn parser_if_command_minimum() {
150        let mut lexer = Lexer::from_memory("if a; then b; fi", Source::Unknown);
151        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
152
153        let result = parser.compound_command().now_or_never().unwrap();
154        let compound_command = result.unwrap().unwrap();
155        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
156            assert_eq!(condition.to_string(), "a");
157            assert_eq!(body.to_string(), "b");
158            assert_eq!(elifs, []);
159            assert_eq!(r#else, None);
160        });
161
162        let next = parser.peek_token().now_or_never().unwrap().unwrap();
163        assert_eq!(next.id, EndOfInput);
164    }
165
166    #[test]
167    fn parser_if_command_one_elif() {
168        let mut lexer = Lexer::from_memory(
169            "if\ntrue\nthen\nfalse\n\nelif x; then y& fi",
170            Source::Unknown,
171        );
172        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
173
174        let result = parser.compound_command().now_or_never().unwrap();
175        let compound_command = result.unwrap().unwrap();
176        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
177            assert_eq!(condition.to_string(), "true");
178            assert_eq!(body.to_string(), "false");
179            assert_eq!(elifs.len(), 1);
180            assert_eq!(elifs[0].to_string(), "elif x; then y&");
181            assert_eq!(r#else, None);
182        });
183
184        let next = parser.peek_token().now_or_never().unwrap().unwrap();
185        assert_eq!(next.id, EndOfInput);
186    }
187
188    #[test]
189    fn parser_if_command_many_elifs() {
190        let mut lexer = Lexer::from_memory(
191            "if a; then b; elif c; then d; elif e 1; e 2& then f 1; f 2& elif g; then h; fi",
192            Source::Unknown,
193        );
194        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
195
196        let result = parser.compound_command().now_or_never().unwrap();
197        let compound_command = result.unwrap().unwrap();
198        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
199            assert_eq!(condition.to_string(), "a");
200            assert_eq!(body.to_string(), "b");
201            assert_eq!(elifs.len(), 3);
202            assert_eq!(elifs[0].to_string(), "elif c; then d");
203            assert_eq!(elifs[1].to_string(), "elif e 1; e 2& then f 1; f 2&");
204            assert_eq!(elifs[2].to_string(), "elif g; then h");
205            assert_eq!(r#else, None);
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_if_command_else() {
214        let mut lexer = Lexer::from_memory("if a; then b; else c; d; fi", Source::Unknown);
215        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
216
217        let result = parser.compound_command().now_or_never().unwrap();
218        let compound_command = result.unwrap().unwrap();
219        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
220            assert_eq!(condition.to_string(), "a");
221            assert_eq!(body.to_string(), "b");
222            assert_eq!(elifs, []);
223            assert_eq!(r#else.unwrap().to_string(), "c; d");
224        });
225
226        let next = parser.peek_token().now_or_never().unwrap().unwrap();
227        assert_eq!(next.id, EndOfInput);
228    }
229
230    #[test]
231    fn parser_if_command_elif_and_else() {
232        let mut lexer =
233            Lexer::from_memory("if 1; then 2; elif 3; then 4; else 5; fi", Source::Unknown);
234        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
235
236        let result = parser.compound_command().now_or_never().unwrap();
237        let compound_command = result.unwrap().unwrap();
238        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
239            assert_eq!(condition.to_string(), "1");
240            assert_eq!(body.to_string(), "2");
241            assert_eq!(elifs.len(), 1);
242            assert_eq!(elifs[0].to_string(), "elif 3; then 4");
243            assert_eq!(r#else.unwrap().to_string(), "5");
244        });
245
246        let next = parser.peek_token().now_or_never().unwrap().unwrap();
247        assert_eq!(next.id, EndOfInput);
248    }
249
250    #[test]
251    fn parser_if_command_without_then_after_if() {
252        let mut lexer = Lexer::from_memory(" if :; fi", Source::Unknown);
253        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
254
255        let result = parser.compound_command().now_or_never().unwrap();
256        let e = result.unwrap_err();
257        assert_matches!(e.cause, ErrorCause::Syntax(SyntaxError::IfMissingThen { if_location }) => {
258            assert_eq!(*if_location.code.value.borrow(), " if :; fi");
259            assert_eq!(if_location.code.start_line_number.get(), 1);
260            assert_eq!(*if_location.code.source, Source::Unknown);
261            assert_eq!(if_location.range, 1..3);
262        });
263        assert_eq!(*e.location.code.value.borrow(), " if :; fi");
264        assert_eq!(e.location.code.start_line_number.get(), 1);
265        assert_eq!(*e.location.code.source, Source::Unknown);
266        assert_eq!(e.location.range, 7..9);
267    }
268
269    #[test]
270    fn parser_if_command_without_then_after_elif() {
271        let mut lexer = Lexer::from_memory("if a; then b; elif c; fi", Source::Unknown);
272        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
273
274        let result = parser.compound_command().now_or_never().unwrap();
275        let e = result.unwrap_err();
276        assert_matches!(e.cause,
277            ErrorCause::Syntax(SyntaxError::ElifMissingThen { elif_location }) => {
278            assert_eq!(*elif_location.code.value.borrow(), "if a; then b; elif c; fi");
279            assert_eq!(elif_location.code.start_line_number.get(), 1);
280            assert_eq!(*elif_location.code.source, Source::Unknown);
281            assert_eq!(elif_location.range, 14..18);
282        });
283        assert_eq!(*e.location.code.value.borrow(), "if a; then b; elif c; fi");
284        assert_eq!(e.location.code.start_line_number.get(), 1);
285        assert_eq!(*e.location.code.source, Source::Unknown);
286        assert_eq!(e.location.range, 22..24);
287    }
288
289    #[test]
290    fn parser_if_command_without_fi() {
291        let mut lexer = Lexer::from_memory("  if :; then :; }", Source::Unknown);
292        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
293
294        let result = parser.compound_command().now_or_never().unwrap();
295        let e = result.unwrap_err();
296        assert_matches!(e.cause,
297            ErrorCause::Syntax(SyntaxError::UnclosedIf { opening_location }) => {
298            assert_eq!(*opening_location.code.value.borrow(), "  if :; then :; }");
299            assert_eq!(opening_location.code.start_line_number.get(), 1);
300            assert_eq!(*opening_location.code.source, Source::Unknown);
301            assert_eq!(opening_location.range, 2..4);
302        });
303        assert_eq!(*e.location.code.value.borrow(), "  if :; then :; }");
304        assert_eq!(e.location.code.start_line_number.get(), 1);
305        assert_eq!(*e.location.code.source, Source::Unknown);
306        assert_eq!(e.location.range, 16..17);
307    }
308
309    #[test]
310    fn parser_if_command_empty_condition() {
311        let mut lexer = Lexer::from_memory("   if then :; fi", Source::Unknown);
312        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
313
314        let result = parser.compound_command().now_or_never().unwrap();
315        let e = result.unwrap_err();
316        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyIfCondition));
317        assert_eq!(*e.location.code.value.borrow(), "   if then :; fi");
318        assert_eq!(e.location.code.start_line_number.get(), 1);
319        assert_eq!(*e.location.code.source, Source::Unknown);
320        assert_eq!(e.location.range, 6..10);
321    }
322
323    #[test]
324    fn parser_if_command_empty_body() {
325        let mut lexer = Lexer::from_memory("if :; then fi", Source::Unknown);
326        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
327
328        let result = parser.compound_command().now_or_never().unwrap();
329        let e = result.unwrap_err();
330        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyIfBody));
331        assert_eq!(*e.location.code.value.borrow(), "if :; then fi");
332        assert_eq!(e.location.code.start_line_number.get(), 1);
333        assert_eq!(*e.location.code.source, Source::Unknown);
334        assert_eq!(e.location.range, 11..13);
335    }
336
337    #[test]
338    fn parser_if_command_empty_elif_condition() {
339        let mut lexer = Lexer::from_memory("if :; then :; elif then :; fi", Source::Unknown);
340        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
341
342        let result = parser.compound_command().now_or_never().unwrap();
343        let e = result.unwrap_err();
344        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyElifCondition));
345        assert_eq!(
346            *e.location.code.value.borrow(),
347            "if :; then :; elif then :; fi"
348        );
349        assert_eq!(e.location.code.start_line_number.get(), 1);
350        assert_eq!(*e.location.code.source, Source::Unknown);
351        assert_eq!(e.location.range, 19..23);
352    }
353
354    #[test]
355    fn parser_if_command_empty_elif_body() {
356        let mut lexer = Lexer::from_memory("if :; then :; elif :; then fi", Source::Unknown);
357        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
358
359        let result = parser.compound_command().now_or_never().unwrap();
360        let e = result.unwrap_err();
361        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyElifBody));
362        assert_eq!(
363            *e.location.code.value.borrow(),
364            "if :; then :; elif :; then fi"
365        );
366        assert_eq!(e.location.code.start_line_number.get(), 1);
367        assert_eq!(*e.location.code.source, Source::Unknown);
368        assert_eq!(e.location.range, 27..29);
369    }
370
371    #[test]
372    fn parser_if_command_empty_else() {
373        let mut lexer = Lexer::from_memory("if :; then :; else fi", Source::Unknown);
374        let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
375
376        let result = parser.compound_command().now_or_never().unwrap();
377        let e = result.unwrap_err();
378        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyElse));
379        assert_eq!(*e.location.code.value.borrow(), "if :; then :; else fi");
380        assert_eq!(e.location.code.start_line_number.get(), 1);
381        assert_eq!(*e.location.code.source, Source::Unknown);
382        assert_eq!(e.location.range, 19..21);
383    }
384}