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::{LessLess, LessLessDash};
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            // TODO <() >()
95            _ => Ok(None),
96        }
97    }
98
99    /// Parses a redirection.
100    ///
101    /// If the current token is not a redirection operator, `Ok(None)` is returned. If a word token
102    /// is missing after the operator, `Err(Error{...})` is returned with a cause of
103    /// [`MissingRedirOperand`](SyntaxError::MissingRedirOperand) or
104    /// [`MissingHereDocDelimiter`](SyntaxError::MissingHereDocDelimiter).
105    pub async fn redirection(&mut self) -> Result<Option<Redir>> {
106        let fd = match self.peek_token().await?.id {
107            IoNumber => {
108                let token = self.take_token_raw().await?;
109                if let Ok(fd) = token.word.to_string().parse() {
110                    Some(Fd(fd))
111                } else {
112                    return Err(Error {
113                        cause: SyntaxError::FdOutOfRange.into(),
114                        location: token.word.location,
115                    });
116                }
117            }
118            IoLocation => {
119                let token = self.take_token_raw().await?;
120                // TODO parse the I/O location
121                return Err(Error {
122                    cause: SyntaxError::InvalidIoLocation.into(),
123                    location: token.word.location,
124                });
125            }
126            _ => None,
127        };
128
129        Ok(self
130            .redirection_body()
131            .await?
132            .map(|body| Redir { fd, body }))
133    }
134
135    /// Parses a (possibly empty) sequence of redirections.
136    pub async fn redirections(&mut self) -> Result<Vec<Redir>> {
137        // TODO substitute global aliases
138        let mut redirs = vec![];
139        while let Some(redir) = self.redirection().await? {
140            redirs.push(redir);
141        }
142        Ok(redirs)
143    }
144}
145
146#[allow(clippy::bool_assert_comparison)]
147#[cfg(test)]
148mod tests {
149    use super::super::error::ErrorCause;
150    use super::super::lex::Lexer;
151    use super::super::lex::Operator::Newline;
152    use super::*;
153    use crate::source::Source;
154    use assert_matches::assert_matches;
155    use futures_util::FutureExt;
156
157    #[test]
158    fn parser_redirection_less() {
159        let mut lexer = Lexer::with_code("</dev/null\n");
160        let mut parser = Parser::new(&mut lexer);
161
162        let result = parser.redirection().now_or_never().unwrap();
163        let redir = result.unwrap().unwrap();
164        assert_eq!(redir.fd, None);
165        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
166            assert_eq!(operator, RedirOp::FileIn);
167            assert_eq!(operand.to_string(), "/dev/null")
168        });
169
170        let next = parser.peek_token().now_or_never().unwrap().unwrap();
171        assert_eq!(next.id, Operator(Newline));
172    }
173
174    #[test]
175    fn parser_redirection_less_greater() {
176        let mut lexer = Lexer::with_code("<> /dev/null\n");
177        let mut parser = Parser::new(&mut lexer);
178
179        let result = parser.redirection().now_or_never().unwrap();
180        let redir = result.unwrap().unwrap();
181        assert_eq!(redir.fd, None);
182        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
183            assert_eq!(operator, RedirOp::FileInOut);
184            assert_eq!(operand.to_string(), "/dev/null")
185        });
186    }
187
188    #[test]
189    fn parser_redirection_greater() {
190        let mut lexer = Lexer::with_code(">/dev/null\n");
191        let mut parser = Parser::new(&mut lexer);
192
193        let result = parser.redirection().now_or_never().unwrap();
194        let redir = result.unwrap().unwrap();
195        assert_eq!(redir.fd, None);
196        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
197            assert_eq!(operator, RedirOp::FileOut);
198            assert_eq!(operand.to_string(), "/dev/null")
199        });
200    }
201
202    #[test]
203    fn parser_redirection_greater_greater() {
204        let mut lexer = Lexer::with_code(" >> /dev/null\n");
205        let mut parser = Parser::new(&mut lexer);
206
207        let result = parser.redirection().now_or_never().unwrap();
208        let redir = result.unwrap().unwrap();
209        assert_eq!(redir.fd, None);
210        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
211            assert_eq!(operator, RedirOp::FileAppend);
212            assert_eq!(operand.to_string(), "/dev/null")
213        });
214    }
215
216    #[test]
217    fn parser_redirection_greater_bar() {
218        let mut lexer = Lexer::with_code(">| /dev/null\n");
219        let mut parser = Parser::new(&mut lexer);
220
221        let result = parser.redirection().now_or_never().unwrap();
222        let redir = result.unwrap().unwrap();
223        assert_eq!(redir.fd, None);
224        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
225            assert_eq!(operator, RedirOp::FileClobber);
226            assert_eq!(operand.to_string(), "/dev/null")
227        });
228    }
229
230    #[test]
231    fn parser_redirection_less_and() {
232        let mut lexer = Lexer::with_code("<& -\n");
233        let mut parser = Parser::new(&mut lexer);
234
235        let result = parser.redirection().now_or_never().unwrap();
236        let redir = result.unwrap().unwrap();
237        assert_eq!(redir.fd, None);
238        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
239            assert_eq!(operator, RedirOp::FdIn);
240            assert_eq!(operand.to_string(), "-")
241        });
242    }
243
244    #[test]
245    fn parser_redirection_greater_and() {
246        let mut lexer = Lexer::with_code(">& 3\n");
247        let mut parser = Parser::new(&mut lexer);
248
249        let result = parser.redirection().now_or_never().unwrap();
250        let redir = result.unwrap().unwrap();
251        assert_eq!(redir.fd, None);
252        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
253            assert_eq!(operator, RedirOp::FdOut);
254            assert_eq!(operand.to_string(), "3")
255        });
256    }
257
258    #[test]
259    fn parser_redirection_greater_greater_bar() {
260        let mut lexer = Lexer::with_code(">>| 3\n");
261        let mut parser = Parser::new(&mut lexer);
262
263        let result = parser.redirection().now_or_never().unwrap();
264        let redir = result.unwrap().unwrap();
265        assert_eq!(redir.fd, None);
266        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
267            assert_eq!(operator, RedirOp::Pipe);
268            assert_eq!(operand.to_string(), "3")
269        });
270    }
271
272    #[test]
273    fn parser_redirection_less_less_less() {
274        let mut lexer = Lexer::with_code("<<< foo\n");
275        let mut parser = Parser::new(&mut lexer);
276
277        let result = parser.redirection().now_or_never().unwrap();
278        let redir = result.unwrap().unwrap();
279        assert_eq!(redir.fd, None);
280        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
281            assert_eq!(operator, RedirOp::String);
282            assert_eq!(operand.to_string(), "foo")
283        });
284    }
285
286    #[test]
287    fn parser_redirection_less_less() {
288        let mut lexer = Lexer::with_code("<<end \nend\n");
289        let mut parser = Parser::new(&mut lexer);
290
291        let result = parser.redirection().now_or_never().unwrap();
292        let redir = result.unwrap().unwrap();
293        assert_eq!(redir.fd, None);
294        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
295
296        parser
297            .newline_and_here_doc_contents()
298            .now_or_never()
299            .unwrap()
300            .unwrap();
301        assert_eq!(here_doc.delimiter.to_string(), "end");
302        assert_eq!(here_doc.remove_tabs, false);
303        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
304    }
305
306    #[test]
307    fn parser_redirection_less_less_dash() {
308        let mut lexer = Lexer::with_code("<<-end \nend\n");
309        let mut parser = Parser::new(&mut lexer);
310
311        let result = parser.redirection().now_or_never().unwrap();
312        let redir = result.unwrap().unwrap();
313        assert_eq!(redir.fd, None);
314        let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
315
316        parser
317            .newline_and_here_doc_contents()
318            .now_or_never()
319            .unwrap()
320            .unwrap();
321        assert_eq!(here_doc.delimiter.to_string(), "end");
322        assert_eq!(here_doc.remove_tabs, true);
323        assert_eq!(here_doc.content.get().unwrap().to_string(), "");
324    }
325
326    #[test]
327    fn parser_redirection_with_io_number() {
328        let mut lexer = Lexer::with_code("12< /dev/null\n");
329        let mut parser = Parser::new(&mut lexer);
330
331        let result = parser.redirection().now_or_never().unwrap();
332        let redir = result.unwrap().unwrap();
333        assert_eq!(redir.fd, Some(Fd(12)));
334        assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
335            assert_eq!(operator, RedirOp::FileIn);
336            assert_eq!(operand.to_string(), "/dev/null")
337        });
338
339        let next = parser.peek_token().now_or_never().unwrap().unwrap();
340        assert_eq!(next.id, Operator(Newline));
341    }
342
343    #[test]
344    fn parser_redirection_fd_out_of_range() {
345        let mut lexer = Lexer::with_code("9999999999999999999999999999999999999999< x");
346        let mut parser = Parser::new(&mut lexer);
347
348        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
349        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::FdOutOfRange));
350        assert_eq!(
351            *e.location.code.value.borrow(),
352            "9999999999999999999999999999999999999999< x"
353        );
354        assert_eq!(e.location.code.start_line_number.get(), 1);
355        assert_eq!(*e.location.code.source, Source::Unknown);
356        assert_eq!(e.location.range, 0..40);
357    }
358
359    #[test]
360    fn parser_redirection_io_location() {
361        let mut lexer = Lexer::with_code("{n}< /dev/null\n");
362        let mut parser = Parser::new(&mut lexer);
363
364        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
365        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidIoLocation));
366        assert_eq!(*e.location.code.value.borrow(), "{n}< /dev/null\n");
367        assert_eq!(e.location.code.start_line_number.get(), 1);
368        assert_eq!(*e.location.code.source, Source::Unknown);
369        assert_eq!(e.location.range, 0..3);
370    }
371
372    #[test]
373    fn parser_redirection_not_operator() {
374        let mut lexer = Lexer::with_code("x");
375        let mut parser = Parser::new(&mut lexer);
376
377        let result = parser.redirection().now_or_never().unwrap();
378        assert_eq!(result, Ok(None));
379    }
380
381    #[test]
382    fn parser_redirection_non_word_operand() {
383        let mut lexer = Lexer::with_code(" < >");
384        let mut parser = Parser::new(&mut lexer);
385
386        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
387        assert_eq!(
388            e.cause,
389            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
390        );
391        assert_eq!(*e.location.code.value.borrow(), " < >");
392        assert_eq!(e.location.code.start_line_number.get(), 1);
393        assert_eq!(*e.location.code.source, Source::Unknown);
394        assert_eq!(e.location.range, 3..4);
395    }
396
397    #[test]
398    fn parser_redirection_eof_operand() {
399        let mut lexer = Lexer::with_code("  < ");
400        let mut parser = Parser::new(&mut lexer);
401
402        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
403        assert_eq!(
404            e.cause,
405            ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
406        );
407        assert_eq!(*e.location.code.value.borrow(), "  < ");
408        assert_eq!(e.location.code.start_line_number.get(), 1);
409        assert_eq!(*e.location.code.source, Source::Unknown);
410        assert_eq!(e.location.range, 4..4);
411    }
412
413    #[test]
414    fn parser_redirection_not_heredoc_delimiter() {
415        let mut lexer = Lexer::with_code("<< <<");
416        let mut parser = Parser::new(&mut lexer);
417
418        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
419        assert_eq!(
420            e.cause,
421            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
422        );
423        assert_eq!(*e.location.code.value.borrow(), "<< <<");
424        assert_eq!(e.location.code.start_line_number.get(), 1);
425        assert_eq!(*e.location.code.source, Source::Unknown);
426        assert_eq!(e.location.range, 3..5);
427    }
428
429    #[test]
430    fn parser_redirection_eof_heredoc_delimiter() {
431        let mut lexer = Lexer::with_code("<<");
432        let mut parser = Parser::new(&mut lexer);
433
434        let e = parser.redirection().now_or_never().unwrap().unwrap_err();
435        assert_eq!(
436            e.cause,
437            ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
438        );
439        assert_eq!(*e.location.code.value.borrow(), "<<");
440        assert_eq!(e.location.code.start_line_number.get(), 1);
441        assert_eq!(*e.location.code.source, Source::Unknown);
442        assert_eq!(e.location.range, 2..2);
443    }
444}