Source code

Revision control

Copy as Markdown

Other Tools

//! 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<isize>), 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<isize>), EncodingParseError> {
// See also the following other approaches:
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<isize>), EncodingParseError>;
fn next(&mut self) -> Option<Self::Item> {
if self.s.is_empty() {
return None;
}
Some(self.extract_encoding())
}
}
// TODO: Is `isize` correct here?
fn parse_stack_layout(s: &mut &str) -> Result<Option<isize>, 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<ParseError> for EncodingParseError {
fn from(e: ParseError) -> Self {
Self::ParseError(e)
}
}
impl From<ParseIntError> 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<isize>, 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<isize>)]) {
let actual: Vec<_> = MethodEncodingIter::new(s)
.collect::<Result<_, _>>()
.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),
],
);
}
}