substrait_expr_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse::{Parse, ParseStream};
4
5// Convert rust code to a field in a names-only schema
6// Example Input:
7//  a: { b: {}, c: { d: {} } }
8//
9// Example Output:
10//  NamesOnlySchemaNode { name: "a".to_string(), children: vec![
11//    NamesOnlySchemaNode { name: "b".to_string(), children: vec![] },
12//    NamesOnlySchemaNode { name: "c".to_string(), children: vec![
13//      NamesOnlySchemaNode { name: "d".to_string(), children: vec![] }
14//    ]}
15//  ]}
16fn rust_field_to_names_field(field: &Field) -> proc_macro2::TokenStream {
17    let name = field.name.to_string();
18    let children = rust_to_names_fields(&field.ty);
19    quote! {substrait_expr::helpers::schema::NamesOnlySchemaNode {
20        name: #name.to_string(),
21        children: #children,
22    }}
23}
24
25// Convert rust code to a vector of NamesOnlySchemaNode
26//
27// Example input:
28//  { a: { b: {}, c: { d: {} } } }
29//
30// Example Output:
31// vec![
32//   NamesOnlySchemaNode { name: "a".to_string(), children: vec![
33//     NamesOnlySchemaNode { name: "b".to_string(), children: vec![] },
34//     NamesOnlySchemaNode { name: "c".to_string(), children: vec![
35//       NamesOnlySchemaNode { name: "d".to_string(), children: vec![] }
36//     ]}
37//   ]}
38// ]
39fn rust_to_names_fields(schema: &NestedType) -> proc_macro2::TokenStream {
40    let parsed_fields = schema
41        .fields
42        .iter()
43        .map(|field| rust_field_to_names_field(field))
44        .collect::<Vec<_>>();
45    quote! {vec![#(#parsed_fields),*]}
46}
47
48// Convert rust code to a root NamesOnlySchemaNode (that has the empty string for a name)
49//
50// Example input:
51//  { a: { b: {}, c: { d: {} } } }
52//
53// Example Output:
54// NamesOnlySchemaNode {
55//   name: "".to_string(),
56//   vec![
57//     NamesOnlySchemaNode { name: "a".to_string(), children: vec![
58//       NamesOnlySchemaNode { name: "b".to_string(), children: vec![] },
59//       NamesOnlySchemaNode { name: "c".to_string(), children: vec![
60//         NamesOnlySchemaNode { name: "d".to_string(), children: vec![] }
61//       ]}
62//     ]}
63//   ]
64// }
65fn rust_to_names_schema(schema: &NestedType) -> proc_macro2::TokenStream {
66    let children = rust_to_names_fields(schema);
67    quote! {
68        substrait_expr::helpers::schema::SchemaInfo::Names(substrait_expr::helpers::schema::NamesOnlySchema::new(#children))
69    }
70}
71
72// New rust syntax for a field in a names only schema
73//
74// Examples:
75//  foo: {}
76//  blah: { x: {}, y: { z: {} } }
77struct Field {
78    name: syn::Ident,
79    _colon_token: syn::Token![:],
80    ty: NestedType,
81}
82
83impl Parse for Field {
84    fn parse(input: ParseStream) -> syn::Result<Self> {
85        Ok(Field {
86            name: input.parse()?,
87            _colon_token: input.parse()?,
88            ty: input.parse()?,
89        })
90    }
91}
92
93// New rust syntax for a nested names-only type ({field, field, field})
94//
95// Examples:
96//  { foo: {} }
97//  { blah: { x: {}, y: { z: {} } } }
98struct NestedType {
99    _brace_token: syn::token::Brace,
100    fields: syn::punctuated::Punctuated<Field, syn::Token![,]>,
101}
102
103impl Parse for NestedType {
104    fn parse(input: ParseStream) -> syn::Result<Self> {
105        let content;
106        Ok(Self {
107            _brace_token: syn::braced!(content in input),
108            fields: content.parse_terminated(Field::parse, syn::Token![,])?,
109        })
110    }
111}
112
113fn names_schema2(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
114    let struct_type: NestedType = syn::parse2(input)?;
115    Ok(rust_to_names_schema(&struct_type))
116}
117
118/// A macro to create names-only schemas from a dictionary-like rust syntax
119///
120/// # Examples
121/// ```ignore
122/// use substrait_expr::macros::names_schema;
123///
124/// let schema = names_schema!({
125///   vector: {},
126///   metadata: {
127///     caption: {},
128///     user_score: {}
129///  }
130/// });
131/// ```
132#[proc_macro]
133pub fn names_schema(input: TokenStream) -> TokenStream {
134    let input = proc_macro2::TokenStream::from(input);
135    names_schema2(input).unwrap().into()
136}