Skip to main content

safe_migrate/analysis/
expr_visitor.rs

1use crate::analysis::expr_ir::ExprIr;
2use squawk_syntax::ast::{AstNode, Expr};
3
4pub struct ExprVisitor;
5
6impl ExprVisitor {
7    pub fn convert(expr: Expr) -> ExprIr {
8        match expr {
9            Expr::Literal(lit) => Self::convert_literal(lit),
10            Expr::NameRef(nr) => Self::convert_name_ref(nr),
11            Expr::CallExpr(ce) => Self::convert_call_expr(ce),
12            Expr::BinExpr(be) => Self::convert_bin_expr(be),
13            Expr::CastExpr(ce) => Self::convert_cast_expr(ce),
14            Expr::PrefixExpr(pe) => pe
15                .expr()
16                .map(Self::convert)
17                .unwrap_or(ExprIr::Literal("<prefix>".into())),
18            Expr::ParenExpr(pe) => pe
19                .expr()
20                .map(Self::convert)
21                .unwrap_or(ExprIr::Literal("<paren>".into())),
22            Expr::CaseExpr(ce) => Self::convert_case_expr(ce),
23            Expr::ArrayExpr(ae) => Self::convert_array_expr(ae),
24            Expr::BetweenExpr(be) => Self::convert_between_expr(be),
25            Expr::IndexExpr(ie) => Self::convert_index_expr(ie),
26            Expr::SliceExpr(se) => Self::convert_slice_expr(se),
27            Expr::FieldExpr(fe) => Self::convert_field_expr(fe),
28            Expr::PostfixExpr(pe) => Self::convert_postfix_expr(pe),
29            Expr::Collate(ce) => {
30                let left = ce
31                    .expr()
32                    .map(Self::convert)
33                    .unwrap_or(ExprIr::Literal("<lhs>".into()));
34                let right = ce
35                    .collation_ref()
36                    .map(|c| c.syntax().text().to_string())
37                    .unwrap_or_else(|| "<collation>".into());
38                ExprIr::BinaryOp {
39                    left: Box::new(left),
40                    op: "COLLATE".to_string(),
41                    right: Box::new(ExprIr::Literal(right)),
42                }
43            }
44            _ => ExprIr::Literal("<complex>".into()),
45        }
46    }
47
48    fn convert_literal(lit: squawk_syntax::ast::Literal) -> ExprIr {
49        ExprIr::Literal(lit.syntax().text().to_string())
50    }
51
52    fn convert_name_ref(nr: squawk_syntax::ast::NameRef) -> ExprIr {
53        let name = nr.text().to_string();
54        ExprIr::ColumnRef(name)
55    }
56
57    fn convert_call_expr(ce: squawk_syntax::ast::CallExpr) -> ExprIr {
58        let name = ce
59            .expr()
60            .map(|e| match e {
61                Expr::NameRef(nr) => nr.text().to_string(),
62                other => other.syntax().text().to_string(),
63            })
64            .unwrap_or_else(|| "<fn>".into());
65
66        let args = ce
67            .arg_list()
68            .map(|al| {
69                al.args()
70                    .filter_map(|arg| arg.expr())
71                    .map(Self::convert)
72                    .collect()
73            })
74            .unwrap_or_default();
75
76        ExprIr::FunctionCall { name, args }
77    }
78
79    fn convert_bin_expr(be: squawk_syntax::ast::BinExpr) -> ExprIr {
80        let left = be
81            .lhs()
82            .map(Self::convert)
83            .unwrap_or(ExprIr::Literal("<lhs>".into()));
84        let right = be
85            .rhs()
86            .map(Self::convert)
87            .unwrap_or(ExprIr::Literal("<rhs>".into()));
88
89        use squawk_syntax::ast::BinOp;
90        let op = be
91            .op()
92            .map(|o| match o {
93                BinOp::And(t) => t.text().to_string(),
94                BinOp::Caret(t) => t.text().to_string(),
95                BinOp::ColonEq(t) => t.text().to_string(),
96                BinOp::Eq(t) => t.text().to_string(),
97                BinOp::FatArrow(t) => t.text().to_string(),
98                BinOp::Gteq(t) => t.text().to_string(),
99                BinOp::Ilike(t) => t.text().to_string(),
100                BinOp::In(t) => t.text().to_string(),
101                BinOp::Is(t) => t.text().to_string(),
102                BinOp::LAngle(t) => t.text().to_string(),
103                BinOp::Like(t) => t.text().to_string(),
104                BinOp::Lteq(t) => t.text().to_string(),
105                BinOp::Minus(t) => t.text().to_string(),
106                BinOp::Neq(t) => t.text().to_string(),
107                BinOp::Neqb(t) => t.text().to_string(),
108                BinOp::Or(t) => t.text().to_string(),
109                BinOp::Overlaps(t) => t.text().to_string(),
110                BinOp::Percent(t) => t.text().to_string(),
111                BinOp::Plus(t) => t.text().to_string(),
112                BinOp::RAngle(t) => t.text().to_string(),
113                BinOp::Slash(t) => t.text().to_string(),
114                BinOp::Star(t) => t.text().to_string(),
115                BinOp::AtTimeZone(n) => n.syntax().text().to_string(),
116                BinOp::ColonColon(n) => n.syntax().text().to_string(),
117                BinOp::CustomOp(n) => n.syntax().text().to_string(),
118                BinOp::IsDistinctFrom(n) => n.syntax().text().to_string(),
119                BinOp::IsNot(n) => n.syntax().text().to_string(),
120                BinOp::IsNotDistinctFrom(n) => n.syntax().text().to_string(),
121                BinOp::NotIlike(n) => n.syntax().text().to_string(),
122                BinOp::NotIn(n) => n.syntax().text().to_string(),
123                BinOp::NotLike(n) => n.syntax().text().to_string(),
124                BinOp::NotSimilarTo(n) => n.syntax().text().to_string(),
125                BinOp::OperatorCall(n) => n.syntax().text().to_string(),
126                BinOp::SimilarTo(n) => n.syntax().text().to_string(),
127                BinOp::Escape(t) => t.text().to_string(),
128            })
129            .unwrap_or_else(|| "<op>".into());
130
131        ExprIr::BinaryOp {
132            left: Box::new(left),
133            op,
134            right: Box::new(right),
135        }
136    }
137
138    fn convert_cast_expr(ce: squawk_syntax::ast::CastExpr) -> ExprIr {
139        let inner = ce
140            .expr()
141            .map(Self::convert)
142            .unwrap_or(ExprIr::Literal("<cast_inner>".into()));
143        let target_type = ce
144            .ty()
145            .map(|t| t.syntax().text().to_string())
146            .unwrap_or_else(|| "<type>".into());
147        ExprIr::Cast {
148            expr: Box::new(inner),
149            target_type,
150        }
151    }
152
153    fn convert_case_expr(ce: squawk_syntax::ast::CaseExpr) -> ExprIr {
154        let mut branches: Vec<ExprIr> = Vec::new();
155
156        branches.push(ce.expr().map(Self::convert).unwrap_or(ExprIr::Omitted));
157
158        if let Some(wcl) = ce.when_clause_list() {
159            for when in wcl.when_clauses() {
160                branches.push(
161                    when.condition()
162                        .map(Self::convert)
163                        .unwrap_or(ExprIr::Omitted),
164                );
165                branches.push(when.then().map(Self::convert).unwrap_or(ExprIr::Omitted));
166            }
167        }
168
169        branches.push(
170            ce.else_clause()
171                .and_then(|ec| ec.expr())
172                .map(Self::convert)
173                .unwrap_or(ExprIr::Omitted),
174        );
175
176        ExprIr::FunctionCall {
177            name: "<case>".into(),
178            args: branches,
179        }
180    }
181
182    fn convert_array_expr(ae: squawk_syntax::ast::ArrayExpr) -> ExprIr {
183        let elements: Vec<ExprIr> = ae.exprs().map(Self::convert).collect();
184        ExprIr::FunctionCall {
185            name: "<array>".into(),
186            args: elements,
187        }
188    }
189
190    fn convert_between_expr(be: squawk_syntax::ast::BetweenExpr) -> ExprIr {
191        let args = vec![
192            be.target().map(Self::convert).unwrap_or(ExprIr::Omitted),
193            be.start().map(Self::convert).unwrap_or(ExprIr::Omitted),
194            be.end().map(Self::convert).unwrap_or(ExprIr::Omitted),
195        ];
196        ExprIr::FunctionCall {
197            name: "<between>".into(),
198            args,
199        }
200    }
201
202    fn convert_index_expr(ie: squawk_syntax::ast::IndexExpr) -> ExprIr {
203        let args = vec![
204            ie.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
205            ie.index().map(Self::convert).unwrap_or(ExprIr::Omitted),
206        ];
207        ExprIr::FunctionCall {
208            name: "<index>".into(),
209            args,
210        }
211    }
212
213    fn convert_slice_expr(se: squawk_syntax::ast::SliceExpr) -> ExprIr {
214        let args = vec![
215            se.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
216            se.start().map(Self::convert).unwrap_or(ExprIr::Omitted),
217            se.end().map(Self::convert).unwrap_or(ExprIr::Omitted),
218        ];
219        ExprIr::FunctionCall {
220            name: "<slice>".into(),
221            args,
222        }
223    }
224
225    fn convert_field_expr(fe: squawk_syntax::ast::FieldExpr) -> ExprIr {
226        let args = vec![
227            fe.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
228            fe.field()
229                .map(|f| ExprIr::Literal(f.text().to_string()))
230                .unwrap_or(ExprIr::Omitted),
231        ];
232        ExprIr::FunctionCall {
233            name: "<field>".into(),
234            args,
235        }
236    }
237
238    fn convert_postfix_expr(pe: squawk_syntax::ast::PostfixExpr) -> ExprIr {
239        let mut args = Vec::new();
240        for child in pe.syntax().children() {
241            if let Some(expr) = Expr::cast(child) {
242                args.push(Self::convert(expr));
243            }
244        }
245        ExprIr::FunctionCall {
246            name: "<postfix>".into(),
247            args,
248        }
249    }
250}