diff --git a/bacon.toml b/bacon.toml new file mode 100644 index 0000000..9830490 --- /dev/null +++ b/bacon.toml @@ -0,0 +1,65 @@ +# This is a configuration file for the bacon tool +# More info at https://github.com/Canop/bacon + +default_job = "check" + +[jobs] + +[jobs.check] +command = ["cargo", "check", "--color", "always"] +need_stdout = false + +[jobs.check-all] +command = ["cargo", "check", "--all-targets", "--color", "always"] +need_stdout = false +watch = ["tests", "benches", "examples"] + +[jobs.clippy] +command = ["cargo", "clippy", "--color", "always"] +need_stdout = false + +[jobs.clippy-all] +command = ["cargo", "clippy", "--all-targets", "--color", "always"] +need_stdout = false +watch = ["tests", "benches", "examples"] + +[jobs.test] +command = ["cargo", "test", "--color", "always"] +need_stdout = true +watch = ["tests"] + +[jobs.doc] +command = ["cargo", "doc", "--color", "always", "--no-deps"] +need_stdout = false + +# if the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +# You can run your application and have the result displayed in bacon, +# *if* it makes sense for this crate. You can run an example the same +# way. Don't forget the `--color always` part or the errors won't be +# properly parsed. +[jobs.run] +command = ["cargo", "run", "--color", "always"] +need_stdout = true +allow_warnings = true + +[jobs.expand] +command = ["cargo", "expand", "expand", "--color", "always"] +need_stdout = true + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal prefs.toml file instead. +[keybindings] +a = "job:check-all" +i = "job:initial" +c = "job:clippy" +d = "job:doc-open" +t = "job:test" +r = "job:run" diff --git a/mono-fmt-macro/src/lib.rs b/mono-fmt-macro/src/lib.rs index 23a2a20..a1df169 100644 --- a/mono-fmt-macro/src/lib.rs +++ b/mono-fmt-macro/src/lib.rs @@ -1,29 +1,33 @@ //! a bunch of this code is adapted from [stylish](https://github.com/Nullus157/stylish-rs) #![allow(dead_code, unreachable_code, unused_variables)] +use std::cell::Cell; + use format::Parse as _; use proc_macro::TokenStream; -use quote::quote; +use quote::{ToTokens, quote}; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, Expr, ExprAssign, ExprPath, Ident, LitStr, PathArguments, Result, Token, }; +use to_tokens::Scoped; mod format; mod to_tokens; struct Input { - crate_ident: Ident, - format_str: String, + prefix: proc_macro2::TokenStream, + format_str: LitStr, positional_args: Vec, named_args: Vec<(Ident, Expr)>, } impl Parse for Input { fn parse(input: ParseStream<'_>) -> Result { - let crate_ident = input.parse()?; + let crate_ident = input.parse::()?; + let prefix = quote! { #crate_ident::_private }; - let format_str = input.parse::()?.value(); + let format_str = input.parse::()?; let mut positional_args = Vec::new(); let mut named_args = Vec::new(); @@ -59,7 +63,7 @@ impl Parse for Input { } } Ok(Self { - crate_ident, + prefix, format_str, positional_args, named_args, @@ -68,13 +72,12 @@ impl Parse for Input { } fn format_args_impl(input: Input) -> syn::Result { - todo!(); - let (_, fmt_parts) = format::Format::parse(&input.format_str).unwrap(); + let str = input.format_str.value(); + let (_, fmt_parts) = format::Format::parse(&str).unwrap(); - Ok(quote! { - fmt_parts - } - .into()) + let current_position = Cell::new(0); + + Ok(Scoped::new(&input, &fmt_parts, ¤t_position).to_token_stream().into()) } #[proc_macro] diff --git a/mono-fmt-macro/src/to_tokens.rs b/mono-fmt-macro/src/to_tokens.rs index 8ef9c61..e0b87cc 100644 --- a/mono-fmt-macro/src/to_tokens.rs +++ b/mono-fmt-macro/src/to_tokens.rs @@ -1,16 +1,28 @@ -use proc_macro2::TokenStream; +use std::cell::Cell; + +use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; -use crate::format::{Align, FormatTrait}; +use crate::{ + format::{ + Align, Count, Format, FormatArg, FormatArgRef, FormatTrait, FormatterArgs, Piece, Sign, + }, + Input, +}; -pub struct Scoped<'a, T> { - export: &'a syn::Path, +pub(crate) struct Scoped<'a, T> { + input: &'a Input, + current_position: &'a Cell, inner: &'a T, } impl<'a, T> Scoped<'a, T> { - pub fn new(export: &'a syn::Path, inner: &'a T) -> Self { - Self { export, inner } + pub fn new(input: &'a Input, inner: &'a T, current_position: &'a Cell) -> Self { + Self { + input, + inner, + current_position, + } } fn scope<'b, U>(&self, inner: &'b U) -> Scoped<'b, U> @@ -19,7 +31,8 @@ impl<'a, T> Scoped<'a, T> { { Scoped { inner, - export: self.export, + input: self.input, + current_position: self.current_position, } } @@ -28,6 +41,164 @@ impl<'a, T> Scoped<'a, T> { } } +impl ToTokens for Scoped<'_, Format<'_>> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let parts = self.inner.pieces.iter().map(|piece| self.scope(piece)); + + tokens.extend(quote! { + ( + #(#parts),* + ) + }) + } +} + +impl ToTokens for Scoped<'_, Piece<'_>> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = &self.input.prefix; + + match self.inner { + Piece::Lit(literal) => { + let lit = syn::LitStr::new(literal, self.input.format_str.span()); + + tokens.extend(quote! { #prefix::Str(#lit) }); + } + Piece::Arg(arg) => self.scope(arg).to_tokens(tokens), + } + } +} + +impl ToTokens for Scoped<'_, FormatArg<'_>> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = &self.input.prefix; + + let base = self.inner.format_spec.format_trait; + + let expr = match self.inner.arg { + None => { + let current_position = self.current_position.get(); + self.current_position.set(current_position + 1); + + self.input.positional_args[current_position].to_token_stream() + } + Some(FormatArgRef::Positional(idx)) => { + self.input.positional_args[idx].to_token_stream() + } + Some(FormatArgRef::Named(name)) => self + .input + .named_args + .iter() + .find(|(arg, _)| arg == name) + .map(|(_, expr)| expr.to_token_stream()) + .unwrap_or_else(|| Ident::new(name, Span::call_site()).to_token_stream()), + }; + + let opt_ty = opt_ty_tokens(self.scope(&self.inner.format_spec.formatter_args)); + let opt_values = opt_value_tokens(self.scope(&self.inner.format_spec.formatter_args)); + + + tokens.extend(quote! { #prefix::#base::<_, #opt_ty>(#expr, #opt_values) }) + } +} + +fn opt_value_tokens(scope: Scoped<'_, FormatterArgs<'_>>) -> TokenStream { + let args = &scope.inner; + let prefix = &scope.input.prefix; + + let mut opts = quote! { () }; + + if args.alternate { + opts = quote! { #prefix::WithAlternate(#opts) }; + } + + if let Some(width) = args.width { + let width = match width { + Count::Integer(int) => int, + Count::Parameter(_) => panic!("parameter counts are not supported right now"), + }; + opts = quote! { #prefix::WithWidth(#opts) }; + } + + if let Some(align) = args.align { + opts = quote! { #prefix::WithAlign(#opts) }; + } + + if let Some(Sign::Plus) = args.sign { + opts = quote! { #prefix::WithSignPlus(#opts) }; + } + + if let Some(Sign::Minus) = args.sign { + opts = quote! { #prefix::WithMinus(#opts)}; + } + + if let Some(precision) = args.precision { + let precision = match precision { + Count::Integer(int) => int, + Count::Parameter(_) => panic!("parameter counts are not supported right now"), + }; + opts = quote! { #prefix::WithPrecision(#opts) }; + } + + if let Some(Sign::Plus) = args.sign { + opts = quote! { #prefix::WithSignPlus(#opts) }; + } + + if args.zero { + opts = quote! { #prefix::WithSignAwareZeroPad(#opts) }; + } + + opts +} + +fn opt_ty_tokens(scope: Scoped<'_, FormatterArgs<'_>>) -> TokenStream { + let args = &scope.inner; + let prefix = &scope.input.prefix; + + let mut opts = quote! { () }; + + if args.alternate { + opts = quote! { #prefix::WithAlternate<#opts> }; + } + + if let Some(width) = args.width { + let width = match width { + Count::Integer(int) => int, + Count::Parameter(_) => panic!("parameter counts are not supported right now"), + }; + opts = quote! { #prefix::WithWidth<#opts, #width> }; + } + + if let Some(align) = args.align { + opts = quote! { #prefix::WithAlign<#opts, #align> }; + } + + if let Some(Sign::Plus) = args.sign { + opts = quote! { #prefix::WithSignPlus<#opts> }; + } + + if let Some(Sign::Minus) = args.sign { + opts = quote! { #prefix::WithMinus<#opts> }; + } + + if let Some(precision) = args.precision { + let precision = match precision { + Count::Integer(int) => int, + Count::Parameter(_) => panic!("parameter counts are not supported right now"), + }; + opts = quote! { #prefix::WithPrecision<#opts, #precision> }; + } + + if let Some(Sign::Plus) = args.sign { + opts = quote! { #prefix::WithSignPlus<#opts> }; + } + + if args.zero { + opts = quote! { #prefix::WithSignAwareZeroPad<#opts> }; + } + + opts +} + impl ToTokens for Align { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(match self { @@ -41,15 +212,15 @@ impl ToTokens for Align { impl ToTokens for FormatTrait { fn to_tokens(&self, tokens: &mut TokenStream) { match self { - FormatTrait::Display => quote!(Display), - FormatTrait::Debug => quote!(Debug), - FormatTrait::Octal => quote!(Octal), - FormatTrait::LowerHex => quote!(LowerHex), - FormatTrait::UpperHex => quote!(UpperHex), - FormatTrait::Pointer => quote!(Pointer), - FormatTrait::Binary => quote!(Binary), - FormatTrait::LowerExp => quote!(LowerExp), - FormatTrait::UpperExp => quote!(UpperExp), + FormatTrait::Display => quote! { DisplayArg }, + FormatTrait::Debug => quote! { DebugArg }, + FormatTrait::Octal => quote! { OctalArg }, + FormatTrait::LowerHex => quote! { LowerHexArg }, + FormatTrait::UpperHex => quote! { UpperHexArg }, + FormatTrait::Pointer => quote! { PointerArg }, + FormatTrait::Binary => quote! { BinaryArg }, + FormatTrait::LowerExp => quote! { LowerExpArg }, + FormatTrait::UpperExp => quote! { UpperExpArg }, } .to_tokens(tokens) } diff --git a/src/lib.rs b/src/lib.rs index d91e145..325bdf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,10 @@ impl Formatter { } impl Formatter { - fn wrap_with<'opt, ONew: FmtOpts>(&mut self, opts: &ONew) -> Formatter<&mut W, ONew::ReplaceInnermost> { + fn wrap_with<'opt, ONew: FmtOpts>( + &mut self, + opts: &ONew, + ) -> Formatter<&mut W, ONew::ReplaceInnermost> { Formatter { buf: &mut self.buf, opts: opts.override_other(self.opts), @@ -88,15 +91,19 @@ impl Formatter { } } -pub fn write(buffer: W, args: A) -> Result { - let mut fmt = Formatter::new(buffer); - args.fmt(&mut fmt) -} +pub mod helpers { + use crate::{Arguments, Formatter, Result, Write}; -pub fn format(args: A) -> String { - let mut string = String::new(); - write(&mut string, args).unwrap(); - string + pub fn write(buffer: W, args: A) -> Result { + let mut fmt = Formatter::new(buffer); + args.fmt(&mut fmt) + } + + pub fn format(args: A) -> String { + let mut string = String::new(); + write(&mut string, args).unwrap(); + string + } } /// Not part of the public API. @@ -116,7 +123,7 @@ mod _private { #[macro_export] macro_rules! format { ($($tt:tt)*) => { - $crate::format($crate::format_args!($($tt)*)) + $crate::helpers::format($crate::format_args!($($tt)*)) }; } @@ -154,17 +161,3 @@ mod tests { assert_eq!(result, "a: 32523532"); } } - -// testing -fn fmt() { - let a = ( - _private::Str("amount: "), - _private::DebugArg::<_, _private::WithAlternate<()>>(5, _private::WithAlternate(())), - ); - - let mut str = String::new(); - let mut f = Formatter::new(&mut str); - Arguments::fmt(&a, &mut f).unwrap(); - - println!("{str}"); -} diff --git a/src/rust_core_impl/mod.rs b/src/rust_core_impl/mod.rs index 3b06eba..8957941 100644 --- a/src/rust_core_impl/mod.rs +++ b/src/rust_core_impl/mod.rs @@ -154,7 +154,11 @@ impl Formatter { // Writes the sign if it exists, and then the prefix if it was requested #[inline(never)] - fn write_prefix(f: &mut Formatter, sign: Option, prefix: Option<&str>) -> Result { + fn write_prefix( + f: &mut Formatter, + sign: Option, + prefix: Option<&str>, + ) -> Result { if let Some(c) = sign { f.buf.write_char(c)?; } @@ -183,14 +187,16 @@ impl Formatter { // is zero Some(min) if self.sign_aware_zero_pad() => { write_prefix(self, sign, prefix)?; - let post_padding = self.padding(min - width, Alignment::Right, '0', Alignment::Right)?; + let post_padding = + self.padding(min - width, Alignment::Right, '0', Alignment::Right)?; self.buf.write_str(buf)?; post_padding.write(self)?; Ok(()) } // Otherwise, the sign and prefix goes after the padding Some(min) => { - let post_padding = self.padding(min - width, Alignment::Right, self.fill(), self.align())?; + let post_padding = + self.padding(min - width, Alignment::Right, self.fill(), self.align())?; write_prefix(self, sign, prefix)?; self.buf.write_str(buf)?; post_padding.write(self) @@ -221,7 +227,7 @@ impl Formatter { // remaining parts go through the ordinary padding process. let len = formatted.len(); - + if width <= len { // no padding self.write_formatted_parts(&formatted)