HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Expand string patterns in Elisp

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
expandstringpatternselisp

Problem

I need to expand logical patterns to list of strings.

For example I have the following definition:

(expand-pattern '(and "xy"
                      (or "1" "2")
                      (or (and "ab0"
                               (or "1" "2" "3"))
                          "cd01")))


Which evaluates to the following list of strings:

("xy1ab01" "xy2ab01" "xy1ab02" "xy2ab02" "xy1ab03" "xy2ab03" "xy1cd01" "xy2cd01")


I wrote the following code:

(defun expand-pattern (pattern)
  (defun expat (parents expr)
    (pcase expr
      ((or (pred stringp)
           (pred numberp))   (mapcar (lambda (parent)
                                     (cons expr parent))
                                     parents))
      (`(and ,first)         (expat parents first))
      (`(and ,first . ,rest) (expat (expat parents first)
                                    (cons 'and rest)))
      (`(or . ,args)         (apply #'append 
                                    (mapcar (lambda (arg)
                                              (expat parents arg))
                                            args)))
      (_                     (error "unknown %S" expr))))
  (mapcar (lambda (tokens)
            (mapconcat (lambda (token)
                         (format "%s" token))
                       (reverse tokens)
                       ""))
          (expat '(()) pattern)))


I am new to Emacs Lisp and would like to know, if this is a reasonable way to solve the problem in Elisp or if it can be optimized, to simplify the code.

Solution

Scoping

In Emacs Lisp a nesting a defun inside another defun does not create a lexically scoped function. The symbol for the 'nested' function is still interned at in the current objarray. The behavior is unlike that of Scheme.

In Emacs Lisp let ((f (lambda (x) (body))))...(funcall f some-value) is the general structure for a 'local' function.

Formatting

The formatting of the code within the call to pcase is difficult to read. When the condition and consequent cannot be listed on the same line, it is probably more common to place the consequent below the conditional.

Suggested Alternative Format

(defun expand-pattern (pattern)
  (mapcar (lambda (tokens)
            (mapconcat (lambda (token)
                         (format "%s" token))
                       (reverse tokens)
                       ""))
          (expat '(()) pattern)))

(defun expat (parents expr)
  "Utility function for expand pattern."
  (pcase expr
    ((or (pred stringp)
         (pred numberp))
     (mapcar (lambda (parent)
               (cons expr parent))
             parents))
    (`(and ,first)
     (expat parents first))
    (`(and ,first . ,rest)
     (expat (expat parents first)
            (cons 'and rest)))
    (`(or . ,args)
     (apply #'append 
            (mapcar (lambda (arg)
                      (expat parents arg))
                    args)))
    (_ (error "unknown %S" expr))))


Other Remarks

Because expat throws an error, it might make sense to have expand-pattern invoke expat within a try...catch block. Alternatively, expat could return nil when no match is found. If nil is a possible return value from processing the pattern, then a "Lispy" thing to do is to return two values, the first being the result of processing the expression and the second being either nil or t to indicate an error or no error.

Code Snippets

(defun expand-pattern (pattern)
  (mapcar (lambda (tokens)
            (mapconcat (lambda (token)
                         (format "%s" token))
                       (reverse tokens)
                       ""))
          (expat '(()) pattern)))

(defun expat (parents expr)
  "Utility function for expand pattern."
  (pcase expr
    ((or (pred stringp)
         (pred numberp))
     (mapcar (lambda (parent)
               (cons expr parent))
             parents))
    (`(and ,first)
     (expat parents first))
    (`(and ,first . ,rest)
     (expat (expat parents first)
            (cons 'and rest)))
    (`(or . ,args)
     (apply #'append 
            (mapcar (lambda (arg)
                      (expat parents arg))
                    args)))
    (_ (error "unknown %S" expr))))

Context

StackExchange Code Review Q#148421, answer score: 6

Revisions (0)

No revisions yet.