c64 0.1.0-alpha.0

Driver for the Commodore 64 platform
//! HAL for Commodore 64 floating point calculations.

use core::{
    cmp::Ordering,
    ffi::CStr,
    marker::PhantomData,
    ops::{AddAssign, Neg},
};

use crate::{
    hal::{basic::BasicRom, kernal::KernalRom, Basic, Kernal},
    pac::{
        basic,
        kernal::{self, F40},
        peripheral,
    },
};

/// The Floating Point Accumulator register.
pub struct FAC<'a, HIRAM, LORAM> {
    _fac: peripheral::FAC,
    _cur: PhantomData<&'a (HIRAM, LORAM)>,
}

impl<'a> FAC<'a, Kernal, Basic> {
    /// Enables using the floating point functions in the Kernal and BASIC ROMs.
    pub fn with_basic<CHAREN>(fac: peripheral::FAC, basic: &'a BasicRom<'_, CHAREN>) -> Self {
        // We don't actually need to store the BASIC ROM, we just take its lifetime.
        let _ = basic;
        Self {
            _fac: fac,
            _cur: PhantomData,
        }
    }
}

impl<'a, LORAM> FAC<'a, Kernal, LORAM> {
    /// Enables using the floating point functions in the Kernal ROM.
    ///
    /// If the `KernalRom` happens to also carry the BASIC ROM marker, then this also
    /// enables using the floating point functions in the BASIC ROM.
    pub fn with_kernal<CHAREN>(
        fac: peripheral::FAC,
        kernal: &'a KernalRom<'_, LORAM, CHAREN>,
    ) -> Self {
        // We don't actually need to store the Kernal ROM, we just take its lifetime.
        let _ = kernal;
        Self {
            _fac: fac,
            _cur: PhantomData,
        }
    }
}

impl<'a> FAC<'a, Kernal, Basic> {
    /// Loads the given floating point number to FAC.
    pub fn load(&mut self, value: &F40) {
        unsafe { basic::movfm(value) };
    }

    /// Rounds the contents of FAC and reads it.
    ///
    /// Triggers an `?OVERFLOW ERROR` if, after rounding, the contents of FAC can no
    /// longer be represented as a floating point number on the C64.
    pub fn read(&mut self) -> F40 {
        let mut ret = F40::zero();
        unsafe { basic::movmf(&mut ret) };
        ret
    }

    /// Converts a 16-bit signed integer into a floating point number in FAC.
    pub fn load_i16(&mut self, value: i16) {
        unsafe { basic::givayf(value) }
    }

    /// Converts the number in FAC into a 32-bit signed integer, and then returns that
    /// integer cast to an `i16`.
    ///
    /// # Errors
    ///
    /// Triggers an `?ILLEGAL QUANTITY ERROR` if the number in FAC is not in range for `i16`.
    pub fn to_i16(&mut self) -> i16 {
        unsafe { basic::facinx() }
    }

    /// Copies ARG into FAC.
    pub fn load_from_arg(&mut self, arg: &ARG) {
        let _ = arg;
        unsafe { basic::movaf() };
    }

    /// Rounds FAC and then copies it to ARG.
    pub fn copy_to_arg(&mut self, arg: &mut ARG) {
        let _ = arg;
        unsafe { basic::movfa() };
    }

    /// Rounds FAC and converts the result into a nul-terminated ASCII string.
    ///
    /// This method preserves the (rounded) value of FAC, at the cost of latency, code
    /// size, and printing the rounded value. To print the contents of FAC exactly and
    /// more quickly (at the cost of resetting it), use [`Self::into_cstr`].
    pub fn to_cstr<'s>(&mut self, fpstr: &'s mut FPSTR) -> &'s CStr {
        let _ = fpstr;
        let val = self.read();
        let ptr = unsafe { basic::fout() };
        self.load(&val);
        unsafe { CStr::from_ptr(ptr) }
    }

    /// Converts the contents of FAC into a nul-terminated ASCII string.
    ///
    /// After this call, the contents of FAC is reset to 0. To round FAC instead (at the
    /// cost of printing the rounded value), use [`Self::to_cstr`].
    pub fn into_cstr<'s>(&mut self, fpstr: &'s mut FPSTR) -> &'s CStr {
        let _ = fpstr;
        let ptr = unsafe { basic::fout() };
        self.load_i16(0);
        unsafe { CStr::from_ptr(ptr) }
    }
}

impl<'a, LORAM> FAC<'a, Kernal, LORAM> {
    /// Sets `FAC = cos(FAC)`.
    pub fn cos(&mut self) {
        unsafe { kernal::cos() }
    }

    /// Sets `FAC = sin(FAC)`.
    pub fn sin(&mut self) {
        unsafe { kernal::sin() }
    }

    /// Sets `FAC = tan(FAC)`.
    pub fn tan(&mut self) {
        unsafe { kernal::tan() }
    }

    /// Sets `FAC = arctan(FAC)`.
    pub fn arctan(&mut self) {
        unsafe { kernal::atn() }
    }

    /// Sets `FAC = poly(FAC)`.
    pub fn poly1<const N: usize>(&mut self, poly: &kernal::Polynomial<kernal::OddTerms, N>) {
        unsafe { poly.poly() };
    }

    /// Sets `FAC = poly(FAC)`.
    pub fn poly2<const N: usize>(&mut self, poly: &kernal::Polynomial<kernal::AllTerms, N>) {
        unsafe { poly.poly() };
    }
}

impl<'a> PartialEq<F40> for FAC<'a, Kernal, Basic> {
    fn eq(&self, other: &F40) -> bool {
        matches!(self.partial_cmp(other), Some(Ordering::Equal))
    }
}

impl<'a> PartialOrd<F40> for FAC<'a, Kernal, Basic> {
    fn partial_cmp(&self, other: &F40) -> Option<Ordering> {
        Some(unsafe { basic::fcomp(other) })
    }
}

impl<'a> Neg for FAC<'a, Kernal, Basic> {
    type Output = Self;

    fn neg(self) -> Self::Output {
        unsafe { basic::negop() };
        self
    }
}

impl<'a> AddAssign<&F40> for FAC<'a, Kernal, Basic> {
    fn add_assign(&mut self, rhs: &F40) {
        unsafe { basic::fadd(rhs) }
    }
}

impl<'a> AddAssign<&ARG> for FAC<'a, Kernal, Basic> {
    fn add_assign(&mut self, rhs: &ARG) {
        let _ = rhs;
        unsafe { basic::faddt() }
    }
}

/// The Floating Point Argument register.
pub struct ARG {
    _arg: peripheral::ARG,
}

impl ARG {
    /// Constructs an access API for the ARG register.
    pub fn new(arg: peripheral::ARG) -> Self {
        Self { _arg: arg }
    }
}

/// The work area for floating point to string conversions.
pub struct FPSTR {
    _fpstr: peripheral::FPSTR,
}

impl FPSTR {
    /// Constructs an access API for the work area.
    pub fn new(fpstr: peripheral::FPSTR) -> Self {
        Self { _fpstr: fpstr }
    }
}