1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-18 09:40:25 +02:00

Support => within case, and improve error messages for cond and case

* module/ice-9/boot-9.scm (cond, case): Reimplement using syntax-case,
  with improved error messages and support for '=>' within 'case' as
  mandated by the R7RS.  Add warnings for duplicate case datums and
  case datums that cannot be meaningfully compared using 'eqv?'.

* module/system/base/message.scm (%warning-types): Add 'bad-case-datum'
  and 'duplicate-case-datum' warning types.

* test-suite/tests/syntax.test (cond, case): Update tests to reflect
  improved error reporting.  Add tests for '=>' within 'case'.

* test-suite/tests/tree-il.test (partial evaluation): Update tests to
  reflect changes in how 'case' is expanded.

* doc/ref/api-control.texi (Conditionals): Document '=>' within 'case'.
This commit is contained in:
Mark H Weaver 2012-02-07 19:40:29 -05:00
parent b131b233ff
commit e7cf0457d7
5 changed files with 234 additions and 84 deletions

View file

@ -212,18 +212,30 @@ result of the @code{cond}-expression.
@end deffn @end deffn
@deffn syntax case key clause1 clause2 @dots{} @deffn syntax case key clause1 clause2 @dots{}
@var{key} may be any expression, the @var{clause}s must have the form @var{key} may be any expression, and the @var{clause}s must have the form
@lisp @lisp
((@var{datum1} @dots{}) @var{expr1} @var{expr2} @dots{}) ((@var{datum1} @dots{}) @var{expr1} @var{expr2} @dots{})
@end lisp @end lisp
or
@lisp
((@var{datum1} @dots{}) => @var{expression})
@end lisp
and the last @var{clause} may have the form and the last @var{clause} may have the form
@lisp @lisp
(else @var{expr1} @var{expr2} @dots{}) (else @var{expr1} @var{expr2} @dots{})
@end lisp @end lisp
or
@lisp
(else => @var{expression})
@end lisp
All @var{datum}s must be distinct. First, @var{key} is evaluated. The All @var{datum}s must be distinct. First, @var{key} is evaluated. The
result of this evaluation is compared against all @var{datum} values using result of this evaluation is compared against all @var{datum} values using
@code{eqv?}. When this comparison succeeds, the expression(s) following @code{eqv?}. When this comparison succeeds, the expression(s) following
@ -234,6 +246,11 @@ If the @var{key} matches no @var{datum} and there is an
@code{else}-clause, the expressions following the @code{else} are @code{else}-clause, the expressions following the @code{else} are
evaluated. If there is no such clause, the result of the expression is evaluated. If there is no such clause, the result of the expression is
unspecified. unspecified.
For the @code{=>} clause types, @var{expression} is evaluated and the
resulting procedure is applied to the value of @var{key}. The result of
this procedure application is then the result of the
@code{case}-expression.
@end deffn @end deffn

View file

@ -411,70 +411,150 @@ If there is no handler at all, Guile prints an error and then exits."
((_ x) x) ((_ x) x)
((_ x y ...) (let ((t x)) (if t t (or y ...)))))) ((_ x y ...) (let ((t x)) (if t t (or y ...))))))
(include-from-path "ice-9/quasisyntax")
(define-syntax-rule (when test stmt stmt* ...) (define-syntax-rule (when test stmt stmt* ...)
(if test (begin stmt stmt* ...))) (if test (begin stmt stmt* ...)))
(define-syntax-rule (unless test stmt stmt* ...) (define-syntax-rule (unless test stmt stmt* ...)
(if (not test) (begin stmt stmt* ...))) (if (not test) (begin stmt stmt* ...)))
;; The "maybe-more" bits are something of a hack, so that we can support
;; SRFI-61. Rewrites into a standalone syntax-case macro would be
;; appreciated.
(define-syntax cond (define-syntax cond
(syntax-rules (=> else) (lambda (whole-expr)
((_ "maybe-more" test consequent) (define (fold f seed xs)
(if test consequent)) (let loop ((xs xs) (seed seed))
(if (null? xs) seed
((_ "maybe-more" test consequent clause ...) (loop (cdr xs) (f (car xs) seed)))))
(if test consequent (cond clause ...))) (define (reverse-map f xs)
(fold (lambda (x seed) (cons (f x) seed))
((_ (else else1 else2 ...)) '() xs))
(begin else1 else2 ...)) (syntax-case whole-expr ()
((_ clause clauses ...)
((_ (test => receiver) more-clause ...) #`(begin
(let ((t test)) #,@(fold (lambda (clause-builder tail)
(cond "maybe-more" t (receiver t) more-clause ...))) (clause-builder tail))
#'()
((_ (generator guard => receiver) more-clause ...) (reverse-map
(call-with-values (lambda () generator) (lambda (clause)
(lambda t (define* (bad-clause #:optional (msg "invalid clause"))
(cond "maybe-more" (syntax-violation 'cond msg whole-expr clause))
(apply guard t) (apply receiver t) more-clause ...)))) (syntax-case clause (=> else)
((else e e* ...)
((_ (test => receiver ...) more-clause ...) (lambda (tail)
(syntax-violation 'cond "wrong number of receiver expressions" (if (null? tail)
'(test => receiver ...))) #'((begin e e* ...))
((_ (generator guard => receiver ...) more-clause ...) (bad-clause "else must be the last clause"))))
(syntax-violation 'cond "wrong number of receiver expressions" ((else . _) (bad-clause))
'(generator guard => receiver ...))) ((test => receiver)
(lambda (tail)
((_ (test) more-clause ...) #`((let ((t test))
(let ((t test)) (if t
(cond "maybe-more" t t more-clause ...))) (receiver t)
#,@tail)))))
((_ (test body1 body2 ...) more-clause ...) ((test => receiver ...)
(cond "maybe-more" (bad-clause "wrong number of receiver expressions"))
test (begin body1 body2 ...) more-clause ...)))) ((generator guard => receiver)
(lambda (tail)
#`((call-with-values (lambda () generator)
(lambda vals
(if (apply guard vals)
(apply receiver vals)
#,@tail))))))
((generator guard => receiver ...)
(bad-clause "wrong number of receiver expressions"))
((test)
(lambda (tail)
#`((let ((t test))
(if t t #,@tail)))))
((test e e* ...)
(lambda (tail)
#`((if test
(begin e e* ...)
#,@tail))))
(_ (bad-clause))))
#'(clause clauses ...))))))))
(define-syntax case (define-syntax case
(syntax-rules (else) (lambda (whole-expr)
((case (key ...) (define (fold f seed xs)
clauses ...) (let loop ((xs xs) (seed seed))
(let ((atom-key (key ...))) (if (null? xs) seed
(case atom-key clauses ...))) (loop (cdr xs) (f (car xs) seed)))))
((case key (define (fold2 f a b xs)
(else result1 result2 ...)) (let loop ((xs xs) (a a) (b b))
(begin result1 result2 ...)) (if (null? xs) (values a b)
((case key (call-with-values
((atoms ...) result1 result2 ...)) (lambda () (f (car xs) a b))
(if (memv key '(atoms ...)) (lambda (a b)
(begin result1 result2 ...))) (loop (cdr xs) a b))))))
((case key (define (reverse-map-with-seed f seed xs)
((atoms ...) result1 result2 ...) (fold2 (lambda (x ys seed)
clause clauses ...) (call-with-values
(if (memv key '(atoms ...)) (lambda () (f x seed))
(begin result1 result2 ...) (lambda (y seed)
(case key clause clauses ...))))) (values (cons y ys) seed))))
'() seed xs))
(syntax-case whole-expr ()
((_ expr clause clauses ...)
(with-syntax ((key #'key))
#`(let ((key expr))
#,@(fold
(lambda (clause-builder tail)
(clause-builder tail))
#'()
(reverse-map-with-seed
(lambda (clause seen)
(define* (bad-clause #:optional (msg "invalid clause"))
(syntax-violation 'case msg whole-expr clause))
(syntax-case clause ()
((test . rest)
(with-syntax
((clause-expr
(syntax-case #'rest (=>)
((=> receiver) #'(receiver key))
((=> receiver ...)
(bad-clause
"wrong number of receiver expressions"))
((e e* ...) #'(begin e e* ...))
(_ (bad-clause)))))
(syntax-case #'test (else)
((datums ...)
(let ((seen
(fold
(lambda (datum seen)
(define (warn-datum type)
((@ (system base message)
warning)
type
(append (source-properties datum)
(source-properties
(syntax->datum #'test)))
datum
(syntax->datum clause)
(syntax->datum whole-expr)))
(if (memv datum seen)
(warn-datum 'duplicate-case-datum))
(if (or (pair? datum)
(array? datum)
(generalized-vector? datum))
(warn-datum 'bad-case-datum))
(cons datum seen))
seen
(map syntax->datum #'(datums ...)))))
(values (lambda (tail)
#`((if (memv key '(datums ...))
clause-expr
#,@tail)))
seen)))
(else (values (lambda (tail)
(if (null? tail)
#'(clause-expr)
(bad-clause
"else must be the last clause")))
seen))
(_ (bad-clause)))))
(_ (bad-clause))))
'() #'(clause clauses ...)))))))))
(define-syntax do (define-syntax do
(syntax-rules () (syntax-rules ()
@ -502,8 +582,6 @@ If there is no handler at all, Guile prints an error and then exits."
(define-syntax-rule (delay exp) (define-syntax-rule (delay exp)
(make-promise (lambda () exp))) (make-promise (lambda () exp)))
(include-from-path "ice-9/quasisyntax")
(define-syntax current-source-location (define-syntax current-source-location
(lambda (x) (lambda (x)
(syntax-case x () (syntax-case x ()

View file

@ -126,6 +126,20 @@
"~A: warning: possibly wrong number of arguments to `~A'~%" "~A: warning: possibly wrong number of arguments to `~A'~%"
loc name)))) loc name))))
(duplicate-case-datum
"report a duplicate datum in a case expression"
,(lambda (port loc datum clause case-expr)
(emit port
"~A: warning: duplicate datum ~S in clause ~S of case expression ~S~%"
loc datum clause case-expr)))
(bad-case-datum
"report a case datum that cannot be meaningfully compared using `eqv?'"
,(lambda (port loc datum clause case-expr)
(emit port
"~A: warning: datum ~S cannot be meaningfully compared using `eqv?' in clause ~S of case expression ~S~%"
loc datum clause case-expr)))
(format (format
"report wrong number of arguments to `format'" "report wrong number of arguments to `format'"
,(lambda (port loc . rest) ,(lambda (port loc . rest)

View file

@ -648,11 +648,13 @@
(pass-if-syntax-error "missing recipient" (pass-if-syntax-error "missing recipient"
'(cond . "wrong number of receiver expressions") '(cond . "wrong number of receiver expressions")
(cond (#t identity =>))) (eval '(cond (#t identity =>))
(interaction-environment)))
(pass-if-syntax-error "extra recipient" (pass-if-syntax-error "extra recipient"
'(cond . "wrong number of receiver expressions") '(cond . "wrong number of receiver expressions")
(cond (#t identity => identity identity)))) (eval '(cond (#t identity => identity identity))
(interaction-environment))))
(with-test-prefix "bad or missing clauses" (with-test-prefix "bad or missing clauses"
@ -662,43 +664,48 @@
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond #t)" (pass-if-syntax-error "(cond #t)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond #t) (eval '(cond #t)
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond 1)" (pass-if-syntax-error "(cond 1)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond 1) (eval '(cond 1)
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond 1 2)" (pass-if-syntax-error "(cond 1 2)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond 1 2) (eval '(cond 1 2)
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond 1 2 3)" (pass-if-syntax-error "(cond 1 2 3)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond 1 2 3) (eval '(cond 1 2 3)
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond 1 2 3 4)" (pass-if-syntax-error "(cond 1 2 3 4)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond 1 2 3 4) (eval '(cond 1 2 3 4)
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond ())" (pass-if-syntax-error "(cond ())"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond ()) (eval '(cond ())
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond () 1)" (pass-if-syntax-error "(cond () 1)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond () 1) (eval '(cond () 1)
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(cond (1) 1)" (pass-if-syntax-error "(cond (1) 1)"
exception:generic-syncase-error '(cond . "invalid clause")
(eval '(cond (1) 1) (eval '(cond (1) 1)
(interaction-environment)))
(pass-if-syntax-error "(cond (else #f) (#t #t))"
'(cond . "else must be the last clause")
(eval '(cond (else #f) (#t #t))
(interaction-environment)))) (interaction-environment))))
(with-test-prefix "wrong number of arguments" (with-test-prefix "wrong number of arguments"
@ -712,10 +719,46 @@
(pass-if "clause with empty labels list" (pass-if "clause with empty labels list"
(case 1 (() #f) (else #t))) (case 1 (() #f) (else #t)))
(with-test-prefix "case handles '=> correctly"
(pass-if "(1 2 3) => list"
(equal? (case 1 ((1 2 3) => list))
'(1)))
(pass-if "else => list"
(equal? (case 6
((1 2 3) 'wrong)
(else => list))
'(6)))
(with-test-prefix "bound '=> is handled correctly"
(pass-if "(1) => 'ok"
(let ((=> 'foo))
(eq? (case 1 ((1) => 'ok)) 'ok)))
(pass-if "else =>"
(let ((=> 'foo))
(eq? (case 1 (else =>)) 'foo)))
(pass-if "else => list"
(let ((=> 'foo))
(eq? (case 1 (else => identity)) identity))))
(pass-if-syntax-error "missing recipient"
'(case . "wrong number of receiver expressions")
(eval '(case 1 ((1) =>))
(interaction-environment)))
(pass-if-syntax-error "extra recipient"
'(case . "wrong number of receiver expressions")
(eval '(case 1 ((1) => identity identity))
(interaction-environment))))
(with-test-prefix "case is hygienic" (with-test-prefix "case is hygienic"
(pass-if-syntax-error "bound 'else is handled correctly" (pass-if-syntax-error "bound 'else is handled correctly"
exception:generic-syncase-error '(case . "invalid clause")
(eval '(let ((else #f)) (case 1 (else #f))) (eval '(let ((else #f)) (case 1 (else #f)))
(interaction-environment)))) (interaction-environment))))
@ -742,22 +785,22 @@
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(case 1 \"foo\")" (pass-if-syntax-error "(case 1 \"foo\")"
exception:generic-syncase-error '(case . "invalid clause")
(eval '(case 1 "foo") (eval '(case 1 "foo")
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(case 1 ())" (pass-if-syntax-error "(case 1 ())"
exception:generic-syncase-error '(case . "invalid clause")
(eval '(case 1 ()) (eval '(case 1 ())
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(case 1 (\"foo\"))" (pass-if-syntax-error "(case 1 (\"foo\"))"
exception:generic-syncase-error '(case . "invalid clause")
(eval '(case 1 ("foo")) (eval '(case 1 ("foo"))
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(case 1 (\"foo\" \"bar\"))" (pass-if-syntax-error "(case 1 (\"foo\" \"bar\"))"
exception:generic-syncase-error '(case . "invalid clause")
(eval '(case 1 ("foo" "bar")) (eval '(case 1 ("foo" "bar"))
(interaction-environment))) (interaction-environment)))
@ -767,7 +810,7 @@
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(case 1 ((2) \"bar\") (else))" (pass-if-syntax-error "(case 1 ((2) \"bar\") (else))"
exception:generic-syncase-error '(case . "invalid clause")
(eval '(case 1 ((2) "bar") (else)) (eval '(case 1 ((2) "bar") (else))
(interaction-environment))) (interaction-environment)))
@ -777,7 +820,7 @@
(interaction-environment))) (interaction-environment)))
(pass-if-syntax-error "(case 1 (else #f) ((1) #t))" (pass-if-syntax-error "(case 1 (else #f) ((1) #t))"
exception:generic-syncase-error '(case . "else must be the last clause")
(eval '(case 1 (else #f) ((1) #t)) (eval '(case 1 (else #f) ((1) #t))
(interaction-environment))))) (interaction-environment)))))

View file

@ -1156,14 +1156,14 @@
(case foo (case foo
((3 2 1) 'a) ((3 2 1) 'a)
(else 'b)) (else 'b))
(if (let (t) (_) ((toplevel foo)) (let (key) (_) ((toplevel foo))
(if (apply (primitive eqv?) (lexical t _) (const 3)) (if (if (apply (primitive eqv?) (lexical key _) (const 3))
(const #t) (const #t)
(if (apply (primitive eqv?) (lexical t _) (const 2)) (if (apply (primitive eqv?) (lexical key _) (const 2))
(const #t) (const #t)
(apply (primitive eqv?) (lexical t _) (const 1))))) (apply (primitive eqv?) (lexical key _) (const 1))))
(const a) (const a)
(const b))) (const b))))
(pass-if-peval (pass-if-peval
;; Memv with non-constant key, empty list, test context. Currently ;; Memv with non-constant key, empty list, test context. Currently
@ -1171,9 +1171,7 @@
(case foo (case foo
(() 'a) (() 'a)
(else 'b)) (else 'b))
(if (begin (toplevel foo) (const #f)) (begin (toplevel foo) (const b)))
(const a)
(const b)))
;; ;;
;; Below are cases where constant propagation should bail out. ;; Below are cases where constant propagation should bail out.