Os
let platform (): String
Which platform this program is running on: "linux", "macos" or "windows". Compiled in, not detected at runtime; a binary cannot move between platforms, so there is nothing to detect.
let temp_dir (): Result[String, String]
A fresh, empty, private directory. The caller owns it and is responsible for Os.remove_tree on it.
let self_exe (): Result[String, String]
The path of the running executable, for a program that needs to re-invoke itself.
let make_dir (path: String): Result[Unit, String]
Creates one directory. The PARENT must already exist; this makes a single level, not a path.
Err when the parent is missing, when the name is taken by a file, or when permission is refused. A directory that already exists is also an Err, so a caller who does not care should check Os.exists first.
let rename_file (from: String) (to: String): Result[Unit, String]
Atomic within one filesystem, and an error rather than a silent copy across two. The point is that the destination is never a half-written file.
let remove_file (path: String): Result[Unit, String]
Deletes a file. Err when it is not there, when it is a directory, or when permission is refused — Os.remove_tree is the one for directories.
let remove_tree (path: String): Result[Unit, String]
Deletes a directory and everything under it. Symlinks are removed, never followed into.
let copy_tree (src: String) (dst: String): Result[Unit, String]
Copies the CONTENTS of src into dst, which must already exist the semantics of cp -r src/. dst. File modes are not preserved.
let exit_with (code: Int): Unit
Exit with a status code, printing nothing. panic_raw also exits non-zero but prints its message first, which is wrong when the report has already been written.
let env_var (name: String): Option[String]
env_var_raw returns an ARRAY, empty when unset, matching read_file_raw: hand-written runtime IR cannot construct an Option without hard-coding tag numbers the backend assigns per program, so the wrapping happens here where Some/None are ordinary variants.
let read_file (path: String): Result[String, String]
Reads a whole file as text.
Err when the file is missing, unreadable, or not valid UTF-8. Use Os.read_bytes for a file that is not text, or that might not be.
let write_file (path: String) (contents: String): Result[Unit, String]
Writes text to a file, creating it or REPLACING what was there.
The parent directory must exist. Nothing is written on failure, so a full disk leaves the old contents rather than a truncated file.
let read_bytes (path: String): Result[Bytes, String]
An empty file reads back as empty Bytes, NOT as an error, because the array is empty only when the file could not be opened at all, so "no such file" and "a file with nothing in it" stay distinct.
let write_bytes (path: String) (data: Bytes): Result[Unit, String]
Writes bytes to a file, creating it or REPLACING what was there.
The Bytes counterpart to Os.write_file: use this for anything that is not text, and for text you have already encoded.
let append_bytes (path: String) (data: Bytes): Result[Unit, String]
Creates the file when it does not exist, the way >> does.
enum Mode
How to open. A closed set, passed as small integers across the extern boundary; an extern taking a mode STRING would let a caller hand fopen anything at all.
enum Seek
Where a seek measures from.
let open (path: String) (mode: Mode): Result[File, String]
Write truncates an existing file; Append does not. Both create one that is not there, and Read does not.
struct ProcessResult
What a finished process left behind.
exit_code is the child's own status: a non-zero one is an ordinary result, not an error. Failing to START the process is the Err. What a finished process left behind.
exit_code is the child's own status: a non-zero one is an ordinary result, not an error. Failing to START the process is the Err.
let run_process (program: String) (args: Array[String]): Result[ProcessResult, String]
A non-zero exit code is an ordinary Ok. Err means the process could not be started at all.
let cwd (): Result[String, String]
The process's current working directory, as an absolute path.
let chdir (path: String): Result[Unit, String]
Changes the process's working directory.
Affects the WHOLE process, including every relative path used afterwards and every child it spawns — so a library that changes it and does not change it back has altered something its caller never agreed to.
let home_dir (): Option[String]
$HOME, or %USERPROFILE% on Windows. None when it is unset -- no guessing from a username, because a guess that is usually right is the worst kind of wrong for a path a program is about to write to.
let read_stdin_line (): Result[Option[String], String]
--- Standard input and error ---
None is END OF STREAM and Some("") is a blank line, and keeping those apart is the whole reason these exist. The shims the language server uses return "" for both, so a filter reading until end of input would stop at the first blank line, which is wrong in a way that only shows up on real data.
The line arrives WITHOUT its newline, and a final line with no trailing newline is an ordinary line rather than something to drop.
let read_stdin (max_len: Int): Result[Bytes, String]
Up to max bytes, as Bytes. Stdin carries whatever was piped into it, which is not always text. An EMPTY result is end of stream; a SHORT one is data, because a pipe hands over what it has rather than waiting to fill a buffer.
The bytes are copied into a Plum value before this returns, so the result stays valid across later reads; the shim's own buffer is reused and would not.
let write_stderr (s: String): Unit
Writes to standard error and flushes. NOT println, which is stdout, since a diagnostic that lands in the same stream as the program's output cannot be separated from it by whoever is reading. No trailing newline is added; say so if you want one.
enum Input
--- Reading with a deadline (issue #7) ---
Three outcomes, and Option has already spent itself on two of them: read_stdin_line uses None for end of stream, which is the distinction the whole function exists for. A timed read has to say "a line", "the input ended" or "nothing arrived in time", so it says so in a type with three names rather than nesting two Options and asking the reader to remember which is which.
let read_stdin_line_timeout (d: Time.Duration): Result[Input[String], String]
The timeout bounds the WHOLE CALL, not just the wait for the first byte, so a slow writer cannot leave a caller blocked mid-line past its deadline. Bytes already read are kept, so TimedOut is never data loss: calling again resumes where this stopped.
let read_stdin_timeout (max_len: Int) (d: Time.Duration): Result[Input[Bytes], String]
Up to max_len bytes, with the same three outcomes. A SHORT read is Got, not TimedOut: a pipe handing over what it has is data arriving, not a deadline passing.
struct Metadata
No exists field: a path that is not there is an Err, so a Metadata in hand always describes something real. That is the contract is_directory already set, and having one of these answer exists: false while the other errored would be two answers to one question.
let exists (path: String): Result[Bool, String]
Three-way underneath, and the third case is why this returns a Result rather than a Bool: "does this exist" and "can I see whether this exists" are different questions. A permission error is an Err, not a silent Ok(false).
This is the ONE function here where a missing path is Ok, and deliberately so; it is the function whose whole job is to say so.
let stat (path: String): Result[Metadata, String]
A missing path is an Err, matching is_directory.
let file_size (path: String): Result[Int, String]
Bytes. Meaningful for a regular file; for a directory it is whatever the filesystem records for the directory itself, which is not the size of its contents.
let mtime (path: String): Result[Int, String]
Seconds since the Unix epoch, the same unit as Time.now(), so the two can be compared without a conversion.
let list_dir (path: String): Result[Array[String], String]
Lists a directory's entries by NAME, without . or .., in whatever order the filesystem gives.
Names, not paths: join them onto the directory with Path.join to get something you can open.
let is_directory (path: String): Result[Bool, String]
Whether the path is a directory. Ok(false) for a file, and for something that is not there at all — use Os.exists to tell those apart.