diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2021-02-04 01:43:20 -0800 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2021-02-04 01:43:20 -0800 |
commit | 8132c60cdb613e27eb2b8e58b8d1ea9ae01a615c (patch) | |
tree | 62d32a6245964cb20ed4febfa743afd3862c4b3e | |
parent | 8bc0ac3a54d248ba2d4a8a045dc2bc0619a60886 (diff) | |
download | txr-8132c60cdb613e27eb2b8e58b8d1ea9ae01a615c.tar.gz txr-8132c60cdb613e27eb2b8e58b8d1ea9ae01a615c.tar.bz2 txr-8132c60cdb613e27eb2b8e58b8d1ea9ae01a615c.zip |
matcher: lambda-match: redoc, bugfix, test-cases
* share/txr/stdlib/match.tl (expand-lambda-match): In a case
that takes the maximum number of fixed args and no dotted
pattern, in a function that is variadic, we must assert that
the rest parameter is nil: there are no additional arguments.
In the lambda args, we must generate the colon that separates
the optional arguments.
* tests/011/patmatch.tl: basic test cases for lambda-match
and defun-match.
* txr.1: lambda-match and defun-match redocumented, with
examples.
-rw-r--r-- | share/txr/stdlib/match.tl | 7 | ||||
-rw-r--r-- | tests/011/patmatch.tl | 50 | ||||
-rw-r--r-- | txr.1 | 185 |
3 files changed, 205 insertions, 37 deletions
diff --git a/share/txr/stdlib/match.tl b/share/txr/stdlib/match.tl index e2e1ac4a..e2a55256 100644 --- a/share/txr/stdlib/match.tl +++ b/share/txr/stdlib/match.tl @@ -656,11 +656,16 @@ (when (< pc.nfixed max-args) (set exp ^(unless ,[present-vec pc.nfixed] ,exp))) + (when (and variadic (not vp) (= pc.nfixed max-args)) + (set exp ^(unless ,rest-temp + ,exp))) (unless (zerop counter) (set exp ^(unless ,result-temp ,exp))) exp)))) ^(lambda (,*fix-arg-temps - ,*(mapcar (ret ^(,@1 nil ,@2)) opt-arg-temps present-p-temps) + ,*(if opt-arg-temps + (cons : (mapcar (ret ^(,@1 nil ,@2)) + opt-arg-temps present-p-temps))) . ,rest-temp) (let (,matched-p-temp ,result-temp) ,*ex-clauses diff --git a/tests/011/patmatch.tl b/tests/011/patmatch.tl index 46fc2719..5dd735b9 100644 --- a/tests/011/patmatch.tl +++ b/tests/011/patmatch.tl @@ -186,3 +186,53 @@ (test (let ((h #H(() (a 1) (b 2)))) (when-match @[h x @(oddp y)] 'a (list x y))) (a 1)) + +(test + (let ((f (lambda-match + (() (list 0 :args)) + ((@a) (list 1 :arg a)) + ((@a @b) (list 2 :args a b)) + ((@a @b . @c) (list* '> 2 :args a b c))))) + (list [f] [f 1] [f 1 2] [f 1 2 3])) + ((0 :args) (1 :arg 1) (2 :args 1 2) (> 2 :args 1 2 3))) + +(test + [(lambda-match + ((0 1) :zero-one) + ((1 0) :one-zero) + ((@x @y) :no-match)) 1 0] + :one-zero) + +(test + [(lambda-match + ((0 1) :zero-one) + ((1 0) :one-zero) + ((@x @y) :no-match)) 1 1] + :no-match) + +(test + [(lambda-match + ((0 1) :zero-one) + ((1 0) :one-zero) + ((@x @y) :no-match)) 1 2 3] + :error) + +(defun-match fib + ((0) 1) + ((1) 1) + ((@x) (+ (fib (pred x)) (fib (ppred x))))) + +(test (fib 0) 1) +(test (fib 1) 1) +(test (fib 2) 2) +(test (fib 3) 3) +(test (fib 4) 5) +(test (fib 5) 8) + +(defun-match ack + ((0 @n) (+ n 1)) + ((@m 0) (ack (- m 1) 1)) + ((@m @n) (ack (- m 1) (ack m (- n 1))))) + +(test (ack 1 1) 3) +(test (ack 2 2) 7) @@ -40828,44 +40828,105 @@ if there are no forms. .desc The .code lambda-match -is closely based on +is conceptually similar to .codn match-case . The arguments of .code lambda-match are zero or more clauses similar to those of -.codn match-case . +.codn match-case , +each consisting of a compound expression headed by a +.meta pattern +followed by zero or more +.metn form -s. -An invocation of -.code lambda-match -evaluates to an anonymous function which can take any number of arguments. +The macro generates a +.code lambda +expression which evaluates to an anonymous function +in the usual way. -Whenever the function is called, its arguments are treated as a list object -which is subject to pattern matching via the clauses. The -.mets pattern -from every clause is tested against the argument list. If it matches -each associated +When the anonymous function is called, each clause's +.meta pattern +is matched against the the function's actual arguments. When a +match occurs, each .meta form -is evaluated in the scope of the variables emanating from that +associated with the .meta pattern -and the value of the last +is evaluated, and the value of the last .meta form -is returned. If no +becomes the return value of the function. +If none of the clauses match, then +.code nil +is returned. + +Whenever .meta pattern -matches, the function returns -.codn nil . +is a list-like pattern, it is not matched against a list object, as is the +usual case with a list-like pattern, but against the actual arguments. +For instance, the pattern +.code "(@a @b @c)" +expects that the function was called with exactly three arguments. If +that is the case, the patterns are then matched to the arguments. The pattern +.code @a +takes the first argument, binding it to variable +.code a +and so forth. -Note: the -.code lambda-macro -can be understood in terms of the following equivalence: +If +.meta pattern +is a dotted list-like pattern, then the dot position is matched +against the remaining arguments. For instance, the pattern +.code "(@a @b . @c)" +requires at least two arguments. The first two are bound to +.code a +and +.codn b , +respectively. The list of remaining arguments, if any, is bound to +.codn c , +which will be +.code nil +if there are no remaining arguments. + +Any non-list-like +.meta pattern +.code P +is analyzed as an equivalent list-like dotted pattern due to +.code P +syntax being equivalent to +.code "(. P)" +syntax. Such a pattern matches the list of all arguments. +Thus, the following are all equivalent: .verb - (lambda-match ...) <--> (lambda (. rest) (match-case rest ...)) + (lambda-match (@a a)) + (lambda-match ((. @a) a)) + (lambda a a) + (lambda (. a) a) .brev -Here, the -.code rest -variable is to be understood as a generated, unique symbol. +The characteristics of the resulting anonymous function are determined as +follows. + +If at least one +.meta pattern +specified in a +.meta lambda-match +is a dotted pattern, the function is variadic. + +The arity of the resulting anonymous function is determined as follows, from +the lengths of the patterns. The length of a pattern is the number of +elements, not including the dotted element. + +The length of the longest pattern determines the number of fixed +arguments. Unless the function is variadic, it may not be called with more +arguments than can be matched by the longest pattern. + +The length of the shortest pattern determines the number of required arguments. +The function may not be called with fewer arguments than can be matched +by the shortest pattern. + +If these two lengths are unequal, then the function has a number of optional +arguments, equal to the difference. Note: an anonymous function which takes one argument and matches that object against clauses using @@ -40875,6 +40936,34 @@ can be obtained with the operator, using the pattern: .codn "(do match @1 ...)" . +.TP* Examples: + +.verb + (let ((f (lambda-match + (() (list 0 :args)) + ((@a) (list 1 :arg a)) + ((@a @b) (list 2 :args a b)) + ((@a @b . @c) (list* '> 2 :args a b c))))) + (list [f] [f 1] [f 1 2] [f 1 2 3])) + --> + ((0 :args) (1 :arg 1) (2 :args 1 2) (> 2 :args 1 2 3)) + + [(lambda-match + ((0 1) :zero-one) + ((1 0) :one-zero) + ((@x @y) :no-match)) 1 0] --> :one-zero + + [(lambda-match + ((0 1) :zero-one) + ((1 0) :one-zero) + ((@x @y) :no-match)) 1 1] --> :no-match + + [(lambda-match + ((0 1) :zero-one) + ((1 0) :one-zero) + ((@x @y) :no-match)) 1 2 3] --> ;; error +.brev + .coNP Macro @ defun-match .synb .mets (defun-match < name >> {( pattern << form *)}*) @@ -40885,21 +40974,9 @@ The macro can be used to define a top-level function in the style of .codn lambda-match . -It works according to the following equivalence: - -.verb - (defun-match name ...) <--> (defun name (. rest) (match-case rest ...)) -.brev - -Here, the -.code rest -variable is to be understood as a generated, unique symbol. - -Because -.code defun-match -is defined in terms of +It produces a form which has all of the properties of .codn defun , -it has all of the properties, such as a block of the same +such as a block of the same .meta name being established around the implicit .code match-case @@ -40907,6 +40984,42 @@ so that .code return-from is possible. +The +.mono +.meti >> ( pattern << form *) +.onom +clauses of +.code defun-match +have exactly the same syntax and semantics as those of +.codn lambda-match . + +.TP* Examples: + +.verb + ;; Fibonacci + (defun-match fib + ((0) 1) + ((1) 1) + ((@x) (+ (fib (pred x)) (fib (ppred x))))) + + (fib 0) -> 1 + (fib 1) -> 1 + (fib 2) -> 2 + (fib 3) -> 3 + (fib 4) -> 5 + (fib 5) -> 8 + + ;; Ackermann + (defun-match ack + ((0 @n) (+ n 1)) + ((@m 0) (ack (- m 1) 1)) + ((@m @n) (ack (- m 1) (ack m (- n 1))))) + + (ack 3 7) -> 1021 + (ack 1 1) -> 3 + (ack 2 2) -> 7 +.brev + .SS* Quasiquote Operator Syntax .coNP Macro @ qquote .synb |