Книга: Practical Common Lisp

Making the Dream a Reality

Making the Dream a Reality

Okay, enough fantasizing about good-looking code; now you need to get to work writing define-binary-class—writing the code that will turn that concise expression of what an ID3 tag looks like into code that can represent one in memory, read one off disk, and write it back out.

To start with, you should define a package for this library. Here's the package file that comes with the version you can download from the book's Web site:

(in-package :cl-user)
(defpackage :com.gigamonkeys.binary-data
(:use :common-lisp :com.gigamonkeys.macro-utilities)
(:export :define-binary-class
:define-tagged-binary-class
:define-binary-type
:read-value
:write-value
:*in-progress-objects*
:parent-of-type
:current-binary-object
:+null+))

The COM.GIGAMONKEYS.MACRO-UTILITIES package contains the with-gensyms and once-only macros from Chapter 8.

Since you already have a handwritten version of the code you want to generate, it shouldn't be too hard to write such a macro. Just take it in small pieces, starting with a version of define-binary-class that generates just the DEFCLASS form.

If you look back at the define-binary-class form, you'll see that it takes two arguments, the name id3-tag and a list of slot specifiers, each of which is itself a two-item list. From those pieces you need to build the appropriate DEFCLASS form. Clearly, the biggest difference between the define-binary-class form and a proper DEFCLASS form is in the slot specifiers. A single slot specifier from define-binary-class looks something like this:

(major-version u1)

But that's not a legal slot specifier for a DEFCLASS. Instead, you need something like this:

(major-version :initarg :major-version :accessor major-version)

Easy enough. First define a simple function to translate a symbol to the corresponding keyword symbol.

(defun as-keyword (sym) (intern (string sym) :keyword))

Now define a function that takes a define-binary-class slot specifier and returns a DEFCLASS slot specifier.

(defun slot->defclass-slot (spec)
(let ((name (first spec)))
`(,name :initarg ,(as-keyword name) :accessor ,name)))

You can test this function at the REPL after switching to your new package with a call to IN-PACKAGE.

BINARY-DATA> (slot->defclass-slot '(major-version u1))
(MAJOR-VERSION :INITARG :MAJOR-VERSION :ACCESSOR MAJOR-VERSION)

Looks good. Now the first version of define-binary-class is trivial.

(defmacro define-binary-class (name slots)
`(defclass ,name ()
,(mapcar #'slot->defclass-slot slots)))

This is simple template-style macro—define-binary-class generates a DEFCLASS form by interpolating the name of the class and a list of slot specifiers constructed by applying slot->defclass-slot to each element of the list of slots specifiers from the define-binary-class form.

To see exactly what code this macro generates, you can evaluate this expression at the REPL.

(macroexpand-1 '(define-binary-class id3-tag
((identifier (iso-8859-1-string :length 3))
(major-version u1)
(revision u1)
(flags u1)
(size id3-tag-size)
(frames (id3-frames :tag-size size)))))

The result, slightly reformatted here for better readability, should look familiar since it's exactly the class definition you wrote by hand earlier:

(defclass id3-tag ()
((identifier :initarg :identifier :accessor identifier)
(major-version :initarg :major-version :accessor major-version)
(revision :initarg :revision :accessor revision)
(flags :initarg :flags :accessor flags)
(size :initarg :size :accessor size)
(frames :initarg :frames :accessor frames)))

Оглавление книги


Генерация: 6.481. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз