use super::framework::*;
#[derive(Debug)]
pub struct Items {
tspan: Span,
items: Vec<Item>,
errors: Vec<syn::Error>,
atoms: Vec<AtomForReport>,
}
#[derive(Debug, Clone)]
pub struct AtomForReport {
text: String,
span: Span,
}
#[derive(Debug)]
enum Item {
Plain {
text: String,
span: Option<Span>,
},
Complex {
pre: TokenStream,
text: String,
post: TokenStream,
te_span: Span,
},
}
#[derive(Copy, Clone, Debug)]
pub struct IdentFragInfallible(pub Void);
impl From<IdentFragInfallible> for syn::Error {
fn from(i: IdentFragInfallible) -> syn::Error {
void::unreachable(i.0)
}
}
impl IdentFragInfallible {
pub fn unreachable(&self) -> ! {
void::unreachable(self.0)
}
}
pub trait IdentFrag: Spanned {
type BadIdent: Clone;
fn frag_to_tokens(
&self,
out: &mut TokenStream,
) -> Result<(), Self::BadIdent>;
fn fragment(&self) -> String;
fn note_atoms(&self, out: &mut Vec<AtomForReport>) {
out.push(AtomForReport {
text: self.fragment(),
span: self.span(),
});
}
}
impl<T: ToTokens + quote::IdentFragment + Display> IdentFrag for T {
type BadIdent = IdentFragInfallible;
fn frag_to_tokens(
&self,
out: &mut TokenStream,
) -> Result<(), IdentFragInfallible> {
Ok(self.to_tokens(out))
}
fn fragment(&self) -> String {
self.to_string()
}
}
#[cfg(feature = "case")]
macro_rules! define_cases { {
$(
$heck:ident $( $keyword:literal )*,
)*
} => {
#[derive(Debug, Clone, Copy)]
pub enum ChangeCase {
$( $heck, )*
}
impl FromStr for ChangeCase {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
$(
$( $keyword )|* => ChangeCase::$heck,
)*
_ => return Err(()),
})
}
}
impl ChangeCase {
fn apply(self, input: &str) -> String {
match self {
$(
ChangeCase::$heck => heck::$heck(input).to_string(),
)*
}
}
}
} }
#[cfg(not(feature = "case"))]
macro_rules! define_cases { {
$(
$heck:ident $( $keyword:literal )*,
)*
} => {
#[derive(Debug, Clone, Copy)]
pub enum ChangeCase {}
impl FromStr for ChangeCase {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
$(
$( $keyword )|* => {}
)*
_ => return Err(()),
}
panic!(
"case changing not supported, enable `case` feature of `derive-adhoc`"
);
}
}
impl ChangeCase {
fn apply(self, _input: &str) -> String {
match self {}
}
}
} }
define_cases! {
AsUpperCamelCase "pascal_case" "upper_camel_case" ,
AsLowerCamelCase "lower_camel_case" ,
AsSnakeCase "snake_case" ,
AsShoutySnakeCase "shouty_snake_case" ,
}
pub struct TokenPastesAsIdent<T>(pub T);
impl<T: ToTokens> Spanned for TokenPastesAsIdent<T> {
fn span(&self) -> Span {
self.0.span()
}
}
impl<T: ToTokens> IdentFrag for TokenPastesAsIdent<T> {
type BadIdent = IdentFragInfallible;
fn frag_to_tokens(
&self,
out: &mut TokenStream,
) -> Result<(), Self::BadIdent> {
Ok(self.0.to_tokens(out))
}
fn fragment(&self) -> String {
self.0.to_token_stream().to_string()
}
}
impl Items {
pub fn new(tspan: Span) -> Self {
Items {
tspan,
items: vec![],
errors: vec![],
atoms: vec![],
}
}
}
#[derive(Debug)]
struct Pasted {
whole: String,
span: Span,
atoms: Vec<AtomForReport>,
}
impl Spanned for Pasted {
fn span(&self) -> Span {
self.span
}
}
impl IdentFrag for Pasted {
type BadIdent = syn::Error;
fn fragment(&self) -> String {
self.whole.clone()
}
fn note_atoms(&self, atoms: &mut Vec<AtomForReport>) {
atoms.extend(self.atoms.iter().cloned())
}
fn frag_to_tokens(&self, out: &mut TokenStream) -> syn::Result<()> {
let ident = convert_to_ident(self)?;
ident.to_tokens(out);
Ok(())
}
}
type Piece<'i> = (&'i str, Option<Span>);
fn mk_ident<'i>(
out_span: Span,
change_case: Option<ChangeCase>,
pieces: impl Iterator<Item = Piece<'i>> + Clone,
atoms: Vec<AtomForReport>,
) -> Pasted {
let whole = pieces.clone().map(|i| i.0).collect::<String>();
let whole = if let Some(change_case) = change_case {
change_case.apply(&whole)
} else {
whole
};
Pasted {
span: out_span,
whole,
atoms,
}
}
#[derive(Eq, PartialEq, Debug)]
struct InvalidIdent;
fn convert_to_ident(pasted: &Pasted) -> syn::Result<syn::Ident> {
let mut ident = (|| {
let s = &pasted.whole;
let prefixed;
let (ident, comparator) = match syn::parse_str::<Ident>(s) {
Ok(ident) => {
(ident, s)
}
Err(_) => {
prefixed = format!("r#{}", s);
let ident = syn::parse_str::<Ident>(&prefixed)
.map_err(|_| InvalidIdent)?;
(ident, &prefixed)
}
};
if &ident.to_string() != comparator {
return Err(InvalidIdent);
}
Ok(ident)
})()
.map_err(|_| {
let mut err = pasted.span.error(format_args!(
"constructed identifier {:?} is invalid",
&pasted.whole,
));
for (
AtomForReport {
text: piece,
span: pspan,
},
pfx,
) in izip!(
&pasted.atoms,
chain!(iter::once(""), iter::repeat("X")),
) {
match syn::parse_str(&format!("{}{}", pfx, piece)) {
Ok::<IdentAny, _>(_) => {}
Err(_) => err
.combine(pspan.error(
"probably-invalid input to identifier pasting",
)),
}
}
err
})?;
ident.set_span(pasted.span);
Ok(ident)
}
#[test]
fn ident_from_str() {
let span = Span::call_site();
let chk = |s: &str, exp: Result<&str, _>| {
let p = Pasted {
whole: s.to_string(),
span,
atoms: vec![],
};
let parsed = convert_to_ident(&p)
.map(|i| i.to_string())
.map_err(|_| InvalidIdent);
let exp = exp.map(|i| i.to_string());
assert_eq!(parsed, exp);
};
let chk_ok = |s| chk(s, Ok(s));
let chk_err = |s| chk(s, Err(InvalidIdent));
chk("for", Ok("r#for"));
chk_ok("r#for");
chk_ok("_thing");
chk_ok("thing_");
chk_ok("r#raw");
chk_err("");
chk_err("a b");
chk_err("spc ");
chk_err(" spc");
chk_err("r#a spc");
chk_err(" r#a ");
chk_err(" r#for ");
chk_err("r#r#doubly_raw");
chk_err("0");
}
pub fn expand(
ctx: &Context<'_>,
kw_span: Span,
content: &Template<paste::Items>,
out: &mut impl ExpansionOutput,
) -> syn::Result<()> {
let mut items = paste::Items::new(kw_span);
content.expand(ctx, &mut items);
items.assemble(out, None)
}
impl Items {
fn append_atom(&mut self, item: Item) {
match &item {
Item::Plain {
text,
span: Some(span),
..
}
| Item::Complex {
text,
te_span: span,
..
} => {
self.atoms.push(AtomForReport {
text: text.clone(),
span: *span,
});
}
Item::Plain { span: None, .. } => {}
};
self.items.push(item);
}
fn append_item_raw(&mut self, item: Item) {
self.items.push(item);
}
fn append_plain<V: Display>(&mut self, span: Span, v: V) {
self.append_atom(Item::Plain {
text: v.to_string(),
span: Some(span),
})
}
pub fn append_fixed_string(&mut self, text: &'static str) {
self.append_atom(Item::Plain {
text: text.into(),
span: None,
})
}
pub fn assemble(
self,
out: &mut impl ExpansionOutput,
change_case: Option<ChangeCase>,
) -> syn::Result<()> {
if !self.errors.is_empty() {
for error in self.errors {
out.record_error(error);
}
return Ok(());
}
match Self::assemble_inner(
self.tspan,
self.items,
change_case,
self.atoms,
)? {
Either::Left(ident) => out.append_identfrag_toks(
&ident, )?,
Either::Right((tspan, pre, ident, post)) => out.append_idpath(
tspan,
|ta| ta.append(pre),
&ident,
|ta| ta.append(post),
)?,
}
Ok(())
}
fn assemble_inner(
tspan: Span,
items: Vec<Item>,
change_case: Option<ChangeCase>,
atoms: Vec<AtomForReport>,
) -> syn::Result<Either<Pasted, (Span, TokenStream, Pasted, TokenStream)>>
{
let out_span = tspan;
let nontrivial = items
.iter()
.enumerate()
.filter_map(|(pos, it)| match it {
Item::Plain { .. } => None,
Item::Complex { te_span, .. } => Some((pos, te_span)),
})
.at_most_one()
.map_err(|several| {
let mut several = several.map(|(_pos, span)| {
span.error("multiple nontrivial entries in ${paste ...}")
});
let mut collect = several.next().unwrap();
collect.extend(several);
collect
})?
.map(|(pos, _)| pos);
fn plain_strs(items: &[Item]) -> impl Iterator<Item = Piece> + Clone {
items.iter().map(|item| match item {
Item::Plain { text, span } => (text.as_str(), *span),
_ => panic!("non plain item"),
})
}
if let Some(nontrivial) = nontrivial {
let mut items = items;
let (items, items_after) = items.split_at_mut(nontrivial + 1);
let (items_before, items) = items.split_at_mut(nontrivial);
let nontrivial = &mut items[0];
let mk_ident_nt = |(text, txspan): Piece, atoms| {
mk_ident(
out_span,
change_case,
chain!(
plain_strs(items_before),
iter::once((text, txspan)),
plain_strs(items_after),
),
atoms,
)
};
match nontrivial {
Item::Complex {
pre,
text,
post,
te_span,
} => {
return Ok(Either::Right((
tspan,
mem::take(pre),
mk_ident_nt((text, Some(*te_span)), atoms),
mem::take(post),
)))
}
Item::Plain { .. } => panic!("trivial nontrivial"),
}
} else {
return Ok(Either::Left(
mk_ident(out_span, change_case, plain_strs(&items), atoms), ));
}
}
}
impl SubstParseContext for Items {
type NotInPaste = Void;
type NotInBool = ();
type BoolOnly = Void;
fn not_in_bool(_: &impl Spanned) -> syn::Result<()> {
Ok(())
}
fn not_in_paste(span: &impl Spanned) -> syn::Result<Void> {
Err(span
.error("not allowed in within ${paste ...} (or case_changing)"))
}
type SpecialParseContext = Option<AngleBrackets>;
fn special_before_element_hook(
special: &mut Option<AngleBrackets>,
input: ParseStream,
) -> syn::Result<Option<SpecialInstructions>> {
if input.peek(Token![>]) {
if let Some(state) = special {
let _: Token![>] = input.parse()?;
state.found_close = true;
return Ok(Some(SpecialInstructions::EndOfTemplate));
} else {
return Err(
input.error("stray > within curly-bracketed ${paste }")
);
}
}
return Ok(None);
}
}
#[derive(Default)]
pub struct AngleBrackets {
found_close: bool,
}
impl AngleBrackets {
pub fn finish(self, start_span: Span) -> syn::Result<()> {
if !self.found_close {
return Err(
start_span.error("unmatched paste $< start - missing >")
);
}
Ok(())
}
}
impl ExpansionOutput for Items {
fn append_display<S: Display + Spanned>(&mut self, plain: &S) {
self.append_plain(plain.span(), plain);
}
fn append_identfrag_toks<I: IdentFrag>(
&mut self,
ident: &I,
) -> Result<(), I::BadIdent> {
ident.note_atoms(&mut self.atoms);
self.append_plain(ident.span(), ident.fragment());
Ok(())
}
fn append_idpath<A, B, I>(
&mut self,
te_span: Span,
pre_: A,
ident: &I,
post_: B,
) -> Result<(), I::BadIdent>
where
A: FnOnce(&mut TokenAccumulator),
B: FnOnce(&mut TokenAccumulator),
I: IdentFrag,
{
let mut pre = TokenAccumulator::new();
pre_(&mut pre);
let mut post = TokenAccumulator::new();
post_(&mut post);
let text = ident.fragment();
let mut handle_err = |prepost: TokenAccumulator| {
prepost.tokens().unwrap_or_else(|err| {
self.record_error(err);
TokenStream::new()
})
};
let pre = handle_err(pre);
let post = handle_err(post);
ident.note_atoms(&mut self.atoms);
self.append_item_raw(Item::Complex {
pre,
post,
text,
te_span,
});
Ok(())
}
fn append_syn_litstr(&mut self, lit: &syn::LitStr) {
self.append_plain(lit.span(), lit.value());
}
fn append_syn_type(&mut self, te_span: Span, ty: &syn::Type) {
(|| {
match ty {
syn::Type::Path(path) => {
let mut path = path.clone();
let (last_segment, last_punct) = path
.path
.segments
.pop()
.ok_or_else(|| {
te_span.error(
"derive-adhoc token pasting applied to path with no components",
)
})?
.into_tuple();
let syn::PathSegment { ident, arguments } = last_segment;
let mut pre = TokenStream::new();
path.to_tokens(&mut pre);
let text = ident.to_string();
let mut post = TokenStream::new();
arguments.to_tokens(&mut post);
last_punct.to_tokens(&mut post);
let item = Item::Complex {
pre,
text,
post,
te_span,
};
self.append_atom(item)
}
x => {
return Err(x.error(
"derive-adhoc macro wanted to do identifier pasting, but complex type provided",
))
}
}
Ok::<_, syn::Error>(())
})()
.unwrap_or_else(|e| self.record_error(e));
}
fn append_tokens_with(
&mut self,
not_in_paste: &Void,
_: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>,
) -> syn::Result<()> {
void::unreachable(*not_in_paste)
}
fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> ! {
void::unreachable(*bool_only)
}
fn record_error(&mut self, err: syn::Error) {
self.errors.push(err);
}
fn default_subst_meta_as() -> SubstMetaAs<Self> {
SubstMetaAs::str(())
}
}
impl Expand<Items> for TemplateElement<Items> {
fn expand(&self, ctx: &Context, out: &mut Items) -> syn::Result<()> {
match self {
TE::Ident(ident) => out.append_identfrag_toks(&ident)?,
TE::LitStr(lit) => out.append_syn_litstr(&lit),
TE::Subst(e) => e.expand(ctx, out)?,
TE::Repeat(e) => e.expand(ctx, out),
TE::Literal(_, not_in_paste)
| TE::Punct(_, not_in_paste)
| TE::Group { not_in_paste, .. } => {
void::unreachable(*not_in_paste)
}
}
Ok(())
}
}