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, span)| 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        if let Some(block_id) = decl.block_id() {
1031            // If the decl is a custom command
1032            let block = engine_state.get_block(block_id);
1033
1034            // check types after acquiring block to avoid unnecessarily cloning Signature
1035            check_input_types(&input, &block.signature, head)?;
1036
1037            // Set up a callee stack with the captures and move arguments from the stack into variables
1038            let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
1039
1040            gather_arguments(
1041                engine_state,
1042                block,
1043                &mut caller_stack,
1044                &mut callee_stack,
1045                *args_base,
1046                args_len,
1047                head,
1048            )?;
1049
1050            // Add one to the recursion count, so we don't recurse too deep. Stack overflows are not
1051            // recoverable in Rust.
1052            callee_stack.recursion_count += 1;
1053
1054            let result =
1055                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
1062            result
1063        } else {
1064            check_input_types(&input, &decl.signature(), head)?;
1065            // FIXME: precalculate this and save it somewhere
1066            let span = Span::merge_many(
1067                std::iter::once(head).chain(
1068                    caller_stack
1069                        .arguments
1070                        .get_args(*args_base, args_len)
1071                        .iter()
1072                        .flat_map(|arg| arg.span()),
1073                ),
1074            );
1075
1076            let call = Call {
1077                decl_id,
1078                head,
1079                span,
1080                args_base: *args_base,
1081                args_len,
1082            };
1083
1084            // Run the call
1085            decl.run(engine_state, &mut caller_stack, &(&call).into(), input)
1086        }
1087    })();
1088
1089    drop(caller_stack);
1090
1091    // Important that this runs, to reset state post-call:
1092    ctx.stack.arguments.leave_frame(ctx.args_base);
1093    ctx.redirect_out = None;
1094    ctx.redirect_err = None;
1095
1096    result
1097}
1098
1099fn find_named_var_id(
1100    sig: &Signature,
1101    name: &[u8],
1102    short: &[u8],
1103    span: Span,
1104) -> Result<VarId, ShellError> {
1105    sig.named
1106        .iter()
1107        .find(|n| {
1108            if !n.long.is_empty() {
1109                n.long.as_bytes() == name
1110            } else {
1111                // It's possible to only have a short name and no long name
1112                n.short
1113                    .is_some_and(|s| s.encode_utf8(&mut [0; 4]).as_bytes() == short)
1114            }
1115        })
1116        .ok_or_else(|| ShellError::IrEvalError {
1117            msg: format!(
1118                "block does not have an argument named `{}`",
1119                String::from_utf8_lossy(name)
1120            ),
1121            span: Some(span),
1122        })
1123        .and_then(|flag| expect_named_var_id(flag, span))
1124}
1125
1126fn expect_named_var_id(arg: &Flag, span: Span) -> Result<VarId, ShellError> {
1127    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1128        msg: format!(
1129            "block signature is missing var id for named arg `{}`",
1130            arg.long
1131        ),
1132        span: Some(span),
1133    })
1134}
1135
1136fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result<VarId, ShellError> {
1137    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1138        msg: format!(
1139            "block signature is missing var id for positional arg `{}`",
1140            arg.name
1141        ),
1142        span: Some(span),
1143    })
1144}
1145
1146/// Move arguments from the stack into variables for a custom command
1147fn gather_arguments(
1148    engine_state: &EngineState,
1149    block: &Block,
1150    caller_stack: &mut Stack,
1151    callee_stack: &mut Stack,
1152    args_base: usize,
1153    args_len: usize,
1154    call_head: Span,
1155) -> Result<(), ShellError> {
1156    let mut positional_iter = block
1157        .signature
1158        .required_positional
1159        .iter()
1160        .map(|p| (p, true))
1161        .chain(
1162            block
1163                .signature
1164                .optional_positional
1165                .iter()
1166                .map(|p| (p, false)),
1167        );
1168
1169    // Arguments that didn't get consumed by required/optional
1170    let mut rest = vec![];
1171
1172    // If we encounter a spread, all further positionals should go to rest
1173    let mut always_spread = false;
1174
1175    for arg in caller_stack.arguments.drain_args(args_base, args_len) {
1176        match arg {
1177            Argument::Positional { span, val, .. } => {
1178                // Don't check next positional arg if we encountered a spread previously
1179                let next = (!always_spread).then(|| positional_iter.next()).flatten();
1180                if let Some((positional_arg, required)) = next {
1181                    let var_id = expect_positional_var_id(positional_arg, span)?;
1182                    if required {
1183                        // By checking the type of the bound variable rather than converting the
1184                        // SyntaxShape here, we might be able to save some allocations and effort
1185                        let variable = engine_state.get_var(var_id);
1186                        check_type(&val, &variable.ty)?;
1187                    }
1188                    callee_stack.add_var(var_id, val);
1189                } else {
1190                    rest.push(val);
1191                }
1192            }
1193            Argument::Spread { vals, .. } => {
1194                if let Value::List { vals, .. } = vals {
1195                    rest.extend(vals);
1196                    // All further positional args should go to spread
1197                    always_spread = true;
1198                } else if let Value::Error { error, .. } = vals {
1199                    return Err(*error);
1200                } else {
1201                    return Err(ShellError::CannotSpreadAsList { span: vals.span() });
1202                }
1203            }
1204            Argument::Flag {
1205                data,
1206                name,
1207                short,
1208                span,
1209            } => {
1210                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1211                callee_stack.add_var(var_id, Value::bool(true, span))
1212            }
1213            Argument::Named {
1214                data,
1215                name,
1216                short,
1217                span,
1218                val,
1219                ..
1220            } => {
1221                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1222                callee_stack.add_var(var_id, val)
1223            }
1224            Argument::ParserInfo { .. } => (),
1225        }
1226    }
1227
1228    // Add the collected rest of the arguments if a spread argument exists
1229    if let Some(rest_arg) = &block.signature.rest_positional {
1230        let rest_span = rest.first().map(|v| v.span()).unwrap_or(call_head);
1231        let var_id = expect_positional_var_id(rest_arg, rest_span)?;
1232        callee_stack.add_var(var_id, Value::list(rest, rest_span));
1233    }
1234
1235    // Check for arguments that haven't yet been set and set them to their defaults
1236    for (positional_arg, _) in positional_iter {
1237        let var_id = expect_positional_var_id(positional_arg, call_head)?;
1238        callee_stack.add_var(
1239            var_id,
1240            positional_arg
1241                .default_value
1242                .clone()
1243                .unwrap_or(Value::nothing(call_head)),
1244        );
1245    }
1246
1247    for named_arg in &block.signature.named {
1248        if let Some(var_id) = named_arg.var_id {
1249            // For named arguments, we do this check by looking to see if the variable was set yet on
1250            // the stack. This assumes that the stack's variables was previously empty, but that's a
1251            // fair assumption for a brand new callee stack.
1252            if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) {
1253                let val = if named_arg.arg.is_none() {
1254                    Value::bool(false, call_head)
1255                } else if let Some(value) = &named_arg.default_value {
1256                    value.clone()
1257                } else {
1258                    Value::nothing(call_head)
1259                };
1260                callee_stack.add_var(var_id, val);
1261            }
1262        }
1263    }
1264
1265    Ok(())
1266}
1267
1268/// Type check helper. Produces `CantConvert` error if `val` is not compatible with `ty`.
1269fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> {
1270    match val {
1271        Value::Error { error, .. } => Err(*error.clone()),
1272        _ if val.is_subtype_of(ty) => Ok(()),
1273        _ => Err(ShellError::CantConvert {
1274            to_type: ty.to_string(),
1275            from_type: val.get_type().to_string(),
1276            span: val.span(),
1277            help: None,
1278        }),
1279    }
1280}
1281
1282/// Type check pipeline input against command's input types
1283fn check_input_types(
1284    input: &PipelineData,
1285    signature: &Signature,
1286    head: Span,
1287) -> Result<(), ShellError> {
1288    let io_types = &signature.input_output_types;
1289
1290    // If a command doesn't have any input/output types, then treat command input type as any
1291    if io_types.is_empty() {
1292        return Ok(());
1293    }
1294
1295    // If a command only has a nothing input type, then allow any input data
1296    if io_types.iter().all(|(intype, _)| intype == &Type::Nothing) {
1297        return Ok(());
1298    }
1299
1300    match input {
1301        // early return error directly if detected
1302        PipelineData::Value(Value::Error { error, .. }, ..) => return Err(*error.clone()),
1303        // bypass run-time typechecking for custom types
1304        PipelineData::Value(Value::Custom { .. }, ..) => return Ok(()),
1305        _ => (),
1306    }
1307
1308    // Check if the input type is compatible with *any* of the command's possible input types
1309    if io_types
1310        .iter()
1311        .any(|(command_type, _)| input.is_subtype_of(command_type))
1312    {
1313        return Ok(());
1314    }
1315
1316    let mut input_types = io_types
1317        .iter()
1318        .map(|(input, _)| input.to_string())
1319        .collect::<Vec<String>>();
1320
1321    let expected_string = match input_types.len() {
1322        0 => {
1323            return Err(ShellError::NushellFailed {
1324                msg: "Command input type strings is empty, despite being non-zero earlier"
1325                    .to_string(),
1326            })
1327        }
1328        1 => input_types.swap_remove(0),
1329        2 => input_types.join(" and "),
1330        _ => {
1331            input_types
1332                .last_mut()
1333                .expect("Vector with length >2 has no elements")
1334                .insert_str(0, "and ");
1335            input_types.join(", ")
1336        }
1337    };
1338
1339    match input {
1340        PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: head }),
1341        _ => Err(ShellError::OnlySupportsThisInputType {
1342            exp_input_type: expected_string,
1343            wrong_type: input.get_type().to_string(),
1344            dst_span: head,
1345            src_span: input.span().unwrap_or(Span::unknown()),
1346        }),
1347    }
1348}
1349
1350/// Get variable from [`Stack`] or [`EngineState`]
1351fn get_var(ctx: &EvalContext<'_>, var_id: VarId, span: Span) -> Result<Value, ShellError> {
1352    match var_id {
1353        // $env
1354        ENV_VARIABLE_ID => {
1355            let env_vars = ctx.stack.get_env_vars(ctx.engine_state);
1356            let env_columns = env_vars.keys();
1357            let env_values = env_vars.values();
1358
1359            let mut pairs = env_columns
1360                .map(|x| x.to_string())
1361                .zip(env_values.cloned())
1362                .collect::<Vec<(String, Value)>>();
1363
1364            pairs.sort_by(|a, b| a.0.cmp(&b.0));
1365
1366            Ok(Value::record(pairs.into_iter().collect(), span))
1367        }
1368        _ => ctx.stack.get_var(var_id, span).or_else(|err| {
1369            // $nu is handled by getting constant
1370            if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() {
1371                Ok(const_val.with_span(span))
1372            } else {
1373                Err(err)
1374            }
1375        }),
1376    }
1377}
1378
1379/// Get an environment variable, case-insensitively
1380fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> {
1381    // Read scopes in order
1382    for overlays in ctx
1383        .stack
1384        .env_vars
1385        .iter()
1386        .rev()
1387        .chain(std::iter::once(&ctx.engine_state.env_vars))
1388    {
1389        // Read overlays in order
1390        for overlay_name in ctx.stack.active_overlays.iter().rev() {
1391            let Some(map) = overlays.get(overlay_name) else {
1392                // Skip if overlay doesn't exist in this scope
1393                continue;
1394            };
1395            let hidden = ctx.stack.env_hidden.get(overlay_name);
1396            let is_hidden = |key: &str| hidden.is_some_and(|hidden| hidden.contains(key));
1397
1398            if let Some(val) = map
1399                // Check for exact match
1400                .get(key)
1401                // Skip when encountering an overlay where the key is hidden
1402                .filter(|_| !is_hidden(key))
1403                .or_else(|| {
1404                    // Check to see if it exists at all in the map, with a different case
1405                    map.iter().find_map(|(k, v)| {
1406                        // Again, skip something that's hidden
1407                        (k.eq_ignore_case(key) && !is_hidden(k)).then_some(v)
1408                    })
1409                })
1410            {
1411                return Some(val);
1412            }
1413        }
1414    }
1415    // Not found
1416    None
1417}
1418
1419/// Get the existing name of an environment variable, case-insensitively. This is used to implement
1420/// case preservation of environment variables, so that changing an environment variable that
1421/// already exists always uses the same case.
1422fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> {
1423    // Read scopes in order
1424    ctx.stack
1425        .env_vars
1426        .iter()
1427        .rev()
1428        .chain(std::iter::once(&ctx.engine_state.env_vars))
1429        .flat_map(|overlays| {
1430            // Read overlays in order
1431            ctx.stack
1432                .active_overlays
1433                .iter()
1434                .rev()
1435                .filter_map(|name| overlays.get(name))
1436        })
1437        .find_map(|map| {
1438            // Use the hashmap first to try to be faster?
1439            if map.contains_key(key) {
1440                Some(Cow::Borrowed(key))
1441            } else {
1442                map.keys().find(|k| k.eq_ignore_case(key)).map(|k| {
1443                    // it exists, but with a different case
1444                    Cow::Owned(k.to_owned())
1445                })
1446            }
1447        })
1448        // didn't exist.
1449        .unwrap_or(Cow::Borrowed(key))
1450}
1451
1452/// Helper to collect values into [`PipelineData`], preserving original span and metadata
1453///
1454/// The metadata is removed if it is the file data source, as that's just meant to mark streams.
1455fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> {
1456    let span = data.span().unwrap_or(fallback_span);
1457    let metadata = match data.metadata() {
1458        // Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
1459        // check where some input came from.
1460        Some(PipelineMetadata {
1461            data_source: DataSource::FilePath(_),
1462            content_type: None,
1463        }) => None,
1464        other => other,
1465    };
1466    let value = data.into_value(span)?;
1467    Ok(PipelineData::Value(value, metadata))
1468}
1469
1470/// Helper for drain behavior.
1471fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result<InstructionResult, ShellError> {
1472    use self::InstructionResult::*;
1473    match data {
1474        PipelineData::ByteStream(stream, ..) => {
1475            let span = stream.span();
1476            let callback_spans = stream.get_caller_spans().clone();
1477            if let Err(mut err) = stream.drain() {
1478                ctx.stack.set_last_error(&err);
1479                if callback_spans.is_empty() {
1480                    return Err(err);
1481                } else {
1482                    for s in callback_spans {
1483                        err = ShellError::EvalBlockWithInput {
1484                            span: s,
1485                            sources: vec![err],
1486                        }
1487                    }
1488                    return Err(err);
1489                }
1490            } else {
1491                ctx.stack.set_last_exit_code(0, span);
1492            }
1493        }
1494        PipelineData::ListStream(stream, ..) => {
1495            let callback_spans = stream.get_caller_spans().clone();
1496            if let Err(mut err) = stream.drain() {
1497                if callback_spans.is_empty() {
1498                    return Err(err);
1499                } else {
1500                    for s in callback_spans {
1501                        err = ShellError::EvalBlockWithInput {
1502                            span: s,
1503                            sources: vec![err],
1504                        }
1505                    }
1506                    return Err(err);
1507                }
1508            }
1509        }
1510        PipelineData::Value(..) | PipelineData::Empty => {}
1511    }
1512    Ok(Continue)
1513}
1514
1515enum RedirectionStream {
1516    Out,
1517    Err,
1518}
1519
1520/// Open a file for redirection
1521fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<File>, ShellError> {
1522    let path_expanded =
1523        expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
1524    let mut options = File::options();
1525    if append {
1526        options.append(true);
1527    } else {
1528        options.write(true).truncate(true);
1529    }
1530    let file = options
1531        .create(true)
1532        .open(&path_expanded)
1533        .map_err(|err| IoError::new(err.kind(), path.span(), path_expanded))?;
1534    Ok(Arc::new(file))
1535}
1536
1537/// Set up a [`Redirection`] from a [`RedirectMode`]
1538fn eval_redirection(
1539    ctx: &mut EvalContext<'_>,
1540    mode: &RedirectMode,
1541    span: Span,
1542    which: RedirectionStream,
1543) -> Result<Option<Redirection>, ShellError> {
1544    match mode {
1545        RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))),
1546        RedirectMode::PipeSeparate => Ok(Some(Redirection::Pipe(OutDest::PipeSeparate))),
1547        RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
1548        RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
1549        RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
1550        RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
1551        RedirectMode::File { file_num } => {
1552            let file = ctx
1553                .files
1554                .get(*file_num as usize)
1555                .cloned()
1556                .flatten()
1557                .ok_or_else(|| ShellError::IrEvalError {
1558                    msg: format!("Tried to redirect to file #{file_num}, but it is not open"),
1559                    span: Some(span),
1560                })?;
1561            Ok(Some(Redirection::File(file)))
1562        }
1563        RedirectMode::Caller => Ok(match which {
1564            RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe),
1565            RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe),
1566        }),
1567    }
1568}
1569
1570/// Do an `iterate` instruction. This can be called repeatedly to get more values from an iterable
1571fn eval_iterate(
1572    ctx: &mut EvalContext<'_>,
1573    dst: RegId,
1574    stream: RegId,
1575    end_index: usize,
1576) -> Result<InstructionResult, ShellError> {
1577    let mut data = ctx.take_reg(stream);
1578    if let PipelineData::ListStream(list_stream, _) = &mut data {
1579        // Modify the stream, taking one value off, and branching if it's empty
1580        if let Some(val) = list_stream.next_value() {
1581            ctx.put_reg(dst, val.into_pipeline_data());
1582            ctx.put_reg(stream, data); // put the stream back so it can be iterated on again
1583            Ok(InstructionResult::Continue)
1584        } else {
1585            ctx.put_reg(dst, PipelineData::Empty);
1586            Ok(InstructionResult::Branch(end_index))
1587        }
1588    } else {
1589        // Convert the PipelineData to an iterator, and wrap it in a ListStream so it can be
1590        // iterated on
1591        let metadata = data.metadata();
1592        let span = data.span().unwrap_or(Span::unknown());
1593        ctx.put_reg(
1594            stream,
1595            PipelineData::ListStream(
1596                ListStream::new(data.into_iter(), span, Signals::EMPTY),
1597                metadata,
1598            ),
1599        );
1600        eval_iterate(ctx, dst, stream, end_index)
1601    }
1602}
1603
1604/// Redirect environment from the callee stack to the caller stack
1605fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
1606    // TODO: make this more efficient
1607    // Grab all environment variables from the callee
1608    let caller_env_vars = caller_stack.get_env_var_names(engine_state);
1609
1610    // remove env vars that are present in the caller but not in the callee
1611    // (the callee hid them)
1612    for var in caller_env_vars.iter() {
1613        if !callee_stack.has_env_var(engine_state, var) {
1614            caller_stack.remove_env_var(engine_state, var);
1615        }
1616    }
1617
1618    // add new env vars from callee to caller
1619    for (var, value) in callee_stack.get_stack_env_vars() {
1620        caller_stack.add_env_var(var, value);
1621    }
1622
1623    // set config to callee config, to capture any updates to that
1624    caller_stack.config.clone_from(&callee_stack.config);
1625}