Skip to main content

sva_ast/
expr.rs

1// Concern: declares Expr, the source-free math-expression meaning | Non-concern: lexing, parsing, binding a ref path to a node (graph.rs) | IO: none
2
3use crate::diag::ByteSpan;
4
5pub const JOIN: &str = "join";
6pub const SERIES: &str = "sum";
7pub const INDEX: &str = "idx";
8pub const FLOOR: &str = "floor";
9pub const CEIL: &str = "ceil";
10pub const DEGREE: &str = "deg";
11
12#[derive(Clone, Copy, Debug)]
13pub enum BinOp {
14    Add,
15    Sub,
16    Mul,
17    Div,
18    Mod,
19}
20
21impl PartialEq for BinOp {
22    fn eq(&self, other: &Self) -> bool {
23        matches!(
24            (self, other),
25            (BinOp::Add, BinOp::Add)
26                | (BinOp::Sub, BinOp::Sub)
27                | (BinOp::Mul, BinOp::Mul)
28                | (BinOp::Div, BinOp::Div)
29                | (BinOp::Mod, BinOp::Mod)
30        )
31    }
32}
33impl Eq for BinOp {}
34
35/// `Bars` lives only until `Graph::resolve_bar_spans` rewrites it.
36/// `Samples` counts the observation's own grid, which no parse can settle.
37#[derive(Clone, Debug)]
38pub enum Literal {
39    Num(f64),
40    Bars(f64),
41    Samples(f64),
42    Str(String),
43}
44
45impl PartialEq for Literal {
46    fn eq(&self, other: &Self) -> bool {
47        match (self, other) {
48            (Literal::Num(a), Literal::Num(b))
49            | (Literal::Bars(a), Literal::Bars(b))
50            | (Literal::Samples(a), Literal::Samples(b)) => a.to_bits() == b.to_bits(),
51            (Literal::Str(a), Literal::Str(b)) => a == b,
52            _ => false,
53        }
54    }
55}
56impl Eq for Literal {}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub enum Arg {
60    Pos(Expr),
61    Named(String, Expr),
62}
63
64/// `x(e)` reads an instant, `x[i]` a sample index.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum Address {
67    Time,
68    Index,
69}
70
71/// Meaning only: which node a path names lives in `graph.rs`, so two structurally identical
72/// expressions from different files compare equal, `span` excluded from `Eq`. `binds` holds
73/// an invocation's named arguments in written order: `@lp-def(t, cutoff=800)`.
74#[derive(Clone, Debug)]
75pub enum Expr {
76    Lit(Literal),
77    Var(String),
78    Bin(BinOp, Box<Expr>, Box<Expr>),
79    Call {
80        name: String,
81        args: Vec<Arg>,
82        span: ByteSpan,
83    },
84    Ref {
85        path: String,
86        arg: Box<Expr>,
87        binds: Vec<(String, Expr)>,
88        address: Address,
89        span: ByteSpan,
90    },
91    SelfRef {
92        arg: Box<Expr>,
93        address: Address,
94        span: ByteSpan,
95    },
96    /// `x[i]`: a parameter's signal at sample index `i`.
97    Indexed {
98        name: String,
99        arg: Box<Expr>,
100        span: ByteSpan,
101    },
102}
103
104impl PartialEq for Expr {
105    fn eq(&self, other: &Self) -> bool {
106        match (self, other) {
107            (Expr::Lit(a), Expr::Lit(b)) => a == b,
108            (Expr::Var(a), Expr::Var(b)) => a == b,
109            (Expr::Bin(o1, l1, r1), Expr::Bin(o2, l2, r2)) => o1 == o2 && l1 == l2 && r1 == r2,
110            (
111                Expr::Call {
112                    name: n1, args: a1, ..
113                },
114                Expr::Call {
115                    name: n2, args: a2, ..
116                },
117            ) => n1 == n2 && a1 == a2,
118            (
119                Expr::Ref {
120                    path: p1,
121                    arg: a1,
122                    binds: b1,
123                    address: x1,
124                    ..
125                },
126                Expr::Ref {
127                    path: p2,
128                    arg: a2,
129                    binds: b2,
130                    address: x2,
131                    ..
132                },
133            ) => p1 == p2 && a1 == a2 && b1 == b2 && x1 == x2,
134            (
135                Expr::SelfRef {
136                    arg: a1,
137                    address: x1,
138                    ..
139                },
140                Expr::SelfRef {
141                    arg: a2,
142                    address: x2,
143                    ..
144                },
145            ) => a1 == a2 && x1 == x2,
146            (
147                Expr::Indexed {
148                    name: n1, arg: a1, ..
149                },
150                Expr::Indexed {
151                    name: n2, arg: a2, ..
152                },
153            ) => n1 == n2 && a1 == a2,
154            _ => false,
155        }
156    }
157}
158impl Eq for Expr {}
159
160/// Which instance a ref's `binds` name: `Keep` moves a read, `Substitute` picks one.
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162pub enum Binds {
163    Keep,
164    Substitute,
165}
166
167/// One level down, in written order; `Substitute` makes a `Ref`'s `binds` children.
168pub fn children(e: &Expr, binds: Binds) -> Vec<&Expr> {
169    match e {
170        Expr::Lit(_) | Expr::Var(_) => Vec::new(),
171        Expr::Bin(_, l, r) => vec![l, r],
172        Expr::SelfRef { arg, .. } | Expr::Indexed { arg, .. } => vec![arg],
173        Expr::Ref { arg, binds: bs, .. } => {
174            let mut out = vec![arg.as_ref()];
175            if binds == Binds::Substitute {
176                out.extend(bs.iter().map(|(_, v)| v));
177            }
178            out
179        }
180        Expr::Call { args, .. } => args
181            .iter()
182            .map(|a| {
183                let (Arg::Pos(x) | Arg::Named(_, x)) = a;
184                x
185            })
186            .collect(),
187    }
188}
189
190/// Every child replaced by `f`'s answer, one level down; the caller owns its recursion.
191pub fn map_children<E>(
192    e: &Expr,
193    binds: Binds,
194    mut f: impl FnMut(&Expr) -> Result<Expr, E>,
195) -> Result<Expr, E> {
196    Ok(match e {
197        Expr::Lit(_) | Expr::Var(_) => e.clone(),
198        Expr::Bin(op, l, r) => Expr::Bin(*op, Box::new(f(l)?), Box::new(f(r)?)),
199        Expr::SelfRef { arg, address, span } => Expr::SelfRef {
200            arg: Box::new(f(arg)?),
201            address: *address,
202            span: *span,
203        },
204        Expr::Indexed { name, arg, span } => Expr::Indexed {
205            name: name.clone(),
206            arg: Box::new(f(arg)?),
207            span: *span,
208        },
209        Expr::Ref {
210            path,
211            arg,
212            binds: bs,
213            address,
214            span,
215        } => {
216            let arg = Box::new(f(arg)?);
217            let mut rebuilt = Vec::with_capacity(bs.len());
218            for (k, v) in bs {
219                rebuilt.push((
220                    k.clone(),
221                    if binds == Binds::Substitute {
222                        f(v)?
223                    } else {
224                        v.clone()
225                    },
226                ));
227            }
228            Expr::Ref {
229                path: path.clone(),
230                arg,
231                binds: rebuilt,
232                address: *address,
233                span: *span,
234            }
235        }
236        Expr::Call { name, args, span } => {
237            let mut rebuilt = Vec::with_capacity(args.len());
238            for a in args {
239                rebuilt.push(match a {
240                    Arg::Pos(x) => Arg::Pos(f(x)?),
241                    Arg::Named(k, x) => Arg::Named(k.clone(), f(x)?),
242                });
243            }
244            Expr::Call {
245                name: name.clone(),
246                args: rebuilt,
247                span: *span,
248            }
249        }
250    })
251}
252
253pub(crate) fn map_children_ok(e: &Expr, binds: Binds, mut f: impl FnMut(&Expr) -> Expr) -> Expr {
254    let mapped: Result<Expr, ()> = map_children(e, binds, |c| Ok(f(c)));
255    mapped.expect("an infallible map never refuses")
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn structural_equality_ignores_spans() {
264        let a = Expr::Ref {
265            path: "kick".to_string(),
266            arg: Box::new(Expr::Var("t".to_string())),
267            binds: Vec::new(),
268            address: Address::Time,
269            span: ByteSpan::new(0, 4),
270        };
271        let b = Expr::Ref {
272            path: "kick".to_string(),
273            arg: Box::new(Expr::Var("t".to_string())),
274            binds: Vec::new(),
275            address: Address::Time,
276            span: ByteSpan::new(99, 200),
277        };
278        assert_eq!(a, b);
279    }
280
281    #[test]
282    fn float_literals_compare_by_bit_pattern() {
283        assert_eq!(Literal::Num(0.0), Literal::Num(0.0));
284        assert_ne!(Literal::Num(0.0), Literal::Num(-0.0), "-0.0 != 0.0 exactly");
285        assert_eq!(Literal::Num(f64::NAN), Literal::Num(f64::NAN));
286    }
287}