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}