Pudu programming language
Menu
All documentation pages

Documentation

Files and the system

Author
Chris M. Pérez Santiago
Version
0.1.0
Source
Edit this page on GitHub

Programs read files, write output, and ask the machine questions. In Pudu every one of those can fail, so every one returns a Result, and nothing reaches the outside world except through a standard library call you can see.

Output

Std.Io writes to the program's standard output and error output:

module Report

import Std.Io as Io

fn main() -> Int {
  let lines = ["name   score", "ada       95", "grace    100"]
  match Io.writeLines(&lines) {
    case Ok(_) => 0
    case Err(problem) => {
      let _reported = Io.writeErrorLine("could not write the report: {problem}")
      1
    }
  }
}
FunctionWrites
Io.writeLine(text)one line to standard output
Io.writeLines(&lines)several lines, stopping at the first failure
Io.writeErrorLine(text)one line to standard error
Io.writeValue(value)any value, rendered as text

Reading and writing files

A whole file is read with Io.read and written with Io.write. Both use plain text paths and answer a Result whose error says what went wrong:

module Notes

import Std.Env as Env
import Std.Io as Io

fn keepNotes(path: Str) -> Result[Int, Str] {
  Io.write(path, "first note\n") ?
  Io.appendLine(path, "second note") ?
  let lines = Io.readLines(path) ?
  let text = Io.read(path) ?
  Io.remove(path) ?
  if text.contains("second") { Ok(lines.length()) } else { Err("the note was not kept") }
}

fn main() -> Int {
  let path = Io.join(Env.temporaryDirectory(), "pudu-notes-example.txt")
  match keepNotes(path) {
    case Ok(count) => if count == 2 && !Io.exists(path) { 0 } else { 1 }
    case Err(_) => 1
  }
}

Because every call answers a Result, ? gives a function a straight line through the steps that stops at the first one that fails.

Large files

Io.read holds a whole file in memory. For a file that may be large, fold over it one line at a time instead; only the current chunk is ever held:

module Totals

import Std.Env as Env
import Std.Io as Io
import Std.Option as Option
import Std.Text as Text

fn main() -> Int {
  let path = Io.join(Env.temporaryDirectory(), "pudu-totals-example.txt")
  let prepared = Io.writeFileLines(path, &["10", "20", "not a number", "12"])
  if prepared != Ok(()) { return 1 }
  let summed = Io.foldLines(path, 0, fn(total: Int, line: Str) => total + Option.unwrapOr(Text.wholeOf(line), 0))
  let _removed = Io.remove(path)
  if summed == Ok(42) { 0 } else { 1 }
}

Io.forEachLine does the same when there is nothing to accumulate, and Std.Json.foldLines and Std.Csv.foldRows read structured files the same way.

Paths

Std.Path builds and takes apart paths without touching the filesystem:

module Paths

import Std.Path as Path

fn main() -> Int {
  let report = Path.joinAll(&["reports", "2026", "summary.csv"])
  let checks = [
    Path.nameOf(report) == "summary.csv",
    Path.stemOf(report) == "summary",
    Path.extensionOf(report) == Some("csv"),
    Path.withExtension(report, "json").endsWith("summary.json"),
    Path.normalize("reports/./2026/../2027") == Path.join("reports", "2027")
  ]
  if checks.filter(fn(held: Bool) => !held).isEmpty() { 0 } else { 1 }
}

Directories

FunctionDoes
Io.list(directory)the names a directory holds
Io.listPaths(directory)the same names, joined to the directory
Io.makeDirectory(path)creates a directory and any parents it needs
Io.exists(path)whether something is there
Fs.metadata(path)size, kind, and permissions
Fs.removeTree(path)removes a directory and everything under it
Fs.writeTextAtomically(path, text)replaces a file so no reader ever sees it half written

The environment

Std.Env answers questions about how the program was started:

module Settings

import Std.Env as Env
import Std.Option as Option
import Std.Text as Text

fn portSetting() -> Int {
  let written = Env.variableOr("PUDU_EXAMPLE_PORT", "8080")
  Option.unwrapOr(Text.wholeOf(written), 8080)
}

fn main() -> Int {
  let verbose = Env.hasFlag("--verbose")
  let started = Env.elapsedMilliseconds()
  if portSetting() > 0 && (verbose || !verbose) && started >= 0 { 0 } else { 1 }
}
FunctionAnswers
Env.all()every argument
Env.at(position)one argument, or None
Env.hasFlag(flag)whether an exact argument was given
Env.option(name)the value after a named argument, as in --port 80
Env.variable(name)an environment variable, or None
Env.variableOr(name, fallback)an environment variable, or a fallback

For a program with many options, Std.Args declares them, checks them, and writes the help text.

Other programs

Std.Process starts other programs, writes to their input, and reads their output, with the same Result on every step.