use core::ops::Range;
use core::ptr::NonNull;
use core::slice;
use crate::display::InputDisplay;
use crate::fmt;
use crate::input::{Input, MaybeString};
#[must_use]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Span {
start: NonNull<u8>,
end: NonNull<u8>,
}
impl Span {
#[must_use]
#[inline(always)]
pub fn len(self) -> usize {
self.end.as_ptr() as usize - self.start.as_ptr() as usize
}
#[must_use]
#[inline(always)]
pub fn is_empty(self) -> bool {
self.start == self.end
}
#[must_use]
#[inline(always)]
pub fn is_within(self, other: Span) -> bool {
other.start <= self.start && other.end >= self.end
}
#[must_use]
#[inline(always)]
pub fn is_start_of(self, other: Span) -> bool {
self.is_empty() && other.start == self.start
}
#[must_use]
#[inline(always)]
pub fn is_end_of(self, other: Span) -> bool {
self.is_empty() && other.end == self.end
}
#[must_use]
#[inline(always)]
pub fn is_overlapping_start_of(self, other: Span) -> bool {
other.start > self.start
}
#[must_use]
#[inline(always)]
pub fn is_overlapping_end_of(self, other: Span) -> bool {
other.end < self.end
}
#[must_use]
#[inline(always)]
#[allow(clippy::suspicious_operation_groupings)]
pub fn is_start_within(self, other: Span) -> bool {
self.start >= other.start && self.start < other.end
}
#[must_use]
#[inline(always)]
#[allow(clippy::suspicious_operation_groupings)]
pub fn is_end_within(self, other: Span) -> bool {
self.end >= other.start && self.end < other.end
}
pub fn start(self) -> Self {
Self {
start: self.start,
end: self.start,
}
}
pub fn end(self) -> Self {
Self {
start: self.end,
end: self.end,
}
}
#[must_use]
pub fn of<P>(self, parent: P) -> Option<P>
where
P: Parent,
{
parent.extract(self)
}
#[must_use]
#[inline(always)]
pub fn range_of(self, parent: Span) -> Option<Range<usize>> {
if self.is_within(parent) {
let start_offset = self.start.as_ptr() as usize - parent.start.as_ptr() as usize;
let end_offset = self.end.as_ptr() as usize - parent.start.as_ptr() as usize;
Some(start_offset..end_offset)
} else {
None
}
}
#[must_use]
#[inline(always)]
pub fn non_empty(self) -> Option<Self> {
if self.is_empty() {
None
} else {
Some(self)
}
}
#[inline(always)]
#[allow(clippy::needless_pass_by_value)]
pub fn debug_for(self, input: MaybeString<'_>) -> DebugFor<'_> {
DebugFor {
bytes: input.as_dangerous_bytes(),
str_hint: input.is_string(),
span: self,
}
}
}
impl fmt::DisplayBase for Span {
fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result {
w.write_str("(ptr: ")?;
w.write_usize(self.start.as_ptr() as usize)?;
w.write_str(", len: ")?;
w.write_usize(self.len())?;
w.write_char(')')
}
}
impl From<&[u8]> for Span {
#[inline(always)]
fn from(value: &[u8]) -> Self {
let range = value.as_ptr_range();
unsafe {
Self {
start: NonNull::new_unchecked(range.start as _),
end: NonNull::new_unchecked(range.end as _),
}
}
}
}
impl From<&str> for Span {
#[inline(always)]
fn from(value: &str) -> Self {
Self::from(value.as_bytes())
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Span")
.field("ptr", &self.start)
.field("len", &self.len())
.finish()
}
}
unsafe impl Send for Span {}
unsafe impl Sync for Span {}
#[must_use]
pub struct DebugFor<'a> {
span: Span,
str_hint: bool,
bytes: &'a [u8],
}
impl<'a> fmt::Debug for DebugFor<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.span.of(self.bytes) {
Some(valid) => {
let display = InputDisplay::from_bytes(valid).with_formatter(f);
let display = if self.str_hint {
display.str_hint()
} else {
display
};
f.debug_tuple("Span").field(&display).finish()
}
None => fmt::Debug::fmt(&self.span, f),
}
}
}
pub trait Parent: Sized {
fn extract(self, span: Span) -> Option<Self>;
}
impl Parent for &[u8] {
#[inline(always)]
fn extract(self, span: Span) -> Option<Self> {
if span.is_within(self.into()) {
unsafe { Some(slice::from_raw_parts(span.start.as_ptr(), span.len())) }
} else {
None
}
}
}
impl Parent for &str {
#[inline]
fn extract(self, span: Span) -> Option<Self> {
span.range_of(self.into()).and_then(|range| {
if self.is_char_boundary(range.start) && self.is_char_boundary(range.end) {
Some(&self[range])
} else {
None
}
})
}
}
impl<'i, T> Parent for T
where
T: Input<'i>,
{
#[inline]
fn extract(self, span: Span) -> Option<Self> {
span.range_of(self.span()).and_then(|range| {
if self.verify_token_boundary(range.start).is_ok()
&& self.verify_token_boundary(range.end).is_ok()
{
let sub = unsafe {
let (_, tail) = self.split_at_byte_unchecked(range.start);
let (sub, _) = tail.split_at_byte_unchecked(range.end - range.start);
sub
};
Some(sub)
} else {
None
}
})
}
}