USerial — v0.7.2011.05.24 May 24th, 2011
Patrick Stein

I am releasing a new version of my USerial library. This version cleans up many messes from earlier releases. Unfortunately, in that process, it breaks compatibility with earlier releases.

Obtaining

Getting the USerial library:

Differences

The differences between this version and earlier versions of this library include:

  • Use of ContextL layered functions instead of CLOS methods
  • Elimination of :buffer parameter in favor of using the *buffer* special variable
  • Cleaning up macros which no longer required the :buffer parameter
  • Serializers for arbitrarily large integers and unsigned integers
  • Serializer for raw sequence of bytes
  • New make-list-serializer macro

By using ContextL layered functions, one has the ability to define a serializer and/or unserializer in a particular ContextL layer. This can be used to create new versions of the serializer without losing the ability to use the older version when required.

In the process, I have created macros to assist in creating completely custom serializers. This both streamlines their definition and should allow any future modifications to the USerial library to fly under the radar. Code that before looked like this:

(defmethod serialize ((key (eql :foo)) (value foo-struct)
                      &key (buffer userial:*buffer*) &allow-other-keys)
  ... some code ...
  buffer)

(defmethod unserialize ((key (eql :foo))
                        &key (buffer userial:*buffer*) &allow-other-keys)
  (values (progn ... some code ...)
          buffer))

Should now look like this:

(define-serializer (:foo (value foo-struct))
  ... some code ...)

(define-unserializer (:foo)
  ... some code ...)

And, when you find you need to add a new version of your :foo serializer but you don’t want to lose the old one, you can add:

(contextl:deflayer new-version)

(define-serializer (:foo (value foo-struct) :layer new-version)
  ... some new code ...)

Without the :buffer parameter everywhere, code that used to look like this:

(serialize* (:string aa :uint8 bb) :buffer buf)
(buffer-rewind :buffer buf)
(unserialize-slots* (:string name :uint8 age) object :buffer buf)

Should now look like this:

(with-buffer buf
  (serialize* :string aa :uint8 bb)
  (buffer-rewind)
  (unserialize-slots* object :string name :uint age))

There are now :int and :uint serializers that encode arbitrarily large integers and unsigned integers, respectively. There is also a serializer that copies a sequence of bytes as is without any prefix or suffix. To unserialize, you either have to provide a buffer of the appropriate length with the :output parameter or provide appropriate :start and :end keywords.

(serialize :raw-bytes uchar-array &key (start 0)
                                       (end (length uchar-array)))
(unserialize :raw-bytes &key output
                             (start 0)
                             (end (length output)))

And, if you have a serialize/unserialize pair for type :foo you can use the make-list-serializer macro to create a serialize/unserialize pair for a list of items that can be serialized with the :foo serializer.

(make-list-serializer :list-of-uint8 :uint8)
(serialize :list-of-int8 '(0 1 1 2 3 5 8 13 21 34 55 89))

At the USerial home page, you can find more complete documentation.

USerial — v0.7.2011.05.24 May 24th, 2011
Patrick Stein

I am releasing a new version of my USerial library. This version cleans up many messes from earlier releases. Unfortunately, in that process, it breaks compatibility with earlier releases.

Obtaining

Getting the USerial library:

Differences

The differences between this version and earlier versions of this library include:

  • Use of ContextL layered functions instead of CLOS methods
  • Elimination of :buffer parameter in favor of using the *buffer* special variable
  • Cleaning up macros which no longer required the :buffer parameter
  • Serializers for arbitrarily large integers and unsigned integers
  • Serializer for raw sequence of bytes
  • New make-list-serializer macro

By using ContextL layered functions, one has the ability to define a serializer and/or unserializer in a particular ContextL layer. This can be used to create new versions of the serializer without losing the ability to use the older version when required.

In the process, I have created macros to assist in creating completely custom serializers. This both streamlines their definition and should allow any future modifications to the USerial library to fly under the radar. Code that before looked like this:

(defmethod serialize ((key (eql :foo)) (value foo-struct)
                      &key (buffer userial:*buffer*) &allow-other-keys)
  ... some code ...
  buffer)

(defmethod unserialize ((key (eql :foo))
                        &key (buffer userial:*buffer*) &allow-other-keys)
  (values (progn ... some code ...)
          buffer))

Should now look like this:

(define-serializer (:foo (value foo-struct))
  ... some code ...)

(define-unserializer (:foo)
  ... some code ...)

And, when you find you need to add a new version of your :foo serializer but you don’t want to lose the old one, you can add:

(contextl:deflayer new-version)

(define-serializer (:foo (value foo-struct) :layer new-version)
  ... some new code ...)

Without the :buffer parameter everywhere, code that used to look like this:

(serialize* (:string aa :uint8 bb) :buffer buf)
(buffer-rewind :buffer buf)
(unserialize-slots* (:string name :uint8 age) object :buffer buf)

Should now look like this:

(with-buffer buf
  (serialize* :string aa :uint8 bb)
  (buffer-rewind)
  (unserialize-slots* object :string name :uint age))

There are now :int and :uint serializers that encode arbitrarily large integers and unsigned integers, respectively. There is also a serializer that copies a sequence of bytes as is without any prefix or suffix. To unserialize, you either have to provide a buffer of the appropriate length with the :output parameter or provide appropriate :start and :end keywords.

(serialize :raw-bytes uchar-array &key (start 0)
                                       (end (length uchar-array)))
(unserialize :raw-bytes &key output
                             (start 0)
                             (end (length output)))

And, if you have a serialize/unserialize pair for type :foo you can use the make-list-serializer macro to create a serialize/unserialize pair for a list of items that can be serialized with the :foo serializer.

(make-list-serializer :list-of-uint8 :uint8)
(serialize :list-of-int8 '(0 1 1 2 3 5 8 13 21 34 55 89))

At the USerial home page, you can find more complete documentation.

Method Versions — Retracted May 19th, 2011
Patrick Stein

When I wrote the method-versions library, I had read about ContextL and decided that I only wanted a very small subset of it and wanted it to be very easy to use. It turns out that I didn’t quite understand the complexity-level of ContextL. It is actually very similar to what I had wanted.

With my library, you set up some versions and a variable to track the current version:

(method-versions:define-method-version :v1.0)
(method-versions:define-method-version :v1.1 :v1.0)

(declaim (special *protocol-version*))
(defparameter *protocol-version* :v1.0)

In ContextL, you just set up some layers:

(contextl:deflayer :v1.0)
(contextl:deflayer :v1.1 (:v1.0))

In my library, you then set up a generic function that uses a special method combination that keys off of the special variable:

(defgeneric send-cmd (cmd)
  (:method-combination method-versions:method-versions-method-combination
                       *protocol-version*))

In ContextL, you declare a layered function:

(contextl:define-layered-function (cmd))

In my library, you then declare different methods using the version as a method qualifier.

(defmethod send-cmd ((cmd login-cmd))
  (send-string (login-name cmd))
  (send-string (login-password cmd)))

(defmethod send-cmd :v1.0 ((cmd login-cmd))
  (send-string (login-name cmd))
  (send-string (login-password cmd))
  (send-string (login-location cmd)))

In ContextL, you declare layered methods specifying which layer the functions belong to:

(contextl:define-layered-method send-cmd ((cmd login-cmd))
  (send-string (login-name cmd))
  (send-string (login-password cmd)))

(contextl:define-layered-method send-cmd :in :v1.0 ((cmd login-cmd))
  (send-string (login-name cmd))
  (send-string (login-password cmd))
  (send-string (login-location cmd)))

In my library, you set your special variable appropriately and invoke the method:

(let ((*protocol-version* :v1.1))
  (send-cmd cmd))

In ContextL, you declare which layer you want to be active when you go to invoke the method:

(contextl:with-active-layers (:v1.1)
  (send-cmd cmd))

My library does not let you specify other method qualifiers like :around or :after. ContextL does.

I am going to leave my library published because I think it is a reasonably understandable, yet non-trivial, use of non-standard method combinations. However, I am going to end up using ContextL for the projects that I had intended for my library.

Method Versions — v0.1.2011.05.18 May 18th, 2011
Patrick Stein

Edit: After re-reading some of the ContextL papers, I believe that I am actually just going to use ContextL as it’s a much more flexible superset of this library. I will probably still keep this library published as an example of a non-trivial, but glarkable, method combination.

I am releasing a new library that allows one to dispatch generic methods based on the value of a global parameter.

There are situations where one might like to dispatch a method on some information other than the required parameters of the method. For many situations, it is sufficient to switch between those methods based on some external parameter. The method-versions library allows one to do just that.

Obtaining

Internationalization Example

In this example, we do a silly form of internationalization. To that end, we will use English as the default language and define some other languages.

 (method-versions:define-method-version latin)
 (method-versions:define-method-version pig-latin)
 (method-versions:define-method-version french latin)
 (method-versions:define-method-version spanish latin)

We will prepare a language parameter and a welcome method that is versioned on the language.

 (declaim (special *language*))
 (defparameter *language* nil)
 
 (defgeneric welcome ()
   (:method-combination method-versions:method-version-method-combination
                        *language*))

And, we define welcome methods for the various languages (accidentally forgetting spanish).

 (defmethod welcome () :welcome)
 (defmethod welcome :latin     () :velkominum)
 (defmethod welcome :pig-latin () :elcomeway)
 (defmethod welcome :french    () :bonjour)

Then, we will try each of the languages in turn.

 (mapcar #'(lambda (ll)
             (let ((*language* ll))
               (welcome)))
         '(nil :latin :pig-latin :french :spanish))
 => (:welcome :velkominum :elcomeway :bonjour :velkominum)

USerial — v0.6.2011.05.12 May 12th, 2011
Patrick Stein

The latest release of my USerial library provides a way to make a simple serialize/unserialize pair for a list where every item can be serialized using the same key.

(make-list-serializer :list-of-integers :uint32)

(with-buffer (make-buffer)
  (serialize :list-of-integers '(1 2 3 4 5 6 7))
  (buffer-rewind)
  (reduce #'+ (unserialize :list-of-integers))) => 28

Here is the latest:

l