minparser 0.15.0

Simple parsing functions
/*
 * Minparser Simple parsing functions
 *
 * Copyright (C) 2024-2026 Paolo De Donato
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
//! Other useful parsing tools.
use crate::always_impl;
#[allow(clippy::wildcard_imports)]
use crate::prelude::*;

/// Tool that matches the newline characters sequences `\n` and `\r\n`.
#[derive(Debug, Clone, Copy, Default)]
pub struct Newline;

impl<'a> Tool<'a> for Newline {
    type Data = ();
    type Error = Cursor<'a>;

    fn parse(&self, st: CursorStart<'a>) -> Result<CursorLen<'a, Self::Data>, Self::Error> {
        Seq {
            first: RepeatAny::new_bounds('\r', TrueTool, 1),
            second: '\n',
        }
        .parse(st)
        .map(|d| d.get_data().1)
        .map_err(Either::into_second)
    }
}

/// Tool that matches any sequence of Unicode whitespaces.
#[derive(Debug, Clone, Copy, Default)]
pub struct WhiteSP;

impl<'a> AlwaysTool<'a> for WhiteSP {
    fn parse_always(&self, st: CursorStart<'a>) -> CursorLen<'a, Self::Data> {
        RepeatAny::new_unbounded(Predicate::new(char::is_whitespace), TrueTool)
            .parse_always(st)
            .get_data()
            .1
    }
}
always_impl!(WhiteSP);

/// Tool to retrieve a sequence of consecutive (Unicode) letters and numbers, which the first
/// character is not a number
#[derive(Debug, Clone, Copy, Default)]
pub struct Ident;

impl<'a> Tool<'a> for Ident {
    type Data = &'a str;
    type Error = Cursor<'a>;

    fn parse(&self, st: CursorStart<'a>) -> Result<CursorLen<'a, Self::Data>, Self::Error> {
        let su = Seq {
            first: Predicate::new(char::is_alphabetic),
            second: RepeatAny::new_unbounded(Predicate::new(char::is_alphanumeric), TrueTool),
        };
        GetStr(su).parse(st).map_err(Either::into_first)
    }
}

struct NumberCount {
    num: usize,
    radix: u32,
}

impl<Z> InsertB<u32, Z> for NumberCount {
    fn insert(&mut self, data: u32) {
        self.num = self.num * (self.radix as usize) + (data as usize);
    }
    fn insert_sep(&mut self, _: Z) {}
}

/// Tool that returns an integer with the specified basis (from 2 to 36 as specified by
/// [`to_digit`](char::to_digit).
#[derive(Debug, Clone, Copy, Default)]
pub struct AsciiNumber(u32);

impl AsciiNumber {
    /// Create a new instance with the specified radix.
    ///
    /// # Panics
    /// Panics if the value is less than 2 or greater than 36.
    #[must_use]
    pub fn new(radix: u32) -> Self {
        assert!((2..=36).contains(&radix), "Invalid radix");
        Self(radix)
    }
}

impl<'a> Tool<'a> for AsciiNumber {
    type Data = usize;
    type Error = Cursor<'a>;

    fn parse(&self, st: CursorStart<'a>) -> Result<CursorLen<'a, Self::Data>, Self::Error> {
        let mut count = NumberCount {
            num: 0,
            radix: self.0,
        };
        let st = Repeat::new_unbounded(
            PredicateData::new(|c: char| c.to_digit(self.0)).map(|(d, _)| d),
            TrueTool.set_error::<Self::Error>(),
            1,
        )
        .parse_logic(st, &mut count)?;
        Ok(st.map_data(|()| count.num))
    }
}

/// Tool that returns an integer and deduces the radix by the prefix:
/// - `0x`: 16;
/// - `0o`: 8;
/// - `0b`: 2;
/// - no prefix: 10.
///
/// ```rust
/// use minparser::prelude::*;
/// use minparser::utils::PfxAsciiNumber;
/// let (d, st) = Cursor::from("0x3a 0o2330 0932703").chain(&PfxAsciiNumber).unwrap().get_data();
/// assert_eq!(d, 0x3a);
/// let (d, st) = st.chain_replace(&' ').unwrap().chain_replace(&PfxAsciiNumber).unwrap().get_data();
/// assert_eq!(d, 0o2330);
/// let (d, st) = st.chain_replace(&' ').unwrap().chain_replace(&PfxAsciiNumber).unwrap().get_data();
/// assert_eq!(d, 932703);
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct PfxAsciiNumber;

impl<'a> Tool<'a> for PfxAsciiNumber {
    type Data = usize;
    type Error = Cursor<'a>;

    fn parse(&self, st: CursorStart<'a>) -> Result<CursorLen<'a, Self::Data>, Self::Error> {
        let st = Cursor::from(st);
        st.chain("0x").map_or_else(
            |er| {
                er.chain("0o").map_or_else(
                    |eer| {
                        eer.chain("0b").map_or_else(
                            |eeer| eeer.chain(&AsciiNumber::new(10)),
                            |nnnx| nnnx.chain_replace(&AsciiNumber::new(2)),
                        )
                    },
                    |nnx| nnx.chain_replace(&AsciiNumber::new(8)),
                )
            },
            |nx| nx.chain_replace(&AsciiNumber::new(16)),
        )
    }
}