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

mod exec;

use crate::error::{self, Result};
use crate::isa::{An, Long};
use crate::vm::{Mem, Regs};
use crate::signal::Signal;

use std::fs::File;
use std::io::Read;
use std::os::unix::fs::MetadataExt;
use std::path::Path;

/// A MC68EC020 virtual machine.
#[derive(Debug, Default)]
pub struct Vm {
	/// The registers.
	regs: Regs,

	/// The memory.
	mem: Mem,
}

impl Vm {
 	/// Constructs a new emulator.
	#[inline]
	#[must_use]
	pub fn new() -> Self {
		Default::default()
	}

	/// Reads an image into memory.
	///
	/// If the allocated memory is insufficient to con-
	/// tain the entire image, the image is truncated
	/// until it can fit.
	///
	/// # Errors
	///
	/// This method will forward any errors from reading
	/// the image.
	#[inline]
	pub fn read_image<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
		let path = path.as_ref();

		eprintln!("reading executable image at \"{}\"", path.display());
		let mut file = File::open_buffered(path)?;

		if file.get_ref().metadata()?.size() > Mem::IMG_SIZE as u64 {
			let e = error::Builder::new()
				.message("executable file may not be larger than `8388608` bytes")
				.build();

			return Err(e);
		}

		let slot = self.mem.raw_mut()
			.get_mut(..Mem::IMG_SIZE)
			.unwrap_or_default();

		let image_size = file.read(slot)?;
		eprintln!("successfully read `{image_size}` byte(s) of executable");

		Ok(())
	}

	/// Boots the virtual machine.
	#[inline]
	pub fn boot(&mut self) {
		const SP_ADDR: Long = Long::from_u32(0);
		const PC_ADDR: Long = Long::from_u32(4);

		let mem = self.mem();
		let sp = mem.load_long(SP_ADDR).unwrap();
		let pc = mem.load_long(PC_ADDR).unwrap();

		let regs = self.regs_mut();
		*regs.an_mut(An::Sp) = sp;
		*regs.pc_mut() = pc;
	}

	/// Renders a signal.
	#[inline]
	pub fn render_signal(&mut self, _buf: &mut Signal) {
		self.boot();

		loop {
			let exception = self.exec();
			eprintln!("got exception #{}: \"{}\"", exception.as_u8(), exception.as_str());
		}
	}

	/// Borrows the registers.
	#[inline(always)]
	#[must_use]
	pub fn regs(&self) -> &Regs {
		&self.regs
	}

	/// Mutably borrows the registers.
	#[inline(always)]
	#[must_use]
	pub fn regs_mut(&mut self) -> &mut Regs {
		&mut self.regs
	}

	/// Borrows memory.
	#[inline(always)]
	#[must_use]
	pub fn mem(&self) -> &Mem {
		&self.mem
	}

	/// Mutably borrows memory.
	#[inline(always)]
	#[must_use]
	pub fn mem_mut(&mut self) -> &mut Mem {
		&mut self.mem
	}
}