//! Utility for parsing an Objective-C method type encoding. //! //! TODO: Move this to `objc2-encode` when more stable. use core::fmt; use core::num::ParseIntError; use std::error::Error; use crate::encode::{Encoding, EncodingBox, ParseError}; #[derive(Debug, PartialEq, Eq)] pub(crate) struct MethodEncodingIter<'a> { s: &'a str, } impl<'a> MethodEncodingIter<'a> { pub(crate) fn new(s: &'a str) -> Self { Self { s } } pub(crate) fn extract_return( &mut self, ) -> Result<(EncodingBox, Option), EncodingParseError> { // TODO: Verify stack layout self.next().ok_or(EncodingParseError::MissingReturn)? } pub(crate) fn verify_receiver(&mut self) -> Result<(), EncodingParseError> { // TODO: Verify stack layout let (enc, _stack_layout) = self.next().ok_or(EncodingParseError::MissingReceiver)??; if !Encoding::Object.equivalent_to_box(&enc) { return Err(EncodingParseError::InvalidReceiver(enc)); } Ok(()) } pub(crate) fn verify_sel(&mut self) -> Result<(), EncodingParseError> { let (enc, _stack_layout) = self.next().ok_or(EncodingParseError::MissingSel)??; if !Encoding::Sel.equivalent_to_box(&enc) { return Err(EncodingParseError::InvalidSel(enc)); } Ok(()) } fn extract_encoding(&mut self) -> Result<(EncodingBox, Option), EncodingParseError> { // See also the following other approaches: // objrs: https://gitlab.com/objrs/objrs/-/blob/b4f6598696b3fa622e6fddce7aff281770b0a8c2/src/test.rs // libobjc2: https://github.com/gnustep/libobjc2/blob/v2.1/encoding2.c // objc4: https://github.com/apple-oss-distributions/objc4/blob/objc4-841.13/runtime/objc-typeencoding.mm let encoding = EncodingBox::from_start_of_str(&mut self.s)?; let stack_layout = parse_stack_layout(&mut self.s)?; Ok((encoding, stack_layout)) } } impl Iterator for MethodEncodingIter<'_> { type Item = Result<(EncodingBox, Option), EncodingParseError>; fn next(&mut self) -> Option { if self.s.is_empty() { return None; } Some(self.extract_encoding()) } } // TODO: Is `isize` correct here? fn parse_stack_layout(s: &mut &str) -> Result, ParseIntError> { let rest = s.trim_start_matches(|c: char| c.is_ascii_digit() || c == '-' || c == '+'); let stack_layout = &s[..s.len() - rest.len()]; *s = rest; if stack_layout.is_empty() { return Ok(None); } stack_layout.parse().map(Some) } #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) enum EncodingParseError { ParseError(ParseError), InvalidStackLayoutInteger, MissingReturn, MissingReceiver, MissingSel, InvalidReceiver(EncodingBox), InvalidSel(EncodingBox), } impl From for EncodingParseError { fn from(e: ParseError) -> Self { Self::ParseError(e) } } impl From for EncodingParseError { fn from(_: ParseIntError) -> Self { Self::InvalidStackLayoutInteger } } impl fmt::Display for EncodingParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if !matches!(self, Self::ParseError(_)) { write!(f, "failed parsing encoding: ")?; } match self { Self::ParseError(e) => write!(f, "{e}")?, Self::InvalidStackLayoutInteger => write!(f, "invalid integer for stack layout")?, Self::MissingReturn => write!(f, "return type must be present")?, Self::MissingReceiver => write!(f, "receiver type must be present")?, Self::MissingSel => write!(f, "selector type must be present")?, Self::InvalidReceiver(enc) => { write!(f, "receiver encoding must be '@', but it was '{enc}'")?; } Self::InvalidSel(enc) => { write!(f, "selector encoding must be '@', but it was '{enc}'")?; } } write!(f, ". This is likely a bug, please report it!") } } impl Error for EncodingParseError {} #[cfg(test)] mod tests { use super::*; use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; fn assert_stack_layout(mut types: &str, expected: Option, rest: &str) { let sl = parse_stack_layout(&mut types).unwrap(); assert_eq!(sl, expected); assert_eq!(types, rest); } #[test] fn stack_layout_extract() { assert_stack_layout("", None, ""); assert_stack_layout("abc", None, "abc"); assert_stack_layout("abc12abc", None, "abc12abc"); assert_stack_layout("0", Some(0), ""); assert_stack_layout("1abc", Some(1), "abc"); assert_stack_layout("42def24", Some(42), "def24"); assert_stack_layout("1234567890xyz", Some(1234567890), "xyz"); assert_stack_layout("-1a", Some(-1), "a"); assert_stack_layout("-1a", Some(-1), "a"); // GNU runtime's register parameter hint?? assert_stack_layout("+1a", Some(1), "a"); } fn assert_encoding_extract(s: &str, expected: &[(EncodingBox, Option)]) { let actual: Vec<_> = MethodEncodingIter::new(s) .collect::>() .unwrap_or_else(|e| panic!("{}", e)); assert_eq!(&actual, expected); } #[test] fn parse_bitfield() { assert_encoding_extract( "@48@0:8Ad16r^*24{bitfield=b64b1}32i48", &[ (EncodingBox::Object, Some(48)), (EncodingBox::Object, Some(0)), (EncodingBox::Sel, Some(8)), (EncodingBox::Atomic(Box::new(EncodingBox::Double)), Some(16)), ( EncodingBox::Pointer(Box::new(EncodingBox::String)), Some(24), ), ( EncodingBox::Struct( "bitfield".into(), vec![ EncodingBox::BitField(64, None), EncodingBox::BitField(1, None), ], ), Some(32), ), (EncodingBox::Int, Some(48)), ], ); } #[test] fn parse_complex() { assert_encoding_extract( "jf16@0:8", &[ (EncodingBox::FloatComplex, Some(16)), (EncodingBox::Object, Some(0)), (EncodingBox::Sel, Some(8)), ], ); assert_encoding_extract( "jf@:", &[ (EncodingBox::FloatComplex, None), (EncodingBox::Object, None), (EncodingBox::Sel, None), ], ); } }