Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

/development/std/io.nct

io.nct

//! Common user-facing I/O API.
//!
//! `std/io` exposes ordinary file and text APIs. Fallible functions return `T!`
//! and fail with the compiler built-in `error` payload. Target-specific syscall
//! results and errno wrappers stay behind `#target`-gated std internals.

use std/error.Error
use std/mem.alloc_pages
use std/mem.free_pages
use std/os.{OSError, OSErrorKind}
use std/path.Utf8Path
use std/ptr.copy_str_to_ptr
use std/ptr.from_addr
use std/ptr.store_u8_to_ptr

#target: "arm64-darwin"
pub(nocter) primitive open_read_raw(path: *u8): i32!

#target: "arm64-darwin"
pub(nocter) primitive create_raw(path: *u8): i32!

#target: "arm64-darwin"
pub(nocter) primitive append_raw(path: *u8): i32!

#target: "arm64-darwin"
pub(nocter) primitive write_text_raw(fd: i32, text: &str): void!

#target: "arm64-darwin"
pub(nocter) primitive write_bytes_raw(fd: i32, bytes: &[u8]): void!

#target: "arm64-darwin"
pub(nocter) primitive read_bytes_raw(fd: i32, buffer: &+[u8]): usize!

#target: "arm64-darwin"
pub(nocter) primitive close_fd_raw(fd: i32): void

#target: "arm64-darwin"
pub(nocter) func stdout_fd(): i32 {
    return 1
}

#target: "arm64-darwin"
pub(nocter) func stderr_fd(): i32 {
    return 2
}

#target: "arm64-darwin"
pub(nocter) func open_read_fd(path: &str): i32! {
    let allocation_size: usize = path.len() + 1
    let address: usize = alloc_pages(allocation_size)?
    copy_str_to_ptr(from_addr(address), 0, path)
    store_u8_to_ptr(from_addr(address), allocation_size - 1, 0)

    let fd = open_read_raw(from_addr(address)) catch error {
        free_pages(address, allocation_size)
        return error
    }
    free_pages(address, allocation_size)
    return fd
}

#target: "arm64-darwin"
pub(nocter) func create_fd(path: &str): i32! {
    let allocation_size: usize = path.len() + 1
    let address: usize = alloc_pages(allocation_size)?
    copy_str_to_ptr(from_addr(address), 0, path)
    store_u8_to_ptr(from_addr(address), allocation_size - 1, 0)

    let fd = create_raw(from_addr(address)) catch error {
        free_pages(address, allocation_size)
        return error
    }
    free_pages(address, allocation_size)
    return fd
}

#target: "arm64-darwin"
pub(nocter) func append_fd(path: &str): i32! {
    let allocation_size: usize = path.len() + 1
    let address: usize = alloc_pages(allocation_size)?
    copy_str_to_ptr(from_addr(address), 0, path)
    store_u8_to_ptr(from_addr(address), allocation_size - 1, 0)

    let fd = append_raw(from_addr(address)) catch error {
        free_pages(address, allocation_size)
        return error
    }
    free_pages(address, allocation_size)
    return fd
}

#target: "arm64-darwin"
pub(nocter) func read_fd(fd: i32, buffer: &+[u8]): usize! {
    return read_bytes_raw(fd, buffer)?
}

#target: "arm64-darwin"
pub(nocter) func write_fd(fd: i32, bytes: &[u8]): void! {
    write_bytes_raw(fd, bytes)?
    return
}

#target: "arm64-darwin"
pub(nocter) func write_text_fd(fd: i32, text: &str): void! {
    write_text_raw(fd, text)?
    return
}

#target: "arm64-darwin"
pub(nocter) func close_fd(fd: i32): void {
    close_fd_raw(fd)
    return
}

// Compatibility classification type. It is an ordinary enum, not the fallible
// payload type.
pub(nocter) enum IOError {
    interrupted(error: OSError)
    would_block(error: OSError)
    not_found(error: OSError)
    permission_denied(error: OSError)
    already_exists(error: OSError)
    invalid_input(error: OSError)
    broken_pipe(error: OSError)
    timed_out(error: OSError)
    unsupported(error: OSError)
    unexpected_os_error(error: OSError)
}

pub struct File {
    fd: i32
    close_on_drop: bool
}

pub interface Reader {
    pub method &+self.read(buffer: &+[u8]): usize! from static
}

pub interface Writer {
    pub method &+self.write(bytes: &[u8]): void! from static
    pub method &+self.flush(): void! { return }
}

pub(nocter) func from_os_error(error: OSError): error {
    if error.kind is OSErrorKind.interrupted {
        return Error.new("std.io.interrupted", "operation interrupted")
    }
    if error.kind is OSErrorKind.would_block {
        return Error.new("std.io.would_block", "operation would block")
    }
    if error.kind is OSErrorKind.not_found {
        return Error.new("std.io.not_found", "file not found")
    }
    if error.kind is OSErrorKind.permission_denied {
        return Error.new("std.io.permission_denied", "permission denied")
    }
    if error.kind is OSErrorKind.already_exists {
        return Error.new("std.io.already_exists", "file already exists")
    }
    if error.kind is OSErrorKind.invalid_input {
        return Error.new("std.io.invalid_input", "invalid I/O input")
    }
    if error.kind is OSErrorKind.broken_pipe {
        return Error.new("std.io.broken_pipe", "broken pipe")
    }
    if error.kind is OSErrorKind.timed_out {
        return Error.new("std.io.timed_out", "operation timed out")
    }
    if error.kind is OSErrorKind.unsupported {
        return Error.new("std.io.unsupported", "unsupported I/O operation")
    }
    return Error.new("std.io.os_error", "OS operation failed")
}

pub func stdout(): File {
    return File { fd: stdout_fd(), close_on_drop: false }
}

pub func stderr(): File {
    return File { fd: stderr_fd(), close_on_drop: false }
}

pub func open(path: &str): File! {
    return File.open(path)?
}

pub func create(path: &str): File! { return File.create(path)? }

pub func append(path: &str): File! { return File.append(path)? }

pub func open_path(path: &Utf8Path): File! { return File.open(path.view())? }

pub func create_path(path: &Utf8Path): File! { return File.create(path.view())? }

pub func append_path(path: &Utf8Path): File! { return File.append(path.view())? }

construct File {
    pub default func open(path: &str): Self! {
        let fd = open_read_fd(path)?
        return File { fd: fd, close_on_drop: true }
    }

    pub func create(path: &str): Self! {
        let fd = create_fd(path)?
        return File { fd: fd, close_on_drop: true }
    }

    pub func append(path: &str): Self! {
        let fd = append_fd(path)?
        return File { fd: fd, close_on_drop: true }
    }
}

pub func read(file: &+File, buffer: &+[u8]): usize! {
    return read_fd(file.fd, buffer)?
}

pub func write(file: &+File, bytes: &[u8]): void! {
    write_fd(file.fd, bytes)?
    return
}

pub func write_text(file: &+File, text: &str): void! {
    write_text_fd(file.fd, text)?
    return
}

pub func close(file: &+File): void {
    if file.close_on_drop {
        close_fd(file.fd)
        file.close_on_drop = false
    }
    return
}

impl File {
    pub method &+self.write_text(text: &str): void! {
        write_text(self, text)?
        return
    }

    pub method &+self.close(): void {
        close(self)
        return
    }

    drop &+self {
        close(self)
        return
    }
}

impl Reader for File {
    method &+self.read(buffer: &+[u8]): usize! from static { return read(self, buffer)? }
}

impl Writer for File {
    method &+self.write(bytes: &[u8]): void! from static { write(self, bytes)? return }
    method &+self.flush(): void! { return }
}

pub func print(text: &str): void! {
    write_text_fd(1, text)?
    return
}

func unsupported(): error {
    return Error.new("std.io.unsupported", "unsupported I/O operation")
}