Programming Language

Nocter

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

/development/std/process.nct

process.nct

//! User-facing `std/process` API.
//!
//! exit is a standard-library API, not a compiler primitive. The target-specific
//! implementation calls the active target boundary.

use std/error.Error
use std/mem.RawBuffer
use std/mem.TryAllocator
use std/mem.alloc
use std/mem.alloc_pages
use std/mem.bytes_mut as raw_bytes_mut
use std/mem.free_pages
use std/mem.free
use std/mem.page_allocator
use std/mem.try_alloc
use std/mem.try_free
use std/os.darwin_f_getpath
use std/os.darwin_max_path_len
use std/os.darwin_sys_close
use std/os.darwin_sys_fcntl
use std/os.darwin_sys_open
use std/os.syscall1
use std/os.syscall3
use std/os.trap
use std/ptr.addr
use std/ptr.copy_str_to_ptr
use std/ptr.from_addr
use std/ptr.store_u8_to_ptr
use std/ptr.str_from_raw_parts
use std/string.String
use std/string.bytes as string_bytes
use std/vec.Vec

#target: "arm64-darwin"
pub(nocter) primitive exit_raw(code: i32): never

#target: "arm64-darwin"
pub(nocter) primitive arg_count_raw(): usize

#target: "arm64-darwin"
pub(nocter) primitive arg_raw(index: usize): &str from static

#target: "arm64-darwin"
pub(nocter) primitive env_count_raw(): usize

#target: "arm64-darwin"
pub(nocter) primitive env_name_raw(index: usize): &str from static

#target: "arm64-darwin"
pub(nocter) primitive env_value_raw(index: usize): &str from static

pub copy struct EnvironmentEntry {
    pub name: &str
    pub value: &str
}

#target: "arm64-darwin"
pub(nocter) func open_current_directory(): usize! {
    let allocation_size: usize = 2
    let address: usize = alloc_pages(allocation_size)?
    copy_str_to_ptr(from_addr(address), 0, ".")
    store_u8_to_ptr(from_addr(address), allocation_size - 1, 0)

    let result = syscall3(darwin_sys_open(), address, 0, 0)
    free_pages(address, allocation_size)
    if result.errno != 0 {
        return cwd_failed()
    }
    return result.value
}

#target: "arm64-darwin"
pub(nocter) func open_current_directory_aborting(): usize! {
    let allocation_size: usize = 2
    var allocator = page_allocator()
    var path = alloc(&+allocator, allocation_size, 1)
    copy_str_to_ptr(path.ptr, 0, ".")
    store_u8_to_ptr(path.ptr, allocation_size - 1, 0)

    let result = syscall3(darwin_sys_open(), addr(path.ptr), 0, 0)
    free(&+allocator, move path)
    if result.errno != 0 {
        return cwd_failed()
    }
    return result.value
}

#target: "arm64-darwin"
pub(nocter) func close_fd_word(fd: usize): void {
    let result = syscall1(darwin_sys_close(), fd)
    return
}

#target: "arm64-darwin"
pub(nocter) func cwd_path_len(buffer: &+RawBuffer): usize! {
    let raw_bytes = raw_bytes_mut(buffer)
    var index: usize = 0
    while index < buffer.len {
        if raw_bytes[index] == 0 {
            return index
        }
        index = index + 1
    }
    return cwd_failed()
}

#target: "arm64-darwin"
pub(nocter) func fill_cwd_path(fd: usize, buffer: &+RawBuffer): usize! {
    let result = syscall3(
        darwin_sys_fcntl(),
        fd,
        darwin_f_getpath(),
        addr(buffer.ptr),
    )
    if result.errno != 0 {
        return cwd_failed()
    }
    return cwd_path_len(buffer)?
}

pub func args(): Vec<&str>! from static {
    let count: usize = arg_count_raw()
    var values: Vec<&str> = Vec.with_capacity(count)
    var index: usize = 0
    while index < count {
        let value = arg_raw(index)
        if !valid_utf8(value) {
            return invalid_encoding()
        }
        values.push(value)
        index = index + 1
    }
    return move values
}

pub func arg_count(): usize {
    return arg_count_raw()
}

pub func arg(index: usize): &str?! from static {
    let count: usize = arg_count_raw()
    if index >= count { return none }
    let value = arg_raw(index)
    if !valid_utf8(value) { return invalid_encoding() }
    return value
}

pub func environment_count(): usize {
    return env_count_raw()
}

pub func environment(index: usize): EnvironmentEntry?! from static {
    let count: usize = env_count_raw()
    if index >= count { return none }
    let name = env_name_raw(index)
    let value = env_value_raw(index)
    if !valid_utf8(name) || !valid_utf8(value) { return invalid_encoding() }
    return EnvironmentEntry { name: name, value: value }
}

pub func env(name: &str): &str?! from static {
    if !valid_environment_name(name) {
        return invalid_encoding()
    }
    let count = env_count_raw()
    var index: usize = 0
    while index < count {
        let entry_name = env_name_raw(index)
        let entry_value = env_value_raw(index)
        if !valid_utf8(entry_name) || !valid_utf8(entry_value) {
            return invalid_encoding()
        }
        if entry_name == name {
            return entry_value
        }
        index = index + 1
    }
    return none
}

pub func cwd(): String! {
    let fd = open_current_directory_aborting()?
    var allocator = page_allocator()
    var buffer = alloc(&+allocator, darwin_max_path_len(), 1)
    let len = fill_cwd_path(fd, &+buffer) catch error {
        free(&+allocator, move buffer)
        close_fd_word(fd)
        return error
    }
    let view = str_from_raw_parts(buffer.ptr, len)
    let value = String.copy(view)
    free(&+allocator, move buffer)
    close_fd_word(fd)
    return move value
}

pub func try_cwd(allocator: &+TryAllocator): String! from allocator {
    let fd = open_current_directory() catch error {
        return error
    }
    var buffer = try_alloc(allocator, darwin_max_path_len(), 1) catch error {
        close_fd_word(fd)
        return error
    }
    let len = fill_cwd_path(fd, &+buffer) catch error {
        try_free(allocator, move buffer)
        close_fd_word(fd)
        return error
    }
    let view = str_from_raw_parts(buffer.ptr, len)
    let value = String.try_from_str(allocator, view) catch error {
        try_free(allocator, move buffer)
        close_fd_word(fd)
        return error
    }
    try_free(allocator, move buffer)
    close_fd_word(fd)
    return move value
}

pub(nocter) func valid_environment_name(name: &str): bool {
    if name.len() == 0 || !valid_utf8(name) {
        return false
    }
    let name_bytes = string_bytes(name)
    var index: usize = 0
    while index < name_bytes.len() {
        if name_bytes[index] == 0 || name_bytes[index] == 61 {
            return false
        }
        index = index + 1
    }
    return true
}

pub(nocter) func valid_utf8(value: &str): bool {
    let value_bytes = string_bytes(value)
    var index: usize = 0
    while index < value_bytes.len() {
        let first = value_bytes[index]
        if first <= 127 {
            index = index + 1
        } else if first >= 194 && first <= 223 {
            if index + 1 >= value_bytes.len() || !utf8_continuation(value_bytes[index + 1]) {
                return false
            }
            index = index + 2
        } else if first >= 224 && first <= 239 {
            if index + 2 >= value_bytes.len() {
                return false
            }
            let second = value_bytes[index + 1]
            let second_valid = if first == 224 {
                second >= 160 && second <= 191
            } else if first == 237 {
                second >= 128 && second <= 159
            } else {
                utf8_continuation(second)
            }
            if !second_valid || !utf8_continuation(value_bytes[index + 2]) {
                return false
            }
            index = index + 3
        } else if first >= 240 && first <= 244 {
            if index + 3 >= value_bytes.len() {
                return false
            }
            let second = value_bytes[index + 1]
            let second_valid = if first == 240 {
                second >= 144 && second <= 191
            } else if first == 244 {
                second >= 128 && second <= 143
            } else {
                utf8_continuation(second)
            }
            if !second_valid || !utf8_continuation(value_bytes[index + 2]) || !utf8_continuation(value_bytes[index + 3]) {
                return false
            }
            index = index + 4
        } else {
            return false
        }
    }
    return true
}

pub(nocter) func utf8_continuation(value: u8): bool {
    return value >= 128 && value <= 191
}

pub func exit(code: i32): never {
    exit_raw(code)
}

pub func abort(): never {
    trap()
}

pub func cwd_failed(): error {
    return Error.new("std.process.cwd_failed", "current working directory is not available")
}

pub func invalid_encoding(): error {
    return Error.new(
        "std.process.invalid_encoding",
        "process text or requested environment name is invalid",
    )
}