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::source::Source;
144    use assert_matches::assert_matches;
145    use futures_util::FutureExt;
146
147    #[test]
148    fn parser_if_command_minimum() {
149        let mut lexer = Lexer::with_code("if a; then b; fi");
150        let mut parser = Parser::new(&mut lexer);
151
152        let result = parser.compound_command().now_or_never().unwrap();
153        let compound_command = result.unwrap().unwrap();
154        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
155            assert_eq!(condition.to_string(), "a");
156            assert_eq!(body.to_string(), "b");
157            assert_eq!(elifs, []);
158            assert_eq!(r#else, None);
159        });
160
161        let next = parser.peek_token().now_or_never().unwrap().unwrap();
162        assert_eq!(next.id, EndOfInput);
163    }
164
165    #[test]
166    fn parser_if_command_one_elif() {
167        let mut lexer = Lexer::with_code("if\ntrue\nthen\nfalse\n\nelif x; then y& fi");
168        let mut parser = Parser::new(&mut lexer);
169
170        let result = parser.compound_command().now_or_never().unwrap();
171        let compound_command = result.unwrap().unwrap();
172        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
173            assert_eq!(condition.to_string(), "true");
174            assert_eq!(body.to_string(), "false");
175            assert_eq!(elifs.len(), 1);
176            assert_eq!(elifs[0].to_string(), "elif x; then y&");
177            assert_eq!(r#else, None);
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_if_command_many_elifs() {
186        let mut lexer = Lexer::with_code(
187            "if a; then b; elif c; then d; elif e 1; e 2& then f 1; f 2& elif g; then h; fi",
188        );
189        let mut parser = Parser::new(&mut lexer);
190
191        let result = parser.compound_command().now_or_never().unwrap();
192        let compound_command = result.unwrap().unwrap();
193        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
194            assert_eq!(condition.to_string(), "a");
195            assert_eq!(body.to_string(), "b");
196            assert_eq!(elifs.len(), 3);
197            assert_eq!(elifs[0].to_string(), "elif c; then d");
198            assert_eq!(elifs[1].to_string(), "elif e 1; e 2& then f 1; f 2&");
199            assert_eq!(elifs[2].to_string(), "elif g; then h");
200            assert_eq!(r#else, None);
201        });
202
203        let next = parser.peek_token().now_or_never().unwrap().unwrap();
204        assert_eq!(next.id, EndOfInput);
205    }
206
207    #[test]
208    fn parser_if_command_else() {
209        let mut lexer = Lexer::with_code("if a; then b; else c; d; fi");
210        let mut parser = Parser::new(&mut lexer);
211
212        let result = parser.compound_command().now_or_never().unwrap();
213        let compound_command = result.unwrap().unwrap();
214        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
215            assert_eq!(condition.to_string(), "a");
216            assert_eq!(body.to_string(), "b");
217            assert_eq!(elifs, []);
218            assert_eq!(r#else.unwrap().to_string(), "c; d");
219        });
220
221        let next = parser.peek_token().now_or_never().unwrap().unwrap();
222        assert_eq!(next.id, EndOfInput);
223    }
224
225    #[test]
226    fn parser_if_command_elif_and_else() {
227        let mut lexer = Lexer::with_code("if 1; then 2; elif 3; then 4; else 5; fi");
228        let mut parser = Parser::new(&mut lexer);
229
230        let result = parser.compound_command().now_or_never().unwrap();
231        let compound_command = result.unwrap().unwrap();
232        assert_matches!(compound_command, CompoundCommand::If { condition, body, elifs, r#else } => {
233            assert_eq!(condition.to_string(), "1");
234            assert_eq!(body.to_string(), "2");
235            assert_eq!(elifs.len(), 1);
236            assert_eq!(elifs[0].to_string(), "elif 3; then 4");
237            assert_eq!(r#else.unwrap().to_string(), "5");
238        });
239
240        let next = parser.peek_token().now_or_never().unwrap().unwrap();
241        assert_eq!(next.id, EndOfInput);
242    }
243
244    #[test]
245    fn parser_if_command_without_then_after_if() {
246        let mut lexer = Lexer::with_code(" if :; fi");
247        let mut parser = Parser::new(&mut lexer);
248
249        let result = parser.compound_command().now_or_never().unwrap();
250        let e = result.unwrap_err();
251        assert_matches!(e.cause, ErrorCause::Syntax(SyntaxError::IfMissingThen { if_location }) => {
252            assert_eq!(*if_location.code.value.borrow(), " if :; fi");
253            assert_eq!(if_location.code.start_line_number.get(), 1);
254            assert_eq!(*if_location.code.source, Source::Unknown);
255            assert_eq!(if_location.range, 1..3);
256        });
257        assert_eq!(*e.location.code.value.borrow(), " if :; fi");
258        assert_eq!(e.location.code.start_line_number.get(), 1);
259        assert_eq!(*e.location.code.source, Source::Unknown);
260        assert_eq!(e.location.range, 7..9);
261    }
262
263    #[test]
264    fn parser_if_command_without_then_after_elif() {
265        let mut lexer = Lexer::with_code("if a; then b; elif c; fi");
266        let mut parser = Parser::new(&mut lexer);
267
268        let result = parser.compound_command().now_or_never().unwrap();
269        let e = result.unwrap_err();
270        assert_matches!(e.cause,
271            ErrorCause::Syntax(SyntaxError::ElifMissingThen { elif_location }) => {
272            assert_eq!(*elif_location.code.value.borrow(), "if a; then b; elif c; fi");
273            assert_eq!(elif_location.code.start_line_number.get(), 1);
274            assert_eq!(*elif_location.code.source, Source::Unknown);
275            assert_eq!(elif_location.range, 14..18);
276        });
277        assert_eq!(*e.location.code.value.borrow(), "if a; then b; elif c; fi");
278        assert_eq!(e.location.code.start_line_number.get(), 1);
279        assert_eq!(*e.location.code.source, Source::Unknown);
280        assert_eq!(e.location.range, 22..24);
281    }
282
283    #[test]
284    fn parser_if_command_without_fi() {
285        let mut lexer = Lexer::with_code("  if :; then :; }");
286        let mut parser = Parser::new(&mut lexer);
287
288        let result = parser.compound_command().now_or_never().unwrap();
289        let e = result.unwrap_err();
290        assert_matches!(e.cause,
291            ErrorCause::Syntax(SyntaxError::UnclosedIf { opening_location }) => {
292            assert_eq!(*opening_location.code.value.borrow(), "  if :; then :; }");
293            assert_eq!(opening_location.code.start_line_number.get(), 1);
294            assert_eq!(*opening_location.code.source, Source::Unknown);
295            assert_eq!(opening_location.range, 2..4);
296        });
297        assert_eq!(*e.location.code.value.borrow(), "  if :; then :; }");
298        assert_eq!(e.location.code.start_line_number.get(), 1);
299        assert_eq!(*e.location.code.source, Source::Unknown);
300        assert_eq!(e.location.range, 16..17);
301    }
302
303    #[test]
304    fn parser_if_command_empty_condition() {
305        let mut lexer = Lexer::with_code("   if then :; fi");
306        let mut parser = Parser::new(&mut lexer);
307
308        let result = parser.compound_command().now_or_never().unwrap();
309        let e = result.unwrap_err();
310        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyIfCondition));
311        assert_eq!(*e.location.code.value.borrow(), "   if then :; fi");
312        assert_eq!(e.location.code.start_line_number.get(), 1);
313        assert_eq!(*e.location.code.source, Source::Unknown);
314        assert_eq!(e.location.range, 6..10);
315    }
316
317    #[test]
318    fn parser_if_command_empty_body() {
319        let mut lexer = Lexer::with_code("if :; then fi");
320        let mut parser = Parser::new(&mut lexer);
321
322        let result = parser.compound_command().now_or_never().unwrap();
323        let e = result.unwrap_err();
324        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyIfBody));
325        assert_eq!(*e.location.code.value.borrow(), "if :; then fi");
326        assert_eq!(e.location.code.start_line_number.get(), 1);
327        assert_eq!(*e.location.code.source, Source::Unknown);
328        assert_eq!(e.location.range, 11..13);
329    }
330
331    #[test]
332    fn parser_if_command_empty_elif_condition() {
333        let mut lexer = Lexer::with_code("if :; then :; elif then :; fi");
334        let mut parser = Parser::new(&mut lexer);
335
336        let result = parser.compound_command().now_or_never().unwrap();
337        let e = result.unwrap_err();
338        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyElifCondition));
339        assert_eq!(
340            *e.location.code.value.borrow(),
341            "if :; then :; elif then :; fi"
342        );
343        assert_eq!(e.location.code.start_line_number.get(), 1);
344        assert_eq!(*e.location.code.source, Source::Unknown);
345        assert_eq!(e.location.range, 19..23);
346    }
347
348    #[test]
349    fn parser_if_command_empty_elif_body() {
350        let mut lexer = Lexer::with_code("if :; then :; elif :; then fi");
351        let mut parser = Parser::new(&mut lexer);
352
353        let result = parser.compound_command().now_or_never().unwrap();
354        let e = result.unwrap_err();
355        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyElifBody));
356        assert_eq!(
357            *e.location.code.value.borrow(),
358            "if :; then :; elif :; then fi"
359        );
360        assert_eq!(e.location.code.start_line_number.get(), 1);
361        assert_eq!(*e.location.code.source, Source::Unknown);
362        assert_eq!(e.location.range, 27..29);
363    }
364
365    #[test]
366    fn parser_if_command_empty_else() {
367        let mut lexer = Lexer::with_code("if :; then :; else fi");
368        let mut parser = Parser::new(&mut lexer);
369
370        let result = parser.compound_command().now_or_never().unwrap();
371        let e = result.unwrap_err();
372        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyElse));
373        assert_eq!(*e.location.code.value.borrow(), "if :; then :; else fi");
374        assert_eq!(e.location.code.start_line_number.get(), 1);
375        assert_eq!(*e.location.code.source, Source::Unknown);
376        assert_eq!(e.location.range, 19..21);
377    }
378}