guicho271828 / trivia

Pattern Matcher Compatible with Optima
Other
334 stars 22 forks source link

Destructuring lambda lists don't work #135

Open tfeb opened 2 years ago

tfeb commented 2 years ago

I don't know if the lambda list parser is meant to support these. The source sort of implies it does (compile-destructuring-pattern, for instance), but it doesn't:

(match '(1 (2))
  ((lambda-list a (b))
   (values a b)))

fails. I couldn't find anything in the documentation.

guicho271828 commented 2 years ago

That does not look like a valid d-l-l.

guicho271828 commented 2 years ago

http://www.lispworks.com/documentation/lw60/CLHS/Body/03_de.htm

tfeb commented 2 years ago

It is a valid destructuring-bind lambda list: the syntax productions in the spec for several cases of lambda lists are at best confusing and at worst wrong: you need to look at the normative text, not them.

From 3.4.4.1:

Anywhere in a macro lambda list where a parameter name can appear, and where ordinary lambda list syntax (as described in Section 3.4.1 (Ordinary Lambda Lists)) does not otherwise allow a list, a destructuring lambda list can appear in place of the parameter name. When this is done, then the argument that would match the parameter is treated as a (possibly dotted) list, to be used as an argument list for satisfying the parameters in the embedded lambda list. This is known as destructuring.

And from 3.4.5:

Destructuring lambda lists are closely related to macro lambda lists; see Section 3.4.4 (Macro Lambda Lists). A destructuring lambda list can contain all of the lambda list keywords listed for macro lambda lists except for &environment, and supports destructuring in the same way. Inner lambda lists nested within a macro lambda list have the syntax of destructuring lambda lists.

(My emphasis).

This means, in effect, that anywhere a required variable is expected you can have a complete recursive lambda list (excepting &environment I assume as that makes no sense there).

You could also just try destructuring-bind:

(destructuring-bind (a (b)) '(1 (2))
  (print a)
  (print b))
guicho271828 commented 2 years ago

Hmmm

tfeb commented 2 years ago

If you are unfamiliar with destructuring-bind it is also worth reading the issue that originally introduced it as well as this one.

guicho271828 commented 2 years ago

I keep it open. I welcome pull requests

mathrick commented 2 years ago

@tfeb: I think the fundamental issue here is that LAMBDA-LIST only looks at a single level of expansion, and any subpatterns are treated as Trivia patterns, rather than destructuring lambda sub-list, as would be expected. I defined my own pattern to make LAMBDA-LIST's syntax less annoying while still allowing an escape hatch to use arbitrary Trivia patterns:

;; This is a pattern that expands to lambda-list. Sub-patterns will also be expanded to
;; lambda-list, UNLESS they start with &, in which case they will be regular Trivia
;; patterns. This essentially makes the & pattern a toggle to go in and out of lambda-list
;; syntax
(defpattern & (&rest args)
    ,@(loop for arg in args
            collect
            (match arg
              ((or nil
                   (list 'quote _)
                   (not (satisfies listp)))
               arg)
              ((list* '& rest) rest)
              (_ `(& ,@arg)))))

Example usage

(match '(defun foo 13)
  ((& 'defun name (& or ; <-- escape to Trivia OR here
                     (list x y)
                     (and x (< 42)))) 
   (values name x y)))

(match '(foo (bar baz) :quux 42)
  ((& name (var default) &key quux)
   (values name default quux)))

I don't know whether this is the right way of implementing patterns (there don't seem to be any docs on that, so I was just doing whatever seemed most obvious), but it seems to work.