Expand string patterns in Elisp 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.
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.