1use 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 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 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 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 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 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 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 pub async fn redirections(&mut self) -> Result<Vec<Redir>> {
153 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 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}