mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
#8539 Config preferences backend restructure
This commit is contained in:
parent
34fda66dfa
commit
8bfd4dc1e2
53 changed files with 1748 additions and 680 deletions
19
components/config_plugins/Cargo.toml
Normal file
19
components/config_plugins/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "servo_config_plugins"
|
||||
version = "0.0.1"
|
||||
authors = ["The Servo Project Developers"]
|
||||
license = "MPL-2.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "servo_config_plugins"
|
||||
proc-macro = true
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
syn = "0.15"
|
||||
quote = "0.6"
|
||||
proc-macro2 = "0.4"
|
||||
itertools = "0.8"
|
||||
|
219
components/config_plugins/lib.rs
Normal file
219
components/config_plugins/lib.rs
Normal file
|
@ -0,0 +1,219 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![feature(proc_macro_diagnostic)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use itertools::Itertools;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::*;
|
||||
use std::collections::{hash_map, HashMap};
|
||||
use std::{fmt::Write, iter};
|
||||
use syn::{
|
||||
parse::Result, parse_macro_input, spanned::Spanned, Attribute, Ident, Lit, LitStr, Meta,
|
||||
MetaList, MetaNameValue, NestedMeta, Path,
|
||||
};
|
||||
|
||||
mod parse;
|
||||
use parse::*;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn build_structs(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input: MacroInput = parse_macro_input!(tokens);
|
||||
let out = Build::new(&input)
|
||||
.build(&input.type_def)
|
||||
.unwrap_or_else(|e| {
|
||||
proc_macro::Diagnostic::spanned(
|
||||
e.span().unwrap(),
|
||||
proc_macro::Level::Error,
|
||||
format!("{}", e),
|
||||
)
|
||||
.emit();
|
||||
TokenStream::new()
|
||||
});
|
||||
out.into()
|
||||
}
|
||||
|
||||
struct Build {
|
||||
root_type_name: Ident,
|
||||
gen_accessors: Ident,
|
||||
accessor_type: Path,
|
||||
output: TokenStream,
|
||||
path_stack: Vec<Ident>,
|
||||
path_map: HashMap<String, Vec<Ident>>,
|
||||
}
|
||||
|
||||
impl Build {
|
||||
fn new(input: &MacroInput) -> Self {
|
||||
Build {
|
||||
root_type_name: input.type_def.type_name.clone(),
|
||||
gen_accessors: input.gen_accessors.clone(),
|
||||
accessor_type: input.accessor_type.clone(),
|
||||
output: TokenStream::new(),
|
||||
path_stack: Vec::new(),
|
||||
path_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build(mut self, type_def: &RootTypeDef) -> Result<TokenStream> {
|
||||
self.walk(&type_def.type_def)?;
|
||||
self.build_accessors();
|
||||
Ok(self.output)
|
||||
}
|
||||
|
||||
fn walk(&mut self, type_def: &NewTypeDef) -> Result<()> {
|
||||
self.define_pref_struct(type_def)?;
|
||||
|
||||
for field in type_def.fields.iter() {
|
||||
self.path_stack.push(field.name.clone());
|
||||
|
||||
if let FieldType::NewTypeDef(new_def) = &field.field_type {
|
||||
self.walk(&new_def)?;
|
||||
} else {
|
||||
let pref_name =
|
||||
self.pref_name(field, &self.path_stack[..self.path_stack.len() - 1]);
|
||||
if let hash_map::Entry::Vacant(slot) = self.path_map.entry(pref_name) {
|
||||
slot.insert(self.path_stack.clone());
|
||||
} else {
|
||||
return Err(err(&field.name, "duplicate preference name"));
|
||||
}
|
||||
}
|
||||
|
||||
self.path_stack.pop();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn define_pref_struct(&mut self, type_def: &NewTypeDef) -> Result<()> {
|
||||
let struct_name = self.path_to_name(self.path_stack.iter());
|
||||
let field_defs = type_def
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| self.field_to_tokens(field, &self.path_stack))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
self.output.extend(quote! {
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
pub struct #struct_name {
|
||||
#(#field_defs), *
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_accessors(&mut self) {
|
||||
let accessor_type = &self.accessor_type;
|
||||
let values = self.path_map.iter().map(|(key, path)| {
|
||||
quote! {
|
||||
map.insert(String::from(#key),
|
||||
#accessor_type::new(
|
||||
|prefs| prefs #(.#path)*.clone().into(),
|
||||
|prefs, value| prefs #(.#path)* = value.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let gen_accessors = &self.gen_accessors;
|
||||
let num_prefs = self.path_map.len();
|
||||
|
||||
self.output.extend(quote! {
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref #gen_accessors: std::collections::HashMap<String, #accessor_type> = {
|
||||
let mut map = std::collections::HashMap::with_capacity(#num_prefs);
|
||||
#(#values)*
|
||||
map
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn pref_name(&self, field: &Field, path_stack: &[Ident]) -> String {
|
||||
field
|
||||
.get_field_name_mapping()
|
||||
.map(|pref_attr| pref_attr.value())
|
||||
.unwrap_or_else(|| {
|
||||
path_stack
|
||||
.iter()
|
||||
.chain(iter::once(&field.name))
|
||||
.map(Ident::to_string)
|
||||
.intersperse(String::from("."))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn field_to_tokens(&self, field: &Field, path_stack: &[Ident]) -> Result<TokenStream> {
|
||||
let name = &field.name;
|
||||
Ok(match &field.field_type {
|
||||
FieldType::NewTypeDef(_) => {
|
||||
let type_name = self.path_to_name(path_stack.iter().chain(iter::once(name)));
|
||||
quote! {
|
||||
#[serde(flatten)]
|
||||
pub #name: #type_name
|
||||
}
|
||||
},
|
||||
FieldType::Existing(type_name) => {
|
||||
let pref_name = self.pref_name(field, &path_stack);
|
||||
let attributes = field.get_attributes(&pref_name);
|
||||
quote! {
|
||||
#attributes
|
||||
pub #name: #type_name
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn path_to_name<'p, P: Iterator<Item = &'p Ident> + 'p>(&self, path: P) -> Ident {
|
||||
let mut name = format!("{}", self.root_type_name);
|
||||
for part in path {
|
||||
name.write_fmt(format_args!("__{}", part)).unwrap();
|
||||
}
|
||||
Ident::new(&name, Span::call_site())
|
||||
}
|
||||
}
|
||||
|
||||
impl Field {
|
||||
fn get_attributes(&self, pref_name: &str) -> TokenStream {
|
||||
let mut tokens = TokenStream::new();
|
||||
for attr in self
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|attr| attr_to_pref_name(attr).is_none())
|
||||
{
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
tokens.extend(quote! {
|
||||
#[serde(rename = #pref_name)]
|
||||
});
|
||||
tokens
|
||||
}
|
||||
|
||||
fn get_field_name_mapping(&self) -> Option<LitStr> {
|
||||
self.attributes.iter().filter_map(attr_to_pref_name).next()
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_to_pref_name(attr: &Attribute) -> Option<LitStr> {
|
||||
attr.parse_meta().ok().and_then(|meta| {
|
||||
if let Meta::List(MetaList { ident, nested, .. }) = meta {
|
||||
if ident.to_string() == "serde" {
|
||||
if let Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
ref ident,
|
||||
lit: Lit::Str(val),
|
||||
..
|
||||
}))) = nested.iter().next()
|
||||
{
|
||||
if ident.to_string() == "rename" {
|
||||
return Some(val.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn err<S: Spanned>(s: S, msg: &str) -> syn::Error {
|
||||
syn::Error::new(s.span(), msg)
|
||||
}
|
149
components/config_plugins/parse.rs
Normal file
149
components/config_plugins/parse.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
use syn::{braced, punctuated::Punctuated, token, Attribute, Ident, Path, Token, Type};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
mod kw {
|
||||
syn::custom_keyword!(accessor_type);
|
||||
syn::custom_keyword!(gen_accessors);
|
||||
syn::custom_keyword!(gen_types);
|
||||
}
|
||||
|
||||
pub struct MacroInput {
|
||||
pub type_def: RootTypeDef,
|
||||
pub gen_accessors: Ident,
|
||||
pub accessor_type: Path,
|
||||
}
|
||||
|
||||
enum MacroArg {
|
||||
GenAccessors(ArgInner<kw::gen_accessors, Ident>),
|
||||
AccessorType(ArgInner<kw::accessor_type, Path>),
|
||||
Types(ArgInner<kw::gen_types, RootTypeDef>),
|
||||
}
|
||||
|
||||
struct ArgInner<K, V> {
|
||||
_field_kw: K,
|
||||
_equals: Token![=],
|
||||
value: V,
|
||||
}
|
||||
|
||||
pub struct Field {
|
||||
pub attributes: Vec<Attribute>,
|
||||
pub name: Ident,
|
||||
_colon: Token![:],
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
pub enum FieldType {
|
||||
Existing(Type),
|
||||
NewTypeDef(NewTypeDef),
|
||||
}
|
||||
|
||||
pub struct NewTypeDef {
|
||||
_braces: token::Brace,
|
||||
pub fields: Punctuated<Field, Token![, ]>,
|
||||
}
|
||||
|
||||
pub struct RootTypeDef {
|
||||
pub type_name: Ident,
|
||||
pub type_def: NewTypeDef,
|
||||
}
|
||||
|
||||
impl Parse for MacroInput {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let fields: Punctuated<MacroArg, Token![, ]> = input.parse_terminated(MacroArg::parse)?;
|
||||
let mut gen_accessors = None;
|
||||
let mut type_def = None;
|
||||
let mut accessor_type = None;
|
||||
for arg in fields.into_iter() {
|
||||
match arg {
|
||||
MacroArg::GenAccessors(ArgInner { value, .. }) => gen_accessors = Some(value),
|
||||
MacroArg::AccessorType(ArgInner { value, .. }) => accessor_type = Some(value),
|
||||
MacroArg::Types(ArgInner { value, .. }) => type_def = Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_attr(att_name: &str) -> syn::Error {
|
||||
syn::Error::new(
|
||||
Span::call_site(),
|
||||
format!("Expected `{}` attribute", att_name),
|
||||
)
|
||||
}
|
||||
|
||||
Ok(MacroInput {
|
||||
type_def: type_def.ok_or_else(|| missing_attr("gen_types"))?,
|
||||
gen_accessors: gen_accessors.ok_or_else(|| missing_attr("gen_accessors"))?,
|
||||
accessor_type: accessor_type.ok_or_else(|| missing_attr("accessor_type"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MacroArg {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(kw::gen_types) {
|
||||
Ok(MacroArg::Types(input.parse()?))
|
||||
} else if lookahead.peek(kw::gen_accessors) {
|
||||
Ok(MacroArg::GenAccessors(input.parse()?))
|
||||
} else if lookahead.peek(kw::accessor_type) {
|
||||
Ok(MacroArg::AccessorType(input.parse()?))
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Parse, V: Parse> Parse for ArgInner<K, V> {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(ArgInner {
|
||||
_field_kw: input.parse()?,
|
||||
_equals: input.parse()?,
|
||||
value: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Field {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(Field {
|
||||
attributes: input.call(Attribute::parse_outer)?,
|
||||
name: input.parse()?,
|
||||
_colon: input.parse()?,
|
||||
field_type: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for RootTypeDef {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(RootTypeDef {
|
||||
type_name: input.parse()?,
|
||||
type_def: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for NewTypeDef {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let content;
|
||||
#[allow(clippy::eval_order_dependence)]
|
||||
Ok(NewTypeDef {
|
||||
_braces: braced!(content in input),
|
||||
fields: content.parse_terminated(Field::parse)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for FieldType {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
if input.peek(token::Brace) {
|
||||
Ok(FieldType::NewTypeDef(input.parse()?))
|
||||
} else {
|
||||
Ok(FieldType::Existing(input.parse()?))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue