diff --git a/crates/nu-protocol/src/engine/call.rs b/crates/nu-protocol/src/engine/call.rs new file mode 100644 index 0000000000..cfaa7d2cca --- /dev/null +++ b/crates/nu-protocol/src/engine/call.rs @@ -0,0 +1,16 @@ +use crate::{ast, ir, 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 +/// evaluator. +#[derive(Debug, Clone)] +pub struct Call<'a> { + pub head: Span, + inner: CallImpl<'a>, +} + +#[derive(Debug, Clone)] +enum CallImpl<'a> { + Ast(&'a ast::Call), + Ir(ir::Call<'a>), +} diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 814049a9ce..a93ed600cb 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,4 +1,5 @@ mod cached_file; +mod call; mod call_info; mod capture_block; mod command; @@ -15,6 +16,7 @@ mod variable; pub use cached_file::CachedFile; +pub use call::*; pub use call_info::*; pub use capture_block::*; pub use command::*; diff --git a/crates/nu-protocol/src/ir/call.rs b/crates/nu-protocol/src/ir/call.rs new file mode 100644 index 0000000000..0bec7acc44 --- /dev/null +++ b/crates/nu-protocol/src/ir/call.rs @@ -0,0 +1,148 @@ +use crate::{PipelineData, RegId, ShellError, Span, Value}; + +use super::{CallArg, Instruction}; + +/// 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, +} + +#[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))), + }) + } + + /// The span encompassing the arguments + /// + /// If there are no arguments the span covers where the first argument would exist + /// + /// 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 { + 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; + + Span::new(start, end) + } + + pub fn named_len(&self) -> usize { + self.call_args() + .filter(|arg| matches!(arg, CallArg::Named(..) | CallArg::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 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 positional_len(&self) -> usize { + self.call_args() + .filter(|arg| matches!(arg, CallArg::Positional(..))) + .count() + } + + pub fn positional_iter(&self) -> impl Iterator { + self.arg_refs().filter_map(|arg| match arg { + ArgRef::Positional(value) => Some(value), + _ => None, + }) + } + + pub fn positional_nth(&self, index: usize) -> Option<&Value> { + self.positional_iter().nth(index) + } + + pub fn rest_iter(&self) -> impl Iterator { + self.arg_refs().filter_map(|arg| match arg { + ArgRef::Spread(value) => Some(value), + _ => None, + }) + } + + pub fn rest_iter_flattened(&self) -> Result, ShellError> { + let mut acc = vec![]; + for rest_val in self.rest_iter() { + match rest_val { + Value::List { vals, .. } => acc.extend(vals.iter().cloned()), + Value::Error { error, .. } => return Err(ShellError::clone(error)), + _ => { + return Err(ShellError::CannotSpreadAsList { + span: rest_val.span(), + }) + } + } + } + Ok(acc) + } + + pub fn span(&self) -> Span { + let mut span = *self.head; + for arg in self.arg_refs() { + span.end = span.end.max(arg.span().end); + } + span + } +} diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index 76b095b35c..c77061ee15 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -6,7 +6,10 @@ use crate::{ use serde::{Deserialize, Serialize}; +mod call; mod display; + +pub use call::Call; pub use display::{FmtInstruction, FmtIrBlock}; #[derive(Debug, Clone, Serialize, Deserialize)]