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(clippy::bool_assert_comparison)]
151#[cfg(test)]
152mod tests {
153    use super::super::error::ErrorCause;
154    use super::super::lex::Lexer;
155    use super::super::lex::Operator::Newline;
156    use super::*;
157    use crate::source::Source;
158    use assert_matches::assert_matches;
159    use futures_util::FutureExt;
160
161    #[test]
162    fn parser_redirection_less() {
163        let mut lexer = Lexer::with_code("</dev/null\n");
164        let mut parser = Parser::new(&mut lexer);
165
166        let result = parser.redirection().now_or_never().unwrap();
167        let redir = result.unwrap().unwrap();
168        assert_eq!(redir.fd, None);
169        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
170            assert_eq!(operator, RedirOp::FileIn);
171            assert_eq!(operand.to_string(), "/dev/null")
172        });
173
174        let next = parser.peek_token().now_or_never().unwrap().unwrap();
175        assert_eq!(next.id, Operator(Newline));
176    }
177
178    #[test]
179    fn parser_redirection_less_greater() {
180        let mut lexer = Lexer::with_code("<> /dev/null\n");
181        let mut parser = Parser::new(&mut lexer);
182
183        let result = parser.redirection().now_or_never().unwrap();
184        let redir = result.unwrap().unwrap();
185        assert_eq!(redir.fd, None);
186        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
187            assert_eq!(operator, RedirOp::FileInOut);
188            assert_eq!(operand.to_string(), "/dev/null")
189        });
190    }
191
192    #[test]
193    fn parser_redirection_greater() {
194        let mut lexer = Lexer::with_code(">/dev/null\n");
195        let mut parser = Parser::new(&mut lexer);
196
197        let result = parser.redirection().now_or_never().unwrap();
198        let redir = result.unwrap().unwrap();
199        assert_eq!(redir.fd, None);
200        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
201            assert_eq!(operator, RedirOp::FileOut);
202            assert_eq!(operand.to_string(), "/dev/null")
203        });
204    }
205
206    #[test]
207    fn parser_redirection_greater_greater() {
208        let mut lexer = Lexer::with_code(" >> /dev/null\n");
209        let mut parser = Parser::new(&mut lexer);
210
211        let result = parser.redirection().now_or_never().unwrap();
212        let redir = result.unwrap().unwrap();
213        assert_eq!(redir.fd, None);
214        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
215            assert_eq!(operator, RedirOp::FileAppend);
216            assert_eq!(operand.to_string(), "/dev/null")
217        });
218    }
219
220    #[test]
221    fn parser_redirection_greater_bar() {
222        let mut lexer = Lexer::with_code(">| /dev/null\n");
223        let mut parser = Parser::new(&mut lexer);
224
225        let result = parser.redirection().now_or_never().unwrap();
226        let redir = result.unwrap().unwrap();
227        assert_eq!(redir.fd, None);
228        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
229            assert_eq!(operator, RedirOp::FileClobber);
230            assert_eq!(operand.to_string(), "/dev/null")
231        });
232    }
233
234    #[test]
235    fn parser_redirection_less_and() {
236        let mut lexer = Lexer::with_code("<& -\n");
237        let mut parser = Parser::new(&mut lexer);
238
239        let result = parser.redirection().now_or_never().unwrap();
240        let redir = result.unwrap().unwrap();
241        assert_eq!(redir.fd, None);
242        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
243            assert_eq!(operator, RedirOp::FdIn);
244            assert_eq!(operand.to_string(), "-")
245        });
246    }
247
248    #[test]
249    fn parser_redirection_greater_and() {
250        let mut lexer = Lexer::with_code(">& 3\n");
251        let mut parser = Parser::new(&mut lexer);
252
253        let result = parser.redirection().now_or_never().unwrap();
254        let redir = result.unwrap().unwrap();
255        assert_eq!(redir.fd, None);
256        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
257            assert_eq!(operator, RedirOp::FdOut);
258            assert_eq!(operand.to_string(), "3")
259        });
260    }
261
262    #[test]
263    fn parser_redirection_greater_greater_bar() {
264        let mut lexer = Lexer::with_code(">>| 3\n");
265        let mut parser = Parser::new(&mut lexer);
266
267        let result = parser.redirection().now_or_never().unwrap();
268        let redir = result.unwrap().unwrap();
269        assert_eq!(redir.fd, None);
270        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
271            assert_eq!(operator, RedirOp::Pipe);
272            assert_eq!(operand.to_string(), "3")
273        });
274    }
275
276    #[test]
277    fn parser_redirection_less_paren() {
278        let mut lexer = Lexer::with_code("<(foo)\n");
279        let mut parser = Parser::new(&mut lexer);
280
281        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
282        assert_eq!(
283            e.cause,
284            ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
285        );
286        assert_eq!(*e.location.code.value.borrow(), "<(foo)\n");
287        assert_eq!(e.location.code.start_line_number.get(), 1);
288        assert_eq!(*e.location.code.source, Source::Unknown);
289        assert_eq!(e.location.range, 0..2);
290    }
291
292    #[test]
293    fn parser_redirection_greater_paren() {
294        let mut lexer = Lexer::with_code(">(foo)\n");
295        let mut parser = Parser::new(&mut lexer);
296
297        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
298        assert_eq!(
299            e.cause,
300            ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
301        );
302        assert_eq!(*e.location.code.value.borrow(), ">(foo)\n");
303        assert_eq!(e.location.code.start_line_number.get(), 1);
304        assert_eq!(*e.location.code.source, Source::Unknown);
305        assert_eq!(e.location.range, 0..2);
306    }
307
308    #[test]
309    fn parser_redirection_less_less_less() {
310        let mut lexer = Lexer::with_code("<<< foo\n");
311        let mut parser = Parser::new(&mut lexer);
312
313        let result = parser.redirection().now_or_never().unwrap();
314        let redir = result.unwrap().unwrap();
315        assert_eq!(redir.fd, None);
316        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
317            assert_eq!(operator, RedirOp::String);
318            assert_eq!(operand.to_string(), "foo")
319        });
320    }
321
322    #[test]
323    fn parser_redirection_less_less() {
324        let mut lexer = Lexer::with_code("<<end \nend\n");
325        let mut parser = Parser::new(&mut lexer);
326
327        let result = parser.redirection().now_or_never().unwrap();
328        let redir = result.unwrap().unwrap();
329        assert_eq!(redir.fd, None);
330        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
331
332        parser
333            .newline_and_here_doc_contents()
334            .now_or_never()
335            .unwrap()
336            .unwrap();
337        assert_eq!(here_doc.delimiter.to_string(), "end");
338        assert_eq!(here_doc.remove_tabs, false);
339        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
340    }
341
342    #[test]
343    fn parser_redirection_less_less_dash() {
344        let mut lexer = Lexer::with_code("<<-end \nend\n");
345        let mut parser = Parser::new(&mut lexer);
346
347        let result = parser.redirection().now_or_never().unwrap();
348        let redir = result.unwrap().unwrap();
349        assert_eq!(redir.fd, None);
350        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
351
352        parser
353            .newline_and_here_doc_contents()
354            .now_or_never()
355            .unwrap()
356            .unwrap();
357        assert_eq!(here_doc.delimiter.to_string(), "end");
358        assert_eq!(here_doc.remove_tabs, true);
359        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
360    }
361
362    #[test]
363    fn parser_redirection_with_io_number() {
364        let mut lexer = Lexer::with_code("12< /dev/null\n");
365        let mut parser = Parser::new(&mut lexer);
366
367        let result = parser.redirection().now_or_never().unwrap();
368        let redir = result.unwrap().unwrap();
369        assert_eq!(redir.fd, Some(Fd(12)));
370        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
371            assert_eq!(operator, RedirOp::FileIn);
372            assert_eq!(operand.to_string(), "/dev/null")
373        });
374
375        let next = parser.peek_token().now_or_never().unwrap().unwrap();
376        assert_eq!(next.id, Operator(Newline));
377    }
378
379    #[test]
380    fn parser_redirection_fd_out_of_range() {
381        let mut lexer = Lexer::with_code("9999999999999999999999999999999999999999< x");
382        let mut parser = Parser::new(&mut lexer);
383
384        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
385        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::FdOutOfRange));
386        assert_eq!(
387            *e.location.code.value.borrow(),
388            "9999999999999999999999999999999999999999< x"
389        );
390        assert_eq!(e.location.code.start_line_number.get(), 1);
391        assert_eq!(*e.location.code.source, Source::Unknown);
392        assert_eq!(e.location.range, 0..40);
393    }
394
395    #[test]
396    fn parser_redirection_io_location() {
397        let mut lexer = Lexer::with_code("{n}< /dev/null\n");
398        let mut parser = Parser::new(&mut lexer);
399
400        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
401        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidIoLocation));
402        assert_eq!(*e.location.code.value.borrow(), "{n}< /dev/null\n");
403        assert_eq!(e.location.code.start_line_number.get(), 1);
404        assert_eq!(*e.location.code.source, Source::Unknown);
405        assert_eq!(e.location.range, 0..3);
406    }
407
408    #[test]
409    fn parser_redirection_not_operator() {
410        let mut lexer = Lexer::with_code("x");
411        let mut parser = Parser::new(&mut lexer);
412
413        let result = parser.redirection().now_or_never().unwrap();
414        assert_eq!(result, Ok(None));
415    }
416
417    #[test]
418    fn parser_redirection_non_word_operand() {
419        let mut lexer = Lexer::with_code(" < >");
420        let mut parser = Parser::new(&mut lexer);
421
422        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
423        assert_eq!(
424            e.cause,
425            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
426        );
427        assert_eq!(*e.location.code.value.borrow(), " < >");
428        assert_eq!(e.location.code.start_line_number.get(), 1);
429        assert_eq!(*e.location.code.source, Source::Unknown);
430        assert_eq!(e.location.range, 3..4);
431    }
432
433    #[test]
434    fn parser_redirection_eof_operand() {
435        let mut lexer = Lexer::with_code("  < ");
436        let mut parser = Parser::new(&mut lexer);
437
438        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
439        assert_eq!(
440            e.cause,
441            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
442        );
443        assert_eq!(*e.location.code.value.borrow(), "  < ");
444        assert_eq!(e.location.code.start_line_number.get(), 1);
445        assert_eq!(*e.location.code.source, Source::Unknown);
446        assert_eq!(e.location.range, 4..4);
447    }
448
449    #[test]
450    fn parser_redirection_not_heredoc_delimiter() {
451        let mut lexer = Lexer::with_code("<< <<");
452        let mut parser = Parser::new(&mut lexer);
453
454        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
455        assert_eq!(
456            e.cause,
457            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
458        );
459        assert_eq!(*e.location.code.value.borrow(), "<< <<");
460        assert_eq!(e.location.code.start_line_number.get(), 1);
461        assert_eq!(*e.location.code.source, Source::Unknown);
462        assert_eq!(e.location.range, 3..5);
463    }
464
465    #[test]
466    fn parser_redirection_eof_heredoc_delimiter() {
467        let mut lexer = Lexer::with_code("<<");
468        let mut parser = Parser::new(&mut lexer);
469
470        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
471        assert_eq!(
472            e.cause,
473            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
474        );
475        assert_eq!(*e.location.code.value.borrow(), "<<");
476        assert_eq!(e.location.code.start_line_number.get(), 1);
477        assert_eq!(*e.location.code.source, Source::Unknown);
478        assert_eq!(e.location.range, 2..2);
479    }
480}