Skip to content

Commit

Permalink
Add Encode::None to be able to encode vector / SIMD types
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed May 9, 2024
1 parent 9c18639 commit bafa30b
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 36 deletions.
51 changes: 51 additions & 0 deletions crates/objc2-encode/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ pub enum Encoding {
/// Note that the `=` may be omitted in some situations; this is
/// considered equal to the case where there are no members.
Union(&'static str, &'static [Encoding]),
/// The type does not have an Objective-C encoding.
///
/// This is usually only used on types where Clang fails to generate the
/// Objective-C encoding, like SIMD types marked with
/// `__attribute__((__ext_vector_type__(1)))`.
None,
// TODO: "Vector" types have the '!' encoding, but are not implemented in
// clang

Expand Down Expand Up @@ -260,6 +266,7 @@ impl fmt::Display for Encoding {
mod tests {
use super::*;
use crate::static_str::{static_encoding_str_array, static_encoding_str_len};
use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec;
use core::str::FromStr;
Expand Down Expand Up @@ -602,6 +609,32 @@ mod tests {
!"^{_CGLContextObject}";
!"^{SomeOtherStruct=}";
}
fn none() {
Encoding::None;
"";
!"?";
}
fn none_in_array() {
Encoding::Array(42, &Encoding::None);
!Encoding::Array(42, &Encoding::Unknown);
"[42]";
!"[42i]";
}
fn none_in_pointer() {
Encoding::Pointer(&Encoding::None);
!Encoding::Pointer(&Encoding::Unknown);
"^";
!"";
!"^i";
}
fn none_in_pointer_in_array() {
Encoding::Array(42, &Encoding::Pointer(&Encoding::None));
"[42^]";
}
}
#[test]
Expand Down Expand Up @@ -640,4 +673,22 @@ mod tests {

assert!(!enc.equivalent_to_box(&expected));
}

// Similar to `?`, `` cannot be accurately represented inside pointers
// inside structs, and may be parsed incorrectly.
#[test]
fn none_in_struct() {
let enc = Encoding::Struct("?", &[Encoding::Pointer(&Encoding::None), Encoding::Int]);
let s = "{?=^i}";
assert_eq!(&enc.to_string(), s);

let parsed = EncodingBox::from_str(s).unwrap();
let expected = EncodingBox::Struct(
"?".to_string(),
vec![EncodingBox::Pointer(Box::new(EncodingBox::Int))],
);
assert_eq!(parsed, expected);

assert!(!enc.equivalent_to_box(&expected));
}
}
6 changes: 4 additions & 2 deletions crates/objc2-encode/src/encoding_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ pub enum EncodingBox {
Struct(String, Vec<Self>),
/// Same as [`Encoding::Union`].
Union(String, Vec<Self>),
/// Same as [`Encoding::None`].
None,
}

impl EncodingBox {
Expand Down Expand Up @@ -120,7 +122,7 @@ impl EncodingBox {
let mut parser = Parser::new(s);
parser.strip_leading_qualifiers();

match parser.parse_encoding() {
match parser.parse_encoding_or_none() {
Err(err) => Err(ParseError::new(parser, err)),
Ok(encoding) => {
let remaining = parser.remaining();
Expand Down Expand Up @@ -159,7 +161,7 @@ impl FromStr for EncodingBox {
parser.strip_leading_qualifiers();

parser
.parse_encoding()
.parse_encoding_or_none()
.and_then(|enc| parser.expect_empty().map(|()| enc))
.map_err(|err| ParseError::new(parser, err))
}
Expand Down
5 changes: 5 additions & 0 deletions crates/objc2-encode/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub(crate) fn compare_encodings<E1: EncodingType, E2: EncodingType>(
}
}
}
(NoneInvalid, NoneInvalid) => true,
(_, _) => false,
}
}
Expand Down Expand Up @@ -242,6 +243,7 @@ pub(crate) enum Helper<'a, E = Encoding> {
Indirection(IndirectionKind, &'a E),
Array(u64, &'a E),
Container(ContainerKind, &'a str, &'a [E]),
NoneInvalid,
}

impl<E: EncodingType> Helper<'_, E> {
Expand Down Expand Up @@ -279,6 +281,7 @@ impl<E: EncodingType> Helper<'_, E> {
}
write!(f, "{}", kind.end())?;
}
Self::NoneInvalid => {}
}
Ok(())
}
Expand Down Expand Up @@ -328,6 +331,7 @@ impl Helper<'_> {
}
Self::Container(ContainerKind::Union, name, members)
}
None => Self::NoneInvalid,
}
}
}
Expand Down Expand Up @@ -376,6 +380,7 @@ impl<'a> Helper<'a, EncodingBox> {
}
Self::Container(ContainerKind::Union, name, members)
}
None => Self::NoneInvalid,
}
}
}
Expand Down
142 changes: 110 additions & 32 deletions crates/objc2-encode/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ impl fmt::Display for ErrorKind {

type Result<T, E = ErrorKind> = core::result::Result<T, E>;

enum ParseInner {
Empty,
Encoding(EncodingBox),
ContainerEnd(ContainerKind),
ArrayEnd,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct Parser<'a> {
data: &'a str,
Expand Down Expand Up @@ -142,6 +149,10 @@ impl<'a> Parser<'a> {
self.split_point += 1;
}

fn rollback(&mut self) {
self.split_point -= 1;
}

fn consume_while(&mut self, mut condition: impl FnMut(u8) -> bool) {
while let Some(b) = self.try_peek() {
if condition(b) {
Expand Down Expand Up @@ -288,11 +299,25 @@ impl Parser<'_> {
self.expect_byte(b'=')?;
// Parse as equal if the container is empty
if items.is_empty() {
while self.try_peek() != Some(kind.end_byte()) {
let _ = self.parse_encoding().ok()?;
loop {
match self.parse_inner().ok()? {
ParseInner::Empty => {
// Require the container to have an end
return None;
}
ParseInner::Encoding(_) => {}
ParseInner::ContainerEnd(parsed_kind) => {
if parsed_kind == kind {
return Some(());
} else {
return None;
}
}
ParseInner::ArrayEnd => {
return None;
}
}
}
self.advance();
return Some(());
}
// Parse as equal if the string's container is empty
if self.try_peek() == Some(kind.end_byte()) {
Expand All @@ -305,6 +330,7 @@ impl Parser<'_> {
}
self.expect_byte(kind.end_byte())
}
Helper::NoneInvalid => Some(()),
}
}
}
Expand Down Expand Up @@ -337,34 +363,44 @@ impl Parser<'_> {
let mut items = Vec::new();
// Parse items until hits end
loop {
let b = self.try_peek().ok_or(ErrorKind::WrongEndContainer(kind))?;
if b == kind.end_byte() {
self.advance();
break;
} else {
// Wasn't the end, so try to extract one more encoding
items.push(self.parse_encoding()?);
match self.parse_inner()? {
ParseInner::Empty => {
return Err(ErrorKind::WrongEndContainer(kind));
}
ParseInner::Encoding(enc) => {
items.push(enc);
}
ParseInner::ContainerEnd(parsed_kind) => {
if parsed_kind == kind {
return Ok((s, items));
} else {
return Err(ErrorKind::Unknown(parsed_kind.end_byte()));
}
}
ParseInner::ArrayEnd => {
return Err(ErrorKind::Unknown(b']'));
}
}
}
Ok((s, items))
}

pub(crate) fn parse_encoding(&mut self) -> Result<EncodingBox> {
self.try_parse_encoding()
.and_then(|res| res.ok_or(ErrorKind::UnexpectedEnd))
pub(crate) fn parse_encoding_or_none(&mut self) -> Result<EncodingBox> {
match self.parse_inner()? {
ParseInner::Empty => Ok(EncodingBox::None),
ParseInner::Encoding(enc) => Ok(enc),
ParseInner::ContainerEnd(kind) => Err(ErrorKind::Unknown(kind.end_byte())),
ParseInner::ArrayEnd => Err(ErrorKind::Unknown(b']')),
}
}

fn try_parse_encoding(&mut self) -> Result<Option<EncodingBox>> {
Ok(if let Some(b) = self.try_peek() {
self.advance();
Some(self.parse_encoding_inner(b)?)
} else {
None
})
}
fn parse_inner(&mut self) -> Result<ParseInner> {
if self.is_empty() {
return Ok(ParseInner::Empty);
}
let b = self.peek()?;
self.advance();

fn parse_encoding_inner(&mut self, b: u8) -> Result<EncodingBox> {
Ok(match b {
Ok(ParseInner::Encoding(match b {
b'c' => EncodingBox::Char,
b's' => EncodingBox::Short,
b'i' => EncodingBox::Int,
Expand Down Expand Up @@ -422,26 +458,59 @@ impl Parser<'_> {
EncodingBox::BitField(size, None)
}
}
b'^' => EncodingBox::Pointer(Box::new(self.parse_encoding()?)),
b'A' => EncodingBox::Atomic(Box::new(self.parse_encoding()?)),
b'^' => EncodingBox::Pointer(Box::new(match self.parse_inner()? {
ParseInner::Empty => EncodingBox::None,
ParseInner::Encoding(enc) => enc,
ParseInner::ContainerEnd(_) | ParseInner::ArrayEnd => {
self.rollback();
EncodingBox::None
}
})),
b'A' => EncodingBox::Atomic(Box::new(match self.parse_inner()? {
ParseInner::Empty => EncodingBox::None,
ParseInner::Encoding(enc) => enc,
ParseInner::ContainerEnd(_) | ParseInner::ArrayEnd => {
self.rollback();
EncodingBox::None
}
})),
b'[' => {
let len = self.parse_u64()?;
let item = self.parse_encoding()?;
self.expect_byte(b']').ok_or(ErrorKind::WrongEndArray)?;
EncodingBox::Array(len, Box::new(item))
match self.parse_inner()? {
ParseInner::Empty => {
return Err(ErrorKind::WrongEndArray);
}
ParseInner::Encoding(item) => {
self.expect_byte(b']').ok_or(ErrorKind::WrongEndArray)?;
EncodingBox::Array(len, Box::new(item))
}
ParseInner::ArrayEnd => EncodingBox::Array(len, Box::new(EncodingBox::None)),
ParseInner::ContainerEnd(kind) => {
return Err(ErrorKind::Unknown(kind.end_byte()))
}
}
}
b']' => {
return Ok(ParseInner::ArrayEnd);
}
b'{' => {
let kind = ContainerKind::Struct;
let (name, items) = self.parse_container(kind)?;
EncodingBox::Struct(name.to_string(), items)
}
b'}' => {
return Ok(ParseInner::ContainerEnd(ContainerKind::Struct));
}
b'(' => {
let kind = ContainerKind::Union;
let (name, items) = self.parse_container(kind)?;
EncodingBox::Union(name.to_string(), items)
}
b')' => {
return Ok(ParseInner::ContainerEnd(ContainerKind::Union));
}
b => return Err(ErrorKind::Unknown(b)),
})
}))
}

fn try_parse_bitfield_gnustep(&mut self) -> Result<Option<(u8, EncodingBox)>> {
Expand Down Expand Up @@ -516,7 +585,7 @@ mod tests {
let mut parser = Parser::new(enc);
assert_eq!(
parser
.parse_encoding()
.parse_encoding_or_none()
.and_then(|enc| parser.expect_empty().map(|()| enc)),
expected
);
Expand Down Expand Up @@ -550,4 +619,13 @@ mod tests {
);
assert_bitfield("b2000C257", Err(ErrorKind::IntegerTooLarge));
}

#[test]
fn parse_closing() {
let mut parser = Parser::new("]");
assert_eq!(
parser.parse_encoding_or_none(),
Err(ErrorKind::Unknown(b']'))
);
}
}
2 changes: 2 additions & 0 deletions crates/objc2-encode/src/static_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub(crate) const fn static_encoding_str_len(encoding: &Encoding, level: NestingL
}
res + 1
}
NoneInvalid => 0,
}
}

Expand Down Expand Up @@ -208,6 +209,7 @@ pub(crate) const fn static_encoding_str_array<const LEN: usize>(

res[res_i] = kind.end_byte();
}
NoneInvalid => {}
};
res
}
Expand Down
Loading

0 comments on commit bafa30b

Please sign in to comment.