Documentation/Chapters

Console input, files and subprocesses

On this page

A utility often reads input, processes text and writes a result. BASIL supplies simple console statements and owned platform resources for larger operations. The calls use the same checked errors and ordinary suspension as the rest of the language.

Read console input

This interactive program asks for a complete line:

Example requirements: interactive program; provide a line on stdin.

LINE INPUT "Your name: "; name
PRINT "Hello, "; name

LINE INPUT reads a line without interpreting comma-separated fields. INPUT performs checked field conversion into typed destinations. Conversion and field count errors leave the destination set uncommitted, rather than partially updating it. ReadLine returns an optional line and uses NONE for end of input, which makes stream-processing loops convenient.

The following checked example is fed two lines by the book verifier:

DO
    IF LET line = ReadLine() THEN
        PRINT UPPER(line)
    ELSE
        EXIT DO
    END IF
LOOP

PRINT separates adjacent semicolon items without adding spaces. Commas advance to print zones; they are not CSV output. A trailing separator suppresses the normal newline. Use explicit formatting when generating machine-readable files.

Read and write files

ReadFileText reads a whole UTF-8 file and validates its encoding. ReadBinaryFile returns packed bytes; DecodeUtf8 performs an explicit checked conversion. Large or incremental data can use OpenFile, ReadFileInto and WriteFile instead.

Example requirements: filesystem example; requires an existing notes.txt in the working directory.

TRY
    text = ReadFileText("notes.txt")
    PRINT text
CATCH problem AS IoError
    PRINT "Could not read notes.txt: "; problem.Code
CATCH problem AS StringError
    PRINT "The file is not valid UTF-8"
END TRY

Relative paths resolve from the running process’s working directory, not from the source file. Paths are UTF-8 and cannot contain an interior NUL. Files close on recoverable exits. Calling Close early is useful when a long-lived scope no longer needs a handle; do not construct independent owners around native descriptors.

GetFileInfo provides metadata. ReadDirectory lists entries. MakeDirectory and SetFilePermissions perform checked changes. ReadFileSnapshot and WriteFileAtomic support version-aware editing; FileChanged lets an editor avoid silently overwriting another writer’s work. Treat version conflicts as an application decision to reload, compare or explicitly overwrite.

File open/read/stat/directory/seek operations use bounded workers and park the caller. Writes, flushes and some mutations remain synchronous. A coroutine-friendly API does not mean every underlying filesystem operation is nonblocking.

Run another program

RunProcess captures stdout and stderr while pumping input and output together. It accepts an executable and exact argument strings; it does not insert a shell. An argument containing *, spaces or shell punctuation stays an argument. To request shell interpretation you would have to invoke a shell explicitly.

For long-running processes, use StartProcess, ReadProcessInto, WriteProcess, CloseProcessInput, ProcessExited and WaitProcess. TryReadProcessInto distinguishes NONE (not ready) from SOME(0) (EOF). Captured output currently grows without a built-in application output limit; use streaming and an explicit policy when handling potentially large output.

Example requirements: process creation requires /usr/bin/printf on the host.

child = StartProcess("/usr/bin/printf", ["Hello from a child"])
PRINT WaitProcess(child)

This only demonstrates starting and waiting; use the capture API to consume its output. A Process owns its child leader/group and pipes. Cleanup terminates and acknowledges completion before releasing retained storage; it does not reap other libraries’ children. External detached descendants are outside this initial containment model.

Terminal applications

OpenTerminal provides an owned terminal session. EnterTerminalScreen, cursor/color operations and ReadTerminalEvent support full-screen applications. An event timeout returns NONE; EOF is a present event of its defined kind. Unicode scalar positions and terminal display-cell widths differ, so use TerminalTextWidth/TerminalClipText when laying out text. Cleanup restores terminal mode on ordinary recoverable exits. The BASIL IDE is a complete example of these facilities.

Buffer an owned stream

BufferedReader and BufferedWriter own a File or TCPConnection, including TLS. Use TAKE for a named endpoint; a fresh result such as OpenFile or Stdout can be adopted directly. The buffers are resources, so assignment does not implicitly copy them. Their internal byte accumulators do not change that ownership rule.

writer = BufferedWriter(OpenFile("greeting.txt", "w"))
WriteLine(BYREF writer, "Hello, λ")
file = Finish(TAKE writer)
Close(file)
reader = BufferedReader(OpenFile("greeting.txt"), Capacity=16)
IF LET line = ReadLine(BYREF reader) THEN
    PRINT line
END IF
IF LET extra = ReadLine(BYREF reader) THEN
    PRINT "Unexpected extra line"
ELSE
    PRINT "EOF"
END IF

ReadLine strips LF and a preceding CR. An empty line is SOME(""); empty EOF is NONE. The final line need not have a newline. MaxBytes defaults to 1 MiB and counts bytes before LF, including CR. A line-limit or UTF-8 failure may already have consumed input. Buffered ReadBytes returns up to the requested maximum, stopping at EOF; ReadInto may return just one buffered segment. EndOfStream becomes true only after EOF has been observed and lookahead is exhausted.

Always Flush a writer or Finish it before relying on its output. Cleanup discards pending bytes and closes the endpoint; it cannot report a fallible flush. Finish consumes the writer, flushes and returns ownership of the endpoint. Flush drains the buffer; it is not a disk fsync. A failed write has already queued its complete argument: retry Flush to send the unsent suffix, rather than submitting the same argument twice. BufferSize is a flush threshold, not a maximum memory allocation.

Use standard streams as File values

writer = BufferedWriter(Stdout())
WriteLine(BYREF writer, "Buffered hello")
Flush(BYREF writer)
reader = BufferedReader(Stdin())
IF LET line = ReadLine(BYREF reader) THEN
    PRINT line
END IF

Stdin, Stdout and Stderr return fresh owned duplicates of inherited streams, including redirected files and pipes. Closing a duplicate leaves the original available to PRINT and the other console helpers. The duplicate owners still share underlying position, EOF and file status: use one buffered reader per stream and coordinate writers. Read-only borrowing does not freeze external OS state. File writes remain synchronous; a blocked pipe read may need peer data or EOF before cancellation completes. Endpoint(buffer) borrows its endpoint; bypassing a reader skips lookahead, and bypassing a writer can reorder output. Unbuffer(TAKE reader) returns the endpoint while discarding unread lookahead.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.