nu_engine/
eval_ir.rs

1use std::{borrow::Cow, fs::File, sync::Arc};
2
3use nu_path::{AbsolutePathBuf, expand_path_with};
4use nu_protocol::{
5    DataSource, DeclId, ENV_VARIABLE_ID, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest,
6    PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, Signals,
7    Signature, Span, Spanned, Type, Value, VarId,
8    ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
9    debugger::DebugContext,
10    engine::{
11        Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet,
12    },
13    ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
14    shell_error::io::IoError,
15};
16use nu_utils::IgnoreCaseExt;
17
18use crate::{
19    ENV_CONVERSIONS, convert_env_vars, eval::is_automatic_env_var, eval_block_with_early_return,
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)?;
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                let value = value.follow_cell_path(&path.members)?;
698                ctx.put_reg(*dst, value.into_owned().into_pipeline_data());
699                Ok(Continue)
700            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
701                Err(*error)
702            } else {
703                Err(ShellError::TypeMismatch {
704                    err_message: "expected cell path".into(),
705                    span: path.span().unwrap_or(*span),
706                })
707            }
708        }
709        Instruction::UpsertCellPath {
710            src_dst,
711            path,
712            new_value,
713        } => {
714            let data = ctx.take_reg(*src_dst);
715            let metadata = data.metadata();
716            // Change the span because we're modifying it
717            let mut value = data.into_value(*span)?;
718            let path = ctx.take_reg(*path);
719            let new_value = ctx.collect_reg(*new_value, *span)?;
720            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
721                value.upsert_data_at_cell_path(&path.members, new_value)?;
722                ctx.put_reg(*src_dst, value.into_pipeline_data_with_metadata(metadata));
723                Ok(Continue)
724            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
725                Err(*error)
726            } else {
727                Err(ShellError::TypeMismatch {
728                    err_message: "expected cell path".into(),
729                    span: path.span().unwrap_or(*span),
730                })
731            }
732        }
733        Instruction::Jump { index } => Ok(Branch(*index)),
734        Instruction::BranchIf { cond, index } => {
735            let data = ctx.take_reg(*cond);
736            let data_span = data.span();
737            let val = match data {
738                PipelineData::Value(Value::Bool { val, .. }, _) => val,
739                PipelineData::Value(Value::Error { error, .. }, _) => {
740                    return Err(*error);
741                }
742                _ => {
743                    return Err(ShellError::TypeMismatch {
744                        err_message: "expected bool".into(),
745                        span: data_span.unwrap_or(*span),
746                    });
747                }
748            };
749            if val {
750                Ok(Branch(*index))
751            } else {
752                Ok(Continue)
753            }
754        }
755        Instruction::BranchIfEmpty { src, index } => {
756            let is_empty = matches!(
757                ctx.borrow_reg(*src),
758                PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, _)
759            );
760
761            if is_empty {
762                Ok(Branch(*index))
763            } else {
764                Ok(Continue)
765            }
766        }
767        Instruction::Match {
768            pattern,
769            src,
770            index,
771        } => {
772            let value = ctx.clone_reg_value(*src, *span)?;
773            ctx.matches.clear();
774            if pattern.match_value(&value, &mut ctx.matches) {
775                // Match succeeded: set variables and branch
776                for (var_id, match_value) in ctx.matches.drain(..) {
777                    ctx.stack.add_var(var_id, match_value);
778                }
779                Ok(Branch(*index))
780            } else {
781                // Failed to match, put back original value
782                ctx.matches.clear();
783                Ok(Continue)
784            }
785        }
786        Instruction::CheckMatchGuard { src } => {
787            if matches!(
788                ctx.borrow_reg(*src),
789                PipelineData::Value(Value::Bool { .. }, _)
790            ) {
791                Ok(Continue)
792            } else {
793                Err(ShellError::MatchGuardNotBool { span: *span })
794            }
795        }
796        Instruction::Iterate {
797            dst,
798            stream,
799            end_index,
800        } => eval_iterate(ctx, *dst, *stream, *end_index),
801        Instruction::OnError { index } => {
802            ctx.stack.error_handlers.push(ErrorHandler {
803                handler_index: *index,
804                error_register: None,
805            });
806            Ok(Continue)
807        }
808        Instruction::OnErrorInto { index, dst } => {
809            ctx.stack.error_handlers.push(ErrorHandler {
810                handler_index: *index,
811                error_register: Some(*dst),
812            });
813            Ok(Continue)
814        }
815        Instruction::PopErrorHandler => {
816            ctx.stack.error_handlers.pop(ctx.error_handler_base);
817            Ok(Continue)
818        }
819        Instruction::ReturnEarly { src } => {
820            let val = ctx.collect_reg(*src, *span)?;
821            Err(ShellError::Return {
822                span: *span,
823                value: Box::new(val),
824            })
825        }
826        Instruction::Return { src } => Ok(Return(*src)),
827    }
828}
829
830/// Load a literal value into a register
831fn load_literal(
832    ctx: &mut EvalContext<'_>,
833    dst: RegId,
834    lit: &Literal,
835    span: Span,
836) -> Result<InstructionResult, ShellError> {
837    let value = literal_value(ctx, lit, span)?;
838    ctx.put_reg(dst, PipelineData::Value(value, None));
839    Ok(InstructionResult::Continue)
840}
841
842fn literal_value(
843    ctx: &mut EvalContext<'_>,
844    lit: &Literal,
845    span: Span,
846) -> Result<Value, ShellError> {
847    Ok(match lit {
848        Literal::Bool(b) => Value::bool(*b, span),
849        Literal::Int(i) => Value::int(*i, span),
850        Literal::Float(f) => Value::float(*f, span),
851        Literal::Filesize(q) => Value::filesize(*q, span),
852        Literal::Duration(q) => Value::duration(*q, span),
853        Literal::Binary(bin) => Value::binary(&ctx.data[*bin], span),
854        Literal::Block(block_id) | Literal::RowCondition(block_id) | Literal::Closure(block_id) => {
855            let block = ctx.engine_state.get_block(*block_id);
856            let captures = block
857                .captures
858                .iter()
859                .map(|(var_id, span)| get_var(ctx, *var_id, *span).map(|val| (*var_id, val)))
860                .collect::<Result<Vec<_>, ShellError>>()?;
861            Value::closure(
862                Closure {
863                    block_id: *block_id,
864                    captures,
865                },
866                span,
867            )
868        }
869        Literal::Range {
870            start,
871            step,
872            end,
873            inclusion,
874        } => {
875            let start = ctx.collect_reg(*start, span)?;
876            let step = ctx.collect_reg(*step, span)?;
877            let end = ctx.collect_reg(*end, span)?;
878            let range = Range::new(start, step, end, *inclusion, span)?;
879            Value::range(range, span)
880        }
881        Literal::List { capacity } => Value::list(Vec::with_capacity(*capacity), span),
882        Literal::Record { capacity } => Value::record(Record::with_capacity(*capacity), span),
883        Literal::Filepath {
884            val: path,
885            no_expand,
886        } => {
887            let path = ctx.get_str(*path, span)?;
888            if *no_expand {
889                Value::string(path, span)
890            } else {
891                let cwd = ctx.engine_state.cwd(Some(ctx.stack))?;
892                let path = expand_path_with(path, cwd, true);
893
894                Value::string(path.to_string_lossy(), span)
895            }
896        }
897        Literal::Directory {
898            val: path,
899            no_expand,
900        } => {
901            let path = ctx.get_str(*path, span)?;
902            if path == "-" {
903                Value::string("-", span)
904            } else if *no_expand {
905                Value::string(path, span)
906            } else {
907                let cwd = ctx
908                    .engine_state
909                    .cwd(Some(ctx.stack))
910                    .map(AbsolutePathBuf::into_std_path_buf)
911                    .unwrap_or_default();
912                let path = expand_path_with(path, cwd, true);
913
914                Value::string(path.to_string_lossy(), span)
915            }
916        }
917        Literal::GlobPattern { val, no_expand } => {
918            Value::glob(ctx.get_str(*val, span)?, *no_expand, span)
919        }
920        Literal::String(s) => Value::string(ctx.get_str(*s, span)?, span),
921        Literal::RawString(s) => Value::string(ctx.get_str(*s, span)?, span),
922        Literal::CellPath(path) => Value::cell_path(CellPath::clone(path), span),
923        Literal::Date(dt) => Value::date(**dt, span),
924        Literal::Nothing => Value::nothing(span),
925    })
926}
927
928fn binary_op(
929    ctx: &mut EvalContext<'_>,
930    lhs_dst: RegId,
931    op: &Operator,
932    rhs: RegId,
933    span: Span,
934) -> Result<InstructionResult, ShellError> {
935    let lhs_val = ctx.collect_reg(lhs_dst, span)?;
936    let rhs_val = ctx.collect_reg(rhs, span)?;
937
938    // Handle binary op errors early
939    if let Value::Error { error, .. } = lhs_val {
940        return Err(*error);
941    }
942    if let Value::Error { error, .. } = rhs_val {
943        return Err(*error);
944    }
945
946    // We only have access to one span here, but the generated code usually adds a `span`
947    // instruction to set the output span to the right span.
948    let op_span = span;
949
950    let result = match op {
951        Operator::Comparison(cmp) => match cmp {
952            Comparison::Equal => lhs_val.eq(op_span, &rhs_val, span)?,
953            Comparison::NotEqual => lhs_val.ne(op_span, &rhs_val, span)?,
954            Comparison::LessThan => lhs_val.lt(op_span, &rhs_val, span)?,
955            Comparison::GreaterThan => lhs_val.gt(op_span, &rhs_val, span)?,
956            Comparison::LessThanOrEqual => lhs_val.lte(op_span, &rhs_val, span)?,
957            Comparison::GreaterThanOrEqual => lhs_val.gte(op_span, &rhs_val, span)?,
958            Comparison::RegexMatch => {
959                lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, false, span)?
960            }
961            Comparison::NotRegexMatch => {
962                lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, true, span)?
963            }
964            Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?,
965            Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?,
966            Comparison::Has => lhs_val.has(op_span, &rhs_val, span)?,
967            Comparison::NotHas => lhs_val.not_has(op_span, &rhs_val, span)?,
968            Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?,
969            Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
970        },
971        Operator::Math(mat) => match mat {
972            Math::Add => lhs_val.add(op_span, &rhs_val, span)?,
973            Math::Subtract => lhs_val.sub(op_span, &rhs_val, span)?,
974            Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?,
975            Math::Divide => lhs_val.div(op_span, &rhs_val, span)?,
976            Math::FloorDivide => lhs_val.floor_div(op_span, &rhs_val, span)?,
977            Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?,
978            Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?,
979            Math::Concatenate => lhs_val.concat(op_span, &rhs_val, span)?,
980        },
981        Operator::Boolean(bl) => match bl {
982            Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?,
983            Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?,
984            Boolean::And => lhs_val.and(op_span, &rhs_val, span)?,
985        },
986        Operator::Bits(bit) => match bit {
987            Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?,
988            Bits::BitXor => lhs_val.bit_xor(op_span, &rhs_val, span)?,
989            Bits::BitAnd => lhs_val.bit_and(op_span, &rhs_val, span)?,
990            Bits::ShiftLeft => lhs_val.bit_shl(op_span, &rhs_val, span)?,
991            Bits::ShiftRight => lhs_val.bit_shr(op_span, &rhs_val, span)?,
992        },
993        Operator::Assignment(_asg) => {
994            return Err(ShellError::IrEvalError {
995                msg: "can't eval assignment with the `binary-op` instruction".into(),
996                span: Some(span),
997            });
998        }
999    };
1000
1001    ctx.put_reg(lhs_dst, PipelineData::Value(result, None));
1002
1003    Ok(InstructionResult::Continue)
1004}
1005
1006/// Evaluate a call
1007fn eval_call<D: DebugContext>(
1008    ctx: &mut EvalContext<'_>,
1009    decl_id: DeclId,
1010    head: Span,
1011    input: PipelineData,
1012) -> Result<PipelineData, ShellError> {
1013    let EvalContext {
1014        engine_state,
1015        stack: caller_stack,
1016        args_base,
1017        redirect_out,
1018        redirect_err,
1019        ..
1020    } = ctx;
1021
1022    let args_len = caller_stack.arguments.get_len(*args_base);
1023    let decl = engine_state.get_decl(decl_id);
1024
1025    // Set up redirect modes
1026    let mut caller_stack = caller_stack.push_redirection(redirect_out.take(), redirect_err.take());
1027
1028    let result = (|| {
1029        if let Some(block_id) = decl.block_id() {
1030            // If the decl is a custom command
1031            let block = engine_state.get_block(block_id);
1032
1033            // check types after acquiring block to avoid unnecessarily cloning Signature
1034            check_input_types(&input, &block.signature, head)?;
1035
1036            // Set up a callee stack with the captures and move arguments from the stack into variables
1037            let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
1038
1039            gather_arguments(
1040                engine_state,
1041                block,
1042                &mut caller_stack,
1043                &mut callee_stack,
1044                *args_base,
1045                args_len,
1046                head,
1047            )?;
1048
1049            // Add one to the recursion count, so we don't recurse too deep. Stack overflows are not
1050            // recoverable in Rust.
1051            callee_stack.recursion_count += 1;
1052
1053            let result =
1054                eval_block_with_early_return::<D>(engine_state, &mut callee_stack, block, input);
1055
1056            // Move environment variables back into the caller stack scope if requested to do so
1057            if block.redirect_env {
1058                redirect_env(engine_state, &mut caller_stack, &callee_stack);
1059            }
1060
1061            result
1062        } else {
1063            check_input_types(&input, &decl.signature(), head)?;
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            decl.run(engine_state, &mut caller_stack, &(&call).into(), input)
1085        }
1086    })();
1087
1088    drop(caller_stack);
1089
1090    // Important that this runs, to reset state post-call:
1091    ctx.stack.arguments.leave_frame(ctx.args_base);
1092    ctx.redirect_out = None;
1093    ctx.redirect_err = None;
1094
1095    result
1096}
1097
1098fn find_named_var_id(
1099    sig: &Signature,
1100    name: &[u8],
1101    short: &[u8],
1102    span: Span,
1103) -> Result<VarId, ShellError> {
1104    sig.named
1105        .iter()
1106        .find(|n| {
1107            if !n.long.is_empty() {
1108                n.long.as_bytes() == name
1109            } else {
1110                // It's possible to only have a short name and no long name
1111                n.short
1112                    .is_some_and(|s| s.encode_utf8(&mut [0; 4]).as_bytes() == short)
1113            }
1114        })
1115        .ok_or_else(|| ShellError::IrEvalError {
1116            msg: format!(
1117                "block does not have an argument named `{}`",
1118                String::from_utf8_lossy(name)
1119            ),
1120            span: Some(span),
1121        })
1122        .and_then(|flag| expect_named_var_id(flag, span))
1123}
1124
1125fn expect_named_var_id(arg: &Flag, span: Span) -> Result<VarId, ShellError> {
1126    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1127        msg: format!(
1128            "block signature is missing var id for named arg `{}`",
1129            arg.long
1130        ),
1131        span: Some(span),
1132    })
1133}
1134
1135fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result<VarId, ShellError> {
1136    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1137        msg: format!(
1138            "block signature is missing var id for positional arg `{}`",
1139            arg.name
1140        ),
1141        span: Some(span),
1142    })
1143}
1144
1145/// Move arguments from the stack into variables for a custom command
1146fn gather_arguments(
1147    engine_state: &EngineState,
1148    block: &Block,
1149    caller_stack: &mut Stack,
1150    callee_stack: &mut Stack,
1151    args_base: usize,
1152    args_len: usize,
1153    call_head: Span,
1154) -> Result<(), ShellError> {
1155    let mut positional_iter = block
1156        .signature
1157        .required_positional
1158        .iter()
1159        .map(|p| (p, true))
1160        .chain(
1161            block
1162                .signature
1163                .optional_positional
1164                .iter()
1165                .map(|p| (p, false)),
1166        );
1167
1168    // Arguments that didn't get consumed by required/optional
1169    let mut rest = vec![];
1170
1171    // If we encounter a spread, all further positionals should go to rest
1172    let mut always_spread = false;
1173
1174    for arg in caller_stack.arguments.drain_args(args_base, args_len) {
1175        match arg {
1176            Argument::Positional { span, val, .. } => {
1177                // Don't check next positional arg if we encountered a spread previously
1178                let next = (!always_spread).then(|| positional_iter.next()).flatten();
1179                if let Some((positional_arg, required)) = next {
1180                    let var_id = expect_positional_var_id(positional_arg, span)?;
1181                    if required {
1182                        // By checking the type of the bound variable rather than converting the
1183                        // SyntaxShape here, we might be able to save some allocations and effort
1184                        let variable = engine_state.get_var(var_id);
1185                        check_type(&val, &variable.ty)?;
1186                    }
1187                    callee_stack.add_var(var_id, val);
1188                } else {
1189                    rest.push(val);
1190                }
1191            }
1192            Argument::Spread { vals, .. } => {
1193                if let Value::List { vals, .. } = vals {
1194                    rest.extend(vals);
1195                    // All further positional args should go to spread
1196                    always_spread = true;
1197                } else if let Value::Error { error, .. } = vals {
1198                    return Err(*error);
1199                } else {
1200                    return Err(ShellError::CannotSpreadAsList { span: vals.span() });
1201                }
1202            }
1203            Argument::Flag {
1204                data,
1205                name,
1206                short,
1207                span,
1208            } => {
1209                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1210                callee_stack.add_var(var_id, Value::bool(true, span))
1211            }
1212            Argument::Named {
1213                data,
1214                name,
1215                short,
1216                span,
1217                val,
1218                ..
1219            } => {
1220                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1221                callee_stack.add_var(var_id, val)
1222            }
1223            Argument::ParserInfo { .. } => (),
1224        }
1225    }
1226
1227    // Add the collected rest of the arguments if a spread argument exists
1228    if let Some(rest_arg) = &block.signature.rest_positional {
1229        let rest_span = rest.first().map(|v| v.span()).unwrap_or(call_head);
1230        let var_id = expect_positional_var_id(rest_arg, rest_span)?;
1231        callee_stack.add_var(var_id, Value::list(rest, rest_span));
1232    }
1233
1234    // Check for arguments that haven't yet been set and set them to their defaults
1235    for (positional_arg, _) in positional_iter {
1236        let var_id = expect_positional_var_id(positional_arg, call_head)?;
1237        callee_stack.add_var(
1238            var_id,
1239            positional_arg
1240                .default_value
1241                .clone()
1242                .unwrap_or(Value::nothing(call_head)),
1243        );
1244    }
1245
1246    for named_arg in &block.signature.named {
1247        if let Some(var_id) = named_arg.var_id {
1248            // For named arguments, we do this check by looking to see if the variable was set yet on
1249            // the stack. This assumes that the stack's variables was previously empty, but that's a
1250            // fair assumption for a brand new callee stack.
1251            if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) {
1252                let val = if named_arg.arg.is_none() {
1253                    Value::bool(false, call_head)
1254                } else if let Some(value) = &named_arg.default_value {
1255                    value.clone()
1256                } else {
1257                    Value::nothing(call_head)
1258                };
1259                callee_stack.add_var(var_id, val);
1260            }
1261        }
1262    }
1263
1264    Ok(())
1265}
1266
1267/// Type check helper. Produces `CantConvert` error if `val` is not compatible with `ty`.
1268fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> {
1269    match val {
1270        Value::Error { error, .. } => Err(*error.clone()),
1271        _ if val.is_subtype_of(ty) => Ok(()),
1272        _ => Err(ShellError::CantConvert {
1273            to_type: ty.to_string(),
1274            from_type: val.get_type().to_string(),
1275            span: val.span(),
1276            help: None,
1277        }),
1278    }
1279}
1280
1281/// Type check pipeline input against command's input types
1282fn check_input_types(
1283    input: &PipelineData,
1284    signature: &Signature,
1285    head: Span,
1286) -> Result<(), ShellError> {
1287    let io_types = &signature.input_output_types;
1288
1289    // If a command doesn't have any input/output types, then treat command input type as any
1290    if io_types.is_empty() {
1291        return Ok(());
1292    }
1293
1294    // If a command only has a nothing input type, then allow any input data
1295    if io_types.iter().all(|(intype, _)| intype == &Type::Nothing) {
1296        return Ok(());
1297    }
1298
1299    match input {
1300        // early return error directly if detected
1301        PipelineData::Value(Value::Error { error, .. }, ..) => return Err(*error.clone()),
1302        // bypass run-time typechecking for custom types
1303        PipelineData::Value(Value::Custom { .. }, ..) => return Ok(()),
1304        _ => (),
1305    }
1306
1307    // Check if the input type is compatible with *any* of the command's possible input types
1308    if io_types
1309        .iter()
1310        .any(|(command_type, _)| input.is_subtype_of(command_type))
1311    {
1312        return Ok(());
1313    }
1314
1315    let mut input_types = io_types
1316        .iter()
1317        .map(|(input, _)| input.to_string())
1318        .collect::<Vec<String>>();
1319
1320    let expected_string = match input_types.len() {
1321        0 => {
1322            return Err(ShellError::NushellFailed {
1323                msg: "Command input type strings is empty, despite being non-zero earlier"
1324                    .to_string(),
1325            });
1326        }
1327        1 => input_types.swap_remove(0),
1328        2 => input_types.join(" and "),
1329        _ => {
1330            input_types
1331                .last_mut()
1332                .expect("Vector with length >2 has no elements")
1333                .insert_str(0, "and ");
1334            input_types.join(", ")
1335        }
1336    };
1337
1338    match input {
1339        PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: head }),
1340        _ => Err(ShellError::OnlySupportsThisInputType {
1341            exp_input_type: expected_string,
1342            wrong_type: input.get_type().to_string(),
1343            dst_span: head,
1344            src_span: input.span().unwrap_or(Span::unknown()),
1345        }),
1346    }
1347}
1348
1349/// Get variable from [`Stack`] or [`EngineState`]
1350fn get_var(ctx: &EvalContext<'_>, var_id: VarId, span: Span) -> Result<Value, ShellError> {
1351    match var_id {
1352        // $env
1353        ENV_VARIABLE_ID => {
1354            let env_vars = ctx.stack.get_env_vars(ctx.engine_state);
1355            let env_columns = env_vars.keys();
1356            let env_values = env_vars.values();
1357
1358            let mut pairs = env_columns
1359                .map(|x| x.to_string())
1360                .zip(env_values.cloned())
1361                .collect::<Vec<(String, Value)>>();
1362
1363            pairs.sort_by(|a, b| a.0.cmp(&b.0));
1364
1365            Ok(Value::record(pairs.into_iter().collect(), span))
1366        }
1367        _ => ctx.stack.get_var(var_id, span).or_else(|err| {
1368            // $nu is handled by getting constant
1369            if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() {
1370                Ok(const_val.with_span(span))
1371            } else {
1372                Err(err)
1373            }
1374        }),
1375    }
1376}
1377
1378/// Get an environment variable, case-insensitively
1379fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> {
1380    // Read scopes in order
1381    for overlays in ctx
1382        .stack
1383        .env_vars
1384        .iter()
1385        .rev()
1386        .chain(std::iter::once(&ctx.engine_state.env_vars))
1387    {
1388        // Read overlays in order
1389        for overlay_name in ctx.stack.active_overlays.iter().rev() {
1390            let Some(map) = overlays.get(overlay_name) else {
1391                // Skip if overlay doesn't exist in this scope
1392                continue;
1393            };
1394            let hidden = ctx.stack.env_hidden.get(overlay_name);
1395            let is_hidden = |key: &str| hidden.is_some_and(|hidden| hidden.contains(key));
1396
1397            if let Some(val) = map
1398                // Check for exact match
1399                .get(key)
1400                // Skip when encountering an overlay where the key is hidden
1401                .filter(|_| !is_hidden(key))
1402                .or_else(|| {
1403                    // Check to see if it exists at all in the map, with a different case
1404                    map.iter().find_map(|(k, v)| {
1405                        // Again, skip something that's hidden
1406                        (k.eq_ignore_case(key) && !is_hidden(k)).then_some(v)
1407                    })
1408                })
1409            {
1410                return Some(val);
1411            }
1412        }
1413    }
1414    // Not found
1415    None
1416}
1417
1418/// Get the existing name of an environment variable, case-insensitively. This is used to implement
1419/// case preservation of environment variables, so that changing an environment variable that
1420/// already exists always uses the same case.
1421fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> {
1422    // Read scopes in order
1423    ctx.stack
1424        .env_vars
1425        .iter()
1426        .rev()
1427        .chain(std::iter::once(&ctx.engine_state.env_vars))
1428        .flat_map(|overlays| {
1429            // Read overlays in order
1430            ctx.stack
1431                .active_overlays
1432                .iter()
1433                .rev()
1434                .filter_map(|name| overlays.get(name))
1435        })
1436        .find_map(|map| {
1437            // Use the hashmap first to try to be faster?
1438            if map.contains_key(key) {
1439                Some(Cow::Borrowed(key))
1440            } else {
1441                map.keys().find(|k| k.eq_ignore_case(key)).map(|k| {
1442                    // it exists, but with a different case
1443                    Cow::Owned(k.to_owned())
1444                })
1445            }
1446        })
1447        // didn't exist.
1448        .unwrap_or(Cow::Borrowed(key))
1449}
1450
1451/// Helper to collect values into [`PipelineData`], preserving original span and metadata
1452///
1453/// The metadata is removed if it is the file data source, as that's just meant to mark streams.
1454fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> {
1455    let span = data.span().unwrap_or(fallback_span);
1456    let metadata = match data.metadata() {
1457        // Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
1458        // check where some input came from.
1459        Some(PipelineMetadata {
1460            data_source: DataSource::FilePath(_),
1461            content_type: None,
1462        }) => None,
1463        other => other,
1464    };
1465    let value = data.into_value(span)?;
1466    Ok(PipelineData::Value(value, metadata))
1467}
1468
1469/// Helper for drain behavior.
1470fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result<InstructionResult, ShellError> {
1471    use self::InstructionResult::*;
1472    match data {
1473        PipelineData::ByteStream(stream, ..) => {
1474            let span = stream.span();
1475            let callback_spans = stream.get_caller_spans().clone();
1476            if let Err(mut err) = stream.drain() {
1477                ctx.stack.set_last_error(&err);
1478                if callback_spans.is_empty() {
1479                    return Err(err);
1480                } else {
1481                    for s in callback_spans {
1482                        err = ShellError::EvalBlockWithInput {
1483                            span: s,
1484                            sources: vec![err],
1485                        }
1486                    }
1487                    return Err(err);
1488                }
1489            } else {
1490                ctx.stack.set_last_exit_code(0, span);
1491            }
1492        }
1493        PipelineData::ListStream(stream, ..) => {
1494            let callback_spans = stream.get_caller_spans().clone();
1495            if let Err(mut err) = stream.drain() {
1496                if callback_spans.is_empty() {
1497                    return Err(err);
1498                } else {
1499                    for s in callback_spans {
1500                        err = ShellError::EvalBlockWithInput {
1501                            span: s,
1502                            sources: vec![err],
1503                        }
1504                    }
1505                    return Err(err);
1506                }
1507            }
1508        }
1509        PipelineData::Value(..) | PipelineData::Empty => {}
1510    }
1511    Ok(Continue)
1512}
1513
1514enum RedirectionStream {
1515    Out,
1516    Err,
1517}
1518
1519/// Open a file for redirection
1520fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<File>, ShellError> {
1521    let path_expanded =
1522        expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
1523    let mut options = File::options();
1524    if append {
1525        options.append(true);
1526    } else {
1527        options.write(true).truncate(true);
1528    }
1529    let file = options
1530        .create(true)
1531        .open(&path_expanded)
1532        .map_err(|err| IoError::new(err, path.span(), path_expanded))?;
1533    Ok(Arc::new(file))
1534}
1535
1536/// Set up a [`Redirection`] from a [`RedirectMode`]
1537fn eval_redirection(
1538    ctx: &mut EvalContext<'_>,
1539    mode: &RedirectMode,
1540    span: Span,
1541    which: RedirectionStream,
1542) -> Result<Option<Redirection>, ShellError> {
1543    match mode {
1544        RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))),
1545        RedirectMode::PipeSeparate => Ok(Some(Redirection::Pipe(OutDest::PipeSeparate))),
1546        RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
1547        RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
1548        RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
1549        RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
1550        RedirectMode::File { file_num } => {
1551            let file = ctx
1552                .files
1553                .get(*file_num as usize)
1554                .cloned()
1555                .flatten()
1556                .ok_or_else(|| ShellError::IrEvalError {
1557                    msg: format!("Tried to redirect to file #{file_num}, but it is not open"),
1558                    span: Some(span),
1559                })?;
1560            Ok(Some(Redirection::File(file)))
1561        }
1562        RedirectMode::Caller => Ok(match which {
1563            RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe),
1564            RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe),
1565        }),
1566    }
1567}
1568
1569/// Do an `iterate` instruction. This can be called repeatedly to get more values from an iterable
1570fn eval_iterate(
1571    ctx: &mut EvalContext<'_>,
1572    dst: RegId,
1573    stream: RegId,
1574    end_index: usize,
1575) -> Result<InstructionResult, ShellError> {
1576    let mut data = ctx.take_reg(stream);
1577    if let PipelineData::ListStream(list_stream, _) = &mut data {
1578        // Modify the stream, taking one value off, and branching if it's empty
1579        if let Some(val) = list_stream.next_value() {
1580            ctx.put_reg(dst, val.into_pipeline_data());
1581            ctx.put_reg(stream, data); // put the stream back so it can be iterated on again
1582            Ok(InstructionResult::Continue)
1583        } else {
1584            ctx.put_reg(dst, PipelineData::Empty);
1585            Ok(InstructionResult::Branch(end_index))
1586        }
1587    } else {
1588        // Convert the PipelineData to an iterator, and wrap it in a ListStream so it can be
1589        // iterated on
1590        let metadata = data.metadata();
1591        let span = data.span().unwrap_or(Span::unknown());
1592        ctx.put_reg(
1593            stream,
1594            PipelineData::ListStream(
1595                ListStream::new(data.into_iter(), span, Signals::EMPTY),
1596                metadata,
1597            ),
1598        );
1599        eval_iterate(ctx, dst, stream, end_index)
1600    }
1601}
1602
1603/// Redirect environment from the callee stack to the caller stack
1604fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
1605    // TODO: make this more efficient
1606    // Grab all environment variables from the callee
1607    let caller_env_vars = caller_stack.get_env_var_names(engine_state);
1608
1609    // remove env vars that are present in the caller but not in the callee
1610    // (the callee hid them)
1611    for var in caller_env_vars.iter() {
1612        if !callee_stack.has_env_var(engine_state, var) {
1613            caller_stack.remove_env_var(engine_state, var);
1614        }
1615    }
1616
1617    // add new env vars from callee to caller
1618    for (var, value) in callee_stack.get_stack_env_vars() {
1619        caller_stack.add_env_var(var, value);
1620    }
1621
1622    // set config to callee config, to capture any updates to that
1623    caller_stack.config.clone_from(&callee_stack.config);
1624}