#![warn(missing_docs)]
use proc_macro::TokenStream;
use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenStream as Tokens, TokenTree};
use nichlink::lexicon;
use nichlink::registry_core::declaration::FACE_FIELD_ORDER;
use nichlink::registry_core::syntax::split_face_fields;
use crate::front_end::{error_at, render, splice, split_mirror_fields, split_semicolons};
use crate::mirror::{Field, mirror_item, punct};
#[path = "front_end.rs"]
mod front_end;
#[path = "mirror.rs"]
mod mirror;
#[proc_macro]
pub fn face_fields(input: TokenStream) -> TokenStream {
normalise(Tokens::from(input))
.unwrap_or_else(|error| error)
.into()
}
#[proc_macro]
pub fn face_rule_or(input: TokenStream) -> TokenStream {
let mut parts = split_semicolons(Tokens::from(input)).into_iter();
let fallback = parts.next().unwrap_or_default();
let needs_registry = parts.next().unwrap_or_default();
let authored = parts.next().unwrap_or_default();
if !authored.is_empty() {
return authored.into();
}
let owns_registry = needs_registry
.into_iter()
.map(|token| token.to_string())
.collect::<String>();
if owns_registry == "true" {
let template: Tokens = "{ #[cfg(not(rust_analyzer))] let __nichlink_rule = super::registry_rule::REGISTRATION_RULE; #[cfg(rust_analyzer)] let __nichlink_rule = __NICHLINK_IDE_FALLBACK; __nichlink_rule }"
.parse()
.expect("the derived rule template is static");
return splice(template, "__NICHLINK_IDE_FALLBACK", &fallback).into();
}
fallback.into()
}
#[proc_macro]
pub fn face_trait_labels_or(input: TokenStream) -> TokenStream {
let mut parts = split_semicolons(Tokens::from(input)).into_iter();
let paths = parts.next().unwrap_or_default();
let labels = parts.next().unwrap_or_default();
let derived = bracket_items(&paths)
.and_then(|items| items.iter().map(last_segment).collect::<Option<Vec<_>>>())
.filter(|names| !names.is_empty());
let Some(derived) = derived else {
let fallback = format!("&{labels}");
return fallback
.parse::<Tokens>()
.map_or_else(|_| labels.into(), TokenStream::from);
};
let literal = format!(
"&[{}]",
derived
.iter()
.map(|name| format!("{name:?}"))
.collect::<Vec<_>>()
.join(", ")
);
literal
.parse::<Tokens>()
.map_or_else(|_| labels.into(), TokenStream::from)
}
fn bracket_items(tokens: &Tokens) -> Option<Vec<Tokens>> {
let group = tokens.clone().into_iter().find_map(|token| match token {
TokenTree::Group(group) if group.delimiter() == Delimiter::Bracket => Some(group),
_ => None,
})?;
let mut items = vec![Tokens::new()];
for token in group.stream() {
let separator = matches!(&token, TokenTree::Punct(punct) if punct.as_char() == ',');
if separator {
items.push(Tokens::new());
} else {
items.last_mut()?.extend([token]);
}
}
Some(items.into_iter().filter(|item| !item.is_empty()).collect())
}
fn last_segment(path: &Tokens) -> Option<String> {
let text = path.to_string().replace(' ', "");
let name = text.rsplit("::").next()?.to_owned();
let mut characters = name.chars();
let first = characters.next()?;
if !(first.is_alphabetic() || first == '_')
|| !characters.all(|c| c.is_alphanumeric() || c == '_')
{
return None;
}
Some(name)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Target {
Control,
External,
}
pub(crate) fn normalise(input: Tokens) -> Result<Tokens, Tokens> {
let mut list = input.into_iter().collect::<Vec<_>>();
let mut target = Target::Control;
if let (Some(TokenTree::Punct(at)), Some(TokenTree::Ident(name))) = (list.first(), list.get(1))
&& at.as_char() == '@'
{
target = match name.to_string().as_str() {
"control" => Target::Control,
"external" => Target::External,
other => {
return Err(error_at(
name.span(),
format!("unknown face field target `{other}`"),
));
}
};
list.drain(0..2);
}
let input = list.into_iter().collect::<Tokens>();
let original = input.to_string();
let mut collector: Option<TokenTree> = None;
let mut fields: Vec<Field> = Vec::new();
for field in split_face_fields(input) {
let tokens = field.into_iter().collect::<Vec<_>>();
let Some(TokenTree::Ident(name)) = tokens.first().cloned() else {
let span = tokens.first().map_or_else(Span::call_site, TokenTree::span);
return Err(error_at(
span,
format!("expected a face field name, found `{}`", render(&tokens)),
));
};
match tokens.get(1) {
Some(TokenTree::Punct(colon)) if colon.as_char() == ':' => {}
_ => {
return Err(error_at(
name.span(),
format!("expected `:` after face field `{name}`"),
));
}
}
if tokens.len() == 2 {
return Err(error_at(
name.span(),
format!("face field `{name}` has no value"),
));
}
if name == "source" && target == Target::Control {
return Err(error_at(
name.span(),
"`source` belongs to `external_object!`; a generated host alias records the file itself"
.to_owned(),
));
}
if name == lexicon::FACE_FIELD_COLLECTOR {
if collector.is_some() {
return Err(error_at(
name.span(),
"face field `collector` is given twice".to_owned(),
));
}
collector = Some(tokens[2].clone());
continue;
}
fields.push(Field {
key: name.to_string(),
span: name.span(),
value_span: tokens.get(1).map_or_else(|| name.span(), TokenTree::span),
tokens: tokens.into_iter().collect(),
});
}
let position = FACE_FIELD_ORDER
.iter()
.enumerate()
.map(|(index, key)| (*key, index))
.collect::<std::collections::BTreeMap<_, _>>();
let mut seen: Vec<&str> = Vec::with_capacity(fields.len());
for field in &fields {
if !position.contains_key(field.key.as_str()) {
return Err(error_at(
field.span,
format!(
"unknown face field `{}`; expected one of: {}",
field.key,
FACE_FIELD_ORDER.join(", ")
),
));
}
if seen.contains(&field.key.as_str()) {
return Err(error_at(
field.span,
format!("face field `{}` is given twice", field.key),
));
}
seen.push(&field.key);
}
let Some(collector) = collector else {
return Err(error_at(
Span::call_site(),
"a face declaration must carry `collector:`".to_owned(),
));
};
fields.sort_by_key(|field| position[field.key.as_str()]);
let mut body = Tokens::new();
body.extend([
TokenTree::Ident(Ident::new(lexicon::FACE_FIELD_COLLECTOR, Span::call_site())),
punct(':', Spacing::Alone),
collector,
punct(',', Spacing::Alone),
]);
for field in &fields {
body.extend(field.tokens.clone());
body.extend([punct(',', Spacing::Alone)]);
}
if target == Target::Control && body.to_string() == original {
return Err(error_at(
Span::call_site(),
"these face fields are in the accepted order but the declaration still did not match; \
check each field's shape against the field list in the macro's documentation"
.to_owned(),
));
}
let mut output = mirror_item(&fields);
output.extend([
punct(':', Spacing::Joint),
punct(':', Spacing::Alone),
TokenTree::Ident(Ident::new(lexicon::RUN_METHOD_CRATE, Span::call_site())),
punct(':', Spacing::Joint),
punct(':', Spacing::Alone),
TokenTree::Ident(Ident::new(
match target {
Target::Control => "__nichlink_object",
Target::External => "__external_object",
},
Span::call_site(),
)),
punct('!', Spacing::Alone),
TokenTree::Group(Group::new(Delimiter::Brace, body)),
]);
Ok(output)
}
#[proc_macro]
pub fn face_fields_mirror(input: TokenStream) -> TokenStream {
match split_mirror_fields(Tokens::from(input)) {
Ok(fields) => mirror_item(&fields).into(),
Err(_) => Tokens::new().into(),
}
}