unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* scm_c_catch question
@ 2014-08-15 22:13 Ian Grant
  2014-08-16 13:24 ` Neil Jerram
  2014-08-18  1:14 ` Ian Grant
  0 siblings, 2 replies; 6+ messages in thread
From: Ian Grant @ 2014-08-15 22:13 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 1929 bytes --]

Hello Guile types,

I have been experimenting with using libguile from within another byte-code
interpreter: Moscow ML. I have a version of Moscow ML with an GNU lightning
interface in which I JIT compile primitives to give access to libguile
functions from Standard ML.

Moscow ML uses an old version of the CAML light runtime which is a
byte-code interpreter implemented in C. The CAML runtime provides
exceptions implemented using a longjmp buffer.

The Moscow ML top-level REPL is implemented as a byte-code compiled ML
function which is invoked by main(). What I would like to do would be to
catch any unhandled Guile exceptions and re-throw them as ML exceptions so
that the toplevel isn't exit'ed by an un-handled scheme exception. To this
end I call the CAML main from the scm_boot_guile callback, under a
scm_c_catch. This code is in the guilert.c file
https://github.com/IanANGrant/red-october/blob/master/src/runtime/guilert.c
The only CAML'ism here is the call to failwith("message"). This does a
longjump 'into the CAML exception bucket'

The problem is that after the first successful catch by the pre-unwind
handler, the next Guile exception is handled by the main_handler. This is
not what I expected. The manual seems to say that it is only after the
main_handler is invoked, that the catch is cancelled. Is this not the right
understanding? The output of the example shows this isn't what happens:

debug: main_trampoline: calling
scm_c_catch(main_call,main_handler,main_pre_unwind_handler)
debug: main_call: calling caml_main
Moscow ML [Red October] 2.10
Type `quit();' to quit.
- scm_repl();
Moscow ML Guile REPL
Type `(quit)' to exit.
> (+ "ab")
debug: main_pre_unwind_handler called
! Uncaught exception:
! Fail  "Uncaught scheme exception"
- scm_repl();
Moscow ML Guile REPL
Type `(quit)' to exit.
> (+ "ab")
debug: main_handler called
debug: main_trampoline: scm_c_catch returned!

Process mosml finished

[-- Attachment #2: Type: text/html, Size: 2271 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: scm_c_catch question
  2014-08-15 22:13 scm_c_catch question Ian Grant
@ 2014-08-16 13:24 ` Neil Jerram
  2014-08-18  1:14 ` Ian Grant
  1 sibling, 0 replies; 6+ messages in thread
From: Neil Jerram @ 2014-08-16 13:24 UTC (permalink / raw)
  To: guile-devel

On 2014-08-16 00:13, Ian Grant wrote:
> Hello Guile types,

Hi Ian,

> I have been experimenting with using libguile from within another
> byte-code interpreter: Moscow ML. I have a version of Moscow ML with
> an GNU lightning interface in which I JIT compile primitives to give
> access to libguile functions from Standard ML.
> 
> Moscow ML uses an old version of the CAML light runtime which is a
> byte-code interpreter implemented in C. The CAML runtime provides
> exceptions implemented using a longjmp buffer.
> 
> The Moscow ML top-level REPL is implemented as a byte-code compiled ML
> function which is invoked by main(). What I would like to do would be
> to catch any unhandled Guile exceptions and re-throw them as ML
> exceptions so that the toplevel isn't exit'ed by an un-handled scheme
> exception. To this end I call the CAML main from the scm_boot_guile
> callback, under a scm_c_catch. This code is in the guilert.c file
> https://github.com/IanANGrant/red-october/blob/master/src/runtime/guilert.c
> [1] The only CAML'ism here is the call to failwith("message"). This
> does a longjump 'into the CAML exception bucket'
> 
> The problem is that after the first successful catch by the pre-unwind
> handler, the next Guile exception is handled by the main_handler. This
> is not what I expected. The manual seems to say that it is only after
> the main_handler is invoked, that the catch is cancelled. Is this not
> the right understanding?

It was a while ago, but it was me who first implemented that pre-unwind 
handler.  So maybe I can help here.

No, I don't think that is the right understanding.  The manual (at least 
for 2.0.11) says:

      A PRE-UNWIND-HANDLER can exit either normally or non-locally.  If
      it exits normally, Guile unwinds the stack and dynamic context and
      then calls the normal (third argument) handler.  If it exits
      non-locally, that exit determines the continuation.

In either case, I believe the intention is for any exception to 
terminate the catch.  In the case where the pre-unwind handler exits 
normally, the catch is terminated after the main handler has been 
called.  In the case where the pre-unwind handler exits non-locally, I 
think it's assumed that the new context is somewhere outside of the 
catch, and hence that the catch is no longer in effect.

In either case, therefore, handling any subsequent exception should 
require establishing a new catch.

In your code, the pre-unwind handler calls failwith() and doesn't expect 
it to return - so where does it jump to?

Regards,
      Neil




^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: scm_c_catch question
  2014-08-15 22:13 scm_c_catch question Ian Grant
  2014-08-16 13:24 ` Neil Jerram
@ 2014-08-18  1:14 ` Ian Grant
  2014-08-20 18:02   ` Neil Jerram
  1 sibling, 1 reply; 6+ messages in thread
From: Ian Grant @ 2014-08-18  1:14 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 3635 bytes --]

Hi Neil,

Sorry, I am replying to my message because I'm not on guile-devel, it
seems. I'll join later.

Thanks for clarifying that. The misleading statement in the manual is just
above the paragraph you quote: on
https://www.gnu.org/software/guile/manual/html_node/Catch.html#Catch it says

    Handler is invoked outside the scope of its own catch. If handler
    again throws to the same key, a new handler from further up the
    call chain is invoked.

It doesn't mention pre_unwind_handler, which implies that the statement
does _not_ apply to pre_unwind_handler. So this should be amended.

But it would be more useful if there were a way to allow a throw to be
aborted as I was expecting. How hard would that be to implement? If you
think the semantics are in fact different, it could be given a new name,
scm_c_catch_and_rethrow or something. What I want is something like BSD
signals semantics, where the handler is not reset when the signal occurs.

You ask "In your code, the pre-unwind handler calls failwith() and doesn't
expect it to return - so where does it jump to?"

I thought I had explained that, sorry. The call to failwith() does a C
longjmp back into the CAML bytecode interpreter (i.e. back down below the
call stack into caml_main). This is not a non-local exit, as far as Guile
is concerned, because it is just a 'naked' C longjmp. So in that sense, the
second time Guile throws an exception it is being thrown from within
pre_unwind_handler.

Is there a way to implement what I want with fluids?

Ian



On Fri, Aug 15, 2014 at 6:13 PM, Ian Grant <ian.a.n.grant@googlemail.com>
wrote:

> Hello Guile types,
>
> I have been experimenting with using libguile from within another
> byte-code interpreter: Moscow ML. I have a version of Moscow ML with an GNU
> lightning interface in which I JIT compile primitives to give access to
> libguile functions from Standard ML.
>
> Moscow ML uses an old version of the CAML light runtime which is a
> byte-code interpreter implemented in C. The CAML runtime provides
> exceptions implemented using a longjmp buffer.
>
> The Moscow ML top-level REPL is implemented as a byte-code compiled ML
> function which is invoked by main(). What I would like to do would be to
> catch any unhandled Guile exceptions and re-throw them as ML exceptions so
> that the toplevel isn't exit'ed by an un-handled scheme exception. To this
> end I call the CAML main from the scm_boot_guile callback, under a
> scm_c_catch. This code is in the guilert.c file
> https://github.com/IanANGrant/red-october/blob/master/src/runtime/guilert.c
> The only CAML'ism here is the call to failwith("message"). This does a
> longjump 'into the CAML exception bucket'
>
> The problem is that after the first successful catch by the pre-unwind
> handler, the next Guile exception is handled by the main_handler. This is
> not what I expected. The manual seems to say that it is only after the
> main_handler is invoked, that the catch is cancelled. Is this not the right
> understanding? The output of the example shows this isn't what happens:
>
> debug: main_trampoline: calling
> scm_c_catch(main_call,main_handler,main_pre_unwind_handler)
> debug: main_call: calling caml_main
> Moscow ML [Red October] 2.10
> Type `quit();' to quit.
> - scm_repl();
> Moscow ML Guile REPL
> Type `(quit)' to exit.
> > (+ "ab")
> debug: main_pre_unwind_handler called
> ! Uncaught exception:
> ! Fail  "Uncaught scheme exception"
> - scm_repl();
> Moscow ML Guile REPL
> Type `(quit)' to exit.
> > (+ "ab")
> debug: main_handler called
> debug: main_trampoline: scm_c_catch returned!
>
> Process mosml finished
>
>

[-- Attachment #2: Type: text/html, Size: 4576 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: scm_c_catch question
  2014-08-18  1:14 ` Ian Grant
@ 2014-08-20 18:02   ` Neil Jerram
  2014-08-24  1:09     ` Ian Grant
  0 siblings, 1 reply; 6+ messages in thread
From: Neil Jerram @ 2014-08-20 18:02 UTC (permalink / raw)
  To: guile-devel, ian.a.n.grant

On 2014-08-18 03:14, Ian Grant wrote:
> Hi Neil,
> 
> Sorry, I am replying to my message because I'm not on guile-devel, it
> seems. I'll join later.
> 
> Thanks for clarifying that. The misleading statement in the manual is
> just above the paragraph you quote: on
> https://www.gnu.org/software/guile/manual/html_node/Catch.html#Catch
> [2] it says
> 
>     Handler is invoked outside the scope of its own catch. If
> handler
>     again throws to the same key, a new handler from further up the
>     call chain is invoked.
> 
> It doesn't mention pre_unwind_handler, which implies that the
> statement does _not_ apply to pre_unwind_handler.

Yes, I believe it is correct that those statements do not apply to the 
pre-unwind handler.

> So this should be
> amended.
> 
> But it would be more useful if there were a way to allow a throw to be
> aborted as I was expecting. How hard would that be to implement? If
> you think the semantics are in fact different, it could be given a new
> name, scm_c_catch_and_rethrow or something. What I want is something
> like BSD signals semantics, where the handler is not reset when the
> signal occurs.
> 
> You ask "In your code, the pre-unwind handler calls failwith() and
> doesn't expect it to return - so where does it jump to?"
> 
> I thought I had explained that, sorry. The call to failwith() does a C
> longjmp back into the CAML bytecode interpreter (i.e. back down below
> the call stack into caml_main). This is not a non-local exit, as far
> as Guile is concerned, because it is just a 'naked' C longjmp. So in
> that sense, the second time Guile throws an exception it is being
> thrown from within pre_unwind_handler.

OK, I think I see now.  From Guile's point of view, the dynamic context 
of the 'catch' remains in effect - as you want - but with a flag to say 
that the pre-unwind handler is already running (actually implemented by 
the %running-exception-handlers fluid in boot-9.scm).  That explains why 
the _next_ exception causes the catch's main handler to be called, 
instead of the pre-unwind handler again.

> Is there a way to implement what I want with fluids?

I don't think so, no.  Basically what you need is to make some call, 
from the pre-unwind handler code, just before calling failwith(), that 
says "this pre-unwind handler should now be reactivated" and is 
implemented by removing itself from %running-exception-handlers.  But 
%running-exception-handlers is hidden inside a lexical environment, so 
that isn't possible without hacking the boot-9 code.

You could try hacking the boot-9 code, though, to see if that idea 
works, and then propose a patch.

Regards,
      Neil




^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: scm_c_catch question
  2014-08-20 18:02   ` Neil Jerram
@ 2014-08-24  1:09     ` Ian Grant
  0 siblings, 0 replies; 6+ messages in thread
From: Ian Grant @ 2014-08-24  1:09 UTC (permalink / raw)
  To: Neil Jerram, guile-devel


[-- Attachment #1.1: Type: text/plain, Size: 3255 bytes --]

Thanks Neil, that works. This is what I did: but I made no attempt to make
the C-level interface backwardly compatible. It seems the same option could
apply to scm_with_throw_handler, but I left that alone. Can anyone tell me
what where the semantics to the latter when the LAZY_THROW_P paramerter was
set? It sounds like it used to evaluate the handler in the dynamic context
of the destination, like a continuation.

Ian




On Wed, Aug 20, 2014 at 2:02 PM, Neil Jerram <neil@ossau.homelinux.net>
wrote:

> On 2014-08-18 03:14, Ian Grant wrote:
>
>> Hi Neil,
>>
>> Sorry, I am replying to my message because I'm not on guile-devel, it
>> seems. I'll join later.
>>
>> Thanks for clarifying that. The misleading statement in the manual is
>> just above the paragraph you quote: on
>> https://www.gnu.org/software/guile/manual/html_node/Catch.html#Catch
>> [2] it says
>>
>>
>>     Handler is invoked outside the scope of its own catch. If
>> handler
>>     again throws to the same key, a new handler from further up the
>>     call chain is invoked.
>>
>> It doesn't mention pre_unwind_handler, which implies that the
>> statement does _not_ apply to pre_unwind_handler.
>>
>
> Yes, I believe it is correct that those statements do not apply to the
> pre-unwind handler.
>
>
>  So this should be
>> amended.
>>
>> But it would be more useful if there were a way to allow a throw to be
>> aborted as I was expecting. How hard would that be to implement? If
>> you think the semantics are in fact different, it could be given a new
>> name, scm_c_catch_and_rethrow or something. What I want is something
>> like BSD signals semantics, where the handler is not reset when the
>> signal occurs.
>>
>> You ask "In your code, the pre-unwind handler calls failwith() and
>> doesn't expect it to return - so where does it jump to?"
>>
>> I thought I had explained that, sorry. The call to failwith() does a C
>> longjmp back into the CAML bytecode interpreter (i.e. back down below
>> the call stack into caml_main). This is not a non-local exit, as far
>> as Guile is concerned, because it is just a 'naked' C longjmp. So in
>> that sense, the second time Guile throws an exception it is being
>> thrown from within pre_unwind_handler.
>>
>
> OK, I think I see now.  From Guile's point of view, the dynamic context of
> the 'catch' remains in effect - as you want - but with a flag to say that
> the pre-unwind handler is already running (actually implemented by the
> %running-exception-handlers fluid in boot-9.scm).  That explains why the
> _next_ exception causes the catch's main handler to be called, instead of
> the pre-unwind handler again.
>
>
>  Is there a way to implement what I want with fluids?
>>
>
> I don't think so, no.  Basically what you need is to make some call, from
> the pre-unwind handler code, just before calling failwith(), that says
> "this pre-unwind handler should now be reactivated" and is implemented by
> removing itself from %running-exception-handlers.  But
> %running-exception-handlers is hidden inside a lexical environment, so that
> isn't possible without hacking the boot-9 code.
>
> You could try hacking the boot-9 code, though, to see if that idea works,
> and then propose a patch.
>
> Regards,
>      Neil
>
>

[-- Attachment #1.2: Type: text/html, Size: 4334 bytes --]

[-- Attachment #2: reset-catch.patch --]
[-- Type: text/x-patch, Size: 6624 bytes --]

--- boot-9.scm	2014-02-14 18:00:33.000000000 -0400
+++ guile-2.0.11/module/ice-9/boot-9.scm	2014-08-22 03:31:22.296841040 -0400
@@ -92,13 +92,13 @@
             (apply abort-to-prompt prompt-tag thrown-k args)
             (apply prev thrown-k args)))))
 
-  (define (custom-throw-handler prompt-tag catch-k pre)
+  (define (custom-throw-handler prompt-tag catch-k pre pre-reset)
     (let ((prev (fluid-ref %exception-handler)))
       (lambda (thrown-k . args)
         (if (or (eq? thrown-k catch-k) (eqv? catch-k #t))
             (let ((running (fluid-ref %running-exception-handlers)))
               (with-fluids ((%running-exception-handlers (cons pre running)))
-                (if (not (memq pre running))
+                (if (or pre-reset (not (memq pre running)))
                     (apply pre thrown-k args))
                 ;; fall through
                 (if prompt-tag
@@ -107,7 +107,7 @@
             (apply prev thrown-k args)))))
 
   (set! catch
-        (lambda* (k thunk handler #:optional pre-unwind-handler)
+        (lambda* (k thunk handler #:optional pre-unwind-handler pre-reset)
           "Invoke @var{thunk} in the dynamic context of @var{handler} for
 exceptions matching @var{key}.  If thunk throws to the symbol
 @var{key}, then @var{handler} is invoked this way:
@@ -120,9 +120,18 @@
 @var{thunk} takes no arguments.  If @var{thunk} returns
 normally, that is the return value of @code{catch}.
 
-Handler is invoked outside the scope of its own @code{catch}.
-If @var{handler} again throws to the same key, a new handler
-from further up the call chain is invoked.
+@code{handler} is invoked outside the scope of its own @code{catch}.
+If @code{handler} again throws to the same key, a new handler from
+further up the call chain is invoked. @code{pre-unwind-handler} is
+invoked @emph{inside} the scope of the catch, in so far as if
+@var{key} is thrown from within the dynamic context of
+@code{pre-unwind-handler}, then @code{handler} will be invoked
+immediately, (i.e. without re-entering @code{pre-unwind-handler}. This
+behaviour can be changed by setting the optional argument
+@var{pre-reset} to @code{#t}, which makes throws within
+@code{pre-unwind-handler} re-enter the @code{pre-unwind-handler}. This
+allows exceptions to be cancelled in the @code{pre-unwind-handler}, by
+throwing to another handler in a different dynamic context.
 
 If the key is @code{#t}, then a throw to @emph{any} symbol will
 match this call to @code{catch}.
@@ -152,7 +161,7 @@
                (with-fluids
                    ((%exception-handler
                      (if pre-unwind-handler
-                         (custom-throw-handler tag k pre-unwind-handler)
+                         (custom-throw-handler tag k pre-unwind-handler pre-reset)
                          (default-throw-handler tag k))))
                  (thunk)))
              (lambda (cont k . args)
@@ -167,7 +176,7 @@
                          "Wrong type argument in position ~a: ~a"
                          (list 1 k) (list k)))
           (with-fluids ((%exception-handler
-                         (custom-throw-handler #f k pre-unwind-handler)))
+                         (custom-throw-handler #f k pre-unwind-handler #f)))
             (thunk))))
 
   (set! throw
--- continuations.c	2014-02-14 18:00:33.000000000 -0400
+++ guile-2.0.11/libguile/continuations.c	2014-08-22 05:50:43.126300151 -0400
@@ -455,7 +455,7 @@
   result = scm_c_catch (SCM_BOOL_T,
 			body, body_data,
 			handler, handler_data,
-			pre_unwind_handler, pre_unwind_handler_data);
+			pre_unwind_handler, pre_unwind_handler_data, 0);
 
   /* Return to old continuation root.
    */
--- load.c	2014-02-28 16:01:27.000000000 -0400
+++ guile-2.0.11/libguile/load.c	2014-08-22 05:51:03.282400099 -0400
@@ -863,7 +863,7 @@
                       SCM2PTR (source),
                       auto_compile_catch_handler,
                       SCM2PTR (source),
-                      NULL, NULL);
+                      NULL, NULL, 0);
 }
 
 /* The auto-compilation code will residualize a .go file in the cache
--- throw.c	2014-02-14 18:00:33.000000000 -0400
+++ guile-2.0.11/libguile/throw.c	2014-08-22 05:50:00.410088349 -0400
@@ -65,13 +65,13 @@
 
 SCM
 scm_catch_with_pre_unwind_handler (SCM key, SCM thunk, SCM handler,
-                                   SCM pre_unwind_handler)
+                                   SCM pre_unwind_handler, SCM pre_reset)
 {
   if (SCM_UNBNDP (pre_unwind_handler))
     return scm_catch (key, thunk, handler);
   else
-    return scm_call_4 (scm_variable_ref (catch_var), key, thunk, handler,
-                       pre_unwind_handler);
+    return scm_call_5 (scm_variable_ref (catch_var), key, thunk, handler,
+                       pre_unwind_handler, pre_reset);
 }
 
 static void
@@ -192,7 +192,7 @@
 scm_c_catch (SCM tag,
 	     scm_t_catch_body body, void *body_data,
 	     scm_t_catch_handler handler, void *handler_data,
-	     scm_t_catch_handler pre_unwind_handler, void *pre_unwind_handler_data)
+	     scm_t_catch_handler pre_unwind_handler, void *pre_unwind_handler_data, int pre_reset)
 {
   SCM sbody, shandler, spre_unwind_handler;
   
@@ -205,7 +205,7 @@
     spre_unwind_handler = SCM_UNDEFINED;
   
   return scm_catch_with_pre_unwind_handler (tag, sbody, shandler,
-                                            spre_unwind_handler);
+                                            spre_unwind_handler, scm_from_bool(pre_reset));
 }
 
 SCM
@@ -216,7 +216,7 @@
   return scm_c_catch (tag,
                       body, body_data,
                       handler, handler_data,
-                      NULL, NULL);
+                      NULL, NULL, 0);
 }
 
 
--- throw.h	2014-01-21 17:25:11.000000000 -0400
+++ guile-2.0.11/libguile/throw.h	2014-08-22 05:47:34.521364905 -0400
@@ -37,7 +37,7 @@
 			 scm_t_catch_handler handler,
 			 void *handler_data,
 			 scm_t_catch_handler pre_unwind_handler,
-			 void *pre_unwind_handler_data);
+			 void *pre_unwind_handler_data, int pre_reset);
 
 SCM_API SCM scm_c_with_throw_handler (SCM tag,
 				      scm_t_catch_body body,
@@ -76,7 +76,7 @@
 SCM_API SCM scm_handle_by_throw (void *, SCM, SCM);
 SCM_API int scm_exit_status (SCM args);
 
-SCM_API SCM scm_catch_with_pre_unwind_handler (SCM tag, SCM thunk, SCM handler, SCM lazy_handler);
+SCM_API SCM scm_catch_with_pre_unwind_handler (SCM tag, SCM thunk, SCM handler, SCM lazy_handler, SCM pre_reset);
 SCM_API SCM scm_catch (SCM tag, SCM thunk, SCM handler);
 SCM_API SCM scm_with_throw_handler (SCM tag, SCM thunk, SCM handler);
 SCM_API SCM scm_ithrow (SCM key, SCM args, int no_return);

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: scm_c_catch question
@ 2014-09-15 21:17 Ian Grant
  0 siblings, 0 replies; 6+ messages in thread
From: Ian Grant @ 2014-09-15 21:17 UTC (permalink / raw)
  To: Ian Grant, guile-devel

[-- Attachment #1: Type: text/plain, Size: 1295 bytes --]

A while ago I posted this patch:

   http://lists.gnu.org/archive/html/guile-devel/2014-08/msg00058.html

No-one had anything to say about it. But I would like to know what to do if
I want my code to work with the next release of Guile.

I think in retrospect this patch is too much. A better patch would leave
the API alone, and just remove the existing check for the current throw
handlers, and make the documentation clearer about exactly what "the
dynamic context of the throw" really means.

If the catcher wants to 'fall through' to the main handler from the
pre-unwind-handler, then she can just return from the pre-unwind-handler.
So what is the function this check provides?

If we remove the check, then the dynamic context of the throw, in which the
pre-unwind-handler is called, includes the throw-handlers in effect at the
time of the throw., So if you throw the same exception from within the
pre-unwind-handler, you re-enter it.

What I am proposing is a change to the actual semantics implemented, but it
is arguably not a change to the semantics documented. Or have I missed the
clearer definition somewhere else in the manual of what "the dynamic
context of the throw" means, and which makes it clear that this is
everything but the throw handlers in effect at the time?!

Ian

[-- Attachment #2: Type: text/html, Size: 1550 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2014-09-15 21:17 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-08-15 22:13 scm_c_catch question Ian Grant
2014-08-16 13:24 ` Neil Jerram
2014-08-18  1:14 ` Ian Grant
2014-08-20 18:02   ` Neil Jerram
2014-08-24  1:09     ` Ian Grant
  -- strict thread matches above, loose matches on Subject: below --
2014-09-15 21:17 Ian Grant

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).