synthphonia/expr/ops/op_impl.rs
1
2#[macro_export]
3/// Defines a macro for creating structures with basic functionality.
4///
5/// This macro generates a structure that includes a single `usize` field, implements several traits, and provides methods related to configuration and naming.
6/// For any given identifier and a name, the generated structure derives traits for debugging, cloning, copying, equality comparison, and hashing.
7/// It includes a method to create an instance from a configuration, specifically fetching a "cost" value, defaulting to 1 if unspecified.
8/// There’s also a method for obtaining the name of the structure as a static string.
9/// Additionally, it implements the `Display` trait for formatted output of the structure's name, and provides a default implementation that creates an instance using a default configuration.
10///
11macro_rules! impl_basic {
12 ($s:ident, $name:expr) => {
13 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14 pub struct $s(pub usize);
15 impl $s {
16 pub fn from_config(config: &$crate::parser::config::Config) -> Self {
17 Self(config.get_usize("cost").unwrap_or(1))
18 }
19 pub fn name() -> &'static str {$name}
20 }
21 impl std::fmt::Display for $s {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 Self::name().fmt(f)
24 }
25 }
26 impl Default for $s {
27 fn default() -> Self {
28 Self::from_config(&Default::default())
29 }
30 }
31 };
32}
33
34#[macro_export]
35/// Expands to provide a default value for various data types.
36///
37/// The macro accepts a type identifier—`Str`, `Int`, `Bool`, `Float`, `ListStr`, or `ListInt`—and emits a corresponding default value: an empty string, integer zero as a 64-bit signed integer, a boolean false, a float wrapped in the project's `F64` utility, a reference to an empty slice of string slices, or an empty array of integers, respectively.
38/// This aids in initializing variables with predetermined standard values across different types within the synthesis framework.
39///
40macro_rules! default_value {
41 (Str) => { "" };
42 (Int) => { 0i64 };
43 (Bool) => { false };
44 (Float) => { $crate::utils::F64(0.0) };
45 (ListStr) => { &[] as &[&str] };
46 (ListInt) => { [] };
47}
48#[macro_export]
49/// Defines a macro that automates the implementation of several traits for a given type.
50///
51/// This macro takes an identifier and a string literal as inputs, implementing functionalities related to the name of the type.
52/// The macro allows the type to return a static string as its name and implements the `Display` trait to facilitate formatted printing, using the name as the displayed representation.
53/// Additionally, it provides a default implementation for the type, initializing it using a configuration obtained from the `Default` trait.
54/// This macro is designed to reduce repetitive code for types that require a static name representation and a standardized construction approach.
55///
56macro_rules! impl_name {
57 ($s:ident, $name:expr) => {
58 impl $s {
59 pub fn name() -> &'static str {$name}
60 }
61 impl std::fmt::Display for $s {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 Self::name().fmt(f)
64 }
65 }
66 impl Default for $s {
67 fn default() -> Self {
68 Self::from_config(&Default::default())
69 }
70 }
71 };
72}
73
74
75#[macro_export]
76/// Implements a macro to define unary operations for expression types.
77///
78/// This macro provides a structured way to implement the `Op1` trait for a given type, enabling the definition of operation costs and evaluation methods.
79/// Specifically, it defines the `cost` method to return a constant value associated with the operation type and the `try_eval` method to attempt evaluation of the operation on an input value.
80///
81/// Within the implementation, the evaluation is performed by matching on the input value type.
82/// For each specified type conversion, it maps an expression over the input's iterable values, collecting results using `galloc_scollect`.
83/// If the input type matches one of the specified conversions, the operation applies and returns `true` along with the resulting value; otherwise, it returns `false` and a `Null` value.
84/// This macro thus facilitates efficient and reusable implementations of unary operations over various value types in the string synthesis framework.
85macro_rules! impl_op1 {
86 ($s:ident, $name:expr, $($t1:ident -> $rt:ident { $f:expr }),*) => {
87 impl $crate::expr::ops::Op1 for $s {
88 fn cost(&self) -> usize { self.0 }
89 fn try_eval(&self, a1 : $crate::value::Value) -> (bool, $crate::value::Value) {
90 match a1 {
91 $(
92 crate::value::Value::$t1(s) => (true, crate::value::Value::$rt(s.iter().map($f).galloc_scollect())),
93 )*
94 _ => (false, crate::value::Value::Null),
95 }
96 }
97 }
98 }
99}
100#[macro_export]
101/// Defines a macro to simplify the creation and implementation of unary operations in the synthesis framework.
102///
103/// This macro automates the process of setting up a new unary operation by invoking several components.
104/// It takes an identifier, a name, and a series of type transformation definitions, associating each with an expression used for transformation.
105/// It uses the `impl_basic!` macro to provide basic trait implementations for the operation, sets up the operation as an implementation of `Enumerator1` from the `forward::enumeration` module, and then employs the `impl_op1!` macro to complete the detailed implementation needed for the operation including how it transforms various input types to the result type using the provided expression.
106/// This macro reduces repetitive code and ensures consistency when creating new operations.
107///
108macro_rules! new_op1 {
109 ($s:ident, $name:expr, $($t1:ident -> $rt:ident { $f:expr }),*) => {
110 $crate::impl_basic!($s, $name);
111 impl $crate::forward::enumeration::Enumerator1 for $s {}
112 $crate::impl_op1!($s, $name, $($t1 -> $rt { $f }),*);
113 };
114}
115
116#[macro_export]
117/// Defines a macro to implement the `Op1` trait for a given type, focusing on optional transformations.
118///
119///
120/// This macro, `impl_op1_opt`, takes a type identifier and a series of transformation patterns, generating an implementation of the `Op1` trait for the specified type.
121/// The trait requires two key methods: `cost`, which returns the associated cost from the tuple stored within the type, and `try_eval`, which attempts to evaluate a unary operation on the provided `Value`.
122/// In `try_eval`, the macro iterates over values of specific types, applying a transformation function that returns an optional result.
123/// It collects results, tracking whether all transformations succeeded, and constructs a new `Value` of the result type, falling back to a default value if necessary.
124/// If the transformation can't be applied to the input type, it returns a flag of `false` with a `Null` value.
125/// This macro streamlines the implementation of the `Op1` trait for operations that may or may not succeed based on optional transformations.
126///
127macro_rules! impl_op1_opt {
128 ($s:ident, $name:expr, $($t1:ident -> $rt:ident { $f:expr }),*) => {
129 impl $crate::expr::ops::Op1 for $s {
130 fn cost(&self) -> usize { self.0 }
131 fn try_eval(&self, a1 : $crate::value::Value) -> (bool, $crate::value::Value) {
132 match a1 {
133 $(
134 crate::value::Value::$t1(s1) => {
135 let mut flag = true;
136 let v = s1.iter().map($f).map(|f| { flag &= f.is_some(); f.unwrap_or($crate::default_value![$rt]) }).galloc_scollect();
137 (flag, crate::value::Value::$rt(v))
138 }
139 )*
140 _ => (false, crate::value::Value::Null),
141 }
142 }
143 }
144 };
145}
146#[macro_export]
147/// Defines a macro for creating a new unary operation.
148///
149/// This macro simplifies the process of defining new unary operators by automating the implementation of necessary traits and logic.
150/// It first abstracts over the implementation of basic traits and characteristics by invoking `impl_basic!` with the specified identifier and name.
151/// Then, it ensures the new type implements `Enumerator1`, which likely facilitates iterating over or evaluating expressions.
152/// Finally, the macro invokes `impl_op1_opt!`, passing along the type, operation name, and mapping of input types to result types along with associated function logic.
153/// Through this structured approach, multiple unary operations with various input types and results can be efficiently defined and integrated into the synthesis framework.
154///
155macro_rules! new_op1_opt {
156 ($s:ident, $name:expr, $($t1:ident -> $rt:ident { $f:expr }),*) => {
157 $crate::impl_basic!($s, $name);
158 impl $crate::forward::enumeration::Enumerator1 for $s {}
159 $crate::impl_op1_opt!($s, $name, $($t1 -> $rt { $f }),*);
160 };
161}
162
163#[macro_export]
164/// Defines a macro for creating new binary operations within the expression framework.
165///
166/// This macro, `new_op2`, takes as input a series of tokens to set up a binary operation type, specifically an identifier `$s`, a name `$name`, and multiple tuples of input types (`$t1` and `$t2`), return type `$rt`, and a functional expression `$f`.
167///
168///
169/// The macro performs several tasks: it invokes an implementation macro, `impl_basic!`, for basic setup using the provided identifier and name.
170/// It implements the `Enumerator2` trait for the operation using the `impl` syntax.
171/// Finally, it calls another implementation macro, `impl_op2!`, which probably performs the core operation setup, defining how each type of binary operation works using the provided type and functional expression pairs.
172/// This structure aids in generating consistent and concise definitions of new binary operations throughout the synthesis module.
173macro_rules! new_op2 {
174 ($s:ident, $name:expr, $(($t1:ident, $t2:ident) -> $rt:ident { $f:expr }),*) => {
175 $crate::impl_basic!($s, $name);
176 impl $crate::forward::enumeration::Enumerator2 for $s {}
177 $crate::impl_op2!($s, $name, $(($t1, $t2) -> $rt { $f }),*);
178 };
179}
180
181#[macro_export]
182/// This macro facilitates the creation of a new binary operation with optional behavior.
183///
184/// It integrates a binary operation into the framework by implementing necessary traits and logic through various components.
185/// Initially, it utilizes `$crate::impl_basic!` to establish a basic setup for a given operation `$s`, associating it with a specified `$name`.
186/// The macro then implements the `Enumerator2` trait for `$s`, effectively enabling the operation to be part of the enumeration process utilized in forward synthesis tasks.
187///
188///
189/// Following this, it calls `$crate::impl_op2_opt!` to define additional implementation details for the operation, identifying the input types `$t1` and `$t2`, the return type `$rt`, and the expression `$f` that dictates the operation's computation.
190/// This approach ensures the cohesive integration of optional binary operations that can be used efficiently within the domain-specific synthesis framework, allowing for complex operations to be handled flexibly and dynamically.
191macro_rules! new_op2_opt {
192 ($s:ident, $name:expr, $(($t1:ident, $t2:ident) -> $rt:ident { $f:expr }),*) => {
193 $crate::impl_basic!($s, $name);
194 impl $crate::forward::enumeration::Enumerator2 for $s {}
195 $crate::impl_op2_opt!($s, $name, $(($t1, $t2) -> $rt { $f }),*);
196 };
197}
198
199#[macro_export]
200/// Creates a new ternary operation macro for use within the string synthesis framework.
201///
202/// The macro accepts the name of the operation and a list of tuples, each representing a combination of three argument types and a return type, along with a closure that defines the operation's functionality.
203/// It expands to implement basic operation utilities and enumeration traits for the specified operation, invoking helper macros `impl_basic!`, which likely sets up foundational properties, and `impl_op3!`, which embeds the provided logic into the operational infrastructure.
204/// Overall, this macro streamlines the definition and integration of complex ternary operations within the module.
205///
206macro_rules! new_op3 {
207 ($s:ident, $name:expr, $(($t1:ident, $t2:ident, $t3:ident) -> $rt:ident { $f:expr }),*) => {
208 $crate::impl_basic!($s, $name);
209 impl $crate::forward::enumeration::Enumerator3 for $s {}
210 $crate::impl_op3!($s, $name, $(($t1, $t2, $t3) -> $rt { $f }),*);
211 };
212}
213
214#[macro_export]
215/// Defines a macro that simplifies the creation and implementation of three-input (ternary) operations with optional functionality.
216///
217/// This macro-rule is structured to declare and implement a new ternary operation by accepting an identifier, a name, and a sequence of type transformations that results in a function.
218/// It leverages other internal macros, such as `impl_basic!` and `impl_op3_opt!`, to automatically generate and bind necessary implementations.
219/// The `impl_basic!` macro assists in setting up basic structure and namespacing for the operation, while `impl_op3_opt!` is likely responsible for the implementation details and handling optional features across different inputs.
220/// The macro also implements the `Enumerator3` trait from the `forward::enumeration` module for the specified identifier, providing enumeration capabilities for the new operation in a modular and reusable manner.
221macro_rules! new_op3_opt {
222 ($s:ident, $name:expr, $(($t1:ident, $t2:ident, $t3:ident) -> $rt:ident { $f:expr }),*) => {
223 $crate::impl_basic!($s, $name);
224 impl $crate::forward::enumeration::Enumerator3 for $s {}
225 $crate::impl_op3_opt!($s, $name, $(($t1, $t2, $t3) -> $rt { $f }),*);
226 };
227}
228#[macro_export]
229/// This macro facilitates the implementation of the `Op2` trait for a given struct, allowing it to define binary operations on values.
230///
231/// It enables configuration of the operation by specifying patterns for value types, corresponding result types, and a closure for computation.
232/// When invoked, the macro takes the struct name, operation name, and a series of type pattern mappings where each pair of input types maps to a result type and an associated function.
233/// For each specified type combination, the generated implementation of the `try_eval` method attempts to evaluate the operation by zipping and transforming the input iterables using the provided closure function.
234/// If the types match, it returns a successful evaluation with the transformed result; otherwise, it returns a failure with a null value.
235/// This macro centralizes the repetitive logic for implementing binary operations across different value types, promoting code reuse and reducing boilerplate.
236///
237macro_rules! impl_op2 {
238 ($s:ident, $name:expr, $(($t1:ident, $t2:ident) -> $rt:ident { $f:expr }),*) => {
239
240 impl $crate::expr::ops::Op2 for $s {
241 fn cost(&self) -> usize { self.0 }
242 fn try_eval(&self, a1 : $crate::value::Value, a2 : $crate::value::Value) -> (bool, crate::value::Value) {
243 match (a1, a2) {
244 $(
245 (crate::value::Value::$t1(s1), crate::value::Value::$t2(s2)) => (true, crate::value::Value::$rt(itertools::izip!(s1.iter(), s2.iter()).map($f).galloc_scollect())),
246 )*
247 _ => (false, crate::value::Value::Null),
248 }
249 }
250 }
251 };
252}
253
254#[macro_export]
255/// Defines a macro for implementing a two-operand operation trait with optional evaluation.
256///
257///
258/// This macro generates implementations for the `Op2` trait for a specified type by providing a framework where specific pairs of input types can define conversion logic and evaluation.
259/// For each tuple of input types and a resulting type provided within the macro invocation, the macro matches on the value types and applies a provided closure `f` to corresponding elements of the input values.
260/// It utilizes the `itertools::izip!` macro to pair elements from both input lists, applying the transformation function `f`, and collecting the results into a new list.
261/// If the operation fails or an element cannot be converted, it uses a default value for the resulting type.
262/// The generated implementation returns a flag indicating whether the operation was successful for all elements and the new value.
263/// If the inputs do not match any specified type combinations, the operation returns `false` with a `Null` value.
264/// This approach facilitates the flexible application of operations over compatible pairings of types while maintaining robust default behavior.
265macro_rules! impl_op2_opt {
266 ($s:ident, $name:expr, $(($t1:ident, $t2:ident) -> $rt:ident { $f:expr }),*) => {
267
268 impl $crate::expr::ops::Op2 for $s {
269 fn cost(&self) -> usize { self.0 }
270 fn try_eval(&self, a1 : $crate::value::Value, a2 : $crate::value::Value) -> (bool, crate::value::Value) {
271 match (a1, a2) {
272 $(
273 (crate::value::Value::$t1(s1), crate::value::Value::$t2(s2)) => {
274 let mut flag = true;
275 let a = itertools::izip!(s1.iter(), s2.iter()).map($f).map(|f| { flag &= f.is_some(); f.unwrap_or($crate::default_value![$rt]) }).galloc_scollect();
276 (flag, crate::value::Value::$rt(a))
277 }
278 )*
279 _ => (false, crate::value::Value::Null),
280 }
281 }
282 }
283 };
284}
285
286#[macro_export]
287/// Defines a macro for implementing a ternary operation trait.
288///
289/// This macro, when invoked, generates an implementation of the `Op3` trait for a given structure.
290/// The trait includes two functions: `cost`, which returns a constant cost value associated with the operation, and `try_eval`, which attempts to evaluate the operation based on three input values.
291///
292///
293/// The `try_eval` function utilizes Rust's pattern matching to handle specific combinations of value types, applying a provided function using `itertools::izip` to iterate over the input values in tandem, mapping them to a result that is collected using `galloc_scollect`.
294/// If the provided input types do not match any of the specified patterns, the function returns a false success flag and a `Null` value, indicating an unsuccessful evaluation.
295/// This macro simplifies the creation of complex operations by automating the repetitive parts of defining the `Op3` implementations.
296macro_rules! impl_op3 {
297 ($s:ident, $name:expr, $(($t1:ident, $t2:ident, $t3:ident) -> $rt:ident { $f:expr }),*) => {
298
299 impl $crate::expr::ops::Op3 for $s {
300 fn cost(&self) -> usize { self.0 }
301 fn try_eval(&self, a1 : $crate::value::Value, a2 : $crate::value::Value, a3 : crate::value::Value) -> (bool, crate::value::Value) {
302 match (a1, a2, a3) {
303 $(
304 (crate::value::Value::$t1(s1), crate::value::Value::$t2(s2), crate::value::Value::$t3(s3)) =>
305 (true, crate::value::Value::$rt(itertools::izip!(s1.iter(), s2.iter(), s3.iter()).map($f).galloc_scollect())),
306 )*
307 _ => (false, crate::value::Value::Null),
308 }
309 }
310 }
311 };
312}
313
314#[macro_export]
315/// A macro for implementing ternary operations with optional characteristics.
316///
317/// This macro, `impl_op3_opt!`, simplifies the process of defining implementations for the `Op3` trait on a specified type (designated by `$s`).
318/// For each combination of argument types and return type specified, it generates the operation's logic by defining the `cost` and `try_eval` methods for the `Op3` trait.
319///
320///
321/// In `try_eval`, the macro handles different matching patterns of input argument types.
322/// It uses pattern matching to destructure given values into tuples of variant types defined in `$t1`, `$t2`, and `$t3`.
323/// The evaluation iterates over combined elements of these tuples using the `itertools::izip!` macro, applying an expression `$f` on them.
324/// This produces an output optionally handled via a `flag`, which determines success.
325/// If any element evaluation yields `None`, the default value specified for the return type `$rt` is used instead.
326/// The resulting values are collected using `galloc_scollect()` into the expected result type wrapped in `crate::value::Value::$rt`.
327/// If none of the specified patterns match, the operation defaults to returning a `false` flag and a `Value::Null`.
328macro_rules! impl_op3_opt {
329 ($s:ident, $name:expr, $(($t1:ident, $t2:ident, $t3:ident) -> $rt:ident { $f:expr }),*) => {
330
331 impl $crate::expr::ops::Op3 for $s {
332 fn cost(&self) -> usize { self.0 }
333 fn try_eval(&self, a1 : $crate::value::Value, a2 : $crate::value::Value, a3 : crate::value::Value) -> (bool, crate::value::Value) {
334 match (a1, a2, a3) {
335 $(
336 (crate::value::Value::$t1(s1), crate::value::Value::$t2(s2), crate::value::Value::$t3(s3)) => {
337 let mut flag = true
338 let a = itertools::izip!(s1.iter(), s2.iter(), s3.iter()).map($f).map(|f| { flag &= f.is_some(); f.unwrap_or($crate::default_value![$rt]) }).galloc_scollect();
339 (flag, crate::value::Value::$rt(a))
340 }
341 )*
342 _ => (false, crate::value::Value::Null),
343 }
344 }
345 }
346 };
347}