looklook 0.9.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 [`Long`] type.

use crate::isa::{Byte, Word};

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

/// A `32`-bit long-word value.
#[repr(transparent)]
#[derive(Copy)]
#[derive_const(
	Clone,
	Default,
	Eq,
	Ord,
	PartialEq,
	PartialOrd,
)]
pub struct Long(u32);

impl Long {
	/// The amount of bits in a long-word.
	pub const BITS: u32 = 32;

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

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

	/// Converts a big-endian long-word to the host endian.
	#[inline]
	#[must_use]
	pub const fn from_be(value: Self) -> Self {
		Self(u32::from_be(value.0))
	}

	/// Reinterprets host-endian bytes as aa long-word.
	#[inline]
	#[must_use]
	pub const fn from_ne_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
		Self(u32::from_ne_bytes(bytes))
	}

	/// Converts big-endian bytes to a host-endian long-word.
	#[inline]
	#[must_use]
	pub const fn from_be_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
		Self(u32::from_be_bytes(bytes))
	}

	/// Adds a [`usize`] value to the long-word.
	///
	/// The result is truncated to fit `32` bits.
	#[inline]
	#[must_use]
	pub const fn add_usize(mut self, value: usize) -> Self {
		self.0 = self.0.wrapping_add(value as u32);
		self
	}

	/// Subtracts a [`usize`] value from the long-word.
	///
	/// The result is truncated to fit `32` bits.
	#[inline]
	#[must_use]
	pub const fn sub_usize(mut self, value: usize) -> Self {
		self.0 = self.0.wrapping_sub(value as u32);
		self
	}

	/// Offsets the long-word using a [`isize`] value.
	///
	/// The result is truncated to fit `32` bits.
	#[inline]
	#[must_use]
	pub const fn offset_isize(mut self, offset: isize) -> Self {
		self.0 = self.0.wrapping_add_signed(offset as i32);
		self
	}

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

	/// Interprets the byte as a [`i32`] value.
	#[inline]
	#[must_use]
	pub const fn as_i32(self) -> i32 {
		self.0.cast_signed()
	}

	/// 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.0 as usize
	}

	/// Converts a host-endian long-word to big-endian.
	#[inline]
	#[must_use]
	pub const fn to_be(self) -> Self {
		Self(self.0.to_be())
	}

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

	/// Converts the host-endian long-word to big-endian bytes.
	#[inline]
	#[must_use]
	pub const fn to_be_bytes(self) -> [u8; size_of::<Self>()] {
		self.0.to_be_bytes()
	}
}

impl Debug for Long {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		let prefix = match self.as_u32().max(1).ilog(16) {
			0 => "$.......",
			1 => "$......",
			2 => "$.....",
			3 => "$....",
			4 => "$...",
			5 => "$..",
			6 => "$.",
			7 => "$",

			_ => {
				unreachable!();
			}
		};

		write!(f, "{prefix}{:x}", self.as_u32())
	}
}

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

const impl From<Byte> for Long {
	#[inline]
	fn from(value: Byte) -> Self {
		Self::from_u32(value.as_u8().into())
	}
}

const impl From<i16> for Long {
	#[inline]
	fn from(value: i16) -> Self {
		Self::from_i32(value.into())
	}
}

const impl From<i32> for Long {
	#[inline(always)]
	fn from(value: i32) -> Self {
		Self::from_i32(value)
	}
}

const impl From<i8> for Long {
	#[inline]
	fn from(value: i8) -> Self {
		Self::from_i32(value.into())
	}
}

const impl From<u16> for Long {
	#[inline]
	fn from(value: u16) -> Self {
		Self::from_u32(value.into())
	}
}

const impl From<u32> for Long {
	#[inline(always)]
	fn from(value: u32) -> Self {
		Self::from_u32(value)
	}
}

const impl From<u8> for Long {
	#[inline]
	fn from(value: u8) -> Self {
		Self::from_u32(value.into())
	}
}

const impl From<Word> for Long {
	#[inline]
	fn from(value: Word) -> Self {
		Self::from_u32(value.as_u16().into())
	}
}