diff --git a/README.md b/README.md index 74f270a..5103ea5 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ parse!(input, "My farm contains some amount of booleans: {many: || :}"); assert_eq!(many, vec![true, false, true, false]); ``` -Additionally you can use the [`try_parse!`] macro if you don't want to panic when the parsing fails. +You can use the [`try_parse!`] macro if you don't want to panic when the parsing fails. [`try_parse!`]: https://docs.rs/prse/latest/prse/macro.try_parse.html @@ -53,11 +53,29 @@ let path: Result = try_parse!(input, "cd {}"); assert_eq!(path.unwrap(), PathBuf::from("C:\\windows\\system32")); ``` -## Roadmap +Additionally you can use the [`Parse`] derive macro to help you parse custom types. +```rust +use prse::{parse, Parse}; + +#[derive(Parse, PartialEq, Eq, Debug)] +#[prse = "({x}, {y})"] +struct Position { + x: i32, + y: i32, +} + +let input = "(1, 3) + (-2, 9)"; + +let (lhs, rhs): (Position, Position) = parse!(input, "{} + {}"); + +assert_eq!(lhs, Position {x: 1, y: 3}); +assert_eq!(rhs, Position {x: -2, y: 9}); +``` + +[`Parse`]: https://docs.rs/prse/latest/prse/derive.Parse.html + +## Alternatives -- Benchmarking -- Add a Derive macro for LendingFromStr -- Have the ability to specify multiple parses if the first one fails #### License diff --git a/prse-derive/src/derive.rs b/prse-derive/src/derive.rs new file mode 100644 index 0000000..ac9c50b --- /dev/null +++ b/prse-derive/src/derive.rs @@ -0,0 +1,259 @@ +use crate::instructions::{Instruction, Instructions}; +use crate::var::Var; +use proc_macro2::{Ident, Span}; +use std::collections::HashSet; +use syn::parse::{Parse, ParseStream}; +use syn::{Attribute, Data, DeriveInput, Generics, Lit, LitStr, Meta, MetaNameValue, Variant}; + +#[derive(Clone)] +pub(crate) enum Derive { + NoAttributes(Generics, Ident), + Struct(Generics, Ident, Fields), + Enum(Generics, Ident, Vec<(Ident, Fields)>), +} + +#[derive(Clone)] +pub(crate) enum Fields { + Named(Instructions), + Unnamed(Instructions), + Unit(String), +} + +fn validate_fields( + fields: syn::Fields, + instructions: Instructions, + span: Span, +) -> syn::Result { + match fields { + syn::Fields::Unit => { + let mut iter = instructions.0.into_iter(); + match iter.next() { + None => Ok(Fields::Unit("".into())), + Some(Instruction::Lit(s)) if iter.next().is_none() => Ok(Fields::Unit(s)), + _ => Err(syn::Error::new( + span, + "A unit field cannot contain variables", + )), + } + } + syn::Fields::Named(fields) => { + let fields: Vec<_> = fields + .named + .into_iter() + .map(|f| (f.ident.unwrap(), f.ty)) + .collect(); + let mut seen_idents = HashSet::new(); + for i in instructions.0.iter() { + if let Instruction::IterParse(..) = i { + return Err(syn::Error::new( + span, + "Iterator parsing is not supported as the iterator is an opaque type.", + )); + } + match i.get_var() { + None => {} + Some(Var::Ident(ident)) => { + if fields.iter().any(|(i, _)| i == ident) { + if seen_idents.contains(ident) { + return Err(syn::Error::new( + span, + format!("Duplicated variable: {ident}"), + )); + } + seen_idents.insert(ident); + } else { + return Err(syn::Error::new( + span, + format!("Unexpected variable: {ident}"), + )); + } + } + _ => { + return Err(syn::Error::new( + span, + "Named fields can only be parsed by name.", + )); + } + } + } + Ok(Fields::Named(instructions)) + } + syn::Fields::Unnamed(fields) => { + let max = fields.unnamed.iter().count() - 1; + let mut count = 0; + for i in instructions.0.iter() { + if let Instruction::IterParse(..) = i { + return Err(syn::Error::new( + span, + "Iterator parsing is not supported as the iterator is an opaque type.", + )); + } + match i.get_var() { + Some(Var::Ident(ident)) => { + return Err(syn::Error::new( + span, + format!("Unexpected named variable: {ident}."), + )); + } + Some(Var::Implied) => { + if count > max { + return Err(syn::Error::new( + span, + format!("Tuple variable must be between 0 and {max}."), + )); + } + count += 1; + } + Some(Var::Position(pos)) if (*pos as usize) > max => { + return Err(syn::Error::new( + span, + format!("Positional variable must be between 0 and {max}."), + )); + } + _ => {} + } + } + Ok(Fields::Unnamed(instructions)) + } + } +} + +impl Parse for Derive { + fn parse(stream: ParseStream) -> syn::Result { + let input: DeriveInput = stream.parse()?; + + match input.data { + Data::Struct(s) => { + no_attributes(s.fields.iter().flat_map(|f| f.attrs.iter()))?; + match attribute_instructions(input.attrs.into_iter())? { + None => Ok(Derive::NoAttributes(input.generics, input.ident)), + Some((instructions, span)) => Ok(Derive::Struct( + input.generics, + input.ident, + validate_fields(s.fields, instructions, span)?, + )), + } + } + Data::Enum(e) => { + no_attributes(input.attrs.iter())?; + no_attributes( + e.variants + .iter() + .flat_map(|v| v.fields.iter().flat_map(|f| f.attrs.iter())), + )?; + + match get_variant_attributes(e.variants.into_iter(), input.ident.span())? { + None => Ok(Derive::NoAttributes(input.generics, input.ident)), + Some(v_instructions) => { + Ok(Derive::Enum(input.generics, input.ident, v_instructions)) + } + } + } + Data::Union(u) => Err(syn::Error::new( + u.union_token.span, + "The derive macro does not currently support unions.", + )), + } + } +} + +fn attribute_instructions( + mut attrs: impl Iterator, +) -> syn::Result> { + while let Some(a) = attrs.next() { + if let Some(lit) = get_prse_lit(&a)? { + for a in attrs.by_ref() { + if get_prse_lit(&a)?.is_some() { + return Err(syn::Error::new( + a.bracket_token.span, + "Expected only a single prse attribute.", + )); + } + } + let span = lit.span(); + let lit_string = lit.value(); + return Ok(Some((Instructions::new(&lit_string, span)?, span))); + } + } + Ok(None) +} + +fn get_prse_lit(a: &Attribute) -> syn::Result> { + if a.path.is_ident("prse") { + match a.parse_meta()? { + Meta::NameValue(MetaNameValue { + lit: Lit::Str(l), .. + }) => Ok(Some(l)), + _ => Err(syn::Error::new( + a.bracket_token.span, + "prse attribute must be of the form #[prse = \"parse_string\"]", + )), + } + } else { + Ok(None) + } +} + +fn no_attributes<'a>(attrs: impl Iterator) -> syn::Result<()> { + attrs.fold(Ok(()), |i, a| { + let error = syn::Error::new(a.bracket_token.span, "Unexpected prse attribute."); + let error = match get_prse_lit(a).map(|a| a.is_some()) { + Err(mut e) => { + e.combine(error); + e + } + Ok(true) => error, + Ok(false) => { + return i; + } + }; + match i { + Ok(()) => Err(error), + Err(mut e) => { + e.combine(error); + Err(e) + } + } + }) +} + +fn get_variant_attributes( + iter: impl Iterator, + input_span: Span, +) -> syn::Result>> { + iter.map(|v| { + ( + (v.ident, v.fields), + attribute_instructions(v.attrs.into_iter()), + ) + }) + .try_fold( + Some(vec![]), + |i, ((v_ident, v_fields), instructions)| match i { + Some(mut v) if v.is_empty() => match instructions? { + None => Ok(None), + Some((instr, span)) => { + v.push((v_ident, validate_fields(v_fields, instr, span)?)); + Ok(Some(v)) + } + }, + Some(mut v) => match instructions? { + None => Err(syn::Error::new( + input_span, + "The derive macro must either have an attribute on each field or none at all.", + )), + Some((instr, span)) => { + v.push((v_ident, validate_fields(v_fields, instr, span)?)); + Ok(Some(v)) + } + }, + None => match instructions? { + None => Ok(None), + Some(_) => Err(syn::Error::new( + input_span, + "The derive macro must either have an attribute on each field or none at all.", + )), + }, + }, + ) +} diff --git a/prse-derive/src/expand_derive.rs b/prse-derive/src/expand_derive.rs new file mode 100644 index 0000000..14fab05 --- /dev/null +++ b/prse-derive/src/expand_derive.rs @@ -0,0 +1,203 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::ToTokens; +use syn::{GenericParam, Generics, ImplGenerics, WhereClause, WherePredicate}; + +use crate::derive::{Derive, Fields}; +use crate::instructions::Instructions; +use crate::invocation::string_to_tokens; + +impl Derive { + pub fn into_token_stream(self) -> TokenStream { + match self { + Derive::NoAttributes(g, i) => expand_default(g, i), + Derive::Struct(mut g, name, f) => { + let (impl_generics, ty_generics, where_clause) = + split_for_impl(&mut g, [].into_iter()); + + let tokens = match f { + Fields::Named(instructions) => expand_field(instructions, quote!(Self), None), + Fields::Unnamed(instructions) => expand_tuple(instructions, quote!(Self), None), + Fields::Unit(s) => expand_unit(s, quote!(Self), None), + }; + + quote! { + #[automatically_derived] + impl #impl_generics ::prse::Parse<'__prse_a> for #name #ty_generics #where_clause { + fn from_str(s: &'__prse_a str) -> Result { + #tokens + } + } + } + } + Derive::Enum(mut g, name, v) => { + let (impl_generics, ty_generics, where_clause) = + split_for_impl(&mut g, [].into_iter()); + + let mut result = None; + + for (variant, f) in v.into_iter().rev() { + result = Some(match f { + Fields::Named(instructions) => { + expand_field(instructions, quote!(Self::#variant), result) + } + Fields::Unnamed(instructions) => { + expand_tuple(instructions, quote!(Self::#variant), result) + } + Fields::Unit(s) => expand_unit(s, quote!(Self::#variant), result), + }); + } + + quote! { + #[automatically_derived] + impl #impl_generics ::prse::Parse<'__prse_a> for #name #ty_generics #where_clause { + fn from_str(s: &'__prse_a str) -> Result { + #result + } + } + } + } + } + } +} + +fn expand_field( + instructions: Instructions, + to_return: TokenStream, + error: Option, +) -> TokenStream { + let func_name = format_ident!("__prse_func"); + let mut renames = vec![]; + let mut return_idents = vec![]; + let mut func_idents = vec![]; + let error = error.unwrap_or_else(|| quote!(Err(e))); + + instructions.gen_return_idents(&mut return_idents, &mut func_idents, &mut renames); + + let mut body = quote!(let mut __prse_parse;); + + instructions.gen_body(&mut body); + + let function = instructions.gen_function(body, func_name.clone()); + + let fields = renames.iter().map(|(l, r)| quote!(#l: #r)); + + quote! { + { + use ::prse::{ExtParseStr, Parse}; + + #function + + match #func_name (s) { + Ok(( #(#func_idents),* )) => { + Ok(( #to_return { #(#fields),* })) + } + Err(e) => #error, + } + } + } +} + +fn expand_tuple( + instructions: Instructions, + to_return: TokenStream, + error: Option, +) -> TokenStream { + let func_name = format_ident!("__prse_func"); + let mut _renames = vec![]; + let mut return_idents = vec![]; + let mut func_idents = vec![]; + let error = error.unwrap_or_else(|| quote!(Err(e))); + + instructions.gen_return_idents(&mut return_idents, &mut func_idents, &mut _renames); + + let mut body = quote!(let mut __prse_parse;); + + instructions.gen_body(&mut body); + + let function = instructions.gen_function(body, func_name.clone()); + + quote! { + { + use ::prse::{ExtParseStr, Parse}; + + #function + + match #func_name (s) { + Ok(( #(#func_idents),* )) => { + Ok(( #to_return ( #(#return_idents),* ))) + } + Err(e) => #error, + } + } + } +} + +fn expand_unit(s: String, to_return: TokenStream, error: Option) -> TokenStream { + let l_string = string_to_tokens(&s); + let error = error.unwrap_or_else(|| { + if cfg!(feature = "alloc") { + quote! { + Err(::prse::ParseError::Literal {expected: (#l_string).into(), found: s.into()}) + } + } else { + quote!(Err(::prse::ParseError::Literal)) + } + }); + quote! { + match s { + #l_string => Ok(#to_return), + _ => #error + } + } +} + +fn expand_default(mut generics: Generics, name: Ident) -> TokenStream { + let (impl_generics, ty_generics, where_clause) = split_for_impl( + &mut generics, + [ + parse_quote!(Self: ::core::str::FromStr), + parse_quote!( + ::Err: core::convert::Into<::prse::ParseError> + ), + ] + .into_iter(), + ); + + quote! { + #[automatically_derived] + impl #impl_generics ::prse::Parse<'__prse_a> for #name #ty_generics #where_clause { + fn from_str(s: &'__prse_a str) -> Result { + ::from_str(&s).map_err(|e| e.into()) + } + } + } +} + +fn split_for_impl( + generics: &mut Generics, + extra_predicates: impl IntoIterator, +) -> (ImplGenerics, TokenStream, Option<&WhereClause>) { + let ty_generics = generics.split_for_impl().1.to_token_stream(); + + generics.params.push(parse_quote!('__prse_a)); + + let type_predicates: Vec = generics + .params + .iter() + .filter_map(|p| { + if let GenericParam::Type(t) = p { + let t = &t.ident; + Some(parse_quote!(#t: ::prse::Parse<'__prse_a>)) + } else { + None + } + }) + .collect(); + + let predicates = &mut generics.make_where_clause().predicates; + predicates.extend(extra_predicates); + predicates.extend(type_predicates.into_iter()); + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + (impl_generics, ty_generics, where_clause) +} diff --git a/prse-derive/src/instructions.rs b/prse-derive/src/instructions.rs index dcf431b..55e3de5 100644 --- a/prse-derive/src/instructions.rs +++ b/prse-derive/src/instructions.rs @@ -1,49 +1,10 @@ +use crate::invocation::string_to_tokens; +use crate::var; +use crate::var::Var; use itertools::Itertools; -use proc_macro2::{Ident, Span}; -use syn::parse::{Parse, ParseStream}; -use syn::{parse_str, LitInt}; - -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] -pub enum Var { - Implied, - Ident(Ident), - Position(u8), -} - -impl Var { - pub fn add_span(&mut self, span: Span) { - if let Var::Ident(i) = self { - i.set_span(span) - } - } -} - -impl Parse for Var { - fn parse(input: ParseStream) -> syn::Result { - if input.is_empty() { - Ok(Var::Implied) - } else { - match input.parse::() { - Ok(l) => { - let pos: u8 = l - .base10_parse() - .map_err(|_| input.error("position must be between 0 and 255."))?; - if !input.is_empty() { - return Err(input.error("expected count.")); - } - Ok(Var::Position(pos)) - } - Err(_) => { - let res = input.parse::().map(Var::Ident)?; - if !input.is_empty() { - return Err(input.error("expected identifier")); - } - Ok(res) - } - } - } - } -} +use proc_macro2::Span; +use proc_macro2::{Ident, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] pub enum Instruction { @@ -66,202 +27,289 @@ impl Instruction { } } -pub fn get_instructions(input: &str, input_span: Span) -> syn::Result> { - let mut i = input.chars().multipeek(); - let mut var_mode = false; - let mut val = String::new(); - let mut instructions = vec![]; - while let Some(c) = i.next() { - match (c, var_mode) { - ('{', false) => { - // Character has been escaped. - if let Some('{') = i.peek() { - val.push(c); - i.next().unwrap(); - } else { - if !val.is_empty() { - instructions.push(Instruction::Lit(val)); +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone)] +pub(crate) struct Instructions(pub Vec); + +impl Instructions { + pub fn new(input: &str, input_span: Span) -> syn::Result { + let mut i = input.chars().multipeek(); + let mut var_mode = false; + let mut val = String::new(); + let mut instructions = vec![]; + while let Some(c) = i.next() { + match (c, var_mode) { + ('{', false) => { + // Character has been escaped. + if let Some('{') = i.peek() { + val.push(c); + i.next().unwrap(); + } else { + if !val.is_empty() { + instructions.push(Instruction::Lit(val)); + } + val = String::new(); + var_mode = true; } - val = String::new(); - var_mode = true; - } - } - ('}', false) => { - if let Some('}') = i.peek() { - val.push(c); - i.next().unwrap(); - } else { - return Err(syn::Error::new( - input_span, - "Found unexpected } bracket. Consider escaping it by changing it to }}.", - )); } - } - ('{', true) => { - if let Some('{') = i.peek() { - val.push(c); - i.next().unwrap(); - } else { - return Err(syn::Error::new( - input_span, - "Unescaped {, consider changing to {{.", - )); + ('}', false) => { + if let Some('}') = i.peek() { + val.push(c); + i.next().unwrap(); + } else { + return Err(syn::Error::new( + input_span, + "Found unexpected } bracket. Consider escaping it by changing it to }}.", + )); + } } - } - ('}', true) => { - if let Some('}') = i.peek() { - if i.peek() != Some(&'}') { + ('{', true) => { + if let Some('{') = i.peek() { val.push(c); i.next().unwrap(); - continue; + } else { + return Err(syn::Error::new( + input_span, + "Unescaped {, consider changing to {{.", + )); } } - if !matches!(instructions.last(), Some(Instruction::Lit(_)) | None) { - return Err(syn::Error::new( - input_span, - "Cannot have two captures without a string in between.", - )); + ('}', true) => { + if let Some('}') = i.peek() { + if i.peek() != Some(&'}') { + val.push(c); + i.next().unwrap(); + continue; + } + } + if !matches!(instructions.last(), Some(Instruction::Lit(_)) | None) { + return Err(syn::Error::new( + input_span, + "Cannot have two captures without a string in between.", + )); + } + instructions.push(var::parse_var(val, input_span)?); + val = String::new(); + var_mode = false; } - instructions.push(parse_var(val, input_span)?); - val = String::new(); - var_mode = false; + (c, _) => val.push(c), } - (c, _) => val.push(c), } - } - if var_mode { - return Err(syn::Error::new( - input_span, - "Expected to find } bracket. Consider adding a } bracket to close the open { bracket.", - )); - } - if !val.is_empty() { - instructions.push(Instruction::Lit(val)); - } + if var_mode { + return Err(syn::Error::new( + input_span, + "Expected to find } bracket. Consider adding a } bracket to close the open { bracket.", + )); + } + if !val.is_empty() { + instructions.push(Instruction::Lit(val)); + } - validate_instructions(instructions, input_span) -} + Self::validate_instructions(instructions, input_span) + } -fn validate_instructions( - instructions: Vec, - input_span: Span, -) -> syn::Result> { - if instructions - .iter() - .any(|i| matches!(i.get_var(), Some(Var::Position(_)))) - { - if !instructions + fn validate_instructions( + instructions: Vec, + input_span: Span, + ) -> syn::Result { + if instructions .iter() - .any(|i| matches!(i.get_var(), Some(Var::Implied))) + .any(|i| matches!(i.get_var(), Some(Var::Position(_)))) { - let has_constant_step = instructions + if !instructions .iter() - .flat_map(|i| match i.get_var() { - Some(Var::Position(p)) => Some(p), - _ => None, - }) - .sorted() - .zip(0_u8..) - .all(|(i, p)| i == &p); - if has_constant_step { - Ok(instructions) + .any(|i| matches!(i.get_var(), Some(Var::Implied))) + { + let has_constant_step = instructions + .iter() + .flat_map(|i| match i.get_var() { + Some(Var::Position(p)) => Some(p), + _ => None, + }) + .sorted() + .zip(0_u8..) + .all(|(i, p)| i == &p); + if has_constant_step { + Ok(Instructions(instructions)) + } else { + Err(syn::Error::new(input_span, "Each positional argument much uniquely map to a corresponding index in the returned tuple.")) + } } else { - Err(syn::Error::new(input_span, "Each positional argument much uniquely map to a corresponding index in the returned tuple.")) + Err(syn::Error::new( + input_span, + "Cannot use implied positional arguments with explicitly defined ones.", + )) } } else { - Err(syn::Error::new( - input_span, - "Cannot use implied positional arguments with explicitly defined ones.", - )) + Ok(Instructions(instructions)) } - } else { - Ok(instructions) } -} -fn parse_var(input: String, input_span: Span) -> syn::Result { - match input.split_once(':') { - Some((var, split)) => { - let mut var: Var = parse_str(var)?; - var.add_span(input_span); - if let Some((sep, num)) = split.rsplit_once(':') { - if sep.is_empty() { - return Err(syn::Error::new(input_span, "separator cannot be empty.")); - } - Ok(if num.trim().is_empty() { - if !cfg!(feature = "alloc") { - return Err(syn::Error::new( - input_span, - "alloc feature is required to parse into a Vec.", - )); - } - Instruction::VecParse(var, String::from(sep)) - } else { - match num.parse() { - Ok(0_u8) => Instruction::IterParse(var, String::from(sep)), - Ok(x) => Instruction::MultiParse(var, String::from(sep), x), - Err(_) => { - return Err(syn::Error::new( - input_span, - format!("expected a number between 0 and 255 but found {num}."), - )); - } + pub fn gen_function(&self, body: TokenStream, func_name: Ident) -> TokenStream { + let mut return_types = vec![]; + let mut generics = vec![]; + for (idx, i) in self + .0 + .iter() + .enumerate() + .filter(|(_, i)| !matches!(i, Instruction::Lit(_))) + { + let type_ident = format_ident!("T{idx}"); + return_types.push(match i { + Instruction::Parse(_) => type_ident.to_token_stream(), + Instruction::VecParse(_, _) => { + if cfg!(feature = "std") { + quote!(::std::vec::Vec<#type_ident>) + } else { + quote!(::alloc::vec::Vec<#type_ident>) } - }) - } else { - Err(syn::Error::new( - input_span, - "invalid multi parse, it must be of the form ::.", - )) - } + } + Instruction::IterParse(_, _) => quote! { + impl ::core::iter::Iterator> + 'a + }, + Instruction::MultiParse(_, _, count) => { + let count = *count as usize; + quote! ([ #type_ident ; #count]) + } + _ => unreachable!(), + }); + generics.push(type_ident); } - None => { - let mut var: Var = parse_str(&input)?; - var.add_span(input_span); - Ok(Instruction::Parse(var)) + + quote! { + fn #func_name <'a, #(#generics: Parse<'a>),* >( + mut __prse_input: &'a str, + ) -> ::core::result::Result<( #(#return_types),* ), ::prse::ParseError> { + #body + } } } -} -#[cfg(test)] -mod tests { - use proc_macro2::Span; + pub fn gen_body(&self, result: &mut TokenStream) { + let mut store_token = None; + let alloc_crate: TokenStream = if cfg!(feature = "std") { + quote!(std) + } else { + quote!(alloc) + }; + + for (idx, i) in self.0.iter().enumerate() { + let var = format_ident!("__prse_{idx}"); + match i { + Instruction::Lit(l_string) => { + let l_string = string_to_tokens(l_string); + + result.append_all(if cfg!(feature = "alloc") { + quote! { + (__prse_parse, __prse_input) = __prse_input.split_once(#l_string) + .ok_or_else(|| ::prse::ParseError::Literal {expected: (#l_string).into(), found: __prse_input.into()})?; + } + } else { + quote! { + (__prse_parse, __prse_input) = __prse_input.split_once(#l_string) + .ok_or_else(|| ::prse::ParseError::Literal)?; + } + }); + + if let Some(t) = store_token { + store_token = None; + result.append_all(t); + } + } + Instruction::Parse(_) => { + store_token = Some(quote! { + let #var = __prse_parse.lending_parse()?; + }); + } + Instruction::VecParse(_, sep) => { + let sep = string_to_tokens(sep); + store_token = Some(quote! { + let #var = __prse_parse.split(#sep) + .map(|p| p.lending_parse()) + .collect::<::core::result::Result<::#alloc_crate::vec::Vec<_>, ::prse::ParseError>>()?; + }); + } + Instruction::IterParse(_, sep) => { + let sep = string_to_tokens(sep); + store_token = Some(quote! { + let #var = __prse_parse.split(#sep) + .map(|p| p.lending_parse()); + }); + } + Instruction::MultiParse(_, sep, count) => { + let sep = string_to_tokens(sep); + let i = 0..*count; + store_token = Some(quote! { + let mut __prse_iter = __prse_parse.split(#sep) + .map(|p| p.lending_parse()); + let #var = [ #( + __prse_iter.next() + .ok_or_else(|| ::prse::ParseError::Multi { + expected: #count, + found: #i, + })?? + ),* ]; + if __prse_iter.next().is_some() { + return Err(::prse::ParseError::Multi { + expected: #count, + found: #count + 1, + }); + } + }); + } + }; + } + result.append_all(store_token.map_or_else(|| if cfg!(feature = "alloc") { + quote! { + if !__prse_input.is_empty() { + return Err(::prse::ParseError::Literal {expected: "".into(), found: __prse_input.into()}) + } + } + } else { + quote! { + if !__prse_input.is_empty() { + return Err(::prse::ParseError::Literal) + } + } + }, |t| quote! { __prse_parse = __prse_input; #t })); - use crate::instructions::get_instructions; + let return_idents = self.0.iter().enumerate().filter_map(|(idx, i)| { + i.get_var()?; + Some(format_ident!("__prse_{idx}")) + }); + result.append_all(quote! { Ok(( #(#return_idents),* )) }); + } - #[test] - fn test_instruction_pass() { - use super::Instruction::*; - use super::Var::*; - #[rustfmt::skip] - let cases = [ - ("{}", vec![Parse(Implied)]), - ("{} {}", vec![Parse(Implied), Lit(" ".into()), Parse(Implied)]), - ("{}\n{}", vec![Parse(Implied), Lit("\n".into()), Parse(Implied)]), - ("😇{}ángeĺ{}!", vec![Lit("😇".into()), Parse(Implied), Lit("ángeĺ".into()), Parse(Implied), Lit("!".into())]), - ("{}{{{}}}{}", vec![Parse(Implied), Lit("{".into()), Parse(Implied), Lit("}".into()), Parse(Implied)]), - (" {}{{:}}}}{} ", vec![Lit(" ".into()), Parse(Implied), Lit("{:}}".into()), Parse(Implied), Lit(" ".into())]), - (" {} {}}}{}", vec![Lit(" ".into()), Parse(Implied), Lit(" ".into()), Parse(Implied), Lit("}".into()), Parse(Implied)]), - ("{:}}:}", vec![VecParse(Implied, "}".into())]), - ("{:{{}}:}", vec![VecParse(Implied, "{}".into())]), - ("{:{{}}: }", vec![VecParse(Implied, "{}".into())]), - ("{hello}", vec![Parse(Ident(syn::Ident::new("hello", Span::call_site())))]), - ("{:,:5}", vec![MultiParse(Implied, ",".into(), 5)]), - ("{:,:0}", vec![IterParse(Implied, ",".into())]), - ("{:,:}", vec![VecParse(Implied, ",".into())]), - ("{:,::1}", vec![MultiParse(Implied, ",:".into(), 1)]), - ("{:,::0}", vec![IterParse(Implied, ",:".into())]), - ("{:,::}", vec![VecParse(Implied, ",:".into())]), - ("{::,::85}", vec![MultiParse(Implied, ":,:".into(), 85)]), - ("{::,::0}", vec![IterParse(Implied, ":,:".into())]), - ("{::,::}", vec![VecParse(Implied, ":,:".into())]), - ("{ 0 }", vec![Parse(Position(0))]), - ("{1} {0}", vec![Parse(Position(1)), Lit(" ".into()), Parse(Position(0))]), - ("{0} { hiya }", vec![Parse(Position(0)), Lit(" ".into()), Parse(Ident(syn::Ident::new("hiya", Span::call_site())))]), - ]; - for (input, expected) in cases { - let output = get_instructions(input, Span::call_site()); - assert_eq!(output.unwrap(), expected); + pub fn gen_return_idents( + &self, + return_idents: &mut Vec, + func_idents: &mut Vec, + renames: &mut Vec<(Ident, Ident)>, + ) { + let mut num_positions = 0; + + for (idx, instruction) in self + .0 + .iter() + .enumerate() + .filter(|(_, i)| !matches!(i, Instruction::Lit(_))) + { + let var = instruction.get_var().unwrap(); + let ident = format_ident!("__prse_{idx}"); + match var { + Var::Implied => { + func_idents.push(ident.clone()); + return_idents.push(ident); + } + Var::Ident(i) => { + func_idents.push(ident.clone()); + renames.push((i.clone(), ident)); + } + Var::Position(p) => { + func_idents.push(format_ident!("__prse_pos_{p}")); + return_idents.push(format_ident!("__prse_pos_{num_positions}")); + num_positions += 1; + } + }; } } } diff --git a/prse-derive/src/invocation.rs b/prse-derive/src/invocation.rs index 08377c5..10d3c73 100644 --- a/prse-derive/src/invocation.rs +++ b/prse-derive/src/invocation.rs @@ -4,12 +4,12 @@ use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{Expr, LitStr, Token}; -use crate::instructions::{get_instructions, Instruction, Var}; +use crate::instructions::Instructions; #[derive(Clone)] pub struct ParseInvocation { input: Expr, - instructions: Vec, + instructions: Instructions, pub try_parse: bool, } @@ -19,7 +19,7 @@ impl Parse for ParseInvocation { let _coma: Token![,] = stream.parse()?; let lit = stream.parse::()?; let lit_string = lit.value(); - let instructions = get_instructions(&lit_string, lit.span())?; + let instructions = Instructions::new(&lit_string, lit.span())?; Ok(Self { input, @@ -29,197 +29,26 @@ impl Parse for ParseInvocation { } } -impl ParseInvocation { - fn gen_function(&self, body: TokenStream, tokens: &mut TokenStream) { - let mut generics = vec![]; - let mut return_types = vec![]; - for (idx, i) in self - .instructions - .iter() - .enumerate() - .filter(|(_, i)| !matches!(i, Instruction::Lit(_))) - { - let type_ident = format_ident!("T{idx}"); - generics.push(match i { - Instruction::Parse(_) => type_ident.to_token_stream(), - Instruction::VecParse(_, _) => { - if cfg!(feature = "std") { - quote!(::std::vec::Vec<#type_ident>) - } else { - quote!(::alloc::vec::Vec<#type_ident>) - } - } - Instruction::IterParse(_, _) => quote! { - impl ::core::iter::Iterator> + 'a - }, - Instruction::MultiParse(_, _, count) => { - let count = *count as usize; - quote! ([ #type_ident ; #count]) - } - _ => unreachable!(), - }); - return_types.push(type_ident); - } - - tokens.append_all(quote! { - fn __prse_func<'a, #(#return_types: LendingFromStr<'a>),* >( - mut __prse_input: &'a str, - ) -> ::core::result::Result<( #(#generics),* ), ::prse::ParseError> { - #body - } - }) - } - - fn gen_body(&self, result: &mut TokenStream) { - let mut store_token = None; - let alloc_crate: TokenStream = if cfg!(feature = "std") { - quote!(std) - } else { - quote!(alloc) - }; - - for (idx, i) in self.instructions.iter().enumerate() { - let var = format_ident!("__prse_{idx}"); - match i { - Instruction::Lit(l_string) => { - let l_string = string_to_tokens(l_string); - - result.append_all(if cfg!(feature = "alloc") { - quote! { - (__prse_parse, __prse_input) = __prse_input.split_once(#l_string) - .ok_or_else(|| ::prse::ParseError::Literal {expected: (#l_string).into(), found: __prse_input.into()})?; - } - } else { - quote! { - (__prse_parse, __prse_input) = __prse_input.split_once(#l_string) - .ok_or_else(|| ::prse::ParseError::Literal)?; - } - }); - - if let Some(t) = store_token { - store_token = None; - result.append_all(t); - } - } - Instruction::Parse(_) => { - store_token = Some(quote! { - let #var = __prse_parse.lending_parse()?; - }); - } - Instruction::VecParse(_, sep) => { - let sep = string_to_tokens(sep); - store_token = Some(quote! { - let #var = __prse_parse.split(#sep) - .map(|p| p.lending_parse()) - .collect::<::core::result::Result<::#alloc_crate::vec::Vec<_>, ::prse::ParseError>>()?; - }); - } - Instruction::IterParse(_, sep) => { - let sep = string_to_tokens(sep); - store_token = Some(quote! { - let #var = __prse_parse.split(#sep) - .map(|p| p.lending_parse()); - }); - } - Instruction::MultiParse(_, sep, count) => { - let sep = string_to_tokens(sep); - let i = 0..*count; - store_token = Some(quote! { - let mut __prse_iter = __prse_parse.split(#sep) - .map(|p| p.lending_parse()); - let #var = [ #( - __prse_iter.next() - .ok_or_else(|| ::prse::ParseError::Multi { - expected: #count, - found: #i, - })?? - ),* ]; - if __prse_iter.next().is_some() { - return Err(::prse::ParseError::Multi { - expected: #count, - found: #count + 1, - }); - } - }); - } - }; - } - result.append_all(store_token.map_or_else(|| if cfg!(feature = "alloc") { - quote! { - if !__prse_input.is_empty() { - return Err(::prse::ParseError::Literal {expected: "".into(), found: __prse_input.into()}) - } - } - } else { - quote! { - if !__prse_input.is_empty() { - return Err(::prse::ParseError::Literal) - } - } - }, |t| quote! { __prse_parse = __prse_input; #t })); - - let return_idents = self.instructions.iter().enumerate().filter_map(|(idx, i)| { - i.get_var()?; - Some(format_ident!("__prse_{idx}")) - }); - result.append_all(quote! { Ok(( #(#return_idents),* )) }); - } -} +impl ParseInvocation {} impl ToTokens for ParseInvocation { fn to_tokens(&self, tokens: &mut TokenStream) { - let mut renames = TokenStream::new(); + let func_name = format_ident!("__prse_func"); + let input = &self.input; + let mut renames = vec![]; let mut return_idents = vec![]; let mut func_idents = vec![]; - let mut num_positions = 0; - - for (idx, instruction) in self - .instructions - .iter() - .enumerate() - .filter(|(_, i)| !matches!(i, Instruction::Lit(_))) - { - let var = instruction.get_var().unwrap(); - let ident = format_ident!("__prse_{idx}"); - match var { - Var::Implied => { - func_idents.push(ident.clone()); - return_idents.push(ident); - } - Var::Ident(i) => { - func_idents.push(ident.clone()); - renames.append_all(quote!(#i = #ident;)); - } - Var::Position(p) => { - func_idents.push(format_ident!("__prse_pos_{p}")); - return_idents.push(format_ident!("__prse_pos_{num_positions}")); - num_positions += 1; - } - }; - } - let mut body = quote!(let mut __prse_parse: &str;); - let mut function = quote!( - use ::prse::{ExtParseStr, LendingFromStr}; - ); + self.instructions + .gen_return_idents(&mut return_idents, &mut func_idents, &mut renames); - self.gen_body(&mut body); + let renames: TokenStream = renames.iter().flat_map(|(l, r)| quote!(#l = #r;)).collect(); - let func_idents: Vec<_> = self - .instructions - .iter() - .enumerate() - .filter_map(|(idx, i)| { - Some(match i.get_var()? { - Var::Position(p) => format_ident!("__prse_pos_{p}"), - _ => format_ident!("__prse_{idx}"), - }) - }) - .collect(); + let mut body = quote!(let mut __prse_parse: &str;); - self.gen_function(body, &mut function); + self.instructions.gen_body(&mut body); - let input = &self.input; + let function = self.instructions.gen_function(body, func_name.clone()); let mut result = quote_spanned! { input.span() => #[allow(clippy::needless_borrow)] @@ -228,7 +57,7 @@ impl ToTokens for ParseInvocation { result.append_all(if self.try_parse { quote! { - match __prse_func(__prse_input) { + match #func_name (__prse_input) { Ok(( #(#func_idents),* )) => { #renames Ok(( #(#return_idents),* )) @@ -238,7 +67,7 @@ impl ToTokens for ParseInvocation { } } else { quote! { - let ( #(#func_idents),* ) = ::prse::__private::unwrap_parse(__prse_func(__prse_input), __prse_input); + let ( #(#func_idents),* ) = ::prse::__private::unwrap_parse( #func_name (__prse_input), __prse_input); #renames #[allow(clippy::unused_unit)] { @@ -249,6 +78,8 @@ impl ToTokens for ParseInvocation { tokens.append_all(quote! { { + use ::prse::{ExtParseStr, Parse}; + #function #result @@ -257,7 +88,7 @@ impl ToTokens for ParseInvocation { } } -fn string_to_tokens(string: &str) -> TokenStream { +pub(crate) fn string_to_tokens(string: &str) -> TokenStream { string .parse() .map_or_else(|_| string.to_token_stream(), |s: char| s.to_token_stream()) diff --git a/prse-derive/src/lib.rs b/prse-derive/src/lib.rs index f7ab73e..77f38b3 100644 --- a/prse-derive/src/lib.rs +++ b/prse-derive/src/lib.rs @@ -7,13 +7,19 @@ extern crate quote; #[macro_use] extern crate syn; +use derive::Derive; use invocation::ParseInvocation; use proc_macro::TokenStream; use quote::ToTokens; +mod derive; +mod expand_derive; mod instructions; mod invocation; -/// The `parse` macro allows you to parse a string into any type that implements [`LendingFromStr`](trait.LendingFromStr.html). +mod var; + +/// The `parse` macro allows you to parse a string into any type that implements [`Parse`](trait.Parse.html). +/// (Which can be derived with the [`Parse`](derive.Parse.html) macro) /// /// ```ignore /// let input = "5 + -2 = 3"; @@ -130,3 +136,82 @@ pub fn try_parse(input: TokenStream) -> TokenStream { input.try_parse = true; input.to_token_stream().into() } + +/// Automatically implements the [`Parse`](trait.Parse.html) trait using one of two methods. +/// +/// You can define how each field should be parsed using the `prse` attribute. +/// +///```ignore +/// use prse::{parse, Parse}; +/// +/// #[derive(Debug, Parse)] +/// #[prse = "({x}, {y})"] +/// struct Position { +/// x: i32, +/// y: i32, +/// } +/// +/// fn main() { +/// let pos: Position = parse!("This is a position: (1, 2)", "This is a position: {}"); +/// assert_eq!(pos.x, 1); +/// assert_eq!(pos.y, 2); +/// } +///``` +/// +/// This can also be done on enums. +/// +///```ignore +/// use prse::{parse, Parse}; +/// +/// #[derive(Debug, Parse, Eq, PartialEq)] +/// enum Position { +/// #[prse = "({x}, {y})"] +/// Position { x: i32, y: i32 }, +/// #[prse = "({})"] +/// SinglePositon(i32), +/// #[prse = "()"] +/// NoPosition, +/// } +/// +/// // the first prse attribute to match is used. +/// let pos0: Position = parse!("This is a position: (1, 2)", "This is a position: {}"); +/// let pos1: Position = parse!("This is a position: (3)", "This is a position: {}"); +/// let pos2: Position = parse!("This is a position: ()", "This is a position: {}"); +/// +/// assert_eq!(pos0, Position::Position { x: 1, y: 2 }); +/// assert_eq!(pos1, Position::SinglePositon(3)); +/// assert_eq!(pos2, Position::NoPosition); +///``` +/// If no prse attributes are found, it will use your [`FromStr`](core::str::FromStr) implementation. +/// ```ignore +/// use prse::{parse, Parse}; +/// +/// #[derive(Debug, Parse)] +/// struct Position { +/// x: i32, +/// y: i32, +/// } +/// +/// impl std::str::FromStr for Position { +/// type Err = (); +/// +/// fn from_str(mut s: &str) -> Result { +/// s = s.strip_prefix('(').ok_or(())?; +/// s = s.strip_suffix(')').ok_or(())?; +/// let (x, y) = s.split_once(',').ok_or(())?; +/// Ok(Position { +/// x: x.parse().map_err(|_| ())?, +/// y: y.trim().parse().map_err(|_| ())?, +/// }) +/// } +/// } +/// +/// let pos: Position = parse!("This is a position: (1, 2)", "This is a position: {}"); +/// assert_eq!(pos.x, 1); +/// assert_eq!(pos.y, 2); +/// ``` +#[proc_macro_derive(Parse, attributes(prse))] +pub fn derive_parse(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as Derive); + input.into_token_stream().into() +} diff --git a/prse-derive/src/var.rs b/prse-derive/src/var.rs new file mode 100644 index 0000000..f6e4604 --- /dev/null +++ b/prse-derive/src/var.rs @@ -0,0 +1,133 @@ +use crate::instructions::Instruction; +use proc_macro2::{Ident, Span}; +use syn::parse::{Parse, ParseStream}; +use syn::{parse_str, LitInt}; + +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +pub enum Var { + Implied, + Ident(Ident), + Position(u8), +} + +impl Var { + pub fn add_span(&mut self, span: Span) { + if let Var::Ident(i) = self { + i.set_span(span) + } + } +} + +impl Parse for Var { + fn parse(input: ParseStream) -> syn::Result { + if input.is_empty() { + Ok(Var::Implied) + } else { + match input.parse::() { + Ok(l) => { + let pos: u8 = l + .base10_parse() + .map_err(|_| input.error("position must be between 0 and 255."))?; + if !input.is_empty() { + return Err(input.error("expected count.")); + } + Ok(Var::Position(pos)) + } + Err(_) => { + let res = input.parse::().map(Var::Ident)?; + if !input.is_empty() { + return Err(input.error("expected identifier")); + } + Ok(res) + } + } + } + } +} + +pub fn parse_var(input: String, input_span: Span) -> syn::Result { + match input.split_once(':') { + Some((var, split)) => { + let mut var: Var = parse_str(var)?; + var.add_span(input_span); + if let Some((sep, num)) = split.rsplit_once(':') { + if sep.is_empty() { + return Err(syn::Error::new(input_span, "separator cannot be empty.")); + } + Ok(if num.trim().is_empty() { + if !cfg!(feature = "alloc") { + return Err(syn::Error::new( + input_span, + "alloc feature is required to parse into a Vec.", + )); + } + Instruction::VecParse(var, String::from(sep)) + } else { + match num.parse() { + Ok(0_u8) => Instruction::IterParse(var, String::from(sep)), + Ok(x) => Instruction::MultiParse(var, String::from(sep), x), + Err(_) => { + return Err(syn::Error::new( + input_span, + format!("expected a number between 0 and 255 but found {num}."), + )); + } + } + }) + } else { + Err(syn::Error::new( + input_span, + "invalid multi parse, it must be of the form ::.", + )) + } + } + None => { + let mut var: Var = parse_str(&input)?; + var.add_span(input_span); + Ok(Instruction::Parse(var)) + } + } +} + +#[cfg(test)] +mod tests { + use proc_macro2::Span; + + use crate::instructions::Instructions; + + #[test] + fn test_instruction_pass() { + use crate::instructions::Instruction::*; + use crate::var::Var::*; + #[rustfmt::skip] + let cases = [ + ("{}", vec![Parse(Implied)]), + ("{} {}", vec![Parse(Implied), Lit(" ".into()), Parse(Implied)]), + ("{}\n{}", vec![Parse(Implied), Lit("\n".into()), Parse(Implied)]), + ("😇{}ángeĺ{}!", vec![Lit("😇".into()), Parse(Implied), Lit("ángeĺ".into()), Parse(Implied), Lit("!".into())]), + ("{}{{{}}}{}", vec![Parse(Implied), Lit("{".into()), Parse(Implied), Lit("}".into()), Parse(Implied)]), + (" {}{{:}}}}{} ", vec![Lit(" ".into()), Parse(Implied), Lit("{:}}".into()), Parse(Implied), Lit(" ".into())]), + (" {} {}}}{}", vec![Lit(" ".into()), Parse(Implied), Lit(" ".into()), Parse(Implied), Lit("}".into()), Parse(Implied)]), + ("{:}}:}", vec![VecParse(Implied, "}".into())]), + ("{:{{}}:}", vec![VecParse(Implied, "{}".into())]), + ("{:{{}}: }", vec![VecParse(Implied, "{}".into())]), + ("{hello}", vec![Parse(Ident(syn::Ident::new("hello", Span::call_site())))]), + ("{:,:5}", vec![MultiParse(Implied, ",".into(), 5)]), + ("{:,:0}", vec![IterParse(Implied, ",".into())]), + ("{:,:}", vec![VecParse(Implied, ",".into())]), + ("{:,::1}", vec![MultiParse(Implied, ",:".into(), 1)]), + ("{:,::0}", vec![IterParse(Implied, ",:".into())]), + ("{:,::}", vec![VecParse(Implied, ",:".into())]), + ("{::,::85}", vec![MultiParse(Implied, ":,:".into(), 85)]), + ("{::,::0}", vec![IterParse(Implied, ":,:".into())]), + ("{::,::}", vec![VecParse(Implied, ":,:".into())]), + ("{ 0 }", vec![Parse(Position(0))]), + ("{1} {0}", vec![Parse(Position(1)), Lit(" ".into()), Parse(Position(0))]), + ("{0} { hiya }", vec![Parse(Position(0)), Lit(" ".into()), Parse(Ident(syn::Ident::new("hiya", Span::call_site())))]), + ]; + for (input, expected) in cases { + let output = Instructions::new(input, Span::call_site()); + assert_eq!(output.unwrap(), Instructions(expected)); + } + } +} diff --git a/src/lending_parse.rs b/src/lending_parse.rs index 3c73d7a..ceebb15 100644 --- a/src/lending_parse.rs +++ b/src/lending_parse.rs @@ -4,19 +4,20 @@ use core::str::FromStr; use crate::parse_error::ParseError; /// Parse a string into the implemented type, unlike [`FromStr`] this trait allows -/// you to borrow the string. -pub trait LendingFromStr<'a> { +/// you to borrow the string. It can be automatically derived using +/// [`Parse`](prse_derive::Parse) and was previously called `LendingFromStr`. +pub trait Parse<'a> { /// Parses a string `s` to a return value of this type. /// /// If parsing succeeds, return the value inside [`Ok`], otherwise /// when the string is ill-formatted return a [`ParseError`]. /// /// ``` - /// # use prse::{parse, LendingFromStr, ParseError}; + /// # use prse::{parse, Parse, ParseError}; /// # #[derive(PartialEq, Debug)] /// struct Count<'b>(&'b str, u32); /// - /// impl<'a> LendingFromStr<'a> for Count<'a> { + /// impl<'a> Parse<'a> for Count<'a> { /// fn from_str(s: &'a str) -> Result { /// let (fruit, count) = s.split_once(':').ok_or_else(|| ParseError::new("expected a colon."))?; /// Ok(Count(<&'a str>::from_str(fruit)?, ::from_str(count.trim())?)) @@ -31,16 +32,16 @@ pub trait LendingFromStr<'a> { Self: Sized; } -impl<'a> LendingFromStr<'a> for &'a str { +impl<'a> Parse<'a> for &'a str { fn from_str(s: &'a str) -> Result { Ok(s) } } -macro_rules! impl_lending_from_str { +macro_rules! impl_parse { ( $( $Ty: ty )+) => { $( - impl<'a> LendingFromStr<'a> for $Ty { + impl<'a> Parse<'a> for $Ty { fn from_str(s: &'a str) -> Result { ::from_str(&s).map_err(|e| e.into()) } @@ -50,10 +51,10 @@ macro_rules! impl_lending_from_str { } #[cfg(feature = "alloc")] -macro_rules! impl_lending_from_str_infallible { +macro_rules! impl_parse_infallible { ( $( $Ty: ty )+) => { $( - impl<'a> LendingFromStr<'a> for $Ty { + impl<'a> Parse<'a> for $Ty { fn from_str(s: &'a str) -> Result { Ok(::from_str(&s).unwrap()) } @@ -62,45 +63,48 @@ macro_rules! impl_lending_from_str_infallible { }; } -impl_lending_from_str!(isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128); -impl_lending_from_str!(bool char f32 f64); -impl_lending_from_str!(NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize); -impl_lending_from_str!(NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize); +impl_parse!(isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128); +impl_parse!(bool char f32 f64); +impl_parse!(NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize); +impl_parse!(NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize); #[cfg(feature = "alloc")] mod impl_alloc { extern crate alloc; - use super::{FromStr, LendingFromStr, ParseError}; + use alloc::string::String; - impl_lending_from_str_infallible!(String); + use super::{FromStr, Parse, ParseError}; + + impl_parse_infallible!(String); } #[cfg(feature = "std")] mod impl_std { - use super::{FromStr, LendingFromStr, ParseError}; use std::ffi::OsString; use std::net::*; use std::path::PathBuf; - impl_lending_from_str_infallible!(OsString PathBuf); - impl_lending_from_str!(IpAddr SocketAddr Ipv4Addr Ipv6Addr SocketAddrV4 SocketAddrV6); + use super::{FromStr, Parse, ParseError}; + + impl_parse_infallible!(OsString PathBuf); + impl_parse!(IpAddr SocketAddr Ipv4Addr Ipv6Addr SocketAddrV4 SocketAddrV6); } -/// An str extension trait to allow you to call the `from_str` from [`LendingFromStr`] +/// An str extension trait to allow you to call the `from_str` from [`Parse`] /// without specifying the type. /// /// The trait is sealed and cannot be implemented on any other type. pub trait ExtParseStr: __private::Sealed { /// Parses the string slice into another type. /// - /// lending_parse can parse into any type that implements the [`LendingFromStr`] trait. - fn lending_parse<'a, F: LendingFromStr<'a>>(&'a self) -> Result; + /// lending_parse can parse into any type that implements the [`Parse`] trait. + fn lending_parse<'a, F: Parse<'a>>(&'a self) -> Result; } impl ExtParseStr for str { - fn lending_parse<'a, F: LendingFromStr<'a>>(&'a self) -> Result { - LendingFromStr::from_str(self) + fn lending_parse<'a, F: Parse<'a>>(&'a self) -> Result { + Parse::from_str(self) } } diff --git a/src/lib.rs b/src/lib.rs index 496df93..a48decd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,8 @@ //! Prse is a no-std compatible small string parsing library with an emphasis on speed and ease of use. //! //! The [`parse!`] and [`try_parse!`] macros can parse anything in the standard library -//! that implements [`FromStr`](std::str::FromStr), or any type that implements [`LendingFromStr`]. -//! -//!
+//! that implements [`FromStr`](std::str::FromStr), or any type that implements [`Parse`]. +//! (Which can be derived with the [`Parse`](derive.Parse.html) macro) //! //! # Examples //! ``` @@ -47,6 +46,26 @@ //! } //! ``` //! +//! Additionally you can use the [`Parse`](prse_derive::Parse) derive macro to help you parse custom types. +//! +//! ```rust +//! use prse::{parse, Parse}; +//! +//! #[derive(Parse, PartialEq, Eq, Debug)] +//! #[prse = "({x}, {y})"] +//! struct Position { +//! x: i32, +//! y: i32, +//! } +//! +//! let input = "(1, 3) + (-2, 9)"; +//! +//! let (lhs, rhs): (Position, Position) = parse!(input, "{} + {}"); +//! +//! assert_eq!(lhs, Position {x: 1, y: 3}); +//! assert_eq!(rhs, Position {x: -2, y: 9}); +//! ``` +//! //! # Repetition //! //! You can parse multiple parts of a string using one of the following methods: @@ -100,9 +119,12 @@ //! assert_eq!(animal_count, 79); //! ``` -pub use prse_derive::{parse, try_parse}; +pub use prse_derive::{parse, try_parse, Parse}; -pub use crate::lending_parse::{ExtParseStr, LendingFromStr}; +#[rustfmt::skip] +pub use crate::lending_parse::{ExtParseStr, Parse}; +/// Deprecated please use [`Parse`](crate::lending_parse::Parse) instead. +pub use crate::lending_parse::Parse as LendingFromStr; pub use crate::parse_error::ParseError; #[doc(hidden)] pub use crate::parse_error::__private; diff --git a/src/parse_error.rs b/src/parse_error.rs index 1e5805c..ec7b674 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -14,7 +14,7 @@ use std::error; #[cfg(feature = "std")] use std::net::AddrParseError; -/// The error returned when trying to parse a type using [`try_parse`](crate::try_parse) or [`LendingFromStr`](crate::LendingFromStr). +/// The error returned when trying to parse a type using [`try_parse`](crate::try_parse) or [`Parse`](crate::Parse). #[derive(Debug)] pub enum ParseError { /// The variant returned when an integer cannot be parsed. @@ -72,13 +72,13 @@ impl ParseError { /// This function stores the passed message into the `Other` variant. /// And as such can only be used when using the `alloc` feature. /// - /// This function can be especially useful when trying to implement [`LendingFromStr`](crate::LendingFromStr). + /// This function can be especially useful when trying to implement [`Parse`](crate::Parse). /// ``` - /// # use prse::{LendingFromStr, ParseError, ExtParseStr, parse}; + /// # use prse::{Parse, ParseError, ExtParseStr, parse}; /// # #[derive(PartialEq, Debug)] /// struct Bool(bool); /// - /// impl<'a> LendingFromStr<'a> for Bool { + /// impl<'a> Parse<'a> for Bool { /// fn from_str(s: &'a str) -> Result { /// match s { /// "false" | "False" => Ok(Bool(false)), @@ -207,6 +207,20 @@ impl_from_parse_error!(AddrParseError, Addr); #[cfg(feature = "std")] impl_from_parse_error!(Box, Dyn); +#[cfg(feature = "alloc")] +impl From<()> for ParseError { + fn from(_: ()) -> Self { + ParseError::Other(String::from("Error: ()")) + } +} + +#[cfg(not(feature = "alloc"))] +impl From<()> for ParseError { + fn from(_: ()) -> Self { + ParseError::Other + } +} + #[doc(hidden)] pub mod __private { use crate::ParseError; diff --git a/tests/test-std/lib.rs b/tests/test-std/lib.rs index a393d90..c5c641a 100644 --- a/tests/test-std/lib.rs +++ b/tests/test-std/lib.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use prse::{parse, try_parse, ParseError}; + use prse::{parse, try_parse, Parse, ParseError}; #[test] fn ui() { @@ -98,4 +98,57 @@ mod tests { let input = "Test"; parse!(input, "Test") } + + #[derive(Debug, Parse)] + struct Position { + x: i32, + y: i32, + } + + #[derive(Debug, Parse)] + #[prse = "({x}, {y})"] + struct Position2 { + x: i32, + y: i32, + } + + #[derive(Debug, Parse, Eq, PartialEq)] + enum Position3 { + #[prse = "({x}, {y})"] + Position { x: i32, y: i32 }, + #[prse = "({})"] + SinglePositon(i32), + #[prse = "()"] + NoPosition, + } + + impl std::str::FromStr for Position { + type Err = (); + + fn from_str(mut s: &str) -> Result { + s = s.strip_prefix('(').ok_or(())?; + s = s.strip_suffix(')').ok_or(())?; + let (x, y) = s.split_once(',').ok_or(())?; + Ok(Position { + x: x.parse().map_err(|_| ())?, + y: y.trim().parse().map_err(|_| ())?, + }) + } + } + + #[test] + fn doc_test() { + let pos: Position = parse!("This is a position: (1, 2)", "This is a position: {}"); + let pos2: Position2 = parse!("This is a position: (-4, 5)", "This is a position: {}"); + assert_eq!(pos.x, 1); + assert_eq!(pos.y, 2); + assert_eq!(pos2.x, -4); + assert_eq!(pos2.y, 5); + let pos0: Position3 = parse!("This is a position: (1, 2)", "This is a position: {}"); + let pos1: Position3 = parse!("This is a position: (3)", "This is a position: {}"); + let pos2: Position3 = parse!("This is a position: ()", "This is a position: {}"); + assert_eq!(pos0, Position3::Position { x: 1, y: 2 }); + assert_eq!(pos1, Position3::SinglePositon(3)); + assert_eq!(pos2, Position3::NoPosition); + } } diff --git a/tests/test-std/ui/derive.rs b/tests/test-std/ui/derive.rs new file mode 100644 index 0000000..2c5829d --- /dev/null +++ b/tests/test-std/ui/derive.rs @@ -0,0 +1,112 @@ +#![allow(unused)] +use prse::{parse, Parse}; + +#[derive(Parse)] +#[prse = "{x} - {y}"] +union A { + x: usize, + y: u32, +} + +#[derive(Parse)] +struct B { + #[prse = "{x} - {y}"] + x: usize, + y: usize, +} + +#[derive(Parse)] +struct C { + y: usize, + #[prse = "{x} - {y}"] + x: usize, +} + +#[derive(Parse)] +#[prse = "D: {}"] +struct D; + +#[derive(Parse)] +#[prse = "E: {0}"] +struct E; + +#[derive(Parse)] +#[prse = "F: {x}"] +struct F; + +#[derive(Parse)] +#[prse = "F: {x} {y}"] +struct G { + y: usize, + #[prse = "{x} - {y}"] + x: usize, +} + +#[derive(Parse)] +#[prse = "G: {a}"] +#[prse = "G: {a} "] +struct H { + a: usize, +} + +#[derive(Parse)] +#[prse = "I"] +enum I { + Unit, +} + +#[derive(Parse)] +#[prse = "J"] +enum J { + #[prse = "J"] + Unit, +} + +#[derive(Parse)] +enum K { + Tup(#[prse = "K: {}"] usize, usize), +} + +#[derive(Parse)] +enum L { + #[prse = "L"] + Unit, + #[prse = "L1"] + Unit2, + Unit3, +} + +#[derive(Parse)] +enum M { + S { + x: usize, + #[prse = "Test: {y}"] + y: usize, + }, +} + +#[derive(Parse)] +enum N { + #[prse("N")] + N, +} + +#[derive(Parse)] +enum O { + #[prse] + O, +} + +#[derive(Parse)] +enum P { + #[prse = "{:,:0}"] + P1(u32), +} + +#[derive(Parse)] +#[prse = "{x:,:0}"] +struct Q { + x: u32, +} + +fn main() {} diff --git a/tests/test-std/ui/derive.stderr b/tests/test-std/ui/derive.stderr new file mode 100644 index 0000000..2d7826d --- /dev/null +++ b/tests/test-std/ui/derive.stderr @@ -0,0 +1,101 @@ +error: The derive macro does not currently support unions. + --> ui/derive.rs:6:1 + | +6 | union A { + | ^^^^^ + +error: Unexpected prse attribute. + --> ui/derive.rs:13:6 + | +13 | #[prse = "{x} - {y}"] + | ^^^^^^^^^^^^^^^^^^^^ + +error: Unexpected prse attribute. + --> ui/derive.rs:21:6 + | +21 | #[prse = "{x} - {y}"] + | ^^^^^^^^^^^^^^^^^^^^ + +error: A unit field cannot contain variables + --> ui/derive.rs:26:10 + | +26 | #[prse = "D: {}"] + | ^^^^^^^ + +error: A unit field cannot contain variables + --> ui/derive.rs:30:10 + | +30 | #[prse = "E: {0}"] + | ^^^^^^^^ + +error: A unit field cannot contain variables + --> ui/derive.rs:34:10 + | +34 | #[prse = "F: {x}"] + | ^^^^^^^^ + +error: Unexpected prse attribute. + --> ui/derive.rs:41:6 + | +41 | #[prse = "{x} - {y}"] + | ^^^^^^^^^^^^^^^^^^^^ + +error: Expected only a single prse attribute. + --> ui/derive.rs:47:2 + | +47 | #[prse = "G: {a} "] + | ^^^^^^^^^^^^^^^^^^ + +error: Unexpected prse attribute. + --> ui/derive.rs:53:2 + | +53 | #[prse = "I"] + | ^^^^^^^^^^^^ + +error: Unexpected prse attribute. + --> ui/derive.rs:59:2 + | +59 | #[prse = "J"] + | ^^^^^^^^^^^^ + +error: Unexpected prse attribute. + --> ui/derive.rs:67:10 + | +67 | Tup(#[prse = "K: {}"] usize, usize), + | ^^^^^^^^^^^^^^^^ + +error: The derive macro must either have an attribute on each field or none at all. + --> ui/derive.rs:71:6 + | +71 | enum L { + | ^ + +error: Unexpected prse attribute. + --> ui/derive.rs:83:10 + | +83 | #[prse = "Test: {y}"] + | ^^^^^^^^^^^^^^^^^^^^ + +error: prse attribute must be of the form #[prse = "parse_string"] + --> ui/derive.rs:90:6 + | +90 | #[prse("N")] + | ^^^^^^^^^^^ + +error: prse attribute must be of the form #[prse = "parse_string"] + --> ui/derive.rs:96:6 + | +96 | #[prse] + | ^^^^^^ + +error: Iterator parsing is not supported as the iterator is an opaque type. + --> ui/derive.rs:102:14 + | +102 | #[prse = "{:,:0}"] + | ^^^^^^^^ + +error: Iterator parsing is not supported as the iterator is an opaque type. + --> ui/derive.rs:107:10 + | +107 | #[prse = "{x:,:0}"] + | ^^^^^^^^^