New feature: proper object lifecycle protocol; init and teardown fragments.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 15 Dec 2015 19:15:23 +0000 (19:15 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 29 May 2016 14:09:04 +0000 (15:09 +0100)
A class definition may contain fragments of C code for initialization
and teardown.  Initialization fragments are invoked from the default
`init' message behaviour, in least-to-most specific order, immediately
after initializing the corresponding class's direct slots (if any);
teardown fragments are included in the default behaviour of a new
`teardown' message, in most-to-least specific order.

Now that we have initialization and teardown, we can implement more
useful object lifecycle functionality around them, e.g., dealing with
dynamically allocated objects.  Appropriate new definitions have been
added to the library.

17 files changed:
doc/SYMBOLS
doc/concepts.tex
doc/meta.tex
doc/runtime.tex
doc/structures.tex
doc/syntax.tex
lib/Makefile.am
lib/sod-hosted.c [new file with mode: 0644]
lib/sod-structs.3
lib/sod.c
lib/sod.h
src/builtin.lisp
src/class-make-impl.lisp
src/class-make-proto.lisp
src/classes.lisp
src/module-parse.lisp
src/sod-module.5

index 2acfd60..f729de4 100644 (file)
@@ -259,7 +259,9 @@ class-make-proto.lisp
   check-message-type                            generic
   check-method-type                             generic
   make-sod-class                                function
+  make-sod-class-initfrag                       generic
   make-sod-class-initializer                    generic
+  make-sod-class-tearfrag                       generic
   make-sod-initializer-using-slot               generic
   make-sod-instance-initializer                 generic
   make-sod-message                              generic
@@ -300,6 +302,7 @@ classes.lisp
   sod-class-class-initializers                  generic setf
   sod-class-direct-superclasses                 generic
   sod-class-ilayout                             generic
+  sod-class-initfrags                           generic setf
   sod-class-initializer                         class
   sod-class-instance-initializers               generic setf
   sod-class-messages                            generic setf
@@ -310,6 +313,7 @@ classes.lisp
   sod-class-precedence-list                     generic
   sod-class-slots                               generic setf
   sod-class-state                               generic
+  sod-class-tearfrags                           generic setf
   sod-class-type                                generic
   sod-class-vtables                             generic
   sod-initializer                               class
@@ -1178,8 +1182,12 @@ make-method-entries
   basic-effective-method sod-class sod-class
 sod-parser:make-scanner-stream
   sod-token-scanner
+make-sod-class-initfrag
+  sod-class t t
 make-sod-class-initializer
   sod-class t t t t
+make-sod-class-tearfrag
+  sod-class t t
 make-sod-initializer-using-slot
   sod-class sod-slot t t t t
 make-sod-instance-initializer
@@ -1339,6 +1347,10 @@ sod-class-direct-superclasses
   sod-class
 sod-class-ilayout
   sod-class
+sod-class-initfrags
+  sod-class
+(setf sod-class-initfrags)
+  t sod-class
 sod-class-instance-initializers
   sod-class
 (setf sod-class-instance-initializers)
@@ -1365,6 +1377,10 @@ sod-class-slots
   t sod-class
 sod-class-state
   sod-class
+sod-class-tearfrags
+  sod-class
+(setf sod-class-tearfrags)
+  t sod-class
 sod-class-type
   sod-class
 sod-class-vtables
@@ -1385,6 +1401,7 @@ sod-message-effective-method-class
   aggregating-message
   sod::initialization-message
   standard-message
+  sod::teardown-message
 sod-message-kernel-function
   aggregating-message
 sod-message-method-class
index f5e3a2f..8151014 100644 (file)
@@ -666,10 +666,13 @@ Construction of a new instance of a class involves three steps.
   necessary.
 \end{enumerate}
 The \descref{SOD_DECL}[macro]{mac} handles constructing instances with
-automatic storage duration (`on the stack').  Programmers can add support for
-other allocation strategies by using the \descref{SOD_INIT}[macro]{mac} and
-the \descref{sod_init}{fun} and \descref{sod_initv}{fun} functions, which
-package up imprinting and initialization.
+automatic storage duration (`on the stack').  Similarly, the
+\descref{SOD_MAKE}[macro]{mac} and the \descref{sod_make}{fun} and
+\descref{sod_makev}{fun} functions construct instances allocated from the
+standard @|malloc| heap.  Programmers can add support for other allocation
+strategies by using the \descref{SOD_INIT}[macro]{mac} and the
+\descref{sod_init}{fun} and \descref{sod_initv}{fun} functions, which package
+up imprinting and initialization.
 
 \subsubsection{Allocation}
 Instances of most classes (specifically including those classes defined by
@@ -743,9 +746,10 @@ message, defined by the @|SodObject| class.  This message uses a nonstandard
 method combination which works like the standard combination, except that the
 \emph{default behaviour}, if there is no overriding method, is to initialize
 the instance's slots using the initializers defined in the class and its
-superclasses.  This default behaviour may be invoked multiple times if some
-method calls on its @|next_method| more than once, unless some other method
-takes steps to prevent this.
+superclasses, and to invoke each superclass's initialization fragments.  This
+default behaviour may be invoked multiple times if some method calls on its
+@|next_method| more than once, unless some other method takes steps to
+prevent this.
 
 Slots are initialized in a well-defined order.
 \begin{itemize}
@@ -755,8 +759,24 @@ Slots are initialized in a well-defined order.
   their definitions appear.
 \end{itemize}
 
-The recommended way to add new initialization behaviour is to define @|after|
-methods on the @|init| message.  These will be run after the slot
+A class can define \emph{initialization fragments}: pieces of literal code to
+be executed to set up a new instance.  Each superclass's initialization
+fragments are executed with @|me| bound to an instance pointer of the
+appropriate superclass type, immediately after that superclass's slots (if
+any) have been initialized; therefore, fragments defined by a more specific
+superclass are executed after fragments defined by a more specific
+superclass.  A class may define more than one initialization fragment: the
+fragments are executed in the order in which they appear in the class
+definition.  It is possible for an initialization fragment to use @|return|
+or @|goto| for special control-flow effects, but this is not likely to be a
+good idea.
+
+Note that an initialization fragment defined in a class is copied literally
+into each subclass's initialization method.  This is fine for simple cases
+but wasteful if the initialization logic is complicated.  More complex
+initialization behaviour should be added either by having an initialization
+fragments call functions (necessarily with external linkage), or by defining
+@|after| methods on the @|init| message.  These will be run after the slot
 initializers have been applied, in reverse precedence order.
 
 Initialization is \emph{parametrized}, so the caller may select from a space
@@ -765,25 +785,6 @@ instance about some other objects known to the caller.  Specifically, the
 @|init| message accepts keyword arguments (\xref{sec:concepts.keywords})
 which can be defined and used by methods defined on it.
 
-\subsubsection{Example}
-The following is a simple function, with syntactic-sugar macro, which
-allocate storage for an instance of a class, imprints and initializes it, and
-returns a pointer to the new instance.
-\begin{prog}
-  void *make_instance(const SodClass *c, @|\dots|) \\
-  \{ \\ \ind
-    va_list ap;
-    void *p = malloc(c@->cls.initsz); \\
-    if (!p) return (0); \\
-    va_start(ap, c); \\
-    sod_initv(c, p, ap); \\
-    va_end(ap); \\
-    return (p); \- \\
-  \}
-  \\+
-  \#define MAKE(cls, keys) (cls *)make_instance(cls\#\#__class, keys)
-\end{prog}
-
 
 \subsection{Destruction}
 \label{sec:concepts.lifecycle.death}
@@ -796,32 +797,61 @@ steps.
 \item \emph{Deallocation} releases the memory used to store the instance so
   that it can be reused.
 \end{enumerate}
+Teardown alone, for objects which require special deallocation, or for which
+deallocation occurs automatically (e.g., instances with automatic storage
+duration, or instances whose storage will be garbage-collected), is performed
+using the \descref{sod_teardown}[function]{fun}.  Destruction of instances
+allocated from the standard @|malloc| heap is done using the
+\descref{sod_destroy}[function]{fun}.
 
 \subsubsection{Teardown}
-Details of teardown are class-specific, but typically it involves releasing
-resources held by the instance, and possibly unlinking it from some larger
-data structure which used to keep track of it.
+Details of initialization are necessarily class-specific, but typically it
+involves setting the instance's slots to appropriate values, and possibly
+linking it into some larger data structure to keep track of it.
+
+Teardown is performed by sending the instance the @|teardown| message,
+defined by the @|SodObject| class.  The message returns an integer, used as a
+boolean flag.  If the message returns zero, then the instance's storage
+should be deallocated.  If the message returns nonzero, then it is safe for
+the caller to forget about instance, but should not deallocate its storage.
+This is \emph{not} an error return: if some teardown method fails then the
+program may be in an inconsistent state and should not continue.
 
-There is no provided protocol for teardown: classes whose instances require
-teardown behaviour must define and implement an appropriate protocol of their
-own.  The following class may serve for simple cases.
+This simple protocol can be used, for example, to implement a reference
+counting system, as follows.
 \begin{prog}
-  [nick = disposable] \\
-  class DisposableObject : SodObject \{ \\- \ind
-    void release() \{ ; \} \\
-    \quad /* Release resources held by the receiver. */ \- \\-
-  \}
-  \\+
-  code c : user \{ \\- \ind
-    /* If p is a a DisposableObject then release its resources. */ \\
-    void maybe_dispose(void *p) \\
+  [nick = ref] \\
+  class ReferenceCountedObject \{ \\ \ind
+    unsigned nref = 1; \\-
+    void inc() \{ me@->ref.nref++; \} \\-
+    [role = around] \\
+    int obj.teardown() \\
     \{ \\ \ind
-      DisposableObject *d = SOD_CONVERT(DisposableObject, p); \\
-      if (d) DisposableObject_release(d); \- \\
+      if (--\,--me@->ref.nref) return (1); \\
+      else return (CALL_NEXT_METHOD); \- \\
     \} \- \\
   \}
 \end{prog}
 
+This message uses a nonstandard method combination which works like the
+standard combination, except that the \emph{default behaviour}, if there is
+no overriding method, is to execute the superclass's teardown fragments, and
+to return zero.  This default behaviour may be invoked multiple times if some
+method calls on its @|next_method| more than once, unless some other method
+takes steps to prevent this.
+
+A class can define \emph{teardown fragments}: pieces of literal code to be
+executed to shut down an instance.  Each superclass's teardown fragments are
+executed with @|me| bound to an instance pointer of the appropriate
+superclass type; fragments defined by a more specific superclass are executed
+before fragments defined by a more specific superclass.  A class may define
+more than one teardown fragment: the fragments are executed in the order in
+which they appear in the class definition.  It is possible for an
+initialization fragment to use @|return| or @|goto| for special control-flow
+effects, but this is not likely to be a good idea.  Similarly, it's probably
+a better idea to use an @|around| method to influence the return value than
+to write an explicit @|return| statement in a teardown fragment.
+
 \subsubsection{Deallocation}
 The details of instance deallocation are obviously specific to the allocation
 strategy used by the instance, and this is often orthogonal from the object's
@@ -832,19 +862,6 @@ of the object's direct class.  Low-level details of deallocation often
 require the proper base address of the instance's storage, which can be
 determined using the \descref{SOD_INSTBASE}[macro]{mac}.
 
-\subsubsection{Example}
-The following is a counterpart to the @|new_instance| function
-(\xref{sec:concepts.lifecycle.birth}), which tears down and deallocates an
-instance allocated using @|malloc|.
-\begin{prog}
-  void free_instance(void *p) \\
-  \{ \\ \ind
-    SodObject *obj = p; \\
-    maybe_dispose(p); \\
-    free(SOD_INSTBASE(obj)); \- \\
-  \}
-\end{prog}
-
 %%%--------------------------------------------------------------------------
 \section{Metaclasses} \label{sec:concepts.metaclasses}
 
index a09ae21..fab8dec 100644 (file)
@@ -33,7 +33,7 @@
       \&key \=:name :nick :location :pset \+ \\
               :superclasses :link :metaclass \\
               :slots :instance-initializers :class-initializers \\
-              :messages :methods}
+              :initfrags :tearfrags :messages :methods}
 \end{describe}
 
 \begin{describe*}
      \dhead{gf}{setf (sod-class-instance-initializers @<class>) @<list>}
      \dhead{gf}{sod-class-class-initializers @<class> @> @<list>}
      \dhead{gf}{setf (sod-class-class-initializers @<class>) @<list>}
+     \dhead{gf}{sod-class-initfrags @<class> @> @<list>}
+     \dhead{gf}{setf (sod-class-initfrags @<class>) @<list>}
+     \dhead{gf}{sod-class-tearfrags @<class> @> @<list>}
+     \dhead{gf}{setf (sod-class-tearfrags @<class>) @<list>}
      \dhead{gf}{sod-class-messages @<class> @> @<list>}
      \dhead{gf}{setf (sod-class-messages @<class>) @<list>}
      \dhead{gf}{sod-class-methods @<class> @> @<list>}
       \nlret @<init>}
 \end{describe}
 
+\begin{describe*}
+    {\dhead{gf}{make-sod-class-initfrag @<class> @<frag> @<pset>
+                                        \&optional @<floc>}
+     \dhead{gf}{make-sod-class-tearfrag @<class> @<frag> @<pset>
+                                        \&optional @<floc>}}
+\end{describe*}
+
 \begin{describe}{cls}{sod-message () \&key :name :location :class :type}
 \end{describe}
 
index 6a1941d..7afe8bf 100644 (file)
@@ -764,6 +764,15 @@ deallocation, and applications are free to use any suitable mechanism.
   \xref{sec:structures.layout.instance}.
 \end{describe*}
 
+\begin{describe}[sod_teardown]{fun}{int sod_teardown(void *@<p>);}
+  Tears down an instance of a class, releasing any resources it holds.
+
+  This function is a very thin wrapper around sending the @|obj.teardown|
+  message.  See the description of that message
+  (page~\pageref{msg:obj.teardown}) and \xref{sec:concepts.lifecycle.death}
+  for details.
+\end{describe}
+
 \subsubsection{Automatic storage duration}
 The following macro constructs an instance with automatic storage duration.
 
@@ -781,7 +790,51 @@ The following macro constructs an instance with automatic storage duration.
   \descref{KWARGS}{mac} or \descref{NO_KWARGS}{mac}.
 
   The instance has automatic storage duration: pointers to it will become
-  invalid when control exits the scope of the declaration.
+  invalid when control exits the scope of the declaration.  If necessary, the
+  instance should be torn down before this happens, using the
+  \descref{sod_teardown}[function]{fun}.
+\end{describe}
+
+\subsubsection{Dynamic allocation}
+The following macros and functions deal with objects allocated from the
+standard C heap.  They don't work in freestanding implementations where
+@|malloc| and @|free| are not available.
+
+\begin{describe*}
+    {\dhead[SOD_MAKE]{mac}{@<cls> *SOD_MAKE(@<cls>, @<keywords>);}
+     \dhead[sod_make]{fun}{void *sod_make(const SodClass *@<cls>, \dots);}
+     \dhead[sod_makev]{fun}
+       {void *sod_makev(const SodClass *@<cls>, va_list @<ap>);}}
+  Constructs and returns a pointer to a new instance of @<cls>.
+
+  The direct class for the new instance is specified as a class name to
+  @|SOD_MAKE|, or a class object to the functions.
+
+  Keyword arguments for the initialization message may be provided.  The
+  @|SOD_MAKE| macro expects a single preprocessor-time argument which is
+  a use of one of \descref{KWARGS}{mac} or \descref{NO_KWARGS}{mac}; the
+  @|sod_make| function expects the keywords as a variable-length argument
+  tail; and @|sod_makev| expects the keywords to be passed indirectly,
+  through the captured argument-tail cursor @<ap>.
+
+  Storage for the new instance will have been allocated using the standard
+  @|malloc| function.  The easiest way to destroy the instance, when it is no
+  longer needed, is probably to call the
+  \descref{sod_destroy}[function]{fun}.
+
+  The return value is an instance pointer for the class @<cls>; the
+  @|SOD_MAKE| macro will have converted it to the correct type, so it should
+  probably be used where possible.
+\end{describe*}
+
+\begin{describe}[sod_destroy]{fun}{int sod_destroy(void *@<p>);}
+  Tears down and frees an instance allocated using @|malloc|.
+
+  The pointer @<p> should be an instance pointer, i.e., a pointer to any of
+  an instance's chains.  The instance is torn down, by sending it the
+  \descref{obj.teardown}[message]{msg}.  If the instance reports itself ready
+  for deallocation, then its storage is released using @|free|.  The return
+  value is the value returned by the @|obj.teardown| message.
 \end{describe}
 
 %%%----- That's all, folks --------------------------------------------------
index c31503e..42739ef 100644 (file)
@@ -108,7 +108,8 @@ recommended.
         size_t _base; \\
         struct SodObject__vtmsgs_obj \{ \\ \ind
           void (*init)(SodObject *me, ...); \\
-          void (*init__v)(SodObject *me, va_list); \- \\
+          void (*init__v)(SodObject *me, va_list); \\
+          int (*teardown)(SodObject *me); \- \\
         \} obj; \- \\
       \};
     \end{nprog} \\
@@ -135,7 +136,7 @@ recommended.
   The instance and vtable layout of @|SodObject| is shown in
   \xref{fig:structures.root.sodobject}.
 
-  The following message is defined.
+  The following messages are defined.
 
   \begin{describe}[obj.init]{msg}{void init(?);}
     Initialize a newly allocated instance.
@@ -147,15 +148,16 @@ recommended.
     method calls on its @|next_method| function more than once.
 
     This default behaviour is to initialize the instance's slots using the
-    defined slot initializers: each slot is initialized using the most
-    specific applicable initializer, if any.  Slots without an initializer
-    are left uninitialized.
+    defined slot initializers, and execute the initialization fragments.
+    Each slot is initialized using the most specific applicable initializer,
+    if any.  Slots without an initializer are left uninitialized.
 
-    Slots are initialized in reverse-precedence order of their defining
-    classes; i.e., slots defined by a less specific superclass are
-    initialized earlier than slots defined by a more specific superclass.
-    Slots defined by the same class are initialized in the order in which
-    they appear in the class definition.
+    Slots are initialized and initialization fragments executed together, a
+    superclass at a time: first, the superclass's slots are initialized (if
+    any); then the superclass's initialization fragments (if any) are
+    executed, starting with the least specific superclass first.  Slots and
+    initialization fragments defined by the same class are processed in the
+    order in which they appear in the class definition.
 
     There are no standard keyword arguments; methods on subclasses are free
     to introduce their own in the usual way.
@@ -167,6 +169,30 @@ recommended.
     For more details on instance construction, see
     \xref{sec:concepts.lifecycle.birth}.
   \end{describe}
+
+  \begin{describe}[obj.teardown]{msg}{int teardown();}
+    Teardown an instance which is no longer required.
+
+    The message returns an integer flag.  A zero value means that the
+    instance is safe to deallocate.  A nonzero value means that the instance
+    should not be deallocated, and that it is safe for the caller to simply
+    forget about it.  This simple protocol may be used, for example, to
+    implement a reference-counting system.
+
+    This message uses a custom method combination which works like the
+    standard method combination except that default behaviour is invoked if
+    no primary or around method overrides.
+
+    This default behaviour is to execute each superclass's teardown
+    fragments, most specific first, and then return zero to indicate that the
+    object is ready for deallocation.  Teardown fragments defined by the same
+    class are processed in the order in which they appear in the class
+    definition.
+
+    It is usual to provide complex teardown behaviour as @|before| methods.
+    Logic to decide whether to allow deallocation is usually implemented as
+    @|around| methods.
+  \end{describe}
 \end{describe}
 
 
index c23e58e..b18acd5 100644 (file)
@@ -585,6 +585,7 @@ class Sub : Super {
 
 <class-item> ::= <slot-item>
 \alt <initializer-item>
+\alt <fragment-item>
 \alt <message-item>
 \alt <method-item>
 \end{grammar}
@@ -675,6 +676,13 @@ The first component of the @<dotted-name> must be the nickname of one of the
 class's superclasses (including itself); the second must be the name of a
 slot defined in that superclass.
 
+\subsubsection{Fragment items}
+\begin{grammar}
+<fragment-item> ::= <fragment-kind> "{" <c-fragment> "}"
+
+<fragment-kind> ::= "init" | "teardown"
+\end{grammar}
+
 \subsubsection{Message items}
 \begin{grammar}
 <message-item> ::=
index 1a4bb72..cac544d 100644 (file)
@@ -45,7 +45,7 @@ libsod_la_SOURCES     += keyword.c keyword-hosted.c
 dist_man_MANS          += keyword.3
 
 pkginclude_HEADERS     += sod.h
-libsod_la_SOURCES      += sod.c
+libsod_la_SOURCES      += sod.c sod-hosted.c
 dist_man_MANS          += sod.3
 
 ###--------------------------------------------------------------------------
diff --git a/lib/sod-hosted.c b/lib/sod-hosted.c
new file mode 100644 (file)
index 0000000..961aa22
--- /dev/null
@@ -0,0 +1,105 @@
+/* -*-c-*-
+ *
+ * Runtime support for SOD requiring hosted implementation
+ *
+ * (c) 2015 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Sensible Object Design, an object system for C.
+ *
+ * SOD is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * SOD is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with SOD; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdlib.h>
+
+#include "sod.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @sod_make@, @sod_makev@ --- *
+ *
+ * Arguments:  @const SodClass *cls@ = class object for new instance
+ *             @va_list ap, ...@ = initialization keyword arguments
+ *
+ * Returns:    Pointer to the newly-allocated initialized instance, or null.
+ *
+ * Use:                Allocates storage for a new instance, initializes it, and
+ *             returns a pointer to it.  If allocation fails, a null pointer
+ *             is returned instead.
+ *
+ *             This function will allocate the storage using @malloc@, and
+ *             then initialize it as for @sod_init@.
+ *
+ *             It's usually convenient to use the macro @SOD_MAKE@ rather
+ *             than calling @sod_make@ directly.
+ *
+ *             (This function is not available in freestanding environments
+ *             lacking @malloc@ and @free@.)
+ */
+
+void *sod_make(const SodClass *cls, ...)
+{
+  void *p;
+  va_list ap;
+
+  va_start(ap, cls);
+  p = sod_makev(cls, ap);
+  va_end(ap);
+  return (p);
+}
+
+void *sod_makev(const SodClass *cls, va_list ap)
+{
+  void *p;
+
+  if ((p = malloc(cls->cls.initsz)) == 0) return (0);
+  return (sod_initv(cls, p, ap));
+}
+
+/* --- @sod_destroy@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to an instance to be torn down, or null
+ *
+ * Returns:    Zero if the object was freed; nonzero if it refused for some
+ *             reason.
+ *
+ * Use:                Invokes the instance's `teardown' method to release any held
+ *             resources, and then calls @free@ to release the instance's
+ *             storage.  See @sod_teardown@ for details, especially
+ *             regarding the return value's meaning.
+ *
+ *             If @p@ is null, then this function does nothing except
+ *             returns zero.
+ *
+ *             (This function is not available in freestanding environments
+ *             lacking @malloc@ and @free@.)
+ */
+
+int sod_destroy(void *p)
+{
+  int rc;
+
+  if (!p) return (0);
+  rc = sod_teardown(p);
+  if (!rc) free(p);
+  return (rc);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
index 6cfbb75..029cabd 100644 (file)
@@ -72,6 +72,7 @@ struct SodObject__vt_obj {
 \h'2n'struct SodObject__vtmsgs_obj {
 \h'4n'void (*init)(SodObject *\fIme\fB, ...);
 \h'4n'void (*init__v)(SodObject *\fIme\fB, va_list);
+\h'4n'int (*teardown)(SodObject *\fIme\fB);
 \h'2n'} obj;
 };
 
@@ -92,6 +93,7 @@ struct SodClass__vt_obj {
 \h'2n'struct SodClass__vtmsgs_obj {
 \h'4n'void (*init)(SodClass *\fIme\fB, ...);
 \h'4n'void (*init__v)(SodClass *\fIme\fB, va_list);
+\h'4n'int (*teardown)(SodClass *\fIme\fB);
 \h'2n'} obj;
 };
 
@@ -254,19 +256,21 @@ if some method calls on its
 function more than once.
 .PP
 This default behaviour is to initialize the instance's slots
-using the defined slot initializers:
-each slot is initialized
+using the defined slot initializers,
+and execute the initialization fragments.
+Each slot is initialized
 using the most specific applicable initializer,
 if any.
 Slots without an initializer
 are left uninitialized.
 .PP
-Slots are initialized in reverse-precedence order
-of their defining classes;
-i.e., slots defined by a less specific superclass are initialized
-earlier than slots defined by a more specific superclass.
-Slots defined by the same class are initialized in the order in which
-they appear in the class definition.
+Slots are initialized and initialization fragments executed together,
+a superclass at a time:
+first, the superclass's slots are initialized (if any);
+then the superclass's initialization fragments (if any) are executed,
+starting with the least specific superclass first.
+Slots and initialization fragments defined by the same class
+are processed in the order in which they appear in the class definition.
 .PP
 There are no standard keyword arguments;
 methods on subclasses are free to
@@ -277,6 +281,38 @@ It is usual to provide complex initialization behaviour as
 methods.
 This ensures that slots have been initialized as necessary
 before the method executes.
+.PP
+The
+.B teardown
+message is used to tear down an instance which is no longer required.
+.PP
+The message returns an integer flag.
+A zero value means that the instance is safe to deallocate.
+A nonzero value means that the instance should not be deallocated,
+and that it is safe for the caller to simply forget about it.
+This simple protocol may be used, for example,
+to implement a reference-counting system.
+.PP
+This message uses a custom method combination
+which works like the standard method combination
+except that default behaviour is invoked if
+no primary or around method overrides.
+This default behaviour is to execute
+each superclass's teardown fragments,
+most specific first,
+and then return zero to indicate
+that the object is ready for deallocation.
+Teardown fragments defined by the same class
+are processed in the order in which they appear
+in the class definition.
+.PP
+It is usual to provide complex teardown behaviour as
+.B before
+methods.
+Logic to decide whether to allow deallocation
+is usually implemented as
+.B around
+methods.
 .
 .SS The SodClass class
 The
index aee28d5..a21a599 100644 (file)
--- a/lib/sod.c
+++ b/lib/sod.c
@@ -159,4 +159,32 @@ void *sod_initv(const SodClass *cls, void *p, va_list ap)
   return (p);
 }
 
+/* --- @sod_teardown@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to an instance to be torn down
+ *
+ * Returns:    Zero if the object is torn down; nonzero if it refused for
+ *             some reason.
+ *
+ * Use:                Invokes the instance's `teardown' method to release any held
+ *             resources.
+ *
+ *             If this function returns nonzero, then the object is still
+ *             active, and may still hold important resources.  This is not
+ *             intended to be a failure condition: failures in teardown are
+ *             usually unrecoverable (or very hard to recover from) and
+ *             should probably cause the program to abort.  A refusal, on
+ *             the other hand, means that the object is still live and
+ *             shouldn't be deallocated, but that this is a normal situation
+ *             and the caller shouldn't worry about it.
+ */
+
+int sod_teardown(void *p)
+{
+  SodObject *obj;
+
+  obj = SOD_CONVERT(SodObject, p);
+  return (SodObject_teardown(obj));
+}
+
 /*----- That's all, folks -------------------------------------------------*/
index 185c6fb..8945810 100644 (file)
--- a/lib/sod.h
+++ b/lib/sod.h
 /* We're going to want to make use of this ourselves. */
 SOD__VARARGS_MACROS_PREAMBLE
 
+/* --- @SOD__IGNORE@ --- *
+ *
+ * Arguments:  @var@ = some variable name
+ *
+ * Use:                Suppress any warning that @var@ isn't used.
+ */
+
+#define SOD__IGNORE(var) ((void)(var))
+
 /* --- @SOD__CAR@ --- *
  *
  * Arguments:  @...@ = a nonempty list of arguments
@@ -238,6 +247,18 @@ struct sod_chain {
 
 #define SOD_INIT(cls, p, keys) ((cls *)sod_init(cls##__class, (p), keys))
 
+/* --- @SOD_MAKE@ --- *
+ *
+ * Arguments:  @cls@ = a class type name
+ *             @keys@ = a @KWARGS(...)@ keyword argument sequence
+ *
+ * Use:                Allocates (using @malloc@) eand initializes storage to be an
+ *             instance of @cls@.  Returns a null pointer if allocation
+ *             fails.  Use @sod_destroy@ to release the instance.
+ */
+
+#define SOD_MAKE(cls, keys) ((cls *)sod_make(cls##__class, keys))
+
 /* --- @SOD_DECL@ --- *
  *
  * Arguments:  @cls@ = a class type name
@@ -313,6 +334,73 @@ extern void *sod_convert(const SodClass */*cls*/, const void */*obj*/);
 extern KWCALL void *sod_init(const SodClass */*cls*/, void */*p*/, ...);
 extern void *sod_initv(const SodClass */*cls*/, void */*p*/, va_list /*ap*/);
 
+/* --- @sod_make@, @sod_makev@ --- *
+ *
+ * Arguments:  @const SodClass *cls@ = class object for new instance
+ *             @va_list ap, ...@ = initialization keyword arguments
+ *
+ * Returns:    Pointer to the newly-allocated initialized instance, or null.
+ *
+ * Use:                Allocates storage for a new instance, initializes it, and
+ *             returns a pointer to it.  If allocation fails, a null pointer
+ *             is returned instead.
+ *
+ *             This function will allocate the storage using @malloc@, and
+ *             then initialize it as for @sod_init@.
+ *
+ *             It's usually convenient to use the macro @SOD_MAKE@ rather
+ *             than calling @sod_make@ directly.
+ *
+ *             (This function is not available in freestanding environments
+ *             lacking @malloc@ and @free@.)
+ */
+
+extern KWCALL void *sod_make(const SodClass */*cls*/, ...);
+extern void *sod_makev(const SodClass */*cls*/, va_list /*ap*/);
+
+/* --- @sod_teardown@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to an instance to be torn down
+ *
+ * Returns:    Zero if the object is torn down; nonzero if it refused for
+ *             some reason.
+ *
+ * Use:                Invokes the instance's `teardown' method to release any held
+ *             resources.
+ *
+ *             If this function returns nonzero, then the object is still
+ *             active, and may still hold important resources.  This is not
+ *             intended to be a failure condition: failures in teardown are
+ *             usually unrecoverable (or very hard to recover from) and
+ *             should probably cause the program to abort.  A refusal, on
+ *             the other hand, means that the object is still live and
+ *             shouldn't be deallocated, but that this is a normal situation
+ *             and the caller shouldn't worry about it.
+ */
+
+extern int sod_teardown(void */*p*/);
+
+/* --- @sod_destroy@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to an instance to be torn down, or null
+ *
+ * Returns:    Zero if the object was freed; nonzero if it refused for some
+ *             reason.
+ *
+ * Use:                Invokes the instance's `teardown' method to release any held
+ *             resources, and then calls @free@ to release the instance's
+ *             storage.  See @sod_teardown@ for details, especially
+ *             regarding the return value's meaning.
+ *
+ *             If @p@ is null, then this function does nothing except
+ *             returns zero.
+ *
+ *             (This function is not available in freestanding environments
+ *             lacking @malloc@ and @free@.)
+ */
+
+extern int sod_destroy(void */*p*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
index ffd8451..d7d0fcb 100644 (file)
@@ -290,6 +290,7 @@ static const SodClass *const ~A__cpl[] = {
                             :test (lambda (class item)
                                     (and (typep item 'islots)
                                          (eq (islots-class item) class)))))
+              (frags (sod-class-initfrags super))
               (this-class-focussed-p nil)
               (isl (format nil "me->~A" (sod-class-nickname super))))
 
@@ -318,6 +319,17 @@ static const SodClass *const ~A__cpl[] = {
                                                         slot-default)))
                    (emit-inst codegen initinst)))))
 
+           ;; Emit the class's initialization fragments.
+           (when frags
+             (let ((used-me-p this-class-focussed-p))
+               (focus-this-class)
+               (unless used-me-p
+                 (deliver-call codegen :void "SOD__IGNORE" "me")))
+             (dolist (frag frags)
+               (codegen-push codegen)
+               (emit-inst codegen frag)
+               (emit-inst codegen (codegen-pop-block codegen))))
+
            ;; If we opened a block to initialize this class then close it
            ;; again.
            (when this-class-focussed-p
@@ -331,6 +343,44 @@ static const SodClass *const ~A__cpl[] = {
 
     (deliver-call codegen :void func-name "sod__obj")))
 
+;; Teardown.
+
+(defclass teardown-message (lifecycle-message)
+  ())
+
+(defclass teardown-effective-method (lifecycle-effective-method)
+  ())
+
+(defmethod sod-message-effective-method-class ((message teardown-message))
+  'teardown-effective-method)
+
+(defmethod lifecycle-method-kernel
+    ((method teardown-effective-method) codegen target)
+  (let* ((class (effective-method-class method))
+        (obj-tag (ilayout-struct-tag class))
+        (func-type (c-type (fun void ("sod__obj" (* (struct obj-tag))))))
+        (func-name (format nil "~A__teardown" class)))
+    (codegen-push codegen)
+    (dolist (super (sod-class-precedence-list class))
+      (let ((frags (sod-class-tearfrags super)))
+       (when frags
+         (emit-banner codegen "Teardown for class `~A'." super)
+         (codegen-push codegen)
+         (declare-me codegen super)
+         (deliver-call codegen :void "SOD__IGNORE" "me")
+         (dolist (frag frags)
+           (codegen-push codegen)
+           (emit-inst codegen frag)
+           (emit-inst codegen (codegen-pop-block codegen)))
+         (emit-inst codegen (codegen-pop-block codegen)))))
+    (codegen-pop-function codegen func-name func-type
+                         "Instance teardown function ~:_~
+                          for class `~A'."
+                         class)
+    (deliver-call codegen :void
+                 (format nil "~A__teardown" class) "sod__obj")
+    (deliver-expr codegen target 0)))
+
 ;;;--------------------------------------------------------------------------
 ;;; Bootstrapping the class graph.
 
@@ -351,6 +401,8 @@ static const SodClass *const ~A__cpl[] = {
                      (c-type (fun void :keys))
                      (make-property-set
                       :message-class 'initialization-message))
+    (make-sod-message sod-object "teardown" (c-type (fun int))
+                     (make-property-set :message-class 'teardown-message))
 
     ;; Sort out the recursion.
     (setf (slot-value sod-class 'chain-link) sod-object)
index d2f3093..bd2407e 100644 (file)
   nil)
 
 ;;;--------------------------------------------------------------------------
+;;; Initialization and teardown fragments.
+
+(defmethod make-sod-class-initfrag
+    ((class sod-class) frag pset &optional location)
+  (declare (ignore pset location))
+  (with-slots (initfrags) class
+    (setf initfrags (append initfrags (list frag)))))
+
+(defmethod make-sod-class-tearfrag
+    ((class sod-class) frag pset &optional location)
+  (declare (ignore pset location))
+  (with-slots (tearfrags) class
+    (setf tearfrags (append tearfrags (list frag)))))
+
+;;;--------------------------------------------------------------------------
 ;;; Messages.
 
 (defmethod make-sod-message
index b10c298..f787bd3 100644 (file)
    You are not expected to call this generic function directly; it's more
    useful as a place to hang methods for custom initializer classes."))
 
+(export 'make-sod-class-initfrag)
+(defgeneric make-sod-class-initfrag (class frag pset &optional location)
+  (:documentation
+   "Attach an initialization fragment FRAG to the CLASS.
+
+   Currently, initialization fragments are just dumb objects held in a
+   list."))
+
+(export 'make-sod-class-tearfrag)
+(defgeneric make-sod-class-tearfrag (class frag pset &optional location)
+  (:documentation
+   "Attach a teardown fragment FRAG to the CLASS.
+
+   Currently, teardown fragments are just dumb objects held in a
+   list."))
+
 ;;;--------------------------------------------------------------------------
 ;;; Messages and methods.
 
index 1a3a925..0c695b2 100644 (file)
@@ -48,7 +48,7 @@
          sod-class-direct-superclasses sod-class-precedence-list
          sod-class-chain-link sod-class-chain-head
          sod-class-chain sod-class-chains
-         sod-class-slots
+         sod-class-slots sod-class-initfrags sod-class-tearfrags
          sod-class-instance-initializers sod-class-class-initializers
          sod-class-messages sod-class-methods
          sod-class-state
                          :accessor sod-class-instance-initializers)
    (class-initializers :initarg :class-initializers :initform nil
                       :type list :accessor sod-class-class-initializers)
+   (initfrags :initarg :initfrags :initform nil
+             :type list :accessor sod-class-initfrags)
+   (tearfrags :initarg :tearfrags :initform nil
+             :type list :accessor sod-class-tearfrags)
    (messages :initarg :messages :initform nil
             :type list :accessor sod-class-messages)
    (methods :initarg :methods :initform nil
index 2866c08..f6d69ee 100644 (file)
 
 (export 'class-item)
 
+(define-pluggable-parser class-item initfrags (scanner class pset)
+  ;; raw-class-item ::= frag-keyword `{' c-fragment `}'
+  ;; frag-keyword ::= `init' | `teardown'
+  (with-parser-context (token-scanner-context :scanner scanner)
+    (parse (seq ((make (or (seq ("init") #'make-sod-class-initfrag)
+                          (seq ("teardown") #'make-sod-class-tearfrag)))
+                (frag (parse-delimited-fragment scanner #\{ #\})))
+            (funcall make class frag pset scanner)))))
+
 (defun parse-class-body (scanner pset name supers)
   ;; class-body ::= `{' class-item* `}'
   ;;
                 ;;   | method-item
                 ;;   | slot-item
                 ;;   | initializer-item
+                ;;   | initfrag-item
                 ;;
                 ;; Most of the above begin with declspecs and a declarator
                 ;; (which might be dotted).  So we parse that here and
index bdb13e5..86c29f8 100644 (file)
@@ -542,6 +542,8 @@ class-definition
 .|
 .I initializer-item
 .|
+.I fragment-item
+.|
 .I message-item
 .|
 .I method-item
@@ -575,6 +577,19 @@ class-definition
 ::=
 .I c-fragment
 .br
+.I fragment-item
+::=
+.I fragment-kind
+.B {
+.I c-fragment
+.B }
+.br
+.I fragment-kind
+::=
+.B init
+|
+.B teardown
+.br
 .I message-item
 ::=
 .<