nu_engine/
eval_ir.rs

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