1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};

// Convert rust code to a field in a names-only schema
// Example Input:
//  a: { b: {}, c: { d: {} } }
//
// Example Output:
//  NamesOnlySchemaNode { name: "a".to_string(), children: vec![
//    NamesOnlySchemaNode { name: "b".to_string(), children: vec![] },
//    NamesOnlySchemaNode { name: "c".to_string(), children: vec![
//      NamesOnlySchemaNode { name: "d".to_string(), children: vec![] }
//    ]}
//  ]}
fn rust_field_to_names_field(field: &Field) -> proc_macro2::TokenStream {
    let name = field.name.to_string();
    let children = rust_to_names_fields(&field.ty);
    quote! {substrait_expr::helpers::schema::NamesOnlySchemaNode {
        name: #name.to_string(),
        children: #children,
    }}
}

// Convert rust code to a vector of NamesOnlySchemaNode
//
// Example input:
//  { a: { b: {}, c: { d: {} } } }
//
// Example Output:
// vec![
//   NamesOnlySchemaNode { name: "a".to_string(), children: vec![
//     NamesOnlySchemaNode { name: "b".to_string(), children: vec![] },
//     NamesOnlySchemaNode { name: "c".to_string(), children: vec![
//       NamesOnlySchemaNode { name: "d".to_string(), children: vec![] }
//     ]}
//   ]}
// ]
fn rust_to_names_fields(schema: &NestedType) -> proc_macro2::TokenStream {
    let parsed_fields = schema
        .fields
        .iter()
        .map(|field| rust_field_to_names_field(field))
        .collect::<Vec<_>>();
    quote! {vec![#(#parsed_fields),*]}
}

// Convert rust code to a root NamesOnlySchemaNode (that has the empty string for a name)
//
// Example input:
//  { a: { b: {}, c: { d: {} } } }
//
// Example Output:
// NamesOnlySchemaNode {
//   name: "".to_string(),
//   vec![
//     NamesOnlySchemaNode { name: "a".to_string(), children: vec![
//       NamesOnlySchemaNode { name: "b".to_string(), children: vec![] },
//       NamesOnlySchemaNode { name: "c".to_string(), children: vec![
//         NamesOnlySchemaNode { name: "d".to_string(), children: vec![] }
//       ]}
//     ]}
//   ]
// }
fn rust_to_names_schema(schema: &NestedType) -> proc_macro2::TokenStream {
    let children = rust_to_names_fields(schema);
    quote! {
        substrait_expr::helpers::schema::SchemaInfo::Names(substrait_expr::helpers::schema::NamesOnlySchema::new(#children))
    }
}

// New rust syntax for a field in a names only schema
//
// Examples:
//  foo: {}
//  blah: { x: {}, y: { z: {} } }
struct Field {
    name: syn::Ident,
    _colon_token: syn::Token![:],
    ty: NestedType,
}

impl Parse for Field {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Field {
            name: input.parse()?,
            _colon_token: input.parse()?,
            ty: input.parse()?,
        })
    }
}

// New rust syntax for a nested names-only type ({field, field, field})
//
// Examples:
//  { foo: {} }
//  { blah: { x: {}, y: { z: {} } } }
struct NestedType {
    _brace_token: syn::token::Brace,
    fields: syn::punctuated::Punctuated<Field, syn::Token![,]>,
}

impl Parse for NestedType {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        Ok(Self {
            _brace_token: syn::braced!(content in input),
            fields: content.parse_terminated(Field::parse, syn::Token![,])?,
        })
    }
}

fn names_schema2(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
    let struct_type: NestedType = syn::parse2(input)?;
    Ok(rust_to_names_schema(&struct_type))
}

/// A macro to create names-only schemas from a dictionary-like rust syntax
///
/// # Examples
/// ```ignore
/// use substrait_expr::macros::names_schema;
///
/// let schema = names_schema!({
///   vector: {},
///   metadata: {
///     caption: {},
///     user_score: {}
///  }
/// });
/// ```
#[proc_macro]
pub fn names_schema(input: TokenStream) -> TokenStream {
    let input = proc_macro2::TokenStream::from(input);
    names_schema2(input).unwrap().into()
}