This is the mail archive of the guile@sourceware.cygnus.com mailing list for the Guile project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: Emacs Scheme interface


Hello,

This is an idea of the new low level interface between Lisp and Scheme
that uses GOOPS for Guile Emacs.

Construction Process
--------------------

1. When one calls lisp-eval in Scheme, that calls Feval (the Emacs's
   eval function) internally.

2. Feval returns a Lisp object.  We have to create a corresponding
   Scheme object.  If the object is directly convertible to a Scheme
   object, we will do so.  However, since we want to share storage
   between Lisp and Scheme, we are going to create a kind of reference
   to the Lisp object.  We call it an instance of Emacs Object that is
   a GOOPS class.

  - Class: <emacs-object>

    An abstract class to hold Lisp objects.  Subclasses of this
    class are actually used.

  - Class: <emacs-pair>
  - Class: <emacs-string>
  - Class: <emacs-vector>
  - Class: <emacs-buffer>
  ...

3. We want to return the same Emacs object for the same Lisp object.
   We need a table to manage this relationship.  Let's think of it
   as a pair of vectors:

     Lisp object vector:  [obj0][obj1][obj2][obj3][...]
   Scheme object vector:  [obj0][obj1][obj2][obj3][...]

   The elements of the Lisp vector are Lisp objects.  The elements of
   the Scheme vector are corresponding Emacs objects mentioned above.
   We can traverse the Lisp vector to find the index of a Lisp object
   and thus the corresponding Emacs object.  An Emacs object should
   have a slot that keeps the index so that we can efficiently find
   the corresponding Lisp object.  (Or it could have a slot to hold
   a Lisp object directly, but we need the index slot anyway as
   described below.)

   This vector implementation is not very efficient, but I'm going to
   write this as the initial implementation because of its easiness.
   The Lisp vector is also used to protect those Lisp objects from GC.

4. We have to create a new Emacs object if it has not been done.  We
   put a Lisp object in the vector, get the index, and make a Emacs
   object with the index like this (but in the C level):

     (make <emacs-object> #:index index)

   The class is practically determined by the type of the Lisp object.
   We put this object in the Scheme vector for later reference.

5. The Emacs object found/created is returned as the value of lisp-eval.

Modification Process
--------------------

Since Emacs uses the same object type for several different concepts
(e.g., integers for characters, lists for sparse keymaps), we want to
distinguish them.  This could be done like this:

  (define-class <emacs-keymap> (<emacs-object>) ...)

  (define (current-local-map)
    (make <emacs-keymap> #:value (lisp-eval '(current-local-map))))

This is not sufficient since it does not always return the same value.
We need to change the class some way like this:

  (define (current-local-map)
    (let ((keymap (lisp-eval '(current-local-map))))
      (%change-emacs-object-class! keymap <emacs-keymap>)
      keymap))

This may be a little bit tricky, so I am not sure if we should do this.

Application Process
-------------------

Once lisp-eval returns a GOOPS object, we can import a Lisp function
and use it like this:

  (define (buffer-string)
    (lisp-eval '(buffer-string)))

  (define-method string-ref ((string <emacs-string>) (n <integer>))
    (lisp-eval (list 'aref string n)))

  (string-ref (buffer-string) 0)  --> 123

If a Emacs object is used as an argument of a Lisp function, it is
automatically converted to the corresponding Lisp object due to the
table defined above, so we don't need to care about that.  We can use
both <string> and <emacs-string> as an argument of the function insert:

  (define insert (string)
    (lisp-eval (list 'insert string)))

  (insert "hello")              ;; OK
  (insert (buffer-string))      ;; OK

If we need to operate an Emacs object by using Scheme procedures,
an explicit conversion is necessary.  It can be done like this:

  (define-method eval-string ((string <emacs-string>))
    (eval-string (%emacs-object->scheme string)))

  (eval-string (buffer-string))

At this level, most operations of lisp variables/functions except
dynamic references can be done transparently, and the code can be
used with the Guile-based Emacs in the future.  (See below for how
to handle dynamic bindings in Scheme.)

Destruction Process
-------------------

When an Emacs object is garbage-collected, we also have to remove
the corresponding Lisp object from the table.  We can use the index
slot of the Emacs object to find the location.

In the vector implementation, we can replace the Lisp object by nil.
That field can be used later.  If there are too many nil fields, we
should squeeze the elements.

Dynamic Bidings
---------------

Dynamic bidings, including references to buffer/frame-local variables,
are done this way:

  (define (lisp-variable-ref var)
    (lisp-eval var))

  (define (lisp-variable-set! var val)
    (lisp-eval `(setq ,var ',val)))

  (define buffer-file-name
    (make-procedure-with-setter
     (lambda () (lisp-variable-ref 'buffer-file-name))
     (lambda (value) (lisp-variable-set! 'buffer-file-name value))))

  (set! (buffer-file-name) "hello")
  (insert (buffer-file-name))

Note that we can't write without parentheses like (insert buffer-file-name),
which we can do with the current implementation.  We should explicitly
indicate what we are doing by using parentheses (and it's less tricky).

I think these interfaces can be used in the future and make it easier
to switch to Guile-based Emacs whenever it is appropriate.

-- Kei

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]