Skip to content

Commit

Permalink
feat: improve the ergonomics of empty leaf nodes (#45)
Browse files Browse the repository at this point in the history
* document behaviour

* appease clippy
  • Loading branch information
soqb authored Sep 3, 2023
1 parent ef44dfe commit c14f153
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 76 deletions.
102 changes: 62 additions & 40 deletions macro/src/expansion.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashSet;

use proc_macro2::Span;
use quote::ToTokens;
use quote::{quote, ToTokens};
use rust_sitter_common::*;
use syn::{parse::Parse, punctuated::Punctuated, *};

Expand Down Expand Up @@ -115,28 +115,40 @@ fn gen_struct_or_variant(
fields: Fields,
variant_ident: Option<Ident>,
containing_type: Ident,
container_attrs: Vec<Attribute>,
out: &mut Vec<Item>,
) {
fields.iter().enumerate().for_each(|(i, field)| {
let ident_str = field
.ident
.as_ref()
.map(|v| v.to_string())
.unwrap_or(format!("{i}"));

if !field
.attrs
.iter()
.any(|attr| attr.path == syn::parse_quote!(rust_sitter::skip))
{
gen_field(
format!("{}_{}", path.clone(), ident_str),
ident_str,
field.clone(),
out,
);
}
});
if fields == Fields::Unit {
let dummy_field = Field {
attrs: container_attrs,
vis: Visibility::Inherited,
ident: None,
colon_token: None,
ty: Type::Verbatim(quote!(())), // unit type.
};
gen_field(format!("{path}_unit"), "unit".to_owned(), dummy_field, out);
} else {
fields.iter().enumerate().for_each(|(i, field)| {
let ident_str = field
.ident
.as_ref()
.map(|v| v.to_string())
.unwrap_or(format!("{i}"));

if !field
.attrs
.iter()
.any(|attr| attr.path == syn::parse_quote!(rust_sitter::skip))
{
gen_field(
format!("{}_{}", path, ident_str),
ident_str,
field.clone(),
out,
);
}
});
}

let extract_ident = Ident::new(&format!("extract_{path}"), Span::call_site());

Expand All @@ -159,10 +171,7 @@ fn gen_struct_or_variant(
.map(|v| v.to_string())
.unwrap_or(format!("{i}"));

let ident = Ident::new(
&format!("extract_{}_{}", path.clone(), ident_str),
Span::call_site(),
);
let ident = Ident::new(&format!("extract_{path}_{ident_str}"), Span::call_site());

syn::parse_quote! {
#ident(&mut cursor, source, &mut last_idx)
Expand All @@ -184,25 +193,36 @@ fn gen_struct_or_variant(
})
.collect::<Vec<ParamOrField>>();

let construct_expr: syn::Expr = if let Some(variant_ident) = variant_ident {
if have_named_field {
syn::parse_quote! {
#containing_type::#variant_ident {
#(#children_parsed),*
let construct_name = match variant_ident {
Some(ident) => quote! {
#containing_type::#ident
},
None => quote! {
#containing_type
},
};

let construct_expr = {
match &fields {
Fields::Unit => {
let ident = Ident::new(&format!("extract_{path}_unit"), Span::call_site());
quote! {
{
#ident(&mut cursor, source, &mut last_idx);
#construct_name
}
}
}
} else {
syn::parse_quote! {
#containing_type::#variant_ident(
Fields::Named(_) => quote! {
#construct_name {
#(#children_parsed),*
}
},
Fields::Unnamed(_) => quote! {
#construct_name(
#(#children_parsed),*
)
}
}
} else {
syn::parse_quote! {
#containing_type {
#(#children_parsed),*
}
},
}
};

Expand Down Expand Up @@ -276,6 +296,7 @@ pub fn expand_grammar(input: ItemMod) -> ItemMod {
v.fields.clone(),
Some(v.ident.clone()),
e.ident.clone(),
v.attrs.clone(),
&mut impl_body,
)
});
Expand Down Expand Up @@ -337,6 +358,7 @@ pub fn expand_grammar(input: ItemMod) -> ItemMod {
s.fields.clone(),
None,
s.ident.clone(),
s.attrs.clone(),
&mut impl_body,
);

Expand Down
20 changes: 19 additions & 1 deletion macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,31 @@ pub fn extra(
/// such as a number, then the `transform` argument can be used to specify a function
/// that will be called with the token's text.
///
/// ## Example
/// The attribute can also be applied to a struct or enum variant with no fields.
///
/// ## Examples
///
/// Using the `leaf` attribute on a field:
/// ```ignore
/// Number(
/// #[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())]
/// u32
/// )
/// ```
///
/// Using the attribute on a unit struct or unit enum variant:
/// ```ignore
/// #[rust_sitter::leaf(text = "9")]
/// struct BigDigit;
///
/// enum SmallDigit {
/// #[rust_sitter::leaf(text = "0")]
/// Zero,
/// #[rust_sitter::leaf(text = "1")]
/// One,
/// }
/// ```
///
pub fn leaf(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
Expand Down
95 changes: 60 additions & 35 deletions tool/src/expansion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,42 @@ fn gen_struct_or_variant(
out: &mut Map<String, Value>,
word_rule: &mut Option<String>,
) {
fn gen_field_optional(
path: &str,
field: &Field,
word_rule: &mut Option<String>,
out: &mut Map<String, Value>,
ident_str: String,
) -> Value {
let (field_contents, is_option) = gen_field(
format!("{path}_{ident_str}"),
field.ty.clone(),
field.attrs.clone(),
word_rule,
out,
);

let core = json!({
"type": "FIELD",
"name": ident_str,
"content": field_contents
});

if is_option {
json!({
"type": "CHOICE",
"members": [
{
"type": "BLANK"
},
core
]
})
} else {
core
}
}

let children = fields
.iter()
.enumerate()
Expand All @@ -263,33 +299,7 @@ fn gen_struct_or_variant(
.map(|v| v.to_string())
.unwrap_or(format!("{i}"));

let (field_contents, is_option) = gen_field(
format!("{}_{}", path.clone(), ident_str),
field.ty.clone(),
field.attrs.clone(),
word_rule,
out,
);

let core = json!({
"type": "FIELD",
"name": ident_str,
"content": field_contents
});

if is_option {
Some(json!({
"type": "CHOICE",
"members": [
{
"type": "BLANK"
},
core
]
}))
} else {
Some(core)
}
Some(gen_field_optional(&path, field, word_rule, out, ident_str))
}
})
.collect::<Vec<Value>>();
Expand All @@ -312,10 +322,25 @@ fn gen_struct_or_variant(

let prec_right_param = prec_right_attr.and_then(|a| a.parse_args_with(Expr::parse).ok());

let seq_rule = json!({
"type": "SEQ",
"members": children
});
let base_rule = match fields {
Fields::Unit => {
let dummy_field = Field {
attrs: attrs.clone(),
vis: Visibility::Inherited,
ident: None,
colon_token: None,
ty: Type::Tuple(TypeTuple {
paren_token: Default::default(),
elems: Punctuated::new(),
}),
};
gen_field_optional(&path, &dummy_field, word_rule, out, "unit".to_owned())
}
_ => json!({
"type": "SEQ",
"members": children
}),
};

let rule = if let Some(Expr::Lit(lit)) = prec_param {
if prec_left_attr.is_some() || prec_right_attr.is_some() {
Expand All @@ -326,7 +351,7 @@ fn gen_struct_or_variant(
json!({
"type": "PREC",
"value": i.base10_parse::<u32>().unwrap(),
"content": seq_rule
"content": base_rule
})
} else {
panic!("Expected integer literal for precedence");
Expand All @@ -340,7 +365,7 @@ fn gen_struct_or_variant(
json!({
"type": "PREC_LEFT",
"value": i.base10_parse::<u32>().unwrap(),
"content": seq_rule
"content": base_rule
})
} else {
panic!("Expected integer literal for precedence");
Expand All @@ -350,13 +375,13 @@ fn gen_struct_or_variant(
json!({
"type": "PREC_RIGHT",
"value": i.base10_parse::<u32>().unwrap(),
"content": seq_rule
"content": base_rule
})
} else {
panic!("Expected integer literal for precedence");
}
} else {
seq_rule
base_rule
};

out.insert(path, rule);
Expand Down

0 comments on commit c14f153

Please sign in to comment.