use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::{Add, AddAssign, Deref};
use std::str::Utf8Error;
use std::sync::Arc;
use ecow::{EcoString, eco_format};
use serde::{Serialize, Serializer};
use typst_syntax::Lines;
use typst_utils::LazyHash;
use crate::diag::{StrResult, bail};
use crate::foundations::{Array, Reflect, Repr, Str, Value, cast, func, scope, ty};
#[ty(scope, cast)]
#[derive(Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct Bytes(Arc<LazyHash<dyn Bytelike>>);
impl Bytes {
pub fn new<T>(data: T) -> Self
where
T: AsRef<[u8]> + Send + Sync + 'static,
{
Self(Arc::new(LazyHash::new(data)))
}
pub fn from_string<T>(data: T) -> Self
where
T: AsRef<str> + Send + Sync + 'static,
{
Self(Arc::new(LazyHash::new(StrWrapper(data))))
}
pub fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
pub fn as_slice(&self) -> &[u8] {
self
}
pub fn as_str(&self) -> Result<&str, Utf8Error> {
self.inner().as_str()
}
pub fn to_vec(&self) -> Vec<u8> {
self.as_slice().to_vec()
}
pub fn to_str(&self) -> Result<Str, Utf8Error> {
match self.inner().as_any().downcast_ref::<Str>() {
Some(string) => Ok(string.clone()),
None => self.as_str().map(Into::into),
}
}
fn locate(&self, index: i64) -> StrResult<usize> {
self.locate_opt(index).ok_or_else(|| out_of_bounds(index, self.len()))
}
fn locate_opt(&self, index: i64) -> Option<usize> {
let len = self.as_slice().len();
let wrapped =
if index >= 0 { Some(index) } else { (len as i64).checked_add(index) };
wrapped.and_then(|v| usize::try_from(v).ok()).filter(|&v| v <= len)
}
fn inner(&self) -> &dyn Bytelike {
&**self.0
}
}
#[scope]
impl Bytes {
#[func(constructor)]
pub fn construct(
value: ToBytes,
) -> Bytes {
value.0
}
#[func(title = "Length")]
pub fn len(&self) -> usize {
self.as_slice().len()
}
#[func]
pub fn at(
&self,
index: i64,
#[named]
default: Option<Value>,
) -> StrResult<Value> {
self.locate_opt(index)
.and_then(|i| self.as_slice().get(i).map(|&b| Value::Int(b.into())))
.or(default)
.ok_or_else(|| out_of_bounds_no_default(index, self.len()))
}
#[func]
pub fn slice(
&self,
start: i64,
#[default]
end: Option<i64>,
#[named]
count: Option<i64>,
) -> StrResult<Bytes> {
let start = self.locate(start)?;
let end = end.or(count.map(|c| start as i64 + c));
let end = self.locate(end.unwrap_or(self.len() as i64))?.max(start);
let slice = &self.as_slice()[start..end];
Ok(Bytes::new(slice.to_vec()))
}
}
impl Debug for Bytes {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Bytes({})", self.len())
}
}
impl Repr for Bytes {
fn repr(&self) -> EcoString {
eco_format!("bytes({})", self.len())
}
}
impl Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.inner().as_bytes()
}
}
impl Eq for Bytes {}
impl PartialEq for Bytes {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self
}
}
impl Add for Bytes {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
self += rhs;
self
}
}
impl AddAssign for Bytes {
fn add_assign(&mut self, rhs: Self) {
if rhs.is_empty() {
} else if self.is_empty() {
*self = rhs;
} else if let Some(vec) = Arc::get_mut(&mut self.0)
.and_then(|unique| unique.as_any_mut().downcast_mut::<Vec<u8>>())
{
vec.extend_from_slice(&rhs);
} else {
*self = Self::new([self.as_slice(), rhs.as_slice()].concat());
}
}
}
impl Serialize for Bytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&self.repr())
} else {
serializer.serialize_bytes(self)
}
}
}
impl TryFrom<&Bytes> for Lines<String> {
type Error = Utf8Error;
#[comemo::memoize]
fn try_from(value: &Bytes) -> Result<Lines<String>, Utf8Error> {
let text = value.as_str()?;
Ok(Lines::new(text.to_string()))
}
}
trait Bytelike: Send + Sync {
fn as_bytes(&self) -> &[u8];
fn as_str(&self) -> Result<&str, Utf8Error>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T> Bytelike for T
where
T: AsRef<[u8]> + Send + Sync + 'static,
{
fn as_bytes(&self) -> &[u8] {
self.as_ref()
}
fn as_str(&self) -> Result<&str, Utf8Error> {
std::str::from_utf8(self.as_ref())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Hash for dyn Bytelike {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
struct StrWrapper<T>(T);
impl<T> Bytelike for StrWrapper<T>
where
T: AsRef<str> + Send + Sync + 'static,
{
fn as_bytes(&self) -> &[u8] {
self.0.as_ref().as_bytes()
}
fn as_str(&self) -> Result<&str, Utf8Error> {
Ok(self.0.as_ref())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub struct ToBytes(Bytes);
cast! {
ToBytes,
v: Str => Self(Bytes::from_string(v)),
v: Array => Self(v.iter()
.map(|item| match item {
Value::Int(byte @ 0..=255) => Ok(*byte as u8),
Value::Int(_) => bail!("number must be between 0 and 255"),
value => Err(<u8 as Reflect>::error(value)),
})
.collect::<Result<Vec<u8>, _>>()
.map(Bytes::new)?
),
v: Bytes => Self(v),
}
#[cold]
fn out_of_bounds(index: i64, len: usize) -> EcoString {
eco_format!("byte index out of bounds (index: {index}, len: {len})")
}
#[cold]
fn out_of_bounds_no_default(index: i64, len: usize) -> EcoString {
eco_format!(
"byte index out of bounds (index: {index}, len: {len}) \
and no default value was specified",
)
}