use proc_macro2::{
Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream as Tokens, TokenTree,
};
use nichlink::lexicon;
use nichlink::registry_core::declaration::FACE_FIELD_ORDER;
#[derive(Clone, Copy, PartialEq, Eq)]
enum MirrorField {
Value,
Type,
Replace,
}
fn mirror_field(key: &str) -> MirrorField {
match key {
"kind" | "preset" | "parts" | "flow_provider" => MirrorField::Type,
"source"
| "params"
| "stable_name"
| "needs_registry"
| "parent"
| "registry_rule_path"
| "registry_rule"
| "admission"
| "flow"
| lexicon::FACE_FIELD_PLUGIN => MirrorField::Value,
_ => MirrorField::Replace,
}
}
pub(crate) struct Field {
pub(crate) key: String,
pub(crate) span: Span,
pub(crate) value_span: Span,
pub(crate) tokens: Tokens,
}
pub(crate) fn mirror_item(fields: &[Field]) -> Tokens {
let mut arguments = Tokens::new();
let mut assignments = Tokens::new();
for (index, key) in FACE_FIELD_ORDER.iter().enumerate() {
if index > 0 {
arguments.extend([punct(',', Spacing::Alone)]);
}
let field = fields.iter().find(|field| field.key == *key);
let written = field.filter(|field| field.tokens.clone().into_iter().count() > 2);
match (mirror_field(key), written) {
(MirrorField::Value, Some(field)) => {
arguments.extend([ident("_")]);
assignments.extend(field.tokens.clone());
}
(MirrorField::Type, Some(field)) => {
arguments.extend(field.tokens.clone().into_iter().skip(2));
assignments.extend(loop_value(field));
}
(_, field) => {
arguments.extend([ident("__Any")]);
if let Some(field) = field {
assignments.extend(loop_value(field));
}
}
}
if field.is_some() {
assignments.extend([punct(',', Spacing::Alone)]);
}
}
let mut literal = assignments;
literal.extend([
punct('.', Spacing::Joint),
punct('.', Spacing::Alone),
ident("loop"),
group(Delimiter::Brace, Tokens::new()),
]);
let literal = group(Delimiter::Brace, literal);
let face_fields = format!("::{}::macros::FaceFields", lexicon::RUN_METHOD_CRATE);
let mut body = Tokens::new();
body.extend([ident("let"), ident("_"), punct(':', Spacing::Alone)]);
body.extend(tokens_of(&face_fields));
body.extend([punct('<', Spacing::Alone)]);
body.extend(arguments);
body.extend([punct('>', Spacing::Alone), punct('=', Spacing::Alone)]);
body.extend(tokens_of(&face_fields));
body.extend([literal, punct(';', Spacing::Alone)]);
let body = group(Delimiter::Brace, body);
let mut function = tokens_of("#[allow(unreachable_code, dead_code)]");
function.extend([
ident("fn"),
ident("__face_fields"),
punct('<', Spacing::Alone),
]);
function.extend([ident("__Any"), punct('>', Spacing::Alone)]);
function.extend([group(Delimiter::Parenthesis, Tokens::new()), body]);
let function = group(Delimiter::Brace, function);
let mut item = tokens_of("#[cfg(rust_analyzer)]");
item.extend([ident("const"), ident("_"), punct(':', Spacing::Alone)]);
item.extend([group(Delimiter::Parenthesis, Tokens::new())]);
item.extend([
punct('=', Spacing::Alone),
function,
punct(';', Spacing::Alone),
]);
item
}
fn loop_value(field: &Field) -> Tokens {
let mut tokens = field.tokens.clone().into_iter().take(1).collect::<Tokens>();
tokens.extend([punct(':', Spacing::Alone)]);
tokens.extend([
TokenTree::Ident(Ident::new("loop", field.value_span)),
group(Delimiter::Brace, Tokens::new()),
]);
tokens
}
fn ident(name: &str) -> TokenTree {
TokenTree::Ident(Ident::new(name, Span::call_site()))
}
fn group(delimiter: Delimiter, tokens: Tokens) -> TokenTree {
TokenTree::Group(Group::new(delimiter, tokens))
}
fn tokens_of(source: &str) -> Tokens {
source
.parse::<Tokens>()
.unwrap_or_else(|error| panic!("the mirror's own tokens are valid: {error} in {source:?}"))
}
pub(crate) fn punct(character: char, spacing: Spacing) -> TokenTree {
TokenTree::Punct(Punct::new(character, spacing))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::front_end::split_mirror_fields;
use crate::normalise;
fn compact(source: &str) -> String {
source
.chars()
.filter(|character| !character.is_whitespace())
.collect()
}
fn mirror(source: &str) -> String {
mirror_item(&split_mirror_fields(source.parse().expect("tokens")).expect("splits"))
.to_string()
}
fn front_end(source: &str) -> String {
normalise(source.parse().expect("tokens"))
.expect("the declaration normalises")
.to_string()
}
fn arguments(output: &str) -> Vec<&str> {
let start = output.find("FaceFields<").expect("a FaceFields literal") + "FaceFields<".len();
let rest = &output[start..];
let end = rest.find('>').expect("a closed argument list");
rest[..end].split(',').collect()
}
#[test]
fn every_mirror_is_valid_rust() {
for source in [
"kind: Tool,",
"kind: Tool; parent: crate::a::NODE_ID;",
"source: \"a/a.rs\", kind: K, name: { zh: \"控件\", en: \"Widget\" }, \
requires: [\"cap\" => \"Provider\"],",
] {
let output = mirror(source);
syn::parse_file(&output).unwrap_or_else(|error| {
panic!("the mirror for `{source}` parses: {error}\n{output}")
});
}
let output =
front_end("@control collector: development, kind: Tool; parent: crate::a::NODE_ID;");
syn::parse_file(&output).unwrap_or_else(|error| {
panic!("the front end's output parses: {error}\nRAW: {output}")
});
assert!(
compact(&output).contains(&format!(
"::{}::__nichlink_object!",
lexicon::RUN_METHOD_CRATE
)),
"{output}"
);
}
#[test]
fn the_mirror_translates_each_field_shape() {
let output = compact(&mirror(
"kind: Tool, preset: NoPreset, name: { zh: \"控件\", en: \"Widget\" }, \
parent: crate::a::NODE_ID, exports: [], provides: [\"x\"],",
));
assert!(output.contains("kind:loop{}"), "{output}");
assert!(output.contains("preset:loop{}"), "{output}");
assert!(output.contains("parent:crate::a::NODE_ID"), "{output}");
for replaced in ["name:loop{}", "exports:loop{}", "provides:loop{}"] {
assert!(output.contains(replaced), "{replaced} missing in {output}");
}
assert!(output.contains("..loop{}"), "{output}");
assert!(output.contains("fn__face_fields<__Any>()"), "{output}");
let arguments = arguments(&output);
assert_eq!(arguments.len(), FACE_FIELD_ORDER.len(), "{arguments:?}");
assert_eq!(arguments[0], "__Any", "source is absent: {arguments:?}");
assert_eq!(arguments[1], "Tool", "kind names a type: {arguments:?}");
assert_eq!(
arguments[2], "NoPreset",
"preset names a type: {arguments:?}"
);
assert_eq!(arguments[4], "__Any", "name is replaced: {arguments:?}");
assert_eq!(arguments[6], "__Any", "exports is replaced: {arguments:?}");
assert_eq!(
arguments[9], "_",
"parent is inferred from its value: {arguments:?}"
);
}
#[test]
fn an_empty_declaration_pins_every_parameter() {
let output = mirror("");
syn::parse_file(&output)
.unwrap_or_else(|error| panic!("the empty mirror parses: {error}\nRAW: {output}"));
let output = compact(&output);
assert!(output.contains("fn__face_fields<__Any>()"), "{output}");
assert!(
arguments(&output).iter().all(|item| *item == "__Any"),
"{output}"
);
}
}