MALDA™ Reference Manual

The AI-First Programming Language - Version 1.0.11

12. Input/Output

This chapter is the I/O how-to: console, files, paths, environment variables, and host process facts. Prefer the io.* namespace. Flat aliases such as print and readFile still run, but the editor warns malda-style. Signatures, git helpers, glob / grep, document extractors, and Spectre.Console live in 13. Built-in Functions.

File I/O is a host capability. The interpreter and C# transpile support it; the JavaScript backend does not (file-io is no on the backend capability matrix).

12.1 Console Output

io.print(value) takes exactly one argument, converts it with string(value), and writes a line (a trailing newline). There is no println. Concatenate or interpolate before the call if you need several pieces on one line.

io.print("Hello, World!");

var name = "Alice";
io.print($"Hello, {name}!");   // interpolation — the $ is required
io.print("n is {n}");          // literal braces: a plain string does not interpolate

12.2 Console Input

io.input(prompt?) writes an optional prompt (no extra newline) and reads one line from stdin. The result is always a string. Convert it if you need a number.

var name = io.input("Enter your name: ");
var age = int(io.input("Enter your age: "));
io.print($"Hello, {name}. Next year you will be {age + 1}.");
End of input is an empty string, not null. At EOF every later io.input also returns "". A loop that only advances on non-empty input never terminates. Treat blank as quit:
while (true) {
    var raw = io.input("? ");
    if (str.trim(raw) == "") {
        break;
    }
    io.print(raw);
}

The call is asynchronous: execution pauses until a line arrives, then resumes at the same statement. It works inside loops, functions, and nested control structures.

For malformed numbers prefer toIntOr(raw, fallback) or toIntOrNull(raw) over bare int(...), which throws. See 13.1 Type Conversion.

12.3 Files

Paths are strings (or a matching capability token). Ordinary failures return null or false; they do not throw. Check the result.

12.3.1 Read and write

io.writeFile(path, content) overwrites the file and returns true on success, false on I/O failure. Strings are written as UTF-8. Objects and arrays are serialized with toJSON first. io.readFile(path) returns the whole file as a string, or null when the path is missing or unreadable.

io.writeFile("_rm_io_notes.txt", "hello");
io.print(io.hasFile("_rm_io_notes.txt"));
io.print(io.readFile("_rm_io_notes.txt"));
io.deleteFile("_rm_io_notes.txt");
io.print(io.hasFile("_rm_io_notes.txt"));
io.writeFile("_rm_io_json.txt", dict { "ok": true });
var obj = parseJSON(io.readFile("_rm_io_json.txt"));
io.print(obj.ok);
io.deleteFile("_rm_io_json.txt");
io.print(io.readFile("_rm_io_missing.txt") == null);

12.3.2 Line ranges and line arrays

Line numbers are 1-based.

io.writeFile("_rm_io_lines.txt", "a\nb\nc\nd");
io.print(io.readFile("_rm_io_lines.txt", 2, 3));
io.deleteFile("_rm_io_lines.txt");

12.3.3 Directories, delete, and binary

CallResult
io.hasFile(path)true when a file exists (disk or embed:)
io.hasDirectory(path)true when a directory exists
io.ensureDir(path)Creates the directory and missing parents; no-op if it already exists. Throws on failure. Rejects embed:
io.listDirectory(path)Array of { name, type, path }. type is "file" or "directory". Missing directories yield []. Empty path means "."
io.deleteFile(path)true if the file was deleted or was already absent; false on an empty path or I/O failure
io.readFileBase64(path) / io.writeFileBase64(path, b64)Raw bytes as base64. Read returns null when missing; write returns false on invalid base64
io.writeFile("_rm_io_list.txt", "x");
var items = io.listDirectory(".");
var found = false;
for (var item in items) {
    if (item.name == "_rm_io_list.txt") {
        found = true;
    }
}
io.print(found);
io.deleteFile("_rm_io_list.txt");

insertAtLine, replaceInFile, and editFile edit text in place — catalog and failure style in 13.8. insertAtLine is interpreter-only.

Digital text from documents: pdf.extractText(path, password?) (PdfPig text layer, no OCR) and doc.extractText(path) (.docx only). Those do throw on a missing file. See 13.8.1.

12.3.4 Embedded folders (embed:)

Compile with malda compile … --embed-folder <dir[=alias]> to pack a directory into the executable. At runtime, read with the virtual scheme embed:<alias>/<relative>. Nothing is extracted to disk. Writes (io.writeFile, io.ensureDir, io.deleteFile, …) reject embed: paths. io.hasEmbeddedFolder(alias) / io.embeddedFolderRoot(alias) inspect what was packed. Full example in 13.8.0.

12.4 Paths

Join and inspect paths instead of concatenating slashes. io.pathJoin uses the host separator. io.pathNormalize returns an absolute path.

io.print(io.getFileName("docs/notes.md"));
io.print(io.pathGetExtension("docs/notes.md"));
io.print(io.isPathUnder("brain", "brain/notes/a.md"));

12.5 Environment and Host

These read the process environment and the machine the program is running on. They are not file I/O, but they are how programs find keys, args, and a place to load sidecar files.

io.print(io.getEnvOr("MALDA_RM_IO_MISSING_VAR_7D05", "fallback"));
var host = getHostPlatform();
io.print(str.length(host.pathSeparator));

Assistant config under ~/.malda (getMaldaHome, getMaldaConfig) is documented with the personal assistant.

12.6 Capability Tokens

@effects("io") is a name allow-list. A tool that takes a path string can still be given "/etc/passwd". Mint an unforgeable token and pass that instead. Tokens are sealed host objects — a dict { kind, path } is not a token.

var notes = cap.fileRead("notes.md");
io.print(notes.kind);
io.print(cap.is(notes, "fileRead"));

Full contract and a forged-dict test: 13.19.3 and Examples/Tools/capability_tokens.malda.

12.7 Searching Files

io.glob(pattern, dirPath?) finds paths. io.grep(pattern, filePath) searches contents. Both take a required first argument and then optional positional arguments. On a missing path or a bad regex they return an error string, not a collection — guard with typeOf(result) == "string".

io.writeFile("_rm_io_glob.txt", "needle");
var found = io.glob("_rm_io_glob.txt", ".");
io.print(found.count);
io.deleteFile("_rm_io_glob.txt");

Default glob cap is 200 results (hard cap 500); the result is { items, count, truncated } with each item { name, type, path }. grep defaults to recursive search with line numbers. Argument lists and the count-only shape are in 13.8.2.

12.8 Sleep

sleep(milliseconds) pauses without spinning the CPU. The argument must be a non-negative integer. It is a built-in, not an io.* member. Useful for keep-alive loops around a server.

sleep(0);
io.print("awake");
while (server.isRunning) {
    sleep(1000);
}
Workflows: sleep, io.writeFile, and other deny-listed effects raise WF1001 / WF1002 when they run in a deterministic workflow section — including when nested in a helper. Put clock, sleep, and I/O inside a step. See 22. Durable Workflows.

12.9 What This Chapter Is Not

NeedGo to
HTTP client / REST calls28. REST Web Client
HTTP / HTML / ui.* servers23. Web UI Overview
SQL clients16. Database Support
Shell commandsrunCommand in 13.10 (blocked dangerous names, 1-hour timeout cap)
Git13.9
Agent file toolscreateReadFileTool and siblings in 18. Agent Orchestration

See Also