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