Skip to main content

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