├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── readme.rs ├── rust-toolchain ├── src ├── lib.rs └── proc_macro │ ├── Cargo.toml │ ├── macros.rs │ └── mod.rs └── tests └── format.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fstrings" 3 | version = "1.0.0" 4 | authors = ["Daniel Henry-Mantilla "] 5 | edition = "2018" 6 | 7 | license = "MIT" 8 | 9 | documentation = "https://docs.rs/fstrings" 10 | repository = "https://github.com/danielhenrymantilla/fstrings-rs.git" 11 | homepage = "https://crates.io/crates/fstrings" 12 | 13 | readme = "README.md" 14 | description = "Python3 fstring interpolation in Rust" 15 | keywords = ["python", "format", "human", "no_std", "interpolation"] 16 | categories = ["rust-patterns", ] 17 | 18 | [workspace] 19 | members = ["src/proc_macro"] 20 | 21 | [dependencies] 22 | proc-macro-hack = "0.5.19" 23 | 24 | [dependencies.fstrings-proc-macro] 25 | version = "1.0.0" 26 | path = "src/proc_macro" 27 | 28 | [features] 29 | default = [] 30 | 31 | nightly = [] 32 | verbose-expansions = ["fstrings-proc-macro/verbose-expansions", ] 33 | 34 | [package.metadata.docs.rs] 35 | features = ["nightly", ] 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Daniel Henry-Mantilla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `::fstrings` 2 | 3 | [![Repository](https://img.shields.io/badge/repository-GitHub-brightgreen.svg)](https://github.com/danielhenrymantilla/fstrings-rs) 4 | [![Latest version](https://img.shields.io/crates/v/fstrings.svg)](https://crates.io/crates/fstrings) 5 | [![Documentation](https://docs.rs/fstrings/badge.svg)](https://docs.rs/fstrings) 6 | [![MSRV](https://img.shields.io/badge/MSRV-1.36.0-white)]( 7 | https://gist.github.com/danielhenrymantilla/8e5b721b3929084562f8f65668920c33) 8 | [![License](https://img.shields.io/crates/l/fstrings.svg)](https://github.com/danielhenrymantilla/fstrings-rs/blob/master/LICENSE) 9 | 10 | - Note, this crate has been rendered obsolete as of 1.58.0, wherein 11 | `let x = 42; println!("{x}");` acts as `let x = 42; println_f!("{x}");` 12 | here. 13 | 14 | A precursor/pioneer crate, for sure, and one which now has been subsumed 15 | by [RFC 2795](https://github.com/rust-lang/rfcs/pull/2795), which 16 | successfully championed the ideas behind this crate! 🏆 17 | 18 | ## Basic fstring interpolation in Rust 19 | 20 | The interpolation works as follows: 21 | 22 | 1. if the (template) string literal contains a named parameter 23 | (_e.g._ `{name}`) 24 | 25 | 1. and no `name = value` argument is fed to the formatting call, 26 | 27 | 1. then an automatic `name = name` argument is added, so that the variable is 28 | effectively interpolated from the current scope. 29 | 30 | ### Example 31 | 32 | ```rust 33 | #[macro_use] 34 | extern crate fstrings; 35 | 36 | fn main () 37 | { 38 | let name = "World"; 39 | 40 | // Usage is simple: just append `_f` to the name of any formatting macro 41 | println_f!("Hello, {name}!"); 42 | 43 | assert_eq!( 44 | f!("Hello, {name}!"), // shorthand for String creation (Python-like) 45 | String::from("Hello, World!"), 46 | ); 47 | 48 | // ## Advanced cases: 49 | { 50 | // It remains compatible with classic formatting parameters 51 | assert_eq!( 52 | f!("{hi}, {name}!", hi = "Hello"), 53 | "Hello, World!", 54 | ); 55 | 56 | // You can override / shadow the named arguments 57 | assert_eq!( 58 | f!("Hello, {name}!", name = "Earth"), 59 | "Hello, Earth!", 60 | ); 61 | 62 | // You can use field access (but no method calls!) 63 | let foo = Foo { name }; /* where */ struct Foo { name: T } 64 | assert_eq!( 65 | f!("Hello, {foo.name}!"), 66 | "Hello, World!", 67 | ); 68 | 69 | // This also works with tuple indexing. 70 | let ft_and_name = (42, name); 71 | assert_eq!( 72 | f!("Hello, {ft_and_name.1}!"), 73 | "Hello, World!", 74 | ); 75 | 76 | // You can use fstrings to debug by appending a `=` after the 77 | // interpolated expression. 78 | let x = 0b_101010; 79 | assert_eq!( 80 | f!("In this context {x=}"), 81 | "In this context x = 42", 82 | ); 83 | } 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /examples/readme.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate fstrings; 3 | 4 | fn main () 5 | { 6 | let name = "World"; 7 | 8 | // Usage is simple: just append `_f` to the name of any formatting macro 9 | println_f!("Hello, {name}!"); 10 | 11 | assert_eq!( 12 | f!("Hello, {name}!"), // shorthand for String creation (Python-like) 13 | String::from("Hello, World!"), 14 | ); 15 | 16 | // ## Advanced cases: 17 | { 18 | // It remains compatible with classic formatting parameters 19 | assert_eq!( 20 | f!("{hi}, {name}!", hi = "Hello"), 21 | "Hello, World!", 22 | ); 23 | 24 | // You can override / shadow the named arguments 25 | assert_eq!( 26 | f!("Hello, {name}!", name = "Earth"), 27 | "Hello, Earth!", 28 | ); 29 | 30 | // You can use field access (but no method calls!) 31 | let foo = Foo { name }; /* where */ struct Foo { name: T } 32 | assert_eq!( 33 | f!("Hello, {foo.name}!"), 34 | "Hello, World!", 35 | ); 36 | 37 | // This also works with tuple indexing. 38 | let ft_and_name = (42, name); 39 | assert_eq!( 40 | f!("Hello, {ft_and_name.1}!"), 41 | "Hello, World!", 42 | ); 43 | 44 | // You can use fstrings to debug by appending a `=` after the 45 | // interpolated expression. 46 | let x = 0b_101010; 47 | assert_eq!( 48 | f!("In this context {x=}"), 49 | "In this context x = 42", 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.36.0 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "nightly", 2 | cfg_attr(all(), doc = include_str!("../README.md")), 3 | )] 4 | #![cfg_attr(not(feature = "nightly"), 5 | doc = "See [crates.io](https://crates.io/crates/fstrings)", 6 | )] 7 | #![cfg_attr(not(feature = "nightly"), 8 | doc = "for more info about this crate.", 9 | )] 10 | 11 | #![no_std] 12 | 13 | extern crate fstrings_proc_macro; 14 | 15 | mod doctest_readme { 16 | macro_rules! with_doc {( 17 | #[doc = $doc_string:expr] 18 | $item:item 19 | ) => ( 20 | #[doc = $doc_string] 21 | $item 22 | )} 23 | 24 | with_doc! { 25 | #[doc = include_str!("../README.md")] 26 | extern {} 27 | } 28 | } 29 | 30 | macro_rules! mk_macros {( @with_dollar![$dol:tt]=> 31 | $( 32 | #[doc = $doc_string:literal] 33 | $printlnf:ident 34 | => $println:ident!($($stream:ident,)? ...) 35 | , 36 | )* 37 | ) => ( 38 | $( 39 | #[doc = $doc_string] 40 | #[macro_export] 41 | macro_rules! $printlnf {( 42 | $($dol $stream : expr,)? $dol($dol args:tt)* 43 | ) => ( 44 | $println!($($dol $stream,)? "{}", $crate::format_args_f!($dol($dol args)*)) 45 | )} 46 | )* 47 | )} 48 | 49 | mk_macros! { @with_dollar![$]=> 50 | #[doc = "Like [`print!`](https://doc.rust-lang.org/std/macro.print.html), but with basic f-string interpolation."] 51 | print_f 52 | => print!(...) 53 | , 54 | #[doc = "Like [`println!`](https://doc.rust-lang.org/std/macro.println.html), but with basic f-string interpolation."] 55 | println_f 56 | => println!(...) 57 | , 58 | #[doc = "Like [`eprint!`](https://doc.rust-lang.org/std/macro.eprint.html), but with basic f-string interpolation."] 59 | eprint_f 60 | => eprint!(...) 61 | , 62 | #[doc = "Like [`eprintln!`](https://doc.rust-lang.org/std/macro.eprintln.html), but with basic f-string interpolation."] 63 | eprintln_f 64 | => eprintln!(...) 65 | , 66 | #[doc = "Like [`format!`](https://doc.rust-lang.org/std/macro.format.html), but with basic f-string interpolation."] 67 | format_f 68 | => format!(...) 69 | , 70 | #[doc = "Shorthand for [`format_f`]."] 71 | f 72 | => format!(...) 73 | , 74 | #[doc = "Like [`panic!`](https://doc.rust-lang.org/std/macro.panic.html), but with basic f-string interpolation."] 75 | panic_f 76 | => panic!(...) 77 | , 78 | #[doc = "Like [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html), but with basic f-string interpolation."] 79 | unreachable_f 80 | => unreachable!(...) 81 | , 82 | #[doc = "Like [`unimplemented!`](https://doc.rust-lang.org/std/macro.unimplemented.html), but with basic f-string interpolation."] 83 | unimplemented_f 84 | => unimplemented!(...) 85 | , 86 | #[doc = "Like [`write!`](https://doc.rust-lang.org/std/macro.write.html), but with basic f-string interpolation."] 87 | write_f 88 | => write!(stream, ...) 89 | , 90 | #[doc = "Like [`writeln!`](https://doc.rust-lang.org/std/macro.writeln.html), but with basic f-string interpolation."] 91 | writeln_f 92 | => writeln!(stream, ...) 93 | , 94 | #[doc = "Shorthand for [`error!(format_f!`]."] 95 | error_f 96 | =>error!(...) 97 | , 98 | #[doc = "Shorthand for [`warn!(format_f!`]."] 99 | warn_f 100 | => warn!(...) 101 | , 102 | #[doc = "Shorthand for [`info!(format_f!`]."] 103 | info_f 104 | => info!(...) 105 | , 106 | #[doc = "Shorthand for [`debug!(format_f!`]."] 107 | debug_f 108 | => debug!(...) 109 | , 110 | #[doc = "Shorthand for [`trace!(format_f!`]."] 111 | trace_f 112 | => trace!(...) 113 | , 114 | } 115 | 116 | /// Like [`format_args!`]( 117 | /// https://doc.rust-lang.org/std/macro.format_args.html), 118 | /// but with basic f-string interpolation. 119 | #[::proc_macro_hack::proc_macro_hack(fake_call_site)] 120 | pub use fstrings_proc_macro::format_args_f; 121 | -------------------------------------------------------------------------------- /src/proc_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [lib] 2 | proc-macro = true 3 | path = "mod.rs" 4 | 5 | [package] 6 | name = "fstrings-proc-macro" 7 | version = "1.0.0" 8 | authors = ["Daniel Henry-Mantilla "] 9 | edition = "2018" 10 | 11 | description = "Python3 fstring interpolation in Rust" 12 | license = "MIT" 13 | 14 | documentation = "https://docs.rs/fstrings" 15 | repository = "https://github.com/danielhenrymantilla/fstrings-rs" 16 | homepage = "https://crates.io/crates/fstrings" 17 | 18 | [dependencies] 19 | proc-macro2 = "1.0.*" 20 | proc-macro-hack = "0.5.11" 21 | quote = "1.0.*" 22 | syn = { version = "1.0.*", features = ["full"]} 23 | 24 | [features] 25 | verbose-expansions = ["syn/extra-traits", ] 26 | -------------------------------------------------------------------------------- /src/proc_macro/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "verbose-expansions"))] 2 | macro_rules! debug_input {($expr:expr) => ($expr)} 3 | 4 | #[cfg(feature = "verbose-expansions")] 5 | macro_rules! debug_input {($expr:expr) => ( 6 | match $expr { expr => { 7 | eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr); 8 | expr 9 | }} 10 | )} 11 | 12 | #[cfg(not(feature = "verbose-expansions"))] 13 | macro_rules! debug_output {($expr:expr) => ($expr)} 14 | 15 | #[cfg(feature = "verbose-expansions")] 16 | macro_rules! debug_output {($expr:expr) => ( 17 | match $expr { expr => { 18 | eprintln!("=>\n{}\n-------------------\n", expr); 19 | expr 20 | }} 21 | )} 22 | -------------------------------------------------------------------------------- /src/proc_macro/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use ::proc_macro::TokenStream; 3 | use ::quote::{ 4 | quote, 5 | ToTokens, 6 | }; 7 | use ::proc_macro2::{ 8 | TokenStream as TokenStream2, 9 | }; 10 | use ::syn::{*, 11 | parse::{ 12 | Parse, 13 | ParseStream, 14 | }, 15 | punctuated::Punctuated, 16 | }; 17 | use ::std::ops::Not; 18 | 19 | #[macro_use] 20 | mod macros; 21 | 22 | #[allow(dead_code)] // dumb compiler does not see the struct being used... 23 | struct Input { 24 | format_literal: LitStr, 25 | positional_args: Vec, 26 | named_args: Vec<(Ident, Expr)>, 27 | } 28 | 29 | impl Parse for Input { 30 | fn parse (input: ParseStream) -> Result 31 | { 32 | let format_literal = input.parse()?; 33 | let mut positional_args = vec![]; 34 | loop { 35 | if input.parse::>()?.is_none() { 36 | return Ok(Self { 37 | format_literal, 38 | positional_args, 39 | named_args: vec![], 40 | }); 41 | } 42 | if input.peek(Ident) && 43 | input.peek2(Token![=]) && 44 | input.peek3(Token![=]).not() 45 | { 46 | // Found a positional parameter 47 | break; 48 | } 49 | positional_args.push(input.parse()?); 50 | } 51 | let named_args = 52 | Punctuated::<_, Token![,]>::parse_terminated_with( 53 | input, 54 | |input| Ok({ 55 | let name: Ident = input.parse()?; 56 | let _: Token![=] = input.parse()?; 57 | let expr: Expr = input.parse()?; 58 | (name, expr) 59 | }), 60 | )? 61 | .into_iter() 62 | .collect() 63 | ; 64 | Ok(Self { 65 | format_literal, 66 | positional_args, 67 | named_args, 68 | }) 69 | } 70 | } 71 | 72 | #[::proc_macro_hack::proc_macro_hack] pub 73 | fn format_args_f (input: TokenStream) -> TokenStream 74 | { 75 | #[allow(unused)] 76 | const FUNCTION_NAME: &str = "format_args_f"; 77 | 78 | debug_input!(&input); 79 | 80 | let Input { 81 | mut format_literal, 82 | mut positional_args, 83 | mut named_args, 84 | } = parse_macro_input!(input); 85 | 86 | let s = format_literal.value(); 87 | let ref mut out_format_literal = String::with_capacity(s.len()); 88 | 89 | let mut iterator = s.char_indices().peekable(); 90 | while let Some((i, c)) = iterator.next() { 91 | out_format_literal.push(c); 92 | if c != '{' { 93 | continue; 94 | } 95 | // encountered `{`, let's see if it was `{{` 96 | if let Some(&(_, '{')) = iterator.peek() { 97 | let _ = iterator.next(); 98 | out_format_literal.push('{'); 99 | continue; 100 | } 101 | let (end, colon_or_closing_brace) = 102 | iterator 103 | .find(|&(_, c)| c == '}' || c == ':') 104 | .expect(concat!( 105 | "Invalid format string literal\n", 106 | "note: if you intended to print `{`, ", 107 | "you can escape it using `{{`", 108 | )) 109 | ; 110 | // We use defer to ensure all the `continue`s append the closing char. 111 | let mut out_format_literal = defer( 112 | &mut *out_format_literal, 113 | |it| it.push(colon_or_closing_brace), 114 | ); 115 | let out_format_literal: &mut String = &mut *out_format_literal; 116 | let mut arg = s[i + 1 .. end].trim(); 117 | if let Some("=") = arg.get(arg.len().saturating_sub(1) ..) { 118 | assert_eq!( 119 | out_format_literal.pop(), // Remove the opening brace 120 | Some('{'), 121 | ); 122 | arg = &arg[.. arg.len() - 1]; 123 | out_format_literal.push_str(arg); 124 | out_format_literal.push_str(" = {"); 125 | } 126 | if arg.is_empty() { 127 | continue; 128 | } 129 | 130 | enum Segment { Ident(Ident), LitInt(LitInt), Self_(Token![self]) } 131 | let segments: Vec = { 132 | impl Parse for Segment { 133 | fn parse (input: ParseStream<'_>) 134 | -> Result 135 | { 136 | let lookahead = input.lookahead1(); 137 | if lookahead.peek(Ident) { 138 | input.parse().map(Segment::Ident) 139 | } else if lookahead.peek(LitInt) { 140 | input.parse().map(Segment::LitInt) 141 | } else if input.peek(Token![self]){ 142 | input.parse().map(Segment::Self_) 143 | } else { 144 | Err(lookahead.error()) 145 | } 146 | } 147 | } 148 | match ::syn::parse::Parser::parse_str( 149 | Punctuated::::parse_separated_nonempty, 150 | arg, 151 | ) 152 | { 153 | | Ok(segments) => segments.into_iter().collect(), 154 | | Err(err) => return err.to_compile_error().into(), 155 | } 156 | }; 157 | match segments.len() { 158 | | 0 => unreachable!("`parse_separated_nonempty` returned empty"), 159 | | 1 => { 160 | out_format_literal.push_str(arg); 161 | match {segments}.pop().unwrap() { 162 | | Segment::LitInt(_) => { 163 | // found something like `{0}`, let `format_args!` 164 | // handle it. 165 | continue; 166 | }, 167 | | Segment::Ident(ident) => { 168 | // if `ident = ...` is not yet among the extra args 169 | if named_args 170 | .iter() 171 | .all(|(it, _)| *it != ident) 172 | { 173 | named_args.push(( 174 | ident.clone(), 175 | parse_quote!(#ident), // Expr 176 | )); 177 | } 178 | }, 179 | | Segment::Self_(_ident) => { 180 | continue; 181 | }, 182 | } 183 | }, 184 | | _ => { 185 | ::std::fmt::Write::write_fmt( 186 | out_format_literal, 187 | format_args!("{}", positional_args.len()), 188 | ).expect("`usize` or `char` Display impl cannot panic"); 189 | let segments: Punctuated = 190 | segments 191 | .into_iter() 192 | .map(|it| match it { 193 | | Segment::Ident(ident) => { 194 | ident.into_token_stream() 195 | }, 196 | | Segment::LitInt(literal) => { 197 | literal.into_token_stream() 198 | }, 199 | | Segment::Self_(self_) => { 200 | self_.into_token_stream() 201 | }, 202 | }) 203 | .collect() 204 | ; 205 | positional_args.push(parse_quote! { 206 | #segments 207 | }) 208 | } 209 | } 210 | } 211 | 212 | let named_args = 213 | named_args 214 | .into_iter() 215 | .map(|(ident, expr)| quote! { 216 | #ident = #expr 217 | }) 218 | ; 219 | format_literal = LitStr::new( 220 | out_format_literal, 221 | format_literal.span(), 222 | ); 223 | TokenStream::from(debug_output!(quote! { 224 | format_args!( 225 | #format_literal 226 | #(, #positional_args)* 227 | #(, #named_args)* 228 | ) 229 | })) 230 | } 231 | 232 | fn defer<'a, T : 'a, Drop : 'a> (x: T, drop: Drop) 233 | -> impl ::core::ops::DerefMut + 'a 234 | where 235 | Drop : FnOnce(T), 236 | { 237 | use ::core::mem::ManuallyDrop; 238 | struct Ret ( 239 | ManuallyDrop, 240 | ManuallyDrop, 241 | ) 242 | where 243 | Drop : FnOnce(T), 244 | ; 245 | impl ::core::ops::Drop for Ret 246 | where 247 | Drop : FnOnce(T), 248 | { 249 | fn drop (self: &'_ mut Self) 250 | { 251 | use ::core::ptr; 252 | unsafe { 253 | // # Safety 254 | // 255 | // - This is the canonical example of using `ManuallyDrop`. 256 | let value = ManuallyDrop::into_inner(ptr::read(&mut self.0)); 257 | let drop = ManuallyDrop::into_inner(ptr::read(&mut self.1)); 258 | drop(value); 259 | } 260 | } 261 | } 262 | impl ::core::ops::Deref for Ret 263 | where 264 | Drop : FnOnce(T), 265 | { 266 | type Target = T; 267 | #[inline] 268 | fn deref (self: &'_ Self) 269 | -> &'_ Self::Target 270 | { 271 | &self.0 272 | } 273 | } 274 | impl ::core::ops::DerefMut for Ret 275 | where 276 | Drop : FnOnce(T), 277 | { 278 | #[inline] 279 | fn deref_mut (self: &'_ mut Self) 280 | -> &'_ mut Self::Target 281 | { 282 | &mut self.0 283 | } 284 | } 285 | Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop)) 286 | } 287 | -------------------------------------------------------------------------------- /tests/format.rs: -------------------------------------------------------------------------------- 1 | use ::fstrings::format_f; 2 | 3 | #[test] 4 | fn format_f_single_line() { 5 | let x = 3; 6 | let y = 40; 7 | let z = -7; 8 | 9 | assert_eq!(&format_f!("{x} {y} {z}"), "3 40 -7"); 10 | } 11 | 12 | #[test] 13 | fn format_f_multi_line() { 14 | let x = 3; 15 | let y = 40; 16 | let z = -7; 17 | 18 | assert_eq!( 19 | &format_f!( 20 | "{x} 21 | {y} 22 | {z}" 23 | ), 24 | "3 25 | 40 26 | -7" 27 | ); 28 | } 29 | --------------------------------------------------------------------------------