Skip to main content

naia_parse/
parse.rs

1//! Very limited rust parser
2//!
3//! https://doc.rust-lang.org/reference/expressions/struct-expr.html
4//! https://docs.rs/syn/0.15.44/syn/enum.Type.html
5//! https://ziglang.org/documentation/0.5.0/#toc-typeInfo
6
7use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
8
9use std::iter::Peekable;
10
11pub struct Attribute {
12    pub name: String,
13    pub tokens: Vec<String>,
14}
15
16#[allow(dead_code)]
17pub enum Visibility {
18    Public,
19    Crate,
20    Restricted,
21    Private,
22}
23
24pub struct Field {
25    pub vis: Visibility,
26    pub field_name: Option<String>,
27    pub ty: Type,
28}
29
30#[allow(dead_code)]
31pub struct Type {
32    pub is_option: bool,
33    pub path: String,
34}
35
36pub struct Struct {
37    pub name: String,
38    pub tuple: bool,
39    pub fields: Vec<Field>,
40}
41
42pub struct EnumVariant {
43    pub name: String,
44    pub tuple: bool,
45    pub fields: Vec<Field>,
46}
47
48pub struct Enum {
49    pub name: String,
50    pub variants: Vec<EnumVariant>,
51}
52
53#[allow(dead_code)]
54pub enum Data {
55    Struct(Struct),
56    Enum(Enum),
57    Union(()),
58}
59
60pub fn next_visibility_modifier(
61    source: &mut Peekable<impl Iterator<Item = TokenTree>>,
62) -> Option<String> {
63    if let Some(TokenTree::Ident(ident)) = source.peek() {
64        if format!("{}", ident) == "pub" {
65            source.next();
66
67            // skip (crate) and alike
68            if let Some(TokenTree::Group(group)) = source.peek() {
69                if group.delimiter() == Delimiter::Parenthesis {
70                    next_group(source);
71                }
72            }
73
74            return Some("pub".to_string());
75        }
76    }
77
78    None
79}
80
81pub fn next_punct(source: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<String> {
82    if let Some(TokenTree::Punct(punct)) = source.peek() {
83        let punct = format!("{}", punct);
84        source.next();
85        return Some(punct);
86    }
87
88    None
89}
90
91pub fn next_exact_punct(
92    source: &mut Peekable<impl Iterator<Item = TokenTree>>,
93    pattern: &str,
94) -> Option<String> {
95    if let Some(TokenTree::Punct(punct)) = source.peek() {
96        let punct = format!("{}", punct);
97        if punct == pattern {
98            source.next();
99            return Some(punct);
100        }
101    }
102
103    None
104}
105
106pub fn next_eof<T: Iterator>(source: &mut Peekable<T>) -> Option<()> {
107    if source.peek().is_none() {
108        Some(())
109    } else {
110        None
111    }
112}
113
114pub fn next_ident(source: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<String> {
115    if let Some(TokenTree::Ident(ident)) = source.peek() {
116        let ident = format!("{}", ident);
117        source.next();
118        Some(ident)
119    } else {
120        None
121    }
122}
123
124pub fn next_group(source: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<Group> {
125    if let Some(TokenTree::Group(_)) = source.peek() {
126        let group = match source.next().expect("expected to read another token") {
127            TokenTree::Group(group) => group,
128            _ => unreachable!("just checked with peek()!"),
129        };
130        Some(group)
131    } else {
132        None
133    }
134}
135
136fn skip_doc_string(source: &mut Peekable<impl Iterator<Item = TokenTree>>) {
137    while skip_doc_string_inner(source) {}
138}
139
140fn skip_doc_string_inner(source: &mut Peekable<impl Iterator<Item = TokenTree>>) -> bool {
141    if next_exact_punct(source, "#").is_none() {
142        return false;
143    }
144    if next_group(source).is_none() {
145        return false;
146    }
147    true
148}
149
150#[allow(dead_code)]
151pub fn debug_current_token(source: &mut Peekable<impl Iterator<Item = TokenTree>>) {
152    println!("{:?}", source.peek());
153}
154
155fn next_type<T: Iterator<Item = TokenTree>>(source: &mut Peekable<T>) -> Option<Type> {
156    let group = next_group(source);
157    if let Some(group) = group {
158        let mut tuple_type = Type {
159            is_option: false,
160            path: "".to_string(),
161        };
162
163        while let Some(next_ty) = next_type(&mut group.stream().into_iter().peekable()) {
164            #[allow(clippy::format_push_string)]
165            tuple_type.path.push_str(&format!("{}, ", next_ty.path));
166        }
167
168        return Some(tuple_type);
169    }
170
171    // read a path like a::b::c::d
172    let mut ty = next_ident(source)?;
173    while next_exact_punct(source, ":").is_some() {
174        let _second_colon = next_exact_punct(source, ":").expect("Expecting second :");
175
176        let next_ident = next_ident(source).expect("Expecting next path part after ::");
177        #[allow(clippy::format_push_string)]
178        ty.push_str(&format!("::{}", next_ident));
179    }
180
181    let angel_bracket = next_exact_punct(source, "<");
182    if angel_bracket.is_some() {
183        let mut generic_type = next_type(source).expect("Expecting generic argument");
184        while let Some(_comma) = next_exact_punct(source, ",") {
185            let next_ty = next_type(source).expect("Expecting generic argument");
186            #[allow(clippy::format_push_string)]
187            generic_type.path.push_str(&format!(", {}", next_ty.path));
188        }
189
190        let _closing_bracket =
191            next_exact_punct(source, ">").expect("Expecting closing generic bracket");
192
193        if ty == "Option" {
194            Some(Type {
195                path: generic_type.path,
196                is_option: true,
197            })
198        } else {
199            Some(Type {
200                path: format!("{}<{}>", ty, generic_type.path),
201                is_option: false,
202            })
203        }
204    } else {
205        Some(Type {
206            path: ty,
207            is_option: false,
208        })
209    }
210}
211
212fn next_fields(body: &mut Peekable<impl Iterator<Item = TokenTree>>, named: bool) -> Vec<Field> {
213    let mut fields = vec![];
214
215    loop {
216        if next_eof(body).is_some() {
217            break;
218        }
219
220        skip_doc_string(body);
221
222        let _visibility = next_visibility_modifier(body);
223        let field_name = if named {
224            let field_name = next_ident(body).expect("Field name expected");
225
226            let _ = next_exact_punct(body, ":").expect("Delimeter after field name expected");
227            Some(field_name)
228        } else {
229            None
230        };
231
232        let ty = next_type(body).expect("Expected field type");
233        let _punct = next_punct(body);
234
235        fields.push(Field {
236            vis: Visibility::Public,
237            field_name,
238            ty,
239        });
240    }
241    fields
242}
243
244fn next_struct(source: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Struct {
245    let struct_name = next_ident(source).expect("Unnamed structs are not supported");
246
247    let group = next_group(source);
248    // unit struct
249    if group.is_none() {};
250    if let Some(group) = group {
251        let delimiter = group.delimiter();
252        let tuple = match delimiter {
253            Delimiter::Parenthesis => true,
254            Delimiter::Brace => false,
255
256            _ => panic!("Struct with unsupported delimiter"),
257        };
258
259        let mut body = group.stream().into_iter().peekable();
260        let fields = next_fields(&mut body, !tuple);
261
262        if tuple {
263            next_exact_punct(source, ";").expect("Expected ; on the end of tuple struct");
264        }
265
266        Struct {
267            name: struct_name,
268            tuple,
269            fields,
270        }
271    } else {
272        // skip ; at the end of struct like this: "struct Foo;"
273        let _ = next_punct(source);
274
275        Struct {
276            name: struct_name,
277            fields: vec![],
278            tuple: true,
279        }
280    }
281}
282
283fn next_enum(source: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Enum {
284    let enum_name = next_ident(source).expect("Unnamed enums are not supported");
285
286    let group = next_group(source);
287    // unit enum
288    if let Some(group) = group {
289        let mut body = group.stream().into_iter().peekable();
290
291        let mut variants = vec![];
292        loop {
293            if next_eof(&mut body).is_some() {
294                break;
295            }
296
297            skip_doc_string(&mut body);
298
299            let variant_name = next_ident(&mut body).expect("Unnamed variants are not supported");
300            let group = next_group(&mut body);
301
302            if let Some(group) = group {
303                let delimiter = group.delimiter();
304                let tuple = match delimiter {
305                    Delimiter::Parenthesis => true,
306                    Delimiter::Brace => false,
307
308                    _ => panic!("Enum with unsupported delimiter"),
309                };
310                {
311                    let mut body = group.stream().into_iter().peekable();
312                    let fields = next_fields(&mut body, !tuple);
313                    variants.push(EnumVariant {
314                        name: variant_name,
315                        tuple,
316                        fields,
317                    });
318                }
319                let _maybe_semicolon = next_exact_punct(&mut body, ";");
320                let _maybe_coma = next_exact_punct(&mut body, ",");
321            } else {
322                variants.push(EnumVariant {
323                    name: variant_name,
324                    tuple: true,
325                    fields: vec![],
326                });
327                let _maybe_comma = next_exact_punct(&mut body, ",");
328                continue;
329            }
330        }
331
332        Enum {
333            name: enum_name,
334            variants,
335        }
336    } else {
337        Enum {
338            name: enum_name,
339            variants: vec![],
340        }
341    }
342}
343
344pub fn parse_data(input: TokenStream) -> Data {
345    let mut source = input.into_iter().peekable();
346
347    skip_doc_string(&mut source);
348
349    let pub_or_type = next_ident(&mut source).expect("Not an ident");
350
351    let type_keyword = if pub_or_type == "pub" {
352        next_ident(&mut source).expect("pub(whatever) is not supported yet")
353    } else {
354        pub_or_type
355    };
356
357    let res = match type_keyword.as_str() {
358        "struct" => Data::Struct(next_struct(&mut source)),
359        "enum" => {
360            let enum_ = next_enum(&mut source);
361            Data::Enum(enum_)
362        }
363        "union" => unimplemented!("Unions are not supported"),
364        unexpected => panic!("Unexpected keyword: {}", unexpected),
365    };
366
367    assert!(
368        source.next().is_none(),
369        "Unexpected data after end of the struct"
370    );
371
372    res
373}