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 [`Error`] type.

use crate::error::Builder;
use crate::vm::Exception;

use std::convert::Infallible;
use std::fmt::{self, Display, Formatter};
use std::num::NonZero;
use std::io;
use std::process::{ExitCode, Termination};

/// A LOOKLOOK error.
#[derive(Clone, Debug)]
pub struct Error {
	/// The error message.
	message: Box<str>,

	/// The file offset (if emulation error.)
	offset: Option<NonZero<u64>>,
}

impl Error {
	/// Constructs an error from a builder.
	///
	/// # Panics
	///
	/// See [`Builder::build`].
	#[must_use]
	pub(super) fn from_builder(builder: Builder) -> Self {
		let message = builder.message
			.expect("missing error message");

		let offset = builder.offset;

		Self { message, offset }
	}

	/// Retrieves the error message.
	#[inline]
	#[must_use]
	pub fn message(&self) -> &str {
		&self.message
	}

	/// Retrieves the file offset (if specified.)
	#[inline]
	#[must_use]
	pub fn offset(&self) -> Option<u64> {
		self.offset
			.map(|o| o.get() - 1)
	}
}

impl Display for Error {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		writeln!(f, "\u{001B}[001m\u{001B}[091merror\u{001B}[039m\u{001B}[022m: {}\u{001B}[022m", self.message())?;

		if let Some(offset) = self.offset() {
			writeln!(f, "\u{001B}[001m\u{001B}[096m note\u{001B}[039m\u{001B}[022m: at offset `{offset:#X}`")?;
		}

		Ok(())
	}
}

impl std::error::Error for Error {}

impl From<io::Error> for Error {
	fn from(value: io::Error) -> Self {
		Builder::new()
			.message(value)
			.build()
	}
}

impl From<Exception> for Error {
	fn from(value: Exception) -> Self {
		Builder::new()
			.message(value)
			.build()
	}
}

impl From<Infallible> for Error {
	#[inline(always)]
	fn from(value: Infallible) -> Self {
		match value {}
	}
}

impl Termination for Error {
	fn report(self) -> ExitCode {
		1_u8.into()
	}
}