use std::collections::BTreeMap;
use proc_macro2::{Delimiter, Span, TokenStream, TokenTree};
use super::tokens::{literal_string, location, only_group, path_to_string, split_top_level};
use super::{
FaceSyntax, FaceSyntaxError, FieldSyntax, ParentSyntax, SyntaxLocation, compact,
split_face_fields,
};
impl FaceSyntax {
pub fn field(&self, name: &str) -> Option<String> {
self.fields.get(name).map(|field| compact(&field.tokens))
}
pub fn cfg(&self) -> Option<&str> {
self.cfg.as_deref()
}
pub fn field_location(&self, name: &str) -> Option<&SyntaxLocation> {
self.fields.get(name).map(|field| &field.location)
}
pub fn path(&self, name: &str) -> Option<String> {
let field = self.fields.get(name)?;
syn::parse2::<syn::Path>(field.tokens.clone())
.ok()
.map(|path| path_to_string(&path))
}
pub fn string(&self, name: &str) -> Option<String> {
let field = self.fields.get(name)?;
syn::parse2::<syn::LitStr>(field.tokens.clone())
.ok()
.map(|literal| literal.value())
}
pub fn boolean(&self, name: &str) -> Option<bool> {
let field = self.fields.get(name)?;
syn::parse2::<syn::LitBool>(field.tokens.clone())
.ok()
.map(|literal| literal.value)
}
pub fn localized(&self, name: &str, language: &str) -> Option<String> {
let field = self.fields.get(name)?;
let group = only_group(&field.tokens, Delimiter::Brace)?;
parse_fields(group.stream(), group.span())
.ok()?
.get(language)
.and_then(|field| syn::parse2::<syn::LitStr>(field.tokens.clone()).ok())
.map(|literal| literal.value())
}
pub fn string_list(&self, name: &str) -> Option<Vec<String>> {
match name {
"handle_traits" => return self.trait_labels("handle_contracts", "handle_traits"),
"part_traits" => return self.trait_labels("part_contracts", "part_traits"),
_ => {}
}
self.written_string_list(name)
}
fn trait_labels(&self, path_field: &str, label_field: &str) -> Option<Vec<String>> {
let paths = self.path_list(path_field).unwrap_or_default();
if paths.is_empty() {
return self.written_string_list(label_field);
}
crate::authoring::parse::trait_names_from_paths(&paths.join(","))
.ok()
.map(|labels| {
labels
.split(',')
.map(str::trim)
.filter(|label| !label.is_empty())
.map(str::to_owned)
.collect()
})
}
fn written_string_list(&self, name: &str) -> Option<Vec<String>> {
let field = self.fields.get(name)?;
let group = only_group(&field.tokens, Delimiter::Bracket)?;
split_top_level(group.stream())
.into_iter()
.map(|tokens| syn::parse2::<syn::LitStr>(tokens).map(|literal| literal.value()))
.collect::<Result<Vec<_>, _>>()
.ok()
}
pub fn path_list(&self, name: &str) -> Option<Vec<String>> {
let field = self.fields.get(name)?;
let group = only_group(&field.tokens, Delimiter::Bracket)?;
split_top_level(group.stream())
.into_iter()
.map(|tokens| syn::parse2::<syn::Path>(tokens).map(|path| path_to_string(&path)))
.collect::<Result<Vec<_>, _>>()
.ok()
}
pub fn requirements(&self, name: &str) -> Option<Vec<(String, String)>> {
let field = self.fields.get(name)?;
let group = only_group(&field.tokens, Delimiter::Bracket)?;
split_top_level(group.stream())
.into_iter()
.map(parse_requirement)
.collect::<Option<Vec<_>>>()
}
pub fn option_string(&self, name: &str) -> Option<Option<String>> {
let field = self.fields.get(name)?;
let expression = syn::parse2::<syn::Expr>(field.tokens.clone()).ok()?;
match expression {
syn::Expr::Path(path) if path.path.is_ident("None") => Some(None),
syn::Expr::Call(call) => {
let syn::Expr::Path(function) = *call.func else {
return None;
};
if !function.path.is_ident("Some") || call.args.len() != 1 {
return None;
}
let syn::Expr::Lit(argument) = call.args.first()? else {
return None;
};
let syn::Lit::Str(value) = &argument.lit else {
return None;
};
Some(Some(value.value()))
}
_ => None,
}
}
pub fn parent(&self) -> Option<ParentSyntax> {
let field = self.fields.get("parent")?;
let expression = syn::parse2::<syn::Expr>(field.tokens.clone()).ok()?;
match expression {
syn::Expr::Path(path) => {
let path = path_to_string(&path.path);
if path.ends_with("ROOT_NODE_ID") {
Some(ParentSyntax::Root)
} else {
path.strip_suffix("::NODE_ID")
.map(|module| ParentSyntax::NodePath(module.to_owned()))
}
}
syn::Expr::Call(call) => parse_parent_call(call),
_ => None,
}
}
}
pub(super) fn parse_fields(
tokens: TokenStream,
fallback_span: Span,
) -> Result<BTreeMap<String, FieldSyntax>, FaceSyntaxError> {
let mut fields = BTreeMap::new();
for field in split_face_fields(tokens) {
let mut tokens = field.into_iter();
let Some(TokenTree::Ident(name)) = tokens.next() else {
return Err(super::syntax_error(
fallback_span,
"expected a registration field name",
));
};
let Some(TokenTree::Punct(colon)) = tokens.next() else {
return Err(super::syntax_error(
name.span(),
format!("expected `:` after `{name}`"),
));
};
if colon.as_char() != ':' {
return Err(super::syntax_error(
colon.span(),
format!("expected `:` after `{name}`"),
));
}
let value = tokens.collect::<TokenStream>();
if value.is_empty() {
return Err(super::syntax_error(
name.span(),
format!("field `{name}` has no value"),
));
}
let field_name = name.to_string();
if fields
.insert(
field_name.clone(),
FieldSyntax {
tokens: value,
location: location(name.span()),
},
)
.is_some()
{
return Err(super::syntax_error(
name.span(),
format!("duplicate field `{field_name}`"),
));
}
}
Ok(fields)
}
fn parse_requirement(tokens: TokenStream) -> Option<(String, String)> {
let tokens = tokens.into_iter().collect::<Vec<_>>();
let arrow = tokens.windows(2).position(|pair| {
matches!(&pair[0], TokenTree::Punct(punct) if punct.as_char() == '=')
&& matches!(&pair[1], TokenTree::Punct(punct) if punct.as_char() == '>')
})?;
let left = tokens[..arrow].iter().cloned().collect::<TokenStream>();
let right = tokens[arrow + 2..].iter().cloned().collect::<TokenStream>();
let capability = syn::parse2::<syn::LitStr>(left).ok()?.value();
let provider = syn::parse2::<syn::LitStr>(right).ok()?.value();
Some((capability, provider))
}
fn parse_parent_call(call: syn::ExprCall) -> Option<ParentSyntax> {
let syn::Expr::Path(function) = *call.func else {
return None;
};
let function = path_to_string(&function.path);
if function.ends_with("root_node_id") && call.args.len() == 1 {
return Some(ParentSyntax::Root);
}
if !function.ends_with("NodeId::from_path") || call.args.len() != 2 {
return None;
}
let mut arguments = call.args.iter();
let source = literal_string(arguments.next()?)?;
let kind = literal_string(arguments.next()?)?;
Some(ParentSyntax::FromPath { source, kind })
}