use crate::alloc::FragmentBuf;
use crate::core::error::{Error, ErrorKind};
use crate::core::{Chunk, Fragment, Ident};
use crate::syntax::{Boundary, Delimiter, Profile, UnitDelimiter};
use core::ops::{Bound, RangeBounds};
use std_alloc::borrow::ToOwned;
use std_alloc::boxed::Box;
use std_alloc::collections::TryReserveError;
use std_alloc::string::String;
pub struct IdentBuf<B, D, P> {
inner: FragmentBuf<B, D, P>,
}
impl<B: Boundary, D: Delimiter, P: Profile> IdentBuf<B, D, P> {
#[must_use]
#[inline]
pub(crate) fn can_push_fragment_char(left: Option<&Ident<B, D, P>>, right: char) -> bool {
match left {
None => D::is_ident_start(right) || P::is_ident_start(right),
Some(left) => FragmentBuf::can_push_fragment_char(left, right),
}
}
#[must_use]
#[inline]
pub(crate) fn can_join(left: Option<&Ident<B, D, P>>, right: &Fragment<B, D, P>) -> bool {
right
.chars()
.next()
.is_none_or(|c| Self::can_push_fragment_char(left, c))
}
#[inline]
#[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Result<Self, Error> {
core::str::FromStr::from_str(s)
}
#[inline]
pub fn from_string(s: String) -> Result<Self, Error> {
let _ = Ident::<B, D, P>::new(&s)?;
Ok(Self::from_string_unchecked(s))
}
#[doc = include_str!("docs/methods/insert_fragment.md")]
#[doc = include_str!("docs/sections/panics.md")]
#[doc = include_str!("docs/sections/errors.md")]
#[inline]
pub fn insert_fragment(
&mut self,
idx: usize,
fragment: &Fragment<B, D, P>,
) -> Result<(), Error> {
let Some(first) = fragment.chars().next() else {
return Ok(());
};
if idx == 0 && !D::is_ident_start(first) && !P::is_ident_start(first) {
return Err(Error::new(ErrorKind::InvalidFormat).with_byte_offset(0));
}
self.inner.insert_fragment(idx, fragment)
}
#[doc = include_str!("docs/methods/insert_bounded_fragment.md")]
#[doc = include_str!("docs/sections/panics.md")]
#[doc = include_str!("docs/sections/errors.md")]
#[inline]
pub fn insert_bounded_fragment_with(
&mut self,
idx: usize,
fragment: &Fragment<B, D, P>,
delim: D,
) -> Result<(), Error> {
let Some(first) = fragment.chars().next() else {
return Ok(());
};
if idx == 0 && !D::is_ident_start(first) && !P::is_ident_start(first) {
return Err(Error::new(ErrorKind::InvalidFormat).with_byte_offset(0));
}
self.inner
.insert_bounded_fragment_with(idx, fragment, delim)
}
#[doc = include_str!("docs/methods/insert_delimited_fragment.md")]
#[doc = include_str!("docs/sections/panics.md")]
#[doc = include_str!("docs/sections/errors.md")]
#[inline]
pub fn insert_delimited_fragment_with(
&mut self,
idx: usize,
fragment: &Fragment<B, D, P>,
delim: D,
) -> Result<(), Error> {
let Some(first) = fragment.chars().next() else {
return Ok(());
};
if idx == 0 && !D::is_ident_start(first) && !P::is_ident_start(first) {
return Err(Error::new(ErrorKind::InvalidFormat).with_byte_offset(0));
}
self.inner
.insert_delimited_fragment_with(idx, fragment, delim)
}
#[doc = include_str!("docs/methods/push_delim.md")]
#[doc = include_str!("docs/methods/push_delim.errors.md")]
#[inline]
pub fn push_delim_with(&mut self, delim: D) -> Result<(), Error> {
if !D::APPEND_CLOSED.at_least_identifier()
&& self.inner.is_empty()
&& !D::is_ident_start(delim.as_char())
{
return Err(Error::new(ErrorKind::InvalidFormat).with_byte_offset(0));
}
self.inner.push_delim_with(delim)
}
#[doc = include_str!("docs/methods/remove.md")]
#[doc = include_str!("docs/sections/panics.md")]
#[inline]
pub fn remove(&mut self, idx: usize) -> Result<(), Error> {
let (left, right) = self
.as_ident()
.expect(
"attempted to remove a character from an identifier buffer that was out of bounds",
)
.split_at(idx);
let mut chars = right.chars();
let _ = chars.next();
let right = chars.as_fragment();
if !Self::can_join(left, right) {
return Err(Error::new(ErrorKind::FailedRemove).with_byte_offset(idx));
}
self.remove_unchecked(idx);
Ok(())
}
#[doc = include_str!("docs/methods/replace_range.md")]
#[doc = include_str!("docs/sections/panics.md")]
#[inline]
pub fn replace_range_fragment<R>(
&mut self,
range: R,
replace_with: &Fragment<B, D, P>,
) -> Result<(), Error>
where
R: RangeBounds<usize>,
{
let start = match range.start_bound() {
Bound::Included(idx) => *idx,
Bound::Excluded(idx) => *idx + 1,
Bound::Unbounded => 0,
};
if start == 0 {
let end = match range.end_bound() {
Bound::Included(idx) => *idx + 1,
Bound::Excluded(idx) => *idx,
Bound::Unbounded => self.len(),
};
let valid_first_char = replace_with
.chars()
.next()
.or_else(|| self[end..].chars().next())
.is_none_or(|c| D::is_ident_start(c) || P::is_ident_start(c));
if !valid_first_char {
return Err(Error::new(ErrorKind::InvalidFormat).with_byte_offset(0));
}
}
self.inner.replace_range_fragment(range, replace_with)
}
#[doc = include_str!("docs/methods/split_off.md")]
#[doc = include_str!("docs/sections/panics.md")]
#[must_use]
#[inline]
pub fn split_off(&mut self, idx: usize) -> FragmentBuf<B, D, P> {
self.inner.split_off(idx)
}
}
impl<B, D, P> IdentBuf<B, D, P> {
#[must_use]
#[inline]
pub fn as_ident(&self) -> Option<&Ident<B, D, P>> {
match self.is_empty() {
true => None,
false => Some(Ident::new_unchecked(self.inner.as_str())),
}
}
#[must_use]
#[inline]
pub fn as_ident_or<'a>(&'a self, default: &'a Ident<B, D, P>) -> &'a Ident<B, D, P> {
match self.is_empty() {
true => default,
false => Ident::new_unchecked(self.inner.as_str()),
}
}
#[must_use]
#[inline]
pub fn as_ident_or_anonymous(&self) -> &Ident<B, D, P>
where
D: UnitDelimiter,
{
match self.is_empty() {
true => Ident::new_unchecked(D::STR),
false => Ident::new_unchecked(self.inner.as_str()),
}
}
#[must_use]
#[inline]
pub fn from_ident(orig: &Ident<B, D, P>) -> Self {
Self {
inner: FragmentBuf::from_fragment(orig.as_fragment()),
}
}
#[must_use]
#[inline]
pub(crate) fn from_string_unchecked(s: String) -> Self {
Self {
inner: FragmentBuf::from_string_unchecked(s),
}
}
#[must_use]
#[inline]
pub fn into_boxed_ident(self) -> Option<Box<Ident<B, D, P>>> {
if self.inner.is_empty() {
return None;
}
Some(Ident::new_boxed_unchecked(self.into_string()))
}
#[must_use]
#[inline]
pub fn into_string(self) -> String {
self.inner.into_string()
}
#[must_use]
#[inline]
pub fn leak<'a>(self) -> Option<&'a Ident<B, D, P>> {
match self.is_empty() {
true => None,
false => Some(Ident::new_unchecked(self.inner.into_string().leak())),
}
}
#[inline]
pub(crate) fn remove_unchecked(&mut self, idx: usize) {
self.inner.remove_unchecked(idx)
}
#[must_use]
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: FragmentBuf::with_capacity(capacity),
}
}
#[must_use]
#[inline]
pub fn with_overhead(ident: &Ident<B, D, P>, additional: usize) -> Self {
Self {
inner: FragmentBuf::with_overhead(ident, additional),
}
}
}
impl_buffer_methods! {
name=IdentBuf,
}
impl<B: Boundary, D: Delimiter, P: Profile> core::str::FromStr for IdentBuf<B, D, P> {
type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from_ident(Ident::new(s)?))
}
}
impl<'a, B, D, P> core::convert::From<&'a Ident<B, D, P>> for IdentBuf<B, D, P> {
#[inline]
fn from(orig: &'a Ident<B, D, P>) -> Self {
Self::from_ident(orig)
}
}
impl<B, D, P> Clone for IdentBuf<B, D, P> {
#[inline]
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<B, D, P> ToOwned for Ident<B, D, P> {
type Owned = Box<Ident<B, D, P>>;
#[inline]
fn to_owned(&self) -> Self::Owned {
IdentBuf::from(self)
.into_boxed_ident()
.expect("it should never be the case that an ident fails conversion to a boxed ident")
}
}
impl_buffer_traits! {
name=IdentBuf,
}
impl_typed_slice_traits! {
name=IdentBuf,
index_target=Fragment,
}
impl_typed_slice_cmp! {
name=IdentBuf,
against=Chunk,
}
impl_typed_slice_cmp! {
name=IdentBuf,
against=Fragment,
}
impl_typed_slice_cmp! {
name=IdentBuf,
against=FragmentBuf,
}
impl_typed_slice_cmp! {
name=IdentBuf,
against=Ident,
}