Emacs: Implement a GObject’s virtual function
#############################################

:date: 2016-01-13T13:31:12Z
:category: blog
:tags: c,development,emacs
:url: 2016/01/13/emacs-implement-a-gobject-s-virtual-function/
:save_as: 2016/01/13/emacs-implement-a-gobject-s-virtual-function/index.html
:status: published
:author: "Gergely Polonkai"

I have recently started creating a GLib implementation of the Matrix.org API.  For that, I have
created a GObject interface, MatrixAPI, which has as many virtual functions as API calls (which is
a lot, and expanding).  This way I ended up with the following scenario.

In ``matrix-api.h`` I had a struct like this, with a lot more elements:

.. code-block:: c

   typedef struct {
       void (*initial_sync)(MatrixAPI *api,
                            MatrixAPICallback callback,
                            gpointer user_data,
                            GError **error);
       void (*sync)(MatrixAPI *api,
                    MatrixAPICallback callback,
                    gpointer user_data,
                    GError **error);
       …

And in ``matrix-http-api.c``, which implements ``MatrixAPI``, I have a function like this (again,
with a lot more elements):

.. code-block:: c

   static void
   matrix_http_api_matrix_api_init(GObjectInterface *iface)
   {
       iface->initial_sync = i_initial_sync;
       iface->sync = i_sync;
       …
   }

And every time I wanted to implement a new function from the vtable, I had to copy the prototype,
and add an ``iface->foo_bar = i_foo_bar`` line and an actual function header for ``i_foo_bar``
with the same parameters.  That’s a cumbersome job for more than 40 function headers.  But Emacs
comes to the rescue!

.. code-block:: lisp

   (require 'thingatpt)

   (defun get-point(symbol &optional arg)
     "Get point, optionally running a command beforehand"

     (funcall symbol arg)
     (point))

   (defun copy-symbol-at-point()
     "Copy the symbol under point"

     (interactive)
     (save-excursion
       (let ((beg (get-point 'beginning-of-thing 'symbol))
             (end (get-point 'end-of-thing 'symbol)))

         (copy-region-as-kill beg end))))

   (defun implement-gobject-vfunc()
     "Change a vtable line of a GObject interface to an implementation line like:
   void (*my_iface_func)(type1 param1, type2 param2, ...);
   to
   iface->my_iface_func = i_my_iface_func;"

     (interactive)

     (save-excursion
       (let ((beg ((lambda()
                     (search-forward "(*")
                     (point))))
             (end ((lambda()
                     (back-to-indentation)
                     (point)))))
         (kill-region beg end))
       (copy-symbol-at-point)
       (insert "iface->")
       (end-of-thing 'symbol)
       (delete-char 1)
       (let ((beg (point))
             (end ((lambda()
                     (find-list-end)
                     (point)))))
         (kill-region beg end))
       (insert " = i_")
       (yank 2))
     (next-line)
     (beginning-of-line))

   (defun implement-gobject-vfunc-prototype()
     "Change a vtable line of a GObject interface to an implementation prototype line like:
   void (*my_iface_func)(type1 param1, type2 param2, ...);
   to
   static void
   i_my_iface_func(type1 param1, type2 param2, ...)"

     (interactive)

     (let ((beg ((lambda()
                   (back-to-indentation)
                   (point))))
           (end ((lambda()
                   (beginning-of-line)
                   (point)))))
       (kill-region beg end))
     (insert "static ")
     (search-forward "(*")
     (delete-char -3)
     (newline)
     (insert "i_")
     (end-of-thing 'symbol)
     (delete-char 1)
     (let ((beg (point))
           (end ((lambda()
                   (find-list-end)
                   (point)))))
       (indent-region beg end))
     (delete-char 1))

Now all I have to do is to copy the whole vtable entry into ``matrix_http_api_matrix_api_init()``,
execute :kbd:`M-x implement-gobject-vfunc`, then put the same vtable entry somewhere before the
interface init function, and execute :kbd:`M-x implement-gobject-vfunc-prototype`.
