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