diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index 0cbea61370..da0cb24b6a 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -191,15 +191,15 @@ impl CallExt for Call { } } -impl CallExt for ir::Call<'_> { +impl CallExt for ir::Call { fn has_flag( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, flag_name: &str, ) -> Result { Ok(self - .named_iter() + .named_iter(stack) .find(|(name, _)| *name == flag_name) .is_some()) } @@ -207,10 +207,10 @@ impl CallExt for ir::Call<'_> { fn get_flag( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, name: &str, ) -> Result, ShellError> { - if let Some(val) = self.get_named_arg(name) { + if let Some(val) = self.get_named_arg(stack, name) { T::from_value(val.clone()).map(Some) } else { Ok(None) @@ -220,10 +220,10 @@ impl CallExt for ir::Call<'_> { fn rest( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, starting_pos: usize, ) -> Result, ShellError> { - self.rest_iter_flattened(starting_pos)? + self.rest_iter_flattened(stack, starting_pos)? .into_iter() .map(T::from_value) .collect() @@ -232,10 +232,10 @@ impl CallExt for ir::Call<'_> { fn opt( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, pos: usize, ) -> Result, ShellError> { - self.positional_iter() + self.positional_iter(stack) .nth(pos) .cloned() .map(T::from_value) @@ -258,11 +258,11 @@ impl CallExt for ir::Call<'_> { ) -> Result { if let Some(val) = self.opt(engine_state, stack, pos)? { Ok(val) - } else if self.positional_len() == 0 { + } else if self.positional_len(stack) == 0 { Err(ShellError::AccessEmptyContent { span: *self.head }) } else { Err(ShellError::AccessBeyondEnd { - max_idx: self.positional_len() - 1, + max_idx: self.positional_len(stack) - 1, span: *self.head, }) } @@ -278,6 +278,19 @@ impl CallExt for ir::Call<'_> { } } +/// Creates a `CallExt` trait object from the `engine::Call` reference, for easier implementation +/// +/// XXX: this doesn't work +#[inline(always)] +const fn proxy(call: &engine::Call<'_>) -> &dyn CallExt { + match &call.inner { + CallImpl::AstRef(ast_call) => ast_call, + CallImpl::AstArc(ast_call) => &ast_call, + CallImpl::IrRef(ir_call) => ir_call, + CallImpl::IrArc(ir_call) => &ir_call, + } +} + impl CallExt for engine::Call<'_> { fn has_flag( &self, @@ -285,10 +298,7 @@ impl CallExt for engine::Call<'_> { stack: &mut Stack, flag_name: &str, ) -> Result { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.has_flag(engine_state, stack, flag_name), - CallImpl::Ir(ir_call) => ir_call.has_flag(engine_state, stack, flag_name), - } + proxy(self).has_flag(engine_state, stack, flag_name) } fn get_flag( @@ -297,10 +307,7 @@ impl CallExt for engine::Call<'_> { stack: &mut Stack, name: &str, ) -> Result, ShellError> { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.get_flag(engine_state, stack, name), - CallImpl::Ir(ir_call) => ir_call.get_flag(engine_state, stack, name), - } + proxy(self).get_flag(engine_state, stack, name) } fn rest( @@ -309,10 +316,7 @@ impl CallExt for engine::Call<'_> { stack: &mut Stack, starting_pos: usize, ) -> Result, ShellError> { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.rest(engine_state, stack, starting_pos), - CallImpl::Ir(ir_call) => ir_call.rest(engine_state, stack, starting_pos), - } + proxy(self).rest(engine_state, stack, starting_pos) } fn opt( @@ -321,10 +325,7 @@ impl CallExt for engine::Call<'_> { stack: &mut Stack, pos: usize, ) -> Result, ShellError> { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.opt(engine_state, stack, pos), - CallImpl::Ir(ir_call) => ir_call.opt(engine_state, stack, pos), - } + proxy(self).opt(engine_state, stack, pos) } fn opt_const( @@ -332,10 +333,7 @@ impl CallExt for engine::Call<'_> { working_set: &StateWorkingSet, pos: usize, ) -> Result, ShellError> { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.opt_const(working_set, pos), - CallImpl::Ir(ir_call) => ir_call.opt_const(working_set, pos), - } + proxy(self).opt_const(working_set, pos) } fn req( @@ -344,10 +342,7 @@ impl CallExt for engine::Call<'_> { stack: &mut Stack, pos: usize, ) -> Result { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.req(engine_state, stack, pos), - CallImpl::Ir(ir_call) => ir_call.req(engine_state, stack, pos), - } + proxy(self).req(engine_state, stack, pos) } fn req_parser_info( @@ -356,9 +351,6 @@ impl CallExt for engine::Call<'_> { stack: &mut Stack, name: &str, ) -> Result { - match &self.inner { - CallImpl::Ast(ast_call) => ast_call.req_parser_info(engine_state, stack, name), - CallImpl::Ir(ir_call) => ir_call.req_parser_info(engine_state, stack, name), - } + proxy(self).req_parser_info(engine_state, stack, name) } } diff --git a/crates/nu-protocol/src/engine/argument.rs b/crates/nu-protocol/src/engine/argument.rs new file mode 100644 index 0000000000..fcdfa60708 --- /dev/null +++ b/crates/nu-protocol/src/engine/argument.rs @@ -0,0 +1,86 @@ +use crate::{Span, Value}; + +/// Represents a fully evaluated argument to a call. +#[derive(Debug, Clone)] +pub enum Argument { + /// A positional argument + Positional { + val: Value, + span: Span, + }, + Spread { + vals: Value, + span: Span, + }, + /// A named argument with no value, e.g. `--flag` + Flag { + name: Box, + span: Span, + }, + /// A named argument with a value, e.g. `--flag value` or `--flag=` + Named { + name: Box, + span: Span, + val: Value, + }, +} + +impl Argument { + /// The span encompassing the argument's usage within the call, distinct from the span of the + /// actual value of the argument. + pub fn span(&self) -> Span { + match self { + Argument::Positional { span, .. } => *span, + Argument::Spread { span, .. } => *span, + Argument::Flag { span, .. } => *span, + Argument::Named { span, .. } => *span, + } + } +} + +/// Stores the argument context for calls in IR evaluation. +#[derive(Debug, Clone)] +pub struct ArgumentStack { + arguments: Vec, +} + +impl ArgumentStack { + /// Create a new, empty argument stack. + pub const fn new() -> Self { + ArgumentStack { arguments: vec![] } + } + + /// Returns the index of the end of the argument stack. Call and save this before adding + /// arguments. + pub fn get_base(&self) -> usize { + self.arguments.len() + } + + /// Calculates the number of arguments past the given [previously retrieved](.get_base) base + /// pointer. + pub fn get_len(&self, base: usize) -> usize { + self.arguments.len().checked_sub(base).unwrap_or_else(|| { + panic!( + "base ({}) is beyond the end of the arguments stack ({})", + base, + self.arguments.len() + ); + }) + } + + /// Push an argument onto the end of the argument stack. + pub fn push(&mut self, argument: Argument) { + self.arguments.push(argument); + } + + /// Clear all of the arguments after the given base index, to prepare for the next frame. + pub fn leave_frame(&mut self, base: usize) { + self.arguments.truncate(base); + } + + /// Get arguments for the frame based on the given [`base`](`.get_base()`) and + /// [`len`](`.get_len()`) parameters. + pub fn get_args(&self, base: usize, len: usize) -> &[Argument] { + &self.arguments[base..(base + len)] + } +} diff --git a/crates/nu-protocol/src/engine/call.rs b/crates/nu-protocol/src/engine/call.rs index 2d889d46a2..e62b7210ef 100644 --- a/crates/nu-protocol/src/engine/call.rs +++ b/crates/nu-protocol/src/engine/call.rs @@ -1,8 +1,6 @@ -use crate::{ - ast, - ir::{self, Instruction}, - DeclId, Span, -}; +use std::sync::Arc; + +use crate::{ast, ir, DeclId, Span}; /// This is a HACK to help [`Command`](super::Command) support both the old AST evaluator and the /// new IR evaluator at the same time. It should be removed once we are satisfied with the new @@ -16,8 +14,31 @@ pub struct Call<'a> { #[derive(Debug, Clone)] pub enum CallImpl<'a> { - Ast(&'a ast::Call), - Ir(ir::Call<'a>), + AstRef(&'a ast::Call), + AstArc(Arc), + IrRef(&'a ir::Call), + IrArc(Arc), +} + +impl Call<'_> { + pub fn to_owned(&self) -> Call<'static> { + Call { + head: self.head, + decl_id: self.decl_id, + inner: self.inner.to_owned(), + } + } +} + +impl CallImpl<'_> { + pub fn to_owned(&self) -> CallImpl<'static> { + match self { + CallImpl::AstRef(call) => CallImpl::AstArc(Arc::new((*call).clone())), + CallImpl::AstArc(call) => CallImpl::AstArc(call.clone()), + CallImpl::IrRef(call) => CallImpl::IrArc(Arc::new((*call).clone())), + CallImpl::IrArc(call) => CallImpl::IrArc(call.clone()), + } + } } impl<'a> From<&'a ast::Call> for Call<'a> { @@ -25,20 +46,17 @@ impl<'a> From<&'a ast::Call> for Call<'a> { Call { head: call.head, decl_id: call.decl_id, - inner: CallImpl::Ast(call), + inner: CallImpl::AstRef(call), } } } -impl<'a> From> for Call<'a> { - fn from(call: ir::Call<'a>) -> Self { - let Instruction::Call { decl_id, .. } = *call.instruction else { - panic!("ir::Call instruction was not Call: {:?}", call.instruction); - }; +impl<'a> From<&'a ir::Call> for Call<'a> { + fn from(call: &'a ir::Call) -> Self { Call { - head: *call.head, - decl_id, - inner: CallImpl::Ir(call), + head: call.head, + decl_id: call.decl_id, + inner: CallImpl::IrRef(call), } } } diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index a93ed600cb..201fd53394 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,3 +1,4 @@ +mod argument; mod cached_file; mod call; mod call_info; @@ -16,6 +17,7 @@ mod variable; pub use cached_file::CachedFile; +pub use argument::*; pub use call::*; pub use call_info::*; pub use capture_block::*; diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 8c3f96e827..aaf33dde88 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -11,7 +11,7 @@ use std::{ sync::Arc, }; -use super::RegisterBufCache; +use super::{ArgumentStack, RegisterBufCache}; /// Environment variables per overlay pub type EnvVars = HashMap>; @@ -45,6 +45,8 @@ pub struct Stack { pub active_overlays: Vec, /// Cached register buffers for IR evaluation pub register_buf_cache: RegisterBufCache, + /// Argument stack for IR evaluation + pub argument_stack: ArgumentStack, pub recursion_count: u64, pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) @@ -73,6 +75,7 @@ impl Stack { env_hidden: HashMap::new(), active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], register_buf_cache: RegisterBufCache::new(), + argument_stack: ArgumentStack::new(), recursion_count: 0, parent_stack: None, parent_deletions: vec![], @@ -91,6 +94,7 @@ impl Stack { env_hidden: parent.env_hidden.clone(), active_overlays: parent.active_overlays.clone(), register_buf_cache: RegisterBufCache::new(), + argument_stack: ArgumentStack::new(), recursion_count: parent.recursion_count, vars: vec![], parent_deletions: vec![], @@ -261,6 +265,7 @@ impl Stack { env_hidden: self.env_hidden.clone(), active_overlays: self.active_overlays.clone(), register_buf_cache: RegisterBufCache::new(), + argument_stack: ArgumentStack::new(), recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], @@ -292,6 +297,7 @@ impl Stack { env_hidden: self.env_hidden.clone(), active_overlays: self.active_overlays.clone(), register_buf_cache: RegisterBufCache::new(), + argument_stack: ArgumentStack::new(), recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], diff --git a/crates/nu-protocol/src/ir/call.rs b/crates/nu-protocol/src/ir/call.rs index b65378056d..2f96843156 100644 --- a/crates/nu-protocol/src/ir/call.rs +++ b/crates/nu-protocol/src/ir/call.rs @@ -1,61 +1,28 @@ -use crate::{PipelineData, RegId, ShellError, Span, Value}; - -use super::{CallArg, Instruction}; +use crate::{ + engine::{Argument, Stack}, + ShellError, Span, Value, +}; /// Contains the information for a call being made to a declared command. #[derive(Debug, Clone)] -pub struct Call<'a> { - pub registers: &'a [PipelineData], - pub call_args: &'a [CallArg], - pub instruction: &'a Instruction, - pub head: &'a Span, +pub struct Call { + /// The declaration ID of the command to be invoked. + pub decl_id: usize, + /// The span encompassing the command name, before the arguments. + pub head: Span, + /// The base index of the arguments for this call within the + /// [argument stack](crate::engine::ArgumentStack). + pub args_base: usize, + /// The number of [`Argument`]s for the call. Note that this just counts the number of + /// `Argument` entries on the stack, and has nothing to do with the actual number of positional + /// or spread arguments. + pub args_len: usize, } -#[derive(Clone, Copy)] -enum ArgRef<'a> { - Positional(&'a Value), - Named(&'a str, Option<&'a Value>), - Spread(&'a Value), -} - -impl<'a> ArgRef<'a> { - fn span(self) -> Span { - match self { - ArgRef::Positional(v) => v.span(), - // FIXME: Named without value needs a span! - ArgRef::Named(_, v) => v.map(|v| v.span()).unwrap_or(Span::unknown()), - ArgRef::Spread(v) => v.span(), - } - } -} - -impl<'a> Call<'a> { - fn call_args(&self) -> impl Iterator { - let Instruction::Call { - args_start, - args_len, - .. - } = *self.instruction - else { - panic!("self.instruction is not Call") - }; - self.call_args[args_start..(args_start + args_len)].iter() - } - - fn get_reg_val(&self, reg_id: RegId) -> &Value { - match &self.registers[reg_id.0 as usize] { - PipelineData::Value(value, _) => value, - other => panic!("value in register {reg_id} for argument was not collected: {other:?}"), - } - } - - fn arg_refs(&self) -> impl Iterator> { - self.call_args().map(|arg| match arg { - CallArg::Positional(r) => ArgRef::Positional(self.get_reg_val(*r)), - CallArg::Spread(r) => ArgRef::Spread(self.get_reg_val(*r)), - CallArg::Flag(name) => ArgRef::Named(&name, None), - CallArg::Named(name, r) => ArgRef::Named(&name, Some(self.get_reg_val(*r))), - }) +impl Call { + /// Get the arguments for this call from the arguments stack. + pub fn arguments<'a>(&self, stack: &'a Stack) -> &'a [Argument] { + stack.argument_stack.get_args(self.args_base, self.args_len) } /// The span encompassing the arguments @@ -64,72 +31,87 @@ impl<'a> Call<'a> { /// /// If there are one or more arguments the span encompasses the start of the first argument to /// end of the last argument - pub fn arguments_span(&self) -> Span { + pub fn arguments_span(&self, stack: &Stack) -> Span { let past = self.head.past(); - let start = self - .arg_refs() - .next() - .map(|a| a.span()) - .unwrap_or(past) - .start; - let end = self.arg_refs().last().map(|a| a.span()).unwrap_or(past).end; + let args = self.arguments(stack); + + let start = args.first().map(|a| a.span()).unwrap_or(past).start; + let end = args.last().map(|a| a.span()).unwrap_or(past).end; Span::new(start, end) } - pub fn named_len(&self) -> usize { - self.call_args() - .filter(|arg| matches!(arg, CallArg::Named(..) | CallArg::Flag(..))) + pub fn named_len(&self, stack: &Stack) -> usize { + self.arguments(stack) + .iter() + .filter(|arg| matches!(arg, Argument::Named { .. } | Argument::Flag { .. })) .count() } - pub fn named_iter(&self) -> impl Iterator)> { - self.arg_refs().filter_map(|arg| match arg { - ArgRef::Named(name, value) => Some((name, value)), - _ => None, - }) + pub fn named_iter<'a>( + &self, + stack: &'a Stack, + ) -> impl Iterator)> + 'a { + self.arguments(stack).iter().filter_map( + |arg: &Argument| -> Option<(&str, Option<&Value>)> { + match arg { + Argument::Flag { name, .. } => Some((&name, None)), + Argument::Named { name, val, .. } => Some((&name, Some(val))), + _ => None, + } + }, + ) } - pub fn get_named_arg(&self, flag_name: &str) -> Option<&Value> { - self.arg_refs().find_map(|arg| match arg { - ArgRef::Named(name, value) if name == flag_name => value, - _ => None, - }) + pub fn get_named_arg<'a>(&self, stack: &'a Stack, flag_name: &str) -> Option<&'a Value> { + self.named_iter(stack) + .find_map(|(name, val)| (name == flag_name).then_some(val)) + .flatten() } - pub fn positional_len(&self) -> usize { - self.call_args() - .filter(|arg| matches!(arg, CallArg::Positional(..))) + pub fn positional_len(&self, stack: &Stack) -> usize { + self.arguments(stack) + .iter() + .filter(|arg| matches!(arg, Argument::Positional { .. })) .count() } - pub fn positional_iter(&self) -> impl Iterator { - self.arg_refs().filter_map(|arg| match arg { - ArgRef::Positional(value) => Some(value), + pub fn positional_iter<'a>(&self, stack: &'a Stack) -> impl Iterator { + self.arguments(stack).iter().filter_map(|arg| match arg { + Argument::Positional { val, .. } => Some(val), _ => None, }) } - pub fn positional_nth(&self, index: usize) -> Option<&Value> { - self.positional_iter().nth(index) + pub fn positional_nth<'a>(&self, stack: &'a Stack, index: usize) -> Option<&'a Value> { + self.positional_iter(stack).nth(index) } /// Returns every argument to the rest parameter, as well as whether each argument /// is spread or a normal positional argument (true for spread, false for normal) - pub fn rest_iter(&self, start: usize) -> impl Iterator { - self.arg_refs() + pub fn rest_iter<'a>( + &self, + stack: &'a Stack, + start: usize, + ) -> impl Iterator + 'a { + self.arguments(stack) + .iter() .filter_map(|arg| match arg { - ArgRef::Positional(value) => Some((value, false)), - ArgRef::Spread(value) => Some((value, true)), + Argument::Positional { val, .. } => Some((val, false)), + Argument::Spread { vals, .. } => Some((vals, true)), _ => None, }) .skip(start) } - pub fn rest_iter_flattened(&self, start: usize) -> Result, ShellError> { + pub fn rest_iter_flattened( + &self, + stack: &Stack, + start: usize, + ) -> Result, ShellError> { let mut acc = vec![]; - for (rest_val, spread) in self.rest_iter(start) { + for (rest_val, spread) in self.rest_iter(stack, start) { if spread { match rest_val { Value::List { vals, .. } => acc.extend(vals.iter().cloned()), @@ -147,9 +129,9 @@ impl<'a> Call<'a> { Ok(acc) } - pub fn span(&self) -> Span { - let mut span = *self.head; - for arg in self.arg_refs() { + pub fn span(&self, stack: &Stack) -> Span { + let mut span = self.head; + for arg in self.arguments(stack).iter() { span.end = span.end.max(arg.span().end); } span