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

use crate::value::Value;
use crate::value::error::Error;
use crate::value::parse;
use fixed::types::I48F16;

#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Number(I48F16);

impl core::fmt::Display for Number {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.0.to_string())
    }
}

impl From<usize> for Number {
    fn from(value: usize) -> Self {
        Self(I48F16::from_num(value))
    }
}

impl TryFrom<&[u8]> for Number {
    type Error = Error;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        I48F16::from_str(parse::str(value)?)
            .map_err(|e| Error::Number(e.to_string()))
            .map(Self)
    }
}

impl TryFrom<Number> for usize {
    type Error = ();

    fn try_from(value: Number) -> Result<Self, Self::Error> {
        value.0.checked_to_num().ok_or(())
    }
}

impl TryFrom<Number> for isize {
    type Error = ();

    fn try_from(value: Number) -> Result<Self, Self::Error> {
        value.0.checked_to_num().ok_or(())
    }
}

impl TryFrom<Number> for u32 {
    type Error = ();

    fn try_from(value: Number) -> Result<Self, Self::Error> {
        value.0.checked_to_num().ok_or(())
    }
}

impl From<Number> for Value {
    fn from(number: Number) -> Self {
        Value::from(number.to_string())
    }
}

impl core::ops::Add for Number {
    type Output = Number;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0.saturating_add(rhs.0))
    }
}

impl core::ops::Sub for Number {
    type Output = Number;

    fn sub(self, rhs: Self) -> Self::Output {
        Self(self.0.saturating_sub(rhs.0))
    }
}

impl core::ops::Mul for Number {
    type Output = Number;

    fn mul(self, rhs: Self) -> Self::Output {
        Self(self.0.checked_mul(rhs.0).unwrap_or_default())
    }
}

impl core::ops::Div for Number {
    type Output = Number;

    fn div(self, rhs: Self) -> Self::Output {
        Self(self.0.checked_div(rhs.0).unwrap_or_default())
    }
}

impl core::ops::Rem for Number {
    type Output = Number;

    fn rem(self, rhs: Self) -> Self::Output {
        Self(self.0.checked_rem(rhs.0).unwrap_or_default())
    }
}

impl Number {
    pub(crate) fn abs(self) -> Number {
        Self(self.0.checked_abs().unwrap_or_default())
    }

    pub(crate) fn ceil(self) -> Number {
        Self(self.0.checked_ceil().unwrap_or_default())
    }

    pub(crate) fn floor(self) -> Number {
        Self(self.0.checked_floor().unwrap_or_default())
    }

    pub(crate) fn round(self) -> Number {
        Self(self.0.checked_round().unwrap_or_default())
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!((number("1") + number("2")).to_string(), "3");
        assert_eq!((number("1") + number("-2")).to_string(), "-1");
        assert_eq!((number("1") + number("2.01")).to_string(), "3.01");
    }

    #[test]
    fn test_sub() {
        assert_eq!((number("1") - number("1")).to_string(), "0");
        assert_eq!((number("-1") - number("-10")).to_string(), "9");
        assert_eq!((number("1") - number("1.1")).to_string(), "-0.1");
    }

    #[test]
    fn test_div() {
        assert_eq!((number("1") / number("0")).to_string(), "0");
        assert_eq!((number("0") / number("1")).to_string(), "0");
        assert_eq!((number("0") / number("0")).to_string(), "0");
    }

    #[track_caller]
    fn number(s: &str) -> Number {
        Number::try_from(s.as_bytes()).unwrap()
    }
}