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