Skip to content

LISP - Strings

In Common Lisp, strings are a specialized type of vector, specifically a one-dimensional array whose elements are characters. This structure makes them a sequence, allowing many powerful sequence-manipulation functions to operate on them directly.

String literals are created by enclosing a sequence of characters in double quotes ("). Special characters, such as the double quote itself or the backslash (\), must be escaped with a preceding backslash. For example, "This is a \"quoted\" string.".

Let’s start with some fundamental string operations. Create a new source file named main.lisp and add the following code. A modern Lisp development workflow often involves an interactive REPL (Read-Eval-Print Loop), where you can evaluate these expressions one by one.

;; The `write-line` function prints a string followed by a newline.
(write-line "Hello, Modern Lisp!")
;; The `format` function is a more powerful tool for creating formatted strings.
;; `~a` is a placeholder for an argument, and `~%` is a newline character.
(format t "Welcome to ~a!~%" "the Lisp Tutorial")
;; Escaping special characters
(write-line "This demonstrates \"escaping\" inside a string.")
Hello, Modern Lisp!
Welcome to the Lisp Tutorial!
This demonstrates "escaping" inside a string.

Numeric comparison operators like < and > do not work on strings. Common Lisp provides a dedicated set of functions for lexicographical string comparison, with both case-sensitive and case-insensitive variants.

Case-Sensitive FunctionCase-Insensitive FunctionDescription
string=string-equalReturns T if all argument strings are identical.
string/=string-not-equalReturns the index of the first mismatch, or NIL if any two strings are identical.
string<string-lesspReturns the index of the first mismatch if strings are in strictly ascending lexicographical order, else NIL.
string>string-greaterpReturns the index of the first mismatch if strings are in strictly descending lexicographical order, else NIL.
string<=string-not-greaterpReturns the index of the first mismatch if strings are in ascending (or equal) lexicographical order, else NIL.
string>=string-not-lesspReturns the index of the first mismatch if strings are in descending (or equal) lexicographical order, else NIL.

Let’s explore how these comparison functions work in practice.

;; Case-sensitive equality check
(format t "'alpha' = 'Alpha' -> ~a~%" (string= "alpha" "Alpha"))
;; Case-insensitive equality check
(format t "'alpha' equal 'Alpha' -> ~a~%" (string-equal "alpha" "Alpha"))
;; Less-than check (case-sensitive)
;; 'A' comes before 'a' in ASCII/Unicode
(format t "'Alpha' < 'alpha' -> ~a~%" (string< "Alpha" "alpha"))
;; Mismatch check
;; The mismatch occurs at index 3 ('h' vs 'a')
(format t "'Lisp' /= 'Lisa' -> ~a~%" (string/= "Lisp" "Lisa"))
;; No mismatch returns NIL for inequality checks
(format t "'test' /= 'test' -> ~a~%" (string/= "test" "test"))
'alpha' = 'Alpha' -> NIL
'alpha' equal 'Alpha' -> T
'Alpha' < 'alpha' -> 0
'Lisp' /= 'Lisa' -> 3
'test' /= 'test' -> NIL

Common Lisp provides simple functions for controlling the case of characters within a string.

FunctionDescription
string-upcaseReturns a new string with all alphabetic characters converted to uppercase.
string-downcaseReturns a new string with all alphabetic characters converted to lowercase.
string-capitalizeReturns a new string with the first letter of each word capitalized.
(let ((my-string "a modern lisp tutorial"))
(write-line (string-upcase my-string))
(write-line (string-capitalize my-string)))
A MODERN LISP TUTORIAL
A Modern Lisp Tutorial

These functions are useful for cleaning up user input or data from files by removing unwanted characters from the beginning or end of a string.

FunctionDescription
string-trimRemoves all characters from the character-bag from both the beginning and end of the string.
string-left-trimRemoves characters from the character-bag from the beginning (left side) of the string.
string-right-trimRemoves characters from the character-bag from the end (right side) of the string.

The first argument to these functions is a sequence of characters to be removed, often called a ‘character-bag’.

(let* ((char-bag '(#\Space #\Tab #\Newline))
(messy-string " some text
"))
(format t "Original: '~a'~%" messy-string)
(format t "Trimmed: '~a'~%" (string-trim char-bag messy-string))
(format t "Left-Trimmed: '~a'~%" (string-left-trim char-bag messy-string))
(format t "Right-Trimmed: '~a'~%" (string-right-trim char-bag messy-string)))
Original: ' some text
'
Trimmed: 'some text'
Left-Trimmed: 'some text
'
Right-Trimmed: ' some text'

Since strings are sequences, a wide range of powerful functions can be used on them. Here are some of the most common ones.

(let ((s "Hello World"))
;; `length` returns the number of characters in the string.
(format t "Length: ~d~%" (length s))
;; `subseq` extracts a part of the string. Indices are 0-based.
;; This extracts from index 6 to the end.
(format t "Substring: ~s~%" (subseq s 6))
;; `char` accesses a single character at a specific index.
(format t "Character at index 6: ~c~%" (char s 6)))
Length: 11
Substring: "World"
Character at index 6: W

These operations demonstrate the power of treating strings as sequences.

;; `sort` sorts a sequence. It's destructive, so we sort a copy.
;; `char<` is the predicate used for comparison.
(let ((original "ecbda"))
(let ((sorted (sort (copy-seq original) #'char<)))
(format t "Sorted '~a' -> '~a'~%" original sorted)))
;; `reverse` reverses the order of elements in a sequence.
(write-line (reverse "live"))
;; `concatenate` joins sequences. The first argument specifies the result type.
(write-line (concatenate 'string "Hello, " "Lisp " "World!"))
Sorted 'ecbda' -> 'abcde'
evil
Hello, Lisp World!
  • Use format for complex construction: While concatenate is good for simple joining, format is more flexible and readable for building complex strings from various data types.
  • Understand string immutability: Functions like string-upcase or reverse return a new string. Destructive functions like sort should be used with caution, typically on a copy-seq of the original string to avoid unexpected side effects.
  • Explore string libraries: For advanced needs like regular expressions, consider using a library from Quicklisp, the de-facto package manager for Common Lisp. A popular choice is cl-ppcre.