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 => {
43                if self.mode().portable {
44                    return Err(Error {
45                        cause: SyntaxError::IoTokenAsRedirOperand.into(),
46                        location: operand.word.location,
47                    });
48                }
49            }
50        }
51        Ok(Ok(operand.word))
52    }
53
54    /// Parses a normal redirection body.
55    async fn normal_redirection_body(&mut self, operator: RedirOp) -> Result<RedirBody> {
56        let token = self.take_token_raw().await?;
57        if self.mode().portable && matches!(operator, RedirOp::Pipe | RedirOp::String) {
58            return Err(Error {
59                cause: SyntaxError::NonPortableRedirOperator(operator).into(),
60                location: token.word.location,
61            });
62        }
63        let operand = self
64            .redirection_operand()
65            .await?
66            .map_err(|location| Error {
67                cause: SyntaxError::MissingRedirOperand.into(),
68                location,
69            })?;
70        Ok(RedirBody::Normal { operator, operand })
71    }
72
73    /// Parses the redirection body for a here-document.
74    async fn here_doc_redirection_body(&mut self, remove_tabs: bool) -> Result<RedirBody> {
75        self.take_token_raw().await?;
76        let delimiter = self
77            .redirection_operand()
78            .await?
79            .map_err(|location| Error {
80                cause: SyntaxError::MissingHereDocDelimiter.into(),
81                location,
82            })?;
83        let here_doc = Rc::new(HereDoc {
84            delimiter,
85            remove_tabs,
86            content: OnceCell::new(),
87        });
88        self.memorize_unread_here_doc(Rc::clone(&here_doc));
89
90        Ok(RedirBody::HereDoc(here_doc))
91    }
92
93    /// Parses the redirection body.
94    async fn redirection_body(&mut self) -> Result<Option<RedirBody>> {
95        let operator = match self.peek_token().await?.id {
96            Operator(operator) => operator,
97            _ => return Ok(None),
98        };
99
100        if let Ok(operator) = RedirOp::try_from(operator) {
101            return Ok(Some(self.normal_redirection_body(operator).await?));
102        }
103        match operator {
104            LessLess => Ok(Some(self.here_doc_redirection_body(false).await?)),
105            LessLessDash => Ok(Some(self.here_doc_redirection_body(true).await?)),
106            LessOpenParen | GreaterOpenParen => {
107                let cause = SyntaxError::UnsupportedProcessRedirection.into();
108                let location = self.peek_token().await?.word.location.clone();
109                Err(Error { cause, location })
110            }
111            _ => Ok(None),
112        }
113    }
114
115    /// Parses a redirection.
116    ///
117    /// If the current token is not a redirection operator, `Ok(None)` is returned. If a word token
118    /// is missing after the operator, `Err(Error{...})` is returned with a cause of
119    /// [`MissingRedirOperand`](SyntaxError::MissingRedirOperand) or
120    /// [`MissingHereDocDelimiter`](SyntaxError::MissingHereDocDelimiter).
121    pub async fn redirection(&mut self) -> Result<Option<Redir>> {
122        let fd = match self.peek_token().await?.id {
123            IoNumber => {
124                let token = self.take_token_raw().await?;
125                if let Ok(fd) = token.word.to_string().parse() {
126                    Some(Fd(fd))
127                } else {
128                    return Err(Error {
129                        cause: SyntaxError::FdOutOfRange.into(),
130                        location: token.word.location,
131                    });
132                }
133            }
134            IoLocation => {
135                let token = self.take_token_raw().await?;
136                // TODO parse the I/O location
137                return Err(Error {
138                    cause: SyntaxError::InvalidIoLocation.into(),
139                    location: token.word.location,
140                });
141            }
142            _ => None,
143        };
144
145        Ok(self
146            .redirection_body()
147            .await?
148            .map(|body| Redir { fd, body }))
149    }
150
151    /// Parses a (possibly empty) sequence of redirections.
152    pub async fn redirections(&mut self) -> Result<Vec<Redir>> {
153        // TODO substitute global aliases
154        let mut redirs = vec![];
155        while let Some(redir) = self.redirection().await? {
156            redirs.push(redir);
157        }
158        Ok(redirs)
159    }
160}
161
162#[allow(
163    clippy::bool_assert_comparison,
164    reason = "to make the expected values clearer"
165)]
166#[cfg(test)]
167mod tests {
168    use super::super::error::ErrorCause;
169    use super::super::lex::Lexer;
170    use super::super::lex::Operator::Newline;
171    use super::*;
172    use crate::source::Source;
173    use assert_matches::assert_matches;
174    use futures_util::FutureExt as _;
175
176    #[test]
177    fn parser_redirection_less() {
178        let mut lexer = Lexer::with_code("</dev/null\n");
179        let mut parser = Parser::new(&mut lexer);
180
181        let result = parser.redirection().now_or_never().unwrap();
182        let redir = result.unwrap().unwrap();
183        assert_eq!(redir.fd, None);
184        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
185            assert_eq!(operator, RedirOp::FileIn);
186            assert_eq!(operand.to_string(), "/dev/null")
187        });
188
189        let next = parser.peek_token().now_or_never().unwrap().unwrap();
190        assert_eq!(next.id, Operator(Newline));
191    }
192
193    #[test]
194    fn parser_redirection_less_greater() {
195        let mut lexer = Lexer::with_code("<> /dev/null\n");
196        let mut parser = Parser::new(&mut lexer);
197
198        let result = parser.redirection().now_or_never().unwrap();
199        let redir = result.unwrap().unwrap();
200        assert_eq!(redir.fd, None);
201        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
202            assert_eq!(operator, RedirOp::FileInOut);
203            assert_eq!(operand.to_string(), "/dev/null")
204        });
205    }
206
207    #[test]
208    fn parser_redirection_greater() {
209        let mut lexer = Lexer::with_code(">/dev/null\n");
210        let mut parser = Parser::new(&mut lexer);
211
212        let result = parser.redirection().now_or_never().unwrap();
213        let redir = result.unwrap().unwrap();
214        assert_eq!(redir.fd, None);
215        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
216            assert_eq!(operator, RedirOp::FileOut);
217            assert_eq!(operand.to_string(), "/dev/null")
218        });
219    }
220
221    #[test]
222    fn parser_redirection_greater_greater() {
223        let mut lexer = Lexer::with_code(" >> /dev/null\n");
224        let mut parser = Parser::new(&mut lexer);
225
226        let result = parser.redirection().now_or_never().unwrap();
227        let redir = result.unwrap().unwrap();
228        assert_eq!(redir.fd, None);
229        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
230            assert_eq!(operator, RedirOp::FileAppend);
231            assert_eq!(operand.to_string(), "/dev/null")
232        });
233    }
234
235    #[test]
236    fn parser_redirection_greater_bar() {
237        let mut lexer = Lexer::with_code(">| /dev/null\n");
238        let mut parser = Parser::new(&mut lexer);
239
240        let result = parser.redirection().now_or_never().unwrap();
241        let redir = result.unwrap().unwrap();
242        assert_eq!(redir.fd, None);
243        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
244            assert_eq!(operator, RedirOp::FileClobber);
245            assert_eq!(operand.to_string(), "/dev/null")
246        });
247    }
248
249    #[test]
250    fn parser_redirection_less_and() {
251        let mut lexer = Lexer::with_code("<& -\n");
252        let mut parser = Parser::new(&mut lexer);
253
254        let result = parser.redirection().now_or_never().unwrap();
255        let redir = result.unwrap().unwrap();
256        assert_eq!(redir.fd, None);
257        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
258            assert_eq!(operator, RedirOp::FdIn);
259            assert_eq!(operand.to_string(), "-")
260        });
261    }
262
263    #[test]
264    fn parser_redirection_greater_and() {
265        let mut lexer = Lexer::with_code(">& 3\n");
266        let mut parser = Parser::new(&mut lexer);
267
268        let result = parser.redirection().now_or_never().unwrap();
269        let redir = result.unwrap().unwrap();
270        assert_eq!(redir.fd, None);
271        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
272            assert_eq!(operator, RedirOp::FdOut);
273            assert_eq!(operand.to_string(), "3")
274        });
275    }
276
277    #[test]
278    fn parser_redirection_greater_greater_bar() {
279        let mut lexer = Lexer::with_code(">>| 3\n");
280        let mut parser = Parser::new(&mut lexer);
281
282        let result = parser.redirection().now_or_never().unwrap();
283        let redir = result.unwrap().unwrap();
284        assert_eq!(redir.fd, None);
285        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
286            assert_eq!(operator, RedirOp::Pipe);
287            assert_eq!(operand.to_string(), "3")
288        });
289    }
290
291    #[test]
292    fn parser_redirection_less_paren() {
293        let mut lexer = Lexer::with_code("<(foo)\n");
294        let mut parser = Parser::new(&mut lexer);
295
296        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
297        assert_eq!(
298            e.cause,
299            ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
300        );
301        assert_eq!(*e.location.code.value.borrow(), "<(foo)\n");
302        assert_eq!(e.location.code.start_line_number.get(), 1);
303        assert_eq!(*e.location.code.source, Source::Unknown);
304        assert_eq!(e.location.range, 0..2);
305    }
306
307    #[test]
308    fn parser_redirection_greater_paren() {
309        let mut lexer = Lexer::with_code(">(foo)\n");
310        let mut parser = Parser::new(&mut lexer);
311
312        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
313        assert_eq!(
314            e.cause,
315            ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
316        );
317        assert_eq!(*e.location.code.value.borrow(), ">(foo)\n");
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, 0..2);
321    }
322
323    #[test]
324    fn parser_redirection_less_less_less() {
325        let mut lexer = Lexer::with_code("<<< foo\n");
326        let mut parser = Parser::new(&mut lexer);
327
328        let result = parser.redirection().now_or_never().unwrap();
329        let redir = result.unwrap().unwrap();
330        assert_eq!(redir.fd, None);
331        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
332            assert_eq!(operator, RedirOp::String);
333            assert_eq!(operand.to_string(), "foo")
334        });
335    }
336
337    #[test]
338    fn parser_redirection_less_less() {
339        let mut lexer = Lexer::with_code("<<end \nend\n");
340        let mut parser = Parser::new(&mut lexer);
341
342        let result = parser.redirection().now_or_never().unwrap();
343        let redir = result.unwrap().unwrap();
344        assert_eq!(redir.fd, None);
345        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
346
347        parser
348            .newline_and_here_doc_contents()
349            .now_or_never()
350            .unwrap()
351            .unwrap();
352        assert_eq!(here_doc.delimiter.to_string(), "end");
353        assert_eq!(here_doc.remove_tabs, false);
354        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
355    }
356
357    #[test]
358    fn parser_redirection_less_less_dash() {
359        let mut lexer = Lexer::with_code("<<-end \nend\n");
360        let mut parser = Parser::new(&mut lexer);
361
362        let result = parser.redirection().now_or_never().unwrap();
363        let redir = result.unwrap().unwrap();
364        assert_eq!(redir.fd, None);
365        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
366
367        parser
368            .newline_and_here_doc_contents()
369            .now_or_never()
370            .unwrap()
371            .unwrap();
372        assert_eq!(here_doc.delimiter.to_string(), "end");
373        assert_eq!(here_doc.remove_tabs, true);
374        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
375    }
376
377    #[test]
378    fn parser_redirection_with_io_number() {
379        let mut lexer = Lexer::with_code("12< /dev/null\n");
380        let mut parser = Parser::new(&mut lexer);
381
382        let result = parser.redirection().now_or_never().unwrap();
383        let redir = result.unwrap().unwrap();
384        assert_eq!(redir.fd, Some(Fd(12)));
385        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
386            assert_eq!(operator, RedirOp::FileIn);
387            assert_eq!(operand.to_string(), "/dev/null")
388        });
389
390        let next = parser.peek_token().now_or_never().unwrap().unwrap();
391        assert_eq!(next.id, Operator(Newline));
392    }
393
394    #[test]
395    fn parser_redirection_fd_out_of_range() {
396        let mut lexer = Lexer::with_code("9999999999999999999999999999999999999999< x");
397        let mut parser = Parser::new(&mut lexer);
398
399        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
400        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::FdOutOfRange));
401        assert_eq!(
402            *e.location.code.value.borrow(),
403            "9999999999999999999999999999999999999999< x"
404        );
405        assert_eq!(e.location.code.start_line_number.get(), 1);
406        assert_eq!(*e.location.code.source, Source::Unknown);
407        assert_eq!(e.location.range, 0..40);
408    }
409
410    #[test]
411    fn parser_redirection_io_location() {
412        let mut lexer = Lexer::with_code("{n}< /dev/null\n");
413        let mut parser = Parser::new(&mut lexer);
414
415        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
416        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidIoLocation));
417        assert_eq!(*e.location.code.value.borrow(), "{n}< /dev/null\n");
418        assert_eq!(e.location.code.start_line_number.get(), 1);
419        assert_eq!(*e.location.code.source, Source::Unknown);
420        assert_eq!(e.location.range, 0..3);
421    }
422
423    #[test]
424    fn parser_redirection_not_operator() {
425        let mut lexer = Lexer::with_code("x");
426        let mut parser = Parser::new(&mut lexer);
427
428        let result = parser.redirection().now_or_never().unwrap();
429        assert_eq!(result, Ok(None));
430    }
431
432    #[test]
433    fn parser_redirection_non_word_operand() {
434        let mut lexer = Lexer::with_code(" < >");
435        let mut parser = Parser::new(&mut lexer);
436
437        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
438        assert_eq!(
439            e.cause,
440            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
441        );
442        assert_eq!(*e.location.code.value.borrow(), " < >");
443        assert_eq!(e.location.code.start_line_number.get(), 1);
444        assert_eq!(*e.location.code.source, Source::Unknown);
445        assert_eq!(e.location.range, 3..4);
446    }
447
448    #[test]
449    fn parser_redirection_eof_operand() {
450        let mut lexer = Lexer::with_code("  < ");
451        let mut parser = Parser::new(&mut lexer);
452
453        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
454        assert_eq!(
455            e.cause,
456            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
457        );
458        assert_eq!(*e.location.code.value.borrow(), "  < ");
459        assert_eq!(e.location.code.start_line_number.get(), 1);
460        assert_eq!(*e.location.code.source, Source::Unknown);
461        assert_eq!(e.location.range, 4..4);
462    }
463
464    #[test]
465    fn parser_redirection_not_heredoc_delimiter() {
466        let mut lexer = Lexer::with_code("<< <<");
467        let mut parser = Parser::new(&mut lexer);
468
469        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
470        assert_eq!(
471            e.cause,
472            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
473        );
474        assert_eq!(*e.location.code.value.borrow(), "<< <<");
475        assert_eq!(e.location.code.start_line_number.get(), 1);
476        assert_eq!(*e.location.code.source, Source::Unknown);
477        assert_eq!(e.location.range, 3..5);
478    }
479
480    #[test]
481    fn parser_redirection_eof_heredoc_delimiter() {
482        let mut lexer = Lexer::with_code("<<");
483        let mut parser = Parser::new(&mut lexer);
484
485        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
486        assert_eq!(
487            e.cause,
488            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
489        );
490        assert_eq!(*e.location.code.value.borrow(), "<<");
491        assert_eq!(e.location.code.start_line_number.get(), 1);
492        assert_eq!(*e.location.code.source, Source::Unknown);
493        assert_eq!(e.location.range, 2..2);
494    }
495
496    fn portable_mode() -> yash_env::parser::Mode {
497        let mut mode = yash_env::parser::Mode::default();
498        mode.portable = true;
499        mode
500    }
501
502    #[test]
503    fn parser_redirection_pipe_rejected_in_portable_mode() {
504        let mut lexer = Lexer::with_code(">>| 3\n");
505        lexer.set_mode(portable_mode());
506        let mut parser = Parser::new(&mut lexer);
507
508        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
509        assert_eq!(
510            e.cause,
511            ErrorCause::Syntax(SyntaxError::NonPortableRedirOperator(RedirOp::Pipe))
512        );
513        assert_eq!(e.location.range, 0..3);
514    }
515
516    #[test]
517    fn parser_redirection_here_string_rejected_in_portable_mode() {
518        let mut lexer = Lexer::with_code("<<< foo\n");
519        lexer.set_mode(portable_mode());
520        let mut parser = Parser::new(&mut lexer);
521
522        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
523        assert_eq!(
524            e.cause,
525            ErrorCause::Syntax(SyntaxError::NonPortableRedirOperator(RedirOp::String))
526        );
527        assert_eq!(e.location.range, 0..3);
528    }
529
530    #[test]
531    fn parser_redirection_portable_operators_allowed_in_portable_mode() {
532        // The standard operators must still be accepted in portable mode.
533        for code in [">| f\n", ">> f\n", "<> f\n", ">& 2\n", "<& 0\n"] {
534            let mut lexer = Lexer::with_code(code);
535            lexer.set_mode(portable_mode());
536            let mut parser = Parser::new(&mut lexer);
537
538            let result = parser.redirection().now_or_never().unwrap();
539            assert!(result.is_ok(), "code={code:?}, result={result:?}");
540        }
541    }
542
543    #[test]
544    fn parser_redirection_io_number_operand_rejected_in_portable_mode() {
545        let mut lexer = Lexer::with_code("< 1>");
546        lexer.set_mode(portable_mode());
547        let mut parser = Parser::new(&mut lexer);
548
549        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
550        assert_eq!(
551            e.cause,
552            ErrorCause::Syntax(SyntaxError::IoTokenAsRedirOperand)
553        );
554        assert_eq!(*e.location.code.value.borrow(), "< 1>");
555        assert_eq!(e.location.range, 2..3);
556    }
557
558    #[test]
559    fn parser_redirection_io_location_operand_rejected_in_portable_mode() {
560        let mut lexer = Lexer::with_code("< {n}>");
561        lexer.set_mode(portable_mode());
562        let mut parser = Parser::new(&mut lexer);
563
564        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
565        assert_eq!(
566            e.cause,
567            ErrorCause::Syntax(SyntaxError::IoTokenAsRedirOperand)
568        );
569        assert_eq!(e.location.range, 2..5);
570    }
571
572    #[test]
573    fn parser_redirection_io_number_operand_allowed_without_portable() {
574        let mut lexer = Lexer::with_code("< 1>");
575        let mut parser = Parser::new(&mut lexer);
576
577        let result = parser.redirection().now_or_never().unwrap();
578        let redir = result.unwrap().unwrap();
579        assert_matches!(redir.body, RedirBody::Normal { operand, .. } => {
580            assert_eq!(operand.to_string(), "1");
581        });
582    }
583
584    #[test]
585    fn parser_redirection_io_location_operand_allowed_without_portable() {
586        let mut lexer = Lexer::with_code("< {n}>");
587        let mut parser = Parser::new(&mut lexer);
588
589        let result = parser.redirection().now_or_never().unwrap();
590        let redir = result.unwrap().unwrap();
591        assert_matches!(redir.body, RedirBody::Normal { operand, .. } => {
592            assert_eq!(operand.to_string(), "{n}");
593        });
594    }
595}