use std::borrow::Cow;
use std::fmt;
use std::ops::Deref;
#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AwkStr(Vec<u8>);
impl AwkStr {
#[inline]
pub fn new() -> Self {
AwkStr(Vec::new())
}
#[inline]
pub const fn new_const() -> Self {
AwkStr(Vec::new())
}
#[inline]
pub fn with_capacity(n: usize) -> Self {
AwkStr(Vec::with_capacity(n))
}
#[inline]
pub fn from_vec(v: Vec<u8>) -> Self {
AwkStr(v)
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[inline]
pub fn as_mut_vec(&mut self) -> &mut Vec<u8> {
&mut self.0
}
#[inline]
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
#[inline]
pub fn as_utf8(&self) -> Option<&str> {
std::str::from_utf8(&self.0).ok()
}
#[inline]
pub fn to_str_lossy(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.0)
}
#[inline]
pub fn to_lossy_string(&self) -> String {
String::from_utf8_lossy(&self.0).into_owned()
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
#[inline]
pub fn push_str(&mut self, s: &str) {
self.0.extend_from_slice(s.as_bytes());
}
#[inline]
pub fn push_bytes(&mut self, b: &[u8]) {
self.0.extend_from_slice(b);
}
#[inline]
pub fn push_byte(&mut self, b: u8) {
self.0.push(b);
}
#[inline]
pub fn push_char(&mut self, c: char) {
let mut buf = [0u8; 4];
self.0.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
#[inline]
pub fn push_awkstr(&mut self, other: &AwkStr) {
self.0.extend_from_slice(&other.0);
}
#[inline]
pub fn find_bytes(&self, needle: &[u8]) -> Option<usize> {
if needle.is_empty() {
return Some(0);
}
memchr::memmem::find(&self.0, needle)
}
#[inline]
pub fn contains_bytes(&self, needle: &[u8]) -> bool {
self.find_bytes(needle).is_some()
}
#[inline]
pub fn is_ascii(&self) -> bool {
self.0.is_ascii()
}
#[inline]
pub fn chars_lossy(&self) -> impl Iterator<Item = char> + '_ {
self.0.utf8_chunks().flat_map(|c| {
c.valid()
.chars()
.chain(std::iter::repeat_n('\u{fffd}', c.invalid().len()))
})
}
#[inline]
pub fn char_offset(&self, n: usize) -> usize {
let mut i = 0usize;
let mut seen = 0usize;
while seen < n && i < self.0.len() {
i += crate::runtime::utf8_char_len(&self.0[i..]);
seen += 1;
}
i
}
#[inline]
pub fn substr_chars(&self, start: usize, count: usize) -> AwkStr {
let from = self.char_offset(start);
let mut i = from;
let mut taken = 0usize;
while taken < count && i < self.0.len() {
i += crate::runtime::utf8_char_len(&self.0[i..]);
taken += 1;
}
AwkStr(self.0[from..i].to_vec())
}
#[inline]
pub fn substr_bytes(&self, start: usize, count: usize) -> AwkStr {
if start >= self.0.len() {
return AwkStr::new();
}
let end = start.saturating_add(count).min(self.0.len());
AwkStr(self.0[start..end].to_vec())
}
#[inline]
pub fn slice(&self, start: usize, end: usize) -> AwkStr {
let s = start.min(self.0.len());
let e = end.clamp(s, self.0.len());
AwkStr(self.0[s..e].to_vec())
}
}
impl Deref for AwkStr {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
&self.0
}
}
impl From<String> for AwkStr {
#[inline]
fn from(s: String) -> Self {
AwkStr(s.into_bytes())
}
}
impl From<&String> for AwkStr {
#[inline]
fn from(s: &String) -> Self {
AwkStr(s.as_bytes().to_vec())
}
}
impl From<&str> for AwkStr {
#[inline]
fn from(s: &str) -> Self {
AwkStr(s.as_bytes().to_vec())
}
}
impl From<Vec<u8>> for AwkStr {
#[inline]
fn from(v: Vec<u8>) -> Self {
AwkStr(v)
}
}
impl From<&[u8]> for AwkStr {
#[inline]
fn from(b: &[u8]) -> Self {
AwkStr(b.to_vec())
}
}
impl From<Cow<'_, str>> for AwkStr {
#[inline]
fn from(c: Cow<'_, str>) -> Self {
AwkStr(c.into_owned().into_bytes())
}
}
impl From<char> for AwkStr {
#[inline]
fn from(c: char) -> Self {
let mut s = AwkStr::new();
s.push_char(c);
s
}
}
impl PartialEq<str> for AwkStr {
#[inline]
fn eq(&self, other: &str) -> bool {
self.0 == other.as_bytes()
}
}
impl PartialEq<&str> for AwkStr {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.0 == other.as_bytes()
}
}
impl PartialEq<String> for AwkStr {
#[inline]
fn eq(&self, other: &String) -> bool {
self.0 == other.as_bytes()
}
}
impl PartialEq<AwkStr> for str {
#[inline]
fn eq(&self, other: &AwkStr) -> bool {
self.as_bytes() == other.0
}
}
impl PartialEq<AwkStr> for &str {
#[inline]
fn eq(&self, other: &AwkStr) -> bool {
self.as_bytes() == other.0
}
}
impl PartialEq<[u8]> for AwkStr {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
self.0 == other
}
}
impl fmt::Display for AwkStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.to_str_lossy(), f)
}
}
impl fmt::Debug for AwkStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_utf8() {
Some(s) => fmt::Debug::fmt(s, f),
None => {
f.write_str("\"")?;
for chunk in self.0.utf8_chunks() {
for c in chunk.valid().chars() {
write!(f, "{}", c.escape_debug())?;
}
for b in chunk.invalid() {
write!(f, "\\x{b:02x}")?;
}
}
f.write_str("\"")
}
}
}
}
impl fmt::Write for AwkStr {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
}
impl std::borrow::Borrow<[u8]> for AwkStr {
#[inline]
fn borrow(&self) -> &[u8] {
&self.0
}
}
impl FromIterator<u8> for AwkStr {
fn from_iter<I: IntoIterator<Item = u8>>(iter: I) -> Self {
AwkStr(iter.into_iter().collect())
}
}
impl FromIterator<char> for AwkStr {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let mut s = AwkStr::new();
for c in iter {
s.push_char(c);
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_high_byte_survives_the_round_trip() {
let s = AwkStr::from_vec(vec![b'a', 0xe9, b'b']);
assert_eq!(s.as_bytes(), &[b'a', 0xe9, b'b']);
assert_eq!(s.len(), 3, "length is bytes, not characters");
assert!(s.as_utf8().is_none(), "not valid UTF-8, and not pretended");
assert_eq!(s.to_str_lossy(), "a\u{fffd}b");
assert_eq!(s.clone().into_bytes(), vec![b'a', 0xe9, b'b']);
}
#[test]
fn valid_utf8_borrows_rather_than_copies() {
let s = AwkStr::from("café");
let borrowed = s.as_utf8().expect("valid UTF-8");
assert_eq!(borrowed, "café");
assert_eq!(s.len(), 5, "5 bytes, 4 characters");
assert!(matches!(s.to_str_lossy(), Cow::Borrowed(_)));
}
#[test]
fn equality_and_ordering_are_over_bytes() {
assert_eq!(AwkStr::from("abc"), "abc");
let (abc, abd) = (AwkStr::from("abc"), AwkStr::from("abd"));
assert!(abc < abd);
let (z, high) = (AwkStr::from("z"), AwkStr::from_vec(vec![0xff]));
assert!(z < high);
}
#[test]
fn debug_shows_text_with_escapes_for_unnameable_bytes() {
assert_eq!(format!("{:?}", AwkStr::from("ab")), "\"ab\"");
assert_eq!(
format!("{:?}", AwkStr::from_vec(vec![b'a', 0xe9, b'b'])),
"\"a\\xe9b\""
);
}
#[test]
fn find_bytes_locates_a_byte_no_str_can_name() {
let s = AwkStr::from_vec(vec![b'a', 0xe9, b'b']);
assert_eq!(s.find_bytes(&[0xe9]), Some(1));
assert_eq!(s.find_bytes(b"b"), Some(2));
assert_eq!(s.find_bytes(b"zz"), None);
assert_eq!(s.find_bytes(b""), Some(0), "empty needle matches at 0");
}
#[test]
fn slice_is_by_byte_offset_and_clamps() {
let s = AwkStr::from_vec(vec![b'a', 0xe9, b'b']);
assert_eq!(s.slice(1, 2).as_bytes(), &[0xe9]);
assert_eq!(s.slice(0, 99).as_bytes(), &[b'a', 0xe9, b'b']);
assert_eq!(s.slice(5, 9).as_bytes(), b"");
}
}