use crate::{
context::Shared,
types::TypeId,
value::{
Value, ValueId,
util::base_ref::{BaseRef, WithShared},
},
};
use jstd::Identifier;
#[derive(Identifier)]
pub struct BytesId(usize);
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Bytes {
pub data: Vec<u8>,
pub type_id: TypeId,
}
pub type BytesRef<'str, 'ctx> = BaseRef<&'ctx Shared<'str>, BytesId>;
impl<'s, 'ctx: 's, 'str: 'ctx> WithShared<'s, 'ctx, 'str> for BytesRef<'str, 'ctx> {
fn shared(&'s self) -> &'ctx Shared<'str> {
self.ctx
}
}
impl<'s, 'ctx: 's, 'str: 'ctx, Ctx> BaseRef<Ctx, BytesId>
where
Self: WithShared<'s, 'ctx, 'str>,
{
fn inner(&'s self) -> &'ctx Bytes {
&self.shared().values.bytes[self.id]
}
pub fn data(&'s self) -> &'ctx [u8] {
&self.inner().data
}
pub fn type_id(&'s self) -> TypeId {
self.inner().type_id
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StringEncoding {
Ascii,
Utf16Le,
}
impl StringEncoding {
pub fn label(self) -> &'static str {
match self {
StringEncoding::Ascii => "ascii",
StringEncoding::Utf16Le => "utf16le",
}
}
}
pub fn decode_string(data: &[u8]) -> Option<(StringEncoding, String)> {
if data.is_empty() {
return None;
}
let ascii = data.strip_suffix(&[0]).unwrap_or(data);
if !ascii.is_empty() && ascii.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
return Some((
StringEncoding::Ascii,
ascii.iter().map(|&b| b as char).collect(),
));
}
if data.len() >= 2 && data.len().is_multiple_of(2) {
let (pairs, _) = data.as_chunks::<2>();
let units: Vec<u16> = pairs.iter().map(|&pair| u16::from_le_bytes(pair)).collect();
let units = units.strip_suffix(&[0]).unwrap_or(&units);
if !units.is_empty()
&& units
.iter()
.all(|&u| u < 0x80 && (u as u8).is_ascii_graphic() || u == b' ' as u16)
{
let s: String = units.iter().map(|&u| u as u8 as char).collect();
return Some((StringEncoding::Utf16Le, s));
}
}
None
}
pub fn escape_decoded(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'"' | '\\' => {
out.push('\\');
out.push(c);
}
_ => out.push(c),
}
}
out
}
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum BytesDisplay {
#[default]
Auto,
Ascii,
Utf16Le,
Raw,
}
fn push_ascii_byte(out: &mut String, b: u8) {
match b {
b'"' | b'\\' => {
out.push('\\');
out.push(b as char);
}
_ if b.is_ascii_graphic() || b == b' ' => out.push(b as char),
_ => out.push_str(&format!("\\x{b:02x}")),
}
}
pub fn render_bytes_literal(data: &[u8], mode: BytesDisplay) -> String {
let mut out = String::new();
out.push_str("b\"");
match mode {
BytesDisplay::Auto => {
if let Some((_, s)) = decode_string(data) {
out.push_str(&escape_decoded(&s));
} else {
for &b in data {
out.push_str(&format!("\\x{b:02x}"));
}
}
}
BytesDisplay::Ascii => {
for &b in data {
push_ascii_byte(&mut out, b);
}
}
BytesDisplay::Utf16Le => {
let (pairs, remainder) = data.as_chunks::<2>();
for &pair in pairs {
let unit = u16::from_le_bytes(pair);
match char::from_u32(unit as u32) {
Some(ch) if !ch.is_control() => match ch {
'"' | '\\' => {
out.push('\\');
out.push(ch);
}
_ => out.push(ch),
},
_ => out.push_str(&format!("\\u{{{unit:04x}}}")),
}
}
for &b in remainder {
out.push_str(&format!("\\x{b:02x}"));
}
}
BytesDisplay::Raw => {
for &b in data {
out.push_str(&format!("\\x{b:02x}"));
}
}
}
out.push('"');
out
}
impl std::fmt::Display for BytesRef<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let data = &self.ctx.values.bytes[self.id].data;
let mode = self.ctx.bytes_display(self.id);
if mode != BytesDisplay::Auto {
return f.write_str(&render_bytes_literal(data, mode));
}
if let Some((_, s)) = decode_string(data) {
return write!(f, "b\"{}\"", escape_decoded(&s));
}
write!(f, "b\"")?;
for &b in data {
write!(f, "\\x{:02x}", b)?;
}
write!(f, "\"")
}
}
impl<'str, 'ctx> Value<'str, 'ctx> for BytesRef<'str, 'ctx> {
fn id(&self) -> ValueId {
ValueId::Bytes(self.id)
}
fn size(&self) -> usize {
self.ctx.values.bytes[self.id].data.len()
}
}