Skip to main content

php_ast/ast/
bytes.rs

1use std::borrow::Cow;
2
3#[derive(Clone, Copy)]
4enum ByteStrInner<'arena, 'src> {
5    Src(&'src str),
6    ArenaStr(&'arena str),
7    ArenaBytes(&'arena [u8]),
8}
9
10/// The value of a PHP string literal: arbitrary bytes, which need not be valid UTF-8
11/// (`"\xff"`, `"\u{D800}"`).
12///
13/// A literal without escapes borrows the source buffer; any other value lives in the bump arena.
14/// [`to_str`](ByteStr::to_str) is `Some` when the bytes are valid UTF-8. With the `serde`
15/// feature, valid UTF-8 serializes as a JSON string and any other value as `{"bytes": [..]}`.
16#[derive(Clone, Copy)]
17pub struct ByteStr<'arena, 'src>(ByteStrInner<'arena, 'src>);
18
19impl<'arena, 'src> ByteStr<'arena, 'src> {
20    /// Borrows `s` from the source buffer.
21    #[inline]
22    pub fn from_source(s: &'src str) -> Self {
23        Self(ByteStrInner::Src(s))
24    }
25
26    /// Wraps a string already allocated in the bump arena.
27    #[inline]
28    pub fn from_arena_str(s: &'arena str) -> Self {
29        Self(ByteStrInner::ArenaStr(s))
30    }
31
32    /// Copies `bytes` into `arena`.
33    pub fn new_in(arena: &'arena bumpalo::Bump, bytes: &[u8]) -> Self {
34        match std::str::from_utf8(bytes) {
35            Ok(s) => Self(ByteStrInner::ArenaStr(arena.alloc_str(s))),
36            Err(_) => Self(ByteStrInner::ArenaBytes(arena.alloc_slice_copy(bytes))),
37        }
38    }
39
40    /// The bytes of the string.
41    #[inline]
42    pub fn as_bytes(&self) -> &[u8] {
43        match self.0 {
44            ByteStrInner::Src(s) | ByteStrInner::ArenaStr(s) => s.as_bytes(),
45            ByteStrInner::ArenaBytes(b) => b,
46        }
47    }
48
49    /// The value as `&str`, or `None` when it is not valid UTF-8.
50    #[inline]
51    pub fn to_str(&self) -> Option<&str> {
52        match self.0 {
53            ByteStrInner::Src(s) | ByteStrInner::ArenaStr(s) => Some(s),
54            ByteStrInner::ArenaBytes(_) => None,
55        }
56    }
57
58    /// The value as text, replacing invalid UTF-8 with `U+FFFD`.
59    pub fn to_string_lossy(&self) -> Cow<'_, str> {
60        match self.0 {
61            ByteStrInner::Src(s) | ByteStrInner::ArenaStr(s) => Cow::Borrowed(s),
62            ByteStrInner::ArenaBytes(b) => String::from_utf8_lossy(b),
63        }
64    }
65
66    /// The source slice, when the value borrows the source buffer.
67    #[inline]
68    pub(crate) fn into_src_str(self) -> Option<&'src str> {
69        match self.0 {
70            ByteStrInner::Src(s) => Some(s),
71            _ => None,
72        }
73    }
74}
75
76impl<'arena, 'src> std::ops::Deref for ByteStr<'arena, 'src> {
77    type Target = [u8];
78    #[inline]
79    fn deref(&self) -> &[u8] {
80        self.as_bytes()
81    }
82}
83
84impl<'arena, 'src, 'a, 'b> PartialEq<ByteStr<'a, 'b>> for ByteStr<'arena, 'src> {
85    fn eq(&self, other: &ByteStr<'a, 'b>) -> bool {
86        self.as_bytes() == other.as_bytes()
87    }
88}
89
90impl<'arena, 'src> Eq for ByteStr<'arena, 'src> {}
91
92impl<'arena, 'src> PartialEq<str> for ByteStr<'arena, 'src> {
93    fn eq(&self, other: &str) -> bool {
94        self.as_bytes() == other.as_bytes()
95    }
96}
97
98impl<'arena, 'src> PartialEq<&str> for ByteStr<'arena, 'src> {
99    fn eq(&self, other: &&str) -> bool {
100        self.as_bytes() == other.as_bytes()
101    }
102}
103
104impl<'arena, 'src> PartialEq<[u8]> for ByteStr<'arena, 'src> {
105    fn eq(&self, other: &[u8]) -> bool {
106        self.as_bytes() == other
107    }
108}
109
110impl<'arena, 'src> std::hash::Hash for ByteStr<'arena, 'src> {
111    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
112        self.as_bytes().hash(state)
113    }
114}
115
116impl<'arena, 'src> std::fmt::Debug for ByteStr<'arena, 'src> {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        match self.to_str() {
119            Some(s) => std::fmt::Debug::fmt(s, f),
120            None => write!(f, "b{:?}", self.as_bytes().escape_ascii().to_string()),
121        }
122    }
123}
124
125#[cfg(feature = "serde")]
126impl<'arena, 'src> serde::Serialize for ByteStr<'arena, 'src> {
127    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
128        serialize_bytes(self.as_bytes(), serializer)
129    }
130}
131
132/// Valid UTF-8 as a string, anything else as `{"bytes": [..]}`.
133#[cfg(feature = "serde")]
134pub(crate) fn serialize_bytes<S: serde::Serializer>(
135    bytes: &[u8],
136    serializer: S,
137) -> Result<S::Ok, S::Error> {
138    use serde::ser::SerializeMap;
139    match std::str::from_utf8(bytes) {
140        Ok(s) => serializer.serialize_str(s),
141        Err(_) => {
142            let mut map = serializer.serialize_map(Some(1))?;
143            map.serialize_entry("bytes", bytes)?;
144            map.end()
145        }
146    }
147}