diff --git a/ruststep/src/parser/exchange/data.rs b/ruststep/src/parser/exchange/data.rs index cd0cc117c..ab2b1bf67 100644 --- a/ruststep/src/parser/exchange/data.rs +++ b/ruststep/src/parser/exchange/data.rs @@ -1,7 +1,5 @@ use crate::parser::{combinator::*, exchange::*, token::*}; -use inflector::Inflector; use nom::{branch::alt, Parser}; -use serde::{de, forward_to_deserialize_any}; #[derive(Debug, Clone, PartialEq)] pub struct DataSection { @@ -9,6 +7,12 @@ pub struct DataSection { pub entities: Vec, } +#[derive(Debug, Clone, PartialEq)] +pub enum EntityInstance { + Simple { name: u64, record: Record }, + Complex { name: u64, subsuper: Vec }, +} + /// data_section = `DATA` \[ `(` [parameter_list] `)` \] `;` [entity_instance_list] `ENDSEC;` . pub fn data_section(input: &str) -> ParseResult { tuple_(( @@ -32,12 +36,6 @@ pub fn entity_instance_list(input: &str) -> ParseResult> { many0_(entity_instance).parse(input) } -#[derive(Debug, Clone, PartialEq)] -pub enum EntityInstance { - Simple { name: u64, record: Record }, - Complex { name: u64, subsuper: Vec }, -} - /// entity_instance = [simple_entity_instance] | [complex_entity_instance] . pub fn entity_instance(input: &str) -> ParseResult { alt((simple_entity_instance, complex_entity_instance)).parse(input) @@ -62,41 +60,6 @@ pub fn complex_entity_instance(input: &str) -> ParseResult { .parse(input) } -/// A struct typed in EXPRESS schema -/// -/// serde::Deserialize -/// ------------------- -/// -/// Different from [Parameter], this checks the target struct name: -/// -/// ``` -/// use nom::Finish; -/// use serde::Deserialize; -/// use ruststep::parser::exchange; -/// -/// #[derive(Debug, Deserialize)] -/// struct MyStruct { -/// x: f64, -/// y: f64, -/// } -/// -/// // `MyStruct` as Rust struct must be parsed from `MY_STRUCT` STEP record -/// let (_, record) = exchange::simple_record("MY_STRUCT(1.0, 2.0)").finish().unwrap(); -/// let a: MyStruct = Deserialize::deserialize(&record).unwrap(); -/// -/// // Other type `YOUR_STRUCT` cannot be deserialized -/// // even if internal data `(f64, f64)` is matched. -/// let (_, record) = exchange::simple_record("YOUR_STRUCT(1.0, 2.0)").finish().unwrap(); -/// let a: Result = Deserialize::deserialize(&record); -/// assert!(a.is_err()); -/// ``` -/// -#[derive(Debug, Clone, PartialEq)] -pub struct Record { - pub name: String, - pub parameters: Vec, -} - /// simple_record = [keyword] `(` \[ [parameter_list] \] `)` . pub fn simple_record(input: &str) -> ParseResult { tuple_((keyword, char_('('), opt_(parameter_list), char_(')'))) @@ -119,49 +82,9 @@ pub fn subsuper_record(input: &str) -> ParseResult> { .parse(input) } -impl<'de, 'record> de::Deserializer<'de> for &'record Record { - type Error = crate::error::Error; - - fn deserialize_any(self, _visitor: V) -> Result - where - V: de::Visitor<'de>, - { - Err(de::Error::invalid_type( - de::Unexpected::Other("any"), - &self.name.as_str(), - )) - } - - fn deserialize_struct( - self, - name: &'static str, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - if name != self.name.to_pascal_case() { - return Err(de::Error::invalid_type( - de::Unexpected::StructVariant, - &self.name.as_str(), - )); - } - let seq = de::value::SeqDeserializer::new(self.parameters.iter()); - visitor.visit_seq(seq) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map enum identifier ignored_any - } -} - #[cfg(test)] mod tests { use nom::Finish; - use serde::Deserialize; #[test] fn simple_recode1() { @@ -180,27 +103,4 @@ mod tests { dbg!(record); assert_eq!(res, ""); } - - #[derive(Debug, Deserialize)] - struct MyStruct { - x: f64, - y: f64, - } - - #[test] - fn deserialize_record_to_struct() { - let (res, record) = super::simple_record("MY_STRUCT(1.0, 2.0)") - .finish() - .unwrap(); - assert_eq!(res, ""); - let a: MyStruct = Deserialize::deserialize(&record).unwrap(); - dbg!(a); - - let (res, record) = super::simple_record("YOUR_STRUCT(1.0, 2.0)") - .finish() - .unwrap(); - assert_eq!(res, ""); - let a: Result = Deserialize::deserialize(&record); - assert!(a.is_err()); - } } diff --git a/ruststep/src/parser/exchange/mod.rs b/ruststep/src/parser/exchange/mod.rs index da69ddea4..e31c73748 100644 --- a/ruststep/src/parser/exchange/mod.rs +++ b/ruststep/src/parser/exchange/mod.rs @@ -4,12 +4,14 @@ mod anchor; mod data; mod header; mod parameter; +mod record; mod reference; pub use anchor::*; pub use data::*; pub use header::*; pub use parameter::*; +pub use record::*; pub use reference::*; use crate::parser::{combinator::*, token::*}; diff --git a/ruststep/src/parser/exchange/parameter.rs b/ruststep/src/parser/exchange/parameter.rs index cfd8e861a..762593880 100644 --- a/ruststep/src/parser/exchange/parameter.rs +++ b/ruststep/src/parser/exchange/parameter.rs @@ -1,28 +1,8 @@ use crate::parser::{combinator::*, token::*}; +use inflector::Inflector; use nom::{branch::alt, combinator::value, Parser}; use serde::{de, forward_to_deserialize_any}; -#[derive(Debug, Clone, PartialEq)] -pub enum UntypedParameter { - Integer(i64), - Real(f64), - String(String), - Enumeration(String), - /// The special token dollar sign (`$`) is used to represent an object whose value is not provided in the exchange structure. - NotProvided, - /// A reference to entity or value, parsed by [rhs_occurrence_name] - RValue(RValue), - /// List of other parameters - List(Vec), -} - -/// list = `(` \[ [parameter] { `,` [parameter] } \] `)` . -pub fn list(input: &str) -> ParseResult { - tuple_((char_('('), comma_separated(parameter), char_(')'))) - .map(|(_open, params, _close)| UntypedParameter::List(params)) - .parse(input) -} - /// Primitive value type in STEP data, parsed by [parameter] /// /// Parse @@ -30,7 +10,7 @@ pub fn list(input: &str) -> ParseResult { /// /// ``` /// use nom::Finish; -/// use ruststep::parser::{Parameter, UntypedParameter, exchange}; +/// use ruststep::parser::{Parameter, exchange}; /// /// // Real number /// let (residual, p) = exchange::parameter("1.0").finish().unwrap(); @@ -47,30 +27,56 @@ pub fn list(input: &str) -> ParseResult { /// assert_eq!(residual, ""); /// assert_eq!(p, [Parameter::string("ruststep"), Parameter::real(1.0)].iter().collect()); /// -/// // typed +/// // inline typed struct /// let (residual, p) = exchange::parameter("FILE_NAME('ruststep')").finish().unwrap(); /// assert_eq!(residual, ""); /// assert!(matches!(p, Parameter::Typed { .. })); +/// +/// // inline struct or list can be nested, i.e. `Parameter` can be a tree. +/// let (residual, p) = exchange::parameter("B((1.0, A((2.0, 3.0))))").finish().unwrap(); +/// assert_eq!(residual, ""); +/// if let Parameter::Typed { name, ty } = p { +/// assert_eq!(name, "B"); +/// if let Parameter::List(parameters) = *ty { +/// assert_eq!(parameters.len(), 2); +/// assert_eq!(parameters[0], Parameter::real(1.0)); +/// if let Parameter::Typed { name, ty } = ¶meters[1] { +/// assert_eq!(name, "A"); +/// if let Parameter::List(inner) = &**ty { +/// assert_eq!(inner.len(), 2); +/// assert_eq!(inner[0], Parameter::real(2.0)); +/// assert_eq!(inner[1], Parameter::real(3.0)); +/// } +/// } else { +/// unreachable!() +/// } +/// } else { +/// unreachable!() +/// } +/// } else { +/// unreachable!() +/// } /// ``` /// /// FromIterator /// ------------- -/// Create a list as `Parameter::Untyped(UntypedParameter::List)` from `Iterator` -/// or `Iterator`. +/// Create a list as `Parameter::List` from `Iterator` or `Iterator`. /// /// ``` -/// use ruststep::parser::{Parameter, UntypedParameter}; +/// use ruststep::parser::Parameter; /// /// let p: Parameter = [Parameter::real(1.0), Parameter::real(2.0)] /// .iter() /// .collect(); -/// assert!(matches!(p, Parameter::Untyped(UntypedParameter::List(_)))); +/// assert!(matches!(p, Parameter::List(_))); /// ``` /// /// serde::Deserializer /// ------------------- /// /// This implements a [serde::Deserializer], i.e. a **data format**. +/// For untyped parameters, e.g. real number, can be deserialized into any types +/// as far as compatible in terms of the serde data model. /// /// ``` /// use serde::Deserialize; @@ -82,7 +88,7 @@ pub fn list(input: &str) -> ParseResult { /// y: f64, /// } /// -/// // Create a list as `Parameter::Untyped(UntypedParameter::List)` +/// // Create a list as `Parameter::List` /// let p: Parameter = [Parameter::real(1.0), Parameter::real(2.0)] /// .iter() /// .collect(); @@ -99,28 +105,70 @@ pub fn list(input: &str) -> ParseResult { /// assert!(result.is_err()); /// ``` /// +/// On the other hand, typed parameter, e.g. `A(1)`, must be deserialized into a struct +/// whose name is "A". +/// +/// ``` +/// use serde::Deserialize; +/// use ruststep::parser::{Parameter, exchange}; +/// use nom::Finish; +/// +/// #[derive(Debug, Deserialize)] +/// struct A { +/// x: f64, +/// y: f64, +/// } +/// +/// // can be deserialized into `A` +/// let (res, p) = exchange::parameter("A((1.0, 2.0))").finish().unwrap(); +/// assert_eq!(res, ""); +/// let a: A = Deserialize::deserialize(&p).unwrap(); +/// +/// // B(...) cannot be parsed as `A` +/// let (res, p) = exchange::parameter("B((1.0, 2.0))").finish().unwrap(); +/// assert_eq!(res, ""); +/// let a: Result = Deserialize::deserialize(&p); +/// assert!(a.is_err()); +/// ``` +/// /// [serde::Deserializer]: https://docs.serde.rs/serde/trait.Deserializer.html +/// #[derive(Debug, Clone, PartialEq)] pub enum Parameter { /// Inline *Typed* struct Typed { name: String, ty: Box }, - /// Primitive types e.g. integer. See [UntypedParameter] for detail. - Untyped(UntypedParameter), + + /// Signed integer + Integer(i64), + /// Real number + Real(f64), + /// string literal + String(String), + /// Enumeration defined in EXPRESS schema, like `.TRUE.` + Enumeration(String), + /// List of other parameters + List(Vec), + + /// A reference to entity or value, parsed by [rhs_occurrence_name] + RValue(RValue), + + /// The special token dollar sign (`$`) is used to represent an object whose value is not provided in the exchange structure. + NotProvided, /// Omitted parameter denoted by `*` Omitted, } impl Parameter { pub fn integer(i: i64) -> Self { - Parameter::Untyped(UntypedParameter::Integer(i)) + Parameter::Integer(i) } pub fn real(x: f64) -> Self { - Parameter::Untyped(UntypedParameter::Real(x)) + Parameter::Real(x) } pub fn string(s: &str) -> Self { - Parameter::Untyped(UntypedParameter::String(s.to_string())) + Parameter::String(s.to_string()) } } @@ -138,13 +186,13 @@ impl From for Parameter { impl From for Parameter { fn from(value: String) -> Self { - Parameter::Untyped(UntypedParameter::String(value)) + Parameter::String(value) } } impl std::iter::FromIterator for Parameter { fn from_iter>(iter: Iter) -> Self { - Parameter::Untyped(UntypedParameter::List(iter.into_iter().collect())) + Parameter::List(iter.into_iter().collect()) } } @@ -154,6 +202,13 @@ impl<'a> std::iter::FromIterator<&'a Parameter> for Parameter { } } +/// list = `(` \[ [parameter] { `,` [parameter] } \] `)` . +pub fn list(input: &str) -> ParseResult { + tuple_((char_('('), comma_separated(parameter), char_(')'))) + .map(|(_open, params, _close)| Parameter::List(params)) + .parse(input) +} + /// parameter = [typed_parameter] | [untyped_parameter] | [omitted_parameter] . pub fn parameter(input: &str) -> ParseResult { alt((typed_parameter, untyped_parameter, omitted_parameter)).parse(input) @@ -172,16 +227,15 @@ pub fn typed_parameter(input: &str) -> ParseResult { /// untyped_parameter = `$` | [integer] | [real] | [string] | [rhs_occurrence_name] | [enumeration] | binary | [list] . pub fn untyped_parameter(input: &str) -> ParseResult { alt(( - char_('$').map(|_| UntypedParameter::NotProvided), - real.map(UntypedParameter::Real), - integer.map(UntypedParameter::Integer), - string.map(UntypedParameter::String), - rhs_occurrence_name.map(UntypedParameter::RValue), - enumeration.map(UntypedParameter::Enumeration), + char_('$').map(|_| Parameter::NotProvided), + real.map(Parameter::Real), + integer.map(Parameter::Integer), + string.map(Parameter::String), + rhs_occurrence_name.map(Parameter::RValue), + enumeration.map(Parameter::Enumeration), // FIXME binary list, )) - .map(Parameter::Untyped) .parse(input) } @@ -203,25 +257,45 @@ impl<'de, 'param> de::Deserializer<'de> for &'param Parameter { V: de::Visitor<'de>, { match self { - Parameter::Typed { name: _, ty: _ } => unimplemented!(), - Parameter::Untyped(p) => match p { - UntypedParameter::Integer(val) => visitor.visit_i64(*val), - UntypedParameter::Real(val) => visitor.visit_f64(*val), - UntypedParameter::String(val) => visitor.visit_str(val), - UntypedParameter::List(params) => { - let seq = de::value::SeqDeserializer::new(params.iter()); - visitor.visit_seq(seq) - } - _ => unimplemented!(), - }, + Parameter::Typed { .. } => unimplemented!(), + Parameter::Integer(val) => visitor.visit_i64(*val), + Parameter::Real(val) => visitor.visit_f64(*val), + Parameter::String(val) => visitor.visit_str(val), + Parameter::List(params) => { + let seq = de::value::SeqDeserializer::new(params.iter()); + visitor.visit_seq(seq) + } Parameter::Omitted => unimplemented!(), + _ => unimplemented!(), + } + } + + fn deserialize_struct( + self, + struct_name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + if let Parameter::Typed { name, ty } = self { + if struct_name != name.to_pascal_case() { + return Err(de::Error::invalid_type( + de::Unexpected::Other(name), + &struct_name, + )); + } + ty.deserialize_any(visitor) + } else { + self.deserialize_any(visitor) } } forward_to_deserialize_any! { bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any + tuple_struct map enum identifier ignored_any } } @@ -235,26 +309,19 @@ impl<'de, 'param> de::IntoDeserializer<'de, crate::error::Error> for &'param Par #[cfg(test)] mod tests { use super::*; + use nom::Finish; use serde::Deserialize; - #[test] - fn list_from_iter() { - let l: Parameter = [Parameter::integer(1), Parameter::real(2.0)] - .iter() - .collect(); - assert!(matches!(l, Parameter::Untyped(UntypedParameter::List(_)))); - } - #[test] fn deserialize_int() { - let p = Parameter::Untyped(UntypedParameter::Integer(2)); + let p = Parameter::Integer(2); let a: i64 = Deserialize::deserialize(&p).unwrap(); assert_eq!(a, 2); // can be deserialized as unsigned let a: u32 = Deserialize::deserialize(&p).unwrap(); assert_eq!(a, 2); - let p = Parameter::Untyped(UntypedParameter::Integer(-2)); + let p = Parameter::Integer(-2); let a: i64 = Deserialize::deserialize(&p).unwrap(); assert_eq!(a, -2); // cannot be deserialized negative integer into unsigned @@ -268,12 +335,27 @@ mod tests { y: f64, } + #[derive(Debug, Deserialize)] + struct B { + z: f64, + a: A, + } + #[test] - fn deserialize_parameter_list_to_struct() { - let p: Parameter = [Parameter::real(1.0), Parameter::real(2.0)] - .iter() - .collect(); - let a: A = Deserialize::deserialize(&p).unwrap(); - dbg!(a); + fn deserialize_parameter_typed_nested() { + let (res, p) = super::parameter("B((1.0, A((2.0, 3.0))))") + .finish() + .unwrap(); + assert_eq!(res, ""); + let b: B = Deserialize::deserialize(dbg!(&p)).unwrap(); + dbg!(b); + + // C(...) should not be parsed as A + let (res, p) = super::parameter("B((1.0, C((2.0, 3.0))))") + .finish() + .unwrap(); + assert_eq!(res, ""); + let b: Result = Deserialize::deserialize(dbg!(&p)); + assert!(b.is_err()); } } diff --git a/ruststep/src/parser/exchange/record.rs b/ruststep/src/parser/exchange/record.rs new file mode 100644 index 000000000..abceef71b --- /dev/null +++ b/ruststep/src/parser/exchange/record.rs @@ -0,0 +1,106 @@ +use crate::parser::exchange::*; +use inflector::Inflector; +use serde::{de, forward_to_deserialize_any}; + +/// A struct typed in EXPRESS schema +/// +/// serde::Deserialize +/// ------------------- +/// +/// Similar to typed [Parameter], this checks the target struct name: +/// +/// ``` +/// use nom::Finish; +/// use serde::Deserialize; +/// use ruststep::parser::exchange; +/// +/// #[derive(Debug, Deserialize)] +/// struct MyStruct { +/// x: f64, +/// y: f64, +/// } +/// +/// // `MyStruct` as Rust struct must be parsed from `MY_STRUCT` STEP record +/// let (_, record) = exchange::simple_record("MY_STRUCT(1.0, 2.0)").finish().unwrap(); +/// let a: MyStruct = Deserialize::deserialize(&record).unwrap(); +/// +/// // Other type `YOUR_STRUCT` cannot be deserialized +/// // even if internal data `(f64, f64)` is matched. +/// let (_, record) = exchange::simple_record("YOUR_STRUCT(1.0, 2.0)").finish().unwrap(); +/// let a: Result = Deserialize::deserialize(&record); +/// assert!(a.is_err()); +/// ``` +/// +#[derive(Debug, Clone, PartialEq)] +pub struct Record { + pub name: String, + pub parameters: Vec, +} + +impl<'de, 'record> de::Deserializer<'de> for &'record Record { + type Error = crate::error::Error; + + fn deserialize_any(self, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(de::Error::invalid_type( + de::Unexpected::Other("any"), + &self.name.as_str(), + )) + } + + fn deserialize_struct( + self, + name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + if name != self.name.to_pascal_case() { + return Err(de::Error::invalid_type( + de::Unexpected::StructVariant, + &self.name.as_str(), + )); + } + let seq = de::value::SeqDeserializer::new(self.parameters.iter()); + visitor.visit_seq(seq) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map enum identifier ignored_any + } +} + +#[cfg(test)] +mod tests { + use nom::Finish; + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + struct MyStruct { + x: f64, + y: f64, + } + + #[test] + fn deserialize_record_to_struct() { + let (res, record) = super::simple_record("MY_STRUCT(1.0, 2.0)") + .finish() + .unwrap(); + assert_eq!(res, ""); + let a: MyStruct = Deserialize::deserialize(&record).unwrap(); + dbg!(a); + + let (res, record) = super::simple_record("YOUR_STRUCT(1.0, 2.0)") + .finish() + .unwrap(); + assert_eq!(res, ""); + let a: Result = Deserialize::deserialize(&record); + assert!(a.is_err()); + } +} diff --git a/ruststep/src/parser/mod.rs b/ruststep/src/parser/mod.rs index dd9d953ac..e8800ebd4 100644 --- a/ruststep/src/parser/mod.rs +++ b/ruststep/src/parser/mod.rs @@ -40,7 +40,7 @@ pub mod token; use crate::error::{Result, TokenizeFailed}; use nom::Finish; -pub use exchange::{Parameter, Record, UntypedParameter}; +pub use exchange::{Parameter, Record}; /// Parse HEADER section ///