//! Template syntax //! //! This module contains: //! //! * The types representing a parsed template. //! * The `parse` methods. use super::framework::*; pub use SubstDetails as SD; pub use TemplateElement as TE; #[derive(Debug)] pub struct Template { /// `TD::Subst` is known not to contain `SD::when` pub elements: Vec>, } /// `ARG` as per /// "Named and positional template arguments" /// in the reference. /// /// Parses from one of /// * identifier /// * literal /// * `$EXPN` /// * `${EXPN..}` /// * `{ TEMPLATE }` #[derive(Debug)] pub struct Argument(pub Template); #[derive(Debug)] pub struct TemplateWithWhens { pub elements: Vec>, } #[derive(Debug)] pub enum TemplateElement { Ident(Ident), LitStr(syn::LitStr), Literal(syn::Lit, O::NotInPaste), Punct(Punct, O::NotInPaste), Group { /// Sadly Group's constructors let us only set *both* delimiters delim_span: Span, delimiter: Delimiter, template: Template, not_in_paste: O::NotInPaste, }, /// Might contain `SD::when` Subst(Subst), Repeat(RepeatedTemplate), } #[derive(Debug)] pub struct RepeatedTemplate { pub template: Template, #[allow(clippy::vec_box)] pub whens: Vec>>, pub over: RepeatOver, } #[derive(Debug)] pub struct Subst { pub kw_span: Span, /// Might contain `SD::when` pub sd: SubstDetails, } /// A condition, as found in `if`. // // Giving this a separate name is nicer but also helps avoid using // `Subst` directly in template syntax tree nodes, which is best // avoided because Subst might contain `${when }` inappropriately. pub type Condition = Subst; /// Enum representing nature and payload of a substitution /// /// This is a single enum, for all of the different lexical contexts /// (principal expansion, identifier pasting, boolean predicates) /// because this: /// * Unifies the parsing code, ensuring that all these /// constructs are parsed the same everywhere /// (unless we deviate deliberately). /// * Mostly unifies the expansion and loop/conditional walking code. /// * Avoids the need to recapitulate the keywords in multiple /// enums, or contexts with inner nested enums, or something. /// /// The enum is generic over the lexical context [`SubstParseContext`]. /// This allows the variants that are inapplicable in a particular /// lexical context to be made uninhabited: /// that ensures that detection of such errors occurs during template parsing. #[allow(non_camel_case_types)] // clearer to use the exact ident #[derive(Debug)] pub enum SubstDetails { // variables tname(O::NotInBool), ttype(O::NotInBool), tdeftype(O::NotInBool), vname(O::NotInBool), fname(O::NotInBool), ftype(O::NotInBool), fpatname(O::NotInBool), Vis(SubstVis, O::NotInPaste), // tvis, fvis tdefkwd(O::NotInBool), // attributes Xmeta(meta::SubstMeta), tattrs(RawAttr, O::NotInPaste, O::NotInBool), vattrs(RawAttr, O::NotInPaste, O::NotInBool), fattrs(RawAttr, O::NotInPaste, O::NotInBool), // generics tgens(O::NotInPaste), tdefgens(O::NotInPaste, O::NotInBool), tgnames(O::NotInPaste, O::NotInBool), twheres(O::NotInPaste, O::NotInBool), vpat(SubstVPat, O::NotInPaste, O::NotInBool), vtype(SubstVType, O::NotInPaste, O::NotInBool), tdefvariants(Template, O::NotInPaste, O::NotInBool), fdefine(Option, O::NotInPaste, O::NotInBool), vdefbody( Argument, Template, O::NotInPaste, O::NotInBool, ), // expansion manipulation paste(Template, O::NotInBool), ChangeCase(Template, paste::ChangeCase, O::NotInBool), // special when(Box, O::NotInBool), define(Definition, O::NotInBool), defcond(Definition, O::NotInBool), UserDefined(DefinitionName), // expressions False(O::BoolOnly), True(O::BoolOnly), not(Box, O::BoolOnly), any(Punctuated, O::BoolOnly), all(Punctuated, O::BoolOnly), is_struct(O::BoolOnly), is_enum(O::BoolOnly), is_union(O::BoolOnly), v_is_unit(O::BoolOnly), v_is_tuple(O::BoolOnly), v_is_named(O::BoolOnly), is_empty(O::BoolOnly, Template), approx_equal(O::BoolOnly, [Argument; 2]), // Explicit iteration For(RepeatedTemplate, O::NotInBool), // Conditional substitution. If(SubstIf, O::NotInBool), select1(SubstIf, O::NotInBool), ignore(Template, O::NotInBool), error(ExplicitError, O::NotInBool), dbg(DbgDumpRequest), dbg_all_keywords(O::NotInBool), Crate(O::NotInPaste, O::NotInBool), } #[derive(Debug)] pub struct Definition { pub name: DefinitionName, pub body_span: Span, pub body: B, } #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct DefinitionName(syn::Ident); #[derive(Debug)] pub enum DefinitionBody { Normal(Argument), Paste(Argument), } pub type DefCondBody = Box; #[derive(Debug)] pub struct ExplicitError { pub message: syn::LitStr, } #[derive(Debug)] pub struct SubstIf { /// A series of test/result pairs. /// /// The test that gives "true" /// short-circuits the rest. pub tests: Vec<(Condition, Template)>, /// A final element to expand if all tests fail. pub otherwise: Option>>, pub kw_span: Span, } /// Whether this is `${tvis}` or `${fvis}` #[derive(Debug)] pub enum SubstVis { T, F, FD, } #[derive(Debug)] pub struct SubstVType { pub self_: Option, pub vname: Option, } #[derive(Debug)] pub struct SubstVPat { pub vtype: SubstVType, pub fprefix: Option>, } #[derive(Debug, Clone)] pub enum RawAttr { Default, Include { entries: Punctuated, }, Exclude { exclusions: Punctuated, }, } #[derive(Debug, Clone)] pub struct RawAttrEntry { pub path: syn::Path, } #[derive(Debug)] pub struct DbgDumpRequest { pub note: Option, pub content_parsed: Box, // TODO // We want to print the unexpanded/unevaluated template text, // as part of the debugging output. // // However, all our Template TokenStreams have been $-escaped, // so contain `$orig_dollar`. We could fix this by unescaping // the whole template on input, but it is better for perf // to do this only when required by an actual dbg expansion. // // To resolve this TODO: // - implement a function to de-escape templates // - de-escape the template before making it into a String // - change the type of this variable to contain the String // - resolve the compile errors at the call sites // by actually printing the unexpanded content // Or: // - impl Display for Tempplate // (this is probably much more work and very intrusive) // // pub content_string: String, pub content_string: (), } /// Error returned by `DefinitionName::try_from(syn::Ident)` pub struct InvalidDefinitionName; impl ToTokens for DefinitionName { fn to_tokens(&self, out: &mut TokenStream) { self.0.to_tokens(out) } } impl TryFrom for DefinitionName { type Error = InvalidDefinitionName; fn try_from(ident: syn::Ident) -> Result { // We allow any identifier except those which start with a // lowercase letter or `_`. Lowercase letters, and `_`, are // reserved for builtin functionality. We don't restrict the // exclusion to *ascii* lowercase letters, even though we // don't intend any non-ascii keywords, because that might be // confusing. proc_macros receive identifiers in NFC, so // we don't need to worry about whether this rule might depend // on the input representation. let s = ident.to_string(); let c = s.chars().next().expect("identifer was empty string!"); if c.is_lowercase() { Err(InvalidDefinitionName) } else { Ok(DefinitionName(ident)) } } } impl Display for DefinitionName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) } } impl Parse for DefinitionName { fn parse(input: ParseStream) -> syn::Result { let ident = input.call(Ident::parse_any)?; let span = ident.span(); Ok(ident.try_into().map_err(|InvalidDefinitionName| { span.error( "invalid name for definition - may not start with lowercase", ) })?) } } impl Parse for Definition { fn parse(input: ParseStream) -> syn::Result { let name = input.parse()?; let body_span = input.span(); let mut body: Argument<_> = input.parse()?; // Is it precisely an invocation of ${paste } or $< > ? let body = match (|| { if body.elements.len() != 1 { return None; } let Template { elements } = &mut body.0; // This is tedious. We really want to move match on a vec. match elements.pop().expect("just checked length") { TE::Subst(Subst { kw_span, sd: SD::paste(items, not_in_bool), }) => Some(Argument(Template { elements: vec![TE::Subst(Subst { kw_span, sd: SD::paste(items, not_in_bool), })], })), other => { // Oops, put it back elements.push(other); None } } })() { Some(paste) => DefinitionBody::Paste(paste), None => DefinitionBody::Normal(body), }; Ok(Definition { name, body_span, body, }) } } impl Parse for Definition { fn parse(input: ParseStream) -> syn::Result { let name = input.parse()?; let body_span = input.span(); let body = Box::new(input.parse()?); Ok(Definition { name, body_span, body, }) } } impl Spanned for Subst { fn span(&self) -> Span { self.kw_span } } impl Parse for Template { fn parse(input: ParseStream) -> syn::Result { Template::parse_special(input, &mut Default::default()) } } impl Template { fn parse_special( input: ParseStream, special: &mut O::SpecialParseContext, ) -> syn::Result { TemplateWithWhens::parse_special(input, special)?.try_into() } } impl TemplateWithWhens { fn parse_special( input: ParseStream, mut special: &mut O::SpecialParseContext, ) -> syn::Result { // eprintln!("@@@@@@@@@@ PARSE {}", &input); let mut good = vec![]; let mut errors = ErrorAccumulator::default(); while !input.is_empty() { let special = &mut special; match errors.handle_in(|| { O::special_before_element_hook(special, input) }) { Some(None) => {}, None | // error! quit parsing Some(Some(SpecialInstructions::EndOfTemplate)) => break, } errors.handle_in(|| { let elem = input.parse()?; good.push(elem); Ok(special) }); } errors.finish_with(TemplateWithWhens { elements: good }) } } impl Parse for Argument { fn parse(input: ParseStream) -> syn::Result { let la = input.lookahead1(); if la.peek(token::Brace) { let inner; let _brace = braced!(inner in input); Template::parse(&inner) } else if la.peek(Ident::peek_any) || la.peek(Token![$]) || la.peek(syn::Lit) { let element = input.parse()?; TemplateWithWhens { elements: vec![element], } .try_into() } else { Err(la.error()) } .map(Argument) } } impl Deref for Argument { type Target = Template; fn deref(&self) -> &Template { &self.0 } } /// Proof token that [`deescape_orig_dollar`] was called /// /// Demanded by `Subst::parse_after_dollar`. /// /// Do not construct this yourself. /// Only `deescape_orig_dollar` is allowed to do that. pub enum OrigDollarDeescaped { /// We didn't find any `orig_dollar` NotFound, /// We *did* find `orig_dollar`; if we now see *another* `$` /// things get even stranger. Found, } /// Skip over any `orig_dollar` /// /// Call this after seeing a `$`. /// The `orig_dollar` (hopefully) came from /// [`definition::escape_dollars`](escape_dollars). pub fn deescape_orig_dollar( input: ParseStream, ) -> syn::Result { input.step(|cursor| { let (found, rest) = (|| { let (ident, rest) = cursor.ident()?; (ident == "orig_dollar").then(|| ())?; Some((OrigDollarDeescaped::Found, rest)) })() .unwrap_or((OrigDollarDeescaped::NotFound, *cursor)); Ok((found, rest)) }) } impl Parse for TemplateElement { fn parse(input: ParseStream) -> syn::Result { let input_span = input.span(); let not_in_paste = || O::not_in_paste(&input_span); let backtracked = input.fork(); Ok(match input.parse()? { TT::Group(group) => { let delim_span = group.span_open(); let delimiter = group.delimiter(); let t_parser = |input: ParseStream| Template::parse(input); let template = t_parser.parse2(group.stream())?; TE::Group { not_in_paste: not_in_paste()?, delim_span, delimiter, template, } } TT::Ident(tt) => TE::Ident(tt), tt @ TT::Literal(..) => match syn::parse2(tt.into())? { syn::Lit::Str(s) => TE::LitStr(s), other => TE::Literal(other, not_in_paste()?), }, TT::Punct(tok) if tok.as_char() == '#' => { if let Ok(attr) = syn::Attribute::parse_inner(&backtracked) { if let Some(attr) = attr.first() { return Err(attr.error( "inner attributes are reserved syntax, anywhere in derive-deftly templates" )); } } TE::Punct(tok, not_in_paste()?) } TT::Punct(tok) if tok.as_char() != '$' => { TE::Punct(tok, not_in_paste()?) } TT::Punct(_dollar) => { let deescaped = deescape_orig_dollar(input)?; let la = input.lookahead1(); if la.peek(Token![$]) { // $$ let dollar: Punct = input.parse()?; match deescaped { OrigDollarDeescaped::NotFound => {}, OrigDollarDeescaped::Found => { match deescape_orig_dollar(input)? { OrigDollarDeescaped::Found => {}, OrigDollarDeescaped::NotFound => { return Err(dollar.error( "found `$orig_dollar $` not followed by another orig_dollar!" )) }, } }, } TE::Punct(dollar, not_in_paste()?) } else if la.peek(token::Paren) { RepeatedTemplate::parse_in_parens(input)? } else { TE::Subst(Subst::parse_after_dollar(la, input, deescaped)?) } } }) } } /// Parse `Self` using named subkeyword arguments within the `${...}` /// /// Provides [`parse()`](ParseUsingSubkeywords::parse) in terms of: /// * An implementation of /// [`new_default()`](ParseUsingSubkeywords::new_default) /// (which must generally be manually provided) /// * An implementation of [`ParseOneSubkeyword`], /// usually made with `impl_parse_subkeywords!`. pub trait ParseUsingSubkeywords: Sized + ParseOneSubkeyword { /// Parse the body of `Self`, processing names subkeyword arguments /// /// `kw_span` is for the top-level keyword introducing `Self`. fn parse(input: ParseStream, kw_span: Span) -> syn::Result { let mut out = Self::new_default(kw_span)?; while !input.is_empty() { let subkw: IdentAny = input.parse()?; let _: Token![=] = input.parse()?; out.process_one_keyword(&subkw, input).unwrap_or_else(|| { Err(subkw.error("unknown $vpat/$vconstr argument sub-keyword")) })?; } Ok(out) } /// Make a new `Self` with default values for all parameters /// /// This is used when the keyword is invoked without being enclosed /// in `${...}`, and as the starting point when it *is* enclosed. /// /// `kw_span` is to be used if to construct any `SubstParseContext` /// lexical context tokens (eg, `NotInPaste`) in `Self`. fn new_default(kw_span: Span) -> syn::Result; } pub trait ParseOneSubkeyword: Sized { /// Process the value for a keyword `subkw`. /// /// `input` is inside the `{ }`, just after the `=`. /// /// Generally implemented by `impl_parse_subkeywords!`, /// which generates code involving a a call to [`subkw_parse_store`]. fn process_one_keyword( &mut self, kw: &syn::Ident, input: ParseStream, ) -> Option>; } /// Helper for `impl_parse_subkeywords!`; parse and store subkw argument /// /// Parses and stores a template element argument to a subkeyword, /// using [`Argument::parse`]. /// /// Detects repeated specification of the same keyword, as an error. /// /// `KO` is the lexical parsing context, and determines what /// kind of values the template author can supply. fn subkw_parse_store( subkw: &syn::Ident, input: ParseStream, dest: &mut Option>, ) -> syn::Result<()> where KO: SubstParseContext, { if let Some(_) = &dest { // TODO preserve previous keyword so we can report it? return Err( subkw.error("same argument sub-keyword specified more than once") ); } *dest = Some(input.parse()?); Ok(()) } /// Implements `ParseOneSubkeyword` for the use of `ParseUsingSubkeywords` /// /// Input syntax is `TYPE: (SUBKEYWORD-SPEC), (SUBKEYWORD-SPEC), ...` /// where each SUBKEYWORD-SPEC is one of: /// * `(field)`: recognises `"field"` and stores in `self.field`. /// * `("subkw": .field)`: recognises `"subkw"` /// * `(..substruct)`: calls `self.substruct.process_one_keyword`, /// thereby incorporating the sub-structure's subkeywords /// /// You can write `TYPE: ...` /// which results in /// `impl ... for TYPE`. macro_rules! impl_parse_one_subkeyword { { $ty:ident $( < $O:ident > )?: $( ( $($spec:tt)+ ) ),* $(,)? } => { impl $(<$O: SubstParseContext>)? ParseOneSubkeyword for $ty $(<$O>)? { fn process_one_keyword(&mut self, got: &syn::Ident, ps: ParseStream) -> Option> { $( impl_parse_one_subkeyword!{ @ (self, got, ps) @ $($spec)+ } )* None } } // Internal input syntax @ (self,got,ps) @ SUBKEYWORD-SPEC // (we must pass (self,got,ps) explicitly for annoying hygiene reasons) }; { @ $bind:tt @ $exp:ident } => { impl_parse_one_subkeyword! { @@ $bind @ stringify!($exp), . $exp } }; { @ $bind:tt @ $exp:literal: . $($field:tt)+ } => { impl_parse_one_subkeyword! { @@ $bind @ $exp, . $($field)+ } }; { @ ($self:expr, $got:expr, $ps:expr) @ .. $($substruct:tt)+ } => { if let Some(r) = $self.$($substruct)+.process_one_keyword($got, $ps) { return Some(r); } // Internal input syntax @@ (self,got,ps) @ SUBKW, .FIELD-ACCESSORS }; { @@ ($self:expr, $got:expr, $ps:expr) @ $exp:expr, .$($field:tt)+ } => { if $got == $exp { return Some(subkw_parse_store($got, $ps, &mut $self.$($field)+)); } } } impl_parse_one_subkeyword! { SubstVType: ("self": .self_), (vname), } impl_parse_one_subkeyword! { SubstVPat: (..vtype), (fprefix), } impl ParseUsingSubkeywords for SubstVType { fn new_default(_tspan: Span) -> syn::Result { Ok(SubstVType { self_: None, vname: None, }) } } impl ParseUsingSubkeywords for SubstVPat { fn new_default(tspan: Span) -> syn::Result { Ok(SubstVPat { vtype: SubstVType::new_default(tspan)?, fprefix: None, }) } } impl Subst { /// Parses everything including a `$` (which we insist on) #[allow(dead_code)] // This was once used for ${paste } fn parse_entire(input: ParseStream) -> syn::Result { let _dollar: Token![$] = input.parse()?; let deescaped = deescape_orig_dollar(input)?; let la = input.lookahead1(); Self::parse_after_dollar(la, input, deescaped) } /// Parses everything after the `$`, possibly including a pair of `{ }` /// /// You must have called [`deescape_orig_dollar`], /// and handled the `$$` case. fn parse_after_dollar( la: Lookahead1, input: ParseStream, _deescaped: OrigDollarDeescaped, ) -> syn::Result { if la.peek(token::Brace) { let exp; struct Only(Subst); impl Parse for Only { fn parse(input: ParseStream) -> syn::Result { let subst = input.parse()?; let unwanted: Option = input.parse()?; if let Some(unwanted) = unwanted { return Err(unwanted.error( "unexpected arguments to expansion keyword", )); } Ok(Only(subst)) } } let _brace = braced!(exp in input); let exp = exp.parse()?; let Only(exp) = exp; Ok(exp) } else if la.peek(syn::Ident::peek_any) { let exp: TokenTree = input.parse()?; // get it as TT let exp = syn::parse2(exp.to_token_stream())?; Ok(exp) } else if la.peek(Token![<]) { let angle: Token![<] = input.parse()?; let state = paste::AngleBrackets::default(); let mut special = Some(state); let template = Template::parse_special(input, &mut special)?; let state = special.unwrap(); state.finish(angle.span())?; Ok(Subst { kw_span: angle.span(), sd: SD::paste(template, O::not_in_bool(&angle)?), }) } else { return Err(la.error()); } } } /// Parses only the content (ie, after the `$` and inside any `{ }`) impl Parse for Subst { fn parse<'i>(input: ParseStream<'i>) -> syn::Result { let kw: IdentAny = input.parse()?; let from_sd = |sd| { Ok(Subst { sd, kw_span: kw.span(), }) }; // See `tests/pub-export/pub-b/pub-b.rs` #[cfg(feature = "bizarre")] let kw = { let s = kw.to_string(); let s = s .strip_suffix("_bizarre") .ok_or_else(|| kw.error("bizarre mode but not _bizarre"))?; IdentAny(syn::Ident::new(s, kw.span())) }; // keyword!{ KEYWORD [ {BLOCK WITH BINDINGS} ] [ CONSTRUCTOR-ARGS ] } // expands to something like: // // if supplied_keyword = "KEYWORD" { // return Ok(Subst { // sd: SubstDetails::KEYWORD CONSTRUCTOR-ARGS, // .. // }) // } // // KEYWORD can be "KEYWORD_STRING": CONSTRUCTOR, // in case the enum variant name is not precisely the keyword. // // See `keyword_general!` in utils.rs for full details. macro_rules! keyword { { $($args:tt)* } => { keyword_general! { kw from_sd SD; $($args)* } } } let not_in_paste = || O::not_in_paste(&kw); let not_in_bool = || O::not_in_bool(&kw); let bool_only = || O::bool_only(&kw); let parse_if = |input| SubstIf::parse(input, kw.span()); let in_parens = |input: ParseStream<'i>| { let inner; let _paren = parenthesized!(inner in input); Ok(inner) }; let parse_def_body = |input: ParseStream<'i>, m| { if input.is_empty() { return Err(kw.error(m)); } Template::parse(input) }; let parse_meta = |input, scope| meta::SubstMeta::parse(input, kw.span(), scope); keyword! { tname(not_in_bool()?) } keyword! { ttype(not_in_bool()?) } keyword! { tdeftype(not_in_bool()?) } keyword! { vname(not_in_bool()?) } keyword! { fname(not_in_bool()?) } keyword! { ftype(not_in_bool()?) } keyword! { fpatname(not_in_bool()?) } keyword! { tdefkwd(not_in_bool()?) } keyword! { "tvis": Vis(SubstVis::T, not_in_paste()?) } keyword! { "fvis": Vis(SubstVis::F, not_in_paste()?) } keyword! { "fdefvis": Vis(SubstVis::FD, not_in_paste()?) } keyword! { is_struct(bool_only()?) } keyword! { is_enum(bool_only()?) } keyword! { is_union(bool_only()?) } keyword! { v_is_unit(bool_only()?) } keyword! { v_is_tuple(bool_only()?) } keyword! { v_is_named(bool_only()?) } keyword! { tgens(not_in_paste()?) } keyword! { tdefgens(not_in_paste()?, not_in_bool()?) } keyword! { tgnames(not_in_paste()?, not_in_bool()?) } keyword! { twheres(not_in_paste()?, not_in_bool()?) } use meta::Scope as MS; keyword! { "tmeta": Xmeta(parse_meta(input, MS::T)?) } keyword! { "vmeta": Xmeta(parse_meta(input, MS::V)?) } keyword! { "fmeta": Xmeta(parse_meta(input, MS::F)?) } keyword! { tattrs(input.parse()?, not_in_paste()?, not_in_bool()?) } keyword! { vattrs(input.parse()?, not_in_paste()?, not_in_bool()?) } keyword! { fattrs(input.parse()?, not_in_paste()?, not_in_bool()?) } keyword! { vtype( SubstVType::parse(input, kw.span())?, not_in_paste()?, not_in_bool()?, ) } keyword! { vpat( SubstVPat::parse(input, kw.span())?, not_in_paste()?, not_in_bool()?, ) } keyword! { tdefvariants( parse_def_body( input, "tdefvariants needs to contain the variant definitions", )?, not_in_paste()?, not_in_bool()?, ) } keyword! { fdefine( (!input.is_empty()).then(|| input.parse()).transpose()?, not_in_paste()?, not_in_bool()? ) } keyword! { vdefbody( input.parse()?, parse_def_body( input, "vdefbody needs to contain the body definition", )?, not_in_paste()?, not_in_bool()?, ) } keyword! { is_empty(bool_only()?, { let content; let _ = parenthesized!(content in input); content.parse()? }) } keyword! { approx_equal(bool_only()?, { let args = Punctuated::<_, Token![,]>::parse_separated_nonempty( &in_parens(input)?, )?; if args.len() != 2 { return Err(kw.error( "approx_equal() requires two comma-separated arguments" )) } let mut args = args.into_iter(); // std::array::from_fn needs MSRV 1.63 let mut arg = || args.next().unwrap(); [ arg(), arg() ] }) } keyword! { paste(Template::parse(input)?, not_in_bool()?) } keyword! { when(input.parse()?, not_in_bool()?) } keyword! { define(input.parse()?, not_in_bool()?) } keyword! { defcond(input.parse()?, not_in_bool()?) } keyword! { "false": False(bool_only()?) } keyword! { "true": True(bool_only()?) } keyword! { "if": If(parse_if(input)?, not_in_bool()?) } keyword! { select1(parse_if(input)?, not_in_bool()?) } keyword! { ignore(input.parse()?, not_in_bool()?) } keyword! { error(input.parse()?, not_in_bool()?) } keyword! { dbg(input.parse()?) } keyword! { dbg_all_keywords(not_in_bool()?) } keyword! { "crate": Crate(not_in_paste()?, not_in_bool()?) } keyword! { "_dd_intern_crate": Crate(not_in_paste()?, not_in_bool()?) } keyword! { "for": For( RepeatedTemplate::parse_for(input)?, not_in_bool()?, )} let any_all_contents = |input: ParseStream<'i>| { Punctuated::parse_terminated(&in_parens(input)?) }; keyword! { any(any_all_contents(input)?, bool_only()?) } keyword! { all(any_all_contents(input)?, bool_only()?) } keyword! { not(in_parens(input)?.parse()?, bool_only()?) } if let Ok(case) = kw.to_string().parse() { return from_sd(SD::ChangeCase( Template::parse(input)?, case, not_in_bool()?, )); } if let Ok(user_defined) = kw.clone().try_into() { return from_sd(SD::UserDefined(user_defined)); } Err(kw.error("unknown derive-deftly keyword")) } } impl Parse for DbgDumpRequest { fn parse(input: ParseStream) -> syn::Result { O::parse_maybe_within_parens(input, |input| { let note = if input.peek(syn::LitStr) { let note: syn::LitStr = input.parse()?; O::parse_maybe_comma(input)?; Some(note.value()) } else { None }; let content_buf; let content = if O::IS_BOOL { input } else { let _ = braced!(content_buf in input); &content_buf }; // was content.to_string(); but $orig_dollar, see TODO let content_string = (); let content_parsed = content.parse()?; Ok(DbgDumpRequest { note, content_string, content_parsed, }) }) } } impl DbgDumpRequest { pub fn display_heading<'s>( &'s self, ctx: &'s Context, ) -> impl Display + 's { struct Adapter<'a, O: SubstParseContext>( &'a DbgDumpRequest, &'a Context<'a>, ); impl Display for Adapter<'_, O> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Adapter(ddr, ctx) = self; if let Some(note) = &ddr.note { write!(f, "{:?} ", ¬e)?; } write!(f, "{}", ctx.display_for_dbg()) } } Adapter(self, ctx) } } impl Parse for ExplicitError { fn parse(input: ParseStream) -> syn::Result { let message: syn::LitStr = input.parse()?; if message.suffix() != "" { return Err(message.error("suffix forbidden on error string")); } Ok(ExplicitError { message }) } } impl SubstIf { fn parse(input: ParseStream, kw_span: Span) -> syn::Result { let mut tests = Vec::new(); let mut otherwise = None; loop { let condition = input.parse()?; let content; let _br = braced![ content in input ]; let consequence = Template::parse(&content)?; tests.push((condition, consequence)); // (I'd like to use a lookahead here too, but it doesn't // accept "Nothing") if input.is_empty() { // no more conditions if there is not an "else" break; } let lookahead1 = input.lookahead1(); if lookahead1.peek(syn::Ident) { // this is another expansion keyword, then // skipped `else if` continue; } else if lookahead1.peek(Token![else]) { // else clause } else { return Err(lookahead1.error()); } let _else: Token![else] = input.parse()?; let lookahead = input.lookahead1(); if lookahead.peek(Token![if]) { let _if: Token![if] = input.parse()?; // got an "else if": continue to the next iteration. continue; } else if lookahead.peek(token::Brace) { let content; let _br = braced![ content in input ]; otherwise = Some(Template::parse(&content)?.into()); break; // No more input allowed. // Subst::parse_after_dollar will detect any remaining // tokens and make an error if there are any. } else { return Err(lookahead.error()); } } Ok(SubstIf { kw_span, tests, otherwise, }) } } impl SubstVis { pub fn syn_vis<'c>( &self, ctx: &'c Context<'c>, tspan: Span, ) -> syn::Result<&'c syn::Visibility> { let field_decl_vis = || Ok::<_, syn::Error>(&ctx.field(&tspan)?.field.vis); Ok(match self { SubstVis::T => &ctx.top.vis, SubstVis::FD => field_decl_vis()?, SubstVis::F => { // Cause an error for fvis in enums, even though // we only look at the toplevel visibility. let field = field_decl_vis()?; match ctx.top.data { syn::Data::Struct(_) | syn::Data::Union(_) => field, syn::Data::Enum(_) => &ctx.top.vis, } } }) } } impl RawAttrEntry { fn simple(self, _negated: &Token![!]) -> syn::Result { Ok(self.path) } } impl Parse for RawAttr { fn parse(input: ParseStream) -> syn::Result { if input.is_empty() { return Ok(RawAttr::Default); } let la = input.lookahead1(); let negated; if la.peek(Token![!]) { negated = Some(input.parse()?); } else if la.peek(Token![=]) { let _: Token![=] = input.parse()?; negated = None; } else { negated = None; } let entries: Punctuated = input.call(Punctuated::parse_terminated)?; if let Some(negated) = &negated { let exclusions = entries .into_iter() .map(|ent| ent.simple(negated)) .try_collect()?; Ok(RawAttr::Exclude { exclusions }) } else { Ok(RawAttr::Include { entries }) } } } impl Parse for RawAttrEntry { fn parse(input: ParseStream) -> syn::Result { let path = input.parse()?; Ok(RawAttrEntry { path }) } } /// Matches `$te` looking for `SD::when` /// /// If so, returns `Left(kw_span, Box)`. /// Otherwise returns Right($e). /// /// `$te: TemplateElement` or `$te: &TemplateElement` macro_rules! te_extract_when { { $te:expr } => { match $te { TE::Subst(Subst { kw_span, sd: SD::when(bc, _) }) => { Left((kw_span, bc)) } other => Right(other), } } } impl RepeatedTemplate { fn parse_in_parens(input: ParseStream) -> syn::Result> { let template; let paren = parenthesized!(template in input); let rt = RepeatedTemplate::parse(&template, paren.span, None)?; Ok(TE::Repeat(rt)) } fn parse_for(input: ParseStream) -> syn::Result> { let over: Ident = input.parse()?; let over = if over == "fields" { RepeatOver::Fields } else if over == "variants" { RepeatOver::Variants } else { return Err( over.error("$for must be followed by 'fields' or 'variants'") ); }; let template; let brace = braced!(template in input); RepeatedTemplate::parse(&template, brace.span, Some(over)) } fn parse( input: ParseStream, span: DelimSpan, over: Option, ) -> Result, syn::Error> { use TemplateWithWhens as TWW; let TWW { elements } = TWW::parse_special(input, &mut Default::default())?; // split `when` off let mut elements = VecDeque::from(elements); let mut whens = vec![]; while let Some(()) = { match elements.pop_front().map(|e| te_extract_when!(e)) { Some(Left((_kw_span, bc))) => { whens.push(bc); Some(()) } Some(Right(other)) => { elements.push_front(other); None } None => None, } } {} let elements = Vec::from(elements); let template = TemplateWithWhens { elements }; let template = Template::try_from(template)?; let over = match over { Some(over) => Ok(over), None => { let mut visitor = RepeatAnalysisVisitor::default(); template.analyse_repeat(&mut visitor)?; visitor.finish(span) } }; match over { Ok(over) => Ok(RepeatedTemplate { over, template, whens, }), Err(errs) => Err(errs), } } } impl TryFrom> for Template where O: SubstParseContext, { type Error = syn::Error; fn try_from(unchecked: TemplateWithWhens) -> syn::Result> { let TemplateWithWhens { elements } = unchecked; for e in &elements { if let Left((kw_span, _)) = te_extract_when!(e) { return Err(kw_span.error( "${when } must be at the top-level of a repetition, before other content" )); } } Ok(Template { elements }) } } pub fn preprocess_attrs( attrs: &[syn::Attribute], ) -> syn::Result { attrs .iter() .filter_map(|attr| { // infallible filtering for attributes we are interested in match attr.style { syn::AttrStyle::Outer => {} syn::AttrStyle::Inner(_) => return None, }; if attr.path().leading_colon.is_some() { return None; } let segment = attr.path().segments.iter().exactly_one().ok()?; if segment.ident != "deftly" { return None; } Some(attr) }) .map(|attr| { attr.call_in_parens(meta::PreprocessedValueList::parse_inner) }) .collect() } pub fn preprocess_fields( fields: &syn::Fields, ) -> syn::Result> { let fields = match fields { syn::Fields::Named(f) => &f.named, syn::Fields::Unnamed(f) => &f.unnamed, syn::Fields::Unit => return Ok(vec![]), }; fields .into_iter() .map(|field| { let pmetas = preprocess_attrs(&field.attrs)?; Ok(PreprocessedField { pmetas }) }) .collect() }