Skip to content

LISP - Getting Substring

In Common Lisp, a string is a specialized one-dimensional array of characters. Because strings are sequences, many powerful sequence functions can be used to manipulate them. The most fundamental operation for extracting a part of a string is creating a substring.

The subseq function (short for sub-sequence) is the primary tool for extracting a portion of any sequence, including strings. Its syntax is (subseq sequence start &optional end).

  • sequence: The string (or other sequence) you want to extract from.
  • start: The 0-based starting index of the substring (inclusive).
  • end: The optional 0-based ending index (exclusive). If omitted, the substring goes to the end of the string.
(defparameter *message* "Welcome to the Lisp world!")
;; Get substring from index 15 to the end
(format t "Substring (end): '~a'~%" (subseq *message* 15))
;; Get substring from index 8 to 14
(format t "Substring (range): '~a'~%" (subseq *message* 8 14))
Substring (end): 'Lisp world!'
Substring (range): 'to the'

Calling subseq with invalid indices will signal an error. It’s good practice to validate indices or handle potential errors gracefully, for example, by using handler-case.

(defun safe-subseq (str start &optional end)
"A safe wrapper around subseq that returns NIL on error."
(handler-case (subseq str start end)
;; In Common Lisp, all errors inherit from the `error` condition.
(error (c)
(format t "Error while getting substring: ~a~%" c)
nil)))
;; --- Usage ---
;; This will fail because the start index is out of bounds
(let ((result (safe-subseq "Hello" 10)))
(format t "Result of invalid call: ~a~%" result))
;; This will fail because the end index is out of bounds
(let ((result (safe-subseq "Hello" 0 100)))
(format t "Result of invalid call: ~a~%" result))
Error while getting substring: The start bounding index, 10, is beyond the end of the sequence, 5.
Result of invalid call: NIL
Error while getting substring: The end bounding index, 100, is beyond the end of the sequence, 5.
Result of invalid call: NIL

The handler-case macro attempts to execute its first form. If an error condition is signaled during execution, it catches the condition object c, prints a message, and returns nil instead of crashing the program.

Practical Application: Parsing a Log Entry

Section titled “Practical Application: Parsing a Log Entry”

Substrings are essential for parsing structured text. Let’s write a function to parse a log entry like "[INFO] 2023-10-27: User logged in." into its components.

(defun parse-log-entry (log-line)
"Parses a log entry and returns its components as a list."
(let* ((level-end (position #] log-line))
(date-start (+ level-end 2))
(date-end (+ date-start 10))
(msg-start (+ date-end 2)))
(list
:level (subseq log-line 1 level-end)
:date (subseq log-line date-start date-end)
:message (subseq log-line msg-start))))
;; --- Usage ---
(defparameter *log1* "[INFO] 2023-10-27: User logged in.")
(defparameter *log2* "[ERROR] 2023-10-28: Database connection failed.")
(print (parse-log-entry *log1*))
(print (parse-log-entry *log2*))
(:LEVEL "INFO" :DATE "2023-10-27" :MESSAGE "User logged in.")
(:LEVEL "ERROR" :DATE "2023-10-28" :MESSAGE "Database connection failed.")

Beyond subseq, the Lisp standard library includes many other helpful functions for string manipulation:

  • Searching: (search "needle" "haystack") finds the starting position of a substring.
  • Concatenation: (concatenate 'string "Hello, " "world!") joins multiple strings together.
  • Trimming: (string-trim '(#\Space #\Tab) " some text ") removes whitespace from the ends.
  • Case Conversion: string-upcase, string-downcase, and string-capitalize change the case of a string.