typeset_parser/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as Quoted;
3use quote::quote;
4use std::fmt::Debug;
5use std::ops::ControlFlow;
6use syn::{
7    parenthesized,
8    parse::{discouraged::Speculative, Parse, ParseStream, Result},
9    parse_macro_input, Error, Ident, LitStr,
10};
11
12fn _parsed<T: Parse>(input: ParseStream) -> Result<T> {
13    let _input = input.fork();
14    match _input.parse::<T>() {
15        Err(error) => Err(error),
16        Ok(result) => {
17            input.advance_to(&_input);
18            Ok(result)
19        }
20    }
21}
22
23fn _parse_any<T>(input: ParseStream, parsers: Vec<fn(ParseStream) -> Result<T>>) -> Result<T> {
24    let result = parsers.iter().try_fold(Vec::new(), |mut errors, parser| {
25        let _input = input.fork();
26        match parser(&_input) {
27            Ok(value) => {
28                input.advance_to(&_input);
29                ControlFlow::Break(value)
30            }
31            Err(new_error) => {
32                errors.push(new_error);
33                ControlFlow::Continue(errors)
34            }
35        }
36    });
37    match result {
38        ControlFlow::Break(value) => Ok(value),
39        ControlFlow::Continue(errors) => {
40            let error = Error::new(input.span(), "Failed to parse any");
41            Err(errors
42                .iter()
43                .cloned()
44                .fold(error, |mut out_error, in_error| {
45                    out_error.combine(in_error);
46                    out_error
47                }))
48        }
49    }
50}
51
52fn _parse_group<T>(input: ParseStream, parser: fn(ParseStream) -> Result<T>) -> Result<T> {
53    let content;
54    parenthesized!(content in input);
55    parser(&content)
56}
57
58#[derive(Debug, Clone)]
59enum UnaryOp {
60    Fix,
61    Grp,
62    Seq,
63    Nest,
64    Pack,
65}
66
67fn _parse_unary_op(input: ParseStream) -> Result<UnaryOp> {
68    let item: Ident = input.parse()?;
69    match item.to_string().as_str() {
70        "fix" => Ok(UnaryOp::Fix),
71        "grp" => Ok(UnaryOp::Grp),
72        "seq" => Ok(UnaryOp::Seq),
73        "nest" => Ok(UnaryOp::Nest),
74        "pack" => Ok(UnaryOp::Pack),
75        _ => Err(Error::new(item.span(), "Expected a unary operator")),
76    }
77}
78
79mod binary_tokens {
80    use syn::custom_punctuation;
81    custom_punctuation!(Unpadded, &);
82    custom_punctuation!(Padded, +);
83    custom_punctuation!(FixedUnpadded, !&);
84    custom_punctuation!(FixedPadded, !+);
85    custom_punctuation!(Newline, @);
86    custom_punctuation!(DoubleNewline, @@);
87}
88
89#[derive(Debug, Clone)]
90enum BinaryOp {
91    Unpadded,
92    Padded,
93    FixedUnpadded,
94    FixedPadded,
95    Newline,
96    DoubleNewline,
97}
98
99fn _parse_binary_op(input: ParseStream) -> Result<BinaryOp> {
100    use binary_tokens::*;
101    _parse_any(
102        input,
103        vec![
104            |input| _parsed::<Unpadded>(input).map(|_| BinaryOp::Unpadded),
105            |input| _parsed::<Padded>(input).map(|_| BinaryOp::Padded),
106            |input| _parsed::<FixedUnpadded>(input).map(|_| BinaryOp::FixedUnpadded),
107            |input| _parsed::<FixedPadded>(input).map(|_| BinaryOp::FixedPadded),
108            |input| _parsed::<DoubleNewline>(input).map(|_| BinaryOp::DoubleNewline),
109            |input| _parsed::<Newline>(input).map(|_| BinaryOp::Newline),
110        ],
111    )
112}
113
114#[derive(Debug, Clone)]
115enum Ast {
116    Null,
117    Variable(Ident),
118    Text(String),
119    Unary(UnaryOp, Box<Ast>),
120    Binary(BinaryOp, Box<Ast>, Box<Ast>),
121}
122
123fn _parse_null(input: ParseStream) -> Result<Box<Ast>> {
124    let item: Ident = input.parse()?;
125    match item.to_string().as_str() {
126        "null" => Ok(Box::new(Ast::Null)),
127        _ => Err(Error::new(item.span(), "Expected a unary operator")),
128    }
129}
130
131fn _parse_variable(input: ParseStream) -> Result<Box<Ast>> {
132    let name = _parsed::<Ident>(input)?;
133    Ok(Box::new(Ast::Variable(name)))
134}
135
136fn _parse_text(input: ParseStream) -> Result<Box<Ast>> {
137    let data = _parsed::<LitStr>(input)?;
138    Ok(Box::new(Ast::Text(data.value())))
139}
140
141fn _parse_group_ast(input: ParseStream) -> Result<Box<Ast>> {
142    _parse_group(input, _parse_ast)
143}
144
145fn _parse_primary(input: ParseStream) -> Result<Box<Ast>> {
146    _parse_any(
147        input,
148        vec![_parse_null, _parse_variable, _parse_text, _parse_group_ast],
149    )
150}
151
152fn _parse_atom(input: ParseStream) -> Result<Box<Ast>> {
153    _parse_any(input, vec![_parse_unary, _parse_primary])
154}
155
156fn _parse_unary(input: ParseStream) -> Result<Box<Ast>> {
157    let op = _parse_unary_op(input)?;
158    let ast = _parse_primary(input)?;
159    Ok(Box::new(Ast::Unary(op, ast)))
160}
161
162fn _parse_binary(input: ParseStream) -> Result<Box<Ast>> {
163    let left = _parse_atom(input)?;
164    let op = _parse_binary_op(input)?;
165    let right = _parse_ast(input)?;
166    Ok(Box::new(Ast::Binary(op, left, right)))
167}
168
169fn _parse_ast(input: ParseStream) -> Result<Box<Ast>> {
170    _parse_any(input, vec![_parse_binary, _parse_atom])
171}
172
173impl Parse for Box<Ast> {
174    fn parse(input: ParseStream) -> Result<Self> {
175        let _input = input.fork();
176        match _parse_ast(&_input) {
177            Err(error) => Err(error),
178            Ok(result) => {
179                input.advance_to(&_input);
180                if input.is_empty() {
181                    Ok(result)
182                } else {
183                    Err(Error::new(
184                        input.span(),
185                        format!("Failed to parse layout:\n{}", input),
186                    ))
187                }
188            }
189        }
190    }
191}
192
193fn _reify_layout(ast: Ast) -> Quoted {
194    match ast {
195        Ast::Null => quote! { typeset::null() },
196        Ast::Variable(name) => quote! { #name.clone() },
197        Ast::Text(data) => quote! { typeset::text(#data.to_string()) },
198        Ast::Unary(UnaryOp::Fix, ast1) => {
199            let layout = _reify_layout(*ast1);
200            quote! { typeset::fix(#layout) }
201        }
202        Ast::Unary(UnaryOp::Grp, ast1) => {
203            let layout = _reify_layout(*ast1);
204            quote! { typeset::grp(#layout) }
205        }
206        Ast::Unary(UnaryOp::Seq, ast1) => {
207            let layout = _reify_layout(*ast1);
208            quote! { typeset::seq(#layout) }
209        }
210        Ast::Unary(UnaryOp::Nest, ast1) => {
211            let layout = _reify_layout(*ast1);
212            quote! { typeset::nest(#layout) }
213        }
214        Ast::Unary(UnaryOp::Pack, ast1) => {
215            let layout = _reify_layout(*ast1);
216            quote! { typeset::pack(#layout) }
217        }
218        Ast::Binary(BinaryOp::Unpadded, left, right) => {
219            let left_layout = _reify_layout(*left);
220            let right_layout = _reify_layout(*right);
221            quote! {
222              typeset::comp(
223                #left_layout,
224                #right_layout,
225                false,
226                false
227              )
228            }
229        }
230        Ast::Binary(BinaryOp::Padded, left, right) => {
231            let left_layout = _reify_layout(*left);
232            let right_layout = _reify_layout(*right);
233            quote! {
234              typeset::comp(
235                #left_layout,
236                #right_layout,
237                true,
238                false
239              )
240            }
241        }
242        Ast::Binary(BinaryOp::FixedUnpadded, left, right) => {
243            let left_layout = _reify_layout(*left);
244            let right_layout = _reify_layout(*right);
245            quote! {
246              typeset::comp(
247                #left_layout,
248                #right_layout,
249                false,
250                true
251              )
252            }
253        }
254        Ast::Binary(BinaryOp::FixedPadded, left, right) => {
255            let left_layout = _reify_layout(*left);
256            let right_layout = _reify_layout(*right);
257            quote! {
258              typeset::comp(
259                #left_layout,
260                #right_layout,
261                true,
262                true
263              )
264            }
265        }
266        Ast::Binary(BinaryOp::Newline, left, right) => {
267            let left_layout = _reify_layout(*left);
268            let right_layout = _reify_layout(*right);
269            quote! {
270              typeset::line(
271                #left_layout,
272                #right_layout
273              )
274            }
275        }
276        Ast::Binary(BinaryOp::DoubleNewline, left, right) => {
277            let left_layout = _reify_layout(*left);
278            let right_layout = _reify_layout(*right);
279            quote! {
280              typeset::line(
281                #left_layout,
282                typeset::line(
283                  typeset::null(),
284                  #right_layout
285                )
286              )
287            }
288        }
289    }
290}
291
292#[proc_macro]
293pub fn layout(input: TokenStream) -> TokenStream {
294    let ast = parse_macro_input!(input as Box<Ast>);
295    let output = _reify_layout(*ast);
296    quote! { #output }.into()
297}