Skip to main content

safe_migrate/analysis/
expr_visitor.rs

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