synthphonia/expr/
mod.rs

1use enum_dispatch::enum_dispatch;
2
3use crate::{debg2, galloc::AllocForAny, parser::problem::FunSig, value::{ConstValue, Value}};
4
5
6/// Program running context
7pub mod context;
8
9/// Context-free grammar
10pub mod cfg;
11
12/// Operators
13pub mod ops;
14
15use derive_more::DebugCustom;
16
17use self::{context::Context, ops::{Op1, Op1Enum, Op2, Op2Enum, Op3, Op3Enum}};
18#[derive(DebugCustom, PartialEq, Eq, Clone, Hash)]
19/// Expressions, statically referenced.
20/// 
21/// These expressions include constants, variables, and operations, each of which is encapsulated in the `Expr` enum. 
22/// Constants are represented by the `Const` variant, holding a `ConstValue`. 
23/// Variables are stored as a 64-bit integer with the `Var` variant. 
24/// Operations are categorized into unary, binary, and ternary, represented by `Op1`, `Op2`, and `Op3` respectively, each associated with an operation enumeration and the relevant sub-expressions. 
25/// 
26/// 
27/// The enum variants include formatting annotations used in debugging to display the expressions in a readable format, enhancing the ability to trace and verify expression states during execution. 
28/// These representations ensure the flexibility and power required for manipulating and evaluating expressions within the synthesis tasks.
29pub enum Expr {
30    #[debug(fmt = "{:?}", _0)]
31    Const(ConstValue),
32    #[debug(fmt = "<{:?}>", _0)]
33    Var(i64),
34    #[debug(fmt = "({} {:?})", _0, _1)]
35    Op1(&'static Op1Enum, &'static Expr),
36    #[debug(fmt = "({} {:?} {:?})", _0, _1, _2)]
37    Op2(&'static Op2Enum, &'static Expr, &'static Expr),
38    #[debug(fmt = "({} {:?} {:?} {:?})", _0, _1, _2, _3)]
39    Op3(&'static Op3Enum, &'static Expr, &'static Expr, &'static Expr),
40}
41
42impl Expr {
43    /// Evaluates the expression within a given context to produce a `Value`. 
44    pub fn eval(&self, ctx: &Context) -> Value {
45        
46        match self {
47            Expr::Const(c) => c.value(ctx.len()),
48            Expr::Var(index) => ctx[*index],
49            Expr::Op1(op1, a1) => op1.eval(a1.eval(ctx)),
50            Expr::Op2(op2, a1, a2) => op2.eval(a1.eval(ctx), a2.eval(ctx)),
51            Expr::Op3(op3, a1, a2, a3) => op3.eval(a1.eval(ctx), a2.eval(ctx), a3.eval(ctx)),
52        }
53    }
54    /// Calculates the cost of an expression. 
55    pub fn cost(&self) -> usize {
56        match self {
57            Expr::Const(c) => 1,
58            Expr::Var(index) => 1,
59            Expr::Op1(op1, a1) => op1.cost() + a1.cost(),
60            Expr::Op2(op2, a1, a2) => op2.cost() + a1.cost() + a2.cost(),
61            Expr::Op3(op3, a1, a2, a3) => op3.cost() + a1.cost() + a2.cost() + a3.cost(),
62        }
63    }
64    /// Determines whether an expression contains another expression. 
65    pub fn contains(&self, other: &Expr) -> bool {
66        if self == other { true } 
67        else {
68            match self {
69                Expr::Const(_) => false,
70                Expr::Var(_) => false,
71                Expr::Op1(_, e1) => e1.contains(other),
72                Expr::Op2(_, e1, e2) => e1.contains(other) || e2.contains(other),
73                Expr::Op3(_, e1, e2, e3) => e1.contains(other) || e2.contains(other) || e3.contains(other),
74            }
75        }
76    }
77    /// Formats an expression into a string representation. 
78    pub fn format(&self, sig: &FunSig) -> String {
79        match self {
80            Expr::Const(c) => format!("{:?}", c),
81            Expr::Var(index) => sig.args[*index as usize].0.clone(),
82            Expr::Op1(op1, a1) => format!("({} {})", op1, a1.format(sig)),
83            Expr::Op2(op2, a1, a2) => format!("({} {} {})", op2, a1.format(sig), a2.format(sig)),
84            Expr::Op3(op3, a1, a2, a3) => format!("({} {} {} {})", op3, a1.format(sig), a2.format(sig), a3.format(sig)),
85        }
86    }
87    /// Construct a ternary expression that represents an if-then-else operation within the context of the `Expr` enum. 
88    pub fn ite(&'static self, t: &'static Expr, f: &'static Expr) -> &'static Expr {
89        crate::expr!(Ite {self} {t} {f}).galloc()
90    }
91    /// Converts an `Expr` into an `Expression`. 
92    pub fn to_expression(&self) -> Expression {
93        match self {
94            Expr::Const(c) => Expression::Const(*c),
95            Expr::Var(v) => Expression::Var(*v),
96            Expr::Op1(op, a1) => Expression::Op1((*op).clone(), a1.to_expression().into()),
97            Expr::Op2(op, a1, a2) => Expression::Op2((*op).clone(), a1.to_expression().into(), a2.to_expression().into()),
98            Expr::Op3(op, a1, a2, a3) => Expression::Op3((*op).clone(), a1.to_expression().into(), a2.to_expression().into(), a3.to_expression().into()),
99        }
100    }
101}
102
103#[derive(DebugCustom, PartialEq, Eq, Clone, Hash)]
104/// Expressions, owned.
105pub enum Expression {
106    #[debug(fmt = "{:?}", _0)]
107    Const(ConstValue),
108    #[debug(fmt = "<{:?}>", _0)]
109    Var(i64),
110    #[debug(fmt = "({} {:?})", _0, _1)]
111    Op1(Op1Enum, Box<Expression>),
112    #[debug(fmt = "({} {:?} {:?})", _0, _1, _2)]
113    Op2(Op2Enum, Box<Expression>, Box<Expression>),
114    #[debug(fmt = "({} {:?} {:?} {:?})", _0, _1, _2, _3)]
115    Op3(Op3Enum, Box<Expression>, Box<Expression>, Box<Expression>),
116}
117
118impl Expression {
119    /// Converts an `Expression` into a statically allocated `Expr` reference. 
120    pub fn alloc_local(self) -> &'static Expr {
121        match self {
122            Expression::Const(a) => Expr::Const(a).galloc(),
123            Expression::Var(v) => Expr::Var(v).galloc(),
124            Expression::Op1(op1, a1) => Expr::Op1(op1.galloc(), a1.alloc_local()).galloc(),
125            Expression::Op2(op1, a1, a2) => Expr::Op2(op1.galloc(), a1.alloc_local(), a2.alloc_local()).galloc(),
126            Expression::Op3(op1, a1, a2, a3) => Expr::Op3(op1.galloc(), a1.alloc_local(), a2.alloc_local(), a3.alloc_local()).galloc(),
127        }
128    }
129}
130
131#[macro_export]
132macro_rules! expr_no_use {
133    ($l:literal) => { $crate::expr::Expr::Const($crate::const_value!($l))};
134    ([$l:literal]) => { $crate::expr::Expr::Var($l)};
135    ({$l:expr}) => { $l };
136    ($op:ident $a1:tt) => { 
137        crate::expr::Expr::Op1(Op1Enum::$op($op::default()).galloc(), crate::expr![$a1].galloc())
138    };
139    ($op:ident $a1:tt $a2:tt) => { 
140        crate::expr::Expr::Op2(Op2Enum::$op($op::default()).galloc(), crate::expr![$a1].galloc(), crate::expr![$a2].galloc())
141    };
142    ($op:ident $a1:tt $a2:tt $a3:tt) => {
143        crate::expr::Expr::Op3(Op3Enum::$op($op::default()).galloc(), crate::expr![$a1].galloc(), crate::expr![$a2].galloc(), crate::expr![$a3].galloc())
144    };
145    ( ($( $inner:tt )*) ) => { $crate::expr_no_use!($($inner)*) };
146}
147
148#[macro_export]
149/// Macro to create of expressions in the Synthphonia module. 
150macro_rules! expr {
151    ( $( $inner:tt )*) => { {
152        use $crate::galloc::AllocForAny;
153        use $crate::expr::ops::str::*;
154        use $crate::expr::ops::*;
155        use $crate::expr::ops::float::*;
156        $crate::expr_no_use!($($inner)*) 
157    }};
158}
159
160#[cfg(test)]
161mod tests {
162    use crate::{value::Value, galloc, expr::{ops::str::{Replace, Concat}, context::Context}, const_value};
163    use crate::galloc::AllocForAny;
164
165    #[test]
166    fn test1() {
167        let input = const_value!("938-242-504").value(1);
168        let output = const_value!("938.242.504").value(1);
169        let ctx = Context::new(1, vec![input], vec![], output);
170        let e = expr!{ (Replace (Replace [0] "-" ".") "-" ".") };
171        assert_eq!(e.eval(&ctx), output);
172    }
173}
174
175