Subject: Re: bug#41956: [PATCH] ice-9: exceptions: Properly format the error message.

From: Bengt Richter <[email-masked]>
Date: Thu, 25 Jun 2020 18:33:41 +0200

Hi Ricardo et al,

On +2020-06-25 12:04:27 +0200, Ricardo Wurmus wrote:
> 
> Hi Maxim,
> 
> here’s what I did in the REPL:
> 
> --8<---------------cut here---------------start------------->8---
> scheme@(guile-user)> ,m (ice-9 exceptions)
> scheme@(ice-9 exceptions)> (define (my/guile-system-error-converter key args)
>   (apply (case-lambda
>           ((subr msg-args msg errno . rest)
>            ;; XXX TODO we should return a more specific error
>            ;; (usually an I/O error) as expected by R6RS programs.
>            ;; Unfortunately this often requires the 'filename' (or
>            ;; other?) which is not currently provided by the native
>            ;; Guile exceptions.
> 		   (make-exception
> 			(make-external-error)
>             (make-exception-with-origin subr)
>             (apply make-exception-with-message msg)
>             (make-exception-with-irritants msg-args)))
>           (_ (guile-external-error-converter key args)))
>          args))
> scheme@(ice-9 exceptions)> (set! guile-exception-converters (acons 'system-error my/guile-system-error-converter guile-exception-converters))
> scheme@(ice-9 exceptions)> ,m (guile-user)
> scheme@(guile-user)> (guard (c ((message-condition? c)
> 		  (format #t "message: ~a~%" (condition-message c))))
> 		  (canonicalize-path "/doesntexist"))
> message: No such file or directory
> $11 = #t
> scheme@(guile-user)> 
> --8<---------------cut here---------------end--------------->8---
> 
> -- 
> Ricardo

What do you think of using (ice-9 match) to make a universal throwage-formatter,
with the idea of making readable top level code for how exceptions become messages on screen?

I started a hack to explore the (throw 'whatever any ...) space, beginning like

--8<---------------cut here---------------start------------->8---
(use-modules (ice-9 match))
(define (make-exception-message key rest)
  (begin
    (let*((l (cons key rest)))
      (match l
	     (('system-error subr message args data ...)
	      ;; e.g. thrown with key 'system-error: ("open-fdes" "~A" ("No such file or directory") (2))
	      (format #f (string-append "match-1: subr ~s threw '~s " message " sterror: ~s") subr key args (strerror (car (car data)))))

	     (('signal   any ...)
              ;; not yet implemented
	      (format #f "match-2: <NYI:unix signal no> any: ~s" any))
	     
	     (('keyword-argument-error subr message args data)
	      ;; with-crossed-fingers...
	      (format #f (string-append "match-3: subr ~s threw '~s " message) subr key args))
;; FIXME: string-append formats NAGI not a good idea, see a fix example below
	     		      
	     (('wrong-type-arg subr message (args ...) (data ...))
	      ;; E.g.,  thrown with key 'wrong-type-arg: ("sqrt" "Wrong type argument in position ~A: ~S" (1 x) (x))
	      (format #f "match-4: subr ~s threw '~s: ~s" subr key (format #f message args data)))
	     
	     (('out-of-range subr message (lo hi bad1) ((bad2)))
              ;; E.g., thrown with key 'out-of-range: (#f "Value out of range ~S to ~S: ~S" (0 3 4) (4))
	      (format #f "match-5: (internal) threw '~s: ~s" 'out-of-range (format #f message lo hi bad2)))

	     (('unbound-variable #f message args data)
	      ;; E.g. thrown with key 'unbound-variable: (#f "Unbound variable: ~S" (foo) #f)
	      (format #f (string-append "match-6: subr ~s threw '~s " message) #f key args)) ;; data))
;; FIXME: string-append formats NAGI
[...]
--8<---------------cut here---------------end--------------->8---

I made a guile hack that I could call from bash so I could type a line and (eval-string it) as a source of exceptions,
and found that I could get secondary exceptions from make-exception-message, so I wrapped that with a (catch ...)
something like

--8<---------------cut here---------------start------------->8---
(define verbose-exception-handler (lambda (k . rest )
                      (begin
			(format #t "thrown with key '~s: ~s\n" k rest)
			(format #t "catch return=~a\n" (catch #t
					(lambda () (make-exception-message  k  rest))
					(lambda (inner-key . inner-rest)
					  (format #t "caught by inner handler: thrown with key '~s: ~s\n" inner-key inner-rest))))
			;; (format #t "thrown with key '~s: ~s\n" k rest)
			(newline)
;;;; [...]
--8<---------------cut here---------------end--------------->8---

And using that like

--8<---------------cut here---------------start------------->8---
(define (wrap-main args)
  (begin
    (display (catch #t
                    (lambda () (apply main-defaults args))
		    verbose-exception-handler
                    ))))
--8<---------------cut here---------------end--------------->8---
(main-defaults sets up missing args and calls main with fixed args plus opt rest)

I'm wondering if some of the obscure error messages I've encountered
might be secondary, and could be clarified by wrapping the primary
exception formatting similarly, (adding apropriate hint).

WDYT? And is (ice-9 match) too massive to use for memory-limited platforms?

BTW, does guile have a Content-type X-...-like standard for naming throw keys?,
e.g. 'X-longjump-result-delivery
if someone should want to use throw for non-"standard (?? :)" purposes?

--
Regards, Bengt Richter