looklook 0.7.0

Descriptive signal synthesiser.
// Copyright 2026 Gabriel Bjørnager Jensen.
//
// This file is part of LOOKLOOK.
//
// LOOKLOOK is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later ver-
// sion.
//
// LOOKLOOK is distributed in the hope that it will be useful, but WITHOUT ANY WAR-
// RANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License along
// with LOOKLOOK. If not, see <https://www.gnu.org/licenses/>.

//! The [`Int`] type.

use std::fmt::{self, Debug, Display, Formatter};

/// A machine integer.
#[repr(C)]
#[derive(Copy)]
pub struct Int<const N: usize>([u8; N]);

impl<const N: usize> Int<N> {
	/// The amount of bits in the integer.
	pub const BITS: u32 = u8::BITS * N as u32;

	/// Converts a big-endian integer to the host endian.
	#[inline]
	#[must_use]
	pub const fn from_be(value: Self) -> Self {
		Self::from_be_bytes(value.to_ne_bytes())
	}

	/// Reinterprets host-endian bytes as an integer.
	#[inline(always)]
	#[must_use]
	pub const fn from_ne_bytes(bytes: [u8; N]) -> Self {
		Self(bytes)
	}

	/// Converts big-endian bytes to a host-endian integer.
	#[inline]
	#[must_use]
	pub const fn from_be_bytes(mut bytes: [u8; N]) -> Self {
		if cfg!(target_endian = "little") {
			bytes.reverse();
		}

		Self::from_ne_bytes(bytes)
	}

	/// Converts a host-endian integer to big-endian.
	#[inline]
	#[must_use]
	pub const fn to_be(self) -> Self {
		Self::from_ne_bytes(self.to_be_bytes())
	}

	/// Reinterprets the long-word as host-endian bytes.
	#[inline(always)]
	#[must_use]
	pub const fn to_ne_bytes(self) -> [u8; N] {
		self.0
	}

	/// Converts the host-endian long-word to big-endian bytes.
	#[inline]
	#[must_use]
	pub const fn to_be_bytes(mut self) -> [u8; N] {
		if cfg!(target_endian = "little") {
			self.0.reverse();
		}

		self.0
	}
}

impl Int<1> {
	/// Constructs a long-word from a [`u8`] value.
	#[inline(always)]
	#[must_use]
	pub const fn from_u8(value: u8) -> Self {
		Self::from_ne_bytes(value.to_ne_bytes())
	}

	/// Constructs a long-word from an [`i8`] value.
	#[inline(always)]
	#[must_use]
	pub const fn from_i8(value: i8) -> Self {
		Self::from_ne_bytes(value.to_ne_bytes())
	}

	/// Zero-extends the byte to a word.
	#[inline]
	#[must_use]
	pub const fn zero_extend(self) -> Int<2> {
		let bytes = u16::from(self.as_u8()).to_ne_bytes();
		Int::from_ne_bytes(bytes)
	}

	/// Sign-extends the byte to a word.
	#[inline]
	#[must_use]
	pub const fn sign_extend(self) -> Int<2> {
		let bytes = i16::from(self.as_i8()).to_ne_bytes();
		Int::from_ne_bytes(bytes)
	}

	/// Interprets the byte as a [`u8`] value.
	#[inline(always)]
	#[must_use]
	pub const fn as_u8(self) -> u8 {
		u8::from_ne_bytes(self.0)
	}

	/// Interprets the byte as a [`i8`] value.
	#[inline(always)]
	#[must_use]
	pub const fn as_i8(self) -> i8 {
		i8::from_ne_bytes(self.0)
	}
}

impl Int<2> {
	/// Constructs a word from a [`u16`] value.
	#[inline(always)]
	#[must_use]
	pub const fn from_u16(value: u16) -> Self {
		Self::from_ne_bytes(value.to_ne_bytes())
	}

	/// Constructs a word from an [`i16`] value.
	#[inline(always)]
	#[must_use]
	pub const fn from_i16(value: i16) -> Self {
		Self::from_ne_bytes(value.to_ne_bytes())
	}

	/// Zero-extends the word to a long-word.
	#[inline]
	#[must_use]
	pub const fn zero_extend(self) -> Int<4> {
		let bytes = u32::from(self.as_u16()).to_ne_bytes();
		Int::from_ne_bytes(bytes)
	}

	/// Sign-extends the word to a long-word.
	#[inline]
	#[must_use]
	pub const fn sign_extend(self) -> Int<4> {
		let bytes = i32::from(self.as_i16()).to_ne_bytes();
		Int::from_ne_bytes(bytes)
	}

	/// Interprets the word as a [`u16`] value.
	#[inline(always)]
	#[must_use]
	pub const fn as_u16(self) -> u16 {
		u16::from_ne_bytes(self.0)
	}

	/// Interprets the word as a [`i16`] value.
	#[inline(always)]
	#[must_use]
	pub const fn as_i16(self) -> i16 {
		i16::from_ne_bytes(self.0)
	}
}

impl Int<4> {
	/// Constructs a long-word from a [`u32`] value.
	#[inline(always)]
	#[must_use]
	pub const fn from_u32(value: u32) -> Self {
		Self::from_ne_bytes(value.to_ne_bytes())
	}

	/// Constructs a long-word from an [`i32`] value.
	#[inline(always)]
	#[must_use]
	pub const fn from_i32(value: i32) -> Self {
		Self::from_ne_bytes(value.to_ne_bytes())
	}

	/// Interprets the long-word as a [`u32`] value.
	#[inline(always)]
	#[must_use]
	pub const fn as_u32(self) -> u32 {
		u32::from_ne_bytes(self.0)
	}

	/// Interprets the long-word as a [`i32`] value.
	#[inline(always)]
	#[must_use]
	pub const fn as_i32(self) -> i32 {
		i32::from_ne_bytes(self.0)
	}

	/// Interprets the long-word value as a zero-extended [`usize`] value.
	#[inline]
	#[must_use]
	pub const fn as_usize(self) -> usize {
		// NOTE: We always require at least `32` bits per pointer address in the host, so
		// this conversion is always lossless.
		self.as_u32() as usize
	}
}

const impl<const N: usize> Clone for Int<N> {
	#[inline(always)]
	fn clone(&self) -> Self {
		*self
	}
}

impl Debug for Int<1> {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		write!(f, "{:#04X}", self.as_i8())
	}
}

impl Debug for Int<2> {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		write!(f, "{:#06X}", self.as_i16())
	}
}

const impl<const N: usize> Default for Int<N> {
	#[inline(always)]
	fn default() -> Self {
		const { Self::from_ne_bytes([Default::default(); _]) }
	}
}

impl Debug for Int<4> {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		write!(f, "{:#010X}", self.as_i32())
	}
}

impl Display for Int<1> {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Debug::fmt(self, f)
	}
}

impl Display for Int<2> {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Debug::fmt(self, f)
	}
}

impl Display for Int<4> {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Debug::fmt(self, f)
	}
}

const impl Eq for Int<1> {}

const impl Eq for Int<2> {}

const impl Eq for Int<4> {}

const impl PartialEq for Int<1> {
	#[inline]
	fn eq(&self, other: &Self) -> bool {
		self.as_u8().eq(&other.as_u8())
	}
}

const impl PartialEq for Int<2> {
	#[inline]
	fn eq(&self, other: &Self) -> bool {
		self.as_u16().eq(&other.as_u16())
	}
}

const impl PartialEq for Int<4> {
	#[inline]
	fn eq(&self, other: &Self) -> bool {
		self.as_u32().eq(&other.as_u32())
	}
}