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
33fn 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 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}