pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

//! A byte span.

use crate::value::parse;
use core::ops::Range;
use core::str::FromStr;
use serde::Deserialize;
use serde::Serialize;

#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Serialize, Deserialize)]
pub struct Span(pub(crate) usize, pub(crate) usize);

impl core::fmt::Debug for Span {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "Span({}, {})", self.0, self.1)
    }
}

impl core::fmt::Display for Span {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}-{}", self.0, self.1)
    }
}

impl FromStr for Span {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        value
            .split_once('-')
            .and_then(|(lo, hi)| parse::usize(lo.as_bytes()).map(|lo| (lo, hi)).ok())
            .and_then(|(lo, hi)| parse::usize(hi.as_bytes()).map(|hi| (lo, hi)).ok())
            .map(|(lo, hi)| Span::new(lo, hi))
            .ok_or_else(|| value.to_string())
    }
}

impl From<&[u8]> for Span {
    fn from(value: &[u8]) -> Self {
        Self::new(0, value.len())
    }
}

impl From<usize> for Span {
    fn from(value: usize) -> Self {
        Self::new(0, value)
    }
}

impl From<Range<usize>> for Span {
    fn from(value: Range<usize>) -> Self {
        Self::new(value.start, value.end)
    }
}

impl Span {
    pub fn new(lo: usize, hi: usize) -> Self {
        if lo > hi {
            Self::default()
        } else {
            Self(lo, hi)
        }
    }

    pub fn lo(&self) -> usize {
        self.0
    }

    pub fn hi(&self) -> usize {
        self.1
    }

    pub fn len(&self) -> usize {
        self.1.saturating_sub(self.0)
    }

    pub fn is_empty(&self) -> bool {
        self.0 == self.1
    }

    pub fn with_lo(self, lo: usize) -> Self {
        Self(lo, self.1)
    }

    pub fn with_hi(self, hi: usize) -> Self {
        Self(self.0, hi)
    }

    pub fn into_lo(self) -> Self {
        Self(self.0, self.0)
    }

    pub fn into_hi(self) -> Self {
        Self(self.1, self.1)
    }

    pub fn read<'a, T: Default>(&self, list: &'a [T]) -> &'a [T] {
        list.get(self.range()).unwrap_or_default()
    }

    pub fn range(self) -> Range<usize> {
        self.0..self.1
    }

    pub fn lo_range(self) -> Range<usize> {
        self.0..self.0
    }

    pub fn hi_range(self) -> Range<usize> {
        self.1..self.1
    }

    pub fn contains(self, other: Span) -> bool {
        self.0 <= other.0 && self.1 >= other.1
    }

    #[doc(hidden)]
    pub fn diff(self, diff: isize) -> Self {
        Span::new(self.0, self.1.saturating_add_signed(diff))
    }

    #[doc(hidden)]
    pub fn expand(self, left: usize, right: usize) -> Self {
        Span::new(self.0.saturating_sub(left), self.1.saturating_add(right))
    }

    #[doc(hidden)]
    pub fn contract(self, left: usize, right: usize) -> Self {
        Span::new(self.0.saturating_add(left), self.1.saturating_sub(right))
    }

    #[doc(hidden)]
    pub(crate) fn zoom(self, span: Span) -> Self {
        Span::new(
            self.0.saturating_add(span.lo()),
            self.0.saturating_add(span.hi()),
        )
    }

    #[doc(hidden)]
    pub fn left(self, span: Span) -> Self {
        Span::new(
            self.0.saturating_sub(span.lo()),
            self.1.saturating_sub(span.lo()),
        )
    }

    #[doc(hidden)]
    pub fn right(self, span: Span) -> Self {
        Span::new(
            self.0.saturating_add(span.lo()),
            self.1.saturating_add(span.lo()),
        )
    }
}