hydroperfox_smodel_proc/
lib.rs

1#![feature(proc_macro_diagnostic)]
2
3use proc_macro2::Span;
4
5#[macro_use]
6mod shared_array;
7use shared_array::*;
8
9#[macro_use]
10mod shared_map;
11use shared_map::*;
12
13mod symbol;
14use symbol::*;
15
16mod tree_semantics;
17use syn::spanned::Spanned;
18use tree_semantics::*;
19
20mod processing;
21use processing::*;
22
23// use std::iter::FromIterator;
24use proc_macro::TokenStream;
25// use proc_macro2::Span;
26use quote::{quote, ToTokens};
27// use quote::{quote, quote_spanned};
28use syn::parse::{Parse, ParseStream, Result};
29use syn::punctuated::Punctuated;
30use syn::token::Comma;
31// use syn::spanned::Spanned;
32use syn::{braced, parenthesized, parse_macro_input, Attribute, Expr, FnArg, Generics, Ident, Pat, Path, Stmt, Token, Type, Visibility, WhereClause};
33
34use std::cell::RefCell;
35use std::collections::HashMap;
36use std::hash::Hash;
37use std::ops::Deref;
38use std::rc::{Rc, Weak};
39use std::str::FromStr;
40use by_address::ByAddress;
41
42/// Data module name.
43const DATA: &'static str = "__data__";
44
45const DATA_PREFIX: &'static str = "__data_";
46
47/// Field name used for holding an enumeration of subtypes.
48const DATA_VARIANT_FIELD: &'static str = "__variant";
49
50/// Prefix used for enumerations of subtypes.
51const DATA_VARIANT_PREFIX: &'static str = "__variant_";
52
53/// Variant name used for indicating that no subtype is instantiated.
54const DATA_VARIANT_NO_SUBTYPE: &'static str = "__Nothing";
55
56struct SmTypeTree {
57    smodel_path: proc_macro2::TokenStream,
58    arena_type_name: proc_macro2::TokenStream,
59    data_types: Vec<Rc<SmType>>,
60}
61
62struct SmType {
63    attributes: Vec<Attribute>,
64    visibility: Visibility,
65    name: Ident,
66    inherits: Option<Ident>,
67    fields: Vec<Rc<SmTypeField>>,
68    constructor: Option<SmTypeConstructor>,
69    methods: Vec<Rc<SmTypeMethod>>,
70}
71
72struct SmTypeField {
73    is_ref: bool,
74    name: Ident,
75    type_annotation: Type,
76    default_value: Expr,
77}
78
79enum SmTypeMethodOrConstructor {
80    Method(SmTypeMethod),
81    Constructor(SmTypeConstructor),
82}
83
84struct SmTypeConstructor {
85    attributes: Vec<Attribute>,
86    visibility: Visibility,
87    generics: Generics,
88    name: Ident,
89    inputs: Punctuated<FnArg, Comma>,
90    super_arguments: Punctuated<Expr, Comma>,
91    statements: Vec<Stmt>,
92}
93
94struct SmTypeMethod {
95    attributes: RefCell<Vec<Attribute>>,
96    visibility: Visibility,
97    is_override: bool,
98    name: Ident,
99    generics: Generics,
100    inputs: Punctuated<FnArg, Comma>,
101    result_type: Option<Type>,
102    statements: proc_macro2::TokenStream,
103}
104
105impl Parse for SmTypeTree {
106    fn parse(input: ParseStream) -> Result<Self> {
107        let mut smodel_path: Option<Path> = None;
108        if input.peek(Token![mod]) {
109            input.parse::<Token![mod]>()?;
110            input.parse::<Ident>()?;
111            input.parse::<Token![=]>()?;
112            smodel_path = Some(parse_full_qualified_id(input)?);
113            input.parse::<Token![;]>()?;
114        }
115        let arena_type_name = parse_smtype_arena_type_name(input)?.to_token_stream();
116        let mut data_types = vec![];
117        while !input.is_empty() {
118            data_types.push(Rc::new(input.parse::<SmType>()?));
119        }
120        Ok(Self {
121            smodel_path: smodel_path.map(|p| p.to_token_stream()).unwrap_or(proc_macro2::TokenStream::from_str("::hydroperfox_smodel").unwrap()),
122            arena_type_name,
123            data_types,
124        })
125    }
126}
127
128fn parse_full_qualified_id(input: ParseStream) -> Result<Path> {
129    Ok(Path::parse_mod_style(input)?)
130}
131
132impl Parse for SmType {
133    fn parse(input: ParseStream) -> Result<Self> {
134        let attributes = Attribute::parse_outer(input)?;
135        let visibility = input.parse::<Visibility>()?;
136 
137        input.parse::<Token![struct]>()?;
138 
139        let name = input.parse::<Ident>()?;
140        let name_str = name.to_string();
141
142        // Inherits
143        let mut inherits: Option<Ident> = None;
144        if input.peek(Token![:]) {
145            input.parse::<Token![:]>()?;
146            inherits = Some(input.parse::<Ident>()?);
147        }
148
149        let mut fields: Vec<Rc<SmTypeField>> = vec![];
150        let mut constructor: Option<SmTypeConstructor> = None;
151        let mut methods: Vec<Rc<SmTypeMethod>> = vec![];
152        let braced_content;
153        let _ = braced!(braced_content in input);
154
155        while !braced_content.is_empty() {
156            if braced_content.peek(Token![let]) {
157                fields.push(Rc::new(parse_smtype_field(&braced_content)?));
158            } else {
159                match parse_smtype_method(&braced_content, &name_str)? {
160                    SmTypeMethodOrConstructor::Constructor(ctor) => {
161                        constructor = Some(ctor);
162                    },
163                    SmTypeMethodOrConstructor::Method(m) => {
164                        methods.push(Rc::new(m));
165                    },
166                }
167            }
168        }
169
170        Ok(Self {
171            attributes,
172            visibility,
173            name,
174            inherits,
175            fields,
176            constructor,
177            methods,
178        })
179    }
180}
181
182fn parse_smtype_field(input: ParseStream) -> Result<SmTypeField> {
183    input.parse::<Token![let]>()?;
184    let is_ref = if input.peek(Token![ref]) {
185        input.parse::<Token![ref]>()?;
186        true
187    } else {
188        false
189    };
190    let name = input.parse::<Ident>()?;
191    input.parse::<Token![:]>()?;
192    let type_annotation = input.parse::<Type>()?;
193    input.parse::<Token![=]>()?;
194    let default_value = input.parse::<Expr>()?;
195    input.parse::<Token![;]>()?;
196
197    Ok(SmTypeField {
198        is_ref,
199        name,
200        type_annotation,
201        default_value,
202    })
203}
204
205fn parse_smtype_method(input: ParseStream, smtype_name: &str) -> Result<SmTypeMethodOrConstructor> {
206    let attributes = Attribute::parse_outer(input)?;
207    let visibility = input.parse::<Visibility>()?;
208    let is_override = if input.peek(Token![override]) {
209        input.parse::<Token![override]>()?;
210        true
211    } else {
212        false
213    };
214    input.parse::<Token![fn]>()?;
215    let mut is_constructor = false;
216    let id = input.parse::<Ident>()?;
217    if !is_override && id.to_string() == smtype_name {
218        // id.span().unwrap().error("Identifier must be equals \"constructor\"").emit();
219        is_constructor = true;
220    }
221    let mut generics = input.parse::<Generics>()?;
222
223    let parens_content;
224    parenthesized!(parens_content in input);
225    let inputs = parens_content.parse_terminated(FnArg::parse, Comma)?;
226
227    let result_type: Option<Type> = if !is_constructor && input.peek(Token![->]) {
228        input.parse::<Token![->]>()?;
229        Some(input.parse::<Type>()?)
230    } else {
231        None
232    };
233
234    generics.where_clause = if input.peek(Token![where]) { Some(input.parse::<WhereClause>()?) } else { None };
235
236    let braced_content;
237    let _ = braced!(braced_content in input);
238
239    if !is_constructor {
240        let statements = braced_content.parse::<proc_macro2::TokenStream>()?;
241        return Ok(SmTypeMethodOrConstructor::Method(SmTypeMethod {
242            attributes: RefCell::new(attributes),
243            visibility,
244            is_override,
245            name: id,
246            generics,
247            inputs,
248            result_type,
249            statements,
250        }));
251    }
252
253    braced_content.parse::<Token![super]>()?;
254
255    let paren_content;
256    let _ = parenthesized!(paren_content in braced_content);
257    let super_arguments = paren_content.parse_terminated(Expr::parse, Comma)?;
258    braced_content.parse::<Token![;]>()?;
259
260    let mut statements = vec![];
261    while !braced_content.is_empty() {
262        statements.push(braced_content.parse::<Stmt>()?);
263    }
264
265    Ok(SmTypeMethodOrConstructor::Constructor(SmTypeConstructor {
266        attributes,
267        visibility,
268        generics,
269        name: id,
270        inputs,
271        super_arguments,
272        statements,
273    }))
274}
275
276fn parse_smtype_arena_type_name(input: ParseStream) -> Result<Path> {
277    input.parse::<Token![type]>()?;
278    let id = input.parse::<Ident>()?;
279    if id.to_string() != "Arena" {
280        id.span().unwrap().error("Identifier must be equals \"Arena\"").emit();
281    }
282    input.parse::<Token![=]>()?;
283    let path = Path::parse_mod_style(input)?;
284    input.parse::<Token![;]>()?;
285    Ok(path)
286}
287
288#[proc_macro]
289pub fn smodel(input: TokenStream) -> TokenStream {
290    let SmTypeTree {
291        smodel_path, arena_type_name, data_types
292    } = parse_macro_input!(input as SmTypeTree);
293
294    let mut host = SModelHost::new();
295
296    // # Validations
297
298    // 1. Ensure there is at least one data type.
299
300    if data_types.is_empty() {
301        panic!("There must be at least one data type.");
302    }
303
304    // 2. Ensure the first type inherits no other one.
305
306    if data_types[0].inherits.is_some() {
307        data_types[0].name.span().unwrap().error("First data type must inherit no base.").emit();
308        return TokenStream::new();
309    }
310    let base_smtype_data_name = Ident::new(&(DATA_PREFIX.to_string() + &data_types[0].name.to_string()), Span::call_site());
311
312    // 3. Ensure all other types inherit another one.
313
314    for m in data_types[1..].iter() {
315        if m.inherits.is_none() {
316            m.name.span().unwrap().error("Data type must inherit a base.").emit();
317            return TokenStream::new();
318        }
319    }
320
321    // # Processing steps
322
323    let data_id = Ident::new(DATA, Span::call_site());
324
325    // 1. Output the arena type.
326    host.output.extend::<TokenStream>(quote! {
327        pub type #arena_type_name = #smodel_path::Arena<#data_id::#base_smtype_data_name>;
328    }.try_into().unwrap());
329
330    // 2. Traverse each type in a first pass.
331    for smtype_node in data_types.iter() {
332        if !ProcessingStep2().exec(&mut host, smtype_node) {
333            return TokenStream::new();
334        }
335    }
336
337    // 3. Traverse each type in a second pass.
338    for smtype_node in data_types.iter() {
339        let Some(smtype) = host.semantics.get(smtype_node) else {
340            continue;
341        };
342
343        let asc_smtype_list = smtype.asc_smtype_list();
344        let mut field_output = proc_macro2::TokenStream::new();
345        let smtype_name = smtype.name();
346
347        // 3.1. Write out the base data accessor
348        //
349        // A `Weak<#DATA::FirstM>` value.
350        //
351        // For example, for the basemost data type, this
352        // is always "self.0"; for a direct subtype of the basemost
353        // data type, this is always "self.0.0".
354
355        let mut base_accessor = "self.0".to_owned();
356        let mut m1 = smtype.clone();
357        while let Some(m2) = m1.inherits() {
358            base_accessor.push_str(".0");
359            m1 = m2;
360        }
361
362        // 3.2. Traverse each field.
363        for field in smtype_node.fields.iter() {
364            if !ProcessingStep3_2().exec(&mut host, &smtype, field, &base_accessor, &asc_smtype_list, &mut field_output) {
365                return TokenStream::new();
366            }
367        }
368
369        // 3.3. Contribute a #DATA_VARIANT_FIELD field to #DATA::M
370        // holding the enumeration of subtypes.
371        let subtype_enum = Ident::new(&(DATA_VARIANT_PREFIX.to_owned() + &smtype_name), Span::call_site());
372        let data_variant_field_id = Ident::new(DATA_VARIANT_FIELD, Span::call_site());
373        field_output.extend(quote! {
374            pub #data_variant_field_id: #subtype_enum,
375        });
376
377        // 3.4. Contribute an enumeration of subtypes at the `#DATA` module.
378        let mut variants: Vec<proc_macro2::TokenStream> = vec![];
379        for subtype in smtype.subtypes().iter() {
380            let sn = DATA_PREFIX.to_owned() + &subtype.name();
381            variants.push(proc_macro2::TokenStream::from_str(&format!("{sn}(::std::rc::Rc<{sn}>)")).unwrap());
382        }
383        let data_variant_no_subtype = Ident::new(DATA_VARIANT_NO_SUBTYPE, Span::call_site());
384        variants.push(data_variant_no_subtype.to_token_stream());
385        host.data_output.extend(quote! {
386            pub enum #subtype_enum {
387                #(#variants),*
388            }
389        });
390
391        let smtype_data_id = Ident::new(&format!("{DATA_PREFIX}{}", smtype_name), Span::call_site());
392
393        // 3.5. Define the data structure #DATA::M at the #DATA module output,
394        // containing all field output.
395        host.data_output.extend(quote! {
396            pub struct #smtype_data_id {
397                #field_output
398            }
399        });
400
401        // 3.6. Define the structure M
402        ProcessingStep3_6().exec(&mut host, &smtype_node, &smtype, &base_accessor, &smodel_path);
403
404        // 3.7. Define the constructor
405        ProcessingStep3_7().exec(&mut host, smtype_node.constructor.as_ref(), &smtype, &asc_smtype_list, &arena_type_name.to_string());
406
407        // 3.8. Traverse each method
408        for method in smtype_node.methods.iter() {
409            if !ProcessingStep3_8().exec(&mut host, method, &smtype) {
410                return TokenStream::new();
411            }
412        }
413    }
414
415    // 4. Traverse each type in a third pass.
416    for smtype_node in data_types.iter() {
417        let Some(smtype) = host.semantics.get(smtype_node) else {
418            continue;
419        };
420
421        let smtype_name = smtype.name();
422        let smtype_name_id = Ident::new(&smtype_name, Span::call_site());
423
424        // 4.1. Traverse each method
425        for method in smtype_node.methods.iter() {
426            ProcessingStep4_1().exec(&mut host, method, &smtype);
427        }
428
429        // * Contribute a `to::<T: TryFrom<M>>()` method.
430        // * Contribute an `is::<T>()` method.
431        smtype.method_output().borrow_mut().extend(quote! {
432            pub fn to<T: TryFrom<#smtype_name_id, Error = #smodel_path::SModelError>>(&self) -> Result<T, #smodel_path::SModelError> {
433                T::try_from(self.clone())
434            }
435            pub fn is<T: TryFrom<#smtype_name_id, Error = #smodel_path::SModelError>>(&self) -> bool {
436                T::try_from(self.clone()).is_ok()
437            }
438        });
439
440        let method_output = smtype.method_output().borrow().clone();
441
442        // Output the code of all methods to an `impl` block for the data type.
443        host.output.extend::<TokenStream>(quote! {
444            impl #smtype_name_id {
445                #method_output
446            }
447        }.try_into().unwrap());
448    }
449
450    let data_output = host.data_output;
451
452    // 5. Output the `mod #DATA { use super::*; ... }` module with its respective contents
453    host.output.extend::<TokenStream>(quote! {
454        #[allow(non_camel_case_types, non_snake_case)]
455        mod #data_id {
456            use super::*;
457
458            #data_output
459        }
460    }.try_into().unwrap());
461
462    // 5. Return output.
463    host.output
464}
465
466fn convert_function_input_to_arguments(input: &Punctuated<FnArg, Comma>) -> Punctuated<proc_macro2::TokenStream, Comma> {
467    let mut out = Punctuated::<proc_macro2::TokenStream, Comma>::new();
468    for arg in input.iter() {
469        if let FnArg::Receiver(_) = arg {
470            arg.span().unwrap().error("Unexpected receiver.").emit();
471            continue;
472        } else {
473            let FnArg::Typed(pt) = arg else {
474                panic!();
475            };
476            let Pat::Ident(id) = pt.pat.as_ref() else {
477                pt.pat.span().unwrap().error("Pattern must be an identifier.").emit();
478                continue;
479            };
480            out.push(id.to_token_stream());
481        }
482    }
483    out
484}