nu_engine/
eval_ir.rs

1use std::{borrow::Cow, fs::File, sync::Arc};
2
3use nu_path::{expand_path_with, AbsolutePathBuf};
4use nu_protocol::{
5    ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
6    debugger::DebugContext,
7    engine::{
8        Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet,
9    },
10    ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
11    shell_error::io::IoError,
12    DataSource, DeclId, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest, PipelineData,
13    PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature, Span,
14    Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
15};
16use nu_utils::IgnoreCaseExt;
17
18use crate::{
19    convert_env_vars, eval::is_automatic_env_var, eval_block_with_early_return, ENV_CONVERSIONS,
20};
21
22/// Evaluate the compiled representation of a [`Block`].
23pub fn eval_ir_block<D: DebugContext>(
24    engine_state: &EngineState,
25    stack: &mut Stack,
26    block: &Block,
27    input: PipelineData,
28) -> Result<PipelineData, ShellError> {
29    // Rust does not check recursion limits outside of const evaluation.
30    // But nu programs run in the same process as the shell.
31    // To prevent a stack overflow in user code from crashing the shell,
32    // we limit the recursion depth of function calls.
33    let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64;
34    if stack.recursion_count > maximum_call_stack_depth {
35        return Err(ShellError::RecursionLimitReached {
36            recursion_limit: maximum_call_stack_depth,
37            span: block.span,
38        });
39    }
40
41    if let Some(ir_block) = &block.ir_block {
42        D::enter_block(engine_state, block);
43
44        let args_base = stack.arguments.get_base();
45        let error_handler_base = stack.error_handlers.get_base();
46
47        // Allocate and initialize registers. I've found that it's not really worth trying to avoid
48        // the heap allocation here by reusing buffers - our allocator is fast enough
49        let mut registers = Vec::with_capacity(ir_block.register_count as usize);
50        for _ in 0..ir_block.register_count {
51            registers.push(PipelineData::Empty);
52        }
53
54        // Initialize file storage.
55        let mut files = vec![None; ir_block.file_count as usize];
56
57        let result = eval_ir_block_impl::<D>(
58            &mut EvalContext {
59                engine_state,
60                stack,
61                data: &ir_block.data,
62                block_span: &block.span,
63                args_base,
64                error_handler_base,
65                redirect_out: None,
66                redirect_err: None,
67                matches: vec![],
68                registers: &mut registers[..],
69                files: &mut files[..],
70            },
71            ir_block,
72            input,
73        );
74
75        stack.error_handlers.leave_frame(error_handler_base);
76        stack.arguments.leave_frame(args_base);
77
78        D::leave_block(engine_state, block);
79
80        result
81    } else {
82        // FIXME blocks having IR should not be optional
83        Err(ShellError::GenericError {
84            error: "Can't evaluate block in IR mode".into(),
85            msg: "block is missing compiled representation".into(),
86            span: block.span,
87            help: Some("the IrBlock is probably missing due to a compilation error".into()),
88            inner: vec![],
89        })
90    }
91}
92
93/// All of the pointers necessary for evaluation
94struct EvalContext<'a> {
95    engine_state: &'a EngineState,
96    stack: &'a mut Stack,
97    data: &'a Arc<[u8]>,
98    /// The span of the block
99    block_span: &'a Option<Span>,
100    /// Base index on the argument stack to reset to after a call
101    args_base: usize,
102    /// Base index on the error handler stack to reset to after a call
103    error_handler_base: usize,
104    /// State set by redirect-out
105    redirect_out: Option<Redirection>,
106    /// State set by redirect-err
107    redirect_err: Option<Redirection>,
108    /// Scratch space to use for `match`
109    matches: Vec<(VarId, Value)>,
110    /// Intermediate pipeline data storage used by instructions, indexed by RegId
111    registers: &'a mut [PipelineData],
112    /// Holds open files used by redirections
113    files: &'a mut [Option<Arc<File>>],
114}
115
116impl<'a> EvalContext<'a> {
117    /// Replace the contents of a register with a new value
118    #[inline]
119    fn put_reg(&mut self, reg_id: RegId, new_value: PipelineData) {
120        // log::trace!("{reg_id} <- {new_value:?}");
121        self.registers[reg_id.get() as usize] = new_value;
122    }
123
124    /// Borrow the contents of a register.
125    #[inline]
126    fn borrow_reg(&self, reg_id: RegId) -> &PipelineData {
127        &self.registers[reg_id.get() as usize]
128    }
129
130    /// Replace the contents of a register with `Empty` and then return the value that it contained
131    #[inline]
132    fn take_reg(&mut self, reg_id: RegId) -> PipelineData {
133        // log::trace!("<- {reg_id}");
134        std::mem::replace(
135            &mut self.registers[reg_id.get() as usize],
136            PipelineData::Empty,
137        )
138    }
139
140    /// Clone data from a register. Must be collected first.
141    fn clone_reg(&mut self, reg_id: RegId, error_span: Span) -> Result<PipelineData, ShellError> {
142        match &self.registers[reg_id.get() as usize] {
143            PipelineData::Empty => Ok(PipelineData::Empty),
144            PipelineData::Value(val, meta) => Ok(PipelineData::Value(val.clone(), meta.clone())),
145            _ => Err(ShellError::IrEvalError {
146                msg: "Must collect to value before using instruction that clones from a register"
147                    .into(),
148                span: Some(error_span),
149            }),
150        }
151    }
152
153    /// Clone a value from a register. Must be collected first.
154    fn clone_reg_value(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
155        match self.clone_reg(reg_id, fallback_span)? {
156            PipelineData::Empty => Ok(Value::nothing(fallback_span)),
157            PipelineData::Value(val, _) => Ok(val),
158            _ => unreachable!("clone_reg should never return stream data"),
159        }
160    }
161
162    /// Take and implicitly collect a register to a value
163    fn collect_reg(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
164        let data = self.take_reg(reg_id);
165        let span = data.span().unwrap_or(fallback_span);
166        data.into_value(span)
167    }
168
169    /// Get a string from data or produce evaluation error if it's invalid UTF-8
170    fn get_str(&self, slice: DataSlice, error_span: Span) -> Result<&'a str, ShellError> {
171        std::str::from_utf8(&self.data[slice]).map_err(|_| ShellError::IrEvalError {
172            msg: format!("data slice does not refer to valid UTF-8: {slice:?}"),
173            span: Some(error_span),
174        })
175    }
176}
177
178/// Eval an IR block on the provided slice of registers.
179fn eval_ir_block_impl<D: DebugContext>(
180    ctx: &mut EvalContext<'_>,
181    ir_block: &IrBlock,
182    input: PipelineData,
183) -> Result<PipelineData, ShellError> {
184    if !ctx.registers.is_empty() {
185        ctx.registers[0] = input;
186    }
187
188    // Program counter, starts at zero.
189    let mut pc = 0;
190    let need_backtrace = ctx.engine_state.get_env_var("NU_BACKTRACE").is_some();
191
192    while pc < ir_block.instructions.len() {
193        let instruction = &ir_block.instructions[pc];
194        let span = &ir_block.spans[pc];
195        let ast = &ir_block.ast[pc];
196
197        D::enter_instruction(ctx.engine_state, ir_block, pc, ctx.registers);
198
199        let result = eval_instruction::<D>(ctx, instruction, span, ast, need_backtrace);
200
201        D::leave_instruction(
202            ctx.engine_state,
203            ir_block,
204            pc,
205            ctx.registers,
206            result.as_ref().err(),
207        );
208
209        match result {
210            Ok(InstructionResult::Continue) => {
211                pc += 1;
212            }
213            Ok(InstructionResult::Branch(next_pc)) => {
214                pc = next_pc;
215            }
216            Ok(InstructionResult::Return(reg_id)) => {
217                return Ok(ctx.take_reg(reg_id));
218            }
219            Err(
220                err @ (ShellError::Return { .. }
221                | ShellError::Continue { .. }
222                | ShellError::Break { .. }),
223            ) => {
224                // These block control related errors should be passed through
225                return Err(err);
226            }
227            Err(err) => {
228                if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
229                    // If an error handler is set, branch there
230                    prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span)));
231                    pc = error_handler.handler_index;
232                } else if need_backtrace {
233                    let err = ShellError::into_chainned(err, *span);
234                    return Err(err);
235                } else {
236                    return Err(err);
237                }
238            }
239        }
240    }
241
242    // Fell out of the loop, without encountering a Return.
243    Err(ShellError::IrEvalError {
244        msg: format!(
245            "Program counter out of range (pc={pc}, len={len})",
246            len = ir_block.instructions.len(),
247        ),
248        span: *ctx.block_span,
249    })
250}
251
252/// Prepare the context for an error handler
253fn prepare_error_handler(
254    ctx: &mut EvalContext<'_>,
255    error_handler: ErrorHandler,
256    error: Option<Spanned<ShellError>>,
257) {
258    if let Some(reg_id) = error_handler.error_register {
259        if let Some(error) = error {
260            // Stack state has to be updated for stuff like LAST_EXIT_CODE
261            ctx.stack.set_last_error(&error.item);
262            // Create the error value and put it in the register
263            ctx.put_reg(
264                reg_id,
265                error
266                    .item
267                    .into_value(&StateWorkingSet::new(ctx.engine_state), error.span)
268                    .into_pipeline_data(),
269            );
270        } else {
271            // Set the register to empty
272            ctx.put_reg(reg_id, PipelineData::Empty);
273        }
274    }
275}
276
277/// The result of performing an instruction. Describes what should happen next
278#[derive(Debug)]
279enum InstructionResult {
280    Continue,
281    Branch(usize),
282    Return(RegId),
283}
284
285/// Perform an instruction
286fn eval_instruction<D: DebugContext>(
287    ctx: &mut EvalContext<'_>,
288    instruction: &Instruction,
289    span: &Span,
290    ast: &Option<IrAstRef>,
291    need_backtrace: bool,
292) -> Result<InstructionResult, ShellError> {
293    use self::InstructionResult::*;
294
295    // See the docs for `Instruction` for more information on what these instructions are supposed
296    // to do.
297    match instruction {
298        Instruction::Unreachable => Err(ShellError::IrEvalError {
299            msg: "Reached unreachable code".into(),
300            span: Some(*span),
301        }),
302        Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span),
303        Instruction::LoadValue { dst, val } => {
304            ctx.put_reg(*dst, Value::clone(val).into_pipeline_data());
305            Ok(Continue)
306        }
307        Instruction::Move { dst, src } => {
308            let val = ctx.take_reg(*src);
309            ctx.put_reg(*dst, val);
310            Ok(Continue)
311        }
312        Instruction::Clone { dst, src } => {
313            let data = ctx.clone_reg(*src, *span)?;
314            ctx.put_reg(*dst, data);
315            Ok(Continue)
316        }
317        Instruction::Collect { src_dst } => {
318            let data = ctx.take_reg(*src_dst);
319            let value = collect(data, *span)?;
320            ctx.put_reg(*src_dst, value);
321            Ok(Continue)
322        }
323        Instruction::Span { src_dst } => {
324            let data = ctx.take_reg(*src_dst);
325            let spanned = data.with_span(*span);
326            ctx.put_reg(*src_dst, spanned);
327            Ok(Continue)
328        }
329        Instruction::Drop { src } => {
330            ctx.take_reg(*src);
331            Ok(Continue)
332        }
333        Instruction::Drain { src } => {
334            let data = ctx.take_reg(*src);
335            drain(ctx, data)
336        }
337        Instruction::DrainIfEnd { src } => {
338            let data = ctx.take_reg(*src);
339            let res = {
340                let stack = &mut ctx
341                    .stack
342                    .push_redirection(ctx.redirect_out.clone(), ctx.redirect_err.clone());
343                data.drain_to_out_dests(ctx.engine_state, stack)?
344            };
345            ctx.put_reg(*src, res);
346            Ok(Continue)
347        }
348        Instruction::LoadVariable { dst, var_id } => {
349            let value = get_var(ctx, *var_id, *span)?;
350            ctx.put_reg(*dst, value.into_pipeline_data());
351            Ok(Continue)
352        }
353        Instruction::StoreVariable { var_id, src } => {
354            let value = ctx.collect_reg(*src, *span)?;
355            ctx.stack.add_var(*var_id, value);
356            Ok(Continue)
357        }
358        Instruction::DropVariable { var_id } => {
359            ctx.stack.remove_var(*var_id);
360            Ok(Continue)
361        }
362        Instruction::LoadEnv { dst, key } => {
363            let key = ctx.get_str(*key, *span)?;
364            if let Some(value) = get_env_var_case_insensitive(ctx, key) {
365                let new_value = value.clone().into_pipeline_data();
366                ctx.put_reg(*dst, new_value);
367                Ok(Continue)
368            } else {
369                // FIXME: using the same span twice, shouldn't this really be
370                // EnvVarNotFoundAtRuntime? There are tests that depend on CantFindColumn though...
371                Err(ShellError::CantFindColumn {
372                    col_name: key.into(),
373                    span: Some(*span),
374                    src_span: *span,
375                })
376            }
377        }
378        Instruction::LoadEnvOpt { dst, key } => {
379            let key = ctx.get_str(*key, *span)?;
380            let value = get_env_var_case_insensitive(ctx, key)
381                .cloned()
382                .unwrap_or(Value::nothing(*span));
383            ctx.put_reg(*dst, value.into_pipeline_data());
384            Ok(Continue)
385        }
386        Instruction::StoreEnv { key, src } => {
387            let key = ctx.get_str(*key, *span)?;
388            let value = ctx.collect_reg(*src, *span)?;
389
390            let key = get_env_var_name_case_insensitive(ctx, key);
391
392            if !is_automatic_env_var(&key) {
393                let is_config = key == "config";
394                let update_conversions = key == ENV_CONVERSIONS;
395
396                ctx.stack.add_env_var(key.into_owned(), value.clone());
397
398                if is_config {
399                    ctx.stack.update_config(ctx.engine_state)?;
400                }
401                if update_conversions {
402                    convert_env_vars(ctx.stack, ctx.engine_state, &value)?;
403                }
404                Ok(Continue)
405            } else {
406                Err(ShellError::AutomaticEnvVarSetManually {
407                    envvar_name: key.into(),
408                    span: *span,
409                })
410            }
411        }
412        Instruction::PushPositional { src } => {
413            let val = ctx.collect_reg(*src, *span)?.with_span(*span);
414            ctx.stack.arguments.push(Argument::Positional {
415                span: *span,
416                val,
417                ast: ast.clone().map(|ast_ref| ast_ref.0),
418            });
419            Ok(Continue)
420        }
421        Instruction::AppendRest { src } => {
422            let vals = ctx.collect_reg(*src, *span)?.with_span(*span);
423            ctx.stack.arguments.push(Argument::Spread {
424                span: *span,
425                vals,
426                ast: ast.clone().map(|ast_ref| ast_ref.0),
427            });
428            Ok(Continue)
429        }
430        Instruction::PushFlag { name } => {
431            let data = ctx.data.clone();
432            ctx.stack.arguments.push(Argument::Flag {
433                data,
434                name: *name,
435                short: DataSlice::empty(),
436                span: *span,
437            });
438            Ok(Continue)
439        }
440        Instruction::PushShortFlag { short } => {
441            let data = ctx.data.clone();
442            ctx.stack.arguments.push(Argument::Flag {
443                data,
444                name: DataSlice::empty(),
445                short: *short,
446                span: *span,
447            });
448            Ok(Continue)
449        }
450        Instruction::PushNamed { name, src } => {
451            let val = ctx.collect_reg(*src, *span)?.with_span(*span);
452            let data = ctx.data.clone();
453            ctx.stack.arguments.push(Argument::Named {
454                data,
455                name: *name,
456                short: DataSlice::empty(),
457                span: *span,
458                val,
459                ast: ast.clone().map(|ast_ref| ast_ref.0),
460            });
461            Ok(Continue)
462        }
463        Instruction::PushShortNamed { short, src } => {
464            let val = ctx.collect_reg(*src, *span)?.with_span(*span);
465            let data = ctx.data.clone();
466            ctx.stack.arguments.push(Argument::Named {
467                data,
468                name: DataSlice::empty(),
469                short: *short,
470                span: *span,
471                val,
472                ast: ast.clone().map(|ast_ref| ast_ref.0),
473            });
474            Ok(Continue)
475        }
476        Instruction::PushParserInfo { name, info } => {
477            let data = ctx.data.clone();
478            ctx.stack.arguments.push(Argument::ParserInfo {
479                data,
480                name: *name,
481                info: info.clone(),
482            });
483            Ok(Continue)
484        }
485        Instruction::RedirectOut { mode } => {
486            ctx.redirect_out = eval_redirection(ctx, mode, *span, RedirectionStream::Out)?;
487            Ok(Continue)
488        }
489        Instruction::RedirectErr { mode } => {
490            ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?;
491            Ok(Continue)
492        }
493        Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) {
494            #[cfg(feature = "os")]
495            PipelineData::ByteStream(stream, _)
496                if matches!(stream.source(), nu_protocol::ByteStreamSource::Child(_)) =>
497            {
498                Ok(Continue)
499            }
500            _ => Err(ShellError::GenericError {
501                error: "Can't redirect stderr of internal command output".into(),
502                msg: "piping stderr only works on external commands".into(),
503                span: Some(*span),
504                help: None,
505                inner: vec![],
506            }),
507        },
508        Instruction::OpenFile {
509            file_num,
510            path,
511            append,
512        } => {
513            let path = ctx.collect_reg(*path, *span)?;
514            let file = open_file(ctx, &path, *append)?;
515            ctx.files[*file_num as usize] = Some(file);
516            Ok(Continue)
517        }
518        Instruction::WriteFile { file_num, src } => {
519            let src = ctx.take_reg(*src);
520            let file = ctx
521                .files
522                .get(*file_num as usize)
523                .cloned()
524                .flatten()
525                .ok_or_else(|| ShellError::IrEvalError {
526                    msg: format!("Tried to write to file #{file_num}, but it is not open"),
527                    span: Some(*span),
528                })?;
529            let is_external = if let PipelineData::ByteStream(stream, ..) = &src {
530                stream.source().is_external()
531            } else {
532                false
533            };
534            if let Err(err) = src.write_to(file.as_ref()) {
535                if is_external {
536                    ctx.stack.set_last_error(&err);
537                }
538                Err(err)?
539            } else {
540                Ok(Continue)
541            }
542        }
543        Instruction::CloseFile { file_num } => {
544            if ctx.files[*file_num as usize].take().is_some() {
545                Ok(Continue)
546            } else {
547                Err(ShellError::IrEvalError {
548                    msg: format!("Tried to close file #{file_num}, but it is not open"),
549                    span: Some(*span),
550                })
551            }
552        }
553        Instruction::Call { decl_id, src_dst } => {
554            let input = ctx.take_reg(*src_dst);
555            let mut result = eval_call::<D>(ctx, *decl_id, *span, input)?;
556            if need_backtrace {
557                match &mut result {
558                    PipelineData::ByteStream(s, ..) => s.push_caller_span(*span),
559                    PipelineData::ListStream(s, ..) => s.push_caller_span(*span),
560                    _ => (),
561                };
562            }
563            ctx.put_reg(*src_dst, result);
564            Ok(Continue)
565        }
566        Instruction::StringAppend { src_dst, val } => {
567            let string_value = ctx.collect_reg(*src_dst, *span)?;
568            let operand_value = ctx.collect_reg(*val, *span)?;
569            let string_span = string_value.span();
570
571            let mut string = string_value.into_string()?;
572            let operand = if let Value::String { val, .. } = operand_value {
573                // Small optimization, so we don't have to copy the string *again*
574                val
575            } else {
576                operand_value.to_expanded_string(", ", ctx.engine_state.get_config())
577            };
578            string.push_str(&operand);
579
580            let new_string_value = Value::string(string, string_span);
581            ctx.put_reg(*src_dst, new_string_value.into_pipeline_data());
582            Ok(Continue)
583        }
584        Instruction::GlobFrom { src_dst, no_expand } => {
585            let string_value = ctx.collect_reg(*src_dst, *span)?;
586            let glob_value = if matches!(string_value, Value::Glob { .. }) {
587                // It already is a glob, so don't touch it.
588                string_value
589            } else {
590                // Treat it as a string, then cast
591                let string = string_value.into_string()?;
592                Value::glob(string, *no_expand, *span)
593            };
594            ctx.put_reg(*src_dst, glob_value.into_pipeline_data());
595            Ok(Continue)
596        }
597        Instruction::ListPush { src_dst, item } => {
598            let list_value = ctx.collect_reg(*src_dst, *span)?;
599            let item = ctx.collect_reg(*item, *span)?;
600            let list_span = list_value.span();
601            let mut list = list_value.into_list()?;
602            list.push(item);
603            ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data());
604            Ok(Continue)
605        }
606        Instruction::ListSpread { src_dst, items } => {
607            let list_value = ctx.collect_reg(*src_dst, *span)?;
608            let items = ctx.collect_reg(*items, *span)?;
609            let list_span = list_value.span();
610            let items_span = items.span();
611            let mut list = list_value.into_list()?;
612            list.extend(
613                items
614                    .into_list()
615                    .map_err(|_| ShellError::CannotSpreadAsList { span: items_span })?,
616            );
617            ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data());
618            Ok(Continue)
619        }
620        Instruction::RecordInsert { src_dst, key, val } => {
621            let record_value = ctx.collect_reg(*src_dst, *span)?;
622            let key = ctx.collect_reg(*key, *span)?;
623            let val = ctx.collect_reg(*val, *span)?;
624            let record_span = record_value.span();
625            let mut record = record_value.into_record()?;
626
627            let key = key.coerce_into_string()?;
628            if let Some(old_value) = record.insert(&key, val) {
629                return Err(ShellError::ColumnDefinedTwice {
630                    col_name: key,
631                    second_use: *span,
632                    first_use: old_value.span(),
633                });
634            }
635
636            ctx.put_reg(
637                *src_dst,
638                Value::record(record, record_span).into_pipeline_data(),
639            );
640            Ok(Continue)
641        }
642        Instruction::RecordSpread { src_dst, items } => {
643            let record_value = ctx.collect_reg(*src_dst, *span)?;
644            let items = ctx.collect_reg(*items, *span)?;
645            let record_span = record_value.span();
646            let items_span = items.span();
647            let mut record = record_value.into_record()?;
648            // Not using .extend() here because it doesn't handle duplicates
649            for (key, val) in items
650                .into_record()
651                .map_err(|_| ShellError::CannotSpreadAsRecord { span: items_span })?
652            {
653                if let Some(first_value) = record.insert(&key, val) {
654                    return Err(ShellError::ColumnDefinedTwice {
655                        col_name: key,
656                        second_use: *span,
657                        first_use: first_value.span(),
658                    });
659                }
660            }
661            ctx.put_reg(
662                *src_dst,
663                Value::record(record, record_span).into_pipeline_data(),
664            );
665            Ok(Continue)
666        }
667        Instruction::Not { src_dst } => {
668            let bool = ctx.collect_reg(*src_dst, *span)?;
669            let negated = !bool.as_bool()?;
670            ctx.put_reg(
671                *src_dst,
672                Value::bool(negated, bool.span()).into_pipeline_data(),
673            );
674            Ok(Continue)
675        }
676        Instruction::BinaryOp { lhs_dst, op, rhs } => binary_op(ctx, *lhs_dst, op, *rhs, *span),
677        Instruction::FollowCellPath { src_dst, path } => {
678            let data = ctx.take_reg(*src_dst);
679            let path = ctx.take_reg(*path);
680            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
681                let value = data.follow_cell_path(&path.members, *span, true)?;
682                ctx.put_reg(*src_dst, value.into_pipeline_data());
683                Ok(Continue)
684            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
685                Err(*error)
686            } else {
687                Err(ShellError::TypeMismatch {
688                    err_message: "expected cell path".into(),
689                    span: path.span().unwrap_or(*span),
690                })
691            }
692        }
693        Instruction::CloneCellPath { dst, src, path } => {
694            let value = ctx.clone_reg_value(*src, *span)?;
695            let path = ctx.take_reg(*path);
696            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
697                // TODO: make follow_cell_path() not have to take ownership, probably using Cow
698                let value = value.follow_cell_path(&path.members, true)?;
699                ctx.put_reg(*dst, value.into_pipeline_data());
700                Ok(Continue)
701            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
702                Err(*error)
703            } else {
704                Err(ShellError::TypeMismatch {
705                    err_message: "expected cell path".into(),
706                    span: path.span().unwrap_or(*span),
707                })
708            }
709        }
710        Instruction::UpsertCellPath {
711            src_dst,
712            path,
713            new_value,
714        } => {
715            let data = ctx.take_reg(*src_dst);
716            let metadata = data.metadata();
717            // Change the span because we're modifying it
718            let mut value = data.into_value(*span)?;
719            let path = ctx.take_reg(*path);
720            let new_value = ctx.collect_reg(*new_value, *span)?;
721            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
722                value.upsert_data_at_cell_path(&path.members, new_value)?;
723                ctx.put_reg(*src_dst, value.into_pipeline_data_with_metadata(metadata));
724                Ok(Continue)
725            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
726                Err(*error)
727            } else {
728                Err(ShellError::TypeMismatch {
729                    err_message: "expected cell path".into(),
730                    span: path.span().unwrap_or(*span),
731                })
732            }
733        }
734        Instruction::Jump { index } => Ok(Branch(*index)),
735        Instruction::BranchIf { cond, index } => {
736            let data = ctx.take_reg(*cond);
737            let data_span = data.span();
738            let val = match data {
739                PipelineData::Value(Value::Bool { val, .. }, _) => val,
740                PipelineData::Value(Value::Error { error, .. }, _) => {
741                    return Err(*error);
742                }
743                _ => {
744                    return Err(ShellError::TypeMismatch {
745                        err_message: "expected bool".into(),
746                        span: data_span.unwrap_or(*span),
747                    });
748                }
749            };
750            if val {
751                Ok(Branch(*index))
752            } else {
753                Ok(Continue)
754            }
755        }
756        Instruction::BranchIfEmpty { src, index } => {
757            let is_empty = matches!(
758                ctx.borrow_reg(*src),
759                PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, _)
760            );
761
762            if is_empty {
763                Ok(Branch(*index))
764            } else {
765                Ok(Continue)
766            }
767        }
768        Instruction::Match {
769            pattern,
770            src,
771            index,
772        } => {
773            let value = ctx.clone_reg_value(*src, *span)?;
774            ctx.matches.clear();
775            if pattern.match_value(&value, &mut ctx.matches) {
776                // Match succeeded: set variables and branch
777                for (var_id, match_value) in ctx.matches.drain(..) {
778                    ctx.stack.add_var(var_id, match_value);
779                }
780                Ok(Branch(*index))
781            } else {
782                // Failed to match, put back original value
783                ctx.matches.clear();
784                Ok(Continue)
785            }
786        }
787        Instruction::CheckMatchGuard { src } => {
788            if matches!(
789                ctx.borrow_reg(*src),
790                PipelineData::Value(Value::Bool { .. }, _)
791            ) {
792                Ok(Continue)
793            } else {
794                Err(ShellError::MatchGuardNotBool { span: *span })
795            }
796        }
797        Instruction::Iterate {
798            dst,
799            stream,
800            end_index,
801        } => eval_iterate(ctx, *dst, *stream, *end_index),
802        Instruction::OnError { index } => {
803            ctx.stack.error_handlers.push(ErrorHandler {
804                handler_index: *index,
805                error_register: None,
806            });
807            Ok(Continue)
808        }
809        Instruction::OnErrorInto { index, dst } => {
810            ctx.stack.error_handlers.push(ErrorHandler {
811                handler_index: *index,
812                error_register: Some(*dst),
813            });
814            Ok(Continue)
815        }
816        Instruction::PopErrorHandler => {
817            ctx.stack.error_handlers.pop(ctx.error_handler_base);
818            Ok(Continue)
819        }
820        Instruction::ReturnEarly { src } => {
821            let val = ctx.collect_reg(*src, *span)?;
822            Err(ShellError::Return {
823                span: *span,
824                value: Box::new(val),
825            })
826        }
827        Instruction::Return { src } => Ok(Return(*src)),
828    }
829}
830
831/// Load a literal value into a register
832fn load_literal(
833    ctx: &mut EvalContext<'_>,
834    dst: RegId,
835    lit: &Literal,
836    span: Span,
837) -> Result<InstructionResult, ShellError> {
838    let value = literal_value(ctx, lit, span)?;
839    ctx.put_reg(dst, PipelineData::Value(value, None));
840    Ok(InstructionResult::Continue)
841}
842
843fn literal_value(
844    ctx: &mut EvalContext<'_>,
845    lit: &Literal,
846    span: Span,
847) -> Result<Value, ShellError> {
848    Ok(match lit {
849        Literal::Bool(b) => Value::bool(*b, span),
850        Literal::Int(i) => Value::int(*i, span),
851        Literal::Float(f) => Value::float(*f, span),
852        Literal::Filesize(q) => Value::filesize(*q, span),
853        Literal::Duration(q) => Value::duration(*q, span),
854        Literal::Binary(bin) => Value::binary(&ctx.data[*bin], span),
855        Literal::Block(block_id) | Literal::RowCondition(block_id) | Literal::Closure(block_id) => {
856            let block = ctx.engine_state.get_block(*block_id);
857            let captures = block
858                .captures
859                .iter()
860                .map(|var_id| get_var(ctx, *var_id, span).map(|val| (*var_id, val)))
861                .collect::<Result<Vec<_>, ShellError>>()?;
862            Value::closure(
863                Closure {
864                    block_id: *block_id,
865                    captures,
866                },
867                span,
868            )
869        }
870        Literal::Range {
871            start,
872            step,
873            end,
874            inclusion,
875        } => {
876            let start = ctx.collect_reg(*start, span)?;
877            let step = ctx.collect_reg(*step, span)?;
878            let end = ctx.collect_reg(*end, span)?;
879            let range = Range::new(start, step, end, *inclusion, span)?;
880            Value::range(range, span)
881        }
882        Literal::List { capacity } => Value::list(Vec::with_capacity(*capacity), span),
883        Literal::Record { capacity } => Value::record(Record::with_capacity(*capacity), span),
884        Literal::Filepath {
885            val: path,
886            no_expand,
887        } => {
888            let path = ctx.get_str(*path, span)?;
889            if *no_expand {
890                Value::string(path, span)
891            } else {
892                let cwd = ctx.engine_state.cwd(Some(ctx.stack))?;
893                let path = expand_path_with(path, cwd, true);
894
895                Value::string(path.to_string_lossy(), span)
896            }
897        }
898        Literal::Directory {
899            val: path,
900            no_expand,
901        } => {
902            let path = ctx.get_str(*path, span)?;
903            if path == "-" {
904                Value::string("-", span)
905            } else if *no_expand {
906                Value::string(path, span)
907            } else {
908                let cwd = ctx
909                    .engine_state
910                    .cwd(Some(ctx.stack))
911                    .map(AbsolutePathBuf::into_std_path_buf)
912                    .unwrap_or_default();
913                let path = expand_path_with(path, cwd, true);
914
915                Value::string(path.to_string_lossy(), span)
916            }
917        }
918        Literal::GlobPattern { val, no_expand } => {
919            Value::glob(ctx.get_str(*val, span)?, *no_expand, span)
920        }
921        Literal::String(s) => Value::string(ctx.get_str(*s, span)?, span),
922        Literal::RawString(s) => Value::string(ctx.get_str(*s, span)?, span),
923        Literal::CellPath(path) => Value::cell_path(CellPath::clone(path), span),
924        Literal::Date(dt) => Value::date(**dt, span),
925        Literal::Nothing => Value::nothing(span),
926    })
927}
928
929fn binary_op(
930    ctx: &mut EvalContext<'_>,
931    lhs_dst: RegId,
932    op: &Operator,
933    rhs: RegId,
934    span: Span,
935) -> Result<InstructionResult, ShellError> {
936    let lhs_val = ctx.collect_reg(lhs_dst, span)?;
937    let rhs_val = ctx.collect_reg(rhs, span)?;
938
939    // Handle binary op errors early
940    if let Value::Error { error, .. } = lhs_val {
941        return Err(*error);
942    }
943    if let Value::Error { error, .. } = rhs_val {
944        return Err(*error);
945    }
946
947    // We only have access to one span here, but the generated code usually adds a `span`
948    // instruction to set the output span to the right span.
949    let op_span = span;
950
951    let result = match op {
952        Operator::Comparison(cmp) => match cmp {
953            Comparison::Equal => lhs_val.eq(op_span, &rhs_val, span)?,
954            Comparison::NotEqual => lhs_val.ne(op_span, &rhs_val, span)?,
955            Comparison::LessThan => lhs_val.lt(op_span, &rhs_val, span)?,
956            Comparison::GreaterThan => lhs_val.gt(op_span, &rhs_val, span)?,
957            Comparison::LessThanOrEqual => lhs_val.lte(op_span, &rhs_val, span)?,
958            Comparison::GreaterThanOrEqual => lhs_val.gte(op_span, &rhs_val, span)?,
959            Comparison::RegexMatch => {
960                lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, false, span)?
961            }
962            Comparison::NotRegexMatch => {
963                lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, true, span)?
964            }
965            Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?,
966            Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?,
967            Comparison::Has => lhs_val.has(op_span, &rhs_val, span)?,
968            Comparison::NotHas => lhs_val.not_has(op_span, &rhs_val, span)?,
969            Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?,
970            Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
971        },
972        Operator::Math(mat) => match mat {
973            Math::Add => lhs_val.add(op_span, &rhs_val, span)?,
974            Math::Subtract => lhs_val.sub(op_span, &rhs_val, span)?,
975            Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?,
976            Math::Divide => lhs_val.div(op_span, &rhs_val, span)?,
977            Math::FloorDivide => lhs_val.floor_div(op_span, &rhs_val, span)?,
978            Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?,
979            Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?,
980            Math::Concatenate => lhs_val.concat(op_span, &rhs_val, span)?,
981        },
982        Operator::Boolean(bl) => match bl {
983            Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?,
984            Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?,
985            Boolean::And => lhs_val.and(op_span, &rhs_val, span)?,
986        },
987        Operator::Bits(bit) => match bit {
988            Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?,
989            Bits::BitXor => lhs_val.bit_xor(op_span, &rhs_val, span)?,
990            Bits::BitAnd => lhs_val.bit_and(op_span, &rhs_val, span)?,
991            Bits::ShiftLeft => lhs_val.bit_shl(op_span, &rhs_val, span)?,
992            Bits::ShiftRight => lhs_val.bit_shr(op_span, &rhs_val, span)?,
993        },
994        Operator::Assignment(_asg) => {
995            return Err(ShellError::IrEvalError {
996                msg: "can't eval assignment with the `binary-op` instruction".into(),
997                span: Some(span),
998            })
999        }
1000    };
1001
1002    ctx.put_reg(lhs_dst, PipelineData::Value(result, None));
1003
1004    Ok(InstructionResult::Continue)
1005}
1006
1007/// Evaluate a call
1008fn eval_call<D: DebugContext>(
1009    ctx: &mut EvalContext<'_>,
1010    decl_id: DeclId,
1011    head: Span,
1012    input: PipelineData,
1013) -> Result<PipelineData, ShellError> {
1014    let EvalContext {
1015        engine_state,
1016        stack: caller_stack,
1017        args_base,
1018        redirect_out,
1019        redirect_err,
1020        ..
1021    } = ctx;
1022
1023    let args_len = caller_stack.arguments.get_len(*args_base);
1024    let decl = engine_state.get_decl(decl_id);
1025
1026    // Set up redirect modes
1027    let mut caller_stack = caller_stack.push_redirection(redirect_out.take(), redirect_err.take());
1028
1029    let result;
1030
1031    if let Some(block_id) = decl.block_id() {
1032        // If the decl is a custom command
1033        let block = engine_state.get_block(block_id);
1034
1035        // check types after acquiring block to avoid unnecessarily cloning Signature
1036        check_input_types(&input, &block.signature, head)?;
1037
1038        // Set up a callee stack with the captures and move arguments from the stack into variables
1039        let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
1040
1041        gather_arguments(
1042            engine_state,
1043            block,
1044            &mut caller_stack,
1045            &mut callee_stack,
1046            *args_base,
1047            args_len,
1048            head,
1049        )?;
1050
1051        // Add one to the recursion count, so we don't recurse too deep. Stack overflows are not
1052        // recoverable in Rust.
1053        callee_stack.recursion_count += 1;
1054
1055        result = eval_block_with_early_return::<D>(engine_state, &mut callee_stack, block, input);
1056
1057        // Move environment variables back into the caller stack scope if requested to do so
1058        if block.redirect_env {
1059            redirect_env(engine_state, &mut caller_stack, &callee_stack);
1060        }
1061    } else {
1062        check_input_types(&input, &decl.signature(), head)?;
1063
1064        // FIXME: precalculate this and save it somewhere
1065        let span = Span::merge_many(
1066            std::iter::once(head).chain(
1067                caller_stack
1068                    .arguments
1069                    .get_args(*args_base, args_len)
1070                    .iter()
1071                    .flat_map(|arg| arg.span()),
1072            ),
1073        );
1074
1075        let call = Call {
1076            decl_id,
1077            head,
1078            span,
1079            args_base: *args_base,
1080            args_len,
1081        };
1082
1083        // Run the call
1084        result = decl.run(engine_state, &mut caller_stack, &(&call).into(), input);
1085    };
1086
1087    drop(caller_stack);
1088
1089    // Important that this runs, to reset state post-call:
1090    ctx.stack.arguments.leave_frame(ctx.args_base);
1091    ctx.redirect_out = None;
1092    ctx.redirect_err = None;
1093
1094    result
1095}
1096
1097fn find_named_var_id(
1098    sig: &Signature,
1099    name: &[u8],
1100    short: &[u8],
1101    span: Span,
1102) -> Result<VarId, ShellError> {
1103    sig.named
1104        .iter()
1105        .find(|n| {
1106            if !n.long.is_empty() {
1107                n.long.as_bytes() == name
1108            } else {
1109                // It's possible to only have a short name and no long name
1110                n.short
1111                    .is_some_and(|s| s.encode_utf8(&mut [0; 4]).as_bytes() == short)
1112            }
1113        })
1114        .ok_or_else(|| ShellError::IrEvalError {
1115            msg: format!(
1116                "block does not have an argument named `{}`",
1117                String::from_utf8_lossy(name)
1118            ),
1119            span: Some(span),
1120        })
1121        .and_then(|flag| expect_named_var_id(flag, span))
1122}
1123
1124fn expect_named_var_id(arg: &Flag, span: Span) -> Result<VarId, ShellError> {
1125    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1126        msg: format!(
1127            "block signature is missing var id for named arg `{}`",
1128            arg.long
1129        ),
1130        span: Some(span),
1131    })
1132}
1133
1134fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result<VarId, ShellError> {
1135    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1136        msg: format!(
1137            "block signature is missing var id for positional arg `{}`",
1138            arg.name
1139        ),
1140        span: Some(span),
1141    })
1142}
1143
1144/// Move arguments from the stack into variables for a custom command
1145fn gather_arguments(
1146    engine_state: &EngineState,
1147    block: &Block,
1148    caller_stack: &mut Stack,
1149    callee_stack: &mut Stack,
1150    args_base: usize,
1151    args_len: usize,
1152    call_head: Span,
1153) -> Result<(), ShellError> {
1154    let mut positional_iter = block
1155        .signature
1156        .required_positional
1157        .iter()
1158        .map(|p| (p, true))
1159        .chain(
1160            block
1161                .signature
1162                .optional_positional
1163                .iter()
1164                .map(|p| (p, false)),
1165        );
1166
1167    // Arguments that didn't get consumed by required/optional
1168    let mut rest = vec![];
1169
1170    // If we encounter a spread, all further positionals should go to rest
1171    let mut always_spread = false;
1172
1173    for arg in caller_stack.arguments.drain_args(args_base, args_len) {
1174        match arg {
1175            Argument::Positional { span, val, .. } => {
1176                // Don't check next positional arg if we encountered a spread previously
1177                let next = (!always_spread).then(|| positional_iter.next()).flatten();
1178                if let Some((positional_arg, required)) = next {
1179                    let var_id = expect_positional_var_id(positional_arg, span)?;
1180                    if required {
1181                        // By checking the type of the bound variable rather than converting the
1182                        // SyntaxShape here, we might be able to save some allocations and effort
1183                        let variable = engine_state.get_var(var_id);
1184                        check_type(&val, &variable.ty)?;
1185                    }
1186                    callee_stack.add_var(var_id, val);
1187                } else {
1188                    rest.push(val);
1189                }
1190            }
1191            Argument::Spread { vals, .. } => {
1192                if let Value::List { vals, .. } = vals {
1193                    rest.extend(vals);
1194                    // All further positional args should go to spread
1195                    always_spread = true;
1196                } else if let Value::Error { error, .. } = vals {
1197                    return Err(*error);
1198                } else {
1199                    return Err(ShellError::CannotSpreadAsList { span: vals.span() });
1200                }
1201            }
1202            Argument::Flag {
1203                data,
1204                name,
1205                short,
1206                span,
1207            } => {
1208                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1209                callee_stack.add_var(var_id, Value::bool(true, span))
1210            }
1211            Argument::Named {
1212                data,
1213                name,
1214                short,
1215                span,
1216                val,
1217                ..
1218            } => {
1219                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1220                callee_stack.add_var(var_id, val)
1221            }
1222            Argument::ParserInfo { .. } => (),
1223        }
1224    }
1225
1226    // Add the collected rest of the arguments if a spread argument exists
1227    if let Some(rest_arg) = &block.signature.rest_positional {
1228        let rest_span = rest.first().map(|v| v.span()).unwrap_or(call_head);
1229        let var_id = expect_positional_var_id(rest_arg, rest_span)?;
1230        callee_stack.add_var(var_id, Value::list(rest, rest_span));
1231    }
1232
1233    // Check for arguments that haven't yet been set and set them to their defaults
1234    for (positional_arg, _) in positional_iter {
1235        let var_id = expect_positional_var_id(positional_arg, call_head)?;
1236        callee_stack.add_var(
1237            var_id,
1238            positional_arg
1239                .default_value
1240                .clone()
1241                .unwrap_or(Value::nothing(call_head)),
1242        );
1243    }
1244
1245    for named_arg in &block.signature.named {
1246        if let Some(var_id) = named_arg.var_id {
1247            // For named arguments, we do this check by looking to see if the variable was set yet on
1248            // the stack. This assumes that the stack's variables was previously empty, but that's a
1249            // fair assumption for a brand new callee stack.
1250            if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) {
1251                let val = if named_arg.arg.is_none() {
1252                    Value::bool(false, call_head)
1253                } else if let Some(value) = &named_arg.default_value {
1254                    value.clone()
1255                } else {
1256                    Value::nothing(call_head)
1257                };
1258                callee_stack.add_var(var_id, val);
1259            }
1260        }
1261    }
1262
1263    Ok(())
1264}
1265
1266/// Type check helper. Produces `CantConvert` error if `val` is not compatible with `ty`.
1267fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> {
1268    match val {
1269        Value::Error { error, .. } => Err(*error.clone()),
1270        _ if val.is_subtype_of(ty) => Ok(()),
1271        _ => Err(ShellError::CantConvert {
1272            to_type: ty.to_string(),
1273            from_type: val.get_type().to_string(),
1274            span: val.span(),
1275            help: None,
1276        }),
1277    }
1278}
1279
1280/// Type check pipeline input against command's input types
1281fn check_input_types(
1282    input: &PipelineData,
1283    signature: &Signature,
1284    head: Span,
1285) -> Result<(), ShellError> {
1286    let io_types = &signature.input_output_types;
1287
1288    // If a command doesn't have any input/output types, then treat command input type as any
1289    if io_types.is_empty() {
1290        return Ok(());
1291    }
1292
1293    // If a command only has a nothing input type, then allow any input data
1294    if io_types.iter().all(|(intype, _)| intype == &Type::Nothing) {
1295        return Ok(());
1296    }
1297
1298    match input {
1299        // early return error directly if detected
1300        PipelineData::Value(Value::Error { error, .. }, ..) => return Err(*error.clone()),
1301        // bypass run-time typechecking for custom types
1302        PipelineData::Value(Value::Custom { .. }, ..) => return Ok(()),
1303        _ => (),
1304    }
1305
1306    // Check if the input type is compatible with *any* of the command's possible input types
1307    if io_types
1308        .iter()
1309        .any(|(command_type, _)| input.is_subtype_of(command_type))
1310    {
1311        return Ok(());
1312    }
1313
1314    let mut input_types = io_types
1315        .iter()
1316        .map(|(input, _)| input.to_string())
1317        .collect::<Vec<String>>();
1318
1319    let expected_string = match input_types.len() {
1320        0 => {
1321            return Err(ShellError::NushellFailed {
1322                msg: "Command input type strings is empty, despite being non-zero earlier"
1323                    .to_string(),
1324            })
1325        }
1326        1 => input_types.swap_remove(0),
1327        2 => input_types.join(" and "),
1328        _ => {
1329            input_types
1330                .last_mut()
1331                .expect("Vector with length >2 has no elements")
1332                .insert_str(0, "and ");
1333            input_types.join(", ")
1334        }
1335    };
1336
1337    match input {
1338        PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: head }),
1339        _ => Err(ShellError::OnlySupportsThisInputType {
1340            exp_input_type: expected_string,
1341            wrong_type: input.get_type().to_string(),
1342            dst_span: head,
1343            src_span: input.span().unwrap_or(Span::unknown()),
1344        }),
1345    }
1346}
1347
1348/// Get variable from [`Stack`] or [`EngineState`]
1349fn get_var(ctx: &EvalContext<'_>, var_id: VarId, span: Span) -> Result<Value, ShellError> {
1350    match var_id {
1351        // $env
1352        ENV_VARIABLE_ID => {
1353            let env_vars = ctx.stack.get_env_vars(ctx.engine_state);
1354            let env_columns = env_vars.keys();
1355            let env_values = env_vars.values();
1356
1357            let mut pairs = env_columns
1358                .map(|x| x.to_string())
1359                .zip(env_values.cloned())
1360                .collect::<Vec<(String, Value)>>();
1361
1362            pairs.sort_by(|a, b| a.0.cmp(&b.0));
1363
1364            Ok(Value::record(pairs.into_iter().collect(), span))
1365        }
1366        _ => ctx.stack.get_var(var_id, span).or_else(|err| {
1367            // $nu is handled by getting constant
1368            if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() {
1369                Ok(const_val.with_span(span))
1370            } else {
1371                Err(err)
1372            }
1373        }),
1374    }
1375}
1376
1377/// Get an environment variable, case-insensitively
1378fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> {
1379    // Read scopes in order
1380    for overlays in ctx
1381        .stack
1382        .env_vars
1383        .iter()
1384        .rev()
1385        .chain(std::iter::once(&ctx.engine_state.env_vars))
1386    {
1387        // Read overlays in order
1388        for overlay_name in ctx.stack.active_overlays.iter().rev() {
1389            let Some(map) = overlays.get(overlay_name) else {
1390                // Skip if overlay doesn't exist in this scope
1391                continue;
1392            };
1393            let hidden = ctx.stack.env_hidden.get(overlay_name);
1394            let is_hidden = |key: &str| hidden.is_some_and(|hidden| hidden.contains(key));
1395
1396            if let Some(val) = map
1397                // Check for exact match
1398                .get(key)
1399                // Skip when encountering an overlay where the key is hidden
1400                .filter(|_| !is_hidden(key))
1401                .or_else(|| {
1402                    // Check to see if it exists at all in the map, with a different case
1403                    map.iter().find_map(|(k, v)| {
1404                        // Again, skip something that's hidden
1405                        (k.eq_ignore_case(key) && !is_hidden(k)).then_some(v)
1406                    })
1407                })
1408            {
1409                return Some(val);
1410            }
1411        }
1412    }
1413    // Not found
1414    None
1415}
1416
1417/// Get the existing name of an environment variable, case-insensitively. This is used to implement
1418/// case preservation of environment variables, so that changing an environment variable that
1419/// already exists always uses the same case.
1420fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> {
1421    // Read scopes in order
1422    ctx.stack
1423        .env_vars
1424        .iter()
1425        .rev()
1426        .chain(std::iter::once(&ctx.engine_state.env_vars))
1427        .flat_map(|overlays| {
1428            // Read overlays in order
1429            ctx.stack
1430                .active_overlays
1431                .iter()
1432                .rev()
1433                .filter_map(|name| overlays.get(name))
1434        })
1435        .find_map(|map| {
1436            // Use the hashmap first to try to be faster?
1437            if map.contains_key(key) {
1438                Some(Cow::Borrowed(key))
1439            } else {
1440                map.keys().find(|k| k.eq_ignore_case(key)).map(|k| {
1441                    // it exists, but with a different case
1442                    Cow::Owned(k.to_owned())
1443                })
1444            }
1445        })
1446        // didn't exist.
1447        .unwrap_or(Cow::Borrowed(key))
1448}
1449
1450/// Helper to collect values into [`PipelineData`], preserving original span and metadata
1451///
1452/// The metadata is removed if it is the file data source, as that's just meant to mark streams.
1453fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> {
1454    let span = data.span().unwrap_or(fallback_span);
1455    let metadata = match data.metadata() {
1456        // Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
1457        // check where some input came from.
1458        Some(PipelineMetadata {
1459            data_source: DataSource::FilePath(_),
1460            content_type: None,
1461        }) => None,
1462        other => other,
1463    };
1464    let value = data.into_value(span)?;
1465    Ok(PipelineData::Value(value, metadata))
1466}
1467
1468/// Helper for drain behavior.
1469fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result<InstructionResult, ShellError> {
1470    use self::InstructionResult::*;
1471    match data {
1472        PipelineData::ByteStream(stream, ..) => {
1473            let span = stream.span();
1474            let callback_spans = stream.get_caller_spans().clone();
1475            if let Err(mut err) = stream.drain() {
1476                ctx.stack.set_last_error(&err);
1477                if callback_spans.is_empty() {
1478                    return Err(err);
1479                } else {
1480                    for s in callback_spans {
1481                        err = ShellError::EvalBlockWithInput {
1482                            span: s,
1483                            sources: vec![err],
1484                        }
1485                    }
1486                    return Err(err);
1487                }
1488            } else {
1489                ctx.stack.set_last_exit_code(0, span);
1490            }
1491        }
1492        PipelineData::ListStream(stream, ..) => {
1493            let callback_spans = stream.get_caller_spans().clone();
1494            if let Err(mut err) = stream.drain() {
1495                if callback_spans.is_empty() {
1496                    return Err(err);
1497                } else {
1498                    for s in callback_spans {
1499                        err = ShellError::EvalBlockWithInput {
1500                            span: s,
1501                            sources: vec![err],
1502                        }
1503                    }
1504                    return Err(err);
1505                }
1506            }
1507        }
1508        PipelineData::Value(..) | PipelineData::Empty => {}
1509    }
1510    Ok(Continue)
1511}
1512
1513enum RedirectionStream {
1514    Out,
1515    Err,
1516}
1517
1518/// Open a file for redirection
1519fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<File>, ShellError> {
1520    let path_expanded =
1521        expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
1522    let mut options = File::options();
1523    if append {
1524        options.append(true);
1525    } else {
1526        options.write(true).truncate(true);
1527    }
1528    let file = options
1529        .create(true)
1530        .open(&path_expanded)
1531        .map_err(|err| IoError::new(err.kind(), path.span(), path_expanded))?;
1532    Ok(Arc::new(file))
1533}
1534
1535/// Set up a [`Redirection`] from a [`RedirectMode`]
1536fn eval_redirection(
1537    ctx: &mut EvalContext<'_>,
1538    mode: &RedirectMode,
1539    span: Span,
1540    which: RedirectionStream,
1541) -> Result<Option<Redirection>, ShellError> {
1542    match mode {
1543        RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))),
1544        RedirectMode::PipeSeparate => Ok(Some(Redirection::Pipe(OutDest::PipeSeparate))),
1545        RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
1546        RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
1547        RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
1548        RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
1549        RedirectMode::File { file_num } => {
1550            let file = ctx
1551                .files
1552                .get(*file_num as usize)
1553                .cloned()
1554                .flatten()
1555                .ok_or_else(|| ShellError::IrEvalError {
1556                    msg: format!("Tried to redirect to file #{file_num}, but it is not open"),
1557                    span: Some(span),
1558                })?;
1559            Ok(Some(Redirection::File(file)))
1560        }
1561        RedirectMode::Caller => Ok(match which {
1562            RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe),
1563            RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe),
1564        }),
1565    }
1566}
1567
1568/// Do an `iterate` instruction. This can be called repeatedly to get more values from an iterable
1569fn eval_iterate(
1570    ctx: &mut EvalContext<'_>,
1571    dst: RegId,
1572    stream: RegId,
1573    end_index: usize,
1574) -> Result<InstructionResult, ShellError> {
1575    let mut data = ctx.take_reg(stream);
1576    if let PipelineData::ListStream(list_stream, _) = &mut data {
1577        // Modify the stream, taking one value off, and branching if it's empty
1578        if let Some(val) = list_stream.next_value() {
1579            ctx.put_reg(dst, val.into_pipeline_data());
1580            ctx.put_reg(stream, data); // put the stream back so it can be iterated on again
1581            Ok(InstructionResult::Continue)
1582        } else {
1583            ctx.put_reg(dst, PipelineData::Empty);
1584            Ok(InstructionResult::Branch(end_index))
1585        }
1586    } else {
1587        // Convert the PipelineData to an iterator, and wrap it in a ListStream so it can be
1588        // iterated on
1589        let metadata = data.metadata();
1590        let span = data.span().unwrap_or(Span::unknown());
1591        ctx.put_reg(
1592            stream,
1593            PipelineData::ListStream(
1594                ListStream::new(data.into_iter(), span, Signals::EMPTY),
1595                metadata,
1596            ),
1597        );
1598        eval_iterate(ctx, dst, stream, end_index)
1599    }
1600}
1601
1602/// Redirect environment from the callee stack to the caller stack
1603fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
1604    // TODO: make this more efficient
1605    // Grab all environment variables from the callee
1606    let caller_env_vars = caller_stack.get_env_var_names(engine_state);
1607
1608    // remove env vars that are present in the caller but not in the callee
1609    // (the callee hid them)
1610    for var in caller_env_vars.iter() {
1611        if !callee_stack.has_env_var(engine_state, var) {
1612            caller_stack.remove_env_var(engine_state, var);
1613        }
1614    }
1615
1616    // add new env vars from callee to caller
1617    for (var, value) in callee_stack.get_stack_env_vars() {
1618        caller_stack.add_env_var(var, value);
1619    }
1620
1621    // set config to callee config, to capture any updates to that
1622    caller_stack.config.clone_from(&callee_stack.config);
1623}