usually keywords, and the reader binds *PACKAGE* to the KEYWORD package while reading feature expressions. Thus, a name with no package qualification will be read as a keyword symbol. So, you could write a function that behaves slightly differently in each of the implementations just mentioned like this:

(defun foo ()

#+allegro (do-one-thing)

#+sbcl (do-another-thing)

#+clisp (something-else)

#+cmu (yet-another-version)

#-(or allegro sbcl clisp cmu) (error 'Not implemented'))

In Allegro that code will be read as if it had been written like this:

(defun foo ()

(do-one-thing))

while in SBCL the reader will read this:

(defun foo ()

(do-another-thing))

while in an implementation other than one of the ones specifically conditionalized, it will read this:

(defun foo ()

(error 'Not implemented'))

Because the conditionalization happens in the reader, the compiler doesn't even see expressions that are skipped.[166] This means you pay no runtime cost for having different versions for different implementations. Also, when the reader skips conditionalized expressions, it doesn't bother interning symbols, so the skipped expressions can safely contain symbols from packages that may not exist in other implementations.

Packaging the Library

Speaking of packages, if you download the complete code for this library, you'll see that it's defined in a new package, com.gigamonkeys.pathnames. I'll discuss the details of defining and using packages in Chapter 21. For now you should note that some implementations provide their own packages that contain functions with some of the same names as the ones you'll define in this chapter and make those names available in the CL- USER package. Thus, if you try to define the functions from this library while in the CL-USER package, you may get errors or warnings about clobbering existing definitions. To avoid this possibility, you can create a file called packages.lisp with the following contents:

(in-package :cl-user)

(defpackage :com.gigamonkeys.pathnames

(:use :common-lisp)

(:export

:list-directory

:file-exists-p

:directory-pathname-p

:file-pathname-p

:pathname-as-directory

:pathname-as-file

:walk-directory

:directory-p

:file-p))

and LOAD it. Then at the REPL or at the top of the file where you type the definitions from this chapter, type the following expression:

(in-package :com.gigamonkeys.pathnames)

In addition to avoiding name conflicts with symbols already available in CL-USER, packaging the library this way also makes it easier to use in other code, as you'll see in several future chapters.

Listing a Directory

You can implement the function for listing a single directory, list-directory, as a thin wrapper around the standard function DIRECTORY. DIRECTORY takes a special kind of pathname, called a wild pathname, that has one or more components containing the special value :wild and returns a list of pathnames representing files in the file system that match the wild pathname.[167] The matching algorithm—like most things having to do with the interaction between Lisp and a particular file system—isn't defined by the language standard, but most implementations on Unix and Windows follow the same basic scheme.

The DIRECTORY function has two problems that you need to address with list-directory. The main one is that certain aspects of its behavior differ fairly significantly between different Common Lisp implementations, even on the same operating system. The other is that while DIRECTORY provides a powerful interface for listing files, to use it properly requires understanding some rather subtle points about the pathname abstraction. Between these subtleties and the idiosyncrasies of different implementations, actually writing portable code that uses DIRECTORY to do something as simple as listing all the files and subdirectories in a single directory can be a frustrating experience. You can deal with those subtleties and idiosyncrasies once and for all, by writing list-directory, and forget them thereafter.

One subtlety I discussed in Chapter 14 is the two ways to represent the name of a directory as a pathname: directory form and file form.

To get DIRECTORY to return a list of files in /home/peter/, you need to pass it a wild pathname whose directory component is the directory you want to list and whose name and type components are :wild. Thus, to get a listing of the files in /home/peter/, it might seem you could write this:

(directory (make-pathname :name :wild :type :wild :defaults home-dir))

where home-dir is a pathname representing /home/peter/. This would work if home-dir were in directory form. But if it were in file form—for example, if it had been created by parsing the namestring '/home/peter'—then that same expression would list all the files in /home since the name component 'peter' would be replaced with :wild.

To avoid having to worry about explicitly converting between representations, you can define list- directory to accept a nonwild pathname in either form, which it will then convert to the appropriate wild pathname.

To help with this, you should define a few helper functions. One, component-present-p, will test whether a given component of a pathname is 'present,' meaning neither NIL nor the special value :unspecific.[168] Another, directory-pathname-p, tests whether a pathname is already in directory form, and the third, pathname-as-directory, converts any pathname to a directory form pathname.

(defun component-present-p (value)

(and value (not (eql value :unspecific))))

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату