Skip to content

LISP - Streams

Input/Output (I/O) in Common Lisp is handled through streams. A stream is a source or sink of data, such as the console, a file, or even a string. This provides a uniform API for interacting with different kinds of data sources.

Common Lisp provides several standard streams that are always available:

  • *standard-input*: The default input stream, typically connected to the keyboard.
  • *standard-output*: The default output stream, typically connected to the console display.
  • *error-output*: Used for printing error messages and warnings.
  • *trace-output*: Used for tracing function execution.

The format Function: Your Go-To for Output

Section titled “The format Function: Your Go-To for Output”

While functions like write and write-line exist, the format function is the most powerful and versatile tool for producing formatted output. Its first argument is the destination stream (t for *standard-output*), and the second is a control string with directives.

  • ~a (Aesthetic): Prints an object in a human-readable way.
  • ~s (S-expression): Prints an object in a machine-readable Lisp syntax.
  • ~d (Decimal): Prints an integer in base 10.
  • ~%: Prints a newline character.
  • ~~: Prints a literal tilde ~.
(let ((name "World") (n 42))
(format t "Hello, ~a! The number is ~d.~%" name n))
; Output: Hello, World! The number is 42.

You can also use format to create strings by using nil as the destination:

(let ((message (format nil "User ~a logged in." "Alice")))
(print message))
; Output: "User Alice logged in."

The best practice for file I/O is the with-open-file macro. It automatically opens a file, binds the stream to a variable, and—most importantly—ensures the stream is closed afterward, even if an error occurs.

Example: Writing to and Reading from a File

Section titled “Example: Writing to and Reading from a File”
(defun file-io-example ()
(let ((filename "greetings.txt"))
;; Write to the file
(with-open-file (out-stream filename
:direction :output
:if-exists :supersede)
(format out-stream "Hello from Lisp!~%")
(format out-stream "The year is ~d.~%" 2023))
(format t "File '~a' written successfully.~%~%" filename)
;; Read from the file and print each line
(format t "Reading from file:~%")
(with-open-file (in-stream filename
:direction :input)
(loop for line = (read-line in-stream nil nil)
while line
do (format t " -> ~a~%" line)))))
;; Run the example
(file-io-example)

You can wrap file operations in handler-case to gracefully handle potential errors, like a file not being found.

(handler-case
(with-open-file (stream "non-existent-file.txt"))
(file-error (c)
(format *error-output* "Caught a file error: ~a~%" c)))

String streams allow you to treat a string as if it were a file. This is useful for parsing data from a string or building up a complex string in memory.

(let ((data-string "user-id 12345"))
(with-input-from-string (s data-string)
(let ((header (read s))
(id (read s)))
(format t "Header: ~s, ID: ~s~%" header id))))
; Output: Header: USER-ID, ID: 12345

Gray Streams are a standardized extension to Common Lisp that allows you to implement your own custom stream classes. This is an advanced feature used for tasks like creating streams that compress data, communicate over a network, or interact with custom hardware. For most applications, the built-in stream types are sufficient.