Skip to main content

mini_kanren/
macros.rs

1//! Macros for embedding miniKANREN as DSL in Rust
2
3/// Creates a goal that succeeds if any of its subgoals succeeds
4#[macro_export]
5macro_rules! disj {
6    () => { $crate::prelude::fail() };
7    ($g:expr) => { $g };
8    ($g0:expr; $($g:expr);*) => { $crate::prelude::disj2($g0, $crate::disj!($($g);*))}
9}
10
11/// Creates a goal that succeeds if all of its subgoals succeed
12#[macro_export]
13macro_rules! conj {
14    () => { $crate::prelude::succeed() };
15    ($g:expr) => { $g };
16    ($g0:expr, $($g:expr),*) => { $crate::prelude::conj2($g0, $crate::conj!($($g),*))}
17}
18
19/// Define a relation.
20/// A relation is a function that creates a goal.
21#[macro_export]
22macro_rules! defrel {
23
24    ($(#[$outer:meta])* pub $name:ident($($args:ident),*) { $($g:expr),* $(,)? }) => {
25        $(#[$outer])*
26        pub fn $name($($args: impl 'static + Into<$crate::prelude::Value>),*) -> impl $crate::prelude::Goal<$crate::prelude::Substitution<'static>> {
27            defrel!(@body: $($args),* { $($g),* })
28        }
29    };
30
31    ($(#[$outer:meta])* $name:ident($($args:ident),*) { $($g:expr),* $(,)? }) => {
32        $(#[$outer])*
33        fn $name($($args: impl 'static + Into<$crate::prelude::Value>),*) -> impl $crate::prelude::Goal<$crate::prelude::Substitution<'static>> {
34            defrel!(@body: $($args),* { $($g),* })
35        }
36    };
37
38    ($(#[$outer:meta])* pub trace $name:ident($($args:ident),*) { $($g:expr),* $(,)? }) => {
39        $(#[$outer])*
40        pub fn $name($($args: impl 'static + Into<$crate::prelude::Value>),*) -> impl $crate::prelude::Goal<$crate::prelude::Substitution<'static>> {
41            defrel!(@tracebody: $name, $($args),* { $($g),* })
42        }
43    };
44
45    ($(#[$outer:meta])* trace $name:ident($($args:ident),*) { $($g:expr),* $(,)? }) => {
46        $(#[$outer])*
47        fn $name($($args: impl 'static + Into<$crate::prelude::Value>),*) -> impl $crate::prelude::Goal<$crate::prelude::Substitution<'static>> {
48            defrel!(@tracebody: $name, $($args),* { $($g),* })
49        }
50    };
51
52    // alternate syntax: separate goals with ;
53    (pub $name:ident($($args:ident),*) { $($g:expr);* $(;)? }) => {
54        defrel!{pub $name($($args),*) { $($g),* }}
55    };
56
57    // alternate syntax: separate goals with ;
58    ($name:ident($($args:ident),*) { $($g:expr);* $(;)? }) => {
59        defrel!{$name($($args),*) { $($g),* }}
60    };
61
62    (@body: $($args:ident),* { $($g:expr),* }) => {{
63        $(
64            let $args = $args.into();
65        )*
66        move |s| {
67            $(
68                let $args = $args.clone();
69            )*
70            $crate::prelude::Stream::suspension(move || $crate::conj!($($g),*).apply(s))
71        }
72    }};
73
74    (@tracebody: $name:ident, $($args:ident),* { $($g:expr),* }) => {{
75        $(
76            let $args = $args.into();
77        )*
78        move |s: $crate::prelude::Substitution<'static>| {
79            {
80                print!("{} apply:", stringify!($name));
81                let sx = {
82                    $(
83                        let $args = $args.clone();
84                        print!(" {}={:?}", stringify!($args), s.reify(&$args));
85                    )*
86                    $crate::conj!($($g),*).apply(s.clone())
87                };
88                match sx {
89                   $crate::prelude::Stream::Pair(first_sub, next) => {
90                        print!(" succeeded with");
91                        $(print!(" {}={:?}", stringify!($args), first_sub.reify(&$args));)*
92                        if next.is_empty() {
93                            println!();
94                        } else {
95                            println!(" ...");
96                        }
97                   }
98                   $crate::prelude::Stream::Suspension(_) => println!(" ..."),
99                   $crate::prelude::Stream::Empty => println!(" failed."),
100                }
101            }
102
103            $(
104                let $args = $args.clone();
105            )*
106
107            $crate::prelude::Stream::suspension(move || {
108                let sx = $crate::conj!($($g),*).apply(s);
109                sx
110            })
111        }
112    }};
113}
114
115/// Define a relation.
116/// A relation is a function that creates a goal.
117#[macro_export]
118macro_rules! defmatch {
119
120    ($(#[$outer:meta])* pub $name:ident($($args:ident),*) { $($body:tt)* }) => {
121        defrel! {
122            $(#[$outer])*
123            pub $name($($args),*) {
124                $crate::matche! { list![$($args.clone()),*],
125                    $($body)*
126                }
127            }
128        }
129    };
130
131    ($(#[$outer:meta])* $name:ident($($args:ident),*) { $($body:tt)* }) => {
132        defrel! {
133            $(#[$outer])*
134            $name($($args),*) {
135                $crate::matche! { list![$($args.clone()),*],
136                    $($body)*
137                }
138            }
139        }
140    };
141}
142
143/// Run one or more goals.
144///
145/// The syntax `run!(n, var(s), goal1, goal2, ...)` produces at most n
146/// solutions in Scheme you wold write `(run n var(s) goal1 goal2 ...)`.
147/// The syntax `run!(*, var(s), goal1, goal2, ...)` produces all
148/// solutions in Scheme you wold write `(run* var(s) goal1 goal2 ...)`.
149/// The latter may result in an infinite recursion which eventually
150/// crashes with a stack overflow.
151///
152/// We support an additional syntax `run!(var(s), goal1, goal2, ...)`
153/// that returns a (possibly infinite) iterator over all solutions.
154#[macro_export]
155macro_rules! run {
156    (*, ($($x:ident),*), $($body:tt)*) => {
157        $crate::run!(@ *, ($($x),*), $($body)*)
158    };
159
160    (*, $q:ident, $($g:expr),* $(,)?) => {
161        $crate::run!(@ *, $q, $($g),*)
162    };
163
164    ($n:expr, ($($x:ident),*), $($body:tt)*) => {
165        $crate::run!(@ $n, ($($x),*), $($body)*)
166    };
167
168    ($n:tt, $q:ident, $($g:expr),* $(,)?) => {
169        $crate::run!(@ $n, $q, $($g),*)
170    };
171
172    (($($x:ident),*), $($body:tt)*) => {
173        $crate::run!(@ iter, ($($x),*), $($body)*)
174    };
175
176    ($q:ident, $($g:expr),* $(,)?) => {
177        $crate::run!(@ iter, $q, $($g),*)
178    };
179
180    (@ $n:tt, ($($x:ident),*), $($g:expr),* $(,)?) => {
181        $crate::run!(@ $n, q, {
182            $crate::fresh!(
183                ($($x),*),
184                $crate::prelude::eq(vec![$($crate::prelude::Value::var($x.clone())),*], q),
185                $($g),*
186            )
187        })
188    };
189
190    (@ *, $q:ident, $($g:expr),* $(,)?) => {{
191        let $q = $crate::prelude::Var::new(stringify!($q));
192        let var = $crate::prelude::Value::var($q.clone());
193        $crate::conj!($($g),*).run_inf().map(move |s| s.reify(&var))
194    }};
195
196    (@ iter, $q:ident, $($g:expr),* $(,)?) => {{
197        let $q = $crate::prelude::Var::new(stringify!($q));
198        let var = $crate::prelude::Value::var($q.clone());
199        $crate::conj!($($g),*).iter().map(move |s| s.reify(&var))
200    }};
201
202    (@ $n:expr, $q:ident, $($g:expr),* $(,)?) => {{
203        let $q = $crate::prelude::Var::new(stringify!($q));
204        let var = $crate::prelude::Value::var($q.clone());
205        $crate::conj!($($g),*).run($n).map(move |s| s.reify(&var))
206    }};
207}
208
209/// Bind fresh variables with scope inside the body of `fresh!`.
210#[macro_export]
211macro_rules! fresh {
212    (($($x:ident),*), $($g:expr),* $(,)?) => {{
213        $( let $x = $crate::prelude::Var::new(stringify!($x)); )*
214        $crate::conj!($($g),*)
215    }}
216}
217
218/// Creates a goal that succeeds if any of its *lines* succeeds.
219/// Every successful *line* contributes one or more values.
220///
221/// A *line* (separated by `;`) succeeds if all of its
222/// goals (separated by `,`) succeed.
223#[macro_export]
224macro_rules! conde {
225    ( $($($g:expr),*;)* ) => {
226        $crate::disj!($($crate::conj!( $($g),*));*)
227    }
228}
229
230/// Creates a goal that succeeds if any of its *lines* succeeds.
231/// Only the first *line* that succeeds can contribute values.
232///
233/// A *line* (separated by `;`) succeeds if all of its
234/// goals (separated by `,`) succeed.
235#[macro_export]
236macro_rules! conda {
237    ($($g:expr),*) => { $crate::conj!($($g),*) };
238
239    ($g0:expr, $($g:expr),+; $($rest:tt)*) => {
240        $crate::prelude::ifte($g0, $crate::conj!($($g),*), $crate::conda!($($rest)*))
241    };
242
243    ($g0:expr; $($rest:tt)*) => {
244        $crate::prelude::ifte($g0, $crate::succeed(), $crate::conda!($($rest)*))
245    };
246}
247
248/// `Condu!` behaves like `conda!`, except that a successful line
249/// succeeds only once.
250#[macro_export]
251macro_rules! condu {
252    ($g0:expr, $($g:expr),+;) => {
253        $crate::conj!($crate::once($g0), $($g),*)
254    };
255
256    ($g0:expr;) => {
257        $crate::once($g0)
258    };
259
260    ($g0:expr, $($g:expr),+; $($rest:tt)*) => {
261        $crate::prelude::ifte($crate::once($g0), $crate::conj!($($g),*), $crate::condu!($($rest)*))
262    };
263
264    ($g0:expr; $($rest:tt)*) => {
265        $crate::prelude::ifte($crate::once($g0), $crate::succeed(), $crate::condu!($($rest)*))
266    };
267}
268
269/// `Matche!`  behaves like `conde!` but allows pattern matching.
270/// This simplifies destructuring and creation of fresh variables.
271///
272/// Every successful *line* contributes one or more values.
273#[macro_export]
274macro_rules! matche {
275    ( $val:expr, $( $pat:tt => $($goal:expr),* ; )+ ) => {
276        $crate::disj! {
277            $( $crate::matche!(@match: $val, $pat => $($goal),*) );*
278        }
279    };
280
281    // $val matches a single-element list
282    (@match: $val:expr, ($h:tt) => $($goal:expr),*) => {
283        fresh! { (h),
284            $crate::goals::list::conso(h, (), $val.clone()),
285            $crate::matche!(@match: h, $h => $($goal),*)
286        }
287    };
288
289    // $val matches a pair
290    (@match: $val:expr, ($h:tt ; $t:tt) => $($goal:expr),*) => {
291        fresh! { (h, t),
292            $crate::goals::list::conso(h, t, $val.clone()),
293            $crate::matche!(@match: h, $h => $crate::matche!(@match: t, $t => $($goal),*))
294        }
295    };
296
297    // $val matches a a list with at least one item
298    (@match: $val:expr, ($h:tt, $($rest:tt)*) => $($goal:expr),*) => {
299        fresh! { (h, t),
300            $crate::goals::list::conso(h, t, $val.clone()),
301            $crate::matche!(@match: h, $h => $crate::matche!(@match: t, ($($rest)*) => $($goal),*))
302        }
303    };
304
305    // $val matches anything - effectively it's ignored
306    (@match: $val:expr, _ => $($goal:expr),*) => {
307        $crate::conj!{
308            $($goal),*
309        }
310    };
311
312    // $val matches a name - it is bound to a variable with that name
313    (@match: $val:expr, $v:ident => $($goal:expr),*) => {
314        fresh! { ($v),
315            $crate::prelude::eq($val.clone(), $v),
316            $($goal),*
317        }
318    };
319
320    // $val matches an expression - they are unified
321    (@match: $val:expr, $c:expr => $($goal:expr),*) => {
322        $crate::conj!{
323            $crate::prelude::eq($val.clone(), $c),
324            $($goal),*
325        }
326    };
327}
328
329#[cfg(test)]
330mod tests {
331    use crate::testing::{fails, has_unique_solution, succeeds};
332    use crate::{eq, fail, list, succeed};
333    use crate::{RawGoal, Value};
334
335    #[test]
336    fn matching_anything_succeeds_always() {
337        succeeds(matche! { q,
338            _ => ;
339        });
340    }
341
342    #[test]
343    fn matching_fails_if_any_further_goals_fail() {
344        fails(matche! { q,
345            _ => succeed(), fail(), succeed();
346        });
347    }
348
349    #[test]
350    fn matching_a_constant_binds_value() {
351        has_unique_solution(
352            run!(
353                q,
354                matche! { q,
355                    1 => ;
356                }
357            ),
358            1.into(),
359        );
360    }
361
362    #[test]
363    fn each_successful_matching_line_contributes_a_value() {
364        assert_eq!(
365            run!(*, q,
366                matche! { q,
367                    1 => ;
368                    2 => fail();
369                    3 => ;
370                }
371            )
372            .into_vec(),
373            vec![1, 3],
374        );
375    }
376
377    #[test]
378    fn matching_deconstructs_lists() {
379        assert_eq!(
380            run!(*, q,
381                matche! { q,
382                    (_) => ;
383                    (_, _) => ;
384                    (_, _ ; _) => ;
385                }
386            )
387            .into_vec(),
388            vec![
389                list![Value::rv(0)],
390                list![Value::rv(0), Value::rv(1)],
391                list![Value::rv(0), Value::rv(1) ; Value::rv(2)],
392            ],
393        );
394    }
395
396    #[test]
397    fn matching_deconstructs_lists_and_binds_fresh_vars() {
398        assert_eq!(
399            run!(*, q,
400                matche! { q,
401                    (a) => eq(a, 1);
402                    (b, c) => eq(b, c);
403                    (a, _ ; d) => eq(d, 2);
404                }
405            )
406            .into_vec(),
407            vec![
408                list![1],
409                list![Value::rv(0), Value::rv(0)],
410                list![Value::rv(0), Value::rv(1) ; 2],
411            ],
412        );
413    }
414
415    #[test]
416    fn matche_can_escape_outside_vars() {
417        let x = 42;
418        has_unique_solution(
419            run!(
420                q,
421                matche! { q,
422                    // The trick here is that wrapping x in {} turns it into an expression
423                    ({x}) => ;
424                }
425            ),
426            list![42],
427        );
428    }
429
430    #[test]
431    fn matche_matches_values() {
432        has_unique_solution(
433            run!(
434                q,
435                matche! { list![1, q],
436                    (b, c) => eq(b, c);
437                }
438            ),
439            1.into(),
440        );
441    }
442}