Skip to main content

ttt_derive/
lib.rs

1mod attributes;
2mod utils;
3
4use proc_macro_error::proc_macro_error;
5use synstructure::decl_derive;
6
7mod debruijn_indexed;
8decl_derive! { [DeBruijnIndexed, attributes(var_index, variable, binding, metadata)] =>
9    /// Derives an implementation of the `ttt::DeBruijnIndexed` trait.
10    ///
11    /// # Annotating variables
12    ///
13    /// The fields representing de Bruijn indices in a type should either:
14    ///  - be a field of type `usize`, marked with the `#[var_index]` attribute
15    ///  - or a type which wraps a de Bruijn index, marked with the `#[variable]` attribute
16    ///
17    /// ## Example
18    /// ```
19    /// use ttt::DeBruijnIndexed;
20    ///
21    /// #[derive(DeBruijnIndexed)]
22    /// struct Variable(#[var_index] usize);
23    ///
24    ///
25    /// #[derive(DeBruijnIndexed)]
26    /// enum ContainsAVar {
27    ///     HasNoVar,
28    ///     HasAVar(#[variable] Variable),
29    /// }
30    /// ```
31    ///
32    /// # Binders
33    ///
34    /// Fields which represent syntax nodes under a binder should be annotated with the `#[binding]` attribute.
35    /// These fields will be implemented with logic to shift target indices when recursively applying operations to their indices.
36    ///
37    /// ## Example
38    /// ```
39    /// use ttt::DeBruijnIndexed;
40    ///
41    /// #[derive(DeBruijnIndexed)]
42    /// enum LambdaTerm {
43    ///     Var(#[var_index] usize),
44    ///     Lam(#[binding] Box<LambdaTerm>),
45    ///     App(Box<LambdaTerm>, Box<LambdaTerm>),
46    /// }
47    ///
48    /// #[derive(DeBruijnIndexed)]
49    /// enum LambdaTermWithInnerVarType {
50    ///     Var(#[variable] Variable),
51    ///     Lam(#[binding] Box<LambdaTerm>),
52    ///     App(Box<LambdaTerm>, Box<LambdaTerm>),
53    /// }
54    ///
55    /// #[derive(DeBruijnIndexed)]
56    /// struct Variable(#[var_index] usize);
57    /// ```
58    ///
59    /// # Skipping metadata
60    ///
61    /// Fields which do not contain AST data can be marked with the `#[metadata]` attribute and they will be ignored when applying variable operations.
62    /// This might be used for example if storing a string representation of a variable name alongside its de Bruijn index.
63    /// For the purposes of this macro, `#[var_name]` and `#[binding_name]` have equivalent effects to `#[metadata]`, but these have more specific effects in other macros.
64    ///
65    /// ## Example
66    /// ```
67    /// #[derive(ttt::DeBruijnIndexed)]
68    /// enum LambdaTerm {
69    ///     Var {
70    ///         #[metadata] name: String,
71    ///         #[var_index] dbn_index: usize
72    ///     },
73    ///     Lam(#[binding] Box<LambdaTerm>),
74    ///     App(Box<LambdaTerm>, Box<LambdaTerm>),
75    /// }
76    ///
77    /// ```
78    #[proc_macro_error]
79    debruijn_indexed::derive
80}
81
82mod substitute;
83decl_derive! { [Substitute, attributes(var_index, subst_types, variable, binding)] =>
84    /// Derives implementations of the `ttt::Substitute` trait.
85    ///
86    /// The macro has two modes of operation:
87    ///  - the typical case is 'deep substitution', replacing a variable inside a data type which an expression of the same type,
88    ///  - alternatively, if the type is a *variable wrapper* (meaning a struct containing a variable, or an enum where every variant contains a variable), then we implement 'shallow substitution' which will replace the entire structure with an expression, or embed the variable into the target type.
89    ///
90    /// # Deep Substitution
91    ///
92    /// In the typical case, a type `T` deriving `Substitute` will implement `Substitute<T, Target=T>`.
93    ///
94    /// ## Example
95    /// ```
96    /// use ttt::{DeBruijnIndexed, Substitute};
97    ///
98    /// // LambdaExpr: Substitute<LambdaExpr, Target = LambdaExpr>
99    /// #[derive(Clone, DeBruijnIndexed, Substitute)]
100    /// enum LambdaExpr {
101    ///     Var(#[var_index] usize),
102    ///     Lambda(#[binding] Box<LambdaExpr>),
103    ///     App(Box<LambdaExpr>, Box<LambdaExpr>),
104    /// }
105    /// ```
106    ///
107    /// ## Substituting for multiple types
108    ///
109    /// A list of types to be substituted for may be specified as an annotation on the deriving type, as `#[subst_types(T, U, ...)]`.
110    /// In this case `Substitute<T>` will be implemented for each type `T` specified in the list.
111    /// This may be useful if your type contains an inner type with its own substitute implementation which operates on different types.
112    ///
113    /// ### Example
114    /// ```
115    /// use ttt::{DeBruijnIndexed, Substitute};
116    ///
117    /// // LambdaValue: Substitute<LambdaValue, Target = LambdaValue>
118    /// // LambdaExpr: Substitute<LambdaValue, Target = LambdaExpr>
119    /// // LambdaExpr: Substitute<LambdaExpr, Target = LambdaExpr>
120    ///
121    /// #[derive(Clone, DeBruijnIndexed, Substitute)]
122    /// #[subst_types(LambdaExpr, LambdaValue)]
123    /// enum LambdaValue {
124    ///     Var(#[var_index] usize),
125    ///     Lambda(#[binding] Box<LambdaValue>),
126    /// }
127    ///
128    /// #[derive(Clone, DeBruijnIndexed, Substitute)]
129    /// #[subst_types(LambdaExpr, LambdaValue)]
130    /// enum LambdaExpr {
131    ///     Value(LambdaValue),
132    ///     Var(#[var_index] usize),
133    ///     Lambda(#[binding] Box<LambdaExpr>),
134    ///     App(Box<LambdaExpr>, Box<LambdaExpr>),
135    /// }
136    /// ```
137    ///
138    /// # Shallow substitution
139    ///
140    /// A variable is any field annotated with the `#[variable]` attribute, or any field of type `usize` which is annotated with `#[var_index]`.
141    /// A variable wrapper is any struct containing a variable, or an enum where every variant contains a variable.
142    /// If a type `Var` is a variable wrapper, then `Substitute<Expr, Target=Expr>` can be derived on `Var` for any type `Expr`, as long as `Expr: From<Var>`.
143    ///
144    /// ### Example
145    /// ```
146    /// use ttt::{DeBruijnIndexed, Substitute};
147    ///
148    /// #[derive(PartialEq, Debug, Clone, DeBruijnIndexed, Substitute)]
149    /// enum LambdaExpr {
150    ///     Var(#[variable] Variable),
151    ///     Lambda(#[binding] Box<LambdaExpr>),
152    ///     App(Box<LambdaExpr>, Box<LambdaExpr>),
153    ///     Unit,
154    /// }
155    ///
156    /// // Variable: Substitute<LambdaExpr>
157    /// #[derive(PartialEq, Debug, Clone, DeBruijnIndexed, Substitute)]
158    /// #[subst_types(LambdaExpr)]
159    /// struct Variable {
160    ///     #[var_index] index: usize,
161    ///     #[metadata] name: String
162    /// }
163    ///
164    /// impl From<Variable> for LambdaExpr {
165    ///     fn from(value: Variable) -> Self {
166    ///         LambdaExpr::Var(value)
167    ///     }
168    /// }
169    ///
170    /// let variable = Variable {
171    ///     index: 0,
172    ///     name: "".to_string(),
173    /// };
174    ///
175    /// let lambda_expr = LambdaExpr::Lambda( Box::new(LambdaExpr::Unit) );
176    ///
177    /// let substituted: Result<LambdaExpr, _> = variable.substitute(&lambda_expr, 0);
178    /// assert_eq!(substituted, Ok(lambda_expr.clone()));
179    ///
180    /// // No substitution occurs because the variables do not match, but
181    /// // `variable` is wrapped in a `LambdaExpr::Var(..)` so that it has
182    /// // the correct type.
183    /// let substituted2 = variable.substitute(&lambda_expr, 1);
184    /// assert_eq!(substituted2, Ok(LambdaExpr::Var(variable)));
185    /// ```
186    #[proc_macro_error]
187    substitute::derive
188}
189
190mod evaluate;
191decl_derive! { [Evaluate, attributes(eval_target, context_type, binding, evaluate_with, evaluate_pattern, metadata, var_name, eval_error_type)] =>
192    /// Derives an implementation of the [ttt::Evaluate] trait.
193    /// 
194    /// The default behaviour sets the associated type [Target](ttt::Evaluate::Target) to `Self`, and will call [evaluate](ttt::Evaluate::evaluate) recursively on each field 
195    /// and will reconstruct the current variant from the evaluated fields.
196    /// Custom evaluation behaviour can be specified with attributes on variants.
197    /// 
198    /// # Evaluator patterns
199    /// 
200    /// The simplest way to specify custom evaluation logic is with the `#[evaluate_pattern {...}]` attribute.
201    /// This attribute accepts a match arm which matches a list of evaluated fields, and yields the evaluated expression in the arm body.
202    /// 
203    /// **TODO**: This should accept multiple match arms
204    /// 
205    /// ```
206    /// use ttt::Evaluate;
207    /// 
208    /// #[derive(Debug, Clone, PartialEq, Evaluate)]
209    /// enum AddExpr {
210    ///     Num(#[metadata] i32),
211    ///     #[evaluate_pattern { 
212    ///         (AddExpr::Num(lhs), AddExpr::Num(rhs)) => AddExpr::Num(lhs + rhs) 
213    ///     }]
214    ///     Add(Box<AddExpr>, Box<AddExpr>),
215    /// }
216    /// 
217    /// use AddExpr::*;
218    /// let expr = Add(
219    ///     Box::new(Num(19)), 
220    ///     Box::new(Add(
221    ///         Box::new(Num(12)), 
222    ///         Box::new(Num(11))
223    ///     ))
224    /// );
225    /// 
226    /// let evalled = expr.evaluate_closed(false);
227    /// 
228    /// assert_eq!(evalled, Ok(Num(42)));
229    /// ```
230    /// 
231    /// ## Returning errors 
232    /// Evaluate patterns can use early returns to yield errors. By default the error type is set to [ttt::EvalError], but this can be overridden with the `#[eval_error_type]` attribute
233    /// 
234    /// ```
235    /// use ttt::Evaluate;
236    /// 
237    /// #[derive(Debug, PartialEq)]
238    /// enum DivError {
239    ///     DivideByZero,
240    /// }
241    /// 
242    /// #[derive(Debug, Clone, PartialEq, Evaluate)]
243    /// #[eval_error_type(DivError)]
244    /// enum DivExpr {
245    ///     Num(#[metadata] f32),
246    ///     #[evaluate_pattern { (DivExpr::Num(lhs), DivExpr::Num(rhs)) => 
247    ///         if rhs == 0.0 {
248    ///             return Err(DivError::DivideByZero)
249    ///         } else {
250    ///             DivExpr::Num(lhs / rhs) 
251    ///         }
252    ///     }]
253    ///     Div(Box<DivExpr>, Box<DivExpr>),
254    /// } 
255    /// ```
256    /// 
257    /// A more common usage pattern for this is to use the `?` operator to propagate errors from other function calls.
258    /// 
259    /// ```
260    /// use ttt::{DeBruijnIndexed, Substitute, Evaluate};
261    /// 
262    /// #[derive(Clone, DeBruijnIndexed, Substitute, Evaluate, PartialEq, Debug)]
263    /// enum LambdaExpr {
264    ///     Var(#[var_index] usize),
265    ///     Lambda(#[binding] Box<LambdaExpr>),
266    ///     #[evaluate_pattern {
267    ///         (LambdaExpr::Lambda(body), arg) => body.substitute(&arg, 0)?
268    ///     }]
269    ///     App(Box<LambdaExpr>, Box<LambdaExpr>),
270    /// } 
271    /// ```
272    /// 
273    /// In this example, the default error type [ttt::EvalError] implements `From<ttt::SubstError>`, so the `?` operator will convert any errors returned by `body.substitute(...)`
274    /// and return them as an [EvalError](ttt::EvalError).
275    /// 
276    /// Evaluator patterns cannot access the evaluation context because they are intended for expressing short transformations on the syntax nodes.
277    /// If you need to access the context you should specify an evaluator function, detailed in the next section.
278    /// 
279    /// # Evaluator Functions
280    /// 
281    /// If you require a larger code block to evaluate a variant, or you need access to the context variable, you can extract the evaluation logic into a separate function
282    /// and specify it with the `#[evaluate_with(...)]` attribute.
283    /// This attribute accepts as a parameter any expression which resolves to a function with the type 
284    /// `(&Context, EvalledField1, EvalledField2, ...) -> Result<Target, Error>`.
285    /// 
286    /// ```
287    /// use ttt::{DeBruijnIndexed, Substitute, Evaluate, Context};
288    /// 
289    /// #[derive(Clone, DeBruijnIndexed, Substitute, Evaluate, PartialEq, Debug)]
290    /// enum LambdaExpr {
291    ///     #[evaluate_with(lookup_var)]
292    ///     Var(#[var_index] usize),
293    ///     Lambda(#[binding] Box<LambdaExpr>),
294    ///     #[evaluate_pattern {
295    ///         (LambdaExpr::Lambda(body), arg) => body.substitute(&arg, 0)?
296    ///     }]
297    ///     App(Box<LambdaExpr>, Box<LambdaExpr>),
298    /// } 
299    /// 
300    /// fn lookup_var(ctx: &<LambdaExpr as Evaluate>::Context, var_index: usize) -> Result<LambdaExpr, ttt::EvalError> {
301    ///     match ctx.get(var_index).unwrap() {
302    ///         Some(value) => Ok(value),
303    ///         None => Ok(LambdaExpr::Var(var_index))
304    ///     }
305    /// }
306    /// ```
307    /// 
308    /// The above example could be implemented automatically in a macro for any variant containing a variable, but we leave this to the user to allow flexibility in handling
309    /// variable errors. 
310    /// 
311    /// # Evaluating into a different type 
312    /// 
313    /// ```
314    /// use ttt::{DeBruijnIndexed, Substitute, Evaluate};
315    /// 
316    /// #[derive(Clone, DeBruijnIndexed, Substitute, PartialEq, Debug)]
317    /// enum LambdaValue {
318    ///     Var(#[var_index] usize),
319    ///     Lambda(#[binding] Box<LambdaValue>)
320    /// }
321    /// 
322    /// #[derive(Clone, DeBruijnIndexed, Substitute, Evaluate, PartialEq, Debug)]
323    /// //#[eval_target(LambdaValue)]
324    /// enum LambdaExpr {
325    ///     Var(#[var_index] usize),
326    ///     Lambda(#[binding] Box<LambdaExpr>),
327    ///     //#[evaluate_pattern { _ => todo!() }]
328    ///     App(Box<LambdaExpr>, Box<LambdaExpr>),
329    /// } 
330    /// ```
331    /// 
332    /// # Unwrapping Variants
333    /// 
334    /// ```
335    /// enum Expr {
336    /// }
337    /// struct App {
338    ///     fst: Box<Expr>,
339    ///     snd: Box<Expr>,
340    /// }
341    /// 
342    /// ```
343    #[proc_macro_error]
344    evaluate::derive
345}