Skip to main content

nu_parser/
parse_source.rs

1use crate::{
2    lite_parser::LiteCommand,
3    parse_def::has_flag_const,
4    parse_helpers::{garbage, garbage_pipeline},
5    parse_pipelines::{parse_redirection, redirecting_builtin_error},
6    parser::{
7        ArgumentParsingLevel, CallKind, ParsedInternalCall, compile_block, parse,
8        parse_internal_call,
9    },
10};
11
12use log::trace;
13use nu_path::{absolute_with, is_windows_device_path};
14#[cfg(feature = "plugin")]
15use nu_protocol::ast::Call;
16use nu_protocol::{
17    BlockId, DeclId, ParseError, Span, Type, VarId,
18    ast::{Block, Expr, Expression, Pipeline, PipelineElement},
19    engine::StateWorkingSet,
20    eval_const::eval_constant,
21    parser_path::ParserPath,
22};
23use std::{
24    path::{Path, PathBuf},
25    sync::Arc,
26};
27
28pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
29#[cfg(feature = "plugin")]
30pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS";
31
32pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
33    trace!("parsing source");
34    let spans = &lite_command.parts;
35    let name = working_set.get_span_contents(spans[0]);
36
37    if name == b"source" || name == b"source-env" {
38        if let Some(redirection) = lite_command.redirection.as_ref() {
39            let name = if name == b"source" {
40                "source"
41            } else {
42                "source-env"
43            };
44            working_set.error(redirecting_builtin_error(name, redirection));
45            return garbage_pipeline(working_set, spans);
46        }
47
48        let scoped = name == b"source-env";
49
50        if let Some(decl_id) = working_set.find_decl(name) {
51            #[allow(deprecated)]
52            let cwd = working_set.get_cwd();
53
54            let ParsedInternalCall {
55                call,
56                output,
57                call_kind,
58            } = parse_internal_call(
59                working_set,
60                spans[0],
61                &spans[1..],
62                decl_id,
63                ArgumentParsingLevel::Full,
64                None,
65            );
66
67            if call_kind == CallKind::Help {
68                return Pipeline::from_vec(vec![Expression::new(
69                    working_set,
70                    Expr::Call(call),
71                    Span::concat(spans),
72                    output,
73                )]);
74            }
75
76            let first_expr = call.positional_iter().next();
77            if let Some(expr) = first_expr {
78                let val = match eval_constant(working_set, expr) {
79                    Ok(val) => val,
80                    Err(err) => {
81                        working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
82                        return Pipeline::from_vec(vec![Expression::new(
83                            working_set,
84                            Expr::Call(call),
85                            Span::concat(&spans[1..]),
86                            Type::Any,
87                        )]);
88                    }
89                };
90
91                if val.is_nothing() {
92                    let mut call = call;
93                    call.set_parser_info(
94                        "noop".to_string(),
95                        Expression::new_unknown(Expr::Nothing, Span::unknown(), Type::Nothing),
96                    );
97                    return Pipeline::from_vec(vec![Expression::new(
98                        working_set,
99                        Expr::Call(call),
100                        Span::concat(spans),
101                        Type::Any,
102                    )]);
103                }
104
105                let filename = match val.coerce_into_string() {
106                    Ok(s) => s,
107                    Err(err) => {
108                        working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
109                        return Pipeline::from_vec(vec![Expression::new(
110                            working_set,
111                            Expr::Call(call),
112                            Span::concat(&spans[1..]),
113                            Type::Any,
114                        )]);
115                    }
116                };
117
118                if let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(LIB_DIRS_VAR)) {
119                    if let Some(contents) = path.read(working_set) {
120                        if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
121                            working_set.error(e);
122                            return garbage_pipeline(working_set, spans);
123                        }
124
125                        let mut block = parse(
126                            working_set,
127                            Some(&path.path().to_string_lossy()),
128                            &contents,
129                            scoped,
130                        );
131                        if block.ir_block.is_none() {
132                            let block_mut = Arc::make_mut(&mut block);
133                            compile_block(working_set, block_mut);
134                        }
135
136                        working_set.files.pop();
137
138                        let block_id = working_set.add_block(block);
139
140                        let mut call_with_block = call;
141
142                        call_with_block.set_parser_info(
143                            "block_id".to_string(),
144                            Expression::new(
145                                working_set,
146                                Expr::Int(block_id.get() as i64),
147                                spans[1],
148                                Type::Any,
149                            ),
150                        );
151
152                        call_with_block.set_parser_info(
153                            "block_id_name".to_string(),
154                            Expression::new(
155                                working_set,
156                                Expr::Filepath(path.path_buf().display().to_string(), false),
157                                spans[1],
158                                Type::String,
159                            ),
160                        );
161
162                        return Pipeline::from_vec(vec![Expression::new(
163                            working_set,
164                            Expr::Call(call_with_block),
165                            Span::concat(spans),
166                            Type::Any,
167                        )]);
168                    }
169                } else {
170                    working_set.error(ParseError::SourcedFileNotFound(filename, spans[1]));
171                }
172            }
173            return Pipeline::from_vec(vec![Expression::new(
174                working_set,
175                Expr::Call(call),
176                Span::concat(spans),
177                Type::Any,
178            )]);
179        }
180    }
181    working_set.error(ParseError::UnknownState(
182        "internal error: source statement unparsable".into(),
183        Span::concat(spans),
184    ));
185    garbage_pipeline(working_set, spans)
186}
187
188pub fn parse_run(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
189    trace!("parsing run");
190    let expr = parse_run_expr_internal(working_set, &lite_command.parts, lite_command);
191    Pipeline::from_vec(vec![expr])
192}
193
194fn find_keyword_decl_by_name(working_set: &StateWorkingSet, name: &[u8]) -> Option<DeclId> {
195    (0..working_set.num_decls())
196        .map(DeclId::new)
197        .find(|decl_id| {
198            let decl = working_set.get_decl(*decl_id);
199            decl.name().as_bytes() == name && decl.is_keyword()
200        })
201}
202
203fn parse_run_expr_internal(
204    working_set: &mut StateWorkingSet,
205    spans: &[Span],
206    lite_command: &LiteCommand,
207) -> Expression {
208    trace!("parsing run expression");
209    let name = working_set.get_span_contents(spans.first().copied().unwrap_or(Span::unknown()));
210
211    if name == b"run" {
212        if let Some(redirection) = lite_command.redirection.as_ref() {
213            working_set.error(redirecting_builtin_error("run", redirection));
214            return garbage(working_set, Span::concat(spans));
215        }
216
217        if let Some(decl_id) =
218            find_keyword_decl_by_name(working_set, name).or_else(|| working_set.find_decl(name))
219        {
220            #[allow(deprecated)]
221            let cwd = working_set.get_cwd();
222
223            let ParsedInternalCall {
224                call,
225                output,
226                call_kind,
227            } = parse_internal_call(
228                working_set,
229                spans[0],
230                &spans[1..],
231                decl_id,
232                ArgumentParsingLevel::Full,
233                None,
234            );
235
236            if call_kind == CallKind::Help {
237                return Expression::new(working_set, Expr::Call(call), Span::concat(spans), output);
238            }
239
240            let do_full_reparse = match has_flag_const(working_set, &call, "full-reparse") {
241                Ok(value) => value,
242                Err(()) => {
243                    return Expression::new(
244                        working_set,
245                        Expr::Call(call),
246                        Span::concat(spans),
247                        output,
248                    );
249                }
250            };
251
252            let first_expr = call.positional_iter().next();
253            if let Some(expr) = first_expr {
254                let val = match eval_constant(working_set, expr) {
255                    Ok(val) => val,
256                    Err(err) => {
257                        working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
258                        return Expression::new(
259                            working_set,
260                            Expr::Call(call),
261                            Span::concat(&spans[1..]),
262                            Type::Any,
263                        );
264                    }
265                };
266
267                if val.is_nothing() {
268                    let mut call = call;
269                    call.set_parser_info(
270                        "noop".to_string(),
271                        Expression::new_unknown(Expr::Nothing, Span::unknown(), Type::Nothing),
272                    );
273                    return Expression::new(
274                        working_set,
275                        Expr::Call(call),
276                        Span::concat(spans),
277                        Type::Any,
278                    );
279                }
280
281                let filename = match val.coerce_into_string() {
282                    Ok(s) => s,
283                    Err(err) => {
284                        working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
285                        return Expression::new(
286                            working_set,
287                            Expr::Call(call),
288                            Span::concat(&spans[1..]),
289                            Type::Any,
290                        );
291                    }
292                };
293
294                if let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(LIB_DIRS_VAR)) {
295                    if do_full_reparse {
296                        let mut call_with_block = call;
297                        call_with_block.set_parser_info(
298                            "block_id_name".to_string(),
299                            Expression::new(
300                                working_set,
301                                Expr::Filepath(path.path_buf().display().to_string(), false),
302                                spans[1],
303                                Type::String,
304                            ),
305                        );
306                        call_with_block.set_parser_info(
307                            "full_reparse".to_string(),
308                            Expression::new(working_set, Expr::Bool(true), spans[1], Type::Bool),
309                        );
310                        return Expression::new(
311                            working_set,
312                            Expr::Call(call_with_block),
313                            Span::concat(spans),
314                            Type::Any,
315                        );
316                    }
317
318                    if let Some(contents) = path.read(working_set) {
319                        if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
320                            working_set.error(e);
321                            return garbage(working_set, Span::concat(spans));
322                        }
323
324                        let mut block = parse(
325                            working_set,
326                            Some(&path.path().to_string_lossy()),
327                            &contents,
328                            false,
329                        );
330                        if block.ir_block.is_none() {
331                            let block_mut = Arc::make_mut(&mut block);
332                            compile_block(working_set, block_mut);
333                        }
334
335                        working_set.files.pop();
336
337                        let script_main_block_id =
338                            find_main_block_id_in_script(working_set, &block);
339
340                        let block_id = working_set.add_block(block);
341
342                        let mut call_with_block = call;
343
344                        call_with_block.set_parser_info(
345                            "block_id".to_string(),
346                            Expression::new(
347                                working_set,
348                                Expr::Int(block_id.get() as i64),
349                                spans[1],
350                                Type::Any,
351                            ),
352                        );
353
354                        call_with_block.set_parser_info(
355                            "block_id_name".to_string(),
356                            Expression::new(
357                                working_set,
358                                Expr::Filepath(path.path_buf().display().to_string(), false),
359                                spans[1],
360                                Type::String,
361                            ),
362                        );
363                        if let Some(main_block_id) = script_main_block_id {
364                            call_with_block.set_parser_info(
365                                "main_block_id".to_string(),
366                                Expression::new(
367                                    working_set,
368                                    Expr::Int(main_block_id.get() as i64),
369                                    spans[1],
370                                    Type::Any,
371                                ),
372                            );
373                        }
374                        return Expression::new(
375                            working_set,
376                            Expr::Call(call_with_block),
377                            Span::concat(spans),
378                            Type::Any,
379                        );
380                    }
381                } else {
382                    working_set.error(ParseError::SourcedFileNotFound(filename, spans[1]));
383                }
384            }
385            return Expression::new(
386                working_set,
387                Expr::Call(call),
388                Span::concat(spans),
389                Type::Any,
390            );
391        }
392    }
393    working_set.error(ParseError::UnknownState(
394        "internal error: run statement unparsable".into(),
395        Span::concat(spans),
396    ));
397    garbage(working_set, Span::concat(spans))
398}
399
400pub fn find_main_block_id_in_script(
401    working_set: &StateWorkingSet<'_>,
402    script_block: &Block,
403) -> Option<BlockId> {
404    script_block.pipelines.iter().find_map(|pipeline| {
405        if pipeline.elements.len() != 1 {
406            return None;
407        }
408
409        let expr = &pipeline.elements[0].expr;
410        let Expr::Call(call) = &expr.expr else {
411            return None;
412        };
413        let decl_name = working_set.get_decl(call.decl_id).name();
414        if decl_name != "def" && decl_name != "export def" {
415            return None;
416        }
417
418        let mut positional = call.positional_iter();
419        let command_name = positional.next().and_then(Expression::as_string)?;
420        if command_name != "main" {
421            return None;
422        }
423
424        let _ = positional.next();
425        positional.next().and_then(Expression::as_block)
426    })
427}
428
429pub fn parse_run_expr(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
430    let lite_command = LiteCommand {
431        parts: spans.to_vec(),
432        pipe: None,
433        redirection: None,
434        comments: vec![],
435        attribute_idx: vec![],
436    };
437    parse_run_expr_internal(working_set, spans, &lite_command)
438}
439
440pub fn parse_where_expr(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
441    trace!("parsing: where");
442
443    if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"where" {
444        working_set.error(ParseError::UnknownState(
445            "internal error: Wrong call name for 'where' command".into(),
446            Span::concat(spans),
447        ));
448        return garbage(working_set, Span::concat(spans));
449    }
450
451    if spans.len() < 2 {
452        working_set.error(ParseError::MissingPositional(
453            "row condition".into(),
454            Span::concat(spans),
455            "where <row_condition>".into(),
456        ));
457        return garbage(working_set, Span::concat(spans));
458    }
459
460    let call = match working_set.find_decl(b"where") {
461        Some(decl_id) => {
462            let ParsedInternalCall {
463                call,
464                output,
465                call_kind,
466            } = parse_internal_call(
467                working_set,
468                spans[0],
469                &spans[1..],
470                decl_id,
471                ArgumentParsingLevel::Full,
472                None,
473            );
474
475            if call_kind != CallKind::Valid {
476                return Expression::new(working_set, Expr::Call(call), Span::concat(spans), output);
477            }
478
479            call
480        }
481        None => {
482            working_set.error(ParseError::UnknownState(
483                "internal error: 'where' declaration not found".into(),
484                Span::concat(spans),
485            ));
486            return garbage(working_set, Span::concat(spans));
487        }
488    };
489
490    Expression::new(
491        working_set,
492        Expr::Call(call),
493        Span::concat(spans),
494        Type::Any,
495    )
496}
497
498pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
499    let expr = parse_where_expr(working_set, &lite_command.parts);
500    let redirection = lite_command
501        .redirection
502        .as_ref()
503        .map(|r| parse_redirection(working_set, r));
504
505    let element = PipelineElement {
506        pipe: None,
507        expr,
508        redirection,
509    };
510
511    Pipeline {
512        elements: vec![element],
513    }
514}
515
516#[cfg(feature = "plugin")]
517pub fn parse_plugin_use(working_set: &mut StateWorkingSet, call: Box<Call>) -> Pipeline {
518    use nu_protocol::{FromValue, PluginRegistryFile};
519
520    #[allow(deprecated)]
521    let cwd = working_set.get_cwd();
522
523    if let Err(err) = (|| {
524        let name = call
525            .positional_iter()
526            .next()
527            .map(|expr| {
528                eval_constant(working_set, expr)
529                    .and_then(nu_protocol::Spanned::<String>::from_value)
530                    .map_err(|err| err.wrap(working_set, call.head))
531            })
532            .expect("required positional should have been checked")?;
533
534        let plugin_config = call
535            .named_iter()
536            .find(|(arg_name, _, _)| arg_name.item == "plugin-config")
537            .map(|(_, _, expr)| {
538                let expr = expr
539                    .as_ref()
540                    .expect("--plugin-config arg should have been checked already");
541                eval_constant(working_set, expr)
542                    .and_then(nu_protocol::Spanned::<String>::from_value)
543                    .map_err(|err| err.wrap(working_set, call.head))
544            })
545            .transpose()?;
546
547        let filename_query = {
548            let path = nu_path::expand_path_with(&name.item, &cwd, true);
549            path.to_str()
550                .and_then(|path_str| {
551                    find_in_dirs(path_str, working_set, &cwd, Some("NU_PLUGIN_DIRS"))
552                })
553                .map(|parser_path| parser_path.path_buf())
554                .unwrap_or(path)
555        };
556
557        let plugin_config_path = if let Some(custom_path) = &plugin_config {
558            find_in_dirs(&custom_path.item, working_set, &cwd, None).ok_or_else(|| {
559                ParseError::FileNotFound(custom_path.item.clone(), custom_path.span)
560            })?
561        } else {
562            ParserPath::RealPath(
563                working_set
564                    .permanent_state
565                    .plugin_path
566                    .as_ref()
567                    .ok_or_else(|| ParseError::LabeledErrorWithHelp {
568                        error: "Plugin registry file not set".into(),
569                        label: "can't load plugin without registry file".into(),
570                        span: call.head,
571                        help:
572                            "pass --plugin-config to `plugin use` when $nu.plugin-path is not set"
573                                .into(),
574                    })?
575                    .to_owned(),
576            )
577        };
578
579        let file = plugin_config_path.open(working_set).map_err(|err| {
580            ParseError::LabeledError(
581                "Plugin registry file can't be opened".into(),
582                err.to_string(),
583                plugin_config.as_ref().map(|p| p.span).unwrap_or(call.head),
584            )
585        })?;
586
587        let contents = PluginRegistryFile::read_from(file, Some(call.head))
588            .map_err(|err| err.wrap(working_set, call.head))?;
589
590        let plugin_item = contents
591            .plugins
592            .iter()
593            .find(|plugin| plugin.name == name.item || plugin.filename == filename_query)
594            .ok_or_else(|| ParseError::PluginNotFound {
595                name: name.item.clone(),
596                name_span: name.span,
597                plugin_config_span: plugin_config.as_ref().map(|p| p.span),
598            })?;
599
600        nu_plugin_engine::load_plugin_registry_item(working_set, plugin_item, Some(call.head))
601            .map_err(|err| err.wrap(working_set, call.head))?;
602
603        Ok(())
604    })() {
605        working_set.error(err);
606    }
607
608    let call_span = call.span();
609
610    Pipeline::from_vec(vec![Expression::new(
611        working_set,
612        Expr::Call(call),
613        call_span,
614        Type::Nothing,
615    )])
616}
617
618pub fn find_dirs_var(working_set: &StateWorkingSet, var_name: &str) -> Option<VarId> {
619    working_set
620        .find_variable(format!("${var_name}").as_bytes())
621        .filter(|var_id| working_set.get_variable(*var_id).const_val.is_some())
622}
623
624pub fn find_in_dirs(
625    filename: &str,
626    working_set: &StateWorkingSet,
627    cwd: &str,
628    dirs_var_name: Option<&str>,
629) -> Option<ParserPath> {
630    if is_windows_device_path(Path::new(&filename)) {
631        return Some(ParserPath::RealPath(filename.into()));
632    }
633
634    pub fn find_in_dirs_with_id(
635        filename: &str,
636        working_set: &StateWorkingSet,
637        cwd: &str,
638        dirs_var_name: Option<&str>,
639    ) -> Option<ParserPath> {
640        let actual_cwd = working_set
641            .files
642            .current_working_directory()
643            .unwrap_or(Path::new(cwd));
644
645        if let Some(virtual_path) = working_set.find_virtual_path(filename) {
646            return Some(ParserPath::from_virtual_path(
647                working_set,
648                filename,
649                virtual_path,
650            ));
651        } else {
652            let abs_virtual_filename = actual_cwd.join(filename);
653            let abs_virtual_filename = abs_virtual_filename.to_string_lossy();
654
655            if let Some(virtual_path) = working_set.find_virtual_path(&abs_virtual_filename) {
656                return Some(ParserPath::from_virtual_path(
657                    working_set,
658                    &abs_virtual_filename,
659                    virtual_path,
660                ));
661            }
662        }
663
664        if let Ok(p) = absolute_with(filename, actual_cwd)
665            && p.exists()
666        {
667            return Some(ParserPath::RealPath(p));
668        }
669
670        let path = Path::new(filename);
671        if !path.is_relative() {
672            return None;
673        }
674
675        dirs_var_name
676            .as_ref()
677            .and_then(|dirs_var_name| find_dirs_var(working_set, dirs_var_name))
678            .map(|var_id| working_set.get_variable(var_id))?
679            .const_val
680            .as_ref()?
681            .as_list()
682            .ok()?
683            .iter()
684            .map(|lib_dir| -> Option<PathBuf> {
685                let dir = lib_dir.to_path().ok()?;
686                let dir_abs = absolute_with(dir, actual_cwd).ok()?;
687                let path = absolute_with(filename, dir_abs).ok()?;
688                path.exists().then_some(path)
689            })
690            .find(Option::is_some)
691            .flatten()
692            .map(ParserPath::RealPath)
693    }
694
695    pub fn find_in_dirs_old(
696        filename: &str,
697        working_set: &StateWorkingSet,
698        cwd: &str,
699        dirs_env: Option<&str>,
700    ) -> Option<PathBuf> {
701        let actual_cwd = working_set
702            .files
703            .current_working_directory()
704            .unwrap_or(Path::new(cwd));
705
706        if let Ok(p) = absolute_with(filename, actual_cwd)
707            && p.exists()
708        {
709            Some(p)
710        } else {
711            let path = Path::new(filename);
712
713            if path.is_relative() {
714                if let Some(lib_dirs) =
715                    dirs_env.and_then(|dirs_env| working_set.get_env_var(dirs_env))
716                {
717                    if let Ok(dirs) = lib_dirs.as_list() {
718                        for lib_dir in dirs {
719                            if let Ok(dir) = lib_dir.to_path()
720                                && let Ok(dir_abs) = absolute_with(dir, actual_cwd)
721                                && let Ok(path) = absolute_with(filename, dir_abs)
722                                && path.exists()
723                            {
724                                return Some(path);
725                            }
726                        }
727
728                        None
729                    } else {
730                        None
731                    }
732                } else {
733                    None
734                }
735            } else {
736                None
737            }
738        }
739    }
740
741    find_in_dirs_with_id(filename, working_set, cwd, dirs_var_name).or_else(|| {
742        find_in_dirs_old(filename, working_set, cwd, dirs_var_name).map(ParserPath::RealPath)
743    })
744}