Skip to main content

nu_protocol/engine/
call.rs

1use crate::{
2    DeclId, FromValue, ShellError, Span, Value,
3    ast::{self, Expression},
4    ir,
5};
6
7use super::{EngineState, Stack, StateWorkingSet};
8
9/// This is a HACK to help [`Command`](super::Command) support both the old AST evaluator and the
10/// new IR evaluator at the same time. It should be removed once we are satisfied with the new
11/// evaluator.
12#[derive(Debug, Clone)]
13pub struct Call<'a> {
14    pub head: Span,
15    pub decl_id: DeclId,
16    pub inner: CallImpl<'a>,
17}
18
19#[derive(Debug, Clone)]
20pub enum CallImpl<'a> {
21    AstRef(&'a ast::Call),
22    AstBox(Box<ast::Call>),
23    IrRef(&'a ir::Call),
24    IrBox(Box<ir::Call>),
25}
26
27impl Call<'_> {
28    /// Returns a new AST call with the given span. This is often used by commands that need an
29    /// empty call to pass to a command. It's not easily possible to add anything to this.
30    pub fn new(span: Span) -> Self {
31        // this is using the boxed variant, which isn't so efficient... but this is only temporary
32        // anyway.
33        Call {
34            head: span,
35            decl_id: DeclId::new(0),
36            inner: CallImpl::AstBox(Box::new(ast::Call::new(span))),
37        }
38    }
39
40    /// Convert the `Call` from any lifetime into `'static`, by cloning the data within onto the
41    /// heap.
42    pub fn to_owned(&self) -> Call<'static> {
43        Call {
44            head: self.head,
45            decl_id: self.decl_id,
46            inner: self.inner.to_owned(),
47        }
48    }
49
50    /// Check if a boolean flag is set at const-eval time.
51    pub fn has_flag_const(
52        &self,
53        working_set: &StateWorkingSet,
54        stack: &Stack,
55        flag_name: &str,
56    ) -> Result<bool, ShellError> {
57        match &self.inner {
58            CallImpl::AstRef(call) => call.has_flag_const(working_set, flag_name),
59            CallImpl::AstBox(call) => call.has_flag_const(working_set, flag_name),
60            CallImpl::IrRef(call) => ir_has_flag_const(call, stack, flag_name),
61            CallImpl::IrBox(call) => ir_has_flag_const(call, stack, flag_name),
62        }
63    }
64
65    /// Get a typed named argument at const-eval time.
66    pub fn get_flag_const<T: FromValue>(
67        &self,
68        working_set: &StateWorkingSet,
69        stack: &Stack,
70        name: &str,
71    ) -> Result<Option<T>, ShellError> {
72        match &self.inner {
73            CallImpl::AstRef(call) => call.get_flag_const(working_set, name),
74            CallImpl::AstBox(call) => call.get_flag_const(working_set, name),
75            CallImpl::IrRef(call) => ir_get_flag_const(call, stack, name),
76            CallImpl::IrBox(call) => ir_get_flag_const(call, stack, name),
77        }
78    }
79
80    /// Get a required positional argument at const-eval time.
81    pub fn req_const<T: FromValue>(
82        &self,
83        working_set: &StateWorkingSet,
84        stack: &Stack,
85        pos: usize,
86    ) -> Result<T, ShellError> {
87        match &self.inner {
88            CallImpl::AstRef(call) => call.req_const(working_set, pos),
89            CallImpl::AstBox(call) => call.req_const(working_set, pos),
90            CallImpl::IrRef(call) => ir_req_const(call, stack, self.head, pos),
91            CallImpl::IrBox(call) => ir_req_const(call, stack, self.head, pos),
92        }
93    }
94
95    /// Get the rest of the positional arguments at const-eval time.
96    pub fn rest_const<T: FromValue>(
97        &self,
98        working_set: &StateWorkingSet,
99        stack: &Stack,
100        starting_pos: usize,
101    ) -> Result<Vec<T>, ShellError> {
102        match &self.inner {
103            CallImpl::AstRef(call) => call.rest_const(working_set, starting_pos),
104            CallImpl::AstBox(call) => call.rest_const(working_set, starting_pos),
105            CallImpl::IrRef(call) => ir_rest_const(call, stack, starting_pos),
106            CallImpl::IrBox(call) => ir_rest_const(call, stack, starting_pos),
107        }
108    }
109
110    /// Returns a span covering the call's arguments.
111    pub fn arguments_span(&self) -> Span {
112        match &self.inner {
113            CallImpl::AstRef(call) => call.arguments_span(),
114            CallImpl::AstBox(call) => call.arguments_span(),
115            CallImpl::IrRef(call) => call.arguments_span(),
116            CallImpl::IrBox(call) => call.arguments_span(),
117        }
118    }
119
120    /// Returns a span covering the whole call.
121    pub fn span(&self) -> Span {
122        match &self.inner {
123            CallImpl::AstRef(call) => call.span(),
124            CallImpl::AstBox(call) => call.span(),
125            CallImpl::IrRef(call) => call.span(),
126            CallImpl::IrBox(call) => call.span(),
127        }
128    }
129
130    /// Get a parser info argument by name.
131    pub fn get_parser_info<'a>(&'a self, stack: &'a Stack, name: &str) -> Option<&'a Expression> {
132        match &self.inner {
133            CallImpl::AstRef(call) => call.get_parser_info(name),
134            CallImpl::AstBox(call) => call.get_parser_info(name),
135            CallImpl::IrRef(call) => call.get_parser_info(stack, name),
136            CallImpl::IrBox(call) => call.get_parser_info(stack, name),
137        }
138    }
139
140    /// Evaluator-agnostic implementation of `rest_iter_flattened()`. Evaluates or gets all of the
141    /// positional and spread arguments, flattens spreads, and then returns one list of values.
142    pub fn rest_iter_flattened(
143        &self,
144        engine_state: &EngineState,
145        stack: &mut Stack,
146        eval_expression: fn(
147            &EngineState,
148            &mut Stack,
149            &ast::Expression,
150        ) -> Result<Value, ShellError>,
151        starting_pos: usize,
152    ) -> Result<Vec<Value>, ShellError> {
153        fn by_ast(
154            call: &ast::Call,
155            engine_state: &EngineState,
156            stack: &mut Stack,
157            eval_expression: fn(
158                &EngineState,
159                &mut Stack,
160                &ast::Expression,
161            ) -> Result<Value, ShellError>,
162            starting_pos: usize,
163        ) -> Result<Vec<Value>, ShellError> {
164            call.rest_iter_flattened(starting_pos, |expr| {
165                eval_expression(engine_state, stack, expr)
166            })
167        }
168
169        fn by_ir(
170            call: &ir::Call,
171            stack: &Stack,
172            starting_pos: usize,
173        ) -> Result<Vec<Value>, ShellError> {
174            call.rest_iter_flattened(stack, starting_pos)
175        }
176
177        match &self.inner {
178            CallImpl::AstRef(call) => {
179                by_ast(call, engine_state, stack, eval_expression, starting_pos)
180            }
181            CallImpl::AstBox(call) => {
182                by_ast(call, engine_state, stack, eval_expression, starting_pos)
183            }
184            CallImpl::IrRef(call) => by_ir(call, stack, starting_pos),
185            CallImpl::IrBox(call) => by_ir(call, stack, starting_pos),
186        }
187    }
188
189    /// Get the original AST expression for a positional argument.
190    ///
191    /// Under IR this is usually `None`: argument AST is not retained for builtins.
192    pub fn positional_nth<'a>(&'a self, stack: &'a Stack, index: usize) -> Option<&'a Expression> {
193        match &self.inner {
194            CallImpl::AstRef(call) => call.positional_iter().nth(index),
195            CallImpl::AstBox(call) => call.positional_iter().nth(index),
196            CallImpl::IrRef(call) => call.positional_ast(stack, index).map(|arc| arc.as_ref()),
197            CallImpl::IrBox(call) => call.positional_ast(stack, index).map(|arc| arc.as_ref()),
198        }
199    }
200}
201
202impl CallImpl<'_> {
203    pub fn to_owned(&self) -> CallImpl<'static> {
204        match self {
205            CallImpl::AstRef(call) => CallImpl::AstBox(Box::new((*call).clone())),
206            CallImpl::AstBox(call) => CallImpl::AstBox(call.clone()),
207            CallImpl::IrRef(call) => CallImpl::IrBox(Box::new((*call).clone())),
208            CallImpl::IrBox(call) => CallImpl::IrBox(call.clone()),
209        }
210    }
211}
212
213fn ir_has_flag_const(call: &ir::Call, stack: &Stack, flag_name: &str) -> Result<bool, ShellError> {
214    Ok(call
215        .named_iter(stack)
216        .find(|(name, _)| name.item == flag_name)
217        .is_some_and(|(_, value)| !matches!(value, Some(Value::Bool { val: false, .. }))))
218}
219
220fn ir_get_flag_const<T: FromValue>(
221    call: &ir::Call,
222    stack: &Stack,
223    name: &str,
224) -> Result<Option<T>, ShellError> {
225    if let Some(val) = call.get_named_arg(stack, name) {
226        T::from_value(val.clone()).map(Some)
227    } else {
228        Ok(None)
229    }
230}
231
232fn ir_req_const<T: FromValue>(
233    call: &ir::Call,
234    stack: &Stack,
235    head: Span,
236    pos: usize,
237) -> Result<T, ShellError> {
238    let maybe_val = call.positional_nth(stack, pos).cloned();
239    let val = maybe_val.ok_or_else(|| {
240        let max_idx = call.positional_len(stack).checked_sub(1);
241        match max_idx {
242            None => ShellError::AccessEmptyContent { span: head },
243            Some(max_idx) => ShellError::AccessBeyondEnd {
244                max_idx,
245                span: head,
246            },
247        }
248    })?;
249    T::from_value(val)
250}
251
252fn ir_rest_const<T: FromValue>(
253    call: &ir::Call,
254    stack: &Stack,
255    starting_pos: usize,
256) -> Result<Vec<T>, ShellError> {
257    call.rest_iter_flattened(stack, starting_pos)?
258        .into_iter()
259        .map(T::from_value)
260        .collect()
261}
262
263impl<'a> From<&'a ast::Call> for Call<'a> {
264    fn from(call: &'a ast::Call) -> Self {
265        Call {
266            head: call.head,
267            decl_id: call.decl_id,
268            inner: CallImpl::AstRef(call),
269        }
270    }
271}
272
273impl<'a> From<&'a ir::Call> for Call<'a> {
274    fn from(call: &'a ir::Call) -> Self {
275        Call {
276            head: call.head,
277            decl_id: call.decl_id,
278            inner: CallImpl::IrRef(call),
279        }
280    }
281}