Git Product home page Git Product logo

cl-marshal's Introduction

cl-marshal

Simple and fast marshalling of Lisp datastructures. Convert any object into a string representation, put it on a stream an revive it from there. Only minimal changes required to make your CLOS objects serializable. Actually you only need to add 1 method per baseclass.

Just to make that clear: project is not dead - the functionality is simply finished and working.

License

MIT License. See included LICENSE file.

Usage

###Basic Usage

For this ASDF needs to be installed and has to have access to marshal.asd. Otherwise do a load *.lisp/tests-.lisp.

(require :marshal)

Serialization of simple examples:

$ (ms:marshal (list 1 2 3 "Foo" "Bar" (make-array '(3) :initial-contents '(a b c))))
--> (:PCODE 1
          (:LIST 1 1 2 3 (:SIMPLE-STRING 2 "Foo") (:SIMPLE-STRING 3 "Bar")
          (:ARRAY 4 (3) T (A B C))))

Deserialization:

$ (ms:unmarshal '(:PCODE 1
      (:LIST 1 1 2 3 (:SIMPLE-STRING 2 "Foo") (:SIMPLE-STRING 3 "Bar")
      (:ARRAY 4 (3) T (A B C)))))
--> (1 2 3 "Foo" "Bar" #(A B C))

That means that a

(ms:unmarshal (ms:marshal myobject))

returns a deep clone of myobject.

###Advanced Usage Definition of a class

 (defclass ship ()
   ((name :initform "" :initarg :name :accessor name)
    (dimensions :initform '(:width 0 :length 0) :initarg :dimensions :accessor dimensions)
    (course :initform 0 :initarg :course :accessor course)
    (cruise :initform 0 :initarg :cruise :accessor cruise) ; shall be transient
    (dinghy :initform NIL :initarg :dinghy :accessor dinghy :initarg :dinghy)) ; another ship -> ref
   (:documentation "A democlass. Some 'persistant slots', one transient.
  Some numbers, string, lists and object references."))


(defparameter ark (make-instance 'ship :name "Ark" :course 360
                            :dimensions '(:width 30 :length 90)))

Let's try to serialize this:

$ (ms:marshal ark)
--> (:PCODE 1 NIL)

Actually nothing happens.

Next we define the method class-persistant-slots for this class. The method hast to be defined in the package :marshal.

(defmethod ms:class-persistant-slots ((self ship))
  '(name dimensions course dinghy))
--> #<STANDARD-METHOD MARSHAL:CLASS-PERSISTANT-SLOTS (SHIP) {1002B16B31}

Note that the slot cruise is not listed. Therefore it will not be serialized.

$ (ms:marshal ark)
-->  (:PCODE 1
            (:OBJECT 1 SHIP (:SIMPLE-STRING 2 "Ark") (:LIST 3 :WIDTH 30 :LENGTH 90) 360
            (:LIST 4)))

Fine. Try a (ms:unmarshal (ms:marshal ark)) and you will get a clone of the object ark.

Let's define some subclasses (yes, it's Lisp, we use multiple inheritance here).

(defclass sailingship (ship)
  ((sailarea :initform 0 :initarg :sailarea :accessor sailarea))
  )

(defclass motorship (ship)
  ((enginepower :initform 0 :initarg :enginepower :accessor enginepower))
)

(defclass motorsailor (motorship sailingship)
  ()
)

Some instances:

 (defparameter ship2 (make-instance 'sailingship :name "Pinta" :course 270 :cruise 9
			   :dimensions '(:width 7 :length 21) :sailarea 400))
 (defparameter ship3 self) (make-instance 'motorship :name "Titanic" :course 320 :cruise 21
			   :dimensions '(:width 28 :length 269) :enginepower 51000))
 (defparameter ship4 (make-instance 'motorsailor  :name "Krusenstern" :course 180
			   :cruise 17.4 :dimensions '(:width 12 :length 82)
			   :sailarea 3400 :enginepower 2000))

Let's try

$ (ms:marshal ship4)
--> (:PCODE 1
           (:OBJECT 1 MOTORSAILOR (:SIMPLE-STRING 2 "Krusenstern")
               (:LIST 3 :WIDTH 12 :LENGTH 82) 180 (:LIST 4)))

Note that the slots to be marshalled are determined by the function ms:class-peristant-slots of the baseclass ship.

One last class and an instance. please note the backreference to another ship. That's a circular reference.

(defclass dinghy (ship)
  ((aboard :initform NIL :initarg :aboard :accessor aboard)) ; another ship -> circular ref
)

(defparameter ship5 (make-instance 'dinghy :name "Gig" :course 320 :cruise 5
			:dimensions '(:width 2 :length 6) :aboard ship4))
(setf (dinghy ship4) ship5)

$ (marshall ship4)
--> (:PCODE 1
           (:OBJECT 1 MOTORSAILOR (:SIMPLE-STRING 2 "Krusenstern")
              (:LIST 3 :WIDTH 12 :LENGTH 82) 180
              (:OBJECT 4 DINGHY (:SIMPLE-STRING 5 "Gig") (:LIST 6 :WIDTH 2 :LENGTH 6)
                320 (:LIST 7))))

We see the reference to the dhingy (the "sub ship"), but not the backreference. Simply, because so far the back link aboard is still transient.

(defmethod ms:class-persistant-slots ((self dinghy))
  (append (call-next-method) '(aboard)))

$ (marshall ship4)
--> (:PCODE 1
         (:OBJECT 1 MOTORSAILOR (:SIMPLE-STRING 2 "Krusenstern")
             (:LIST 3 :WIDTH 12 :LENGTH 82) 180
             (:OBJECT 4 DINGHY (:SIMPLE-STRING 5 "Gig") (:LIST 6 :WIDTH 2 :LENGTH 6)
                320 (:LIST 7) (:REFERENCE 1))))

Brilliant! References, circles et. are working regardless these are references from and to list, objects, hashtables, array etc.

Understanding the Implementation

Everything is in the package :marshal, nickname :ms.

To enable the serialization of a class, you need to specialise the method ms:class-persistant-slots for this class, or one of its baseclasses. This method must reside in the package :marshal! It has to return a list of slotnames. These slots will be serialized.

A call to ms:marshal on an object will generate the string (actually a sexp) representation. It will try to find the method ms:class-persistant-slots to see which slots hav to be serialized.

A call to ms:unmarshal with sexp generated by a ms:marshalwill revive an object.

You may have different implementations of classed to be serialized and deserialized. For examples different classes on the endpoints of a net work connections. Or simply diffenrent classes as time passes between the persistant storage of a serialization and its retrieval. It is important to understand that the classes that are serialized and the one of the object that will be deserialized need to have the same name and need to have the same slotnames as listed in ms:class-persistant-slots.

If you define a method ms:initialize-unmarshalled-instance for your class, then this method will be called in the end of the deserialization process. This gives you the chance to initialize transient slots, that were not serialized, or to do other initialization tricks.

There is a macro called coding idiom, that defines the language of the marshalling. The default vocabulary is quite verbose. In case you are going to send the objects through a network, you may want to change that to a shorter set of verbs. Well, I think there are betters way to speed that up, e.g. by adding a nginx proxy with automaic gzip compression in front of your lisp webserver. Anyway, you will find an alternative, shorter implementation in coding-idiom.lisp, it is fairly straight-forward.

Installation

The most simple and recommended way to install cl-marshal is by using Quicklisp. If you installed Quicklisp a simple

(ql:quickload :marshal)

will download the package and acually load it. You only need to do this once per machine. Later a

(require :marshal)

will be enough.

Alternatively you may get the code with:

git clone git://github.com/wlbr/cl-marshal.git

Either you add this to your asdf repository, then you will only need to do a (require :marshal) in your source.

Or, you may put the source in a subdirectory of your project and add the file marshal.asd wih its full path to your own asdf definition.

Or, you may put the source in a subdirectory of your project and load the file "marshal.asd" directly. After that a (asdf:load-system "marshal") should be sufficient.

Or, as a kind of worst case, you simply do a direct (load <file>) of the files package.lisp, coding-idiom.lisp, marshal.lisp, and unmarshal.lisp.

Dependencies

None except for asdf.

xlunit for the unit tests only (the tests are not included in the asdf system).

Testing

Tested with SBCL and CCL. No rocket science required, should run in any environment.

A set of unit tests is included in tests.lisp.

Reporting problems

If you run into any trouble or find bugs, please report them via the Github issue tracker.

History

First written as encode/decode for CLOS objects only during a diploma thesis in '95. Major rework/enhancements during a research project in the end of the '90s. Refactoring in 2011 when revisiting Lisp.

Contributors

Written by Michael Wolber. Major fixes and enhancements by Christoph Oechslein.

cl-marshal's People

Contributors

christophejunke avatar wlbr avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Forkers

softdev-ae

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.