osiris-set-std 0.1.17

A standard operation set.
Documentation
//! 0x03 Memory operations (store/load, stack, allocation)
//! * `0x0301` [store_words]:`target` `[start:end]`
//! * `0x0302` [load_words]:`target` `[start:end]`
//! * `0x0303` [get_memory_size]:`target`
//! * `0x0304` [store_floats]:`target` `[start:end]`
//! * `0x0305` [load_floats]:`target` `[start:end]`
/// TODO allocation, etc

use std::collections::HashMap;

use osiris_data::data::atomic::Word;
use osiris_data::data::composite::Array;
use osiris_data::data::identification::{Address, Area};
use osiris_data::memory::MemoryError;
use osiris_process::operation::error::{OperationError, OperationResult, promote};
use osiris_process::operation::{Operation, OperationSet};
use osiris_process::operation::scheme::{ArgumentType, InstructionScheme, OperationId};
use osiris_process::processor::Cpu;
use osiris_process::register::floating_point::Number;
use osiris_process::register::RegisterBank;

pub const SET_MASK: u16 = 0x0300;
pub const STORE_WORDS: OperationId = OperationId::new(SET_MASK | 0x01);
pub const LOAD_WORDS: OperationId = OperationId::new(SET_MASK | 0x02);
pub const GET_MEMORY_SIZE: OperationId = OperationId::new(SET_MASK | 0x03);
pub const STORE_FLOATS: OperationId = OperationId::new(SET_MASK | 0x04);
pub const LOAD_FLOATS: OperationId = OperationId::new(SET_MASK | 0x05);

/// # `0x0301` [store_words]:`target` `[start:end]`
///
/// ## Target
///  - The register containing the address from whom to write to.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - `MEM[REG[$target]; ($end-$start)]` = `REG[$start..=$end]`
///
/// To store only one register into memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::Range],
///  - [OperationError::MemoryError] if a memory error occurs.
pub fn store_words(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let start = cpu.bank_get(scheme.target);
    let chunk = cpu.state.bank.slice(range);
    promote(cpu.ram_mut().copy(Address::from_word(start), Array::from(chunk)))
}

/// # `0x0302` [load_words]:`target` `[start:end]`
///
/// This page describes the instruction scheme.
///
/// ## Target
///  - The register containing the address to read from.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - `REG[$start..=$end]` = `MEM[REG[$target]; ($end-$start)]`
///
/// To load only one register from memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not a [ArgumentType::Range],
///  - [OperationError::MemoryError]:[MemoryError::OutOfBounds] if a memory error occurs.
pub fn load_words(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let memory = cpu.memory();
    let ram = memory.borrow();
    let start = cpu.bank_get(scheme.target).to_u64();
    let end = start + range.count() as u64 - 1;
    let area = Area::region(Address::new(start), (end - start) as usize);
    let slice = promote(ram.slice(area))?;
    cpu.state.bank.copy(range.start, slice);
    Ok(())
}

/// # `0x0303` [get_memory_size]:`target`
///
/// This page describes the instruction scheme.
///
/// ## Target
///  - The register to write the memory size.
///
/// ## Arguments
///  - None.
///
/// ## Operation
///  - `REG[$target] = MEM.len()`
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not a [ArgumentType::NoArgument],
pub fn get_memory_size(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    scheme.argument.get_no_argument()?;
    cpu.bank_set(scheme.target, Word::from_usize(cpu.memory_size()));
    Ok(())
}


/// # `0x0304` [store_floats]:`target` `[start:end]`
///
/// ## Target
///  - The register containing the address from whom to write to.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - copies `MEM[REG[$target]; ($end-$start)]` into `REG[$start..=$end]`
///
/// To store only one register into memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::Range],
///  - [OperationError::MemoryError] if a memory error occurs.
pub fn store_floats(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let start = cpu.bank_get(scheme.target);
    let chunk = cpu.state.vector.slice(range);
    promote(cpu.ram_mut().copy(Address::from_word(start), Number::slice_to_array(chunk)))
}

/// # `0x0305` [load_floats]:`target` `[start:end]`
///
/// This page describes the instruction scheme.
///
/// ## Target
///  - The register containing the address to read from.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - copies `FLOAT[$start..=$end]` into `MEM[REG[$target]; ($end-$start)]`
///
/// To load only one register from memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not a [ArgumentType::Range],
///  - [OperationError::MemoryError]:[MemoryError::OutOfBounds] if a memory error occurs.
pub fn load_floats(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let memory = cpu.memory();
    let ram = memory.borrow();
    let start = cpu.bank_get(scheme.target).to_u64();
    let end = start + range.count() as u64 - 1;
    let area = Area::region(Address::new(start), (end - start) as usize);
    let slice = promote(ram.slice(area))?;
    cpu.state.vector.copy(range.start, &Number::from_words_slice(slice));
    Ok(())
}

/// Returns memory operations.
///
/// ## Operations
///
/// * `0x0301` [store_words]:`target` `[start:end]`
/// * `0x0302` [load_words]:`target` `[start:end]`
/// * `0x0303` [get_memory_size]:`target`
/// * `0x0304` [store_floats]:`target` `[start:end]`
/// * `0x0305` [load_floats]:`target` `[start:end]`
pub fn operation_set() -> OperationSet {
    let mut set: OperationSet = HashMap::new();
    set.insert(
        STORE_WORDS,
        Operation::new(STORE_WORDS, "store-words".to_string(), true, ArgumentType::Range, store_words),
    );
    set.insert(
        LOAD_WORDS,
        Operation::new(LOAD_WORDS, "load-words".to_string(), true, ArgumentType::Range, load_words),
    );
    set.insert(
        GET_MEMORY_SIZE,
        Operation::new(GET_MEMORY_SIZE, "get-memory-size".to_string(), true, ArgumentType::NoArgument, get_memory_size),
    );
    set.insert(
        STORE_FLOATS,
        Operation::new(STORE_FLOATS, "store-float".to_string(), true, ArgumentType::Range, store_floats),
    );
    set.insert(
        LOAD_FLOATS,
        Operation::new(LOAD_FLOATS, "load-floats".to_string(), true, ArgumentType::Range, load_floats),
    );
    set
}