Skip to main content

yash_syntax/parser/
redir.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 redirection
18
19use super::core::Parser;
20use super::core::Result;
21use super::error::Error;
22use super::error::SyntaxError;
23use super::lex::Operator::{GreaterOpenParen, LessLess, LessLessDash, LessOpenParen};
24use super::lex::TokenId::{EndOfInput, IoLocation, IoNumber, Operator, Token};
25use crate::source::Location;
26use crate::syntax::Fd;
27use crate::syntax::HereDoc;
28use crate::syntax::Redir;
29use crate::syntax::RedirBody;
30use crate::syntax::RedirOp;
31use crate::syntax::Word;
32use std::cell::OnceCell;
33use std::rc::Rc;
34
35impl Parser<'_, '_> {
36    /// Parses the operand of a redirection operator.
37    async fn redirection_operand(&mut self) -> Result<std::result::Result<Word, Location>> {
38        let operand = self.take_token_auto(&[]).await?;
39        match operand.id {
40            Token(_) => (),
41            Operator(_) | EndOfInput => return Ok(Err(operand.word.location)),
42            IoNumber | IoLocation => (), // TODO reject if POSIXly-correct
43        }
44        Ok(Ok(operand.word))
45    }
46
47    /// Parses a normal redirection body.
48    async fn normal_redirection_body(&mut self, operator: RedirOp) -> Result<RedirBody> {
49        // TODO reject >>| and <<< if POSIXly-correct
50        self.take_token_raw().await?;
51        let operand = self
52            .redirection_operand()
53            .await?
54            .map_err(|location| Error {
55                cause: SyntaxError::MissingRedirOperand.into(),
56                location,
57            })?;
58        Ok(RedirBody::Normal { operator, operand })
59    }
60
61    /// Parses the redirection body for a here-document.
62    async fn here_doc_redirection_body(&mut self, remove_tabs: bool) -> Result<RedirBody> {
63        self.take_token_raw().await?;
64        let delimiter = self
65            .redirection_operand()
66            .await?
67            .map_err(|location| Error {
68                cause: SyntaxError::MissingHereDocDelimiter.into(),
69                location,
70            })?;
71        let here_doc = Rc::new(HereDoc {
72            delimiter,
73            remove_tabs,
74            content: OnceCell::new(),
75        });
76        self.memorize_unread_here_doc(Rc::clone(&here_doc));
77
78        Ok(RedirBody::HereDoc(here_doc))
79    }
80
81    /// Parses the redirection body.
82    async fn redirection_body(&mut self) -> Result<Option<RedirBody>> {
83        let operator = match self.peek_token().await?.id {
84            Operator(operator) => operator,
85            _ => return Ok(None),
86        };
87
88        if let Ok(operator) = RedirOp::try_from(operator) {
89            return Ok(Some(self.normal_redirection_body(operator).await?));
90        }
91        match operator {
92            LessLess => Ok(Some(self.here_doc_redirection_body(false).await?)),
93            LessLessDash => Ok(Some(self.here_doc_redirection_body(true).await?)),
94            LessOpenParen | GreaterOpenParen => {
95                let cause = SyntaxError::UnsupportedProcessRedirection.into();
96                let location = self.peek_token().await?.word.location.clone();
97                Err(Error { cause, location })
98            }
99            _ => Ok(None),
100        }
101    }
102
103    /// Parses a redirection.
104    ///
105    /// If the current token is not a redirection operator, `Ok(None)` is returned. If a word token
106    /// is missing after the operator, `Err(Error{...})` is returned with a cause of
107    /// [`MissingRedirOperand`](SyntaxError::MissingRedirOperand) or
108    /// [`MissingHereDocDelimiter`](SyntaxError::MissingHereDocDelimiter).
109    pub async fn redirection(&mut self) -> Result<Option<Redir>> {
110        let fd = match self.peek_token().await?.id {
111            IoNumber => {
112                let token = self.take_token_raw().await?;
113                if let Ok(fd) = token.word.to_string().parse() {
114                    Some(Fd(fd))
115                } else {
116                    return Err(Error {
117                        cause: SyntaxError::FdOutOfRange.into(),
118                        location: token.word.location,
119                    });
120                }
121            }
122            IoLocation => {
123                let token = self.take_token_raw().await?;
124                // TODO parse the I/O location
125                return Err(Error {
126                    cause: SyntaxError::InvalidIoLocation.into(),
127                    location: token.word.location,
128                });
129            }
130            _ => None,
131        };
132
133        Ok(self
134            .redirection_body()
135            .await?
136            .map(|body| Redir { fd, body }))
137    }
138
139    /// Parses a (possibly empty) sequence of redirections.
140    pub async fn redirections(&mut self) -> Result<Vec<Redir>> {
141        // TODO substitute global aliases
142        let mut redirs = vec![];
143        while let Some(redir) = self.redirection().await? {
144            redirs.push(redir);
145        }
146        Ok(redirs)
147    }
148}
149
150#[allow(
151    clippy::bool_assert_comparison,
152    reason = "to make the expected values clearer"
153)]
154#[cfg(test)]
155mod tests {
156    use super::super::error::ErrorCause;
157    use super::super::lex::Lexer;
158    use super::super::lex::Operator::Newline;
159    use super::*;
160    use crate::source::Source;
161    use assert_matches::assert_matches;
162    use futures_util::FutureExt as _;
163
164    #[test]
165    fn parser_redirection_less() {
166        let mut lexer = Lexer::with_code("</dev/null\n");
167        let mut parser = Parser::new(&mut lexer);
168
169        let result = parser.redirection().now_or_never().unwrap();
170        let redir = result.unwrap().unwrap();
171        assert_eq!(redir.fd, None);
172        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
173            assert_eq!(operator, RedirOp::FileIn);
174            assert_eq!(operand.to_string(), "/dev/null")
175        });
176
177        let next = parser.peek_token().now_or_never().unwrap().unwrap();
178        assert_eq!(next.id, Operator(Newline));
179    }
180
181    #[test]
182    fn parser_redirection_less_greater() {
183        let mut lexer = Lexer::with_code("<> /dev/null\n");
184        let mut parser = Parser::new(&mut lexer);
185
186        let result = parser.redirection().now_or_never().unwrap();
187        let redir = result.unwrap().unwrap();
188        assert_eq!(redir.fd, None);
189        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
190            assert_eq!(operator, RedirOp::FileInOut);
191            assert_eq!(operand.to_string(), "/dev/null")
192        });
193    }
194
195    #[test]
196    fn parser_redirection_greater() {
197        let mut lexer = Lexer::with_code(">/dev/null\n");
198        let mut parser = Parser::new(&mut lexer);
199
200        let result = parser.redirection().now_or_never().unwrap();
201        let redir = result.unwrap().unwrap();
202        assert_eq!(redir.fd, None);
203        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
204            assert_eq!(operator, RedirOp::FileOut);
205            assert_eq!(operand.to_string(), "/dev/null")
206        });
207    }
208
209    #[test]
210    fn parser_redirection_greater_greater() {
211        let mut lexer = Lexer::with_code(" >> /dev/null\n");
212        let mut parser = Parser::new(&mut lexer);
213
214        let result = parser.redirection().now_or_never().unwrap();
215        let redir = result.unwrap().unwrap();
216        assert_eq!(redir.fd, None);
217        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
218            assert_eq!(operator, RedirOp::FileAppend);
219            assert_eq!(operand.to_string(), "/dev/null")
220        });
221    }
222
223    #[test]
224    fn parser_redirection_greater_bar() {
225        let mut lexer = Lexer::with_code(">| /dev/null\n");
226        let mut parser = Parser::new(&mut lexer);
227
228        let result = parser.redirection().now_or_never().unwrap();
229        let redir = result.unwrap().unwrap();
230        assert_eq!(redir.fd, None);
231        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
232            assert_eq!(operator, RedirOp::FileClobber);
233            assert_eq!(operand.to_string(), "/dev/null")
234        });
235    }
236
237    #[test]
238    fn parser_redirection_less_and() {
239        let mut lexer = Lexer::with_code("<& -\n");
240        let mut parser = Parser::new(&mut lexer);
241
242        let result = parser.redirection().now_or_never().unwrap();
243        let redir = result.unwrap().unwrap();
244        assert_eq!(redir.fd, None);
245        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
246            assert_eq!(operator, RedirOp::FdIn);
247            assert_eq!(operand.to_string(), "-")
248        });
249    }
250
251    #[test]
252    fn parser_redirection_greater_and() {
253        let mut lexer = Lexer::with_code(">& 3\n");
254        let mut parser = Parser::new(&mut lexer);
255
256        let result = parser.redirection().now_or_never().unwrap();
257        let redir = result.unwrap().unwrap();
258        assert_eq!(redir.fd, None);
259        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
260            assert_eq!(operator, RedirOp::FdOut);
261            assert_eq!(operand.to_string(), "3")
262        });
263    }
264
265    #[test]
266    fn parser_redirection_greater_greater_bar() {
267        let mut lexer = Lexer::with_code(">>| 3\n");
268        let mut parser = Parser::new(&mut lexer);
269
270        let result = parser.redirection().now_or_never().unwrap();
271        let redir = result.unwrap().unwrap();
272        assert_eq!(redir.fd, None);
273        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
274            assert_eq!(operator, RedirOp::Pipe);
275            assert_eq!(operand.to_string(), "3")
276        });
277    }
278
279    #[test]
280    fn parser_redirection_less_paren() {
281        let mut lexer = Lexer::with_code("<(foo)\n");
282        let mut parser = Parser::new(&mut lexer);
283
284        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
285        assert_eq!(
286            e.cause,
287            ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
288        );
289        assert_eq!(*e.location.code.value.borrow(), "<(foo)\n");
290        assert_eq!(e.location.code.start_line_number.get(), 1);
291        assert_eq!(*e.location.code.source, Source::Unknown);
292        assert_eq!(e.location.range, 0..2);
293    }
294
295    #[test]
296    fn parser_redirection_greater_paren() {
297        let mut lexer = Lexer::with_code(">(foo)\n");
298        let mut parser = Parser::new(&mut lexer);
299
300        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
301        assert_eq!(
302            e.cause,
303            ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
304        );
305        assert_eq!(*e.location.code.value.borrow(), ">(foo)\n");
306        assert_eq!(e.location.code.start_line_number.get(), 1);
307        assert_eq!(*e.location.code.source, Source::Unknown);
308        assert_eq!(e.location.range, 0..2);
309    }
310
311    #[test]
312    fn parser_redirection_less_less_less() {
313        let mut lexer = Lexer::with_code("<<< foo\n");
314        let mut parser = Parser::new(&mut lexer);
315
316        let result = parser.redirection().now_or_never().unwrap();
317        let redir = result.unwrap().unwrap();
318        assert_eq!(redir.fd, None);
319        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
320            assert_eq!(operator, RedirOp::String);
321            assert_eq!(operand.to_string(), "foo")
322        });
323    }
324
325    #[test]
326    fn parser_redirection_less_less() {
327        let mut lexer = Lexer::with_code("<<end \nend\n");
328        let mut parser = Parser::new(&mut lexer);
329
330        let result = parser.redirection().now_or_never().unwrap();
331        let redir = result.unwrap().unwrap();
332        assert_eq!(redir.fd, None);
333        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
334
335        parser
336            .newline_and_here_doc_contents()
337            .now_or_never()
338            .unwrap()
339            .unwrap();
340        assert_eq!(here_doc.delimiter.to_string(), "end");
341        assert_eq!(here_doc.remove_tabs, false);
342        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
343    }
344
345    #[test]
346    fn parser_redirection_less_less_dash() {
347        let mut lexer = Lexer::with_code("<<-end \nend\n");
348        let mut parser = Parser::new(&mut lexer);
349
350        let result = parser.redirection().now_or_never().unwrap();
351        let redir = result.unwrap().unwrap();
352        assert_eq!(redir.fd, None);
353        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
354
355        parser
356            .newline_and_here_doc_contents()
357            .now_or_never()
358            .unwrap()
359            .unwrap();
360        assert_eq!(here_doc.delimiter.to_string(), "end");
361        assert_eq!(here_doc.remove_tabs, true);
362        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
363    }
364
365    #[test]
366    fn parser_redirection_with_io_number() {
367        let mut lexer = Lexer::with_code("12< /dev/null\n");
368        let mut parser = Parser::new(&mut lexer);
369
370        let result = parser.redirection().now_or_never().unwrap();
371        let redir = result.unwrap().unwrap();
372        assert_eq!(redir.fd, Some(Fd(12)));
373        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
374            assert_eq!(operator, RedirOp::FileIn);
375            assert_eq!(operand.to_string(), "/dev/null")
376        });
377
378        let next = parser.peek_token().now_or_never().unwrap().unwrap();
379        assert_eq!(next.id, Operator(Newline));
380    }
381
382    #[test]
383    fn parser_redirection_fd_out_of_range() {
384        let mut lexer = Lexer::with_code("9999999999999999999999999999999999999999< x");
385        let mut parser = Parser::new(&mut lexer);
386
387        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
388        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::FdOutOfRange));
389        assert_eq!(
390            *e.location.code.value.borrow(),
391            "9999999999999999999999999999999999999999< x"
392        );
393        assert_eq!(e.location.code.start_line_number.get(), 1);
394        assert_eq!(*e.location.code.source, Source::Unknown);
395        assert_eq!(e.location.range, 0..40);
396    }
397
398    #[test]
399    fn parser_redirection_io_location() {
400        let mut lexer = Lexer::with_code("{n}< /dev/null\n");
401        let mut parser = Parser::new(&mut lexer);
402
403        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
404        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidIoLocation));
405        assert_eq!(*e.location.code.value.borrow(), "{n}< /dev/null\n");
406        assert_eq!(e.location.code.start_line_number.get(), 1);
407        assert_eq!(*e.location.code.source, Source::Unknown);
408        assert_eq!(e.location.range, 0..3);
409    }
410
411    #[test]
412    fn parser_redirection_not_operator() {
413        let mut lexer = Lexer::with_code("x");
414        let mut parser = Parser::new(&mut lexer);
415
416        let result = parser.redirection().now_or_never().unwrap();
417        assert_eq!(result, Ok(None));
418    }
419
420    #[test]
421    fn parser_redirection_non_word_operand() {
422        let mut lexer = Lexer::with_code(" < >");
423        let mut parser = Parser::new(&mut lexer);
424
425        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
426        assert_eq!(
427            e.cause,
428            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
429        );
430        assert_eq!(*e.location.code.value.borrow(), " < >");
431        assert_eq!(e.location.code.start_line_number.get(), 1);
432        assert_eq!(*e.location.code.source, Source::Unknown);
433        assert_eq!(e.location.range, 3..4);
434    }
435
436    #[test]
437    fn parser_redirection_eof_operand() {
438        let mut lexer = Lexer::with_code("  < ");
439        let mut parser = Parser::new(&mut lexer);
440
441        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
442        assert_eq!(
443            e.cause,
444            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
445        );
446        assert_eq!(*e.location.code.value.borrow(), "  < ");
447        assert_eq!(e.location.code.start_line_number.get(), 1);
448        assert_eq!(*e.location.code.source, Source::Unknown);
449        assert_eq!(e.location.range, 4..4);
450    }
451
452    #[test]
453    fn parser_redirection_not_heredoc_delimiter() {
454        let mut lexer = Lexer::with_code("<< <<");
455        let mut parser = Parser::new(&mut lexer);
456
457        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
458        assert_eq!(
459            e.cause,
460            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
461        );
462        assert_eq!(*e.location.code.value.borrow(), "<< <<");
463        assert_eq!(e.location.code.start_line_number.get(), 1);
464        assert_eq!(*e.location.code.source, Source::Unknown);
465        assert_eq!(e.location.range, 3..5);
466    }
467
468    #[test]
469    fn parser_redirection_eof_heredoc_delimiter() {
470        let mut lexer = Lexer::with_code("<<");
471        let mut parser = Parser::new(&mut lexer);
472
473        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
474        assert_eq!(
475            e.cause,
476            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
477        );
478        assert_eq!(*e.location.code.value.borrow(), "<<");
479        assert_eq!(e.location.code.start_line_number.get(), 1);
480        assert_eq!(*e.location.code.source, Source::Unknown);
481        assert_eq!(e.location.range, 2..2);
482    }
483}