1use std::{borrow::Cow, fs::File, sync::Arc};
2
3use nu_path::{dots::expand_ndots_safe, expand_path, expand_path_with, expand_tilde};
4#[cfg(feature = "os")]
5use nu_protocol::process::check_exit_status_future;
6use nu_protocol::{
7 CompareTypes, DeclId, ENV_VARIABLE_ID, Flag, IntoPipelineData, IntoSpanned, LabeledError,
8 ListStream, OutDest, PipelineData, PipelineExecutionData, PositionalArg, Range, Record, RegId,
9 ShellError, Signals, Signature, Span, Spanned, Type, Value, VarId,
10 ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
11 combined_type_string,
12 debugger::DebugContext,
13 engine::{
14 Argument, Closure, EngineState, EnvName, ErrorHandler, Matcher, Redirection, Stack,
15 StateWorkingSet, TryHandler,
16 },
17 ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
18 shell_error::{generic::GenericError, io::IoError},
19};
20use nu_utils::IgnoreCaseExt;
21
22use crate::{
23 ENV_CONVERSIONS, convert_env_vars, eval::is_automatic_env_var, eval_block_with_early_return,
24 named_flags::normalize_engine_arguments,
25};
26
27fn expand_external_glob_arg(val: Value) -> Value {
33 if let Value::Glob {
34 val: ref s,
35 no_expand,
36 internal_span,
37 ..
38 } = val
39 && !no_expand
40 && !nu_glob::is_glob(s)
41 {
42 let expanded = expand_ndots_safe(expand_tilde(s.as_str()));
43 return Value::string(expanded.to_string_lossy().into_owned(), internal_span);
44 }
45 val
46}
47
48pub fn eval_ir_block<D: DebugContext>(
49 engine_state: &EngineState,
50 stack: &mut Stack,
51 block: &Block,
52 input: PipelineData,
53) -> Result<PipelineExecutionData, ShellError> {
54 let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64;
59 if stack.recursion_count > maximum_call_stack_depth {
60 return Err(ShellError::RecursionLimitReached {
61 recursion_limit: maximum_call_stack_depth,
62 span: block.span,
63 });
64 }
65
66 let pushed_scope = if let Some(bindings) = &block.scope_bindings {
68 stack.push_scope_bindings(bindings.clone());
69 true
70 } else {
71 false
72 };
73
74 let saved_regions = std::mem::take(&mut stack.ir_scope_regions);
77 let saved_pc = stack.ir_instruction_index.take();
78
79 let result = eval_ir_block_inner::<D>(engine_state, stack, block, input);
80
81 stack.ir_scope_regions = saved_regions;
82 stack.ir_instruction_index = saved_pc;
83 if pushed_scope {
84 stack.pop_scope_bindings();
85 }
86 result
87}
88
89fn eval_ir_block_inner<D: DebugContext>(
90 engine_state: &EngineState,
91 stack: &mut Stack,
92 block: &Block,
93 input: PipelineData,
94) -> Result<PipelineExecutionData, ShellError> {
95 if let Some(ir_block) = &block.ir_block {
96 D::enter_block(engine_state, block);
97
98 stack.ir_scope_regions = ir_block.scope_regions.clone();
99 stack.ir_instruction_index = None;
100
101 let args_base = stack.arguments.get_base();
102 let error_handler_base = stack.error_handlers.get_base();
103
104 let mut registers = Vec::with_capacity(ir_block.register_count as usize);
107 for _ in 0..ir_block.register_count {
108 registers.push(PipelineExecutionData::empty());
109 }
110
111 let mut files = vec![None; ir_block.file_count as usize];
113
114 let result = eval_ir_block_impl::<D>(
115 &mut EvalContext {
116 engine_state,
117 stack,
118 data: &ir_block.data,
119 block_span: &block.span,
120 args_base,
121 error_handler_base,
122 finally_resumes: vec![],
123 redirect_out: None,
124 redirect_err: None,
125 matches: vec![],
126 registers: &mut registers[..],
127 files: &mut files[..],
128 },
129 ir_block,
130 input,
131 );
132
133 stack.error_handlers.leave_frame(error_handler_base);
134 stack.arguments.leave_frame(args_base);
135 stack.ir_instruction_index = None;
136
137 D::leave_block(engine_state, block);
138
139 result
140 } else {
141 let error = if let Some(span) = block.span {
143 ShellError::Generic(
144 GenericError::new(
145 "Can't evaluate block in IR mode",
146 "block is missing compiled representation",
147 span,
148 )
149 .with_help("the IrBlock is probably missing due to a compilation error"),
150 )
151 } else {
152 ShellError::Generic(
153 GenericError::new_internal(
154 "Can't evaluate block in IR mode",
155 "block is missing compiled representation",
156 )
157 .with_help("the IrBlock is probably missing due to a compilation error"),
158 )
159 };
160 Err(error)
161 }
162}
163
164struct EvalContext<'a> {
166 engine_state: &'a EngineState,
167 stack: &'a mut Stack,
168 data: &'a Arc<[u8]>,
169 block_span: &'a Option<Span>,
171 args_base: usize,
173 error_handler_base: usize,
175 finally_resumes: Vec<Option<Unwind>>,
180 redirect_out: Option<Redirection>,
182 redirect_err: Option<Redirection>,
184 matches: Vec<(VarId, Value)>,
186 registers: &'a mut [PipelineExecutionData],
188 files: &'a mut [Option<Arc<File>>],
190}
191
192impl<'a> EvalContext<'a> {
193 #[inline]
195 fn put_reg(&mut self, reg_id: RegId, new_value: PipelineExecutionData) {
196 self.registers[reg_id.get() as usize] = new_value;
198 }
199
200 #[inline]
202 fn borrow_reg(&self, reg_id: RegId) -> &PipelineData {
203 &self.registers[reg_id.get() as usize]
204 }
205
206 #[inline]
208 fn take_reg(&mut self, reg_id: RegId) -> PipelineExecutionData {
209 std::mem::replace(
211 &mut self.registers[reg_id.get() as usize],
212 PipelineExecutionData::empty(),
213 )
214 }
215
216 fn clone_reg(&mut self, reg_id: RegId, error_span: Span) -> Result<PipelineData, ShellError> {
218 match &self.registers[reg_id.get() as usize].body {
221 PipelineData::Empty => Ok(PipelineData::empty()),
222 PipelineData::Value(val, meta) => Ok(PipelineData::value(val.clone(), meta.clone())),
223 _ => Err(ShellError::IrEvalError {
224 msg: "Must collect to value before using instruction that clones from a register"
225 .into(),
226 span: Some(error_span),
227 }),
228 }
229 }
230
231 fn clone_reg_value(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
233 match self.clone_reg(reg_id, fallback_span)? {
234 PipelineData::Empty => Ok(Value::nothing(fallback_span)),
235 PipelineData::Value(val, _) => Ok(val),
236 _ => unreachable!("clone_reg should never return stream data"),
237 }
238 }
239
240 fn collect_reg(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
244 #[cfg(feature = "os")]
247 let body = {
248 let mut data = self.take_reg(reg_id);
249 data.exit.clear();
250 data.body
251 };
252 #[cfg(not(feature = "os"))]
253 let body = self.take_reg(reg_id).body;
254 let span = body.span().unwrap_or(fallback_span);
255 body.into_value(span)
256 }
257
258 fn get_str(&self, slice: DataSlice, error_span: Span) -> Result<&'a str, ShellError> {
260 std::str::from_utf8(&self.data[slice]).map_err(|_| ShellError::IrEvalError {
261 msg: format!("data slice does not refer to valid UTF-8: {slice:?}"),
262 span: Some(error_span),
263 })
264 }
265}
266
267fn eval_ir_block_impl<D: DebugContext>(
269 ctx: &mut EvalContext<'_>,
270 ir_block: &IrBlock,
271 input: PipelineData,
272) -> Result<PipelineExecutionData, ShellError> {
273 if !ctx.registers.is_empty() {
274 ctx.registers[0] = PipelineExecutionData::from(input);
275 }
276
277 let mut pc = 0;
279 let need_backtrace = ctx.engine_state.get_env_var("NU_BACKTRACE").is_some();
280
281 while pc < ir_block.instructions.len() {
282 let instruction = &ir_block.instructions[pc];
283 let span = &ir_block.spans[pc];
284 let ast = &ir_block.ast[pc];
285
286 ctx.stack.ir_instruction_index = Some(pc);
288
289 D::enter_instruction(ctx.engine_state, ctx.stack, ir_block, pc, ctx.registers);
290
291 let result = eval_instruction::<D>(ctx, instruction, span, ast, need_backtrace);
292
293 D::leave_instruction(
294 ctx.engine_state,
295 ctx.stack,
296 ir_block,
297 pc,
298 ctx.registers,
299 result.as_ref().err(),
300 );
301
302 let unwind = match result {
305 Ok(InstructionResult::Continue) => {
306 pc += 1;
307 continue;
308 }
309 Ok(InstructionResult::Branch(next_pc)) => {
310 pc = next_pc;
311 continue;
312 }
313 Ok(InstructionResult::Return(reg_id)) => return Ok(ctx.take_reg(reg_id)),
314 Ok(InstructionResult::ReturnEarly(reg_id)) => {
315 Unwind::Return(ctx.take_reg(reg_id).with_early_return())
318 }
319 Ok(InstructionResult::UnwindJump { index, handlers }) => {
320 Unwind::Jump { index, handlers }
321 }
322 Ok(InstructionResult::EndFinally) => {
323 match ctx.stack.error_handlers.pop(ctx.error_handler_base) {
324 Some(TryHandler::RunningFinally) => {}
325 handler => {
326 return Err(ShellError::IrEvalError {
327 msg: format!(
328 "end-finally expected a running finally handler, found {handler:?}"
329 ),
330 span: Some(*span),
331 });
332 }
333 }
334 match ctx.finally_resumes.pop() {
335 Some(None) => {
337 pc += 1;
338 continue;
339 }
340 Some(Some(unwind)) => unwind,
342 None => {
343 return Err(ShellError::IrEvalError {
344 msg: "end-finally without a matching begin-finally".into(),
345 span: Some(*span),
346 });
347 }
348 }
349 }
350 Err(err @ (ShellError::Continue { .. } | ShellError::Break { .. })) => {
352 return Err(err);
353 }
354 Err(err @ ShellError::Exit { abort: true, .. }) => return Err(err),
356 Err(err) => Unwind::Error(err.into_spanned(*span)),
357 };
358
359 match unwind_through_handlers(ctx, unwind, *span, need_backtrace) {
360 Unwound::Branch(next_pc) => pc = next_pc,
361 Unwound::Return(result) => return *result,
362 }
363 }
364
365 Err(ShellError::IrEvalError {
367 msg: format!(
368 "Program counter out of range (pc={pc}, len={len})",
369 len = ir_block.instructions.len(),
370 ),
371 span: *ctx.block_span,
372 })
373}
374
375#[derive(Debug)]
377enum Unwind {
378 Error(Spanned<ShellError>),
381 Return(PipelineExecutionData),
383 Jump { index: usize, handlers: usize },
385}
386
387enum Unwound {
389 Branch(usize),
391 Return(Box<Result<PipelineExecutionData, ShellError>>),
393}
394
395fn unwind_through_handlers(
404 ctx: &mut EvalContext<'_>,
405 mut unwind: Unwind,
406 span: Span,
407 need_backtrace: bool,
408) -> Unwound {
409 loop {
410 if let Unwind::Jump { index, handlers: 0 } = unwind {
411 return Unwound::Branch(index);
412 }
413
414 let Some(handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) else {
415 return Unwound::Return(Box::new(match unwind {
416 Unwind::Error(err) if need_backtrace => {
417 Err(ShellError::into_chained(err.item, err.span))
418 }
419 Unwind::Error(err) => Err(err.item),
420 Unwind::Return(data) => Ok(data),
421 Unwind::Jump { .. } => Err(ShellError::IrEvalError {
422 msg: "unwind-jump ran out of handlers before reaching its target".into(),
423 span: Some(span),
424 }),
425 }));
426 };
427
428 if let Unwind::Jump { handlers, .. } = &mut unwind {
429 *handlers -= 1;
430 }
431
432 match handler {
433 TryHandler::Catch(handler) => match unwind {
434 Unwind::Error(err) if !matches!(err.item, ShellError::Exit { .. }) => {
436 reset_signals_if_interrupted(ctx, &err.item);
437 prepare_error_handler(ctx, handler, Some(err));
438 return Unwound::Branch(handler.handler_index);
439 }
440 _ => {}
442 },
443 TryHandler::Finally(handler) => {
444 if let Unwind::Return(data) = unwind {
447 #[cfg(feature = "os")]
448 let collected = collect(data, span, false);
449 #[cfg(not(feature = "os"))]
450 let collected = collect(data, span);
451 unwind = match collected {
452 Ok(body) => {
453 Unwind::Return(PipelineExecutionData::from(body).with_early_return())
454 }
455 Err(err) => Unwind::Error(err.into_spanned(span)),
456 };
457 }
458 let error = match &unwind {
461 Unwind::Error(err) if !matches!(err.item, ShellError::Exit { .. }) => {
462 reset_signals_if_interrupted(ctx, &err.item);
463 Some(err.clone())
464 }
465 _ => None,
466 };
467 prepare_error_handler(ctx, handler, error);
468 ctx.stack.error_handlers.push(TryHandler::RunningFinally);
469 ctx.finally_resumes.push(Some(unwind));
470 return Unwound::Branch(handler.handler_index);
471 }
472 TryHandler::RunningFinally => {
473 ctx.finally_resumes.pop();
476 }
477 }
478 }
479}
480
481fn reset_signals_if_interrupted(ctx: &EvalContext<'_>, err: &ShellError) {
484 #[cfg(unix)]
485 let is_terminated_by_signal = matches!(err, ShellError::TerminatedBySignal { .. });
486 #[cfg(not(unix))]
487 let is_terminated_by_signal = false;
488
489 if matches!(err, ShellError::Interrupted { .. }) || is_terminated_by_signal {
490 ctx.engine_state.signals().reset();
491 }
492}
493
494fn prepare_error_handler(
496 ctx: &mut EvalContext<'_>,
497 error_handler: ErrorHandler,
498 error: Option<Spanned<ShellError>>,
499) {
500 if let Some(reg_id) = error_handler.error_register {
501 if let Some(error) = error {
502 ctx.stack.set_last_error(&error.item);
504 ctx.put_reg(
506 reg_id,
507 PipelineExecutionData::from(
508 error
509 .item
510 .into_full_value(
511 &StateWorkingSet::new(ctx.engine_state),
512 ctx.stack,
513 error.span,
514 )
515 .into_pipeline_data(),
516 ),
517 );
518 } else {
519 ctx.put_reg(reg_id, PipelineExecutionData::empty());
521 }
522 }
523}
524
525#[derive(Debug)]
527enum InstructionResult {
528 Continue,
529 Branch(usize),
530 Return(RegId),
531 ReturnEarly(RegId),
539 UnwindJump {
542 index: usize,
543 handlers: usize,
544 },
545 EndFinally,
547}
548
549fn eval_instruction<D: DebugContext>(
550 ctx: &mut EvalContext<'_>,
551 instruction: &Instruction,
552 span: &Span,
553 ast: &Option<IrAstRef>,
554 need_backtrace: bool,
555) -> Result<InstructionResult, ShellError> {
556 use self::InstructionResult::*;
557
558 instruction.check_interrupt(ctx.engine_state, span)?;
560
561 match instruction {
564 Instruction::Unreachable => Err(ShellError::IrEvalError {
565 msg: "Reached unreachable code".into(),
566 span: Some(*span),
567 }),
568 Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span),
569 Instruction::LoadValue { dst, val } => {
570 ctx.put_reg(
571 *dst,
572 PipelineExecutionData::from(Value::clone(val).into_pipeline_data()),
573 );
574 Ok(Continue)
575 }
576 Instruction::Move { dst, src } => {
577 let val = ctx.take_reg(*src);
578 ctx.put_reg(*dst, val);
579 Ok(Continue)
580 }
581 Instruction::Clone { dst, src } => {
582 let data = ctx.clone_reg(*src, *span)?;
583 ctx.put_reg(*dst, PipelineExecutionData::from(data));
584 Ok(Continue)
585 }
586 Instruction::Collect { src_dst } => {
587 let data = ctx.take_reg(*src_dst);
588 #[cfg(feature = "os")]
589 let value = collect(data, *span, true)?;
590 #[cfg(not(feature = "os"))]
591 let value = collect(data, *span)?;
592 ctx.put_reg(*src_dst, PipelineExecutionData::from(value));
593 Ok(Continue)
594 }
595 Instruction::TryCollect { src_dst } => {
596 let data = ctx.take_reg(*src_dst);
597 #[cfg(feature = "os")]
598 let value = collect(data, *span, false)?;
599 #[cfg(not(feature = "os"))]
600 let value = collect(data, *span)?;
601 ctx.put_reg(*src_dst, PipelineExecutionData::from(value));
602 Ok(Continue)
603 }
604 Instruction::Span { src_dst } => {
605 let mut data = ctx.take_reg(*src_dst);
606 data.body = data.body.with_span(*span);
607 ctx.put_reg(*src_dst, data);
608 Ok(Continue)
609 }
610 Instruction::Drop { src } => {
611 ctx.take_reg(*src);
612 Ok(Continue)
613 }
614 Instruction::Drain { src } => {
615 let data = ctx.take_reg(*src);
616 drain(ctx, data)
617 }
618 Instruction::DrainIfEnd { src } => {
619 let data = ctx.take_reg(*src);
620 let res = drain_if_end(ctx, data)?;
621 ctx.put_reg(*src, PipelineExecutionData::from(res));
622 Ok(Continue)
623 }
624 Instruction::LoadVariable {
625 dst,
626 var_id,
627 preserve_origin,
628 } => {
629 let data = if *var_id == nu_protocol::LAST_VARIABLE_ID {
632 ctx.stack.defer_last_result_truncation_warning();
633 ctx.stack.last_result_pipeline_data(*span)
634 } else if *preserve_origin {
635 let value = ctx
637 .stack
638 .get_var_with_origin(*var_id, *span)
639 .or_else(|err| {
640 if let Some(const_val) = ctx.engine_state.get_constant(*var_id).cloned() {
641 Ok(const_val)
642 } else {
643 Err(err)
644 }
645 })?;
646 value.into_pipeline_data()
647 } else {
648 let value = get_var(ctx, *var_id, *span)?;
649 value.into_pipeline_data()
650 };
651 ctx.put_reg(*dst, PipelineExecutionData::from(data));
652 Ok(Continue)
653 }
654 Instruction::StoreVariable { var_id, src } => {
655 let value = ctx.collect_reg(*src, *span)?;
656 if nu_experimental::ENFORCE_RUNTIME_ANNOTATIONS.get() {
658 let variable = ctx.engine_state.get_var(*var_id);
659 let converted_value = check_assignment_type(value, &variable.ty, *span)?;
660 ctx.stack.add_var(*var_id, converted_value);
661 } else {
662 ctx.stack.add_var(*var_id, value);
663 }
664 Ok(Continue)
665 }
666 Instruction::DropVariable { var_id } => {
667 ctx.stack.remove_var(*var_id);
668 Ok(Continue)
669 }
670 Instruction::LoadEnv { dst, key } => {
671 let key = ctx.get_str(*key, *span)?;
672 if let Some(value) = get_env_var(ctx, key) {
673 let new_value = value.clone().into_pipeline_data();
674 ctx.put_reg(*dst, PipelineExecutionData::from(new_value));
675 Ok(Continue)
676 } else {
677 Err(ShellError::CantFindColumn {
680 col_name: key.into(),
681 span: Some(*span),
682 src_span: *span,
683 })
684 }
685 }
686 Instruction::LoadEnvOpt { dst, key } => {
687 let key = ctx.get_str(*key, *span)?;
688 let value = get_env_var(ctx, key)
689 .cloned()
690 .unwrap_or(Value::nothing(*span));
691 ctx.put_reg(
692 *dst,
693 PipelineExecutionData::from(value.into_pipeline_data()),
694 );
695 Ok(Continue)
696 }
697 Instruction::StoreEnv { key, src } => {
698 let key = ctx.get_str(*key, *span)?;
699 let value = ctx.collect_reg(*src, *span)?;
700
701 let key = get_env_var_name(ctx, key);
702
703 if !is_automatic_env_var(&key) {
704 let is_config = key == "config";
705 let update_conversions = key == ENV_CONVERSIONS;
706
707 ctx.stack.add_env_var(key.into_owned(), value.clone());
708
709 if is_config {
710 ctx.stack.update_config(ctx.engine_state)?;
711 }
712 if update_conversions {
713 convert_env_vars(ctx.stack, ctx.engine_state, &value)?;
714 }
715 Ok(Continue)
716 } else {
717 Err(ShellError::AutomaticEnvVarSetManually {
718 envvar_name: key.into(),
719 span: *span,
720 })
721 }
722 }
723 Instruction::PushPositional { src } => {
724 let val = ctx.collect_reg(*src, *span)?;
727 ctx.stack.arguments.push(Argument::Positional {
728 span: *span,
729 val,
730 ast: ast.clone().map(|ast_ref| ast_ref.0),
731 });
732 Ok(Continue)
733 }
734 Instruction::AppendRest { src } => {
735 let vals = ctx.collect_reg(*src, *span)?;
736 ctx.stack.arguments.push(Argument::Spread {
737 span: *span,
738 vals,
739 ast: ast.clone().map(|ast_ref| ast_ref.0),
740 });
741 Ok(Continue)
742 }
743 Instruction::PushFlag { name } => {
744 let data = ctx.data.clone();
745 ctx.stack.arguments.push(Argument::Flag {
746 data,
747 name: *name,
748 short: DataSlice::empty(),
749 span: *span,
750 });
751 Ok(Continue)
752 }
753 Instruction::PushShortFlag { short } => {
754 let data = ctx.data.clone();
755 ctx.stack.arguments.push(Argument::Flag {
756 data,
757 name: DataSlice::empty(),
758 short: *short,
759 span: *span,
760 });
761 Ok(Continue)
762 }
763 Instruction::PushNamed { name, src } => {
764 let val = ctx.collect_reg(*src, *span)?.with_span(*span);
767 let data = ctx.data.clone();
768 ctx.stack.arguments.push(Argument::Named {
769 data,
770 name: *name,
771 short: DataSlice::empty(),
772 span: *span,
773 val,
774 ast: ast.clone().map(|ast_ref| ast_ref.0),
775 });
776 Ok(Continue)
777 }
778 Instruction::PushShortNamed { short, src } => {
779 let val = ctx.collect_reg(*src, *span)?.with_span(*span);
780 let data = ctx.data.clone();
781 ctx.stack.arguments.push(Argument::Named {
782 data,
783 name: DataSlice::empty(),
784 short: *short,
785 span: *span,
786 val,
787 ast: ast.clone().map(|ast_ref| ast_ref.0),
788 });
789 Ok(Continue)
790 }
791 Instruction::PushParserInfo { name, info } => {
792 let data = ctx.data.clone();
793 ctx.stack.arguments.push(Argument::ParserInfo {
794 data,
795 name: *name,
796 info: info.clone(),
797 });
798 Ok(Continue)
799 }
800 Instruction::RedirectOut { mode } => {
801 ctx.redirect_out = eval_redirection(ctx, mode, *span, RedirectionStream::Out)?;
802 Ok(Continue)
803 }
804 Instruction::RedirectErr { mode } => {
805 ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?;
806 Ok(Continue)
807 }
808 Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) {
809 #[cfg(feature = "os")]
810 PipelineData::ByteStream(stream, _)
811 if matches!(stream.source(), nu_protocol::ByteStreamSource::Child(_)) =>
812 {
813 Ok(Continue)
814 }
815 _ => Err(ShellError::Generic(GenericError::new(
816 "Can't redirect stderr of internal command output",
817 "piping stderr only works on external commands",
818 *span,
819 ))),
820 },
821 Instruction::OpenFile {
822 file_num,
823 path,
824 append,
825 } => {
826 let path = ctx.collect_reg(*path, *span)?;
827 let file = open_file(ctx, &path, *append)?;
828 ctx.files[*file_num as usize] = Some(file);
829 Ok(Continue)
830 }
831 Instruction::WriteFile { file_num, src } => {
832 let src = ctx.take_reg(*src);
833 let file = ctx
834 .files
835 .get(*file_num as usize)
836 .cloned()
837 .flatten()
838 .ok_or_else(|| ShellError::IrEvalError {
839 msg: format!("Tried to write to file #{file_num}, but it is not open"),
840 span: Some(*span),
841 })?;
842 let is_external = if let PipelineData::ByteStream(stream, ..) = &src.body {
843 stream.source().is_external()
844 } else {
845 false
846 };
847 if let Err(err) = src.body.write_to(file.as_ref()) {
848 if is_external {
849 ctx.stack.set_last_error(&err);
850 }
851 Err(err)?
852 } else {
853 Ok(Continue)
854 }
855 }
856 Instruction::CloseFile { file_num } => {
857 if ctx.files[*file_num as usize].take().is_some() {
858 Ok(Continue)
859 } else {
860 Err(ShellError::IrEvalError {
861 msg: format!("Tried to close file #{file_num}, but it is not open"),
862 span: Some(*span),
863 })
864 }
865 }
866 Instruction::Call { decl_id, src_dst } => {
867 let input = ctx.take_reg(*src_dst);
868 let input_data = input.body;
870 let mut result = eval_call::<D>(ctx, *decl_id, *span, input_data)?;
871 if need_backtrace {
872 match &mut result {
873 PipelineData::ByteStream(s, ..) => s.push_caller_span(*span),
874 PipelineData::ListStream(s, ..) => s.push_caller_span(*span),
875 _ => (),
876 };
877 }
878 #[cfg(feature = "os")]
882 {
883 let mut original_exit = input.exit;
884 if ctx.engine_state.get_decl(*decl_id).name() == "complete" {
888 original_exit.clear();
889 }
890 let result_exit_status_future = result
891 .clone_exit_status_future()
892 .map(|f| f.with_span(*span));
893 original_exit.push(result_exit_status_future);
894 ctx.put_reg(
895 *src_dst,
896 PipelineExecutionData {
897 body: result,
898 exit: original_exit,
899 early_return: false,
900 },
901 );
902 }
903 #[cfg(not(feature = "os"))]
904 ctx.put_reg(
905 *src_dst,
906 PipelineExecutionData {
907 body: result,
908 early_return: false,
909 },
910 );
911 Ok(Continue)
912 }
913 Instruction::StringAppend { src_dst, val } => {
914 let string_value = ctx.collect_reg(*src_dst, *span)?;
915 let operand_value = ctx.collect_reg(*val, *span)?;
916 let string_span = string_value.span();
917
918 let mut string = string_value.into_string()?;
919 let operand = if let Value::String { val, .. } = operand_value {
920 val
922 } else {
923 operand_value.to_expanded_string(", ", &ctx.stack.get_config(ctx.engine_state))
924 };
925 string.push_str(&operand);
926
927 let new_string_value = Value::string(string, string_span);
928 ctx.put_reg(
929 *src_dst,
930 PipelineExecutionData::from(new_string_value.into_pipeline_data()),
931 );
932 Ok(Continue)
933 }
934 Instruction::GlobFrom { src_dst, no_expand } => {
935 let string_value = ctx.collect_reg(*src_dst, *span)?;
936 let glob_value = if let Value::Glob { .. } = string_value {
937 string_value
939 } else {
940 let string = string_value.into_string()?;
942 Value::glob(string, *no_expand, *span)
943 };
944 ctx.put_reg(
945 *src_dst,
946 PipelineExecutionData::from(glob_value.into_pipeline_data()),
947 );
948 Ok(Continue)
949 }
950 Instruction::ListPush { src_dst, item } => {
951 let list_value = ctx.collect_reg(*src_dst, *span)?;
952 let item = ctx.collect_reg(*item, *span)?;
953 let list_span = list_value.span();
954 let mut list = list_value.into_list()?;
955 list.push(item);
956 ctx.put_reg(
957 *src_dst,
958 PipelineExecutionData::from(Value::list(list, list_span).into_pipeline_data()),
959 );
960 Ok(Continue)
961 }
962 Instruction::ListSpread { src_dst, items } => {
963 let list_value = ctx.collect_reg(*src_dst, *span)?;
964 let items = ctx.collect_reg(*items, *span)?;
965 let list_span = list_value.span();
966 let items_span = items.span();
967 let items = match items {
968 Value::List { vals, .. } => vals.into_owned(),
969 Value::Nothing { .. } => Vec::new(),
970 _ => return Err(ShellError::CannotSpreadAsList { span: items_span }),
971 };
972 let mut list = list_value.into_list()?;
973 list.extend(items);
974 ctx.put_reg(
975 *src_dst,
976 PipelineExecutionData::from(Value::list(list, list_span).into_pipeline_data()),
977 );
978 Ok(Continue)
979 }
980 Instruction::RecordInsert { src_dst, key, val } => {
981 let record_value = ctx.collect_reg(*src_dst, *span)?;
982 let key = ctx.collect_reg(*key, *span)?;
983 let val = ctx.collect_reg(*val, *span)?;
984 let record_span = record_value.span();
985 let mut record = record_value.into_record()?;
986
987 let key = key.coerce_into_string()?;
988 if let Some(old_value) = record.insert(&key, val) {
989 return Err(ShellError::ColumnDefinedTwice {
990 col_name: key,
991 second_use: *span,
992 first_use: old_value.span(),
993 });
994 }
995
996 ctx.put_reg(
997 *src_dst,
998 PipelineExecutionData::from(
999 Value::record(record, record_span).into_pipeline_data(),
1000 ),
1001 );
1002 Ok(Continue)
1003 }
1004 Instruction::RecordSpread { src_dst, items } => {
1005 let record_value = ctx.collect_reg(*src_dst, *span)?;
1006 let items = ctx.collect_reg(*items, *span)?;
1007 let record_span = record_value.span();
1008 let items_span = items.span();
1009 let mut record = record_value.into_record()?;
1010 let items = match items {
1011 Value::Record { val, .. } => val.into_owned(),
1012 Value::Nothing { .. } => Record::new(),
1013 _ => return Err(ShellError::CannotSpreadAsRecord { span: items_span }),
1014 };
1015 for (key, val) in items {
1017 if let Some(first_value) = record.insert(&key, val) {
1018 return Err(ShellError::ColumnDefinedTwice {
1019 col_name: key,
1020 second_use: *span,
1021 first_use: first_value.span(),
1022 });
1023 }
1024 }
1025 ctx.put_reg(
1026 *src_dst,
1027 PipelineExecutionData::from(
1028 Value::record(record, record_span).into_pipeline_data(),
1029 ),
1030 );
1031 Ok(Continue)
1032 }
1033 Instruction::Not { src_dst } => {
1034 let bool = ctx.collect_reg(*src_dst, *span)?;
1035 let negated = !bool.as_bool()?;
1036 ctx.put_reg(
1037 *src_dst,
1038 PipelineExecutionData::from(Value::bool(negated, bool.span()).into_pipeline_data()),
1039 );
1040 Ok(Continue)
1041 }
1042 Instruction::BinaryOp { lhs_dst, op, rhs } => binary_op(ctx, *lhs_dst, op, *rhs, *span),
1043 Instruction::FollowCellPath { src_dst, path } => {
1044 let data = ctx.take_reg(*src_dst);
1045 let path = ctx.take_reg(*path);
1046 if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
1047 let from_ans = data.body.metadata_ref().is_some_and(|m| {
1052 m.custom
1053 .get(nu_protocol::engine::Stack::ANS_LAST_RESULT_METADATA_KEY)
1054 .is_some()
1055 });
1056 let is_ans_last = from_ans
1057 && path.members.len() == 1
1058 && matches!(
1059 &path.members[0],
1060 nu_protocol::ast::PathMember::String { val, .. } if val == "last"
1061 );
1062 let src_meta = data.body.metadata_ref().cloned();
1063 let value = data.body.follow_cell_path(&path.members, *span)?;
1064 let metadata = if is_ans_last {
1065 src_meta.map(|mut m| {
1067 m.custom
1068 .remove(nu_protocol::engine::Stack::ANS_LAST_RESULT_METADATA_KEY);
1069 m
1070 })
1071 } else {
1072 None
1073 };
1074 ctx.put_reg(
1075 *src_dst,
1076 PipelineExecutionData::from(PipelineData::value(value, metadata)),
1077 );
1078 Ok(Continue)
1079 } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
1080 Err(*error)
1081 } else {
1082 Err(ShellError::TypeMismatch {
1083 err_message: "expected cell path".into(),
1084 span: path.span().unwrap_or(*span),
1085 })
1086 }
1087 }
1088 Instruction::CloneCellPath { dst, src, path } => {
1089 let value = ctx.clone_reg_value(*src, *span)?;
1090 let path = ctx.take_reg(*path);
1091 if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
1092 let value = value.follow_cell_path(&path.members)?;
1093 ctx.put_reg(
1094 *dst,
1095 PipelineExecutionData::from(value.into_owned().into_pipeline_data()),
1096 );
1097 Ok(Continue)
1098 } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
1099 Err(*error)
1100 } else {
1101 Err(ShellError::TypeMismatch {
1102 err_message: "expected cell path".into(),
1103 span: path.span().unwrap_or(*span),
1104 })
1105 }
1106 }
1107 Instruction::UpsertCellPath {
1108 src_dst,
1109 path,
1110 new_value,
1111 } => {
1112 let mut data = ctx.take_reg(*src_dst).body;
1113 let metadata = data.take_metadata();
1114 let mut value = data.into_value(*span)?;
1116 let path = ctx.take_reg(*path);
1117 let new_value = ctx.collect_reg(*new_value, *span)?;
1118 if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
1119 value.upsert_data_at_cell_path(&path.members, new_value)?;
1120 ctx.put_reg(
1121 *src_dst,
1122 PipelineExecutionData::from(value.into_pipeline_data_with_metadata(metadata)),
1123 );
1124 Ok(Continue)
1125 } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
1126 Err(*error)
1127 } else {
1128 Err(ShellError::TypeMismatch {
1129 err_message: "expected cell path".into(),
1130 span: path.span().unwrap_or(*span),
1131 })
1132 }
1133 }
1134 Instruction::UpdateVarCellPath {
1135 var_id,
1136 cell_path,
1137 new_value,
1138 } => {
1139 let new_val = ctx.collect_reg(*new_value, *span)?;
1140 let path = ctx.take_reg(*cell_path);
1141 if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
1142 let new_val = if nu_experimental::ENFORCE_RUNTIME_ANNOTATIONS.get() {
1143 let variable = ctx.engine_state.get_var(*var_id);
1144 let expected_ty = variable.ty.follow_cell_path(&path.members);
1145 if let Some(expected_ty) = expected_ty {
1146 check_assignment_type(new_val, &expected_ty, *span)?
1147 } else {
1148 new_val
1149 }
1150 } else {
1151 new_val
1152 };
1153 ctx.stack
1154 .upsert_var_cell_path(*var_id, &path.members, new_val, *span)?;
1155 Ok(Continue)
1156 } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
1157 Err(*error)
1158 } else {
1159 Err(ShellError::TypeMismatch {
1160 err_message: "expected cell path".into(),
1161 span: path.span().unwrap_or(*span),
1162 })
1163 }
1164 }
1165 Instruction::Jump { index } => Ok(Branch(*index)),
1166 Instruction::BranchIf { cond, index } => {
1167 let data = ctx.take_reg(*cond);
1168 let data_span = data.span();
1169 let val = match data.body {
1170 PipelineData::Value(Value::Bool { val, .. }, _) => val,
1171 PipelineData::Value(Value::Error { error, .. }, _) => {
1172 return Err(*error);
1173 }
1174 _ => {
1175 return Err(ShellError::TypeMismatch {
1176 err_message: "expected bool".into(),
1177 span: data_span.unwrap_or(*span),
1178 });
1179 }
1180 };
1181 if val {
1182 Ok(Branch(*index))
1183 } else {
1184 Ok(Continue)
1185 }
1186 }
1187 Instruction::BranchIfEmpty { src, index } => {
1188 let is_empty = matches!(
1189 ctx.borrow_reg(*src),
1190 PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, _)
1191 );
1192
1193 if is_empty {
1194 Ok(Branch(*index))
1195 } else {
1196 Ok(Continue)
1197 }
1198 }
1199 Instruction::Match {
1200 pattern,
1201 src,
1202 index,
1203 } => {
1204 let value = ctx.clone_reg_value(*src, *span)?;
1205 ctx.matches.clear();
1206 if pattern.match_value(&value, &mut ctx.matches) {
1207 for (var_id, match_value) in ctx.matches.drain(..) {
1209 ctx.stack.add_var(var_id, match_value);
1210 }
1211 Ok(Branch(*index))
1212 } else {
1213 ctx.matches.clear();
1215 Ok(Continue)
1216 }
1217 }
1218 Instruction::CheckMatchGuard { src } => {
1219 if matches!(
1220 ctx.borrow_reg(*src),
1221 PipelineData::Value(Value::Bool { .. }, _)
1222 ) {
1223 Ok(Continue)
1224 } else {
1225 Err(ShellError::MatchGuardNotBool { span: *span })
1226 }
1227 }
1228 Instruction::Iterate {
1229 dst,
1230 stream,
1231 end_index,
1232 } => eval_iterate(ctx, *dst, *stream, *end_index, *span),
1233 Instruction::UnwindJump { index, handlers } => Ok(InstructionResult::UnwindJump {
1234 index: *index,
1235 handlers: *handlers,
1236 }),
1237 Instruction::OnError { index } => {
1238 ctx.stack
1239 .error_handlers
1240 .push(TryHandler::Catch(ErrorHandler {
1241 handler_index: *index,
1242 error_register: None,
1243 }));
1244 Ok(Continue)
1245 }
1246 Instruction::OnErrorInto { index, dst } => {
1247 ctx.stack
1248 .error_handlers
1249 .push(TryHandler::Catch(ErrorHandler {
1250 handler_index: *index,
1251 error_register: Some(*dst),
1252 }));
1253 Ok(Continue)
1254 }
1255 Instruction::FinallyInto { index, dst } => {
1256 ctx.stack
1257 .error_handlers
1258 .push(TryHandler::Finally(ErrorHandler {
1259 handler_index: *index,
1260 error_register: Some(*dst),
1261 }));
1262 Ok(Continue)
1263 }
1264 Instruction::PopErrorHandler => {
1265 match ctx.stack.error_handlers.pop(ctx.error_handler_base) {
1266 Some(TryHandler::Catch(_)) => Ok(Continue),
1267 handler => Err(ShellError::IrEvalError {
1268 msg: format!("pop-error-handler expected a catch handler, found {handler:?}"),
1269 span: Some(*span),
1270 }),
1271 }
1272 }
1273 Instruction::BeginFinally => {
1274 match ctx.stack.error_handlers.pop(ctx.error_handler_base) {
1277 Some(TryHandler::Finally(_)) => {
1278 ctx.stack.error_handlers.push(TryHandler::RunningFinally);
1279 ctx.finally_resumes.push(None);
1280 Ok(Continue)
1281 }
1282 handler => Err(ShellError::IrEvalError {
1283 msg: format!("begin-finally expected a finally handler, found {handler:?}"),
1284 span: Some(*span),
1285 }),
1286 }
1287 }
1288 Instruction::EndFinally => Ok(InstructionResult::EndFinally),
1289 Instruction::ReturnEarly { src } => Ok(InstructionResult::ReturnEarly(*src)),
1290 Instruction::Return { src } => Ok(Return(*src)),
1291 }
1292}
1293
1294fn load_literal(
1296 ctx: &mut EvalContext<'_>,
1297 dst: RegId,
1298 lit: &Literal,
1299 span: Span,
1300) -> Result<InstructionResult, ShellError> {
1301 if matches!(lit, Literal::Empty) {
1307 ctx.put_reg(dst, PipelineExecutionData::empty());
1308 } else {
1309 let value = literal_value(ctx, lit, span)?;
1310 ctx.put_reg(
1311 dst,
1312 PipelineExecutionData::from(PipelineData::value(value, None)),
1313 );
1314 }
1315 Ok(InstructionResult::Continue)
1316}
1317
1318fn literal_value(
1319 ctx: &mut EvalContext<'_>,
1320 lit: &Literal,
1321 span: Span,
1322) -> Result<Value, ShellError> {
1323 Ok(match lit {
1324 Literal::Bool(b) => Value::bool(*b, span),
1325 Literal::Int(i) => Value::int(*i, span),
1326 Literal::Float(f) => Value::float(*f, span),
1327 Literal::Filesize(q) => Value::filesize(*q, span),
1328 Literal::Duration(q) => Value::duration(*q, span),
1329 Literal::Binary(bin) => Value::binary(&ctx.data[*bin], span),
1330 Literal::Block(block_id) | Literal::RowCondition(block_id) | Literal::Closure(block_id) => {
1331 let block = ctx.engine_state.get_block(*block_id);
1332 let captures = block
1333 .captures
1334 .iter()
1335 .map(|(var_id, span)| get_var(ctx, *var_id, *span).map(|val| (*var_id, val)))
1336 .collect::<Result<Vec<_>, ShellError>>()?;
1337 Value::closure(
1338 Closure {
1339 block_id: *block_id,
1340 captures,
1341 },
1342 span,
1343 )
1344 }
1345 Literal::Range {
1346 start,
1347 step,
1348 end,
1349 inclusion,
1350 } => {
1351 let start = ctx.collect_reg(*start, span)?;
1352 let step = ctx.collect_reg(*step, span)?;
1353 let end = ctx.collect_reg(*end, span)?;
1354 let range = Range::new(start, step, end, *inclusion, span)?;
1355 Value::range(range, span)
1356 }
1357 Literal::List { capacity } => Value::list(Vec::with_capacity(*capacity), span),
1358 Literal::Record { capacity } => Value::record(Record::with_capacity(*capacity), span),
1359 Literal::Filepath {
1360 val: path,
1361 no_expand,
1362 } => {
1363 let path = ctx.get_str(*path, span)?;
1364 if *no_expand {
1365 Value::string(path, span)
1366 } else {
1367 let path = expand_path(path, true);
1368 Value::string(path.to_string_lossy(), span)
1369 }
1370 }
1371 Literal::Directory {
1372 val: path,
1373 no_expand,
1374 } => {
1375 let path = ctx.get_str(*path, span)?;
1376 if path == "-" {
1377 Value::string("-", span)
1378 } else if *no_expand {
1379 Value::string(path, span)
1380 } else {
1381 let path = expand_path(path, true);
1382 Value::string(path.to_string_lossy(), span)
1383 }
1384 }
1385 Literal::GlobPattern { val, no_expand } => {
1386 Value::glob(ctx.get_str(*val, span)?, *no_expand, span)
1387 }
1388 Literal::String(s) => Value::string(ctx.get_str(*s, span)?, span),
1389 Literal::RawString(s) => Value::string(ctx.get_str(*s, span)?, span),
1390 Literal::CellPath(path) => Value::cell_path(CellPath::clone(path), span),
1391 Literal::Date(dt) => Value::date(**dt, span),
1392 Literal::Nothing => Value::nothing(span),
1393 Literal::Empty => Value::nothing(span),
1395 })
1396}
1397
1398fn binary_op(
1399 ctx: &mut EvalContext<'_>,
1400 lhs_dst: RegId,
1401 op: &Operator,
1402 rhs: RegId,
1403 span: Span,
1404) -> Result<InstructionResult, ShellError> {
1405 let lhs_val = ctx.collect_reg(lhs_dst, span)?;
1406 let rhs_val = ctx.collect_reg(rhs, span)?;
1407
1408 if let Value::Error { error, .. } = lhs_val {
1410 return Err(*error);
1411 }
1412 if let Value::Error { error, .. } = rhs_val {
1413 return Err(*error);
1414 }
1415
1416 let op_span = span;
1419
1420 let result = match op {
1421 Operator::Comparison(cmp) => match cmp {
1422 Comparison::Equal => lhs_val.eq(op_span, &rhs_val, span)?,
1423 Comparison::NotEqual => lhs_val.ne(op_span, &rhs_val, span)?,
1424 Comparison::LessThan => lhs_val.lt(op_span, &rhs_val, span)?,
1425 Comparison::GreaterThan => lhs_val.gt(op_span, &rhs_val, span)?,
1426 Comparison::LessThanOrEqual => lhs_val.lte(op_span, &rhs_val, span)?,
1427 Comparison::GreaterThanOrEqual => lhs_val.gte(op_span, &rhs_val, span)?,
1428 Comparison::RegexMatch => {
1429 lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, false, span)?
1430 }
1431 Comparison::NotRegexMatch => {
1432 lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, true, span)?
1433 }
1434 Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?,
1435 Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?,
1436 Comparison::Has => lhs_val.has(op_span, &rhs_val, span)?,
1437 Comparison::NotHas => lhs_val.not_has(op_span, &rhs_val, span)?,
1438 Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?,
1439 Comparison::NotStartsWith => lhs_val.not_starts_with(op_span, &rhs_val, span)?,
1440 Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
1441 Comparison::NotEndsWith => lhs_val.not_ends_with(op_span, &rhs_val, span)?,
1442 },
1443 Operator::Math(mat) => match mat {
1444 Math::Add => lhs_val.add(op_span, &rhs_val, span)?,
1445 Math::Subtract => lhs_val.sub(op_span, &rhs_val, span)?,
1446 Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?,
1447 Math::Divide => lhs_val.div(op_span, &rhs_val, span)?,
1448 Math::FloorDivide => lhs_val.floor_div(op_span, &rhs_val, span)?,
1449 Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?,
1450 Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?,
1451 Math::Concatenate => lhs_val.concat(op_span, &rhs_val, span)?,
1452 },
1453 Operator::Boolean(bl) => match bl {
1454 Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?,
1455 Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?,
1456 Boolean::And => lhs_val.and(op_span, &rhs_val, span)?,
1457 },
1458 Operator::Bits(bit) => match bit {
1459 Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?,
1460 Bits::BitXor => lhs_val.bit_xor(op_span, &rhs_val, span)?,
1461 Bits::BitAnd => lhs_val.bit_and(op_span, &rhs_val, span)?,
1462 Bits::ShiftLeft => lhs_val.bit_shl(op_span, &rhs_val, span)?,
1463 Bits::ShiftRight => lhs_val.bit_shr(op_span, &rhs_val, span)?,
1464 },
1465 Operator::Assignment(_asg) => {
1466 return Err(ShellError::IrEvalError {
1467 msg: "can't eval assignment with the `binary-op` instruction".into(),
1468 span: Some(span),
1469 });
1470 }
1471 };
1472
1473 ctx.put_reg(
1474 lhs_dst,
1475 PipelineExecutionData::from(PipelineData::value(result, None)),
1476 );
1477
1478 Ok(InstructionResult::Continue)
1479}
1480
1481fn eval_call<D: DebugContext>(
1483 ctx: &mut EvalContext<'_>,
1484 decl_id: DeclId,
1485 head: Span,
1486 mut input: PipelineData,
1487) -> Result<PipelineData, ShellError> {
1488 let EvalContext {
1489 engine_state,
1490 stack: caller_stack,
1491 args_base,
1492 redirect_out,
1493 redirect_err,
1494 ..
1495 } = ctx;
1496
1497 let args_len = caller_stack.arguments.get_len(*args_base);
1498 let decl = engine_state.get_decl(decl_id);
1499 let stderr_pipe_separate = matches!(
1502 redirect_err.as_ref(),
1503 Some(Redirection::Pipe(OutDest::PipeSeparate))
1504 );
1505
1506 let mut caller_stack = caller_stack.push_redirection(redirect_out.take(), redirect_err.take());
1508
1509 let result = (|| {
1510 if let Some(block_id) = decl.block_id() {
1511 let block = engine_state.get_block(block_id);
1513
1514 check_input_types(&input, &block.signature, head)?;
1516
1517 let args_len = normalize_call_arguments(
1519 &block.signature,
1520 &mut caller_stack,
1521 *args_base,
1522 args_len,
1523 )?;
1524
1525 let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
1527
1528 gather_arguments(
1529 engine_state,
1530 block,
1531 &mut caller_stack,
1532 &mut callee_stack,
1533 *args_base,
1534 args_len,
1535 head,
1536 )?;
1537
1538 let mut callee_stack =
1543 callee_stack.with_invocation_stdout(caller_stack.stdout().clone());
1544
1545 callee_stack.recursion_count += 1;
1548
1549 let result =
1550 eval_block_with_early_return::<D>(engine_state, &mut callee_stack, block, input)
1551 .map(|p| p.body);
1552
1553 if block.redirect_env {
1555 redirect_env(engine_state, &mut caller_stack, &callee_stack);
1556 }
1557
1558 result
1559 } else {
1560 let allow_error_input = matches!(input, PipelineData::Value(Value::Error { .. }, ..))
1564 && engine_state
1565 .find_decl(b"ignore", &[])
1566 .is_some_and(|ignore_decl_id| ignore_decl_id == decl_id);
1567 if !allow_error_input {
1568 check_input_types(&input, &decl.signature(), head)?;
1569 }
1570
1571 let args_len = normalize_call_arguments(
1573 &decl.signature(),
1574 &mut caller_stack,
1575 *args_base,
1576 args_len,
1577 )?;
1578
1579 let span = Span::merge_many(
1581 std::iter::once(head).chain(
1582 caller_stack
1583 .arguments
1584 .get_args(*args_base, args_len)
1585 .iter()
1586 .flat_map(|arg| arg.span()),
1587 ),
1588 );
1589
1590 let call = Call {
1591 decl_id,
1592 head,
1593 span,
1594 args_base: *args_base,
1595 args_len,
1596 };
1597
1598 if let PipelineData::Value(v, ..) = &mut input {
1601 v.inject_signals(engine_state);
1602 }
1603 decl.run(engine_state, &mut caller_stack, &(&call).into(), input)
1605 }
1606 })();
1607
1608 drop(caller_stack);
1609
1610 ctx.stack.arguments.leave_frame(ctx.args_base);
1612 ctx.redirect_out = None;
1613 ctx.redirect_err = None;
1614
1615 match result {
1616 Err(err) if stderr_pipe_separate => Ok(PipelineData::Value(Value::error(err, head), None)),
1617 result => result,
1618 }
1619}
1620
1621fn find_named_var_id(
1622 sig: &Signature,
1623 name: &[u8],
1624 short: &[u8],
1625 span: Span,
1626) -> Result<VarId, ShellError> {
1627 sig.named
1628 .iter()
1629 .find(|n| match (n.long_name(), n.short) {
1630 (Some(long), _) => long.as_bytes() == name,
1631 (None, Some(s)) => s.encode_utf8(&mut [0; 4]).as_bytes() == short,
1633 (None, None) => false,
1634 })
1635 .ok_or_else(|| ShellError::IrEvalError {
1636 msg: format!(
1637 "block does not have an argument named `{}`",
1638 String::from_utf8_lossy(name)
1639 ),
1640 span: Some(span),
1641 })
1642 .and_then(|flag| expect_named_var_id(flag, span))
1643}
1644
1645fn expect_named_var_id(arg: &Flag, span: Span) -> Result<VarId, ShellError> {
1646 arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1647 msg: format!(
1648 "block signature is missing var id for named arg `{}`",
1649 arg.long
1650 ),
1651 span: Some(span),
1652 })
1653}
1654
1655fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result<VarId, ShellError> {
1656 arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1657 msg: format!(
1658 "block signature is missing var id for positional arg `{}`",
1659 arg.name
1660 ),
1661 span: Some(span),
1662 })
1663}
1664
1665fn normalize_call_arguments(
1670 signature: &Signature,
1671 stack: &mut Stack,
1672 args_base: usize,
1673 args_len: usize,
1674) -> Result<usize, ShellError> {
1675 let raw: Vec<Argument> = stack.arguments.drain_args(args_base, args_len).collect();
1676 let expanded = normalize_engine_arguments(signature, raw)?;
1677 let new_len = expanded.len();
1678 for arg in expanded {
1679 stack.arguments.push(arg);
1680 }
1681 Ok(new_len)
1682}
1683
1684fn gather_arguments(
1686 engine_state: &EngineState,
1687 block: &Block,
1688 caller_stack: &mut Stack,
1689 callee_stack: &mut Stack,
1690 args_base: usize,
1691 args_len: usize,
1692 call_head: Span,
1693) -> Result<(), ShellError> {
1694 let mut positional_iter = block
1695 .signature
1696 .required_positional
1697 .iter()
1698 .map(|p| (p, true))
1699 .chain(
1700 block
1701 .signature
1702 .optional_positional
1703 .iter()
1704 .map(|p| (p, false)),
1705 );
1706
1707 let mut rest = vec![];
1709 let mut rest_span: Option<Span> = None;
1710
1711 let expand_glob_args = block.signature.allows_unknown_args;
1718
1719 let mut always_spread = false;
1721 let mut remaining_required = block.signature.required_positional.len();
1722
1723 for arg in caller_stack.arguments.drain_args(args_base, args_len) {
1724 match arg {
1725 Argument::Positional { span, val, .. } => {
1726 let next = (!always_spread).then(|| positional_iter.next()).flatten();
1728 if let Some((positional_arg, required)) = next {
1729 let var_id = expect_positional_var_id(positional_arg, span)?;
1730 if required {
1731 let variable = engine_state.get_var(var_id);
1734 check_type(&val, &variable.ty)?;
1735 remaining_required = remaining_required.saturating_sub(1);
1736 }
1737 callee_stack.add_var(var_id, val);
1738 } else {
1739 rest_span = Some(rest_span.map_or(span, |s| s.append(span)));
1742 let val = if expand_glob_args {
1743 expand_external_glob_arg(val)
1744 } else {
1745 val
1746 };
1747 rest.push(val);
1748 }
1749 }
1750 Argument::Spread {
1751 vals,
1752 span: spread_span,
1753 ..
1754 } => match vals {
1755 Value::List { vals, .. } => {
1756 if remaining_required > 0 && !always_spread {
1759 return Err(crate::named_flags::list_spread_before_required_error(
1760 spread_span,
1761 ));
1762 }
1763 rest.extend(vals);
1764 rest_span = Some(rest_span.map_or(spread_span, |s| s.append(spread_span)));
1765 always_spread = true;
1766 }
1767 Value::Nothing { .. } => {
1768 if remaining_required > 0 && !always_spread {
1771 return Err(crate::named_flags::list_spread_before_required_error(
1772 spread_span,
1773 ));
1774 }
1775 rest_span = Some(rest_span.map_or(spread_span, |s| s.append(spread_span)));
1776 always_spread = true;
1777 }
1778 Value::Record { .. } => {
1780 return Err(ShellError::IrEvalError {
1781 msg: "internal error: unexpanded record spread in gather_arguments".into(),
1782 span: Some(spread_span),
1783 });
1784 }
1785 Value::Error { error, .. } => return Err(*error),
1786 _ => return Err(ShellError::CannotSpreadAsList { span: vals.span() }),
1787 },
1788 Argument::Flag {
1789 data,
1790 name,
1791 short,
1792 span,
1793 } => {
1794 let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1795 callee_stack.add_var(var_id, Value::bool(true, span))
1796 }
1797 Argument::Named {
1798 data,
1799 name,
1800 short,
1801 span,
1802 val,
1803 ..
1804 } => {
1805 let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1808 callee_stack.add_var(var_id, val)
1809 }
1810 Argument::ParserInfo { .. } => (),
1811 }
1812 }
1813
1814 if let Some(rest_arg) = &block.signature.rest_positional {
1816 let rest_span = rest_span.unwrap_or(call_head);
1817 let var_id = expect_positional_var_id(rest_arg, rest_span)?;
1818 callee_stack.add_var(var_id, Value::list(rest, rest_span));
1819 }
1820
1821 for (positional_arg, _) in positional_iter {
1823 let var_id = expect_positional_var_id(positional_arg, call_head)?;
1824 callee_stack.add_var(
1825 var_id,
1826 positional_arg
1827 .default_value
1828 .clone()
1829 .unwrap_or(Value::nothing(call_head)),
1830 );
1831 }
1832
1833 for named_arg in &block.signature.named {
1834 if let Some(var_id) = named_arg.var_id {
1835 if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) {
1839 let val = if named_arg.arg.is_none() {
1840 Value::bool(false, call_head)
1841 } else if let Some(value) = &named_arg.default_value {
1842 value.clone()
1843 } else {
1844 Value::nothing(call_head)
1845 };
1846 callee_stack.add_var(var_id, val);
1847 }
1848 }
1849 }
1850
1851 Ok(())
1852}
1853
1854fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> {
1856 match val {
1857 Value::Error { error, .. } => Err(*error.clone()),
1858 _ if val.is_assignable_to(ty) => Ok(()),
1859 _ => Err(ShellError::CantConvert {
1860 to_type: ty.to_string(),
1861 from_type: val.get_type().to_string(),
1862 span: val.span(),
1863 help: None,
1864 }),
1865 }
1866}
1867
1868fn check_assignment_type(
1870 val: Value,
1871 target_ty: &Type,
1872 assignment_span: Span,
1873) -> Result<Value, ShellError> {
1874 match val {
1875 Value::Error { error, .. } => Err(*error),
1876 _ if val.is_assignable_to(target_ty) => Ok(val), _ => {
1878 let expected = target_ty.to_string();
1879 let actual = val.get_type().to_string();
1880
1881 let mut err = LabeledError::new("Type mismatch.");
1882 err = err.with_code("nu::shell::type_mismatch");
1883
1884 if !(val.span() == Span::unknown() || val.span() == Span::test_data()) {
1888 err = err.with_label(format!("the value is a {actual}"), val.span());
1889 }
1890
1891 err = err.with_label(
1892 format!("expected {expected}, got {actual}"),
1893 assignment_span,
1894 );
1895
1896 Err(ShellError::LabeledError(err.into()))
1897 }
1898 }
1899}
1900
1901fn check_input_types(
1903 input: &PipelineData,
1904 signature: &Signature,
1905 head: Span,
1906) -> Result<(), ShellError> {
1907 let io_types = &signature.input_output_types;
1908
1909 if io_types.is_empty() {
1911 return Ok(());
1912 }
1913
1914 if io_types.iter().all(|(intype, _)| intype == &Type::Nothing) {
1916 return Ok(());
1917 }
1918
1919 match input {
1920 PipelineData::Value(Value::Error { error, .. }, ..) => return Err(*error.clone()),
1922 PipelineData::Value(Value::Custom { .. }, ..) => return Ok(()),
1924 _ => (),
1925 }
1926
1927 if io_types
1929 .iter()
1930 .any(|(command_type, _)| input.is_assignable_to(command_type))
1931 {
1932 return Ok(());
1933 }
1934
1935 let input_types: Vec<Type> = io_types.iter().map(|(input, _)| input.clone()).collect();
1936 let expected_string = combined_type_string(&input_types, "and");
1937
1938 match (input, expected_string) {
1939 (PipelineData::Empty, _) => Err(ShellError::PipelineEmpty { dst_span: head }),
1940 (_, Some(expected_string)) => Err(ShellError::OnlySupportsThisInputType {
1941 exp_input_type: expected_string,
1942 wrong_type: input.get_type().to_string(),
1943 dst_span: head,
1944 src_span: input.span().unwrap_or(head),
1945 }),
1946 (_, None) => Err(ShellError::NushellFailed {
1948 msg: "Command input type strings is empty, despite being non-zero earlier".to_string(),
1949 }),
1950 }
1951}
1952
1953fn get_var(ctx: &mut EvalContext<'_>, var_id: VarId, span: Span) -> Result<Value, ShellError> {
1955 match var_id {
1956 ENV_VARIABLE_ID => {
1958 let env_vars = ctx.stack.get_env_vars(ctx.engine_state);
1959 let env_columns = env_vars.keys();
1960 let env_values = env_vars.values();
1961
1962 let mut pairs = env_columns
1963 .map(|x| x.to_string())
1964 .zip(env_values.cloned())
1965 .collect::<Vec<(String, Value)>>();
1966
1967 pairs.sort_by(|a, b| a.0.cmp(&b.0));
1968
1969 Ok(Value::record(pairs.into_iter().collect(), span))
1970 }
1971 id if id == nu_protocol::LAST_VARIABLE_ID => {
1972 ctx.stack.defer_last_result_truncation_warning();
1974 ctx.stack.get_var(var_id, span)
1975 }
1976 _ => ctx.stack.get_var(var_id, span).or_else(|err| {
1977 if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() {
1979 Ok(const_val.with_span(span))
1980 } else {
1981 Err(err)
1982 }
1983 }),
1984 }
1985}
1986
1987fn get_env_var<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> {
1989 for overlays in ctx
1991 .stack
1992 .env_vars
1993 .iter()
1994 .rev()
1995 .chain(std::iter::once(&ctx.engine_state.env_vars))
1996 {
1997 for overlay_name in ctx.stack.active_overlays.iter().rev() {
1999 let Some(map) = overlays.get(overlay_name) else {
2000 continue;
2002 };
2003 let hidden = ctx.stack.env_hidden.get(overlay_name);
2004 let is_hidden = |key: &EnvName| hidden.is_some_and(|hidden| hidden.contains(key));
2005
2006 if let Some(val) = map
2007 .get(&EnvName::from(key))
2009 .filter(|_| !is_hidden(&EnvName::from(key)))
2011 {
2012 return Some(val);
2013 }
2014 }
2015 }
2016 None
2018}
2019
2020fn get_env_var_name<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> {
2024 ctx.stack
2026 .env_vars
2027 .iter()
2028 .rev()
2029 .chain(std::iter::once(&ctx.engine_state.env_vars))
2030 .flat_map(|overlays| {
2031 ctx.stack
2033 .active_overlays
2034 .iter()
2035 .rev()
2036 .filter_map(|name| overlays.get(name))
2037 })
2038 .find_map(|map| {
2039 if map.contains_key(&EnvName::from(key)) {
2041 map.keys()
2043 .find(|k| k.as_str().eq_ignore_case(key))
2044 .map(|k| Cow::Owned(k.as_str().to_owned()))
2045 } else {
2046 None
2047 }
2048 })
2049 .unwrap_or(Cow::Borrowed(key))
2051}
2052
2053fn collect(
2059 pipe: PipelineExecutionData,
2060 fallback_span: Span,
2061 #[cfg(feature = "os")] ignore_error: bool,
2062) -> Result<PipelineData, ShellError> {
2063 let mut data = pipe.body;
2064 let span = data.span().unwrap_or(fallback_span);
2065 let metadata = data.take_metadata().and_then(|m| m.for_collect());
2066 #[cfg(feature = "os")]
2067 if nu_experimental::PIPE_FAIL.get() && !ignore_error {
2068 check_exit_status_future(pipe.exit)?;
2069 }
2070 #[cfg(feature = "os")]
2076 {
2077 use nu_protocol::ByteStreamSource;
2078 let stdout_uncaptured = matches!(
2079 &data,
2080 PipelineData::ByteStream(stream, ..)
2081 if matches!(stream.source(), ByteStreamSource::Child(child) if child.stdout.is_none())
2082 );
2083 if stdout_uncaptured {
2084 data.drain()?;
2085 return Ok(PipelineData::empty());
2086 }
2087 }
2088 let value = data.into_value(span)?;
2089 Ok(PipelineData::value(value, metadata))
2090}
2091
2092fn drain(
2094 ctx: &mut EvalContext<'_>,
2095 data: PipelineExecutionData,
2096) -> Result<InstructionResult, ShellError> {
2097 use self::InstructionResult::*;
2098
2099 match data.body {
2100 PipelineData::ByteStream(stream, ..) => {
2101 let span = stream.span();
2102 let callback_spans = stream.get_caller_spans().clone();
2103 if let Err(mut err) = stream.drain() {
2104 ctx.stack.set_last_error(&err);
2105 if callback_spans.is_empty() {
2106 return Err(err);
2107 } else {
2108 for s in callback_spans {
2109 err = ShellError::EvalBlockWithInput {
2110 span: s,
2111 sources: vec![err],
2112 }
2113 }
2114 return Err(err);
2115 }
2116 } else {
2117 ctx.stack.set_last_exit_code(0, span);
2118 }
2119 }
2120 PipelineData::ListStream(stream, ..) => {
2121 let callback_spans = stream.get_caller_spans().clone();
2122 if let Err(mut err) = stream.drain() {
2123 if callback_spans.is_empty() {
2124 return Err(err);
2125 } else {
2126 for s in callback_spans {
2127 err = ShellError::EvalBlockWithInput {
2128 span: s,
2129 sources: vec![err],
2130 }
2131 }
2132 return Err(err);
2133 }
2134 }
2135 }
2136 PipelineData::Value(..) | PipelineData::Empty => {}
2137 }
2138
2139 let pipefail = nu_experimental::PIPE_FAIL.get();
2140 if !pipefail {
2141 return Ok(Continue);
2142 }
2143 #[cfg(feature = "os")]
2144 {
2145 check_exit_status_future(data.exit).map(|_| Continue)
2146 }
2147 #[cfg(not(feature = "os"))]
2148 Ok(Continue)
2149}
2150
2151fn drain_if_end(
2153 ctx: &mut EvalContext<'_>,
2154 data: PipelineExecutionData,
2155) -> Result<PipelineData, ShellError> {
2156 let stack = &mut ctx
2157 .stack
2158 .push_redirection(ctx.redirect_out.clone(), ctx.redirect_err.clone());
2159 let result = data.body.drain_to_out_dests(ctx.engine_state, stack)?;
2160
2161 let pipefail = nu_experimental::PIPE_FAIL.get();
2162 if !pipefail {
2163 return Ok(result);
2164 }
2165 #[cfg(feature = "os")]
2166 {
2167 check_exit_status_future(data.exit).map(|_| result)
2168 }
2169 #[cfg(not(feature = "os"))]
2170 Ok(result)
2171}
2172
2173enum RedirectionStream {
2174 Out,
2175 Err,
2176}
2177
2178fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<File>, ShellError> {
2180 let path_expanded =
2181 expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
2182 let mut options = File::options();
2183 if append {
2184 options.append(true);
2185 } else {
2186 options.write(true).truncate(true);
2187 }
2188 let file = options
2189 .create(true)
2190 .open(&path_expanded)
2191 .map_err(|err| IoError::new(err, path.span(), path_expanded))?;
2192 Ok(Arc::new(file))
2193}
2194
2195fn eval_redirection(
2197 ctx: &mut EvalContext<'_>,
2198 mode: &RedirectMode,
2199 span: Span,
2200 which: RedirectionStream,
2201) -> Result<Option<Redirection>, ShellError> {
2202 match mode {
2203 RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))),
2204 RedirectMode::PipeSeparate => Ok(Some(Redirection::Pipe(OutDest::PipeSeparate))),
2205 RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
2206 RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
2207 RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
2208 RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
2209 RedirectMode::File { file_num } => {
2210 let file = ctx
2211 .files
2212 .get(*file_num as usize)
2213 .cloned()
2214 .flatten()
2215 .ok_or_else(|| ShellError::IrEvalError {
2216 msg: format!("Tried to redirect to file #{file_num}, but it is not open"),
2217 span: Some(span),
2218 })?;
2219 Ok(Some(Redirection::File(file)))
2220 }
2221 RedirectMode::Caller => Ok(match which {
2222 RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe),
2223 RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe),
2224 }),
2225 }
2226}
2227
2228fn eval_iterate(
2230 ctx: &mut EvalContext<'_>,
2231 dst: RegId,
2232 stream: RegId,
2233 end_index: usize,
2234 span: Span,
2235) -> Result<InstructionResult, ShellError> {
2236 let mut data = ctx.take_reg(stream);
2237 if let PipelineData::ListStream(list_stream, _) = &mut data.body {
2238 if let Some(val) = list_stream.next_value() {
2240 ctx.put_reg(dst, PipelineExecutionData::from(val.into_pipeline_data()));
2241 ctx.put_reg(stream, data); Ok(InstructionResult::Continue)
2243 } else {
2244 ctx.put_reg(dst, PipelineExecutionData::empty());
2245 Ok(InstructionResult::Branch(end_index))
2246 }
2247 } else {
2248 let metadata = data.body.take_metadata();
2251 let span = data.span().unwrap_or(span);
2252 ctx.put_reg(
2253 stream,
2254 PipelineExecutionData::from(PipelineData::list_stream(
2255 ListStream::new(data.body.into_iter(), span, Signals::EMPTY),
2256 metadata,
2257 )),
2258 );
2259 eval_iterate(ctx, dst, stream, end_index, span)
2260 }
2261}
2262
2263fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
2265 let caller_env_vars = caller_stack.get_env_var_names(engine_state);
2268
2269 for var in caller_env_vars.iter() {
2272 if !callee_stack.has_env_var(engine_state, var) {
2273 caller_stack.hide_env_var(engine_state, var);
2274 }
2275 }
2276
2277 for (var, value) in callee_stack.get_stack_env_vars() {
2279 caller_stack.add_env_var(var, value);
2280 }
2281
2282 caller_stack.config.clone_from(&callee_stack.config);
2284}