runmat-runtime 0.4.8

Core runtime for RunMat with builtins, BLAS/LAPACK integration, and execution APIs
Documentation
use once_cell::sync::Lazy;
use runmat_filesystem::File;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, Mutex as StdMutex};

pub(crate) type SharedFileHandle = Arc<StdMutex<Option<File>>>;

#[derive(Clone)]
pub(crate) enum StandardStream {
    Stdin,
    Stdout,
    Stderr,
}

#[derive(Clone)]
enum Resource {
    Standard,
    File(SharedFileHandle),
}

struct CloseEntry {
    info: FileInfo,
    entry: Entry,
    file: Option<File>,
}

struct Entry {
    id: i32,
    name: String,
    path: Option<PathBuf>,
    permission: String,
    machinefmt: String,
    encoding: String,
    resource: Resource,
}

impl Entry {
    fn standard(id: i32, stream: StandardStream) -> Self {
        let name = match stream {
            StandardStream::Stdin => "stdin".to_string(),
            StandardStream::Stdout => "stdout".to_string(),
            StandardStream::Stderr => "stderr".to_string(),
        };
        Self {
            id,
            name,
            path: None,
            permission: match stream {
                StandardStream::Stdin => "r".to_string(),
                StandardStream::Stdout | StandardStream::Stderr => "w".to_string(),
            },
            machinefmt: "native".to_string(),
            encoding: "UTF-8".to_string(),
            resource: Resource::Standard,
        }
    }

    fn info(&self) -> FileInfo {
        FileInfo {
            id: self.id,
            name: self.name.clone(),
            path: self.path.clone(),
            permission: self.permission.clone(),
            machinefmt: self.machinefmt.clone(),
            encoding: self.encoding.clone(),
        }
    }

    fn file_handle(&self) -> Option<SharedFileHandle> {
        match &self.resource {
            Resource::File(handle) => Some(handle.clone()),
            Resource::Standard => None,
        }
    }
}

#[derive(Clone)]
pub(crate) struct FileInfo {
    pub id: i32,
    pub name: String,
    pub path: Option<PathBuf>,
    pub permission: String,
    pub machinefmt: String,
    pub encoding: String,
}

pub(crate) struct RegisteredFile {
    pub path: PathBuf,
    pub permission: String,
    pub machinefmt: String,
    pub encoding: String,
    pub handle: SharedFileHandle,
}

impl RegisteredFile {
    fn into_entry(self, id: i32) -> Entry {
        let display_name = self.path.to_string_lossy().to_string();
        Entry {
            id,
            name: display_name,
            path: Some(self.path),
            permission: self.permission,
            machinefmt: self.machinefmt,
            encoding: self.encoding,
            resource: Resource::File(self.handle),
        }
    }
}

struct FileRegistry {
    next_id: i32,
    entries: HashMap<i32, Entry>,
}

impl FileRegistry {
    fn new() -> Self {
        let mut entries = HashMap::new();
        entries.insert(0, Entry::standard(0, StandardStream::Stdin));
        entries.insert(1, Entry::standard(1, StandardStream::Stdout));
        entries.insert(2, Entry::standard(2, StandardStream::Stderr));
        Self {
            next_id: 3,
            entries,
        }
    }

    fn allocate_id(&mut self) -> i32 {
        let id = self.next_id;
        self.next_id += 1;
        id
    }
}

static REGISTRY: Lazy<Mutex<FileRegistry>> = Lazy::new(|| Mutex::new(FileRegistry::new()));

#[cfg(test)]
static TEST_REGISTRY_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

pub(crate) fn register_file(file: RegisteredFile) -> i32 {
    let mut guard = REGISTRY.lock().expect("file registry poisoned");
    let id = guard.allocate_id();
    let entry = file.into_entry(id);
    guard.entries.insert(id, entry);
    id
}

pub(crate) fn info_for(fid: i32) -> Option<FileInfo> {
    let guard = REGISTRY.lock().expect("file registry poisoned");
    guard.entries.get(&fid).map(|entry| entry.info())
}

pub(crate) fn list_infos() -> Vec<FileInfo> {
    let guard = REGISTRY.lock().expect("file registry poisoned");
    let mut infos: Vec<FileInfo> = guard.entries.values().map(|entry| entry.info()).collect();
    infos.sort_by_key(|info| info.id);
    infos
}

pub(crate) fn take_handle(fid: i32) -> Option<SharedFileHandle> {
    let guard = REGISTRY.lock().expect("file registry poisoned");
    guard
        .entries
        .get(&fid)
        .and_then(|entry| entry.file_handle())
}

#[cfg(test)]
pub(crate) fn close(fid: i32) -> Option<FileInfo> {
    if fid < 3 {
        return None;
    }
    let mut guard = REGISTRY.lock().expect("file registry poisoned");
    guard.entries.remove(&fid).map(|entry| entry.info())
}

pub(crate) async fn close_async(fid: i32) -> std::io::Result<Option<FileInfo>> {
    let entry = {
        if fid < 3 {
            return Ok(None);
        }
        let mut guard = REGISTRY.lock().expect("file registry poisoned");
        guard.entries.remove(&fid).map(|entry| {
            let file = entry.file_handle().and_then(|handle| {
                let mut guard = handle.lock().expect("registered file handle poisoned");
                guard.take()
            });
            CloseEntry {
                info: entry.info(),
                entry,
                file,
            }
        })
    };

    let Some(mut entry) = entry else {
        return Ok(None);
    };

    if let Some(mut file) = entry.file.take() {
        if let Err(err) = file.flush_async().await {
            if let Some(handle) = entry.entry.file_handle() {
                let mut guard = handle.lock().expect("registered file handle poisoned");
                if guard.is_none() {
                    *guard = Some(file);
                }
            }

            let mut guard = REGISTRY.lock().expect("file registry poisoned");
            guard.entries.insert(fid, entry.entry);
            return Err(err);
        }
    }

    Ok(Some(entry.info))
}

#[cfg(test)]
pub(crate) fn reset_for_tests() {
    let mut guard = REGISTRY.lock().expect("file registry poisoned");
    guard.entries.retain(|&id, _| id < 3);
    guard.next_id = 3;
}

#[cfg(test)]
pub(crate) fn test_guard() -> std::sync::MutexGuard<'static, ()> {
    TEST_REGISTRY_LOCK
        .lock()
        .unwrap_or_else(|poison| poison.into_inner())
}