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