kalosm_parse_macro/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote_spanned;
4use quote::{format_ident, quote, ToTokens};
5use std::cmp::Ordering;
6use std::collections::HashMap;
7use std::fmt::Debug;
8use syn::meta::ParseNestedMeta;
9use syn::parse::{Parse, ParseStream};
10use syn::spanned::Spanned;
11use syn::{ext::IdentExt, parse_macro_input, DeriveInput, Field, Ident, LitStr};
12use syn::{DataEnum, Fields, FieldsNamed, LitInt, Path, TypePath, Variant};
13
14/// Derive a default JSON parser for a unit value, struct or enum.
15///
16/// # Examples
17///
18/// You can derive a parser for a struct with fields that implement the `Parse` trait:
19///
20/// ```rust
21/// # use kalosm::language::*;
22/// #[derive(Parse, Schema, Debug, Clone, PartialEq)]
23/// struct Person {
24///     name: String,
25///     age: u32,
26/// }
27///
28/// let parser = Person::new_parser();
29/// let state = parser.create_parser_state();
30/// let person = parser
31///     .parse(&state, b"{ \"name\": \"John\", \"age\": 30 } ")
32///     .unwrap()
33///     .unwrap_finished();
34/// assert_eq!(person.name, "John");
35/// assert_eq!(person.age, 30);
36/// ```
37///
38/// Or an enum with unit variants:
39/// ```rust
40/// # use kalosm::language::*;
41/// #[derive(Parse, Schema, Debug, Clone, PartialEq)]
42/// enum Color {
43///     Red,
44///     Blue,
45///     Green,
46/// }
47///
48/// let parser = Color::new_parser();
49/// let state = parser.create_parser_state();
50/// let color = parser.parse(&state, b"\"Red\" ").unwrap().unwrap_finished();
51/// assert_eq!(color, Color::Red);
52/// ```
53///
54/// You can even derive Parse for an enum with data variants:
55/// ```rust
56/// # use kalosm::language::*;
57/// #[derive(Parse, Schema, Debug, Clone, PartialEq)]
58/// enum Action {
59///     Search { query: String },
60///     Quit,
61/// }
62///
63/// let parser = Action::new_parser();
64/// let state = parser.create_parser_state();
65/// let action = parser
66///     .parse(
67///         &state,
68///         b"{ \"type\": \"Search\", \"data\": { \"query\": \"my query\" } } ",
69///     )
70///     .unwrap()
71///     .unwrap_finished();
72/// assert_eq!(
73///     action,
74///     Action::Search {
75///         query: "my query".to_string()
76///     }
77/// );
78/// ```
79///
80/// ## Attributes
81///
82/// The `#[parse]` attribute modifies the default behavior of the parser. It can be used in the following forms:
83///
84/// - `#[parse(rename = "name")]` renames the field or type to `name` (defaults to the field name)
85///
86/// ```rust
87/// # use kalosm::language::*;
88/// #[derive(Parse, Schema, Clone)]
89/// struct Person {
90///     #[parse(rename = "full name")]
91///     name: String,
92///     age: u32,
93/// }
94///
95/// #[derive(Parse, Schema, Clone)]
96/// enum Color {
97///     #[parse(rename = "red")]
98///     Red,
99///     Blue,
100///     Green,
101/// }
102/// ```
103///
104/// - `#[parse(with = expression)]` uses the expression to parse the field (defaults to the parser provided by the `Parse` implementation for the field type)
105///
106/// ```rust
107/// # use kalosm::language::*;
108/// #[derive(Parse, Schema, Clone)]
109/// struct Person {
110///     #[parse(with = StringParser::new(1..=10))]
111///     name: String,
112///     age: u32,
113/// }
114/// ```
115///
116/// - `#[parse(tag = "tag")]` changes the name of the tag for enum variants (defaults to "type")
117///
118/// ```rust
119/// # use kalosm::language::*;
120/// #[derive(Parse, Schema, Clone)]
121/// #[parse(tag = "action")]
122/// enum Action {
123///     Search { query: String },
124///     Quit,
125/// }
126/// ```
127///
128/// - `#[parse(content = "content")]` changes the name of the content for enum variants (defaults to "data")
129///
130/// ```rust
131/// # use kalosm::language::*;
132/// #[derive(Parse, Schema, Clone)]
133/// #[parse(content = "arguments")]
134/// enum Action {
135///     Search { query: String },
136///     Quit,
137/// }
138/// ```
139#[proc_macro_derive(Parse, attributes(parse))]
140pub fn derive_parse(input: TokenStream) -> TokenStream {
141    // Parse the input tokens into a syntax tree
142    let input = parse_macro_input!(input as DeriveInput);
143
144    match input.data {
145        syn::Data::Struct(data) => match data.fields {
146            syn::Fields::Named(fields) => {
147                let ty = input.ident;
148                if fields.named.is_empty() {
149                    return TokenStream::from(impl_unit_parser(
150                        &input.attrs,
151                        &ty,
152                        quote! { Self {} },
153                    ));
154                }
155                let struct_parser = match StructParser::new(input.attrs, fields, ty) {
156                    Ok(parser) => parser,
157                    Err(err) => return err.to_compile_error().into(),
158                };
159
160                TokenStream::from(struct_parser.parser())
161            }
162            syn::Fields::Unit => {
163                let ty = input.ident;
164                TokenStream::from(impl_unit_parser(&input.attrs, &ty, quote! { Self }))
165            }
166            _ => syn::Error::new(
167                input.ident.span(),
168                "Only structs with named fields are supported",
169            )
170            .to_compile_error()
171            .into(),
172        },
173        syn::Data::Enum(data) => {
174            let ty = input.ident;
175            if data.variants.is_empty() {
176                return syn::Error::new(ty.span(), "Enums with no variants are not supported")
177                    .to_compile_error()
178                    .into();
179            }
180
181            let has_fields = data
182                .variants
183                .iter()
184                .any(|variant| !matches!(&variant.fields, syn::Fields::Unit));
185
186            if has_fields {
187                match EnumParser::new(input.attrs, data, ty)
188                    .and_then(|parser| parser.quote_parser())
189                {
190                    Ok(parser) => parser,
191                    Err(err) => err.to_compile_error(),
192                }
193            } else {
194                unit_enum_parser(input.attrs, data, ty)
195            }
196            .into()
197        }
198        _ => syn::Error::new(
199            input.ident.span(),
200            "Only structs and unit value enums are supported",
201        )
202        .to_compile_error()
203        .into(),
204    }
205}
206
207#[proc_macro_derive(Schema, attributes(parse))]
208pub fn derive_schema(input: TokenStream) -> TokenStream {
209    // Parse the input tokens into a syntax tree
210    let input = parse_macro_input!(input as DeriveInput);
211
212    match input.data {
213        syn::Data::Struct(data) => match data.fields {
214            syn::Fields::Named(fields) => {
215                let ty = input.ident;
216                if fields.named.is_empty() {
217                    return TokenStream::from(unit_schema(&input.attrs, &ty));
218                }
219                let struct_parser = match StructParser::new(input.attrs, fields, ty) {
220                    Ok(parser) => parser,
221                    Err(err) => return err.to_compile_error().into(),
222                };
223
224                TokenStream::from(struct_parser.quote_schema())
225            }
226            syn::Fields::Unit => {
227                let ty = input.ident;
228                TokenStream::from(unit_schema(&input.attrs, &ty))
229            }
230            _ => syn::Error::new(
231                input.ident.span(),
232                "Only structs with named fields are supported",
233            )
234            .to_compile_error()
235            .into(),
236        },
237        syn::Data::Enum(data) => {
238            let ty = input.ident;
239            if data.variants.is_empty() {
240                return syn::Error::new(ty.span(), "Enums with no variants are not supported")
241                    .to_compile_error()
242                    .into();
243            }
244
245            let has_fields = data
246                .variants
247                .iter()
248                .any(|variant| !matches!(&variant.fields, syn::Fields::Unit));
249
250            if has_fields {
251                match EnumParser::new(input.attrs, data, ty)
252                    .and_then(|parser| parser.quote_schema())
253                {
254                    Ok(parser) => parser,
255                    Err(err) => err.to_compile_error(),
256                }
257            } else {
258                unit_enum_schema(data, ty)
259            }
260            .into()
261        }
262        _ => syn::Error::new(
263            input.ident.span(),
264            "Only structs and unit value enums are supported",
265        )
266        .to_compile_error()
267        .into(),
268    }
269}
270
271struct StructParser {
272    attributes: Vec<syn::Attribute>,
273    ty: Ident,
274    name: String,
275    fields: FieldsParser,
276}
277
278impl StructParser {
279    fn new(attributes: Vec<syn::Attribute>, fields: FieldsNamed, ty: Ident) -> syn::Result<Self> {
280        let named = fields.named.into_iter().collect::<Vec<_>>();
281
282        let mut name = ty.unraw().to_string();
283        for attr in &attributes {
284            if attr.path().is_ident("parse") {
285                attr.parse_nested_meta(|meta| {
286                    if let Some(value) = parse_rename_attribute(&meta)? {
287                        name = value.value();
288                    } else {
289                        return Err(meta.error("expected `rename`"));
290                    }
291                    Ok(())
292                })?;
293            }
294        }
295
296        Ok(Self {
297            attributes,
298            name,
299            ty,
300            fields: FieldsParser::new(&named)?,
301        })
302    }
303
304    fn parser(&self) -> TokenStream2 {
305        let field_names = self
306            .fields
307            .fields
308            .iter()
309            .map(|f| f.field.ident.as_ref().unwrap());
310        let construct = quote! {
311            Self {
312                #(
313                    #field_names
314                ),*
315            }
316        };
317
318        let parser = match self.fields.parser(construct) {
319            Ok(parser) => parser,
320            Err(err) => return err.to_compile_error(),
321        };
322
323        let ty = &self.ty;
324
325        quote! {
326            impl kalosm_sample::Parse for #ty {
327                fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
328                    #parser
329                }
330            }
331        }
332    }
333
334    fn quote_schema(&self) -> proc_macro2::TokenStream {
335        let title = &self.name;
336        let ty = &self.ty;
337        let description = doc_comment(&self.attributes);
338        let description = description.map(|description| quote! { .with_description(#description) });
339        let schema = self.fields.quote_schema();
340
341        quote! {
342            impl kalosm_sample::Schema for #ty {
343                fn schema() -> kalosm_sample::SchemaType {
344                    kalosm_sample::SchemaType::Object(
345                        #schema
346                        .with_title(#title)
347                        #description
348                    )
349                }
350            }
351        }
352    }
353}
354
355fn quote_fields(fields: Fields) -> TokenStream2 {
356    match fields {
357        Fields::Named(fields) => {
358            let field_names = fields.named.iter().map(|f| f.ident.as_ref().unwrap());
359            quote! {
360                {
361                    #(
362                        #field_names
363                    ),*
364                }
365            }
366        }
367        Fields::Unnamed(fields) => {
368            let field_names = (0..fields.unnamed.len()).map(|i| format_ident!("data{}", i));
369            quote! {
370                (
371                    #(
372                        #field_names
373                    ),*
374                )
375            }
376        }
377        Fields::Unit => {
378            quote! {}
379        }
380    }
381}
382
383fn impl_unit_parser(attrs: &[syn::Attribute], ty: &Ident, construct: TokenStream2) -> TokenStream2 {
384    let unit_parser = unit_parser(attrs, ty);
385    quote! {
386        impl kalosm_sample::Parse for #ty {
387            fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
388                kalosm_sample::ParserExt::map_output(
389                    #unit_parser,
390                    |_| #construct
391                )
392            }
393        }
394    }
395}
396
397fn unit_schema(attrs: &[syn::Attribute], ty: &Ident) -> TokenStream2 {
398    let name = match unit_parse_literal_name(attrs, ty) {
399        Ok(name) => name,
400        Err(err) => return err.to_compile_error(),
401    };
402
403    quote! {
404        impl kalosm_sample::Schema for #ty {
405            fn schema() -> kalosm_sample::SchemaType {
406                kalosm_sample::SchemaType::Enum(kalosm_sample::EnumSchema::new([
407                    kalosm_sample::SchemaLiteral::String(#name.to_string())
408                ]))
409            }
410        }
411    }
412}
413
414fn unit_parse_literal(attrs: &[syn::Attribute], ty: &Ident, unquoted: bool) -> syn::Result<String> {
415    let ty_string = unit_parse_literal_name(attrs, ty)?;
416
417    Ok(if unquoted {
418        ty_string
419    } else {
420        format!("\"{ty_string}\"")
421    })
422}
423
424fn unit_parse_literal_name(attrs: &[syn::Attribute], ty: &Ident) -> syn::Result<String> {
425    // Look for #[parse(rename = "name")] attribute
426    let mut ty_string = ty.unraw().to_string();
427    for attr in attrs.iter() {
428        if attr.path().is_ident("parse") {
429            attr.parse_nested_meta(|meta| {
430                if let Some(value) = parse_rename_attribute(&meta)? {
431                    ty_string = value.value();
432                    Ok(())
433                } else {
434                    Err(meta.error("expected `rename`"))
435                }
436            })?;
437        }
438    }
439
440    Ok(ty_string)
441}
442
443fn unit_parser(attrs: &[syn::Attribute], ty: &Ident) -> TokenStream2 {
444    let ty_string = match unit_parse_literal(attrs, ty, false) {
445        Ok(ty_string) => ty_string,
446        Err(err) => return err.to_compile_error(),
447    };
448    let ty_string = LitStr::new(&ty_string, ty.span());
449    quote! {
450        kalosm_sample::LiteralParser::new(#ty_string)
451    }
452}
453
454struct EnumParser {
455    ty: Ident,
456    tag: String,
457    data: String,
458    variants: Vec<EnumVariant>,
459}
460
461impl EnumParser {
462    fn new(attrs: Vec<syn::Attribute>, data: DataEnum, ty: Ident) -> syn::Result<Self> {
463        // Look for the tag and content attributes within the #[parse] attribute
464        let mut tag = "type".to_string();
465        let mut content = "data".to_string();
466        for attr in attrs.iter() {
467            if attr.path().is_ident("parse") {
468                attr.parse_nested_meta(|meta| {
469                    if meta.path.is_ident("tag") {
470                        let value = meta
471                            .value()
472                            .and_then(|value| value.parse::<syn::LitStr>())?;
473                        tag = value.value();
474                        Ok(())
475                    } else if meta.path.is_ident("content") {
476                        let value = meta
477                            .value()
478                            .and_then(|value| value.parse::<syn::LitStr>())?;
479                        content = value.value();
480                        Ok(())
481                    } else {
482                        Err(meta.error("expected `tag` or `content`"))
483                    }
484                })?;
485            }
486        }
487
488        let variants = data
489            .variants
490            .iter()
491            .map(EnumVariant::new)
492            .collect::<syn::Result<_>>()?;
493
494        Ok(EnumParser {
495            ty,
496            tag,
497            data: content,
498            variants,
499        })
500    }
501
502    fn quote_parser(&self) -> syn::Result<TokenStream2> {
503        let tag = &self.tag;
504        let ty = &self.ty;
505        let content = &self.data;
506        let mut parser = None;
507
508        for variant in &self.variants {
509            let parse_variant = variant.quote_parser(content)?;
510            match &mut parser {
511                Some(current) => {
512                    *current = quote! {
513                        kalosm_sample::ParserExt::or(
514                            #current,
515                            #parse_variant
516                        )
517                    };
518                }
519                None => {
520                    parser = Some(parse_variant);
521                }
522            }
523        }
524
525        let struct_start = format!("{{ \"{tag}\": \"");
526
527        Ok(quote! {
528            impl kalosm_sample::Parse for #ty {
529                fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
530                    kalosm_sample::ParserExt::then_literal(
531                        kalosm_sample::ParserExt::ignore_output_then(
532                            kalosm_sample::LiteralParser::from(#struct_start),
533                            #parser
534                        ),
535                        r#" }"#
536                    )
537                }
538            }
539        })
540    }
541
542    fn quote_schema(&self) -> syn::Result<proc_macro2::TokenStream> {
543        let tag = &self.tag;
544        let content = &self.data;
545        let ty = &self.ty;
546
547        let variants: Vec<_> = self
548            .variants
549            .iter()
550            .map(|variant| {
551                let variant_name = &variant.name;
552                let variant_parser = variant.quote_schema(tag, content, variant_name)?;
553                Ok(quote! {
554                    #variant_parser
555                })
556            })
557            .collect::<syn::Result<_>>()?;
558
559        Ok(quote! {
560            impl kalosm_sample::Schema for #ty {
561                fn schema() -> kalosm_sample::SchemaType {
562                    kalosm_sample::SchemaType::AnyOf(
563                        kalosm_sample::AnyOfSchema::new([
564                            #(#variants),*
565                        ])
566                    )
567                }
568            }
569        })
570    }
571}
572
573struct EnumVariant {
574    variant: Variant,
575    name: String,
576    ty: EnumVariantType,
577}
578
579impl EnumVariant {
580    fn new(variant: &Variant) -> syn::Result<Self> {
581        let variant_ident = &variant.ident;
582        let mut variant_name = variant_ident.unraw().to_string();
583        // Look for #[parse(rename = "name")] attribute
584        for attr in variant.attrs.iter() {
585            if attr.path().is_ident("parse") {
586                attr.parse_nested_meta(|meta| {
587                    if let Some(value) = parse_rename_attribute(&meta)? {
588                        variant_name = value.value();
589                        Ok(())
590                    } else {
591                        Err(meta.error("expected `rename`"))
592                    }
593                })?;
594            }
595        }
596
597        let parse_variant = match &variant.fields {
598            syn::Fields::Named(fields) => {
599                EnumVariantType::Struct(StructEnumVariantParser::new(fields)?)
600            }
601            syn::Fields::Unnamed(fields) => {
602                let field_vec = fields.unnamed.iter().collect::<Vec<_>>();
603                let [inner] = *field_vec else {
604                    return Err(syn::Error::new(
605                        variant.ident.span(),
606                        "Unnamed enum variants with more or less than one field are not supported",
607                    ));
608                };
609
610                EnumVariantType::Tuple(TupleEnumVariantParser::new(inner))
611            }
612            // If this is a unit variant, we can just parse the type
613            syn::Fields::Unit => EnumVariantType::Unit(UnitEnumVariantParser::new()),
614        };
615
616        Ok(Self {
617            variant: variant.clone(),
618            name: variant_name,
619            ty: parse_variant,
620        })
621    }
622
623    fn construct_variant(&self) -> TokenStream2 {
624        let fields = quote_fields(self.variant.fields.clone());
625        let variant_ident = &self.variant.ident;
626        quote! {
627            Self::#variant_ident #fields
628        }
629    }
630
631    fn quote_parser(&self, content_name: &str) -> syn::Result<TokenStream2> {
632        let construct_variant = self.construct_variant();
633        match &self.ty {
634            EnumVariantType::Struct(parser) => {
635                parser.quote_parser(&self.name, content_name, construct_variant)
636            }
637            EnumVariantType::Tuple(parser) => {
638                parser.quote_parser(&self.name, content_name, construct_variant)
639            }
640            EnumVariantType::Unit(parser) => parser.quote_parser(&self.name, construct_variant),
641        }
642    }
643
644    fn quote_schema(
645        &self,
646        tag: &str,
647        content: &str,
648        variant_name: &str,
649    ) -> syn::Result<proc_macro2::TokenStream> {
650        match &self.ty {
651            EnumVariantType::Struct(parser) => parser.quote_schema(tag, content, variant_name),
652            EnumVariantType::Tuple(parser) => parser.quote_schema(tag, content, variant_name),
653            EnumVariantType::Unit(parser) => parser.quote_schema(tag, variant_name),
654        }
655    }
656}
657
658enum EnumVariantType {
659    Unit(UnitEnumVariantParser),
660    Tuple(TupleEnumVariantParser),
661    Struct(StructEnumVariantParser),
662}
663
664struct UnitEnumVariantParser {}
665
666impl UnitEnumVariantParser {
667    fn new() -> Self {
668        Self {}
669    }
670
671    fn quote_parser(
672        &self,
673        variant_name: &str,
674        construct_variant: TokenStream2,
675    ) -> syn::Result<TokenStream2> {
676        let lit_str_name = LitStr::new(&format!("{variant_name}\""), Span::call_site());
677        Ok(quote! {
678            kalosm_sample::ParserExt::map_output(
679                kalosm_sample::LiteralParser::from(#lit_str_name),
680                |_| #construct_variant
681            )
682        })
683    }
684
685    fn quote_schema(&self, tag: &str, variant_name: &str) -> syn::Result<proc_macro2::TokenStream> {
686        Ok(quote! {
687            kalosm_sample::SchemaType::Object(
688                kalosm_sample::JsonObjectSchema::new([
689                    kalosm_sample::JsonPropertySchema::new(
690                        #tag,
691                        kalosm_sample::SchemaType::Enum(
692                            kalosm_sample::EnumSchema::new([
693                                kalosm_sample::SchemaLiteral::String(#variant_name.to_string())
694                            ])
695                        )
696                    )
697                    .with_required(true)
698                ])
699            )
700        })
701    }
702}
703
704struct StructEnumVariantParser {
705    fields: FieldsParser,
706}
707
708impl StructEnumVariantParser {
709    fn new(fields: &FieldsNamed) -> syn::Result<Self> {
710        let fields = fields.named.iter().cloned().collect::<Vec<_>>();
711        Ok(Self {
712            fields: FieldsParser::new(&fields)?,
713        })
714    }
715
716    fn quote_parser(
717        &self,
718        variant_name: &str,
719        content_name: &str,
720        construct_variant: TokenStream2,
721    ) -> syn::Result<TokenStream2> {
722        let parse_name_and_data = LitStr::new(
723            &format!("{variant_name}\", \"{content_name}\": "),
724            Span::call_site(),
725        );
726        let field_parser = self.fields.parser(construct_variant)?;
727        Ok(quote! {
728            kalosm_sample::ParserExt::ignore_output_then(
729                kalosm_sample::LiteralParser::from(#parse_name_and_data),
730                #field_parser
731            )
732        })
733    }
734
735    fn quote_schema(
736        &self,
737        tag: &str,
738        content: &str,
739        variant_name: &str,
740    ) -> syn::Result<proc_macro2::TokenStream> {
741        let variant_parser = self.fields.quote_schema();
742        Ok(quote! {
743            kalosm_sample::SchemaType::Object(
744                kalosm_sample::JsonObjectSchema::new([
745                    kalosm_sample::JsonPropertySchema::new(
746                        #tag,
747                        kalosm_sample::SchemaType::Enum(
748                            kalosm_sample::EnumSchema::new([
749                                kalosm_sample::SchemaLiteral::String(#variant_name.to_string())
750                            ])
751                        )
752                    )
753                    .with_required(true),
754                    kalosm_sample::JsonPropertySchema::new(
755                        #content,
756                        kalosm_sample::SchemaType::Object(
757                            #variant_parser
758                        )
759                    )
760                    .with_required(true)
761                ])
762            )
763        })
764    }
765}
766
767struct TupleEnumVariantParser {
768    field: Field,
769}
770
771impl TupleEnumVariantParser {
772    fn new(fields: &Field) -> Self {
773        Self {
774            field: fields.clone(),
775        }
776    }
777
778    fn quote_parser(
779        &self,
780        variant_name: &str,
781        content: &str,
782        construct_variant: TokenStream2,
783    ) -> syn::Result<TokenStream2> {
784        let parse_name_and_data = LitStr::new(
785            &format!("{variant_name}\", \"{content}\": "),
786            Span::call_site(),
787        );
788        let ty = &self.field.ty;
789        Ok(quote! {
790            kalosm_sample::ParserExt::map_output(
791                kalosm_sample::ParserExt::ignore_output_then(
792                    kalosm_sample::LiteralParser::from(#parse_name_and_data),
793                    <#ty as kalosm_sample::Parse>::new_parser()
794                ),
795                |data0| #construct_variant
796            )
797        })
798    }
799
800    fn quote_schema(
801        &self,
802        tag: &str,
803        content: &str,
804        variant_name: &str,
805    ) -> syn::Result<proc_macro2::TokenStream> {
806        let ty = &self.field.ty;
807        Ok(quote! {
808            kalosm_sample::SchemaType::Object(
809                kalosm_sample::JsonObjectSchema::new([
810                    kalosm_sample::JsonPropertySchema::new(
811                        #tag,
812                        kalosm_sample::SchemaType::Enum(
813                            kalosm_sample::EnumSchema::new([
814                                kalosm_sample::SchemaLiteral::String(#variant_name.to_string())
815                            ])
816                        )
817                    )
818                    .with_required(true),
819                    kalosm_sample::JsonPropertySchema::new(
820                        #content,
821                        <#ty as kalosm_sample::Schema>::schema()
822                    )
823                    .with_required(true)
824                ])
825            )
826        })
827    }
828}
829
830fn unit_enum_parser(attrs: Vec<syn::Attribute>, data: DataEnum, ty: Ident) -> TokenStream2 {
831    // We can derive an efficient state machine for unit enums
832    let parser_state = format_ident!("{}ParserState", ty);
833
834    // Look for #[parse(unquoted)] on the enum
835    let mut unquoted = false;
836    for attr in attrs.iter() {
837        if attr.path().is_ident("parse") {
838            let result = attr.parse_nested_meta(|meta| {
839                if meta.path.is_ident("unquoted") {
840                    unquoted = true;
841                    return Ok(());
842                }
843                Err(meta.error("expected `unquoted`"))
844            });
845            if let Err(err) = result {
846                return err.to_compile_error();
847            }
848        }
849    }
850
851    let mut parse_construction_map = HashMap::new();
852    for variant in data.variants.iter() {
853        let variant_name = &variant.ident;
854        let fields = &variant.fields;
855        let construct_variant = quote! {
856            #ty::#variant_name #fields
857        };
858        let literal_string = match unit_parse_literal(&variant.attrs, variant_name, unquoted) {
859            Ok(literal_string) => literal_string,
860            Err(err) => return err.to_compile_error(),
861        };
862        parse_construction_map.insert(literal_string.as_bytes().to_vec(), construct_variant);
863    }
864
865    let mut prefix_state_map = HashMap::new();
866    let mut max_state = 0usize;
867    for bytes in parse_construction_map.keys() {
868        for i in 0..bytes.len() + 1 {
869            let prefix = &bytes[..i];
870            if prefix_state_map.contains_key(prefix) {
871                continue;
872            }
873            prefix_state_map.insert(prefix, max_state);
874            max_state += 1;
875        }
876    }
877
878    let mut parse_states = Vec::new();
879    for (state_prefix, state) in &prefix_state_map {
880        let state = LitInt::new(&state.to_string(), ty.span());
881
882        let mut next_bytes = Vec::new();
883        for (next_state_prefix, next_state) in &prefix_state_map {
884            if let Some(&[byte]) = next_state_prefix.strip_prefix(*state_prefix) {
885                let next_state = LitInt::new(&next_state.to_string(), ty.span());
886
887                next_bytes.push(quote! {
888                    #byte => state = #parser_state(#next_state),
889                });
890            }
891        }
892
893        let unrecognized_byte = if let Some(constructor) = parse_construction_map.get(*state_prefix)
894        {
895            quote! {
896                return kalosm_sample::ParseResult::Ok(kalosm_sample::ParseStatus::Finished {
897                    result: #constructor,
898                    remaining: &input[i..],
899                })
900            }
901        } else {
902            quote! {
903                return kalosm_sample::ParseResult::Err(kalosm_sample::ParserError::msg("Unrecognized byte"))
904            }
905        };
906
907        if !next_bytes.is_empty() {
908            parse_states.push(quote! {
909                #state => match byte {
910                    #(#next_bytes)*
911                    _ => #unrecognized_byte,
912                },
913            });
914        } else if parse_construction_map.contains_key(*state_prefix) {
915            parse_states.push(quote! {
916                #state => #unrecognized_byte,
917            });
918        }
919    }
920
921    let mut match_required_next = Vec::new();
922    for (state_prefix, state) in &prefix_state_map {
923        let state = LitInt::new(&state.to_string(), ty.span());
924        let mut required_next = Vec::new();
925        let mut current_prefix = state_prefix.to_vec();
926        loop {
927            let mut valid_next_bytes = Vec::new();
928            for prefix in prefix_state_map.keys() {
929                if let Some(&[byte]) = prefix.strip_prefix(current_prefix.as_slice()) {
930                    valid_next_bytes.push(byte);
931                }
932            }
933            if let [byte] = *valid_next_bytes {
934                current_prefix.push(byte);
935                required_next.push(byte);
936            } else {
937                break;
938            }
939        }
940        if !required_next.is_empty() {
941            let required_next_str = String::from_utf8_lossy(&required_next);
942            match_required_next.push(quote! {
943                #state => #required_next_str,
944            });
945        }
946    }
947
948    let state_type = if max_state <= u8::MAX as usize {
949        quote! { u8 }
950    } else if max_state <= u16::MAX as usize {
951        quote! { u16 }
952    } else if max_state <= u32::MAX as usize {
953        quote! { u32 }
954    } else if max_state <= u64::MAX as usize {
955        quote! { u64 }
956    } else {
957        quote! { u128 }
958    };
959
960    let impl_parser_state = quote! {
961        #[derive(Debug, Clone, Copy)]
962        struct #parser_state(#state_type);
963
964        impl #parser_state {
965            const fn new() -> Self {
966                Self(0)
967            }
968        }
969    };
970
971    let parser = format_ident!("{}Parser", ty);
972    let impl_parser = quote! {
973        struct #parser;
974
975        impl kalosm_sample::CreateParserState for #parser {
976            fn create_parser_state(&self) -> <Self as kalosm_sample::Parser>::PartialState {
977                #parser_state::new()
978            }
979        }
980        impl kalosm_sample::Parser for #parser {
981            type Output = #ty;
982            type PartialState = #parser_state;
983
984            fn parse<'a>(
985                &self,
986                state: &Self::PartialState,
987                input: &'a [u8],
988            ) -> kalosm_sample::ParseResult<kalosm_sample::ParseStatus<'a, Self::PartialState, Self::Output>> {
989                let mut state = *state;
990                for (i, byte) in input.iter().enumerate() {
991                    match state.0 {
992                        #(#parse_states)*
993                        _ => return kalosm_sample::ParseResult::Err(kalosm_sample::ParserError::msg("Invalid state")),
994                    }
995                }
996                kalosm_sample::ParseResult::Ok(kalosm_sample::ParseStatus::Incomplete {
997                    new_state: state,
998                    required_next: std::borrow::Cow::Borrowed(match state.0 {
999                        #(#match_required_next)*
1000                        _ => ""
1001                    }),
1002                })
1003            }
1004        }
1005    };
1006
1007    quote! {
1008        impl kalosm_sample::Parse for #ty {
1009            fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
1010                #impl_parser_state
1011                #impl_parser
1012
1013                #parser
1014            }
1015        }
1016    }
1017}
1018
1019fn unit_enum_schema(data: DataEnum, ty: Ident) -> TokenStream2 {
1020    let mut variants = Vec::new();
1021    for variant in data.variants.iter() {
1022        let variant_name = &variant.ident;
1023        let literal_string = match unit_parse_literal_name(&variant.attrs, variant_name) {
1024            Ok(literal_string) => literal_string,
1025            Err(err) => return err.to_compile_error(),
1026        };
1027        variants.push(literal_string);
1028    }
1029
1030    let schema = unit_enum_schema_type(variants);
1031
1032    quote! {
1033        impl kalosm_sample::Schema for #ty {
1034            fn schema() -> kalosm_sample::SchemaType {
1035                #schema
1036            }
1037        }
1038    }
1039}
1040
1041fn unit_enum_schema_type(variants: impl IntoIterator<Item = String>) -> proc_macro2::TokenStream {
1042    let variants = variants.into_iter().map(|variant| {
1043        let variant = LitStr::new(&variant, variant.span());
1044        quote! {
1045            kalosm_sample::SchemaLiteral::String(#variant.to_string())
1046        }
1047    });
1048
1049    let schema = quote! {
1050        kalosm_sample::EnumSchema::new([#(#variants),*])
1051    };
1052
1053    quote! {
1054        kalosm_sample::SchemaType::Enum(#schema)
1055    }
1056}
1057
1058fn wrap_tuple(ident: &Ident, current: TokenStream2) -> TokenStream2 {
1059    quote! {
1060        (#current, #ident)
1061    }
1062}
1063
1064fn parse_rename_attribute(meta: &ParseNestedMeta) -> syn::Result<Option<LitStr>> {
1065    if meta.path.is_ident("rename") {
1066        let value = meta
1067            .value()
1068            .and_then(|value| value.parse::<syn::LitStr>())?;
1069        return Ok(Some(value));
1070    }
1071    Ok(None)
1072}
1073
1074struct FieldsParser {
1075    fields: Vec<FieldParser>,
1076}
1077
1078impl FieldsParser {
1079    fn new(fields: &[Field]) -> syn::Result<Self> {
1080        Ok(Self {
1081            fields: fields
1082                .iter()
1083                .map(FieldParser::new)
1084                .collect::<syn::Result<_>>()?,
1085        })
1086    }
1087
1088    fn parser(&self, construct: TokenStream2) -> syn::Result<TokenStream2> {
1089        let mut parsers = Vec::new();
1090        let idents: Vec<_> = self
1091            .fields
1092            .iter()
1093            .map(|f| format_ident!("{}_parser", f.field.ident.as_ref().unwrap().unraw()))
1094            .collect();
1095        for (i, (field, parser_ident)) in self.fields.iter().zip(idents.iter()).enumerate() {
1096            let mut literal_text = String::new();
1097            if i == 0 {
1098                literal_text.push_str("{ ");
1099            } else {
1100                literal_text.push_str(", ");
1101            }
1102            let field_name = &field.name;
1103            let field_parser = &field.parser;
1104            literal_text.push_str(&format!("\"{field_name}\": "));
1105            let literal_text = LitStr::new(&literal_text, field.field.ident.span());
1106
1107            parsers.push(quote! {
1108                let #parser_ident = kalosm_sample::ParserExt::ignore_output_then(
1109                    kalosm_sample::LiteralParser::from(#literal_text),
1110                    #field_parser
1111                );
1112            });
1113        }
1114
1115        let mut output_tuple = None;
1116        for field in self.fields.iter() {
1117            let name = field.field.ident.as_ref().unwrap();
1118            match output_tuple {
1119                Some(current) => {
1120                    output_tuple = Some(wrap_tuple(name, current));
1121                }
1122                None => {
1123                    output_tuple = Some(name.to_token_stream());
1124                }
1125            }
1126        }
1127
1128        let mut join_parser: Option<TokenStream2> = None;
1129        for ident in idents.iter() {
1130            match &mut join_parser {
1131                Some(current) => {
1132                    *current = quote! {
1133                        kalosm_sample::ParserExt::then(#current, #ident)
1134                    };
1135                }
1136                None => {
1137                    join_parser = Some(ident.to_token_stream());
1138                }
1139            }
1140        }
1141
1142        Ok(quote! {
1143            {
1144                #(
1145                    #parsers
1146                )*
1147
1148                kalosm_sample::ParserExt::map_output(
1149                    kalosm_sample::ParserExt::then_literal(
1150                        #join_parser,
1151                        r#" }"#
1152                    ),
1153                    |#output_tuple| #construct
1154                )
1155            }
1156        })
1157    }
1158
1159    fn quote_schema(&self) -> proc_macro2::TokenStream {
1160        let properties = self.fields.iter().map(|field| field.quote_schema());
1161        quote! {
1162            kalosm_sample::JsonObjectSchema::new(
1163                vec![#(#properties),*]
1164            )
1165        }
1166    }
1167}
1168
1169struct FieldParser {
1170    field: Field,
1171    parser: Parser,
1172    name: String,
1173}
1174
1175impl FieldParser {
1176    fn new(field: &Field) -> syn::Result<Self> {
1177        let mut field_name = field.ident.as_ref().unwrap().unraw().to_string();
1178        let mut parser: Parser = syn::parse2(field.ty.to_token_stream())?;
1179
1180        // Look for #[parse(rename = "name")] or #[parse(with = expr)] attributes
1181        for attr in field.attrs.iter() {
1182            if attr.path().is_ident("parse") {
1183                attr.parse_nested_meta(|meta| {
1184                    if let Some(value) = parse_rename_attribute(&meta)? {
1185                        field_name = value.value();
1186                        Ok(())
1187                    } else {
1188                        let attribute_applied = parser.apply_attribute(&meta)?;
1189                        if !attribute_applied {
1190                            let mut possible_attributes = vec!["rename"];
1191                            possible_attributes.extend(parser.possible_attributes());
1192                            return Err(meta.error(expected_attributes_error(possible_attributes)));
1193                        }
1194                        Ok(())
1195                    }
1196                })?;
1197            }
1198        }
1199
1200        Ok(Self {
1201            field: field.clone(),
1202            parser,
1203            name: field_name,
1204        })
1205    }
1206
1207    fn quote_schema(&self) -> proc_macro2::TokenStream {
1208        let schema = self.parser.quote_schema();
1209        let name = &self.name;
1210        let description = doc_comment(&self.field.attrs);
1211        let description = description.map(|description| quote! { .with_description(#description) });
1212        quote! {
1213            kalosm_sample::JsonPropertySchema::new(#name.to_string(), #schema)
1214                .with_required(true)
1215                #description
1216        }
1217    }
1218}
1219
1220fn doc_comment(attrs: &[syn::Attribute]) -> Option<String> {
1221    let mut description = String::new();
1222    for attr in attrs {
1223        if !attr.path().is_ident("doc") {
1224            continue;
1225        }
1226        let syn::Meta::NameValue(meta) = &attr.meta else {
1227            continue;
1228        };
1229        if let Ok(lit_str) = syn::parse2::<syn::LitStr>(meta.value.to_token_stream()) {
1230            if !description.is_empty() {
1231                description.push('\n');
1232            }
1233            let value = lit_str.value();
1234            let mut borrowed = &*value;
1235            if borrowed.starts_with(' ') {
1236                borrowed = &borrowed[1..];
1237            }
1238            description.push_str(borrowed);
1239        }
1240    }
1241    (!description.is_empty()).then_some(description)
1242}
1243
1244fn expected_attributes_error(
1245    expected_attributes: impl IntoIterator<Item = &'static str>,
1246) -> String {
1247    let mut error_message = String::from("Expected one of the following attributes: ");
1248    let expected_attributes = expected_attributes.into_iter().collect::<Vec<_>>();
1249    for (i, attribute) in expected_attributes.iter().enumerate() {
1250        error_message.push_str(attribute);
1251        match i.cmp(&(expected_attributes.len().saturating_sub(2))) {
1252            Ordering::Less => {
1253                error_message.push_str(", ");
1254            }
1255            Ordering::Equal => {
1256                error_message.push_str(" or ");
1257            }
1258            Ordering::Greater => {}
1259        }
1260    }
1261    error_message
1262}
1263
1264#[derive(Debug)]
1265enum ParserType {
1266    String(StringParserOptions),
1267    Number(NumberParserOptions),
1268    Integer(NumberParserOptions),
1269    Boolean(BoolOptions),
1270    Custom(proc_macro2::TokenStream),
1271}
1272
1273impl Parse for ParserType {
1274    fn parse(input: ParseStream) -> syn::Result<Self> {
1275        if input.peek(Ident) {
1276            let path = input.parse::<Path>()?;
1277            if let Ok(string) = StringParserOptions::from_path(&path) {
1278                return Ok(Self::String(string));
1279            } else if let Ok(number) = NumberParserOptions::from_path(&path) {
1280                return Ok(match number.ty {
1281                    NumberType::F64 | NumberType::F32 => Self::Number(number),
1282                    _ => Self::Integer(number),
1283                });
1284            } else if let Ok(boolean) = BoolOptions::from_path(&path) {
1285                return Ok(Self::Boolean(boolean));
1286            }
1287            Ok(Self::Custom(path.to_token_stream()))
1288        } else {
1289            Ok(Self::Custom(input.parse()?))
1290        }
1291    }
1292}
1293
1294#[test]
1295fn type_parses() {
1296    assert!(matches!(
1297        dbg!(syn::parse2::<ParserType>(quote! { String })).unwrap(),
1298        ParserType::String(_)
1299    ));
1300    assert!(matches!(
1301        dbg!(syn::parse2::<ParserType>(quote! { std::string::String })).unwrap(),
1302        ParserType::String(_)
1303    ));
1304    assert!(matches!(
1305        dbg!(syn::parse2::<ParserType>(quote! { i32 })).unwrap(),
1306        ParserType::Integer(_)
1307    ));
1308    assert!(matches!(
1309        dbg!(syn::parse2::<ParserType>(quote! { f32 })).unwrap(),
1310        ParserType::Number(_)
1311    ));
1312}
1313
1314#[derive(Debug)]
1315struct Parser {
1316    ty: ParserType,
1317    with: Option<proc_macro2::TokenStream>,
1318    schema: Option<proc_macro2::TokenStream>,
1319}
1320
1321impl Parse for Parser {
1322    fn parse(input: ParseStream) -> syn::Result<Self> {
1323        Ok(Self {
1324            ty: input.parse()?,
1325            with: None,
1326            schema: None,
1327        })
1328    }
1329}
1330
1331impl Parser {
1332    fn apply_attribute(&mut self, input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1333        if input.path.is_ident("with") {
1334            self.with = Some(input.value()?.parse()?);
1335            Ok(true)
1336        } else if input.path.is_ident("schema") {
1337            self.schema = Some(input.value()?.parse()?);
1338            Ok(true)
1339        } else {
1340            match &mut self.ty {
1341                ParserType::String(options) => options.apply_attribute(input),
1342                ParserType::Number(options) => options.apply_attribute(input),
1343                ParserType::Integer(options) => options.apply_attribute(input),
1344                ParserType::Boolean(options) => options.apply_attribute(input),
1345                ParserType::Custom(_) => Ok(false),
1346            }
1347        }
1348    }
1349
1350    fn possible_attributes(&self) -> Vec<&'static str> {
1351        let mut attributes = vec!["with", "schema"];
1352        match &self.ty {
1353            ParserType::String(_) => attributes.extend(StringParserOptions::ATTRIBUTES),
1354            ParserType::Integer(_) | ParserType::Number(_) => {
1355                attributes.extend(NumberParserOptions::ATTRIBUTES)
1356            }
1357            ParserType::Boolean(_) => attributes.extend(BoolOptions::ATTRIBUTES),
1358            _ => {}
1359        }
1360        attributes
1361    }
1362
1363    fn quote_schema(&self) -> proc_macro2::TokenStream {
1364        if let Some(schema) = &self.schema {
1365            return schema.clone();
1366        }
1367
1368        match &self.ty {
1369            ParserType::String(options) => {
1370                let schema = options.quote_schema();
1371                quote! {
1372                    kalosm_sample::SchemaType::String(#schema)
1373                }
1374            }
1375            ParserType::Number(options) => {
1376                let schema = options.quote_schema();
1377                quote! {
1378                    kalosm_sample::SchemaType::Number(#schema)
1379                }
1380            }
1381            ParserType::Integer(options) => {
1382                let schema = options.quote_schema();
1383                quote! {
1384                    kalosm_sample::SchemaType::Integer(#schema)
1385                }
1386            }
1387            ParserType::Boolean(options) => {
1388                let schema = options.quote_schema();
1389                quote! {
1390                    kalosm_sample::SchemaType::Boolean(#schema)
1391                }
1392            }
1393            ParserType::Custom(ty) => {
1394                quote_spanned! {
1395                    ty.span() =>
1396                    <#ty as kalosm_sample::Schema>::schema()
1397                }
1398            }
1399        }
1400    }
1401}
1402
1403impl ToTokens for Parser {
1404    fn to_tokens(&self, tokens: &mut TokenStream2) {
1405        if let Some(with) = &self.with {
1406            with.to_tokens(tokens);
1407            return;
1408        }
1409
1410        tokens.extend(match &self.ty {
1411            ParserType::String(options) => {
1412                quote! {
1413                    #options
1414                }
1415            }
1416            ParserType::Integer(options) => {
1417                quote! {
1418                    #options
1419                }
1420            }
1421            ParserType::Number(options) => {
1422                quote! {
1423                    #options
1424                }
1425            }
1426            ParserType::Boolean(options) => {
1427                quote! {
1428                    #options
1429                }
1430            }
1431            ParserType::Custom(ty) => {
1432                quote! {
1433                    <#ty as kalosm_sample::Parse>::new_parser()
1434                }
1435            }
1436        })
1437    }
1438}
1439
1440// Strings accept these attributes:
1441// - #[parse(character_filter = |c| ...)]
1442// - #[parse(len = 1..=10)]
1443// - #[parse(pattern = "a+")]
1444struct StringParserOptions {
1445    path: Path,
1446    character_filter: Option<proc_macro2::TokenStream>,
1447    len: Option<proc_macro2::TokenStream>,
1448    pattern: Option<LitStr>,
1449}
1450
1451impl Debug for StringParserOptions {
1452    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1453        f.debug_struct("StringParserOptions")
1454            .field("character_filter", &self.character_filter)
1455            .field("len", &self.len)
1456            .field("pattern", &self.pattern.as_ref().map(|p| p.value()))
1457            .finish()
1458    }
1459}
1460
1461impl Parse for StringParserOptions {
1462    fn parse(input: ParseStream) -> syn::Result<Self> {
1463        let path = input.parse()?;
1464        Self::from_path(&path)
1465    }
1466}
1467
1468impl StringParserOptions {
1469    const ATTRIBUTES: &'static [&'static str] = &["character_filter", "len", "pattern"];
1470
1471    fn apply_attribute(&mut self, input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1472        if input.path.is_ident("character_filter") {
1473            self.character_filter = Some(input.value()?.parse()?);
1474            Ok(true)
1475        } else if input.path.is_ident("len") {
1476            self.len = Some(input.value()?.parse()?);
1477            Ok(true)
1478        } else if input.path.is_ident("pattern") {
1479            self.pattern = Some(input.value()?.parse()?);
1480            Ok(true)
1481        } else {
1482            Ok(false)
1483        }
1484    }
1485
1486    fn from_path(path: &Path) -> syn::Result<Self> {
1487        if !is_string(path) {
1488            return Err(syn::Error::new(path.span(), "Expected a string type"));
1489        }
1490        Ok(Self {
1491            path: path.clone(),
1492            character_filter: None,
1493            len: None,
1494            pattern: None,
1495        })
1496    }
1497
1498    fn quote_schema(&self) -> proc_macro2::TokenStream {
1499        let len = self.len.as_ref().map(|len| {
1500            quote_spanned! {
1501                len.span() =>
1502                .with_length(#len)
1503            }
1504        });
1505        let pattern = self.pattern.as_ref().map(|pattern| {
1506            quote_spanned! {
1507                pattern.span() =>
1508                .with_pattern(#pattern)
1509            }
1510        });
1511        let quote = quote_spanned! {
1512            self.path.span() =>
1513            kalosm_sample::StringSchema::new()
1514            #len
1515            #pattern
1516        };
1517        quote
1518    }
1519}
1520
1521impl ToTokens for StringParserOptions {
1522    fn to_tokens(&self, tokens: &mut TokenStream2) {
1523        if let Some(pattern) = &self.pattern {
1524            let pattern_str = pattern.value();
1525            let mut pattern_str = pattern_str.as_str();
1526            // Strip the ^ and $ from the pattern if they are present. The parser will automatically parse the whole contents
1527            if let Some(new_pattern_str) = pattern_str.strip_prefix("^") {
1528                pattern_str = new_pattern_str;
1529            }
1530            if !pattern_str.ends_with("\\$") {
1531                if let Some(new_pattern_str) = pattern_str.strip_suffix('$') {
1532                    pattern_str = new_pattern_str;
1533                }
1534            }
1535            let pattern = LitStr::new(&format!(r#""{}""#, pattern_str), pattern.span());
1536            let quote = quote_spanned! {
1537                pattern.span() =>
1538                kalosm_sample::ParserExt::map_output(
1539                    kalosm_sample::RegexParser::new(#pattern)
1540                        .unwrap(),
1541                    // Trim the quotes
1542                    |string| string[1..string.len() - 1].to_string()
1543                )
1544            };
1545            tokens.extend(quote);
1546            return;
1547        }
1548
1549        let character_filter = self.character_filter.as_ref().map(|filter| {
1550            let quote = quote_spanned! {
1551                filter.span() =>
1552                .with_allowed_characters(#filter)
1553            };
1554            quote
1555        });
1556        let len = self
1557            .len
1558            .as_ref()
1559            .map(|len| len.to_token_stream())
1560            .unwrap_or_else(|| {
1561                quote! {
1562                    0..=usize::MAX
1563                }
1564            });
1565        let quote = quote_spanned! {
1566            self.path.span() =>
1567            kalosm_sample::StringParser::new(#len)
1568            #character_filter
1569        };
1570        tokens.extend(quote);
1571    }
1572}
1573
1574fn is_string(ty: &syn::Path) -> bool {
1575    let string_path = syn::parse_quote!(::std::string::String);
1576    is_path_type(ty, &string_path)
1577}
1578
1579#[derive(Debug)]
1580enum NumberType {
1581    F64,
1582    F32,
1583    I128,
1584    I64,
1585    I32,
1586    I16,
1587    I8,
1588    Isize,
1589    U128,
1590    U64,
1591    U32,
1592    U16,
1593    U8,
1594    Usize,
1595}
1596
1597impl Parse for NumberType {
1598    fn parse(input: ParseStream) -> syn::Result<Self> {
1599        let ty = input.parse()?;
1600
1601        Self::from_path(&ty)
1602    }
1603}
1604
1605impl NumberType {
1606    fn from_path(ty: &Path) -> syn::Result<Self> {
1607        let f64_path = syn::parse_quote!(::std::primitive::f64);
1608        if is_path_type(ty, &f64_path) {
1609            return Ok(Self::F64);
1610        }
1611
1612        let f32_path = syn::parse_quote!(::std::primitive::f32);
1613        if is_path_type(ty, &f32_path) {
1614            return Ok(Self::F32);
1615        }
1616
1617        let i128_path = syn::parse_quote!(::std::primitive::i128);
1618        if is_path_type(ty, &i128_path) {
1619            return Ok(Self::I128);
1620        }
1621
1622        let i64_path = syn::parse_quote!(::std::primitive::i64);
1623        if is_path_type(ty, &i64_path) {
1624            return Ok(Self::I64);
1625        }
1626
1627        let i32_path = syn::parse_quote!(::std::primitive::i32);
1628        if is_path_type(ty, &i32_path) {
1629            return Ok(Self::I32);
1630        }
1631
1632        let i16_path = syn::parse_quote!(::std::primitive::i16);
1633        if is_path_type(ty, &i16_path) {
1634            return Ok(Self::I16);
1635        }
1636
1637        let i8_path = syn::parse_quote!(::std::primitive::i8);
1638        if is_path_type(ty, &i8_path) {
1639            return Ok(Self::I8);
1640        }
1641
1642        let isize_path = syn::parse_quote!(::std::primitive::isize);
1643        if is_path_type(ty, &isize_path) {
1644            return Ok(Self::Isize);
1645        }
1646
1647        let u128_path = syn::parse_quote!(::std::primitive::u128);
1648        if is_path_type(ty, &u128_path) {
1649            return Ok(Self::U128);
1650        }
1651
1652        let u64_path = syn::parse_quote!(::std::primitive::u64);
1653        if is_path_type(ty, &u64_path) {
1654            return Ok(Self::U64);
1655        }
1656
1657        let u32_path = syn::parse_quote!(::std::primitive::u32);
1658        if is_path_type(ty, &u32_path) {
1659            return Ok(Self::U32);
1660        }
1661
1662        let u16_path = syn::parse_quote!(::std::primitive::u16);
1663        if is_path_type(ty, &u16_path) {
1664            return Ok(Self::U16);
1665        }
1666
1667        let u8_path = syn::parse_quote!(::std::primitive::u8);
1668        if is_path_type(ty, &u8_path) {
1669            return Ok(Self::U8);
1670        }
1671
1672        let usize_path = syn::parse_quote!(::std::primitive::usize);
1673        if is_path_type(ty, &usize_path) {
1674            return Ok(Self::Usize);
1675        }
1676
1677        Err(syn::Error::new(ty.span(), "Expected a number type"))
1678    }
1679}
1680
1681impl ToTokens for NumberType {
1682    fn to_tokens(&self, tokens: &mut TokenStream2) {
1683        let quote = match self {
1684            Self::F64 => quote! {kalosm_sample::F64Parser::new()},
1685            Self::F32 => quote! {kalosm_sample::F32Parser::new()},
1686            Self::I128 => quote! {kalosm_sample::I128Parser::new()},
1687            Self::I64 => quote! {kalosm_sample::I64Parser::new()},
1688            Self::I32 => quote! {kalosm_sample::I32Parser::new()},
1689            Self::I16 => quote! {kalosm_sample::I16Parser::new()},
1690            Self::I8 => quote! {kalosm_sample::I8Parser::new()},
1691            Self::Isize => quote! {kalosm_sample::IsizeParser::new()},
1692            Self::U128 => quote! {kalosm_sample::U128Parser::new()},
1693            Self::U64 => quote! {kalosm_sample::U64Parser::new()},
1694            Self::U32 => quote! {kalosm_sample::U32Parser::new()},
1695            Self::U16 => quote! {kalosm_sample::U16Parser::new()},
1696            Self::U8 => quote! {kalosm_sample::U8Parser::new()},
1697            Self::Usize => quote! {kalosm_sample::UsizeParser::new()},
1698        };
1699
1700        tokens.extend(quote);
1701    }
1702}
1703
1704// Numbers accept these attributes:
1705// - #[parse(range = 0.0..=100.0)]
1706struct NumberParserOptions {
1707    path: Path,
1708    ty: NumberType,
1709    range: Option<proc_macro2::TokenStream>,
1710}
1711
1712impl Debug for NumberParserOptions {
1713    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1714        f.debug_struct("NumberParserOptions")
1715            .field("ty", &self.ty)
1716            .field("range", &self.range)
1717            .finish()
1718    }
1719}
1720
1721impl Parse for NumberParserOptions {
1722    fn parse(input: ParseStream) -> syn::Result<Self> {
1723        let path = input.parse::<Path>()?;
1724
1725        Self::from_path(&path)
1726    }
1727}
1728
1729impl NumberParserOptions {
1730    fn from_path(path: &Path) -> syn::Result<Self> {
1731        let ty = NumberType::from_path(path)?;
1732        Ok(Self {
1733            path: path.clone(),
1734            ty,
1735            range: None,
1736        })
1737    }
1738}
1739
1740impl ToTokens for NumberParserOptions {
1741    fn to_tokens(&self, tokens: &mut TokenStream2) {
1742        let range = self.range.as_ref().map(|range| {
1743            let quote = quote_spanned! {
1744                range.span() =>
1745                .with_range(#range)
1746            };
1747            quote
1748        });
1749        let ty = &self.ty;
1750        let quote = quote_spanned! {
1751            self.path.span() =>
1752            #ty
1753            #range
1754        };
1755        tokens.extend(quote);
1756    }
1757}
1758
1759impl NumberParserOptions {
1760    const ATTRIBUTES: &'static [&'static str] = &["range"];
1761
1762    fn apply_attribute(&mut self, input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1763        if input.path.is_ident("range") {
1764            self.range = Some(input.value()?.parse()?);
1765            Ok(true)
1766        } else {
1767            Ok(false)
1768        }
1769    }
1770
1771    fn quote_schema(&self) -> proc_macro2::TokenStream {
1772        match self.ty {
1773            NumberType::F64 | NumberType::F32 => {
1774                let range = self.range.as_ref().map(|range| {
1775                    quote_spanned! {
1776                        range.span() =>
1777                            .with_range({
1778                                let range = #range;
1779                                let start = range.start() as f64;
1780                                let end = range.end() as f64;
1781                                start..=end
1782                            })
1783                    }
1784                });
1785                quote_spanned! {
1786                    self.path.span() =>
1787                    kalosm_sample::NumberSchema::new()
1788                    #range
1789                }
1790            }
1791            _ => quote_spanned! {
1792                self.path.span() =>
1793                kalosm_sample::IntegerSchema::new()
1794            },
1795        }
1796    }
1797}
1798
1799struct BoolOptions {
1800    path: Path,
1801}
1802
1803impl Debug for BoolOptions {
1804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1805        f.debug_struct("BoolOptions").finish()
1806    }
1807}
1808
1809impl BoolOptions {
1810    const ATTRIBUTES: &'static [&'static str] = &[];
1811
1812    fn apply_attribute(&mut self, _input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1813        Ok(false)
1814    }
1815
1816    fn from_path(path: &Path) -> syn::Result<Self> {
1817        if !is_bool(path) {
1818            return Err(syn::Error::new(path.span(), "Expected a boolean type"));
1819        }
1820        Ok(Self { path: path.clone() })
1821    }
1822
1823    fn quote_schema(&self) -> proc_macro2::TokenStream {
1824        quote_spanned! {
1825            self.path.span() =>
1826            kalosm_sample::BooleanSchema::new()
1827        }
1828    }
1829}
1830
1831impl Parse for BoolOptions {
1832    fn parse(input: ParseStream) -> syn::Result<Self> {
1833        let path = input.parse()?;
1834        Self::from_path(&path)
1835    }
1836}
1837
1838impl ToTokens for BoolOptions {
1839    fn to_tokens(&self, tokens: &mut TokenStream2) {
1840        let quote = quote! {
1841            <bool as kalosm_sample::Parse>::new_parser()
1842        };
1843        tokens.extend(quote);
1844    }
1845}
1846
1847fn is_bool(ty: &syn::Path) -> bool {
1848    let bool_path = syn::parse_quote!(::std::bool);
1849    is_path_type(ty, &bool_path)
1850}
1851
1852// Check if the last type segment matches a value
1853fn is_path_type(path: &syn::Path, match_path: &TypePath) -> bool {
1854    let mut path_segments = path.segments.iter().rev();
1855    let mut match_path_segments = match_path.path.segments.iter().rev();
1856    loop {
1857        match (path_segments.next(), match_path_segments.next()) {
1858            (Some(first), Some(second)) => {
1859                if first.ident != second.ident {
1860                    return false;
1861                }
1862            }
1863            (None, None) => return true,
1864            (Some(_), None) => return false,
1865            (None, Some(_)) => return true,
1866        }
1867    }
1868}