nu_protocol/ast/
expression.rs

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