nu_protocol/ast/
expression.rs

1use crate::{
2    ast::{Argument, Block, Expr, ExternalArgument, ImportPattern, MatchPattern, RecordItem},
3    engine::StateWorkingSet,
4    BlockId, DeclId, GetSpan, Signature, Span, SpanId, Type, VarId, IN_VARIABLE_ID,
5};
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8
9use super::ListItem;
10
11/// Wrapper around [`Expr`]
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct Expression {
14    pub expr: Expr,
15    pub span: Span,
16    pub span_id: SpanId,
17    pub ty: Type,
18    pub custom_completion: Option<DeclId>,
19}
20
21impl Expression {
22    pub fn garbage(working_set: &mut StateWorkingSet, span: Span) -> Expression {
23        let span_id = working_set.add_span(span);
24        Expression {
25            expr: Expr::Garbage,
26            span,
27            span_id,
28            ty: Type::Any,
29            custom_completion: None,
30        }
31    }
32
33    pub fn precedence(&self) -> u8 {
34        match &self.expr {
35            Expr::Operator(operator) => operator.precedence(),
36            _ => 0,
37        }
38    }
39
40    pub fn as_block(&self) -> Option<BlockId> {
41        match self.expr {
42            Expr::Block(block_id) => Some(block_id),
43            Expr::Closure(block_id) => Some(block_id),
44            _ => None,
45        }
46    }
47
48    pub fn as_row_condition_block(&self) -> Option<BlockId> {
49        match self.expr {
50            Expr::RowCondition(block_id) => Some(block_id),
51            _ => None,
52        }
53    }
54
55    pub fn as_match_block(&self) -> Option<&[(MatchPattern, Expression)]> {
56        match &self.expr {
57            Expr::MatchBlock(matches) => Some(matches),
58            _ => None,
59        }
60    }
61
62    pub fn as_signature(&self) -> Option<Box<Signature>> {
63        match &self.expr {
64            Expr::Signature(sig) => Some(sig.clone()),
65            _ => None,
66        }
67    }
68
69    pub fn as_keyword(&self) -> Option<&Expression> {
70        match &self.expr {
71            Expr::Keyword(kw) => Some(&kw.expr),
72            _ => None,
73        }
74    }
75
76    pub fn as_var(&self) -> Option<VarId> {
77        match self.expr {
78            Expr::Var(var_id) => Some(var_id),
79            Expr::VarDecl(var_id) => Some(var_id),
80            _ => None,
81        }
82    }
83
84    pub fn as_string(&self) -> Option<String> {
85        match &self.expr {
86            Expr::String(string) => Some(string.clone()),
87            _ => None,
88        }
89    }
90
91    pub fn as_filepath(&self) -> Option<(String, bool)> {
92        match &self.expr {
93            Expr::Filepath(string, quoted) => Some((string.clone(), *quoted)),
94            _ => None,
95        }
96    }
97
98    pub fn as_import_pattern(&self) -> Option<ImportPattern> {
99        match &self.expr {
100            Expr::ImportPattern(pattern) => Some(*pattern.clone()),
101            _ => None,
102        }
103    }
104
105    pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
106        match &self.expr {
107            Expr::AttributeBlock(ab) => ab.item.has_in_variable(working_set),
108            Expr::BinaryOp(left, _, right) => {
109                left.has_in_variable(working_set) || right.has_in_variable(working_set)
110            }
111            Expr::UnaryNot(expr) => expr.has_in_variable(working_set),
112            Expr::Block(block_id) | Expr::Closure(block_id) => {
113                let block = working_set.get_block(*block_id);
114                block
115                    .captures
116                    .iter()
117                    .any(|(var_id, _)| var_id == &IN_VARIABLE_ID)
118                    || block
119                        .pipelines
120                        .iter()
121                        .flat_map(|pipeline| pipeline.elements.first())
122                        .any(|element| element.has_in_variable(working_set))
123            }
124            Expr::Binary(_) => false,
125            Expr::Bool(_) => false,
126            Expr::Call(call) => {
127                for arg in &call.arguments {
128                    match arg {
129                        Argument::Positional(expr)
130                        | Argument::Unknown(expr)
131                        | Argument::Spread(expr) => {
132                            if expr.has_in_variable(working_set) {
133                                return true;
134                            }
135                        }
136                        Argument::Named(named) => {
137                            if let Some(expr) = &named.2 {
138                                if expr.has_in_variable(working_set) {
139                                    return true;
140                                }
141                            }
142                        }
143                    }
144                }
145                false
146            }
147            Expr::CellPath(_) => false,
148            Expr::DateTime(_) => false,
149            Expr::ExternalCall(head, args) => {
150                if head.has_in_variable(working_set) {
151                    return true;
152                }
153                for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in
154                    args.as_ref()
155                {
156                    if expr.has_in_variable(working_set) {
157                        return true;
158                    }
159                }
160                false
161            }
162            Expr::ImportPattern(_) => false,
163            Expr::Overlay(_) => false,
164            Expr::Filepath(_, _) => false,
165            Expr::Directory(_, _) => false,
166            Expr::Float(_) => false,
167            Expr::FullCellPath(full_cell_path) => {
168                if full_cell_path.head.has_in_variable(working_set) {
169                    return true;
170                }
171                false
172            }
173            Expr::Garbage => false,
174            Expr::Nothing => false,
175            Expr::GlobPattern(_, _) => false,
176            Expr::Int(_) => false,
177            Expr::Keyword(kw) => kw.expr.has_in_variable(working_set),
178            Expr::List(list) => {
179                for item in list {
180                    if item.expr().has_in_variable(working_set) {
181                        return true;
182                    }
183                }
184                false
185            }
186            Expr::StringInterpolation(items) | Expr::GlobInterpolation(items, _) => {
187                for i in items {
188                    if i.has_in_variable(working_set) {
189                        return true;
190                    }
191                }
192                false
193            }
194            Expr::Operator(_) => false,
195            Expr::MatchBlock(_) => false,
196            Expr::Range(range) => {
197                if let Some(left) = &range.from {
198                    if left.has_in_variable(working_set) {
199                        return true;
200                    }
201                }
202                if let Some(middle) = &range.next {
203                    if middle.has_in_variable(working_set) {
204                        return true;
205                    }
206                }
207                if let Some(right) = &range.to {
208                    if right.has_in_variable(working_set) {
209                        return true;
210                    }
211                }
212                false
213            }
214            Expr::Record(items) => {
215                for item in items {
216                    match item {
217                        RecordItem::Pair(field_name, field_value) => {
218                            if field_name.has_in_variable(working_set) {
219                                return true;
220                            }
221                            if field_value.has_in_variable(working_set) {
222                                return true;
223                            }
224                        }
225                        RecordItem::Spread(_, record) => {
226                            if record.has_in_variable(working_set) {
227                                return true;
228                            }
229                        }
230                    }
231                }
232                false
233            }
234            Expr::Signature(_) => false,
235            Expr::String(_) => false,
236            Expr::RawString(_) => false,
237            // A `$in` variable found within a `Collect` is local, as it's already been wrapped
238            // This is probably unlikely to happen anyway - the expressions are wrapped depth-first
239            Expr::Collect(_, _) => false,
240            Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
241                let block = working_set.get_block(*block_id);
242
243                if let Some(pipeline) = block.pipelines.first() {
244                    if let Some(expr) = pipeline.elements.first() {
245                        expr.has_in_variable(working_set)
246                    } else {
247                        false
248                    }
249                } else {
250                    false
251                }
252            }
253            Expr::Table(table) => {
254                for header in table.columns.as_ref() {
255                    if header.has_in_variable(working_set) {
256                        return true;
257                    }
258                }
259
260                for row in table.rows.as_ref() {
261                    for cell in row.iter() {
262                        if cell.has_in_variable(working_set) {
263                            return true;
264                        }
265                    }
266                }
267
268                false
269            }
270
271            Expr::ValueWithUnit(value) => value.expr.has_in_variable(working_set),
272            Expr::Var(var_id) => *var_id == IN_VARIABLE_ID,
273            Expr::VarDecl(_) => false,
274        }
275    }
276
277    pub fn replace_span(
278        &mut self,
279        working_set: &mut StateWorkingSet,
280        replaced: Span,
281        new_span: Span,
282    ) {
283        if replaced.contains_span(self.span) {
284            self.span = new_span;
285        }
286        match &mut self.expr {
287            Expr::AttributeBlock(ab) => ab.item.replace_span(working_set, replaced, new_span),
288            Expr::BinaryOp(left, _, right) => {
289                left.replace_span(working_set, replaced, new_span);
290                right.replace_span(working_set, replaced, new_span);
291            }
292            Expr::UnaryNot(expr) => {
293                expr.replace_span(working_set, replaced, new_span);
294            }
295            Expr::Block(block_id) => {
296                // We are cloning the Block itself, rather than the Arc around it.
297                let mut block = Block::clone(working_set.get_block(*block_id));
298
299                for pipeline in block.pipelines.iter_mut() {
300                    for element in pipeline.elements.iter_mut() {
301                        element.replace_span(working_set, replaced, new_span)
302                    }
303                }
304
305                *block_id = working_set.add_block(Arc::new(block));
306            }
307            Expr::Closure(block_id) => {
308                let mut block = (**working_set.get_block(*block_id)).clone();
309
310                for pipeline in block.pipelines.iter_mut() {
311                    for element in pipeline.elements.iter_mut() {
312                        element.replace_span(working_set, replaced, new_span)
313                    }
314                }
315
316                *block_id = working_set.add_block(Arc::new(block));
317            }
318            Expr::Binary(_) => {}
319            Expr::Bool(_) => {}
320            Expr::Call(call) => {
321                if replaced.contains_span(call.head) {
322                    call.head = new_span;
323                }
324                for arg in call.arguments.iter_mut() {
325                    match arg {
326                        Argument::Positional(expr)
327                        | Argument::Unknown(expr)
328                        | Argument::Spread(expr) => {
329                            expr.replace_span(working_set, replaced, new_span);
330                        }
331                        Argument::Named(named) => {
332                            if let Some(expr) = &mut named.2 {
333                                expr.replace_span(working_set, replaced, new_span);
334                            }
335                        }
336                    }
337                }
338            }
339            Expr::CellPath(_) => {}
340            Expr::DateTime(_) => {}
341            Expr::ExternalCall(head, args) => {
342                head.replace_span(working_set, replaced, new_span);
343                for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in
344                    args.as_mut()
345                {
346                    expr.replace_span(working_set, replaced, new_span);
347                }
348            }
349            Expr::Filepath(_, _) => {}
350            Expr::Directory(_, _) => {}
351            Expr::Float(_) => {}
352            Expr::FullCellPath(full_cell_path) => {
353                full_cell_path
354                    .head
355                    .replace_span(working_set, replaced, new_span);
356            }
357            Expr::ImportPattern(_) => {}
358            Expr::Overlay(_) => {}
359            Expr::Garbage => {}
360            Expr::Nothing => {}
361            Expr::GlobPattern(_, _) => {}
362            Expr::MatchBlock(_) => {}
363            Expr::Int(_) => {}
364            Expr::Keyword(kw) => kw.expr.replace_span(working_set, replaced, new_span),
365            Expr::List(list) => {
366                for item in list {
367                    item.expr_mut()
368                        .replace_span(working_set, replaced, new_span);
369                }
370            }
371            Expr::Operator(_) => {}
372            Expr::Range(range) => {
373                if let Some(left) = &mut range.from {
374                    left.replace_span(working_set, replaced, new_span)
375                }
376                if let Some(middle) = &mut range.next {
377                    middle.replace_span(working_set, replaced, new_span)
378                }
379                if let Some(right) = &mut range.to {
380                    right.replace_span(working_set, replaced, new_span)
381                }
382            }
383            Expr::Record(items) => {
384                for item in items {
385                    match item {
386                        RecordItem::Pair(field_name, field_value) => {
387                            field_name.replace_span(working_set, replaced, new_span);
388                            field_value.replace_span(working_set, replaced, new_span);
389                        }
390                        RecordItem::Spread(_, record) => {
391                            record.replace_span(working_set, replaced, new_span);
392                        }
393                    }
394                }
395            }
396            Expr::Signature(_) => {}
397            Expr::String(_) => {}
398            Expr::RawString(_) => {}
399            Expr::StringInterpolation(items) | Expr::GlobInterpolation(items, _) => {
400                for i in items {
401                    i.replace_span(working_set, replaced, new_span)
402                }
403            }
404            Expr::Collect(_, expr) => expr.replace_span(working_set, replaced, new_span),
405            Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
406                let mut block = (**working_set.get_block(*block_id)).clone();
407
408                for pipeline in block.pipelines.iter_mut() {
409                    for element in pipeline.elements.iter_mut() {
410                        element.replace_span(working_set, replaced, new_span)
411                    }
412                }
413
414                *block_id = working_set.add_block(Arc::new(block));
415            }
416            Expr::Table(table) => {
417                for header in table.columns.as_mut() {
418                    header.replace_span(working_set, replaced, new_span)
419                }
420
421                for row in table.rows.as_mut() {
422                    for cell in row.iter_mut() {
423                        cell.replace_span(working_set, replaced, new_span)
424                    }
425                }
426            }
427
428            Expr::ValueWithUnit(value) => value.expr.replace_span(working_set, replaced, new_span),
429            Expr::Var(_) => {}
430            Expr::VarDecl(_) => {}
431        }
432    }
433
434    pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
435        match &mut self.expr {
436            Expr::AttributeBlock(ab) => ab.item.replace_in_variable(working_set, new_var_id),
437            Expr::Bool(_) => {}
438            Expr::Int(_) => {}
439            Expr::Float(_) => {}
440            Expr::Binary(_) => {}
441            Expr::Range(range) => {
442                if let Some(from) = &mut range.from {
443                    from.replace_in_variable(working_set, new_var_id);
444                }
445                if let Some(next) = &mut range.next {
446                    next.replace_in_variable(working_set, new_var_id);
447                }
448                if let Some(to) = &mut range.to {
449                    to.replace_in_variable(working_set, new_var_id);
450                }
451            }
452            Expr::Var(var_id) | Expr::VarDecl(var_id) => {
453                if *var_id == IN_VARIABLE_ID {
454                    *var_id = new_var_id;
455                }
456            }
457            Expr::Call(call) => {
458                for arg in call.arguments.iter_mut() {
459                    match arg {
460                        Argument::Positional(expr)
461                        | Argument::Unknown(expr)
462                        | Argument::Named((_, _, Some(expr)))
463                        | Argument::Spread(expr) => {
464                            expr.replace_in_variable(working_set, new_var_id)
465                        }
466                        Argument::Named((_, _, None)) => {}
467                    }
468                }
469                for expr in call.parser_info.values_mut() {
470                    expr.replace_in_variable(working_set, new_var_id)
471                }
472            }
473            Expr::ExternalCall(head, args) => {
474                head.replace_in_variable(working_set, new_var_id);
475                for arg in args.iter_mut() {
476                    match arg {
477                        ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) => {
478                            expr.replace_in_variable(working_set, new_var_id)
479                        }
480                    }
481                }
482            }
483            Expr::Operator(_) => {}
484            // `$in` in `Collect` has already been handled, so we don't need to check further
485            Expr::Collect(_, _) => {}
486            Expr::Block(block_id)
487            | Expr::Closure(block_id)
488            | Expr::RowCondition(block_id)
489            | Expr::Subexpression(block_id) => {
490                let mut block = Block::clone(working_set.get_block(*block_id));
491                block.replace_in_variable(working_set, new_var_id);
492                *working_set.get_block_mut(*block_id) = block;
493            }
494            Expr::UnaryNot(expr) => {
495                expr.replace_in_variable(working_set, new_var_id);
496            }
497            Expr::BinaryOp(lhs, op, rhs) => {
498                for expr in [lhs, op, rhs] {
499                    expr.replace_in_variable(working_set, new_var_id);
500                }
501            }
502            Expr::MatchBlock(match_patterns) => {
503                for (_, expr) in match_patterns.iter_mut() {
504                    expr.replace_in_variable(working_set, new_var_id);
505                }
506            }
507            Expr::List(items) => {
508                for item in items.iter_mut() {
509                    match item {
510                        ListItem::Item(expr) | ListItem::Spread(_, expr) => {
511                            expr.replace_in_variable(working_set, new_var_id)
512                        }
513                    }
514                }
515            }
516            Expr::Table(table) => {
517                for col_expr in table.columns.iter_mut() {
518                    col_expr.replace_in_variable(working_set, new_var_id);
519                }
520                for row in table.rows.iter_mut() {
521                    for row_expr in row.iter_mut() {
522                        row_expr.replace_in_variable(working_set, new_var_id);
523                    }
524                }
525            }
526            Expr::Record(items) => {
527                for item in items.iter_mut() {
528                    match item {
529                        RecordItem::Pair(key, val) => {
530                            key.replace_in_variable(working_set, new_var_id);
531                            val.replace_in_variable(working_set, new_var_id);
532                        }
533                        RecordItem::Spread(_, expr) => {
534                            expr.replace_in_variable(working_set, new_var_id)
535                        }
536                    }
537                }
538            }
539            Expr::Keyword(kw) => kw.expr.replace_in_variable(working_set, new_var_id),
540            Expr::ValueWithUnit(value_with_unit) => value_with_unit
541                .expr
542                .replace_in_variable(working_set, new_var_id),
543            Expr::DateTime(_) => {}
544            Expr::Filepath(_, _) => {}
545            Expr::Directory(_, _) => {}
546            Expr::GlobPattern(_, _) => {}
547            Expr::String(_) => {}
548            Expr::RawString(_) => {}
549            Expr::CellPath(_) => {}
550            Expr::FullCellPath(full_cell_path) => {
551                full_cell_path
552                    .head
553                    .replace_in_variable(working_set, new_var_id);
554            }
555            Expr::ImportPattern(_) => {}
556            Expr::Overlay(_) => {}
557            Expr::Signature(_) => {}
558            Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
559                for expr in exprs.iter_mut() {
560                    expr.replace_in_variable(working_set, new_var_id);
561                }
562            }
563            Expr::Nothing => {}
564            Expr::Garbage => {}
565        }
566    }
567
568    pub fn new(working_set: &mut StateWorkingSet, expr: Expr, span: Span, ty: Type) -> Expression {
569        let span_id = working_set.add_span(span);
570        Expression {
571            expr,
572            span,
573            span_id,
574            ty,
575            custom_completion: None,
576        }
577    }
578
579    pub fn new_existing(expr: Expr, span: Span, span_id: SpanId, ty: Type) -> Expression {
580        Expression {
581            expr,
582            span,
583            span_id,
584            ty,
585            custom_completion: None,
586        }
587    }
588
589    pub fn new_unknown(expr: Expr, span: Span, ty: Type) -> Expression {
590        Expression {
591            expr,
592            span,
593            span_id: SpanId::new(0),
594            ty,
595            custom_completion: None,
596        }
597    }
598
599    pub fn with_span_id(self, span_id: SpanId) -> Expression {
600        Expression {
601            expr: self.expr,
602            span: self.span,
603            span_id,
604            ty: self.ty,
605            custom_completion: self.custom_completion,
606        }
607    }
608
609    pub fn span(&self, state: &impl GetSpan) -> Span {
610        state.get_span(self.span_id)
611    }
612}