gramma/ast/
transform.rs

1//! Transformations for use in the `#[transform(...)]` attribute in invocations of the
2//! (define_rule!)[crate::define_rule] macro.
3#![allow(non_camel_case_types)]
4
5use core::marker::PhantomData;
6
7use crate::{internal_prelude::*, Rule};
8
9use super::{
10    CompoundToken, DelimitedList, Discard, DualParse, Empty, Ignore, NotEmpty, NotParse,
11    TransformList,
12};
13
14/// This trait defines the behavior of a transformation.
15pub trait TransformInto<Out> {
16    /// Actual rule that should be parsed.
17    type Input;
18    /// Transform the parsed rule into the target rule.
19    fn transform(input: Self::Input) -> Out;
20}
21
22/// Leaves the target rule unchanged.
23#[non_exhaustive]
24pub struct identity {}
25
26impl<T> TransformInto<T> for identity {
27    type Input = T;
28
29    fn transform(input: Self::Input) -> T {
30        input
31    }
32}
33
34/// Applies transform `B` to the target rule, then applies transform `A` to the transformed rule.
35pub struct compose<A, B> {
36    _a: A,
37    _b: B,
38}
39
40impl<A, B, Out> TransformInto<Out> for compose<A, B>
41where
42    A: TransformInto<B::Input>,
43    B: TransformInto<Out>,
44{
45    type Input = A::Input;
46    fn transform(input: Self::Input) -> Out {
47        B::transform(A::transform(input))
48    }
49}
50
51/// Parse and discard `Delim` between each item in the target rule.
52/// If `TRAIL` is true or unspecified, allow an optional instance of `Delim` after the last item.
53/// Useful when matching a `Vec` of rules, for example a comma-separated list.
54pub struct delimited<Delim: Rule, const TRAIL: bool = true> {
55    _delim: PhantomData<Delim>,
56}
57
58impl<T: Rule, Delim: Rule, const TRAIL: bool> TransformInto<Vec<T>> for delimited<Delim, TRAIL> {
59    type Input = DelimitedList<T, Delim, TRAIL>;
60
61    fn transform(input: Self::Input) -> Vec<T> {
62        input.items
63    }
64}
65
66impl<T: Rule, X: TransformInto<T>, Delim: Rule, const TRAIL: bool>
67    TransformInto<TransformList<T, X, Delim>> for delimited<Delim, TRAIL>
68{
69    type Input = TransformList<T, X, Delim, TRAIL>;
70
71    fn transform(input: Self::Input) -> TransformList<T, X, Delim> {
72        TransformList::new(input.items)
73    }
74}
75
76/// Parse an _optional_ `S` before the target rule and discard the result if found.
77/// Useful for ignoring optional whitespace before the target rule.
78pub struct ignore_before<S> {
79    _space: PhantomData<S>,
80}
81
82impl<T: Rule, S: Rule> TransformInto<T> for ignore_before<S> {
83    type Input = (Ignore<S>, T);
84
85    fn transform((_, out): Self::Input) -> T {
86        out
87    }
88}
89
90/// Parse an _optional_ `S` after the target rule and discard the result if found.
91/// Useful for ignoring optional whitespace after the target rule.
92pub struct ignore_after<S> {
93    _space: PhantomData<S>,
94}
95
96impl<T: Rule, S: Rule> TransformInto<T> for ignore_after<S> {
97    type Input = (T, Ignore<S>);
98
99    fn transform((out, _): Self::Input) -> T {
100        out
101    }
102}
103
104/// Parse an _optional_ `S1` before and `S2` after the target rule and discard the results if found.
105/// Useful for ignoring optional whitespace around the target rule.
106pub type ignore_around<S1, S2 = S1> = compose<ignore_before<S1>, ignore_after<S2>>;
107
108/// Parse a _required_ `S` before the target rule and discard the result.
109/// Useful for matching punctuation before the target rule.
110pub struct discard_before<S> {
111    _space: PhantomData<S>,
112}
113
114impl<T: Rule, S: Rule> TransformInto<T> for discard_before<S> {
115    type Input = (Discard<S>, T);
116
117    fn transform((_, out): Self::Input) -> T {
118        out
119    }
120}
121
122/// Parse a _required_ `S` after the target rule and discard the result.
123/// Useful for matching punctuation after the target rule.
124pub struct discard_after<S> {
125    _space: PhantomData<S>,
126}
127
128impl<T: Rule, S: Rule> TransformInto<T> for discard_after<S> {
129    type Input = (T, Discard<S>);
130
131    fn transform((out, _): Self::Input) -> T {
132        out
133    }
134}
135
136/// Parse a _required_ `S1` before and `S2` after the target rule and discard the results.
137/// Useful for matching punctuation around the target rule.
138pub type discard_around<S1, S2 = S1> = compose<discard_before<S1>, discard_after<S2>>;
139
140/// Apply the transformation `X` to the inner value.
141/// Useful when matching an `Option<T>` and you want the rules to apply to `T`.
142pub struct map<X> {
143    _x: PhantomData<X>,
144}
145
146impl<In: Rule, Out, X: TransformInto<Out, Input = In>> TransformInto<Option<Out>> for map<X> {
147    type Input = Option<In>;
148
149    fn transform(input: Self::Input) -> Option<Out> {
150        input.map(X::transform)
151    }
152}
153
154/// Apply the transformation `X` to each item in the target rule.
155/// Useful when matching a `Vec` of rules.
156pub struct for_each<X> {
157    _x: PhantomData<X>,
158}
159
160impl<In: Rule, Out, X: TransformInto<Out, Input = In>> TransformInto<Vec<Out>> for for_each<X> {
161    type Input = TransformList<Out, X>;
162
163    fn transform(input: Self::Input) -> Vec<Out> {
164        input.items
165    }
166}
167
168impl<
169        In: Rule,
170        Out,
171        In1: Rule,
172        X: TransformInto<In1, Input = In>,
173        X1: TransformInto<Out, Input = In1>,
174        Delim: Rule,
175        const TRAIL: bool,
176        const PREFER_SHORT: bool,
177    > TransformInto<TransformList<Out, X1, Delim, TRAIL, PREFER_SHORT>> for for_each<X>
178{
179    type Input = TransformList<Out, compose<X, X1>, Delim, TRAIL, PREFER_SHORT>;
180
181    fn transform(input: Self::Input) -> TransformList<Out, X1, Delim, TRAIL, PREFER_SHORT> {
182        TransformList::new(input.items)
183    }
184}
185
186/// Prefer to end a list rather than continue parsing items.
187#[non_exhaustive]
188pub struct prefer_short<const PREFER_SHORT: bool = true> {}
189
190impl<T: Rule, const PREFER_SHORT: bool> TransformInto<Vec<T>> for prefer_short<PREFER_SHORT> {
191    type Input = TransformList<T, identity, Empty, false, PREFER_SHORT>;
192
193    fn transform(input: Self::Input) -> Vec<T> {
194        input.items
195    }
196}
197
198impl<
199        T: Rule,
200        X: TransformInto<T>,
201        Delim: Rule,
202        const TRAIL: bool,
203        const PREFER_SHORT: bool,
204        const PREFER_SHORT1: bool,
205    > TransformInto<TransformList<T, X, Delim, TRAIL, PREFER_SHORT1>>
206    for prefer_short<PREFER_SHORT>
207{
208    type Input = TransformList<T, X, Delim, TRAIL, PREFER_SHORT>;
209
210    fn transform(input: Self::Input) -> TransformList<T, X, Delim, TRAIL, PREFER_SHORT1> {
211        TransformList::new(input.items)
212    }
213}
214
215/// Parse as a single token for lookahead purposes.
216#[non_exhaustive]
217pub struct compound_token {}
218
219impl<T: Rule> TransformInto<T> for compound_token {
220    type Input = CompoundToken<T>;
221
222    fn transform(input: Self::Input) -> T {
223        input.value
224    }
225}
226
227/// Initially parse as `Outer`.
228/// If `Outer` successfully parses, parse the target rule over the section matched by `Outer`.
229/// Discard the parsed `Outer`.
230///
231/// If you wish to keep both the inner and outer parses, see [DualParse].
232pub struct parse_as<Outer> {
233    _outer: PhantomData<Outer>,
234}
235
236impl<Outer: Rule, Inner: Rule> TransformInto<Inner> for parse_as<Outer> {
237    type Input = DualParse<Outer, Inner>;
238
239    fn transform(input: Self::Input) -> Inner {
240        input.inner
241    }
242}
243
244/// Rejects if the section matched by the target rule starts with `Invalid`.
245pub struct not<Invalid> {
246    _invalid: PhantomData<Invalid>,
247}
248
249impl<Invalid: Rule, Valid: Rule> TransformInto<Valid> for not<Invalid> {
250    type Input = NotParse<Invalid, Valid>;
251
252    fn transform(input: Self::Input) -> Valid {
253        input.value
254    }
255}
256
257pub struct not_empty;
258
259impl<T> TransformInto<T> for not_empty {
260    type Input = NotEmpty<T>;
261
262    fn transform(input: Self::Input) -> T {
263        input.value
264    }
265}