|
@@ -1,24 +1,60 @@
|
|
|
-#[cfg(feature = "to_tokens")]
|
|
|
-use ::quote::{quote, ToTokens};
|
|
|
-use ::std::ops::Not;
|
|
|
-use ::syn::{
|
|
|
+use proc_macro2::{Span, TokenStream};
|
|
|
+
|
|
|
+use quote::{quote, ToTokens};
|
|
|
+use syn::{
|
|
|
parse::{Parse, ParseStream},
|
|
|
- punctuated::Punctuated,
|
|
|
*,
|
|
|
};
|
|
|
-use proc_macro2::TokenStream;
|
|
|
|
|
|
-#[cfg(feature = "to_tokens")]
|
|
|
pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
|
|
- let IfmtInput {
|
|
|
- format_literal,
|
|
|
- positional_args,
|
|
|
- named_args,
|
|
|
- } = input;
|
|
|
+ // build format_literal
|
|
|
+ let mut format_literal = String::new();
|
|
|
+ let mut expr_counter = 0;
|
|
|
+ for segment in input.segments.iter() {
|
|
|
+ match segment {
|
|
|
+ Segment::Literal(s) => format_literal += &s,
|
|
|
+ Segment::Formatted {
|
|
|
+ format_args,
|
|
|
+ segment,
|
|
|
+ } => {
|
|
|
+ format_literal += "{";
|
|
|
+ match segment {
|
|
|
+ FormattedSegment::Expr(_) => {
|
|
|
+ format_literal += &expr_counter.to_string();
|
|
|
+ expr_counter += 1;
|
|
|
+ }
|
|
|
+ FormattedSegment::Ident(ident) => {
|
|
|
+ format_literal += &ident.to_string();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ format_literal += ":";
|
|
|
+ format_literal += format_args;
|
|
|
+ format_literal += "}";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let positional_args = input.segments.iter().filter_map(|seg| {
|
|
|
+ if let Segment::Formatted { segment, .. } = seg {
|
|
|
+ if let FormattedSegment::Expr(expr) = segment {
|
|
|
+ Some(expr)
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- let named_args = named_args.into_iter().map(|(ident, expr)| {
|
|
|
- quote! {
|
|
|
- #ident = #expr
|
|
|
+ let named_args = input.segments.iter().filter_map(|seg| {
|
|
|
+ if let Segment::Formatted { segment, .. } = seg {
|
|
|
+ if let FormattedSegment::Ident(ident) = segment {
|
|
|
+ Some(quote! {#ident = #ident})
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ None
|
|
|
}
|
|
|
});
|
|
|
|
|
@@ -34,219 +70,102 @@ pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
|
|
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
|
|
#[derive(Debug)]
|
|
|
pub struct IfmtInput {
|
|
|
- pub format_literal: LitStr,
|
|
|
- pub positional_args: Vec<Expr>,
|
|
|
- pub named_args: Vec<(Ident, Expr)>,
|
|
|
+ pub segments: Vec<Segment>,
|
|
|
}
|
|
|
|
|
|
impl IfmtInput {
|
|
|
- fn parse_segments(self) -> Result<Self> {
|
|
|
- let IfmtInput {
|
|
|
- mut format_literal,
|
|
|
- mut positional_args,
|
|
|
- mut named_args,
|
|
|
- } = self;
|
|
|
-
|
|
|
- let s = format_literal.value();
|
|
|
- let out_format_literal = &mut String::with_capacity(s.len());
|
|
|
-
|
|
|
- let mut iterator = s.char_indices().peekable();
|
|
|
- while let Some((i, c)) = iterator.next() {
|
|
|
- out_format_literal.push(c);
|
|
|
- if c != '{' {
|
|
|
- continue;
|
|
|
- }
|
|
|
- // encountered `{`, let's see if it was `{{`
|
|
|
- if let Some(&(_, '{')) = iterator.peek() {
|
|
|
- let _ = iterator.next();
|
|
|
- out_format_literal.push('{');
|
|
|
- continue;
|
|
|
- }
|
|
|
- let (end, colon_or_closing_brace) = iterator
|
|
|
- .find(|&(_, c)| c == '}' || c == ':')
|
|
|
- .expect(concat!(
|
|
|
- "Invalid format string literal\n",
|
|
|
- "note: if you intended to print `{`, ",
|
|
|
- "you can escape it using `{{`",
|
|
|
- ));
|
|
|
- // We use defer to ensure all the `continue`s append the closing char.
|
|
|
- let mut out_format_literal = defer(&mut *out_format_literal, |it| {
|
|
|
- it.push(colon_or_closing_brace)
|
|
|
- });
|
|
|
- let out_format_literal: &mut String = *out_format_literal;
|
|
|
- let mut arg = s[i + 1..end].trim();
|
|
|
- if let Some("=") = arg.get(arg.len().saturating_sub(1)..) {
|
|
|
- assert_eq!(
|
|
|
- out_format_literal.pop(), // Remove the opening brace
|
|
|
- Some('{'),
|
|
|
- );
|
|
|
- arg = &arg[..arg.len() - 1];
|
|
|
- out_format_literal.push_str(arg);
|
|
|
- out_format_literal.push_str(" = {");
|
|
|
- }
|
|
|
- if arg.is_empty() {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Debug)]
|
|
|
- enum Segment {
|
|
|
- Ident(Ident),
|
|
|
- LitInt(LitInt),
|
|
|
- }
|
|
|
- let segments: Vec<Segment> = {
|
|
|
- impl Parse for Segment {
|
|
|
- fn parse(input: ParseStream<'_>) -> Result<Self> {
|
|
|
- let lookahead = input.lookahead1();
|
|
|
- if lookahead.peek(Ident) {
|
|
|
- input.parse().map(Segment::Ident)
|
|
|
- } else if lookahead.peek(LitInt) {
|
|
|
- input.parse().map(Segment::LitInt)
|
|
|
- } else {
|
|
|
- Err(lookahead.error())
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- match ::syn::parse::Parser::parse_str(
|
|
|
- Punctuated::<Segment, Token![.]>::parse_separated_nonempty,
|
|
|
- arg,
|
|
|
- ) {
|
|
|
- Ok(segments) => segments.into_iter().collect(),
|
|
|
- Err(err) => return Err(err),
|
|
|
+ pub fn from_str(input: &str) -> Result<Self> {
|
|
|
+ let mut chars = input.chars().peekable();
|
|
|
+ let mut segments = Vec::new();
|
|
|
+ let mut current_literal = String::new();
|
|
|
+ while let Some(c) = chars.next() {
|
|
|
+ if c == '{' {
|
|
|
+ if let Some(c) = chars.next_if(|c| *c == '{') {
|
|
|
+ current_literal.push(c);
|
|
|
+ continue;
|
|
|
}
|
|
|
- };
|
|
|
- match segments.len() {
|
|
|
- 0 => unreachable!("`parse_separated_nonempty` returned empty"),
|
|
|
- 1 => {
|
|
|
- out_format_literal.push_str(arg);
|
|
|
- match { segments }.pop().unwrap() {
|
|
|
- Segment::LitInt(_) => {
|
|
|
- // found something like `{0}`, let `format_args!`
|
|
|
- // handle it.
|
|
|
- continue;
|
|
|
- }
|
|
|
- Segment::Ident(ident) => {
|
|
|
- // if `ident = ...` is not yet among the extra args
|
|
|
- if named_args.iter().all(|(it, _)| *it != ident) {
|
|
|
- named_args.push((
|
|
|
- ident.clone(),
|
|
|
- parse_quote!(#ident), // Expr
|
|
|
- ));
|
|
|
+ segments.push(Segment::Literal(current_literal));
|
|
|
+ current_literal = String::new();
|
|
|
+ let mut current_captured = String::new();
|
|
|
+ while let Some(c) = chars.next() {
|
|
|
+ if c == ':' {
|
|
|
+ let mut current_format_args = String::new();
|
|
|
+ while let Some(c) = chars.next() {
|
|
|
+ if c == '}' {
|
|
|
+ segments.push(Segment::Formatted {
|
|
|
+ format_args: current_format_args,
|
|
|
+ segment: FormattedSegment::parse(¤t_captured)?,
|
|
|
+ });
|
|
|
+ break;
|
|
|
}
|
|
|
+ current_format_args.push(c);
|
|
|
}
|
|
|
+ break;
|
|
|
}
|
|
|
+ if c == '}' {
|
|
|
+ segments.push(Segment::Formatted {
|
|
|
+ format_args: String::new(),
|
|
|
+ segment: FormattedSegment::parse(¤t_captured)?,
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ current_captured.push(c);
|
|
|
}
|
|
|
- _ => {
|
|
|
- ::std::fmt::Write::write_fmt(
|
|
|
- out_format_literal,
|
|
|
- format_args!("{}", positional_args.len()),
|
|
|
- )
|
|
|
- .expect("`usize` or `char` Display impl cannot panic");
|
|
|
- let segments: Punctuated<TokenStream, Token![.]> = segments
|
|
|
- .into_iter()
|
|
|
- .map(|it| match it {
|
|
|
- Segment::Ident(ident) => ident.into_token_stream(),
|
|
|
- Segment::LitInt(literal) => literal.into_token_stream(),
|
|
|
- })
|
|
|
- .collect();
|
|
|
- positional_args.push(parse_quote! {
|
|
|
- #segments
|
|
|
- })
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ current_literal.push(c);
|
|
|
}
|
|
|
}
|
|
|
- format_literal = LitStr::new(out_format_literal, format_literal.span());
|
|
|
-
|
|
|
- Ok(Self {
|
|
|
- format_literal,
|
|
|
- positional_args,
|
|
|
- named_args,
|
|
|
- })
|
|
|
+ segments.push(Segment::Literal(current_literal));
|
|
|
+ Ok(Self { segments })
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- fn parse_positional_args(input: ParseStream) -> Result<Self> {
|
|
|
- let format_literal = input.parse()?;
|
|
|
- let mut positional_args = vec![];
|
|
|
- loop {
|
|
|
- if input.parse::<Option<Token![,]>>()?.is_none() {
|
|
|
- return Ok(Self {
|
|
|
- format_literal,
|
|
|
- positional_args,
|
|
|
- named_args: vec![],
|
|
|
- });
|
|
|
- }
|
|
|
- if input.peek(Ident) && input.peek2(Token![=]) && input.peek3(Token![=]).not() {
|
|
|
- // Found a positional parameter
|
|
|
- break;
|
|
|
- }
|
|
|
- positional_args.push(input.parse()?);
|
|
|
- }
|
|
|
- let named_args = Punctuated::<_, Token![,]>::parse_terminated_with(input, |input| {
|
|
|
- Ok({
|
|
|
- let name: Ident = input.parse()?;
|
|
|
- let _: Token![=] = input.parse()?;
|
|
|
- let expr: Expr = input.parse()?;
|
|
|
- (name, expr)
|
|
|
- })
|
|
|
- })?
|
|
|
- .into_iter()
|
|
|
- .collect();
|
|
|
-
|
|
|
- Ok(Self {
|
|
|
- format_literal,
|
|
|
- positional_args,
|
|
|
- named_args,
|
|
|
- })
|
|
|
- }
|
|
|
+#[derive(Debug)]
|
|
|
+pub enum Segment {
|
|
|
+ Literal(String),
|
|
|
+ Formatted {
|
|
|
+ format_args: String,
|
|
|
+ segment: FormattedSegment,
|
|
|
+ },
|
|
|
}
|
|
|
|
|
|
-impl Parse for IfmtInput {
|
|
|
- fn parse(input: ParseStream) -> Result<Self> {
|
|
|
- Self::parse_positional_args(input).and_then(|new| new.parse_segments())
|
|
|
- }
|
|
|
+#[derive(Debug)]
|
|
|
+pub enum FormattedSegment {
|
|
|
+ Expr(Expr),
|
|
|
+ Ident(Ident),
|
|
|
}
|
|
|
|
|
|
-pub fn defer<'a, T: 'a, Drop: 'a>(x: T, drop: Drop) -> impl ::core::ops::DerefMut<Target = T> + 'a
|
|
|
-where
|
|
|
- Drop: FnOnce(T),
|
|
|
-{
|
|
|
- use ::core::mem::ManuallyDrop;
|
|
|
- struct Ret<T, Drop>(ManuallyDrop<T>, ManuallyDrop<Drop>)
|
|
|
- where
|
|
|
- Drop: FnOnce(T);
|
|
|
- impl<T, Drop> ::core::ops::Drop for Ret<T, Drop>
|
|
|
- where
|
|
|
- Drop: FnOnce(T),
|
|
|
- {
|
|
|
- fn drop(&'_ mut self) {
|
|
|
- use ::core::ptr;
|
|
|
- unsafe {
|
|
|
- // # Safety
|
|
|
- //
|
|
|
- // - This is the canonical example of using `ManuallyDrop`.
|
|
|
- let value = ManuallyDrop::into_inner(ptr::read(&self.0));
|
|
|
- let drop = ManuallyDrop::into_inner(ptr::read(&self.1));
|
|
|
- drop(value);
|
|
|
+impl FormattedSegment {
|
|
|
+ fn parse(input: &str) -> Result<Self> {
|
|
|
+ if let Ok(ident) = parse_str::<Ident>(input) {
|
|
|
+ if &ident.to_string() == input {
|
|
|
+ return Ok(Self::Ident(ident));
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- impl<T, Drop> ::core::ops::Deref for Ret<T, Drop>
|
|
|
- where
|
|
|
- Drop: FnOnce(T),
|
|
|
- {
|
|
|
- type Target = T;
|
|
|
- #[inline]
|
|
|
- fn deref(&'_ self) -> &'_ Self::Target {
|
|
|
- &self.0
|
|
|
+ if let Ok(expr) = parse_str(&("{".to_string() + input + "}")) {
|
|
|
+ Ok(Self::Expr(expr))
|
|
|
+ } else {
|
|
|
+ Err(Error::new(
|
|
|
+ Span::call_site(),
|
|
|
+ "Expected Ident or Expression",
|
|
|
+ ))
|
|
|
}
|
|
|
}
|
|
|
- impl<T, Drop> ::core::ops::DerefMut for Ret<T, Drop>
|
|
|
- where
|
|
|
- Drop: FnOnce(T),
|
|
|
- {
|
|
|
- #[inline]
|
|
|
- fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
|
|
|
- &mut self.0
|
|
|
+}
|
|
|
+
|
|
|
+impl ToTokens for FormattedSegment {
|
|
|
+ fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
|
+ match self {
|
|
|
+ Self::Expr(expr) => expr.to_tokens(tokens),
|
|
|
+ Self::Ident(ident) => ident.to_tokens(tokens),
|
|
|
}
|
|
|
}
|
|
|
- Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop))
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for IfmtInput {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let input: LitStr = input.parse()?;
|
|
|
+ let input_str = input.value();
|
|
|
+ IfmtInput::from_str(&input_str)
|
|
|
+ }
|
|
|
}
|