diff --git a/Cargo.lock b/Cargo.lock index e0a6b159..51470421 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,6 +472,7 @@ dependencies = [ name = "lox_derive" version = "0.1.0" dependencies = [ + "proc-macro2", "quote", "syn 1.0.109", ] @@ -649,9 +650,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] diff --git a/crates/lox_core/src/ndm/kvn/opm.rs b/crates/lox_core/src/ndm/kvn/opm.rs index 3e2bd465..bda47028 100644 --- a/crates/lox_core/src/ndm/kvn/opm.rs +++ b/crates/lox_core/src/ndm/kvn/opm.rs @@ -21,7 +21,6 @@ pub struct Opm { object_id: KvnStringValue, center_name: KvnStringValue, ref_frame: KvnStringValue, - // ref_frame_epoch: KvnDateTimeValue, time_system: KvnStringValue, // comment: KvnStringValue, epoch: KvnDateTimeValue, @@ -161,9 +160,7 @@ MAN_DV_1 = 0.00101500 [km/s] MAN_DV_2 = -0.00187300 [km/s] MAN_DV_3 = 0.00000000 [km/s]"#; - let mut lines = kvn.lines(); - - let opm = Opm::deserialize(&mut lines); + let opm = Opm::deserialize(&mut kvn.lines().peekable()); assert_eq!( opm, diff --git a/crates/lox_core/src/ndm/kvn/parser.rs b/crates/lox_core/src/ndm/kvn/parser.rs index a37a3747..2e8fa8d2 100644 --- a/crates/lox_core/src/ndm/kvn/parser.rs +++ b/crates/lox_core/src/ndm/kvn/parser.rs @@ -10,6 +10,7 @@ use nom::bytes::complete as nb; use nom::character::complete as nc; +use nom::error::{ErrorKind, ParseError}; use nom::sequence as ns; use regex::Regex; @@ -17,32 +18,114 @@ pub type KvnStringValue = KvnValue; pub type KvnIntegerValue = KvnValue; pub type KvnNumericValue = KvnValue; -#[derive(PartialEq, Debug)] -pub enum KvnStringLineParserErr { - ParseError(nom::Err>), +#[derive(Debug, PartialEq)] +pub enum KvnParserErr { + KeywordNotFound, EmptyValue, + ParserError(I, ErrorKind), } #[derive(PartialEq, Debug)] pub enum KvnNumberLineParserErr { - ParseError(nom::Err>), + ParserError(I, ErrorKind), + KeywordNotFound, EmptyValue, InvalidFormat, } #[derive(PartialEq, Debug)] pub enum KvnDateTimeParserErr { - ParseError(nom::Err>), - InvalidDateFormat, + ParserError(I, ErrorKind), + KeywordNotFound, EmptyValue, + InvalidFormat, } #[derive(PartialEq, Debug)] pub enum KvnDeserializerErr { - String(KvnStringLineParserErr), - DateTime(KvnDateTimeParserErr), - Number(KvnNumberLineParserErr), + InvalidDateTimeFormat, + InvalidNumberFormat, + KeywordNotFound, + EmptyValue, UnexpectedEndOfInput, + GeneralParserError(I, ErrorKind), +} + +impl From>> for KvnDeserializerErr { + fn from(value: nom::Err>) -> Self { + match value { + nom::Err::Error(KvnParserErr::EmptyValue) + | nom::Err::Failure(KvnParserErr::EmptyValue) => KvnDeserializerErr::EmptyValue, + nom::Err::Error(KvnParserErr::KeywordNotFound) + | nom::Err::Failure(KvnParserErr::KeywordNotFound) => { + KvnDeserializerErr::KeywordNotFound + } + nom::Err::Error(KvnParserErr::ParserError(i, k)) + | nom::Err::Failure(KvnParserErr::ParserError(i, k)) => { + KvnDeserializerErr::GeneralParserError(i, k) + } + // We don't use streaming deserialization + nom::Err::Incomplete(_) => unimplemented!(), + } + } +} + +impl From>> for KvnDeserializerErr { + fn from(value: nom::Err>) -> Self { + match value { + nom::Err::Error(KvnDateTimeParserErr::EmptyValue) + | nom::Err::Failure(KvnDateTimeParserErr::EmptyValue) => KvnDeserializerErr::EmptyValue, + nom::Err::Error(KvnDateTimeParserErr::KeywordNotFound) + | nom::Err::Failure(KvnDateTimeParserErr::KeywordNotFound) => { + KvnDeserializerErr::KeywordNotFound + } + nom::Err::Error(KvnDateTimeParserErr::InvalidFormat) + | nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat) => { + KvnDeserializerErr::InvalidDateTimeFormat + } + nom::Err::Error(KvnDateTimeParserErr::ParserError(i, k)) + | nom::Err::Failure(KvnDateTimeParserErr::ParserError(i, k)) => { + KvnDeserializerErr::GeneralParserError(i, k) + } + // We don't use streaming deserialization + nom::Err::Incomplete(_) => unimplemented!(), + } + } +} + +impl From>> for KvnDeserializerErr { + fn from(value: nom::Err>) -> Self { + match value { + nom::Err::Error(KvnNumberLineParserErr::EmptyValue) + | nom::Err::Failure(KvnNumberLineParserErr::EmptyValue) => { + KvnDeserializerErr::EmptyValue + } + nom::Err::Error(KvnNumberLineParserErr::KeywordNotFound) + | nom::Err::Failure(KvnNumberLineParserErr::KeywordNotFound) => { + KvnDeserializerErr::KeywordNotFound + } + nom::Err::Error(KvnNumberLineParserErr::InvalidFormat) + | nom::Err::Failure(KvnNumberLineParserErr::InvalidFormat) => { + KvnDeserializerErr::InvalidNumberFormat + } + nom::Err::Error(KvnNumberLineParserErr::ParserError(i, k)) + | nom::Err::Failure(KvnNumberLineParserErr::ParserError(i, k)) => { + KvnDeserializerErr::GeneralParserError(i, k) + } + // We don't use streaming deserialization + nom::Err::Incomplete(_) => unimplemented!(), + } + } +} + +impl ParseError for KvnParserErr { + fn from_error_kind(input: I, kind: ErrorKind) -> Self { + KvnParserErr::ParserError(input, kind) + } + + fn append(_: I, _: ErrorKind, other: Self) -> Self { + other + } } #[derive(PartialEq, Debug, Default)] @@ -64,11 +147,11 @@ pub struct KvnDateTimeValue { pub trait KvnDeserializer { fn deserialize<'a>( - lines: &mut dyn Iterator, + lines: &mut std::iter::Peekable>, ) -> Result>; } -fn comment_line<'a>(input: &'a str) -> nom::IResult<&'a str, &'a str> { +fn comment_line<'a>(input: &'a str) -> nom::IResult<&'a str, &'a str, KvnParserErr<&'a str>> { let (input, _) = nc::space0(input)?; let (remaining, _) = nb::tag("COMMENT ")(input)?; @@ -76,12 +159,28 @@ fn comment_line<'a>(input: &'a str) -> nom::IResult<&'a str, &'a str> { Ok(("", remaining)) } -fn kvn_line<'a>(key: &'a str, input: &'a str) -> nom::IResult<&'a str, (&'a str, &'a str)> { - let equals = ns::tuple(( - nc::space0::<_, nom::error::Error<_>>, - nc::char('='), - nc::space0, - )); +pub fn kvn_line_matches_key<'a>(key: &'a str, input: &'a str) -> bool { + let mut equals = ns::tuple((nc::space0::<_, nom::error::Error<_>>, nc::char('='))); + + // This function should return true in the case of malformed lines which + // are missing the keyword + if equals(input).is_ok() { + return true; + } + + ns::delimited(nc::space0, nb::tag(key), equals)(input).is_ok() +} + +fn kvn_line<'a>( + key: &'a str, + input: &'a str, +) -> nom::IResult<&'a str, (&'a str, &'a str), KvnParserErr<&'a str>> { + let mut equals = ns::tuple((nc::space0::<_, KvnParserErr<_>>, nc::char('='), nc::space0)); + + match equals(input) { + Ok(_) => return Err(nom::Err::Failure(KvnParserErr::KeywordNotFound)), + Err(_) => (), + } let value = nc::not_line_ending; @@ -94,10 +193,9 @@ pub fn parse_kvn_string_line<'a>( key: &'a str, input: &'a str, with_unit: bool, -) -> Result<(&'a str, KvnStringValue), KvnStringLineParserErr<&'a str>> { +) -> nom::IResult<&'a str, KvnStringValue, KvnParserErr<&'a str>> { if key == "COMMENT" { - let (_, comment) = - comment_line(input).map_err(|e| KvnStringLineParserErr::ParseError(e))?; + let (_, comment) = comment_line(input)?; return Ok(( "", @@ -108,8 +206,7 @@ pub fn parse_kvn_string_line<'a>( )); } - let (remaining, result) = - kvn_line(key, input).map_err(|e| KvnStringLineParserErr::ParseError(e))?; + let (remaining, result) = kvn_line(key, input)?; let parsed_right_hand_side = result.1; @@ -129,7 +226,7 @@ pub fn parse_kvn_string_line<'a>( }; if parsed_value.len() == 0 { - return Err(KvnStringLineParserErr::EmptyValue); + return Err(nom::Err::Failure(KvnParserErr::EmptyValue)); } let parsed_value = parsed_value.trim_end(); @@ -147,18 +244,35 @@ pub fn parse_kvn_integer_line<'a>( key: &'a str, input: &'a str, with_unit: bool, -) -> Result<(&'a str, KvnIntegerValue), KvnNumberLineParserErr<&'a str>> { +) -> nom::IResult<&'a str, KvnIntegerValue, KvnNumberLineParserErr<&'a str>> { parse_kvn_string_line(key, input, with_unit) .map_err(|e| match e { - KvnStringLineParserErr::EmptyValue => KvnNumberLineParserErr::EmptyValue, - KvnStringLineParserErr::ParseError(e) => KvnNumberLineParserErr::ParseError(e), + nom::Err::Failure(KvnParserErr::EmptyValue) => { + nom::Err::Failure(KvnNumberLineParserErr::EmptyValue) + } + nom::Err::Error(KvnParserErr::EmptyValue) => { + nom::Err::Error(KvnNumberLineParserErr::EmptyValue) + } + nom::Err::Failure(KvnParserErr::ParserError(input, code)) => { + nom::Err::Failure(KvnNumberLineParserErr::ParserError(input, code)) + } + nom::Err::Error(KvnParserErr::ParserError(input, code)) => { + nom::Err::Error(KvnNumberLineParserErr::ParserError(input, code)) + } + nom::Err::Failure(KvnParserErr::KeywordNotFound) => { + nom::Err::Failure(KvnNumberLineParserErr::KeywordNotFound) + } + nom::Err::Error(KvnParserErr::KeywordNotFound) => { + nom::Err::Error(KvnNumberLineParserErr::KeywordNotFound) + } + nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed), }) .and_then(|result| { let value = result .1 .value .parse::() - .map_err(|_| KvnNumberLineParserErr::InvalidFormat)?; + .map_err(|_| nom::Err::Failure(KvnNumberLineParserErr::InvalidFormat))?; Ok(( "", @@ -174,15 +288,32 @@ pub fn parse_kvn_numeric_line<'a>( key: &'a str, input: &'a str, with_unit: bool, -) -> Result<(&'a str, KvnNumericValue), KvnNumberLineParserErr<&'a str>> { +) -> nom::IResult<&'a str, KvnNumericValue, KvnNumberLineParserErr<&'a str>> { parse_kvn_string_line(key, input, with_unit) .map_err(|e| match e { - KvnStringLineParserErr::EmptyValue => KvnNumberLineParserErr::EmptyValue, - KvnStringLineParserErr::ParseError(e) => KvnNumberLineParserErr::ParseError(e), + nom::Err::Failure(KvnParserErr::EmptyValue) => { + nom::Err::Failure(KvnNumberLineParserErr::EmptyValue) + } + nom::Err::Error(KvnParserErr::EmptyValue) => { + nom::Err::Error(KvnNumberLineParserErr::EmptyValue) + } + nom::Err::Failure(KvnParserErr::ParserError(input, code)) => { + nom::Err::Failure(KvnNumberLineParserErr::ParserError(input, code)) + } + nom::Err::Error(KvnParserErr::ParserError(input, code)) => { + nom::Err::Error(KvnNumberLineParserErr::ParserError(input, code)) + } + nom::Err::Failure(KvnParserErr::KeywordNotFound) => { + nom::Err::Failure(KvnNumberLineParserErr::KeywordNotFound) + } + nom::Err::Error(KvnParserErr::KeywordNotFound) => { + nom::Err::Error(KvnNumberLineParserErr::KeywordNotFound) + } + nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed), }) .and_then(|result| { let value = fast_float::parse(result.1.value) - .map_err(|_| KvnNumberLineParserErr::InvalidFormat)?; + .map_err(|_| nom::Err::Failure(KvnNumberLineParserErr::InvalidFormat))?; Ok(( "", @@ -197,13 +328,33 @@ pub fn parse_kvn_numeric_line<'a>( pub fn parse_kvn_datetime_line<'a>( key: &'a str, input: &'a str, -) -> Result<(&'a str, KvnDateTimeValue), KvnDateTimeParserErr<&'a str>> { - let (_, result) = kvn_line(key, input).map_err(|e| KvnDateTimeParserErr::ParseError(e))?; +) -> nom::IResult<&'a str, KvnDateTimeValue, KvnDateTimeParserErr<&'a str>> { + let (_, result) = kvn_line(key, input).map_err(|e| match e { + nom::Err::Failure(KvnParserErr::EmptyValue) => { + nom::Err::Failure(KvnDateTimeParserErr::EmptyValue) + } + nom::Err::Error(KvnParserErr::EmptyValue) => { + nom::Err::Error(KvnDateTimeParserErr::EmptyValue) + } + nom::Err::Failure(KvnParserErr::ParserError(input, code)) => { + nom::Err::Failure(KvnDateTimeParserErr::ParserError(input, code)) + } + nom::Err::Error(KvnParserErr::ParserError(input, code)) => { + nom::Err::Error(KvnDateTimeParserErr::ParserError(input, code)) + } + nom::Err::Failure(KvnParserErr::KeywordNotFound) => { + nom::Err::Failure(KvnDateTimeParserErr::KeywordNotFound) + } + nom::Err::Error(KvnParserErr::KeywordNotFound) => { + nom::Err::Error(KvnDateTimeParserErr::KeywordNotFound) + } + nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed), + })?; let parsed_value = result.1; if parsed_value.len() == 0 { - return Err(KvnDateTimeParserErr::EmptyValue); + return Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyValue)); } let parsed_value = parsed_value.trim_end(); @@ -217,7 +368,7 @@ pub fn parse_kvn_datetime_line<'a>( let captures = re .captures(parsed_value) - .ok_or(KvnDateTimeParserErr::InvalidDateFormat)?; + .ok_or(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat))?; // yr is a mandatory decimal in the regex so we expect the capture to be // always there and unwrap is fine @@ -275,8 +426,18 @@ pub fn parse_kvn_datetime_line<'a>( } mod test { + use lox_derive::KvnDeserialize; + use super::*; + #[test] + fn test_kvn_line_matches_key() { + assert!(!kvn_line_matches_key("ASD", "AAAAAD123 = ASDFG")); + assert!(kvn_line_matches_key("ASD", "ASD = ASDFG")); + assert!(!kvn_line_matches_key("ASD", "ASD ASD = ASDFG")); + assert!(kvn_line_matches_key("ASD", "= ASDFG")); + } + #[test] fn test_parse_kvn_string_line() { // 7.5.1 A non-empty value field must be assigned to each mandatory keyword except for *‘_START’ and *‘_STOP’ keyword values @@ -313,15 +474,15 @@ mod test { ); assert_eq!( parse_kvn_string_line("ASD", "ASD = ", true), - Err(KvnStringLineParserErr::EmptyValue) + Err(nom::Err::Failure(KvnParserErr::EmptyValue)) ); assert_eq!( parse_kvn_string_line("ASD", "ASD = ", true), - Err(KvnStringLineParserErr::EmptyValue) + Err(nom::Err::Failure(KvnParserErr::EmptyValue)) ); assert_eq!( parse_kvn_string_line("ASD", "ASD =", true), - Err(KvnStringLineParserErr::EmptyValue) + Err(nom::Err::Failure(KvnParserErr::EmptyValue)) ); // 7.4.7 Any white space immediately preceding the end of line shall not be significant. @@ -361,26 +522,19 @@ mod test { assert_eq!( parse_kvn_string_line("ASD", "ASD = [km]", true), - Err(KvnStringLineParserErr::EmptyValue) + Err(nom::Err::Failure(KvnParserErr::EmptyValue)) ); assert_eq!( parse_kvn_string_line("ASD", "ASD [km]", true), - Err(KvnStringLineParserErr::ParseError(nom::Err::Error( - nom::error::Error { - input: "[km]", - code: nom::error::ErrorKind::Char - } + Err(nom::Err::Error(KvnParserErr::ParserError( + "[km]", + nom::error::ErrorKind::Char ))) ); assert_eq!( parse_kvn_string_line("ASD", " = [km]", true), - Err(KvnStringLineParserErr::ParseError(nom::Err::Error( - nom::error::Error { - input: "= [km]", - code: nom::error::ErrorKind::Tag - } - ))) + Err(nom::Err::Failure(KvnParserErr::KeywordNotFound)) ); // 7.4.5 Any white space immediately preceding or following the keyword shall not be significant. @@ -489,12 +643,12 @@ mod test { assert_eq!( parse_kvn_integer_line("SCLK_OFFSET_AT_EPOCH", "SCLK_OFFSET_AT_EPOCH = -asd", true), - Err(KvnNumberLineParserErr::InvalidFormat) + Err(nom::Err::Failure(KvnNumberLineParserErr::InvalidFormat)) ); assert_eq!( parse_kvn_integer_line("SCLK_OFFSET_AT_EPOCH", "SCLK_OFFSET_AT_EPOCH = [s]", true), - Err(KvnNumberLineParserErr::EmptyValue) + Err(nom::Err::Failure(KvnNumberLineParserErr::EmptyValue)) ); } @@ -572,17 +726,118 @@ mod test { assert_eq!( parse_kvn_datetime_line("CREATION_DATE", "CREATION_DATE = 2021,06,03Q05!33!00-123"), - Err(KvnDateTimeParserErr::InvalidDateFormat) + Err(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat)) ); assert_eq!( parse_kvn_datetime_line("CREATION_DATE", "CREATION_DATE = asdffggg"), - Err(KvnDateTimeParserErr::InvalidDateFormat) + Err(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat)) ); assert_eq!( parse_kvn_datetime_line("CREATION_DATE", "CREATION_DATE = "), - Err(KvnDateTimeParserErr::EmptyValue) + Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyValue)) + ); + } + + #[derive(KvnDeserialize, Default, Debug, PartialEq)] + pub struct KvnStruct { + comment: KvnStringValue, + string_value: KvnStringValue, + date_value: KvnDateTimeValue, + numeric_value: KvnNumericValue, + option_numeric_value: Option, + integer_value: KvnIntegerValue, + option_date_value: Option, + } + + #[test] + fn test_parse_whole_struct() { + let kvn = r#"COMMENT Generated by GSOC, R. Kiehling +STRING_VALUE = EUTELSAT W4 +DATE_VALUE = 2021-06-03T05:33:00.123 +NUMERIC_VALUE = 6655.9942 [km] +INTEGER_VALUE = 123 [km] +OPTION_DATE_VALUE = 2021-06-03T05:33:00.123"#; + + assert_eq!( + KvnStruct::deserialize(&mut kvn.lines().peekable()), + Ok(KvnStruct { + comment: KvnValue { + value: "Generated by GSOC, R. Kiehling".to_string(), + unit: None + }, + string_value: KvnValue { + value: "EUTELSAT W4".to_string(), + unit: None + }, + date_value: KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 0, + fractional_second: 0.123 + }, + numeric_value: KvnValue { + value: 6655.9942, + unit: Some("km".to_string()) + }, + option_numeric_value: None, + integer_value: KvnValue { + value: 123, + unit: Some("km".to_string()) + }, + option_date_value: Some(KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 0, + fractional_second: 0.123 + }) + }) + ); + + let kvn = r#"COMMENT Generated by GSOC, R. Kiehling +STRING_VALUE = EUTELSAT W4 +DATE_VALUE = 2021-06-03T05:33:00.123 +NUMERIC_VALUE = 6655.9942 [km] +INTEGER_VALUE = 123 [km]"#; + + assert_eq!( + KvnStruct::deserialize(&mut kvn.lines().peekable()), + Ok(KvnStruct { + comment: KvnValue { + value: "Generated by GSOC, R. Kiehling".to_string(), + unit: None + }, + string_value: KvnValue { + value: "EUTELSAT W4".to_string(), + unit: None + }, + date_value: KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 0, + fractional_second: 0.123 + }, + numeric_value: KvnValue { + value: 6655.9942, + unit: Some("km".to_string()) + }, + option_numeric_value: None, + integer_value: KvnValue { + value: 123, + unit: Some("km".to_string()) + }, + option_date_value: None + }) ); } } diff --git a/crates/lox_derive/Cargo.toml b/crates/lox_derive/Cargo.toml index 2d426b84..2b68007f 100644 --- a/crates/lox_derive/Cargo.toml +++ b/crates/lox_derive/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" proc-macro = true [dependencies] +proc-macro2 = "1.0.81" quote = "1.0.20" -syn = "1.0.98" \ No newline at end of file +syn = "1.0.98" diff --git a/crates/lox_derive/src/lib.rs b/crates/lox_derive/src/lib.rs index 47d4f163..2d49d50e 100644 --- a/crates/lox_derive/src/lib.rs +++ b/crates/lox_derive/src/lib.rs @@ -7,7 +7,96 @@ */ use quote::quote; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Field}; + +fn generate_call_to_deserializer_for_kvn_type( + type_name: &str, + expected_kvn_name: &str, + field: &Field, +) -> Result { + match type_name { + "KvnDateTimeValue" => Ok(quote! { + parse_kvn_datetime_line( + #expected_kvn_name, + line, + ).map_err(|x| KvnDeserializerErr::from(x)) + .map(|x| x.1) + }), + "KvnStringValue" => Ok(quote! { + parse_kvn_string_line( + #expected_kvn_name, + line, + true + ).map_err(|x| KvnDeserializerErr::from(x)) + .map(|x| x.1) + }), + "KvnNumericValue" => Ok(quote! { + parse_kvn_numeric_line( + #expected_kvn_name, + line, + true + ).map_err(|x| KvnDeserializerErr::from(x)) + .map(|x| x.1) + }), + "KvnIntegerValue" => Ok(quote! { + parse_kvn_integer_line( + #expected_kvn_name, + line, + true + ).map_err(|x| KvnDeserializerErr::from(x)) + .map(|x| x.1) + }), + _ => Err(syn::Error::new_spanned( + &field, + "unsupported field type for `#[derive(KvnDeserialize)]`", + ) + .into_compile_error() + .into()), + } +} + +fn generate_call_to_deserializer_for_option_type( + expected_kvn_name: &str, + field: &Field, +) -> Result { + if let syn::Type::Path(type_path) = &field.ty { + let path_part = type_path.path.segments.first(); + if let Some(path_part) = path_part { + if let syn::PathArguments::AngleBracketed(type_argument) = &path_part.arguments { + let type_name = type_argument + .args + .first() + .unwrap() + .span() + .source_text() + .unwrap(); + + let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type( + &type_name.as_ref(), + &expected_kvn_name, + &field, + )?; + + return Ok(quote! { + lines.next_if(|line| + kvn_line_matches_key( + #expected_kvn_name, + line + ) + ).map(|line| { + #deserializer_for_kvn_type + }).transpose()? + }); + } + } + } + + return Err( + syn::Error::new_spanned(&field, "Malformed type for `#[derive(KvnDeserialize)]`") + .into_compile_error() + .into(), + ); +} #[proc_macro_derive(KvnDeserialize)] pub fn derive_kvn_deserialize(item: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -47,47 +136,37 @@ pub fn derive_kvn_deserialize(item: proc_macro::TokenStream) -> proc_macro::Toke // Unwrap is okay because we expect this span to come from the source code let field_type = field.ty.span().source_text().unwrap(); - let (parser_function, parser_error_type, parser_unit_parameter) = - match field_type.as_str() { - "KvnDateTimeValue" => ( - quote! { parse_kvn_datetime_line }, - quote! { KvnDeserializerErr::DateTime}, - quote! {}, - ), - "KvnStringValue" => ( - quote! { parse_kvn_string_line }, - quote! { KvnDeserializerErr::String }, - //@TODO make unit dynamic according to the definition - quote! { ,true }, - ), - "KvnNumericValue" => ( - quote! { parse_kvn_numeric_line }, - quote! { KvnDeserializerErr::Number }, - //@TODO make unit dynamic according to the definition - quote! { ,true }, - ), - "KvnIntegerValue" => ( - quote! { parse_kvn_integer_line }, - quote! { KvnDeserializerErr::Number }, - //@TODO make unit dynamic according to the definition - quote! { ,true }, - ), - _ => { - return Err(syn::Error::new_spanned( - &field, - "unsupported field type for `#[derive(KvnDeserialize)]`", - ) - .into_compile_error() - .into()); + let parser = match field_type.as_str() { + "KvnDateTimeValue" | "KvnStringValue" | "KvnNumericValue" | "KvnIntegerValue" => { + let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type( + &field_type, + &expected_kvn_name, + &field, + )?; + + quote! { + lines.next().map_or_else( + || Err(KvnDeserializerErr::<&str>::UnexpectedEndOfInput), + |line| { + #deserializer_for_kvn_type + })? } - }; + } + "Option" => { + generate_call_to_deserializer_for_option_type(&expected_kvn_name, &field)? + } + _ => { + return Err(syn::Error::new_spanned( + &field, + "unsupported field type for `#[derive(KvnDeserialize)]`", + ) + .into_compile_error() + .into()); + } + }; Ok(quote! { - #field_name: #parser_function( - #expected_kvn_name, - lines.next().ok_or(KvnDeserializerErr::UnexpectedEndOfInput)? - #parser_unit_parameter - ).map_err(|x| #parser_error_type(x))?.1, + #field_name: #parser, }) }) .collect(); @@ -101,13 +180,23 @@ pub fn derive_kvn_deserialize(item: proc_macro::TokenStream) -> proc_macro::Toke let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl(); let deserializer = quote! { + use std::iter::Peekable; + use crate::ndm::kvn::parser::{KvnDeserializer, KvnDeserializerErr}; - use crate::ndm::kvn::parser::{parse_kvn_string_line, parse_kvn_datetime_line, parse_kvn_numeric_line, parse_kvn_integer_line}; + use crate::ndm::kvn::parser::{ + kvn_line_matches_key, + parse_kvn_string_line, + parse_kvn_datetime_line, + parse_kvn_numeric_line, + parse_kvn_integer_line + }; impl #impl_generics KvnDeserializer<#name> for #name #type_generics #where_clause { - fn deserialize<'a>(lines: &mut dyn Iterator) -> Result<#name, KvnDeserializerErr<&'a str>> { + fn deserialize<'a>(lines: &mut std::iter::Peekable>) + -> Result<#name, KvnDeserializerErr<&'a str>> { + Ok(#name { #(#field_initializers)* })