LISP - Input − Output
Lisp - Input & Output
Section titled “Lisp - Input & Output”Input and Output (I/O) are fundamental operations that allow a program to interact with the outside world. Common Lisp provides a rich and flexible set of tools for handling I/O, from reading user input to writing formatted data to files. This tutorial covers the essential functions and modern best practices for I/O in Common Lisp.
Understanding Streams
Section titled “Understanding Streams”In Lisp, all I/O operations happen on streams. A stream is a source or sink of data, such as the terminal, a file, or even a string in memory. Lisp provides several standard streams:
*standard-input*: The default stream for reading input, typically the keyboard.*standard-output*: The default stream for normal program output, typically the screen.*error-output*: The stream for error messages.*query-io*: The stream for interactive prompting and user conversations.
Input Functions
Section titled “Input Functions”The following table provides some of the most commonly used input functions in Common Lisp.
| Rank | Function & Modern Usage |
|---|---|
| 1 | read &optional input-stream eof-error-p eof-value recursive-p Reads a full Lisp object (an S-expression) from a stream. This is powerful but can be a security risk if reading from untrusted sources, as it can execute code. Best used for configuration files or internal data. |
| 2 | read-line &optional input-stream eof-error-p eof-value recursive-p Reads a line of text as a string. This is the safest and most common way to get user input. |
| 3 | read-char &optional input-stream eof-error-p eof-value recursive-p Reads a single character from the stream. |
| 4 | read-from-string string &key … Reads a Lisp object from a string, not a stream. Useful for parsing. |
| 5 | parse-integer string &key :start :end :radix :junk-allowed Safely parses an integer from a string. This is the recommended way to convert string input to a number. |
| 6 | peek-char &optional peek-type input-stream … Looks at the next character in the stream without consuming it. Useful for parsing complex text formats. |
| 7 | read-byte binary-input-stream &optional … Reads a single byte from a binary stream. Essential for working with binary files like images or network protocols. |
Reading and Processing User Input
Section titled “Reading and Processing User Input”The modern approach to reading user input is to read it as a line of text using read-line and then parse it. This avoids the security and usability issues of read.
Example: A Simple Greeting
Section titled “Example: A Simple Greeting”Let’s write a program that asks for the user’s name and prints a greeting. This demonstrates clean, interactive I/O.
;;; main.lisp
(defun greet-user () "Asks for the user's name and prints a personalized greeting." ;; Use princ for prompts without quotes and terpri for a new line. (princ "Please enter your name: ") ;; Ensure the prompt is displayed before waiting for input. (finish-output *query-io*)
;; Read the entire line of input safely. (let ((name (read-line *query-io*))) (format t "~%Hello, ~a! Welcome to modern Lisp programming.~%" name)))
(greet-user)Execution
Section titled “Execution”Please enter your name: Alice
Hello, Alice! Welcome to modern Lisp programming.Example: Calculating the Area of a Circle
Section titled “Example: Calculating the Area of a Circle”This example combines reading text, parsing it into a number, performing a calculation, and printing formatted output.
;;; main.lisp
(defun calculate-circle-area () "Prompts for a radius, calculates the circle's area, and prints the result." (princ "Enter the radius of the circle: ") (finish-output *query-io*)
(let ((radius-string (read-line *query-io*))) ;; Use a handler to gracefully manage invalid input. (handler-case ;; Safely parse the string to a float. (let* ((radius (parse-number:parse-number radius-string)) (area (* pi radius radius))) (format t "A circle with radius ~,2f has an area of ~,2f.~%" radius area)) ;; If parsing fails, this error is caught. (error (c) (format *error-output* "~%Invalid input: '~a' is not a valid number. ~a~%" radius-string c)))))
;; Note: This example assumes a library like `parse-number` is available.;; You could also use `(read-from-string radius-string)` but it's less safe.;; For a standard library only solution, you might do:;; (let ((radius (read-from-string radius-string))) ...)
(calculate-circle-area)Output (Successful Run)
Section titled “Output (Successful Run)”Enter the radius of the circle: 5.5A circle with radius 5.50 has an area of 95.03.Output (Invalid Input)
Section titled “Output (Invalid Input)”Enter the radius of the circle: five
Invalid input: 'five' is not a valid number. Could not parse "five" as a number.Output Functions
Section titled “Output Functions”Common Lisp offers several functions for printing data. The format function is by far the most powerful and versatile.
| Rank | Function and Description |
|---|---|
| 1 | format destination control-string &rest arguments Produces beautifully formatted output to a stream or a string. This is the go-to function for almost all output. |
| 2 | princ object &optional output-stream Prints an object in a human-readable format (e.g., strings without quotes). Good for prompts and messages. |
| 3 | prin1 object &optional output-stream Prints an object in a machine-readable format (e.g., strings with quotes). The output can be read back into Lisp with read. |
| 4 | print object &optional output-stream Like prin1, but adds a newline before and a space after the object. |
| 5 | pprint object &optional output-stream The “pretty printer”. It formats complex Lisp objects (like nested lists or structures) with indentation to make them easy to read. |
| 6 | write-line string &optional output-stream Writes a string to a stream, followed by a newline character. |
| 7 | terpri &optional output-stream (TERminate PRInt) Outputs a newline character. |
| 8 | finish-output & force-output &optional output-stream These functions manage output buffering, ensuring that data is actually sent to its destination (e.g., the terminal or a file). |
Formatted Output with format
Section titled “Formatted Output with format”The format function uses a control string with special directives, each starting with a tilde (~), to format its arguments. The first argument to format is the destination: t for *standard-output*, nil to return the formatted text as a string, or a specific stream.
Here are some of the most useful format directives:
| Directive | Description |
|---|---|
| ~a | Aesthetic (human-readable) representation. (like princ) |
| ~s | Standard (machine-readable) representation. (like prin1) |
| ~d | Decimal integer. |
| ~f | Fixed-format floating-point number. ~,2f rounds to 2 decimal places. |
| ~% | Outputs a newline character. |
| ~& | Fresh-line. Outputs a newline only if not already at the start of a line. |
| ~~ | Outputs a literal tilde character. |
| ~t | Tabulate. Used for creating columns. |
| ~? | Indirection. The argument is a format string itself, with its own arguments. |
Example: Generating a Report
Section titled “Example: Generating a Report”Let’s demonstrate the power of format by creating a simple, well-aligned report from a list of data.
;;; main.lisp
(defun generate-sales-report (data) "Generates a formatted sales report from a list of (item price quantity) lists." (format t "~&~va ~20t~va ~30t~va~%" "Item" "Price" "Total") (format t "~40,,,'-A~%" "") ; Print a separator line (dolist (record data) (let* ((item (first record)) (price (second record)) (quantity (third record)) (total (* price quantity))) (format t "~va ~20t$~,2f ~30t$~,2f~%" item price total))))
(let ((sales-data '(("Keyboard" 75.00 2) ("Mouse" 25.50 3) ("Monitor" 300.00 1)))) (generate-sales-report sales-data))Output
Section titled “Output”Item Price Total----------------------------------------Keyboard $75.00 $150.00Mouse $25.50 $76.50Monitor $300.00 $300.00Modern Development Practices
Section titled “Modern Development Practices”In modern Lisp development, you’ll often interact with files in standard formats like JSON or CSV. While Common Lisp has built-in functions for Lisp’s own data syntax, you’ll use libraries for others. Quicklisp is the de-facto package manager for Common Lisp, making it easy to install libraries for tasks like:
- Parsing JSON: The
jonathanorcl-jsonlibraries. - Parsing CSV: The
cl-csvlibrary. - File Path Management: The
uiop:merge-pathnames*orpathname-utilslibrary for cross-platform path handling.