├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── basic.rs ├── codegen.rs └── tabular.rs ├── runtime-fmt-derive ├── Cargo.toml └── src │ └── lib.rs ├── src ├── codegen.rs ├── erase.rs ├── fmt_macros.rs ├── lib.rs └── macros.rs └── tests ├── bad.rs ├── equivalence.rs ├── flexible.rs └── ifmt.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime-fmt" 3 | version = "0.4.1" 4 | authors = ["Tad Hardesty "] 5 | 6 | description = "Runtime-based string formatting" 7 | readme = "README.md" 8 | license = "MIT/Apache-2.0" 9 | keywords = ["format", "fmt", "runtime", "string", "formatting"] 10 | 11 | repository = "https://github.com/SpaceManiac/runtime-fmt" 12 | documentation = "https://docs.rs/runtime-fmt" 13 | 14 | [dependencies] 15 | unicode-xid = "0.2.0" 16 | 17 | [dev-dependencies] 18 | runtime-fmt-derive = { path = "runtime-fmt-derive", version = "=0.2.0" } 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Tad Hardesty 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | runtime-fmt [![](https://meritbadge.herokuapp.com/runtime-fmt)](https://crates.io/crates/runtime-fmt) [![](https://img.shields.io/badge/docs-online-2020ff.svg)](https://docs.rs/runtime-fmt) 2 | ========== 3 | 4 | A crate for string formatting using runtime format strings. 5 | 6 | This crate provides much the same facilities as `std::fmt`, with the 7 | additional allowance for format strings which are not known until runtime. 8 | Possible applications include internationalization, scripting, or other 9 | customization. 10 | 11 | Each of the standard formatting macros `format_args!`, `format!`, 12 | `print!`, `println!`, `write!`, and `writeln!` have corresponding `rt_` 13 | variants. Calls which previously succeeded unconditionally now return 14 | `Result`, which may indicate a bad format string or arguments. 15 | 16 | The syntax for format strings and for macro invocations is equivalent to 17 | that used by `std::fmt`, including support for positional and named 18 | arguments. This crate shells out to the standard library implementations 19 | for as much as possible to ensure feature parity. 20 | 21 | This crate makes extensive use of the unstable formatting machinery and 22 | therefore **requires nightly**. 23 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate runtime_fmt; 2 | 3 | fn main() { 4 | let format_string = "Hello, {}!"; 5 | rt_println!(format_string, "world").unwrap(); 6 | rt_println!("bogus value {}").unwrap_err(); 7 | rt_println!("bogus}{string").unwrap_err(); 8 | } 9 | -------------------------------------------------------------------------------- /examples/codegen.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate runtime_fmt_derive; 2 | extern crate runtime_fmt; 3 | 4 | use runtime_fmt::PreparedFormat; 5 | 6 | #[derive(FormatArgs)] 7 | struct Struct { 8 | left: i32, 9 | right: &'static str, 10 | } 11 | 12 | #[derive(FormatArgs)] 13 | struct TupleStruct(i32, &'static str); 14 | 15 | #[derive(FormatArgs)] 16 | struct UnitStruct; 17 | 18 | #[derive(FormatArgs)] 19 | struct Alignable { 20 | text: &'static str, 21 | width: usize, 22 | } 23 | 24 | #[derive(FormatArgs)] 25 | struct WithBounds<'a, T: std::fmt::Display + 'a>(&'a T); 26 | 27 | fn main() { 28 | let mut prepared = PreparedFormat::prepare("{left}: {right}").unwrap(); 29 | prepared.newln(); 30 | prepared.print(&Struct { 31 | left: 42, 32 | right: "Hello, world!" 33 | }); 34 | 35 | PreparedFormat::prepare("{0}: {1}\n").unwrap().print( 36 | &TupleStruct(5, "Hello, TupleStruct") 37 | ); 38 | 39 | PreparedFormat::prepare("Hello, UnitStruct\n").unwrap().print(&UnitStruct); 40 | 41 | let prepared = PreparedFormat::prepare("({text:^width$})\n").unwrap(); 42 | prepared.print(&Alignable { 43 | text: "Wow, aligned!", 44 | width: 15 45 | }); 46 | prepared.print(&Alignable { 47 | text: "Wow, aligned!", 48 | width: 20 49 | }); 50 | 51 | PreparedFormat::prepare("{}").unwrap().newln().print(&WithBounds(&256)); 52 | } 53 | -------------------------------------------------------------------------------- /examples/tabular.rs: -------------------------------------------------------------------------------- 1 | //! Tabular data display example. 2 | //! 3 | //! Accepts one command-line argument which is used as the format string for 4 | //! printing a small set of tabular rows. If no argument is provided, a default 5 | //! is used. Demonstrates formatting according to user input. 6 | 7 | #[macro_use] 8 | extern crate runtime_fmt; 9 | 10 | fn main() { 11 | let format_spec = match std::env::args().nth(1) { 12 | Some(arg) => arg, 13 | None => "| {id:<5} | {name:<20} | {city:<20} |".into(), 14 | }; 15 | 16 | if let Err(e) = rt_println!(format_spec, id="ID", name="NAME", city="CITY") { 17 | println!("error in header: {}", e); 18 | if let runtime_fmt::Error::BadSyntax(_) = e { return } 19 | } 20 | for row in rows() { 21 | if let Err(e) = rt_println!(format_spec, id=row.id, name=row.name, city=row.city) { 22 | println!("error: {}", e); 23 | return; 24 | } 25 | } 26 | } 27 | 28 | struct Row { 29 | id: u64, 30 | name: &'static str, 31 | city: &'static str, 32 | } 33 | 34 | fn rows() -> Vec { 35 | vec![ 36 | Row { id: 1, name: "Bort", city: "Neotokyo" }, 37 | Row { id: 2, name: "Xyzzyx", city: "Twisty Passageville" }, 38 | Row { id: 3, name: "Yoshikage Kira", city: "Morioh" }, 39 | Row { id: 4, name: "M.", city: "Vaporwave" }, 40 | ] 41 | } 42 | 43 | -------------------------------------------------------------------------------- /runtime-fmt-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime-fmt-derive" 3 | version = "0.2.0" 4 | authors = ["Tad Hardesty "] 5 | 6 | description = "Runtime-based string formatting, custom derive plugin" 7 | license = "MIT/Apache-2.0" 8 | keywords = ["format", "fmt", "runtime", "string", "formatting"] 9 | 10 | repository = "https://github.com/SpaceManiac/runtime-fmt" 11 | documentation = "https://docs.rs/runtime-fmt-derive" 12 | 13 | [dependencies] 14 | quote = "0.3.15" 15 | syn = "0.11.11" 16 | 17 | [lib] 18 | proc-macro = true 19 | -------------------------------------------------------------------------------- /runtime-fmt-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A custom-derive implementation for the `FormatArgs` trait. 2 | #![recursion_limit="128"] 3 | 4 | extern crate proc_macro; 5 | extern crate syn; 6 | #[macro_use] extern crate quote; 7 | 8 | use proc_macro::TokenStream; 9 | 10 | /// Derive a `FormatArgs` implementation for the provided input struct. 11 | #[proc_macro_derive(FormatArgs)] 12 | pub fn derive_format_args(input: TokenStream) -> TokenStream { 13 | let string = input.to_string(); 14 | let ast = syn::parse_derive_input(&string).unwrap(); 15 | implement(&ast).parse().unwrap() 16 | } 17 | 18 | fn implement(ast: &syn::DeriveInput) -> quote::Tokens { 19 | // The rough structure of this (dummy_ident, extern crate/use) is based on 20 | // how serde_derive does it. 21 | 22 | let ident = &ast.ident; 23 | let variant = match ast.body { 24 | syn::Body::Struct(ref variant) => variant, 25 | _ => panic!("#[derive(FormatArgs)] is not implemented for enums") 26 | }; 27 | 28 | let dummy_ident = syn::Ident::new(format!("_IMPL_FORMAT_ARGS_FOR_{}", ident)); 29 | 30 | let (validate_name, validate_index, get_child, as_usize); 31 | match *variant { 32 | syn::VariantData::Struct(ref fields) => { 33 | get_child = build_fields(fields); 34 | as_usize = build_usize(ast, fields); 35 | validate_index = quote! { false }; 36 | 37 | let index = 0..fields.len(); 38 | let ident: Vec<_> = fields.iter() 39 | .map(|field| field.ident.as_ref().unwrap()) 40 | .map(ToString::to_string) 41 | .collect(); 42 | validate_name = quote! { 43 | match name { 44 | #(#ident => _Option::Some(#index),)* 45 | _ => _Option::None, 46 | } 47 | }; 48 | } 49 | syn::VariantData::Tuple(ref fields) => { 50 | get_child = build_fields(fields); 51 | as_usize = build_usize(ast, fields); 52 | validate_name = quote! { _Option::None }; 53 | 54 | let len = fields.len(); 55 | validate_index = quote! { index < #len }; 56 | } 57 | syn::VariantData::Unit => { 58 | validate_name = quote! { _Option::None }; 59 | validate_index = quote! { false }; 60 | get_child = quote! { panic!("bad index {}", index) }; 61 | as_usize = get_child.clone(); 62 | } 63 | }; 64 | 65 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 66 | quote! { 67 | #[allow(non_upper_case_globals, unused_attributes)] 68 | #[allow(unused_variables, unused_qualifications)] 69 | const #dummy_ident: () = { 70 | extern crate runtime_fmt as _runtime_fmt; 71 | use std::fmt::{Formatter as _Formatter, Result as _Result}; 72 | use std::option::Option as _Option; 73 | #[automatically_derived] 74 | impl #impl_generics _runtime_fmt::FormatArgs for #ident #ty_generics #where_clause { 75 | fn validate_name(name: &str) -> _Option { 76 | #validate_name 77 | } 78 | fn validate_index(index: usize) -> bool { 79 | #validate_index 80 | } 81 | fn get_child<__F>(index: usize) -> _Option _Result> 82 | where __F: _runtime_fmt::codegen::FormatTrait + ?Sized 83 | { 84 | #get_child 85 | } 86 | fn as_usize(index: usize) -> Option &usize> { 87 | #as_usize 88 | } 89 | } 90 | }; 91 | } 92 | } 93 | 94 | fn build_fields(fields: &[syn::Field]) -> quote::Tokens { 95 | let index = 0..fields.len(); 96 | let ty: Vec<_> = fields.iter().map(|field| &field.ty).collect(); 97 | let ident: Vec<_> = fields.iter().enumerate().map(|(idx, field)| match field.ident { 98 | Some(ref ident) => ident.clone(), 99 | None => syn::Ident::from(idx), 100 | }).collect(); 101 | quote! { 102 | match index { 103 | #( 104 | #index => _runtime_fmt::codegen::combine::<__F, Self, #ty, _>( 105 | |this| &this.#ident 106 | ), 107 | )* 108 | _ => panic!("bad index {}", index) 109 | } 110 | } 111 | } 112 | 113 | fn build_usize(ast: &syn::DeriveInput, fields: &[syn::Field]) -> quote::Tokens { 114 | let self_ = &ast.ident; 115 | let (_, ty_generics, where_clause) = ast.generics.split_for_impl(); 116 | 117 | // To avoid causing trouble with lifetime elision rules, an explicit 118 | // lifetime for the input and output is used. 119 | let lifetime = syn::Ident::new("'__as_usize_inner"); 120 | let mut generics2 = ast.generics.clone(); 121 | generics2.lifetimes.insert(0, syn::LifetimeDef { 122 | attrs: vec![], 123 | lifetime: syn::Lifetime { ident: lifetime.clone() }, 124 | bounds: vec![], 125 | }); 126 | let (impl_generics, _, _) = generics2.split_for_impl(); 127 | 128 | let mut result = quote::Tokens::new(); 129 | for (idx, field) in fields.iter().enumerate() { 130 | let ident = match field.ident { 131 | Some(ref ident) => ident.clone(), 132 | None => syn::Ident::from(idx), 133 | }; 134 | let ty = &field.ty; 135 | result.append(quote! { 136 | #idx => { 137 | fn inner #impl_generics (this: &#lifetime #self_ #ty_generics) 138 | -> &#lifetime #ty 139 | #where_clause { &this.#ident } 140 | _runtime_fmt::codegen::as_usize(inner) 141 | }, 142 | }); 143 | } 144 | 145 | quote! { 146 | match index { 147 | #result 148 | _ => panic!("bad index {}", index) 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/codegen.rs: -------------------------------------------------------------------------------- 1 | //! Support for the codegen module. 2 | #![doc(hidden)] 3 | 4 | use std::mem::{size_of, zeroed}; 5 | use std::fmt::*; 6 | 7 | /// Implementors correspond to formatting traits which may apply to values. 8 | pub trait FormatTrait { 9 | /// Return whether this format trait is applicable to a type. 10 | #[inline] 11 | fn allowed() -> bool; 12 | /// Format a value of the given trait using this format trait. 13 | /// Must panic if `allowed::()` is false. 14 | #[inline] 15 | fn perform(t: &T, f: &mut Formatter) -> Result; 16 | } 17 | 18 | // Abuse specialization to provide the `FormatTrait` impl for the actual 19 | // format traits without requiring HKT or other deep chicanery. 20 | trait Specialized { 21 | #[inline] 22 | fn allowed() -> bool; 23 | #[inline] 24 | fn perform(t: &T, f: &mut Formatter) -> Result; 25 | } 26 | 27 | macro_rules! impl_format_trait { 28 | ($($name:ident,)*) => { 29 | $( 30 | impl Specialized for dyn $name { 31 | #[inline] 32 | default fn allowed() -> bool { false } 33 | #[inline] 34 | default fn perform(_: &T, _: &mut Formatter) -> Result { 35 | panic!() 36 | } 37 | } 38 | 39 | impl Specialized for dyn $name { 40 | #[inline] 41 | fn allowed() -> bool { true } 42 | #[inline] 43 | fn perform(t: &T, f: &mut Formatter) -> Result { 44 | t.fmt(f) 45 | } 46 | } 47 | 48 | impl FormatTrait for dyn $name { 49 | #[inline] 50 | fn allowed() -> bool { >::allowed() } 51 | #[inline] 52 | fn perform(t: &T, f: &mut Formatter) -> Result { 53 | >::perform(t, f) 54 | } 55 | } 56 | )* 57 | } 58 | } 59 | 60 | impl_format_trait! { 61 | Display, Debug, LowerExp, UpperExp, Octal, Pointer, Binary, LowerHex, 62 | UpperHex, 63 | } 64 | 65 | #[inline] 66 | fn get_formatter() 67 | -> Option Result> 68 | { 69 | if F::allowed::() { 70 | Some(F::perform::) 71 | } else { 72 | None 73 | } 74 | } 75 | 76 | #[inline] 77 | // The combined function which will be returned by `make_combined`. 78 | fn combined(a: &A, f: &mut Formatter) -> Result 79 | where LHS: Fn(&A) -> &B, RHS: Fn(&B, &mut Formatter) -> Result 80 | { 81 | let lhs = unsafe { zeroed::() }; 82 | let rhs = unsafe { zeroed::() }; 83 | rhs(lhs(a), f) 84 | } 85 | 86 | // Local type alias for the formatting function pointer type. 87 | type FormatFn = fn(&T, &mut Formatter) -> Result; 88 | 89 | // Accepts dummy arguments to allow type parameter inference, and returns 90 | // `combined` instantiated with those arguments. 91 | #[inline] 92 | fn make_combined(_: LHS, _: RHS) -> FormatFn 93 | where LHS: Fn(&A) -> &B, RHS: Fn(&B, &mut Formatter) -> Result 94 | { 95 | // check that both function 96 | assert!(size_of::() == 0, 97 | "Mapper from parent to child must be zero-sized, instead size was {}", 98 | size_of::()); 99 | assert!(size_of::() == 0, 100 | "Formatting function must be zero-sized, instead size was {}", 101 | size_of::()); 102 | combined:: 103 | } 104 | 105 | /// Combine a function from `&A` to `&B` and a formatting trait applicable to 106 | /// `B` and return a function pointer which will convert a `&A` to `&B` and 107 | /// then format it with the given trait. 108 | /// 109 | /// Returns `None` if the formatting trait is not applicable to `B`. 110 | /// Panics if `func` is not zero-sized. 111 | #[inline] 112 | pub fn combine(func: Func) 113 | -> Option> 114 | where F: FormatTrait + ?Sized, Func: Fn(&A) -> &B 115 | { 116 | // Combines `get_formatter` and `make_combined` in one. 117 | get_formatter::().map(|r| make_combined(func, r)) 118 | } 119 | 120 | // Specialization abuse to select only functions which return `&usize`. 121 | trait SpecUsize { 122 | #[inline] 123 | fn convert(f: fn(&T) -> &Self) -> Option &usize>; 124 | } 125 | 126 | impl SpecUsize for U { 127 | #[inline] 128 | default fn convert(_: fn(&T) -> &Self) -> Option &usize> { None } 129 | } 130 | 131 | impl SpecUsize for usize { 132 | #[inline] 133 | fn convert(f: fn(&T) -> &usize) -> Option &usize> { Some(f) } 134 | } 135 | 136 | /// Attempt to convert a function from `&A` to `&B` to a function from `&A` 137 | /// to `&usize`. Returns `Some` only when `B` is `usize`. 138 | #[inline] 139 | pub fn as_usize(f: fn(&A) -> &B) -> Option &usize> { 140 | ::convert::(f) 141 | } 142 | 143 | /// A trait for types against which formatting specifiers may be pre-checked. 144 | /// 145 | /// Implementations may be generated automatically using `runtime-fmt-derive` 146 | /// and `#[derive(FormatArgs)]`. 147 | pub trait FormatArgs { 148 | /// Find the index within this type corresponding to the provided name. 149 | /// 150 | /// If this function returns `Some`, `get_child` with the returned index 151 | /// must not panic. 152 | fn validate_name(name: &str) -> Option; 153 | 154 | /// Validate that a given index is within range for this type. 155 | /// 156 | /// If this function returns `true`, `get_child` with the given index must 157 | /// not panic. 158 | fn validate_index(index: usize) -> bool; 159 | 160 | /// Return the formatter function for the given format trait, accepting 161 | /// `&Self` and using the given format trait on the value at that index. 162 | /// 163 | /// Returns `None` if the given format trait cannot format the child at 164 | /// that index. Panics if the index is invalid. 165 | fn get_child(index: usize) -> Option>; 166 | 167 | /// Return the value at the given index interpreted as a `usize`. 168 | /// 169 | /// Returns `None` if the child at the given index cannot be interpreted 170 | /// as a `usize`. Panics if the index is invalid. 171 | fn as_usize(index: usize) -> Option &usize>; 172 | } 173 | -------------------------------------------------------------------------------- /src/erase.rs: -------------------------------------------------------------------------------- 1 | //! Type erasure for formattable types. 2 | use std::fmt; 3 | use std::convert::TryFrom; 4 | use Error; 5 | 6 | type Func = fn(&T, &mut fmt::Formatter) -> fmt::Result; 7 | 8 | trait AsUsize { 9 | fn as_usize(&self) -> Option; 10 | } 11 | impl AsUsize for T { 12 | #[inline] 13 | default fn as_usize(&self) -> Option { None } 14 | } 15 | impl AsUsize for T where usize: TryFrom, T: Copy { 16 | #[inline] 17 | fn as_usize(&self) -> Option { 18 | usize::try_from(*self).ok() 19 | } 20 | } 21 | 22 | macro_rules! traits { 23 | ($($string:pat, $upper:ident, $lower:ident;)*) => { 24 | $( 25 | trait $upper { 26 | fn $lower() -> Option>; 27 | } 28 | impl $upper for T { 29 | #[inline] 30 | default fn $lower() -> Option> { None } 31 | } 32 | impl $upper for T { 33 | #[inline] 34 | fn $lower() -> Option> { 35 | Some(::fmt) 36 | } 37 | } 38 | )* 39 | 40 | pub trait Format { 41 | fn as_usize(&self) -> Option; 42 | fn by_name<'n>(&self, name: &'n str, idx: usize) -> Result>; 43 | } 44 | 45 | impl Format for T { 46 | #[inline] 47 | fn as_usize(&self) -> Option { 48 | AsUsize::as_usize(self) 49 | } 50 | fn by_name<'n>(&self, name: &'n str, idx: usize) -> Result> { 51 | match name { 52 | $( 53 | $string => match ::$lower() { 54 | Some(f) => Ok(fmt::ArgumentV1::new(self, f)), 55 | None => Err(Error::UnsatisfiedFormat { 56 | idx: idx, 57 | must_implement: stringify!($upper), 58 | }), 59 | }, 60 | )* 61 | _ => Err(Error::NoSuchFormat(name)), 62 | } 63 | } 64 | } 65 | 66 | pub fn codegen_get_child<'n, T: ::FormatArgs>(name: &'n str, idx: usize) 67 | -> Result fmt::Result, Error> 68 | { 69 | match name { 70 | $( 71 | $string => match T::get_child::(idx) { 72 | Some(f) => Ok(f), 73 | None => Err(Error::UnsatisfiedFormat { 74 | idx: idx, 75 | must_implement: stringify!($upper), 76 | }) 77 | }, 78 | )* 79 | _ => Err(Error::NoSuchFormat(name)), 80 | } 81 | } 82 | } 83 | } 84 | 85 | traits! { 86 | "", Display, display; 87 | "?", Debug, debug; 88 | "e", LowerExp, lower_exp; 89 | "E", UpperExp, upper_exp; 90 | "o", Octal, octal; 91 | "p", Pointer, pointer; 92 | "b", Binary, binary; 93 | "x", LowerHex, lower_hex; 94 | "X", UpperHex, upper_hex; 95 | } 96 | -------------------------------------------------------------------------------- /src/fmt_macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Macro support for format strings 12 | //! 13 | //! These structures are used when parsing format strings for the compiler. 14 | //! Parsing does not happen at runtime: structures of `std::fmt::rt` are 15 | //! generated instead. 16 | 17 | //#![unstable(feature = "rustc_private", issue = "27812")] 18 | //#![crate_type = "rlib"] 19 | //#![crate_type = "dylib"] 20 | /*#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", 21 | html_favicon_url = "https://doc.rust-lang.org/favicon.ico", 22 | html_root_url = "https://doc.rust-lang.org/nightly/", 23 | html_playground_url = "https://play.rust-lang.org/", 24 | test(attr(deny(warnings))))]*/ 25 | #![deny(warnings)] 26 | 27 | extern crate unicode_xid; 28 | 29 | pub use self::Piece::*; 30 | pub use self::Position::*; 31 | pub use self::Alignment::*; 32 | pub use self::Flag::*; 33 | pub use self::Count::*; 34 | 35 | use std::str; 36 | use std::string; 37 | use std::iter; 38 | 39 | use self::unicode_xid::UnicodeXID; 40 | 41 | /// A piece is a portion of the format string which represents the next part 42 | /// to emit. These are emitted as a stream by the `Parser` class. 43 | #[derive(Copy, Clone, PartialEq)] 44 | pub enum Piece<'a> { 45 | /// A literal string which should directly be emitted 46 | String(&'a str), 47 | /// This describes that formatting should process the next argument (as 48 | /// specified inside) for emission. 49 | NextArgument(Argument<'a>), 50 | } 51 | 52 | /// Representation of an argument specification. 53 | #[derive(Copy, Clone, PartialEq)] 54 | pub struct Argument<'a> { 55 | /// Where to find this argument 56 | pub position: Position<'a>, 57 | /// How to format the argument 58 | pub format: FormatSpec<'a>, 59 | } 60 | 61 | /// Specification for the formatting of an argument in the format string. 62 | #[derive(Copy, Clone, PartialEq)] 63 | pub struct FormatSpec<'a> { 64 | /// Optionally specified character to fill alignment with 65 | pub fill: Option, 66 | /// Optionally specified alignment 67 | pub align: Alignment, 68 | /// Packed version of various flags provided 69 | pub flags: u32, 70 | /// The integer precision to use 71 | pub precision: Count<'a>, 72 | /// The string width requested for the resulting format 73 | pub width: Count<'a>, 74 | /// The descriptor string representing the name of the format desired for 75 | /// this argument, this can be empty or any number of characters, although 76 | /// it is required to be one word. 77 | pub ty: &'a str, 78 | } 79 | 80 | /// Enum describing where an argument for a format can be located. 81 | #[derive(Copy, Clone, PartialEq)] 82 | pub enum Position<'a> { 83 | /// The argument is located at a specific index. 84 | ArgumentIs(usize), 85 | /// The argument has a name. 86 | ArgumentNamed(&'a str), 87 | } 88 | 89 | /// Enum of alignments which are supported. 90 | #[derive(Copy, Clone, PartialEq)] 91 | pub enum Alignment { 92 | /// The value will be aligned to the left. 93 | AlignLeft, 94 | /// The value will be aligned to the right. 95 | AlignRight, 96 | /// The value will be aligned in the center. 97 | AlignCenter, 98 | /// The value will take on a default alignment. 99 | AlignUnknown, 100 | } 101 | 102 | /// Various flags which can be applied to format strings. The meaning of these 103 | /// flags is defined by the formatters themselves. 104 | #[derive(Copy, Clone, PartialEq)] 105 | pub enum Flag { 106 | /// A `+` will be used to denote positive numbers. 107 | FlagSignPlus, 108 | /// A `-` will be used to denote negative numbers. This is the default. 109 | FlagSignMinus, 110 | /// An alternate form will be used for the value. In the case of numbers, 111 | /// this means that the number will be prefixed with the supplied string. 112 | FlagAlternate, 113 | /// For numbers, this means that the number will be padded with zeroes, 114 | /// and the sign (`+` or `-`) will precede them. 115 | FlagSignAwareZeroPad, 116 | } 117 | 118 | /// A count is used for the precision and width parameters of an integer, and 119 | /// can reference either an argument or a literal integer. 120 | #[derive(Copy, Clone, PartialEq)] 121 | pub enum Count<'a> { 122 | /// The count is specified explicitly. 123 | CountIs(usize), 124 | /// The count is specified by the argument with the given name. 125 | CountIsName(&'a str), 126 | /// The count is specified by the argument at the given index. 127 | CountIsParam(usize), 128 | /// The count is implied and cannot be explicitly specified. 129 | CountImplied, 130 | } 131 | 132 | /// The parser structure for interpreting the input format string. This is 133 | /// modeled as an iterator over `Piece` structures to form a stream of tokens 134 | /// being output. 135 | /// 136 | /// This is a recursive-descent parser for the sake of simplicity, and if 137 | /// necessary there's probably lots of room for improvement performance-wise. 138 | pub struct Parser<'a> { 139 | input: &'a str, 140 | cur: iter::Peekable>, 141 | /// Error messages accumulated during parsing 142 | pub errors: Vec<(string::String, Option)>, 143 | /// Current position of implicit positional argument pointer 144 | curarg: usize, 145 | } 146 | 147 | impl<'a> Iterator for Parser<'a> { 148 | type Item = Piece<'a>; 149 | 150 | fn next(&mut self) -> Option> { 151 | if let Some(&(pos, c)) = self.cur.peek() { 152 | match c { 153 | '{' => { 154 | self.cur.next(); 155 | if self.consume('{') { 156 | Some(String(self.string(pos + 1))) 157 | } else { 158 | let ret = Some(NextArgument(self.argument())); 159 | self.must_consume('}'); 160 | ret 161 | } 162 | } 163 | '}' => { 164 | self.cur.next(); 165 | if self.consume('}') { 166 | Some(String(self.string(pos + 1))) 167 | } else { 168 | self.err_with_note("unmatched `}` found", 169 | "if you intended to print `}`, \ 170 | you can escape it using `}}`"); 171 | None 172 | } 173 | } 174 | _ => Some(String(self.string(pos))), 175 | } 176 | } else { 177 | None 178 | } 179 | } 180 | } 181 | 182 | impl<'a> Parser<'a> { 183 | /// Creates a new parser for the given format string 184 | pub fn new(s: &'a str) -> Parser<'a> { 185 | Parser { 186 | input: s, 187 | cur: s.char_indices().peekable(), 188 | errors: vec![], 189 | curarg: 0, 190 | } 191 | } 192 | 193 | /// Notifies of an error. The message doesn't actually need to be of type 194 | /// String, but I think it does when this eventually uses conditions so it 195 | /// might as well start using it now. 196 | fn err(&mut self, msg: &str) { 197 | self.errors.push((msg.to_owned(), None)); 198 | } 199 | 200 | /// Notifies of an error. The message doesn't actually need to be of type 201 | /// String, but I think it does when this eventually uses conditions so it 202 | /// might as well start using it now. 203 | fn err_with_note(&mut self, msg: &str, note: &str) { 204 | self.errors.push((msg.to_owned(), Some(note.to_owned()))); 205 | } 206 | 207 | /// Optionally consumes the specified character. If the character is not at 208 | /// the current position, then the current iterator isn't moved and false is 209 | /// returned, otherwise the character is consumed and true is returned. 210 | fn consume(&mut self, c: char) -> bool { 211 | if let Some(&(_, maybe)) = self.cur.peek() { 212 | if c == maybe { 213 | self.cur.next(); 214 | true 215 | } else { 216 | false 217 | } 218 | } else { 219 | false 220 | } 221 | } 222 | 223 | /// Forces consumption of the specified character. If the character is not 224 | /// found, an error is emitted. 225 | fn must_consume(&mut self, c: char) { 226 | self.ws(); 227 | if let Some(&(_, maybe)) = self.cur.peek() { 228 | if c == maybe { 229 | self.cur.next(); 230 | } else { 231 | self.err(&format!("expected `{:?}`, found `{:?}`", c, maybe)); 232 | } 233 | } else { 234 | let msg = &format!("expected `{:?}` but string was terminated", c); 235 | if c == '}' { 236 | self.err_with_note(msg, 237 | "if you intended to print `{`, you can escape it using `{{`"); 238 | } else { 239 | self.err(msg); 240 | } 241 | } 242 | } 243 | 244 | /// Consumes all whitespace characters until the first non-whitespace 245 | /// character 246 | fn ws(&mut self) { 247 | while let Some(&(_, c)) = self.cur.peek() { 248 | if c.is_whitespace() { 249 | self.cur.next(); 250 | } else { 251 | break; 252 | } 253 | } 254 | } 255 | 256 | /// Parses all of a string which is to be considered a "raw literal" in a 257 | /// format string. This is everything outside of the braces. 258 | fn string(&mut self, start: usize) -> &'a str { 259 | // we may not consume the character, peek the iterator 260 | while let Some(&(pos, c)) = self.cur.peek() { 261 | match c { 262 | '{' | '}' => { 263 | return &self.input[start..pos]; 264 | } 265 | _ => { 266 | self.cur.next(); 267 | } 268 | } 269 | } 270 | &self.input[start..self.input.len()] 271 | } 272 | 273 | /// Parses an Argument structure, or what's contained within braces inside 274 | /// the format string 275 | fn argument(&mut self) -> Argument<'a> { 276 | let pos = self.position(); 277 | let format = self.format(); 278 | 279 | // Resolve position after parsing format spec. 280 | let pos = match pos { 281 | Some(position) => position, 282 | None => { 283 | let i = self.curarg; 284 | self.curarg += 1; 285 | ArgumentIs(i) 286 | } 287 | }; 288 | 289 | Argument { 290 | position: pos, 291 | format: format, 292 | } 293 | } 294 | 295 | /// Parses a positional argument for a format. This could either be an 296 | /// integer index of an argument, a named argument, or a blank string. 297 | /// Returns `Some(parsed_position)` if the position is not implicitly 298 | /// consuming a macro argument, `None` if it's the case. 299 | fn position(&mut self) -> Option> { 300 | if let Some(i) = self.integer() { 301 | Some(ArgumentIs(i)) 302 | } else { 303 | match self.cur.peek() { 304 | Some(&(_, c)) if c.is_alphabetic() => Some(ArgumentNamed(self.word())), 305 | 306 | // This is an `ArgumentNext`. 307 | // Record the fact and do the resolution after parsing the 308 | // format spec, to make things like `{:.*}` work. 309 | _ => None, 310 | } 311 | } 312 | } 313 | 314 | /// Parses a format specifier at the current position, returning all of the 315 | /// relevant information in the FormatSpec struct. 316 | fn format(&mut self) -> FormatSpec<'a> { 317 | let mut spec = FormatSpec { 318 | fill: None, 319 | align: AlignUnknown, 320 | flags: 0, 321 | precision: CountImplied, 322 | width: CountImplied, 323 | ty: &self.input[..0], 324 | }; 325 | if !self.consume(':') { 326 | return spec; 327 | } 328 | 329 | // fill character 330 | if let Some(&(_, c)) = self.cur.peek() { 331 | match self.cur.clone().skip(1).next() { 332 | Some((_, '>')) | Some((_, '<')) | Some((_, '^')) => { 333 | spec.fill = Some(c); 334 | self.cur.next(); 335 | } 336 | _ => {} 337 | } 338 | } 339 | // Alignment 340 | if self.consume('<') { 341 | spec.align = AlignLeft; 342 | } else if self.consume('>') { 343 | spec.align = AlignRight; 344 | } else if self.consume('^') { 345 | spec.align = AlignCenter; 346 | } 347 | // Sign flags 348 | if self.consume('+') { 349 | spec.flags |= 1 << (FlagSignPlus as u32); 350 | } else if self.consume('-') { 351 | spec.flags |= 1 << (FlagSignMinus as u32); 352 | } 353 | // Alternate marker 354 | if self.consume('#') { 355 | spec.flags |= 1 << (FlagAlternate as u32); 356 | } 357 | // Width and precision 358 | let mut havewidth = false; 359 | if self.consume('0') { 360 | // small ambiguity with '0$' as a format string. In theory this is a 361 | // '0' flag and then an ill-formatted format string with just a '$' 362 | // and no count, but this is better if we instead interpret this as 363 | // no '0' flag and '0$' as the width instead. 364 | if self.consume('$') { 365 | spec.width = CountIsParam(0); 366 | havewidth = true; 367 | } else { 368 | spec.flags |= 1 << (FlagSignAwareZeroPad as u32); 369 | } 370 | } 371 | if !havewidth { 372 | spec.width = self.count(); 373 | } 374 | if self.consume('.') { 375 | if self.consume('*') { 376 | // Resolve `CountIsNextParam`. 377 | // We can do this immediately as `position` is resolved later. 378 | let i = self.curarg; 379 | self.curarg += 1; 380 | spec.precision = CountIsParam(i); 381 | } else { 382 | spec.precision = self.count(); 383 | } 384 | } 385 | // Finally the actual format specifier 386 | if self.consume('?') { 387 | spec.ty = "?"; 388 | } else { 389 | spec.ty = self.word(); 390 | } 391 | spec 392 | } 393 | 394 | /// Parses a Count parameter at the current position. This does not check 395 | /// for 'CountIsNextParam' because that is only used in precision, not 396 | /// width. 397 | fn count(&mut self) -> Count<'a> { 398 | if let Some(i) = self.integer() { 399 | if self.consume('$') { 400 | CountIsParam(i) 401 | } else { 402 | CountIs(i) 403 | } 404 | } else { 405 | let tmp = self.cur.clone(); 406 | let word = self.word(); 407 | if word.is_empty() { 408 | self.cur = tmp; 409 | CountImplied 410 | } else { 411 | if self.consume('$') { 412 | CountIsName(word) 413 | } else { 414 | self.cur = tmp; 415 | CountImplied 416 | } 417 | } 418 | } 419 | } 420 | 421 | /// Parses a word starting at the current position. A word is considered to 422 | /// be an alphabetic character followed by any number of alphanumeric 423 | /// characters. 424 | fn word(&mut self) -> &'a str { 425 | let start = match self.cur.peek() { 426 | Some(&(pos, c)) if UnicodeXID::is_xid_start(c) => { 427 | self.cur.next(); 428 | pos 429 | } 430 | _ => { 431 | return &self.input[..0]; 432 | } 433 | }; 434 | while let Some(&(pos, c)) = self.cur.peek() { 435 | if UnicodeXID::is_xid_continue(c) { 436 | self.cur.next(); 437 | } else { 438 | return &self.input[start..pos]; 439 | } 440 | } 441 | &self.input[start..self.input.len()] 442 | } 443 | 444 | /// Optionally parses an integer at the current position. This doesn't deal 445 | /// with overflow at all, it's just accumulating digits. 446 | fn integer(&mut self) -> Option { 447 | let mut cur = 0; 448 | let mut found = false; 449 | while let Some(&(_, c)) = self.cur.peek() { 450 | if let Some(i) = c.to_digit(10) { 451 | cur = cur * 10 + i as usize; 452 | found = true; 453 | self.cur.next(); 454 | } else { 455 | break; 456 | } 457 | } 458 | if found { 459 | Some(cur) 460 | } else { 461 | None 462 | } 463 | } 464 | } 465 | 466 | #[cfg(test)] 467 | mod tests { 468 | use super::*; 469 | 470 | fn same(fmt: &'static str, p: &[Piece<'static>]) { 471 | let parser = Parser::new(fmt); 472 | assert!(parser.collect::>>() == p); 473 | } 474 | 475 | fn fmtdflt() -> FormatSpec<'static> { 476 | return FormatSpec { 477 | fill: None, 478 | align: AlignUnknown, 479 | flags: 0, 480 | precision: CountImplied, 481 | width: CountImplied, 482 | ty: "", 483 | }; 484 | } 485 | 486 | fn musterr(s: &str) { 487 | let mut p = Parser::new(s); 488 | p.next(); 489 | assert!(!p.errors.is_empty()); 490 | } 491 | 492 | #[test] 493 | fn simple() { 494 | same("asdf", &[String("asdf")]); 495 | same("a{{b", &[String("a"), String("{b")]); 496 | same("a}}b", &[String("a"), String("}b")]); 497 | same("a}}", &[String("a"), String("}")]); 498 | same("}}", &[String("}")]); 499 | same("\\}}", &[String("\\"), String("}")]); 500 | } 501 | 502 | #[test] 503 | fn invalid01() { 504 | musterr("{") 505 | } 506 | #[test] 507 | fn invalid02() { 508 | musterr("}") 509 | } 510 | #[test] 511 | fn invalid04() { 512 | musterr("{3a}") 513 | } 514 | #[test] 515 | fn invalid05() { 516 | musterr("{:|}") 517 | } 518 | #[test] 519 | fn invalid06() { 520 | musterr("{:>>>}") 521 | } 522 | 523 | #[test] 524 | fn format_nothing() { 525 | same("{}", 526 | &[NextArgument(Argument { 527 | position: ArgumentIs(0), 528 | format: fmtdflt(), 529 | })]); 530 | } 531 | #[test] 532 | fn format_position() { 533 | same("{3}", 534 | &[NextArgument(Argument { 535 | position: ArgumentIs(3), 536 | format: fmtdflt(), 537 | })]); 538 | } 539 | #[test] 540 | fn format_position_nothing_else() { 541 | same("{3:}", 542 | &[NextArgument(Argument { 543 | position: ArgumentIs(3), 544 | format: fmtdflt(), 545 | })]); 546 | } 547 | #[test] 548 | fn format_type() { 549 | same("{3:a}", 550 | &[NextArgument(Argument { 551 | position: ArgumentIs(3), 552 | format: FormatSpec { 553 | fill: None, 554 | align: AlignUnknown, 555 | flags: 0, 556 | precision: CountImplied, 557 | width: CountImplied, 558 | ty: "a", 559 | }, 560 | })]); 561 | } 562 | #[test] 563 | fn format_align_fill() { 564 | same("{3:>}", 565 | &[NextArgument(Argument { 566 | position: ArgumentIs(3), 567 | format: FormatSpec { 568 | fill: None, 569 | align: AlignRight, 570 | flags: 0, 571 | precision: CountImplied, 572 | width: CountImplied, 573 | ty: "", 574 | }, 575 | })]); 576 | same("{3:0<}", 577 | &[NextArgument(Argument { 578 | position: ArgumentIs(3), 579 | format: FormatSpec { 580 | fill: Some('0'), 581 | align: AlignLeft, 582 | flags: 0, 583 | precision: CountImplied, 584 | width: CountImplied, 585 | ty: "", 586 | }, 587 | })]); 588 | same("{3:* { 48 | /// Invalid format string syntax. 49 | BadSyntax(Vec<(String, Option)>), 50 | /// A format specifier referred to an out-of-range index. 51 | BadIndex(usize), 52 | /// A format specifier referred to a non-existent name. 53 | BadName(&'a str), 54 | /// A format specifier referred to a non-existent type. 55 | NoSuchFormat(&'a str), 56 | /// A format specifier's type was not satisfied by its argument. 57 | UnsatisfiedFormat { 58 | idx: usize, 59 | must_implement: &'static str, 60 | }, 61 | /// A parameter was of a type not suitable for use as a count. 62 | BadCount(usize), 63 | /// An I/O error from an `rt_write!` or `rt_writeln!` call. 64 | Io(std::io::Error), 65 | /// A formatting error from an `rt_write!` or `rt_writeln!` call. 66 | Fmt(std::fmt::Error), 67 | } 68 | 69 | impl<'a> From for Error<'a> { 70 | fn from(e: std::io::Error) -> Self { 71 | Error::Io(e) 72 | } 73 | } 74 | 75 | impl<'a> From for Error<'a> { 76 | fn from(e: std::fmt::Error) -> Self { 77 | Error::Fmt(e) 78 | } 79 | } 80 | 81 | impl<'a> std::error::Error for Error<'a> { 82 | fn description(&self) -> &str { 83 | match *self { 84 | Error::BadSyntax(_) => "bad syntax", 85 | Error::BadIndex(_) => "out-of-range index", 86 | Error::BadName(_) => "unknown name", 87 | Error::NoSuchFormat(_) => "bad formatting specifier", 88 | Error::UnsatisfiedFormat{..} => "formatting trait not satisfied", 89 | Error::BadCount(_) => "non-integer used as count", 90 | Error::Io(ref e) => e.description(), 91 | Error::Fmt(ref f) => f.description(), 92 | } 93 | } 94 | fn cause(&self) -> Option<&dyn std::error::Error> { 95 | match *self { 96 | Error::Io(ref e) => Some(e), 97 | Error::Fmt(ref e) => Some(e), 98 | _ => None, 99 | } 100 | } 101 | } 102 | 103 | impl<'a> fmt::Display for Error<'a> { 104 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 105 | match *self { 106 | Error::BadIndex(i) => write!(fmt, "index {} out of range", i), 107 | Error::BadName(n) => write!(fmt, "unknown name {:?}", n), 108 | Error::NoSuchFormat(c) => write!(fmt, "bad formatting specifier {:?}", c), 109 | Error::UnsatisfiedFormat { idx, must_implement } => 110 | write!(fmt, "argument {} does not implement {}", idx, must_implement), 111 | Error::BadCount(i) => write!(fmt, "argument {} cannot be used as a count", i), 112 | Error::Io(ref e) => e.fmt(fmt), 113 | Error::Fmt(ref e) => e.fmt(fmt), 114 | Error::BadSyntax(ref errors) => { 115 | for (i, err) in errors.iter().enumerate() { 116 | if i > 0 { 117 | fmt.write_str("; ")?; 118 | } 119 | fmt.write_str(&err.0)?; 120 | if let Some(ref more) = err.1 { 121 | write!(fmt, " ({})", more)?; 122 | } 123 | } 124 | Ok(()) 125 | } 126 | } 127 | } 128 | } 129 | 130 | /// A type-erased parameter, with an optional name. 131 | pub struct Param<'a> { 132 | name: Option<&'static str>, 133 | value: &'a dyn erase::Format, 134 | as_usize: Option, 135 | } 136 | 137 | impl<'a> Param<'a> { 138 | /// Create a nameless parameter from the given value. 139 | pub fn normal(t: &'a T) -> Param<'a> { 140 | use erase::Format; 141 | Param { 142 | name: None, 143 | as_usize: t.as_usize(), 144 | value: t, 145 | } 146 | } 147 | 148 | /// Create a named parameter from the given value. 149 | pub fn named(name: &'static str, t: &'a T) -> Param<'a> { 150 | use erase::Format; 151 | Param { 152 | name: Some(name), 153 | as_usize: t.as_usize(), 154 | value: t, 155 | } 156 | } 157 | } 158 | 159 | enum PreparedArgument { 160 | Normal(fn(&T, &mut fmt::Formatter) -> fmt::Result), 161 | Usize(fn(&T) -> &usize), 162 | } 163 | impl Copy for PreparedArgument {} 164 | impl Clone for PreparedArgument { 165 | fn clone(&self) -> Self { *self } 166 | } 167 | 168 | /// A pre-checked format string, ready for values of a specific type to be 169 | /// formatted against it. 170 | pub struct PreparedFormat<'s, T: FormatArgs> { 171 | inner: Parsed<'s, DelayedParse>, 172 | } 173 | 174 | impl<'s, T: FormatArgs> PreparedFormat<'s, T> { 175 | /// Prepare a format string against a formattable type. 176 | /// 177 | /// Once the format string has been prepared, formatting individual values 178 | /// will not require checking the validity of the format string over again. 179 | #[inline] 180 | pub fn prepare(spec: &'s str) -> Result { 181 | parse(spec, &mut DelayedParse::(PhantomData)) 182 | .map(|result| PreparedFormat { inner: result }) 183 | } 184 | 185 | /// Append a linefeed (`\n`) to the end of this buffer. 186 | #[inline] 187 | pub fn newln(&mut self) -> &mut Self { 188 | self.inner.newln(); 189 | self 190 | } 191 | 192 | /// Call a function accepting `Arguments` with the contents of this buffer. 193 | pub fn with R, R>(&self, t: &T, f: F) -> R { 194 | let pieces = self.inner.pieces(); 195 | let args: Vec = self.inner.args.iter().map(|f| match *f { 196 | PreparedArgument::Normal(func) => ArgumentV1::new(t, func), 197 | PreparedArgument::Usize(func) => ArgumentV1::from_usize(func(t)), 198 | }).collect(); 199 | f(match self.inner.fmt { 200 | Some(ref fmt) => Arguments::new_v1_formatted(&pieces, &args, fmt), 201 | None => Arguments::new_v1(&pieces, &args), 202 | }) 203 | } 204 | 205 | /// Format the given value to a `String`. 206 | #[inline] 207 | pub fn format(&self, t: &T) -> String { 208 | self.with(t, ::std::fmt::format) 209 | } 210 | 211 | /// Print the given value to standard output. 212 | #[inline] 213 | pub fn print(&self, t: &T) { 214 | self.with(t, _print) 215 | } 216 | 217 | /// Write the given value to an `io::Write`. 218 | #[inline] 219 | pub fn write_io(&self, t: &T, dest: &mut W) -> io::Result<()> { 220 | self.with(t, |args| dest.write_fmt(args)) 221 | } 222 | 223 | /// Write the given value to a `fmt::Write`. 224 | #[inline] 225 | pub fn write_fmt(&self, t: &T, dest: &mut W) -> fmt::Result { 226 | self.with(t, |args| dest.write_fmt(args)) 227 | } 228 | } 229 | 230 | impl<'s, T: FormatArgs> Clone for PreparedFormat<'s, T> { 231 | fn clone(&self) -> Self { 232 | PreparedFormat { inner: self.inner.clone() } 233 | } 234 | fn clone_from(&mut self, source: &Self) { 235 | self.inner.clone_from(&source.inner) 236 | } 237 | } 238 | 239 | /// A buffer representing a parsed format string and arguments. 240 | #[derive(Clone)] 241 | pub struct FormatBuf<'s> { 242 | inner: Parsed<'s, ImmediateParse<'s>>, 243 | } 244 | 245 | impl<'s> FormatBuf<'s> { 246 | /// Construct a new buffer from the given format string and arguments. 247 | /// 248 | /// This method should usually not be called directly. Instead use the 249 | /// `rt_format_args!` macro. 250 | #[inline] 251 | pub fn new(spec: &'s str, params: &'s [Param<'s>]) -> Result> { 252 | parse(spec, &mut ImmediateParse(params)) 253 | .map(|result| FormatBuf { inner: result }) 254 | } 255 | 256 | /// Append a linefeed (`\n`) to the end of this buffer. 257 | #[inline] 258 | pub fn newln(&mut self) -> &mut Self { 259 | self.inner.newln(); 260 | self 261 | } 262 | 263 | /// Call a function accepting `Arguments` with the contents of this buffer. 264 | pub fn with R, R>(&self, f: F) -> R { 265 | let pieces = self.inner.pieces(); 266 | f(match self.inner.fmt { 267 | Some(ref fmt) => Arguments::new_v1_formatted(&pieces, &self.inner.args, fmt), 268 | None => Arguments::new_v1(&pieces, &self.inner.args), 269 | }) 270 | } 271 | 272 | /// Format this buffer to a `String`. 273 | #[inline] 274 | pub fn format(&self) -> String { 275 | self.with(::std::fmt::format) 276 | } 277 | 278 | /// Print this buffer to standard output. 279 | #[inline] 280 | pub fn print(&self) { 281 | self.with(_print) 282 | } 283 | 284 | /// Write this buffer to an `io::Write`. 285 | #[inline] 286 | pub fn write_io(&self, dest: &mut W) -> io::Result<()> { 287 | self.with(|args| dest.write_fmt(args)) 288 | } 289 | 290 | /// Write this buffer to a `fmt::Write`. 291 | #[inline] 292 | pub fn write_fmt(&self, dest: &mut W) -> fmt::Result { 293 | self.with(|args| dest.write_fmt(args)) 294 | } 295 | } 296 | 297 | impl<'a> fmt::Display for FormatBuf<'a> { 298 | #[inline] 299 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 300 | self.with(|args| fmt.write_fmt(args)) 301 | } 302 | } 303 | 304 | impl<'a> fmt::Debug for FormatBuf<'a> { 305 | #[inline] 306 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 307 | fmt::Display::fmt(self, fmt) 308 | } 309 | } 310 | 311 | trait ParseTarget<'p> { 312 | type Argument; 313 | fn validate_name(&mut self, name: &str) -> Option; 314 | fn validate_index(&mut self, index: usize) -> bool; 315 | fn format<'s>(&mut self, spec: &'s str, idx: usize) -> Result>; 316 | fn format_usize(&mut self, idx: usize) -> Option; 317 | } 318 | 319 | struct ImmediateParse<'p>(&'p [Param<'p>]); 320 | 321 | impl<'p> ParseTarget<'p> for ImmediateParse<'p> { 322 | type Argument = ArgumentV1<'p>; 323 | 324 | fn validate_name(&mut self, name: &str) -> Option { 325 | self.0.iter().position(|p| p.name.map_or(false, |n| n == name)) 326 | } 327 | 328 | fn validate_index(&mut self, index: usize) -> bool { 329 | index < self.0.len() 330 | } 331 | 332 | fn format<'s>(&mut self, spec: &'s str, idx: usize) -> Result> { 333 | self.0[idx].value.by_name(spec, idx) 334 | } 335 | 336 | fn format_usize(&mut self, idx: usize) -> Option { 337 | self.0[idx].as_usize.as_ref().map(ArgumentV1::from_usize) 338 | } 339 | } 340 | 341 | struct DelayedParse(PhantomData); 342 | 343 | impl<'p, T: FormatArgs> ParseTarget<'p> for DelayedParse { 344 | type Argument = PreparedArgument; 345 | 346 | fn validate_name(&mut self, name: &str) -> Option { 347 | T::validate_name(name) 348 | } 349 | 350 | fn validate_index(&mut self, index: usize) -> bool { 351 | T::validate_index(index) 352 | } 353 | 354 | fn format<'s>(&mut self, spec: &'s str, idx: usize) -> Result> { 355 | erase::codegen_get_child::(spec, idx).map(PreparedArgument::Normal) 356 | } 357 | 358 | fn format_usize(&mut self, idx: usize) -> Option { 359 | T::as_usize(idx).map(PreparedArgument::Usize) 360 | } 361 | } 362 | 363 | struct Parsed<'s, P: ParseTarget<'s>> { 364 | pieces: Vec>, 365 | args: Vec, 366 | fmt: Option>, 367 | } 368 | 369 | impl<'s, P: ParseTarget<'s>> Clone for Parsed<'s, P> 370 | where P::Argument: Clone 371 | { 372 | fn clone(&self) -> Self { 373 | Parsed { 374 | pieces: self.pieces.clone(), 375 | args: self.args.clone(), 376 | fmt: self.fmt.clone(), 377 | } 378 | } 379 | 380 | fn clone_from(&mut self, source: &Self) { 381 | self.pieces.clone_from(&source.pieces); 382 | self.args.clone_from(&source.args); 383 | self.fmt.clone_from(&source.fmt); 384 | } 385 | } 386 | 387 | impl<'s, P: ParseTarget<'s>> Parsed<'s, P> { 388 | fn newln(&mut self) { 389 | // If fmt is None, the number of implicit formatting specifiers 390 | // is the same as the number of arguments. 391 | let len = self.fmt.as_ref().map_or(self.args.len(), |fmt| fmt.len()); 392 | if self.pieces.len() > len { 393 | // The final piece is after the final formatting specifier, so 394 | // it's okay to just add to the end of it. 395 | self.pieces.last_mut().unwrap().to_mut().push_str("\n") 396 | } else { 397 | // The final piece is before the final formatting specifier, so 398 | // a new piece needs to be added at the end. 399 | self.pieces.push("\n".into()) 400 | } 401 | } 402 | 403 | #[inline] 404 | fn pieces(&self) -> Vec<&str> { 405 | self.pieces.iter().map(|r| &**r).collect() 406 | } 407 | } 408 | 409 | fn parse<'s, P: ParseTarget<'s>>(spec: &'s str, target: &mut P) 410 | -> Result, Error<'s>> 411 | { 412 | let mut parser = fmt_macros::Parser::new(spec); 413 | let result = inner_parse(&mut parser, target); 414 | // Perform a separate check so that syntax errors take priority. 415 | if parser.errors.is_empty() { 416 | result 417 | } else { 418 | Err(Error::BadSyntax(parser.errors)) 419 | } 420 | } 421 | 422 | fn inner_parse<'s, P>(parser: &mut fmt_macros::Parser<'s>, target: &mut P) 423 | -> Result, Error<'s>> 424 | where P: ParseTarget<'s> 425 | { 426 | use fmt_macros as p; 427 | 428 | const DEFAULT_FILL: char = ' '; 429 | const DEFAULT_KEY: p::FormatSpec = p::FormatSpec { 430 | fill: None, 431 | align: p::AlignUnknown, 432 | flags: 0, 433 | precision: p::CountImplied, 434 | width: p::CountImplied, 435 | ty: "", 436 | }; 437 | const DEFAULT_VALUE: v1::FormatSpec = v1::FormatSpec { 438 | fill: DEFAULT_FILL, 439 | align: v1::Alignment::Unknown, 440 | flags: 0, 441 | precision: v1::Count::Implied, 442 | width: v1::Count::Implied, 443 | }; 444 | 445 | let mut pieces = Vec::new(); 446 | let mut args = Vec::new(); 447 | let mut fmt = None; 448 | let mut fmt_len = 0; 449 | 450 | let mut str_accum: Cow = "".into(); 451 | while let Some(piece) = parser.next() { 452 | match piece { 453 | p::Piece::String(text) => { 454 | // append string to accumulator 455 | if str_accum.is_empty() { 456 | str_accum = text.into(); 457 | } else if !text.is_empty() { 458 | str_accum.to_mut().push_str(text); 459 | } 460 | } 461 | p::Piece::NextArgument(arg) => { 462 | let mut push_arg = |arg| { 463 | // TODO: if this arg already appears in `args`, don't push 464 | // it another time, reuse the previous index. 465 | let len = args.len(); 466 | args.push(arg); 467 | len 468 | }; 469 | 470 | // flush accumulator always 471 | pieces.push(std::mem::replace(&mut str_accum, "".into())); 472 | 473 | // convert the argument 474 | let idx = match arg.position { 475 | p::Position::ArgumentIs(idx) => { 476 | if !target.validate_index(idx) { 477 | return Err(Error::BadIndex(idx)) 478 | } 479 | idx 480 | } 481 | p::Position::ArgumentNamed(name) => { 482 | match target.validate_name(name) { 483 | Some(idx) => idx, 484 | None => return Err(Error::BadName(name)) 485 | } 486 | } 487 | }; 488 | let argument_pos = push_arg(target.format(arg.format.ty, idx)?); 489 | 490 | // convert the format spec 491 | let mut convert_count = |c| -> Result> { 492 | Ok(match c { 493 | p::CountIs(val) => v1::Count::Is(val), 494 | p::CountIsName(name) => { 495 | let idx = match target.validate_name(name) { 496 | Some(idx) => idx, 497 | None => return Err(Error::BadName(name)) 498 | }; 499 | v1::Count::Param(push_arg(match target.format_usize(idx) { 500 | Some(arg) => arg, 501 | None => return Err(Error::BadCount(idx)) 502 | })) 503 | } 504 | p::CountIsParam(idx) => { 505 | if !target.validate_index(idx) { 506 | return Err(Error::BadIndex(idx)) 507 | } 508 | v1::Count::Param(push_arg(match target.format_usize(idx) { 509 | Some(arg) => arg, 510 | None => return Err(Error::BadCount(idx)) 511 | })) 512 | }, 513 | p::CountImplied => v1::Count::Implied, 514 | }) 515 | }; 516 | 517 | // If specs were implicit but this is non-default, fill in the 518 | // previously-implicit values. 519 | if fmt.is_none() && (arg.format != DEFAULT_KEY || argument_pos != fmt_len) { 520 | fmt = Some((0..fmt_len).map(|i| v1::Argument { 521 | position: v1::Position::At(i), 522 | format: DEFAULT_VALUE, 523 | }).collect::>()); 524 | } 525 | 526 | // If specs are currently explicit, push this spec. 527 | if let Some(fmt) = fmt.as_mut() { 528 | let spec = v1::FormatSpec { 529 | fill: arg.format.fill.unwrap_or(DEFAULT_FILL), 530 | flags: arg.format.flags, 531 | align: match arg.format.align { 532 | p::AlignLeft => v1::Alignment::Left, 533 | p::AlignRight => v1::Alignment::Right, 534 | p::AlignCenter => v1::Alignment::Center, 535 | p::AlignUnknown => v1::Alignment::Unknown, 536 | }, 537 | precision: convert_count(arg.format.precision)?, 538 | width: convert_count(arg.format.width)?, 539 | }; 540 | 541 | // push the format spec and argument value 542 | fmt.push(v1::Argument { 543 | position: v1::Position::At(argument_pos), 544 | format: spec, 545 | }) 546 | } 547 | 548 | fmt_len += 1; 549 | } 550 | } 551 | } 552 | // flush accumulator if needed 553 | if !str_accum.is_empty() { 554 | pieces.push(str_accum); 555 | } 556 | 557 | Ok(Parsed { 558 | pieces: pieces, 559 | args: args, 560 | fmt: fmt, 561 | }) 562 | } 563 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | //! `rt_*` macros 2 | //! 3 | //! The central macro is `rt_format_args!`, analogous to `format_args!`. 4 | //! The rest of the macros correspond to the other `std` formatting macros. 5 | 6 | /// The core macro for runtime formatting. 7 | /// 8 | /// This macro produces a value of type `Result`. Invalid 9 | /// format strings are indicated by an error result. The resulting value can 10 | /// be converted to a `std::fmt::Arguments` via the `with()` method. 11 | /// 12 | /// The syntax accepted is the same as `format_args!`. See the module-level 13 | /// docs for more detail. 14 | #[macro_export] 15 | macro_rules! rt_format_args { 16 | (@[$spec:expr] [$($args:tt)*] $name:tt = $e:expr, $($rest:tt)*) => { 17 | rt_format_args!(@[$spec] [$($args)* $crate::Param::named(stringify!($name), &$e),] $($rest)*) 18 | }; 19 | (@[$spec:expr] [$($args:tt)*] $name:tt = $e:expr) => { 20 | rt_format_args!(@[$spec] [$($args)* $crate::Param::named(stringify!($name), &$e),]) 21 | }; 22 | (@[$spec:expr] [$($args:tt)*] $e:expr, $($rest:tt)*) => { 23 | rt_format_args!(@[$spec] [$($args)* $crate::Param::normal(&$e),] $($rest)*) 24 | }; 25 | (@[$spec:expr] [$($args:tt)*] $e:expr) => { 26 | rt_format_args!(@[$spec] [$($args)* $crate::Param::normal(&$e),]) 27 | }; 28 | (@[$spec:expr] [$($args:tt)*]) => { 29 | $crate::FormatBuf::new(&$spec, &[$($args)*]) 30 | }; 31 | ($spec:expr, $($rest:tt)*) => { 32 | rt_format_args!(@[$spec] [] $($rest)*) 33 | }; 34 | ($spec:expr) => { 35 | $crate::FormatBuf::new(&$spec, &[]) 36 | }; 37 | } 38 | 39 | /// Format a value of type `String` with a runtime format string. 40 | /// 41 | /// The format string should be any type coercible to an `&str`, and will not 42 | /// be consumed. 43 | /// 44 | /// Returns a `Result`. See the module-level docs for more 45 | /// information. 46 | #[macro_export] 47 | macro_rules! rt_format { 48 | ($($rest:tt)*) => { 49 | rt_format_args!($($rest)*).map(|x| x.with(::std::fmt::format)) 50 | } 51 | } 52 | 53 | /// Print to standard output with a runtime format string. 54 | /// 55 | /// Returns a `Result<(), Error>`. Panics if writing to stdout fails. See the 56 | /// module-level docs for more information. 57 | #[macro_export] 58 | macro_rules! rt_print { 59 | ($($rest:tt)*) => { 60 | rt_format_args!($($rest)*).map(|x| x.with($crate::_print)) 61 | } 62 | } 63 | 64 | /// Print to standard output with a runtime format string and trailing newline. 65 | /// 66 | /// Returns a `Result<(), Error>`. Panics if writing to stdout fails. See the 67 | /// module-level docs for more information. 68 | #[macro_export] 69 | macro_rules! rt_println { 70 | ($($rest:tt)*) => { 71 | rt_format_args!($($rest)*).map(|mut x| x.newln().with($crate::_print)) 72 | } 73 | } 74 | 75 | /// Write runtime-formatted data into a buffer. 76 | /// 77 | /// Like `write!`, implementations of either `std::fmt::Write` or 78 | /// `std::io::Write` are accepted. `Error` variants of the appropriate type may 79 | /// be returned. 80 | /// 81 | /// Returns a `Result<(), Error>`. See the module-level docs for more 82 | /// information. 83 | #[macro_export] 84 | macro_rules! rt_write { 85 | ($dest:expr, $($rest:tt)*) => { 86 | rt_format_args!($($rest)*).and_then(|x| 87 | x.with(|args| $dest.write_fmt(args)).map_err(::std::convert::From::from) 88 | ) 89 | } 90 | } 91 | 92 | /// Write runtime-formatted data into a buffer with a trailing newline. 93 | /// 94 | /// Like `writeln!`, implementations of either `std::fmt::Write` or 95 | /// `std::io::Write` are accepted. `Error` variants of the appropriate type may 96 | /// be returned. 97 | /// 98 | /// Returns a `Result<(), Error>`. See the module-level docs for more 99 | /// information. 100 | #[macro_export] 101 | macro_rules! rt_writeln { 102 | ($dest:expr, $($rest:tt)*) => { 103 | rt_format_args!($($rest)*).and_then(|mut x| 104 | x.newln().with(|args| $dest.write_fmt(args)).map_err(::std::convert::From::from) 105 | ) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/bad.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate runtime_fmt; 2 | 3 | use runtime_fmt::Error::*; 4 | 5 | macro_rules! err_with { 6 | ($err:expr; $($rest:tt)*) => { 7 | assert_eq!( 8 | format!("Err({:?})", $err), 9 | format!("{:?}", rt_format!($($rest)*)) 10 | ) 11 | } 12 | } 13 | 14 | macro_rules! err_any { 15 | ($($rest:tt)*) => { 16 | assert!(rt_format!($($rest)*).is_err()) 17 | } 18 | } 19 | 20 | #[test] 21 | fn bad_index() { 22 | err_with!(BadIndex(0); "{}"); 23 | err_with!(BadIndex(7); "{7}"); 24 | err_with!(BadIndex(2); "{} {} {}", "", ""); 25 | } 26 | 27 | #[test] 28 | fn bad_usize() { 29 | err_with!(BadCount(0); "{:.*}", "Not A Usize", "aaaa"); 30 | } 31 | 32 | #[test] 33 | fn bad_syntax() { 34 | err_any!("{-1}"); 35 | } 36 | 37 | #[test] 38 | fn bad_format() { 39 | struct Foo; 40 | 41 | err_with!(NoSuchFormat("q"); "{:q}", ""); 42 | err_with!(UnsatisfiedFormat { idx: 0, must_implement: "Debug" }; 43 | "{:?}", Foo); 44 | } 45 | -------------------------------------------------------------------------------- /tests/equivalence.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate runtime_fmt; 2 | 3 | macro_rules! case { 4 | ($($rest:tt)*) => {{ 5 | let string = format!($($rest)*); 6 | println!("{}", string); 7 | assert_eq!( 8 | string, 9 | rt_format!($($rest)*).unwrap() 10 | ) 11 | }} 12 | } 13 | 14 | #[test] 15 | fn simple_equivalence() { 16 | case!("Literal string!"); 17 | case!("Hello, {}", "world"); 18 | case!("Hello, {}!", "world"); 19 | case!("2 + 2 = {}", 2 + 2); 20 | case!("{0:?} {0}", "A \\ B"); 21 | case!("{} {x}", x="Foo"); 22 | case!("{x} {} {}", "Foo", x="Bar"); 23 | case!("{x} {x} {}", "Foo", x="Bar"); 24 | case!("{x} {} {x}", "Foo", x="Bar"); 25 | case!("{} {x} {}", "Foo", x="Bar"); 26 | case!("{} {x} {x}", "Foo", x="Bar"); 27 | case!("{} {} {x}", "Foo", x="Bar"); 28 | case!("{:x}", 0x3feebb77); 29 | case!("{:X}", 0x3feebb77); 30 | case!("Hex: {:.>4x}", 17); 31 | case!("{:p}", "Hello"); 32 | case!("{}{}{}", "(A)", "_ _", "(B)"); 33 | } -------------------------------------------------------------------------------- /tests/flexible.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate runtime_fmt; 2 | 3 | macro_rules! t { 4 | ($be:expr; $($rest:tt)*) => {{ 5 | assert_eq!( 6 | $be, 7 | rt_format!($($rest)*).unwrap() 8 | ) 9 | }} 10 | } 11 | 12 | #[test] 13 | fn non_usize_pad() { 14 | t!("aaaa"; "{:.*}", 4u8, "aaaaaaaa"); 15 | t!("aaaa"; "{:.*}", 4u16, "aaaaaaaa"); 16 | t!("aaaa"; "{:.*}", 4u32, "aaaaaaaa"); 17 | t!("aaaa"; "{:.*}", 4u64, "aaaaaaaa"); 18 | } 19 | 20 | #[test] 21 | fn formatted_format_str() { 22 | let format = format!("{}{}{}", "Hello, ", "{}", "!"); 23 | // nb: `format` is not `&`d here, so this tests that the macro does it 24 | // generally, anything Deref should be usable 25 | t!("Hello, world!"; format, "world"); 26 | drop(format); 27 | } 28 | 29 | #[test] 30 | fn unused_is_fine() { 31 | t!("1"; rt_format!("{a}", a=1, b=2).unwrap()); 32 | t!("2"; rt_format!("{b}", a=1, b=2).unwrap()); 33 | t!("3 1"; rt_format!("{} {a}", 3, 4, a=1, b=2).unwrap()); 34 | } 35 | -------------------------------------------------------------------------------- /tests/ifmt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | #![deny(warnings)] 12 | #![allow(unused_must_use)] 13 | #![allow(unused_features)] 14 | #![feature(box_syntax)] 15 | 16 | // modified here to replace standard macros with this crate's macros 17 | // modified below to add #[test] and comment out calling inner testcases 18 | // begin modifications 19 | #[macro_use] extern crate runtime_fmt; 20 | macro_rules! format { 21 | ($($rest:tt)*) => { rt_format!($($rest)*).unwrap() } 22 | } 23 | macro_rules! print { 24 | ($($rest:tt)*) => { rt_print!($($rest)*).unwrap() } 25 | } 26 | macro_rules! println { 27 | ($($rest:tt)*) => { rt_println!($($rest)*).unwrap() } 28 | } 29 | macro_rules! write { 30 | ($($rest:tt)*) => { rt_write!($($rest)*).unwrap() } 31 | } 32 | macro_rules! writeln { 33 | ($($rest:tt)*) => { rt_writeln!($($rest)*).unwrap() } 34 | } 35 | // end modifications 36 | 37 | use std::cell::RefCell; 38 | use std::fmt::{self, Write}; 39 | use std::usize; 40 | 41 | struct A; 42 | struct B; 43 | struct C; 44 | struct D; 45 | 46 | impl fmt::LowerHex for A { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | f.write_str("aloha") 49 | } 50 | } 51 | impl fmt::UpperHex for B { 52 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 53 | f.write_str("adios") 54 | } 55 | } 56 | impl fmt::Display for C { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | f.pad_integral(true, "☃", "123") 59 | } 60 | } 61 | impl fmt::Binary for D { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | f.write_str("aa")?; 64 | f.write_char('☃')?; 65 | f.write_str("bb") 66 | } 67 | } 68 | 69 | macro_rules! t { 70 | ($a:expr, $b:expr) => { assert_eq!($a, $b) } 71 | } 72 | 73 | #[test] 74 | pub fn main() { 75 | // Various edge cases without formats 76 | t!(format!(""), ""); 77 | t!(format!("hello"), "hello"); 78 | t!(format!("hello {{"), "hello {"); 79 | 80 | // default formatters should work 81 | t!(format!("{}", 1.0f32), "1"); 82 | t!(format!("{}", 1.0f64), "1"); 83 | t!(format!("{}", "a"), "a"); 84 | t!(format!("{}", "a".to_string()), "a"); 85 | t!(format!("{}", false), "false"); 86 | t!(format!("{}", 'a'), "a"); 87 | 88 | // At least exercise all the formats 89 | t!(format!("{}", true), "true"); 90 | t!(format!("{}", '☃'), "☃"); 91 | t!(format!("{}", 10), "10"); 92 | t!(format!("{}", 10_usize), "10"); 93 | t!(format!("{:?}", '☃'), "'☃'"); 94 | t!(format!("{:?}", 10), "10"); 95 | t!(format!("{:?}", 10_usize), "10"); 96 | t!(format!("{:?}", "true"), "\"true\""); 97 | t!(format!("{:?}", "foo\nbar"), "\"foo\\nbar\""); 98 | t!(format!("{:?}", "foo\n\"bar\"\r\n\'baz\'\t\\qux\\"), 99 | r#""foo\n\"bar\"\r\n\'baz\'\t\\qux\\""#); 100 | t!(format!("{:?}", "foo\0bar\x01baz\u{7f}q\u{75}x"), 101 | r#""foo\u{0}bar\u{1}baz\u{7f}qux""#); 102 | t!(format!("{:o}", 10_usize), "12"); 103 | t!(format!("{:x}", 10_usize), "a"); 104 | t!(format!("{:X}", 10_usize), "A"); 105 | t!(format!("{}", "foo"), "foo"); 106 | t!(format!("{}", "foo".to_string()), "foo"); 107 | if cfg!(target_pointer_width = "32") { 108 | t!(format!("{:#p}", 0x1234 as *const isize), "0x00001234"); 109 | t!(format!("{:#p}", 0x1234 as *mut isize), "0x00001234"); 110 | } else { 111 | t!(format!("{:#p}", 0x1234 as *const isize), "0x0000000000001234"); 112 | t!(format!("{:#p}", 0x1234 as *mut isize), "0x0000000000001234"); 113 | } 114 | t!(format!("{:p}", 0x1234 as *const isize), "0x1234"); 115 | t!(format!("{:p}", 0x1234 as *mut isize), "0x1234"); 116 | t!(format!("{:x}", A), "aloha"); 117 | t!(format!("{:X}", B), "adios"); 118 | t!(format!("foo {} ☃☃☃☃☃☃", "bar"), "foo bar ☃☃☃☃☃☃"); 119 | t!(format!("{1} {0}", 0, 1), "1 0"); 120 | t!(format!("{foo} {bar}", foo=0, bar=1), "0 1"); 121 | t!(format!("{foo} {1} {bar} {0}", 0, 1, foo=2, bar=3), "2 1 3 0"); 122 | t!(format!("{} {0}", "a"), "a a"); 123 | t!(format!("{foo_bar}", foo_bar=1), "1"); 124 | t!(format!("{}", 5 + 5), "10"); 125 | t!(format!("{:#4}", C), "☃123"); 126 | t!(format!("{:b}", D), "aa☃bb"); 127 | 128 | let a: &dyn fmt::Debug = &1; 129 | t!(format!("{:?}", a), "1"); 130 | 131 | 132 | // Formatting strings and their arguments 133 | t!(format!("{}", "a"), "a"); 134 | t!(format!("{:4}", "a"), "a "); 135 | t!(format!("{:4}", "☃"), "☃ "); 136 | t!(format!("{:>4}", "a"), " a"); 137 | t!(format!("{:<4}", "a"), "a "); 138 | t!(format!("{:^5}", "a"), " a "); 139 | t!(format!("{:^5}", "aa"), " aa "); 140 | t!(format!("{:^4}", "a"), " a "); 141 | t!(format!("{:^4}", "aa"), " aa "); 142 | t!(format!("{:.4}", "a"), "a"); 143 | t!(format!("{:4.4}", "a"), "a "); 144 | t!(format!("{:4.4}", "aaaaaaaaaaaaaaaaaa"), "aaaa"); 145 | t!(format!("{:<4.4}", "aaaaaaaaaaaaaaaaaa"), "aaaa"); 146 | t!(format!("{:>4.4}", "aaaaaaaaaaaaaaaaaa"), "aaaa"); 147 | t!(format!("{:^4.4}", "aaaaaaaaaaaaaaaaaa"), "aaaa"); 148 | t!(format!("{:>10.4}", "aaaaaaaaaaaaaaaaaa"), " aaaa"); 149 | t!(format!("{:2.4}", "aaaaa"), "aaaa"); 150 | t!(format!("{:2.4}", "aaaa"), "aaaa"); 151 | t!(format!("{:2.4}", "aaa"), "aaa"); 152 | t!(format!("{:2.4}", "aa"), "aa"); 153 | t!(format!("{:2.4}", "a"), "a "); 154 | t!(format!("{:0>2}", "a"), "0a"); 155 | t!(format!("{:.*}", 4, "aaaaaaaaaaaaaaaaaa"), "aaaa"); 156 | t!(format!("{:.1$}", "aaaaaaaaaaaaaaaaaa", 4), "aaaa"); 157 | t!(format!("{:.a$}", "aaaaaaaaaaaaaaaaaa", a=4), "aaaa"); 158 | t!(format!("{:1$}", "a", 4), "a "); 159 | t!(format!("{1:0$}", 4, "a"), "a "); 160 | t!(format!("{:a$}", "a", a=4), "a "); 161 | t!(format!("{:-#}", "a"), "a"); 162 | t!(format!("{:+#}", "a"), "a"); 163 | t!(format!("{:/^10.8}", "1234567890"), "/12345678/"); 164 | 165 | // Some float stuff 166 | t!(format!("{:}", 1.0f32), "1"); 167 | t!(format!("{:}", 1.0f64), "1"); 168 | t!(format!("{:.3}", 1.0f64), "1.000"); 169 | t!(format!("{:10.3}", 1.0f64), " 1.000"); 170 | t!(format!("{:+10.3}", 1.0f64), " +1.000"); 171 | t!(format!("{:+10.3}", -1.0f64), " -1.000"); 172 | 173 | t!(format!("{:e}", 1.2345e6f32), "1.2345e6"); 174 | t!(format!("{:e}", 1.2345e6f64), "1.2345e6"); 175 | t!(format!("{:E}", 1.2345e6f64), "1.2345E6"); 176 | t!(format!("{:.3e}", 1.2345e6f64), "1.234e6"); 177 | t!(format!("{:10.3e}", 1.2345e6f64), " 1.234e6"); 178 | t!(format!("{:+10.3e}", 1.2345e6f64), " +1.234e6"); 179 | t!(format!("{:+10.3e}", -1.2345e6f64), " -1.234e6"); 180 | 181 | // Float edge cases 182 | t!(format!("{}", -0.0), "0"); 183 | t!(format!("{:?}", -0.0), "-0.0"); 184 | t!(format!("{:?}", 0.0), "0.0"); 185 | 186 | // sign aware zero padding 187 | t!(format!("{:<3}", 1), "1 "); 188 | t!(format!("{:>3}", 1), " 1"); 189 | t!(format!("{:^3}", 1), " 1 "); 190 | t!(format!("{:03}", 1), "001"); 191 | t!(format!("{:<03}", 1), "001"); 192 | t!(format!("{:>03}", 1), "001"); 193 | t!(format!("{:^03}", 1), "001"); 194 | t!(format!("{:+03}", 1), "+01"); 195 | t!(format!("{:<+03}", 1), "+01"); 196 | t!(format!("{:>+03}", 1), "+01"); 197 | t!(format!("{:^+03}", 1), "+01"); 198 | t!(format!("{:#05x}", 1), "0x001"); 199 | t!(format!("{:<#05x}", 1), "0x001"); 200 | t!(format!("{:>#05x}", 1), "0x001"); 201 | t!(format!("{:^#05x}", 1), "0x001"); 202 | t!(format!("{:05}", 1.2), "001.2"); 203 | t!(format!("{:<05}", 1.2), "001.2"); 204 | t!(format!("{:>05}", 1.2), "001.2"); 205 | t!(format!("{:^05}", 1.2), "001.2"); 206 | t!(format!("{:05}", -1.2), "-01.2"); 207 | t!(format!("{:<05}", -1.2), "-01.2"); 208 | t!(format!("{:>05}", -1.2), "-01.2"); 209 | t!(format!("{:^05}", -1.2), "-01.2"); 210 | t!(format!("{:+05}", 1.2), "+01.2"); 211 | t!(format!("{:<+05}", 1.2), "+01.2"); 212 | t!(format!("{:>+05}", 1.2), "+01.2"); 213 | t!(format!("{:^+05}", 1.2), "+01.2"); 214 | 215 | // Ergonomic format_args! 216 | t!(format!("{0:x} {0:X}", 15), "f F"); 217 | t!(format!("{0:x} {0:X} {}", 15), "f F 15"); 218 | // NOTE: For now the longer test cases must not be followed immediately by 219 | // >1 empty lines, or the pretty printer will break. Since no one wants to 220 | // touch the current pretty printer (#751), we have no choice but to work 221 | // around it. Some of the following test cases are also affected. 222 | t!(format!("{:x}{0:X}{a:x}{:X}{1:x}{a:X}", 13, 14, a=15), "dDfEeF"); 223 | t!(format!("{a:x} {a:X}", a=15), "f F"); 224 | 225 | // And its edge cases 226 | t!(format!("{a:.0$} {b:.0$} {0:.0$}\n{a:.c$} {b:.c$} {c:.c$}", 227 | 4, a="abcdefg", b="hijklmn", c=3), 228 | "abcd hijk 4\nabc hij 3"); 229 | t!(format!("{a:.*} {0} {:.*}", 4, 3, "efgh", a="abcdef"), "abcd 4 efg"); 230 | t!(format!("{:.a$} {a} {a:#x}", "aaaaaa", a=2), "aa 2 0x2"); 231 | 232 | 233 | // Test that pointers don't get truncated. 234 | { 235 | let val = usize::MAX; 236 | let exp = format!("{:#x}", val); 237 | t!(format!("{:p}", val as *const isize), exp); 238 | } 239 | 240 | // Escaping 241 | t!(format!("{{"), "{"); 242 | t!(format!("}}"), "}"); 243 | 244 | //test_write(); 245 | //test_print(); 246 | //test_order(); 247 | //test_once(); 248 | 249 | // make sure that format! doesn't move out of local variables 250 | let a: Box<_> = box 3; 251 | format!("{}", a); 252 | format!("{}", a); 253 | 254 | // make sure that format! doesn't cause spurious unused-unsafe warnings when 255 | // it's inside of an outer unsafe block 256 | unsafe { 257 | let a: isize = ::std::mem::transmute(3_usize); 258 | format!("{}", a); 259 | } 260 | 261 | //test_format_args(); 262 | 263 | // test that trailing commas are acceptable 264 | format!("{}", "test",); 265 | format!("{foo}", foo="test",); 266 | 267 | //test_refcell(); 268 | } 269 | 270 | // Basic test to make sure that we can invoke the `write!` macro with an 271 | // fmt::Write instance. 272 | #[test] 273 | fn test_write() { 274 | let mut buf = String::new(); 275 | write!(&mut buf, "{}", 3); 276 | { 277 | let w = &mut buf; 278 | write!(w, "{foo}", foo=4); 279 | write!(w, "{}", "hello"); 280 | writeln!(w, "{}", "line"); 281 | writeln!(w, "{foo}", foo="bar"); 282 | w.write_char('☃'); 283 | w.write_str("str"); 284 | } 285 | 286 | t!(buf, "34helloline\nbar\n☃str"); 287 | } 288 | 289 | // Just make sure that the macros are defined, there's not really a lot that we 290 | // can do with them just yet (to test the output) 291 | #[test] 292 | fn test_print() { 293 | print!("hi"); 294 | print!("{:?}", vec![0u8]); 295 | println!("hello"); 296 | println!("this is a {}", "test"); 297 | println!("{foo}", foo="bar"); 298 | } 299 | 300 | // Just make sure that the macros are defined, there's not really a lot that we 301 | // can do with them just yet (to test the output) 302 | #[test] 303 | fn test_format_args() { 304 | let mut buf = String::new(); 305 | { 306 | let w = &mut buf; 307 | write!(w, "{}", rt_format_args!("{}", 1).unwrap()); 308 | write!(w, "{}", rt_format_args!("test").unwrap()); 309 | write!(w, "{}", rt_format_args!("{test}", test=3).unwrap()); 310 | } 311 | let s = buf; 312 | t!(s, "1test3"); 313 | 314 | let s = rt_format_args!("hello {}", "world").unwrap().with(fmt::format); 315 | t!(s, "hello world"); 316 | let s = format!("{}: {}", "args were", rt_format_args!("hello {}", "world").unwrap()); 317 | t!(s, "args were: hello world"); 318 | } 319 | 320 | #[test] 321 | fn test_order() { 322 | // Make sure format!() arguments are always evaluated in a left-to-right 323 | // ordering 324 | fn foo() -> isize { 325 | static mut FOO: isize = 0; 326 | unsafe { 327 | FOO += 1; 328 | FOO 329 | } 330 | } 331 | assert_eq!(format!("{} {} {a} {b} {} {c}", 332 | foo(), foo(), foo(), a=foo(), b=foo(), c=foo()), 333 | "1 2 4 5 3 6".to_string()); 334 | } 335 | 336 | #[test] 337 | fn test_once() { 338 | // Make sure each argument are evaluated only once even though it may be 339 | // formatted multiple times 340 | fn foo() -> isize { 341 | static mut FOO: isize = 0; 342 | unsafe { 343 | FOO += 1; 344 | FOO 345 | } 346 | } 347 | assert_eq!(format!("{0} {0} {0} {a} {a} {a}", foo(), a=foo()), 348 | "1 1 1 2 2 2".to_string()); 349 | } 350 | 351 | #[test] 352 | fn test_refcell() { 353 | let refcell = RefCell::new(5); 354 | assert_eq!(format!("{:?}", refcell), "RefCell { value: 5 }"); 355 | let borrow = refcell.borrow_mut(); 356 | assert_eq!(format!("{:?}", refcell), "RefCell { value: }"); 357 | drop(borrow); 358 | assert_eq!(format!("{:?}", refcell), "RefCell { value: 5 }"); 359 | } 360 | --------------------------------------------------------------------------------