summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2021-02-04 01:43:20 -0800
committerKaz Kylheku <kaz@kylheku.com>2021-02-04 01:43:20 -0800
commit8132c60cdb613e27eb2b8e58b8d1ea9ae01a615c (patch)
tree62d32a6245964cb20ed4febfa743afd3862c4b3e
parent8bc0ac3a54d248ba2d4a8a045dc2bc0619a60886 (diff)
downloadtxr-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.tl7
-rw-r--r--tests/011/patmatch.tl50
-rw-r--r--txr.1185
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)
diff --git a/txr.1 b/txr.1
index 85711d4c..1b9a672a 100644
--- a/txr.1
+++ b/txr.1
@@ -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