1use crate::{Token, TokenContents};
5use itertools::{Either, Itertools};
6use nu_protocol::{ParseError, Span, ast::RedirectionSource, engine::StateWorkingSet};
7use std::mem;
8
9#[derive(Debug, Clone, Copy)]
10pub enum LiteRedirectionTarget {
11 File {
12 connector: Span,
13 file: Span,
14 append: bool,
15 },
16 Pipe {
17 connector: Span,
18 },
19}
20
21impl LiteRedirectionTarget {
22 pub fn connector(&self) -> Span {
23 match self {
24 LiteRedirectionTarget::File { connector, .. }
25 | LiteRedirectionTarget::Pipe { connector } => *connector,
26 }
27 }
28
29 pub fn spans(&self) -> impl Iterator<Item = Span> {
30 match *self {
31 LiteRedirectionTarget::File {
32 connector, file, ..
33 } => Either::Left([connector, file].into_iter()),
34 LiteRedirectionTarget::Pipe { connector } => Either::Right(std::iter::once(connector)),
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
40pub enum LiteRedirection {
41 Single {
42 source: RedirectionSource,
43 target: LiteRedirectionTarget,
44 },
45 Separate {
46 out: LiteRedirectionTarget,
47 err: LiteRedirectionTarget,
48 },
49}
50
51impl LiteRedirection {
52 pub fn spans(&self) -> impl Iterator<Item = Span> {
53 match self {
54 LiteRedirection::Single { target, .. } => Either::Left(target.spans()),
55 LiteRedirection::Separate { out, err } => {
56 Either::Right(out.spans().chain(err.spans()).sorted())
57 }
58 }
59 }
60}
61
62#[derive(Debug, Clone, Default)]
63pub struct LiteCommand {
64 pub pipe: Option<Span>,
65 pub comments: Vec<Span>,
66 pub parts: Vec<Span>,
67 pub redirection: Option<LiteRedirection>,
68 pub attribute_idx: Vec<usize>,
70}
71
72impl LiteCommand {
73 fn push(&mut self, span: Span) {
74 self.parts.push(span);
75 }
76
77 fn check_accepts_redirection(&self, span: Span) -> Option<ParseError> {
78 self.parts
79 .is_empty()
80 .then_some(ParseError::UnexpectedRedirection { span })
81 }
82
83 fn try_add_redirection(
84 &mut self,
85 source: RedirectionSource,
86 target: LiteRedirectionTarget,
87 ) -> Result<(), ParseError> {
88 let redirection = match (self.redirection.take(), source) {
89 (None, _) if self.parts.is_empty() => Err(ParseError::UnexpectedRedirection {
90 span: target.connector(),
91 }),
92 (None, source) => Ok(LiteRedirection::Single { source, target }),
93 (
94 Some(LiteRedirection::Single {
95 source: RedirectionSource::Stdout,
96 target: out,
97 }),
98 RedirectionSource::Stderr,
99 ) => Ok(LiteRedirection::Separate { out, err: target }),
100 (
101 Some(LiteRedirection::Single {
102 source: RedirectionSource::Stderr,
103 target: err,
104 }),
105 RedirectionSource::Stdout,
106 ) => Ok(LiteRedirection::Separate { out: target, err }),
107 (
108 Some(LiteRedirection::Single {
109 source,
110 target: first,
111 }),
112 _,
113 ) => Err(ParseError::MultipleRedirections(
114 source,
115 first.connector(),
116 target.connector(),
117 )),
118 (
119 Some(LiteRedirection::Separate { out, .. }),
120 RedirectionSource::Stdout | RedirectionSource::StdoutAndStderr,
121 ) => Err(ParseError::MultipleRedirections(
122 RedirectionSource::Stdout,
123 out.connector(),
124 target.connector(),
125 )),
126 (Some(LiteRedirection::Separate { err, .. }), RedirectionSource::Stderr) => {
127 Err(ParseError::MultipleRedirections(
128 RedirectionSource::Stderr,
129 err.connector(),
130 target.connector(),
131 ))
132 }
133 }?;
134
135 self.redirection = Some(redirection);
136
137 Ok(())
138 }
139
140 pub fn parts_including_redirection(&self) -> impl Iterator<Item = Span> + '_ {
141 self.parts
142 .iter()
143 .copied()
144 .chain(
145 self.redirection
146 .iter()
147 .flat_map(|redirection| redirection.spans()),
148 )
149 .sorted_unstable_by_key(|a| (a.start, a.end))
150 }
151
152 pub fn command_parts(&self) -> &[Span] {
153 let command_start = self.attribute_idx.last().copied().unwrap_or(0);
154 &self.parts[command_start..]
155 }
156
157 pub fn has_attributes(&self) -> bool {
158 !self.attribute_idx.is_empty()
159 }
160
161 pub fn attribute_commands(&'_ self) -> impl Iterator<Item = LiteCommand> + '_ {
162 std::iter::once(0)
163 .chain(self.attribute_idx.iter().copied())
164 .tuple_windows()
165 .map(|(s, e)| LiteCommand {
166 parts: self.parts[s..e].to_owned(),
167 ..Default::default()
168 })
169 }
170}
171
172#[derive(Debug, Clone, Default)]
173pub struct LitePipeline {
174 pub commands: Vec<LiteCommand>,
175}
176
177impl LitePipeline {
178 fn push(&mut self, element: &mut LiteCommand) {
179 if !element.parts.is_empty() || element.redirection.is_some() {
180 self.commands.push(mem::take(element));
181 }
182 }
183}
184
185#[derive(Debug, Clone, Default)]
186pub struct LiteBlock {
187 pub block: Vec<LitePipeline>,
188}
189
190impl LiteBlock {
191 fn push(&mut self, pipeline: &mut LitePipeline) {
192 if !pipeline.commands.is_empty() {
193 self.block.push(mem::take(pipeline));
194 }
195 }
196}
197
198fn last_non_comment_token(tokens: &[Token], cur_idx: usize) -> Option<TokenContents> {
199 let mut expect = TokenContents::Comment;
200 for token in tokens.iter().take(cur_idx).rev() {
201 match (token.contents, expect) {
203 (TokenContents::Comment, TokenContents::Comment)
204 | (TokenContents::Comment, TokenContents::Eol) => expect = TokenContents::Eol,
205 (TokenContents::Eol, TokenContents::Eol) => expect = TokenContents::Comment,
206 (token, _) => return Some(token),
207 }
208 }
209 None
210}
211
212#[derive(PartialEq, Eq)]
213enum Mode {
214 Assignment,
215 Attribute,
216 Normal,
217}
218
219pub fn lite_parse(
220 tokens: &[Token],
221 working_set: &StateWorkingSet,
222) -> (LiteBlock, Option<ParseError>) {
223 if tokens.is_empty() {
224 return (LiteBlock::default(), None);
225 }
226
227 let mut block = LiteBlock::default();
228 let mut pipeline = LitePipeline::default();
229 let mut command = LiteCommand::default();
230
231 let mut last_token = TokenContents::Eol;
232 let mut file_redirection = None;
233 let mut curr_comment: Option<Vec<Span>> = None;
234 let mut mode = Mode::Normal;
235 let mut error = None;
236
237 for (idx, token) in tokens.iter().enumerate() {
238 match mode {
239 Mode::Attribute => {
240 match &token.contents {
241 TokenContents::Eol | TokenContents::Semicolon => {
243 command.attribute_idx.push(command.parts.len());
244 mode = Mode::Normal;
245 if let TokenContents::Eol | TokenContents::Semicolon = last_token {
246 curr_comment = None;
248 pipeline.push(&mut command);
249 block.push(&mut pipeline);
250 }
251 }
252 TokenContents::Comment => {
253 command.comments.push(token.span);
254 curr_comment = None;
255 }
256 _ => command.push(token.span),
257 }
258 }
259 Mode::Assignment => {
260 match &token.contents {
261 TokenContents::Eol => {
264 let actual_token = last_non_comment_token(tokens, idx);
269 if actual_token != Some(TokenContents::Pipe) {
270 mode = Mode::Normal;
271 pipeline.push(&mut command);
272 block.push(&mut pipeline);
273 }
274
275 if last_token == TokenContents::Eol {
276 curr_comment = None;
278 }
279 }
280 TokenContents::Semicolon => {
281 mode = Mode::Normal;
282 pipeline.push(&mut command);
283 block.push(&mut pipeline);
284 }
285 TokenContents::Comment => {
286 command.comments.push(token.span);
287 curr_comment = None;
288 }
289 _ => command.push(token.span),
290 }
291 }
292 Mode::Normal => {
293 if let Some((source, append, span)) = file_redirection.take() {
294 match &token.contents {
295 TokenContents::PipePipe => {
296 error = error.or(Some(ParseError::ShellOrOr(token.span)));
297 command.push(span);
298 command.push(token.span);
299 }
300 TokenContents::Item => {
301 let target = LiteRedirectionTarget::File {
302 connector: span,
303 file: token.span,
304 append,
305 };
306 if let Err(err) = command.try_add_redirection(source, target) {
307 error = error.or(Some(err));
308 command.push(span);
309 command.push(token.span)
310 }
311 }
312 TokenContents::AssignmentOperator => {
313 error = error
314 .or(Some(ParseError::Expected("redirection target", token.span)));
315 command.push(span);
316 command.push(token.span);
317 }
318 TokenContents::OutGreaterThan
319 | TokenContents::OutGreaterGreaterThan
320 | TokenContents::ErrGreaterThan
321 | TokenContents::ErrGreaterGreaterThan
322 | TokenContents::OutErrGreaterThan
323 | TokenContents::OutErrGreaterGreaterThan => {
324 error = error
325 .or(Some(ParseError::Expected("redirection target", token.span)));
326 command.push(span);
327 command.push(token.span);
328 }
329 TokenContents::Pipe
330 | TokenContents::ErrGreaterPipe
331 | TokenContents::OutErrGreaterPipe => {
332 error = error
333 .or(Some(ParseError::Expected("redirection target", token.span)));
334 command.push(span);
335 pipeline.push(&mut command);
336 command.pipe = Some(token.span);
337 }
338 TokenContents::Eol => {
339 error = error
340 .or(Some(ParseError::Expected("redirection target", token.span)));
341 command.push(span);
342 pipeline.push(&mut command);
343 }
344 TokenContents::Semicolon => {
345 error = error
346 .or(Some(ParseError::Expected("redirection target", token.span)));
347 command.push(span);
348 pipeline.push(&mut command);
349 block.push(&mut pipeline);
350 }
351 TokenContents::Comment => {
352 error =
353 error.or(Some(ParseError::Expected("redirection target", span)));
354 command.push(span);
355 command.comments.push(token.span);
356 curr_comment = None;
357 }
358 }
359 } else {
360 match &token.contents {
361 TokenContents::PipePipe => {
362 error = error.or(Some(ParseError::ShellOrOr(token.span)));
363 command.push(token.span);
364 }
365 TokenContents::Item => {
366 if working_set.get_span_contents(token.span).starts_with(b"@") {
380 if let TokenContents::Eol | TokenContents::Semicolon = last_token {
381 mode = Mode::Attribute;
382 }
383 command.push(token.span);
384 } else {
385 if let Some(curr_comment) = curr_comment.take() {
387 command.comments = curr_comment;
388 }
389 command.push(token.span);
390 }
391 }
392 TokenContents::AssignmentOperator => {
393 mode = Mode::Assignment;
396 if let Some(curr_comment) = curr_comment.take() {
397 command.comments = curr_comment;
398 }
399 command.push(token.span);
400 }
401 TokenContents::OutGreaterThan => {
402 error = error.or(command.check_accepts_redirection(token.span));
403 file_redirection = Some((RedirectionSource::Stdout, false, token.span));
404 }
405 TokenContents::OutGreaterGreaterThan => {
406 error = error.or(command.check_accepts_redirection(token.span));
407 file_redirection = Some((RedirectionSource::Stdout, true, token.span));
408 }
409 TokenContents::ErrGreaterThan => {
410 error = error.or(command.check_accepts_redirection(token.span));
411 file_redirection = Some((RedirectionSource::Stderr, false, token.span));
412 }
413 TokenContents::ErrGreaterGreaterThan => {
414 error = error.or(command.check_accepts_redirection(token.span));
415 file_redirection = Some((RedirectionSource::Stderr, true, token.span));
416 }
417 TokenContents::OutErrGreaterThan => {
418 error = error.or(command.check_accepts_redirection(token.span));
419 file_redirection =
420 Some((RedirectionSource::StdoutAndStderr, false, token.span));
421 }
422 TokenContents::OutErrGreaterGreaterThan => {
423 error = error.or(command.check_accepts_redirection(token.span));
424 file_redirection =
425 Some((RedirectionSource::StdoutAndStderr, true, token.span));
426 }
427 TokenContents::ErrGreaterPipe => {
428 let target = LiteRedirectionTarget::Pipe {
429 connector: token.span,
430 };
431 if let Err(err) =
432 command.try_add_redirection(RedirectionSource::Stderr, target)
433 {
434 error = error.or(Some(err));
435 }
436 pipeline.push(&mut command);
437 command.pipe = Some(token.span);
438 }
439 TokenContents::OutErrGreaterPipe => {
440 let target = LiteRedirectionTarget::Pipe {
441 connector: token.span,
442 };
443 if let Err(err) = command
444 .try_add_redirection(RedirectionSource::StdoutAndStderr, target)
445 {
446 error = error.or(Some(err));
447 }
448 pipeline.push(&mut command);
449 command.pipe = Some(token.span);
450 }
451 TokenContents::Pipe => {
452 pipeline.push(&mut command);
453 command.pipe = Some(token.span);
454 }
455 TokenContents::Eol => {
456 let actual_token = last_non_comment_token(tokens, idx);
461 if actual_token != Some(TokenContents::Pipe) {
462 pipeline.push(&mut command);
463 block.push(&mut pipeline);
464 }
465
466 if last_token == TokenContents::Eol {
467 curr_comment = None;
469 }
470 }
471 TokenContents::Semicolon => {
472 pipeline.push(&mut command);
473 block.push(&mut pipeline);
474 }
475 TokenContents::Comment => {
476 if last_token != TokenContents::Eol {
478 command.comments.push(token.span);
479 curr_comment = None;
480 } else {
481 if let Some(curr_comment) = &mut curr_comment {
483 curr_comment.push(token.span);
484 } else {
485 curr_comment = Some(vec![token.span]);
486 }
487 }
488 }
489 }
490 }
491 }
492 }
493
494 last_token = token.contents;
495 }
496
497 if let Some((_, _, span)) = file_redirection {
498 command.push(span);
499 error = error.or(Some(ParseError::Expected("redirection target", span)));
500 }
501
502 if let Mode::Attribute = mode {
503 command.attribute_idx.push(command.parts.len());
504 }
505
506 pipeline.push(&mut command);
507 block.push(&mut pipeline);
508
509 if last_non_comment_token(tokens, tokens.len()) == Some(TokenContents::Pipe) {
510 (
511 block,
512 Some(ParseError::UnexpectedEof(
513 "pipeline missing end".into(),
514 tokens[tokens.len() - 1].span,
515 )),
516 )
517 } else {
518 (block, error)
519 }
520}