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