1 ;;;; machine/filesystem-independent pathname functions
3 ;;;; This software is part of the SBCL system. See the README file for
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
12 (in-package "SB-IMPL")
14 (defstruct (logical-host
17 (lambda (logical-host stream
)
18 (print-unreadable-object (logical-host stream
:type t
)
19 (prin1 (logical-host-name logical-host
) stream
))))
21 (parse #'parse-logical-namestring
)
24 (error "called PARSE-NATIVE-NAMESTRING using a ~
25 logical host: ~S" (first x
))))
26 (unparse #'unparse-logical-namestring
)
29 (error "called NATIVE-NAMESTRING using a ~
30 logical host: ~S" (first x
))))
33 (logical-host-name (%pathname-host x
))))
34 (unparse-directory #'unparse-logical-directory
)
35 (unparse-file #'unparse-logical-file
)
36 (unparse-enough #'unparse-enough-namestring
)
37 (unparse-directory-separator ";")
38 (simplify-namestring #'identity
)
39 (customary-case :upper
)))
40 (name-hash 0 :type fixnum
)
41 (name "" :type simple-string
:read-only t
)
42 (translations nil
:type list
)
43 (canon-transls nil
:type list
))
45 ;;; Logical pathnames have the following format:
47 ;;; logical-namestring ::=
48 ;;; [host ":"] [";"] {directory ";"}* [name] ["." type ["." version]]
51 ;;; directory ::= word | wildcard-word | **
52 ;;; name ::= word | wildcard-word
53 ;;; type ::= word | wildcard-word
54 ;;; version ::= pos-int | newest | NEWEST | *
55 ;;; word ::= {uppercase-letter | digit | -}+
56 ;;; wildcard-word ::= [word] '* {word '*}* [word]
57 ;;; pos-int ::= integer > 0
59 ;;; Physical pathnames include all these slots and a device slot.
61 ;;; We can't freeze HOST because later on we define either UNIX-HOST or WIN32-HOST.
62 (declaim (freeze-type logical-host
))
66 (deftype absent-pathname-component
()
67 '(member nil
:unspecific
))
69 (defun make-pattern (pieces)
70 ;; Ensure that the hash will meet the SXASH persistence requirement:
71 ;; "2. For any two objects, x and y, both of which are ... pathnames ... and which are similar,
72 ;; (sxhash x) and (sxhash y) yield the same mathematical value even if x and y exist in
73 ;; different Lisp images of the same implementation."
74 ;; Specifically, hashes that depend on object identity (address) are impermissible.
75 (dolist (piece pieces
)
76 (aver (typep piece
'(or string symbol
(cons (eql :character-set
) string
)))))
77 (%make-pattern
(pathname-sxhash pieces
) pieces
))
79 (declaim (inline pathname-component-present-p
))
80 (defun pathname-component-present-p (component)
81 (not (typep component
'absent-pathname-component
)))
83 ;;; The following functions are used both for Unix and Windows: while
84 ;;; we accept both \ and / as directory separators on Windows, we
85 ;;; print our own always with /, which is much less confusing what
86 ;;; with being \ needing to be escaped.
87 (defun unparse-physical-directory (pathname escape-char
)
88 (declare (pathname pathname
))
89 (unparse-physical-directory-list (%pathname-directory pathname
) escape-char
))
91 (defun unparse-physical-directory-list (directory escape-char
)
92 (declare (list directory
))
95 (ecase (pop directory
)
97 (let ((next (pop directory
)))
98 (cond ((eq :home next
)
100 ((and (consp next
) (eq :home
(car next
)))
102 (pieces (second next
)))
104 (plusp (length next
))
105 (char= #\~
(char next
0)))
106 ;; The only place we need to escape the tilde.
110 (push next directory
)))
113 (dolist (dir directory
)
118 (error ":BACK cannot be represented in namestrings."))
119 ((member :wild-inferiors
)
121 ((or simple-string pattern
(member :wild
))
122 (pieces (unparse-physical-piece dir escape-char
))
125 (error "invalid directory component: ~S" dir
)))))
126 (apply #'concatenate
'simple-string
(pieces))))
128 (defun unparse-physical-file (pathname escape-char
)
129 (declare (type pathname pathname
))
130 (let ((name (%pathname-name pathname
))
131 (type (%pathname-type pathname
)))
132 (collect ((fragments))
133 ;; Note: by ANSI 19.3.1.1.5, we ignore the version slot when
134 ;; translating logical pathnames to a filesystem without
135 ;; versions (like Unix and Win32).
137 (when (and (typep name
'string
)
140 pathname
"the ~S component ~S is of length 0" :name name
))
141 (fragments (unparse-physical-piece
143 :escape-dot
(when (null type
) :unless-at-start
))))
144 (when (pathname-component-present-p type
)
148 "there is a ~S component but no ~S component" :type
:name
))
150 (fragments (unparse-physical-piece
151 type escape-char
:escape-dot t
)))
152 (apply #'concatenate
'simple-string
(fragments)))))
154 (defun unparse-native-physical-file (pathname)
155 (let ((name (pathname-name pathname
))
156 (type (pathname-type pathname
)))
157 (collect ((fragments))
159 ((pathname-component-present-p name
)
160 (unless (stringp name
) ; some kind of wild field
161 (no-native-namestring-error
162 pathname
"of the ~S component ~S." :name name
))
164 (when (pathname-component-present-p type
)
165 (unless (stringp type
) ; some kind of wild field
166 (no-native-namestring-error
167 pathname
"of the ~S component ~S" :type type
))
170 ((pathname-component-present-p type
) ; type without a name
171 (no-native-namestring-error
173 "there is a ~S component but no ~S component" :type
:name
)))
174 (apply #'concatenate
'simple-string
(fragments)))))
176 (defun unparse-physical-enough (pathname defaults escape-char
)
177 (declare (type pathname pathname defaults
))
179 (error "~S cannot be represented relative to ~S."
182 (let* ((pathname-directory (%pathname-directory pathname
))
183 (defaults-directory (%pathname-directory defaults
))
184 (prefix-len (length defaults-directory
))
186 (cond ((null pathname-directory
) '(:relative
))
187 ((eq (car pathname-directory
) :relative
)
189 ((and (> prefix-len
0)
190 (>= (length pathname-directory
) prefix-len
)
191 (compare-component (subseq pathname-directory
194 ;; Pathname starts with a prefix of default. So
195 ;; just use a relative directory from then on out.
196 (cons :relative
(nthcdr prefix-len pathname-directory
)))
197 ((eq (car pathname-directory
) :absolute
)
198 ;; We are an absolute pathname, so we can just use it.
201 (bug "Bad fallthrough in ~S" 'unparse-physical-enough
)))))
202 (strings (unparse-physical-directory-list result-directory escape-char
)))
203 (let* ((pathname-type (%pathname-type pathname
))
204 (type-needed (pathname-component-present-p pathname-type
))
205 (pathname-name (%pathname-name pathname
))
206 (name-needed (or type-needed
208 (not (compare-component pathname-name
212 (unless pathname-name
(lose))
213 (strings (unparse-physical-piece
214 pathname-name escape-char
215 :escape-dot
(when (not pathname-type
) :unless-at-start
))))
217 (unless (pathname-component-present-p pathname-type
)
220 (strings (unparse-physical-piece pathname-type
221 escape-char
:escape-dot t
))))
222 (apply #'concatenate
'simple-string
(strings)))))
225 ;;; To be initialized in unix/win32-pathname.lisp
226 (define-load-time-global *physical-host
* nil
)
228 ;;; Return a value suitable, e.g., for preinitializing
229 ;;; *DEFAULT-PATHNAME-DEFAULTS* before *DEFAULT-PATHNAME-DEFAULTS* is
230 ;;; initialized (at which time we can't safely call e.g. #'PATHNAME).
231 (defun make-trivial-default-pathname ()
232 (intern-pathname *physical-host
* nil nil nil nil
:newest
))
236 (defmethod print-object ((pathname pathname
) stream
)
237 (let ((namestring (handler-case (namestring pathname
)
241 (if (or *print-readably
* *print-escape
*)
244 (coerce namestring
'(simple-array character
(*))))
245 (print-unreadable-object (pathname stream
:type t
)
247 "~@<(with no namestring) ~_:HOST ~S ~_:DEVICE ~S ~_:DIRECTORY ~S ~
248 ~_:NAME ~S ~_:TYPE ~S ~_:VERSION ~S~:>"
249 (%pathname-host pathname
)
250 (%pathname-device pathname
)
251 (%pathname-directory pathname
)
252 (%pathname-name pathname
)
253 (%pathname-type pathname
)
254 (%pathname-version pathname
))))))
256 ;;; Pathnames are stored in an open-addressing weak hash-set.
257 ;;; Ideally there would be only one internal representation of any pathname,
258 ;;; so that EQUAL on pathnames could reduce to EQ.
259 ;;; I'm not sure that's possible. For the time being, we use a comparator
260 ;;; that is stricter (I think) than EQUAL.
262 ;;; The spec is actually extremely underspecified in regard to the meaning of
263 ;;; "pathnames that are equal should be functionally equivalent."
265 ;;; (equal (make-pathname :name "a" :version nil) (make-pathname :name "a" :version :newest))
266 ;;; shows that EQUAL is inconsistent in terms of what "functionally equivalent" means:
267 ;;; SBCL, ABCL, and CCL => T
268 ;;; CLISP and ECL => NIL
269 ;;; Also, on case-sensitive-case-preserving filesystems it's not possible
270 ;;; to know which pathnames are equivalent without asking the filesystem.
272 ;;: TODO: consider similarly interning the DEVICE and TYPE parts
273 (define-load-time-global *pn-dir-table
* nil
)
274 (define-load-time-global *pn-table
* nil
)
275 (declaim (type robinhood-hashset
*pn-dir-table
* *pn-table
*))
277 (defmacro compare-pathname-host
/dev
/dir
/name
/type
(a b
)
278 `(and (eq (%pathname-host
,a
) (%pathname-host
,b
)) ; Interned
279 ;; dir+hash are EQ-comparable thanks to INTERN-PATHNAME
280 (eq (%pathname-dir
+hash
,a
) (%pathname-dir
+hash
,b
))
281 ;; the pathname pieces which are strings aren't interned
282 (compare-component (%pathname-device
,a
) (%pathname-device
,b
))
283 (compare-component (%pathname-name
,a
) (%pathname-name
,b
))
284 (compare-component (%pathname-type
,a
) (%pathname-type
,b
))))
286 (defun pn-table-dir= (entry key
)
287 (or (eq (car entry
) (car key
)) ; quick win if lists are EQ
288 (and (eq (cdr entry
) (cdr key
)) ; hashes match
289 (compare-component (car entry
) (car key
)))))
290 (defun pn-table-hash (pathname)
291 ;; The pathname table makes distinctions between pathnames that EQUAL does not.
292 (mix (sxhash (%pathname-version pathname
))
293 (pathname-sxhash pathname
)))
294 (defun pn-table-pn= (entry key
)
295 (and (compare-pathname-host/dev
/dir
/name
/type entry key
)
296 (eql (%pathname-version entry
) (%pathname-version key
))))
298 (defun !pathname-cold-init
()
299 (setq *pn-dir-table
* (make-hashset 32 #'pn-table-dir
= #'cdr
300 :synchronized t
:weakness t
)
301 *pn-table
* (make-hashset 32 #'pn-table-pn
= #'pn-table-hash
302 :synchronized t
:weakness t
)))
304 ;;; A pathname is logical if the host component is a logical host.
305 ;;; This constructor is used to make an instance of the correct type
306 ;;; from parsed arguments.
307 (defun intern-pathname (host device directory name type version
)
308 ;; We canonicalize logical pathname components to uppercase. ANSI
309 ;; doesn't strictly require this, leaving it up to the implementor;
310 ;; but the arguments given in the X3J13 cleanup issue
311 ;; PATHNAME-LOGICAL:ADD seem compelling: we should canonicalize the
312 ;; case, and uppercase is the ordinary way to do that.
313 (declare (sb-c::tlab
:system
))
314 (flet ((upcase-maybe (x) (typecase x
(string (logical-word-or-lose x
)) (t x
))))
315 (when (typep host
'logical-host
)
316 (setq device
:unspecific
317 directory
(mapcar #'upcase-maybe directory
)
318 name
(upcase-maybe name
)
319 type
(upcase-maybe type
))))
320 (dx-let ((dir-key (cons directory
(pathname-sxhash directory
))))
321 (declare (inline !allocate-pathname
)) ; for DX-allocation
322 (flet ((ensure-heap-string (part) ; return any non-string as-is
323 ;; FIXME: what about pattern pieces and (:HOME "user") ?
324 (cond ((or (not (stringp part
)) (read-only-space-obj-p part
)) part
)
325 ;; Altering a string that is any piece of any arg to INTERN-PATHNAME
326 ;; is a surefire way to corrupt the hashset, so *always* copy the input.
327 ;; Users might not have reason to believe that once a string is passed
328 ;; to any pathname function, it is immutable. We can only hope that
329 ;; they don't mutate strings returned by pathname accessors.
330 (t (let ((l (length part
)))
331 (logically-readonlyize
332 (replace (typecase part
333 (base-string (make-string l
:element-type
'base-char
))
337 (if directory
; find the interned dir-key
338 (hashset-insert-if-absent
339 *pn-dir-table
* dir-key
341 (cons (mapcar #'ensure-heap-string
(car dir
)) (cdr dir
))))))
342 (pn-key (!allocate-pathname host device dir
+hash name type version
)))
343 (declare (dynamic-extent pn-key
))
344 (hashset-insert-if-absent
346 (lambda (tmp &aux
(host (%pathname-host tmp
)))
347 (let ((new (!allocate-pathname
348 host
(%pathname-device tmp
)
349 (%pathname-dir
+hash tmp
)
350 (ensure-heap-string (%pathname-name tmp
))
351 (ensure-heap-string (%pathname-type tmp
))
352 (%pathname-version tmp
))))
353 (when (typep host
'logical-host
)
354 (setf (%instance-layout new
) #.
(find-layout 'logical-pathname
)))
357 ;;; Weak vectors don't work at all once rendered pseudo-static.
358 ;;; so in order to weaken the pathname cache, the vectors are copied on restart.
359 ;;; It may not achieve anything for saved pathnames, since the vector elements
360 ;;; are themselves pseudo-static, but at least newly made ones aren't immortal.
361 (defun rebuild-pathname-cache ()
362 (hashset-rehash *pn-dir-table
* nil
)
363 (hashset-rehash *pn-table
* nil
))
365 (defun show-pn-cache (&aux
(*print-pretty
* nil
) (*package
* (find-package "CL-USER")))
366 (dolist (symbol '(*pn-dir-table
* *pn-table
*))
367 (let* ((hashset (symbol-value symbol
))
368 (v (hss-cells (hashset-storage hashset
)))
369 (n (hs-cells-capacity v
)))
370 (multiple-value-bind (live tombstones unused
) (hs-cells-occupancy v n
)
371 (declare (ignore live
))
372 (format t
"~&~S: size=~D tombstones=~D unused=~D~%" symbol n tombstones unused
))
374 (let ((entry (hs-cell-ref v i
)))
375 (unless (member entry
'(nil 0))
376 (format t
"~4d ~3d ~x " i
(generation-of entry
) (get-lisp-obj-address entry
))
377 (if (eq symbol
'*pn-dir-table
*)
378 (format t
"~16x ~s~%" (cdr entry
) (car entry
))
379 (flet ((index-of (pn-dir)
380 (let ((dirs (hss-cells (hashset-storage *pn-dir-table
*))))
381 (dotimes (i (weak-vector-len dirs
))
382 (when (eq pn-dir
(weak-vector-ref dirs i
))
385 "~16x [~A ~S ~A ~S ~S ~S]~%"
386 (pathname-sxhash entry
)
387 (let ((host (%pathname-host entry
)))
388 (cond ((logical-host-p host
)
389 ;; display with string quotes around name
390 (prin1-to-string (logical-host-name host
)))
391 ((eq host
*physical-host
*) "phys")
393 (%pathname-device entry
)
394 (acond ((%pathname-dir
+hash entry
) (format nil
"@~D" (index-of it
)))
396 (%pathname-name entry
)
397 (%pathname-type entry
)
398 (%pathname-version entry
))))))))))
400 ;;; Vector of logical host objects, each of which contains its translations.
401 ;;; The vector is never mutated- always a new vector is created when adding
402 ;;; translations for a new host. So nothing needs locking.
403 ;;; And the fact that hosts are never deleted keeps things really simple.
404 (define-load-time-global *logical-hosts
* #())
405 (declaim (simple-vector *logical-hosts
*))
409 (defmethod print-object ((pattern pattern
) stream
)
410 (print-unreadable-object (pattern stream
:type t
)
412 (let ((*print-escape
* t
))
413 (pprint-fill stream
(pattern-pieces pattern
) nil
))
414 (prin1 (pattern-pieces pattern
) stream
))))
416 (defun pattern= (pattern1 pattern2
)
417 (declare (type pattern pattern1 pattern2
))
418 (let ((pieces1 (pattern-pieces pattern1
))
419 (pieces2 (pattern-pieces pattern2
)))
420 (and (= (length pieces1
) (length pieces2
))
421 (every (lambda (piece1 piece2
)
424 (and (simple-string-p piece2
)
425 (string= piece1 piece2
)))
428 (eq (car piece1
) (car piece2
))
429 (string= (cdr piece1
) (cdr piece2
))))
431 (eq piece1 piece2
))))
435 ;;; If the string matches the pattern returns the multiple values T
436 ;;; and a list of the matched strings.
437 (defun pattern-matches (pattern string
)
438 (declare (type pattern pattern
)
439 (type simple-string string
))
440 (let ((len (length string
)))
441 (labels ((maybe-prepend (subs cur-sub chars
)
443 (let* ((len (length chars
))
444 (new (make-string len
))
447 (setf (schar new
(decf index
)) char
))
450 (matches (pieces start subs cur-sub chars
)
453 (values t
(maybe-prepend subs cur-sub chars
))
455 (let ((piece (car pieces
)))
458 (let ((end (+ start
(length piece
))))
460 (string= piece string
461 :start2 start
:end2 end
)
462 (matches (cdr pieces
) end
463 (maybe-prepend subs cur-sub chars
)
469 (let ((char (schar string start
)))
470 (if (find char
(cdr piece
) :test
#'char
=)
471 (matches (cdr pieces
) (1+ start
) subs t
472 (cons char chars
))))))))
473 ((member :single-char-wild
)
475 (matches (cdr pieces
) (1+ start
) subs t
476 (cons (schar string start
) chars
))))
477 ((member :multi-char-wild
)
478 (multiple-value-bind (won new-subs
)
479 (matches (cdr pieces
) start subs t chars
)
483 (matches pieces
(1+ start
) subs t
484 (cons (schar string start
)
486 (multiple-value-bind (won subs
)
487 (matches (pattern-pieces pattern
) 0 nil nil nil
)
488 (values won
(reverse subs
))))))
490 ;;; PATHNAME-MATCH-P for directory components
491 (defun directory-components-match (thing wild
)
494 ;; If THING has a null directory, assume that it matches
495 ;; (:ABSOLUTE :WILD-INFERIORS) or (:RELATIVE :WILD-INFERIORS).
498 (member (first wild
) '(:absolute
:relative
))
499 (eq (second wild
) :wild-inferiors
))
501 (let ((wild1 (first wild
)))
502 (if (eq wild1
:wild-inferiors
)
503 (let ((wild-subdirs (rest wild
)))
504 (or (null wild-subdirs
)
506 (when (directory-components-match thing wild-subdirs
)
509 (unless thing
(return nil
)))))
511 (components-match (first thing
) wild1
)
512 (directory-components-match (rest thing
)
515 ;;; Return true if pathname component THING is matched by WILD. (not
517 (defun components-match (thing wild
)
518 (declare (type (or pattern symbol simple-string integer
) thing wild
))
523 ;; String is matched by itself, a matching pattern or :WILD.
526 (values (pattern-matches wild thing
)))
528 (string= thing wild
))))
530 ;; A pattern is only matched by an identical pattern.
531 (and (pattern-p wild
) (pattern= thing wild
)))
533 ;; An integer (version number) is matched by :WILD or the
534 ;; same integer. This branch will actually always be NIL as
535 ;; long as the version is a fixnum.
538 ;;; a predicate for comparing two pathname slot component sub-entries
539 (defun compare-component (this that
)
543 (and (simple-string-p that
)
544 (string= this that
)))
546 ;; PATTERN instances should probably become interned objects
547 ;; so that we can use EQ on them.
548 (and (pattern-p that
)
549 (pattern= this that
)))
551 ;; Even though directory parts are now reliably interned -
552 ;; and so you might be inclined to think that the "full" comparison
553 ;; could be confined to just the interning operation, that's not so,
554 ;; because we also use COMPARE-COMPONENT in ENOUGH-NAMESTRING.
556 (compare-component (car this
) (car that
))
557 (compare-component (cdr this
) (cdr that
))))
561 ;;;; pathname functions
563 (defun pathname= (a b
)
564 (declare (type pathname a b
))
566 (and (compare-pathname-host/dev
/dir
/name
/type a b
)
567 (or (eq (%pathname-host a
) *physical-host
*)
568 (compare-component (pathname-version a
)
569 (pathname-version b
))))))
571 (sb-kernel::assign-equalp-impl
'pathname
#'pathname
=)
572 (sb-kernel::assign-equalp-impl
'logical-pathname
#'pathname
=)
574 ;;; Hash a PATHNAME or a PATHNAME-DIRECTORY or pieces of a PATTERN.
575 ;;; This is called by both SXHASH and by the interning of pathnames, which uses a
576 ;;; multi-step approaching to coalescing shared subparts.
577 ;;; If an EQUAL directory was used before, we share that.
578 ;;; Since a directory is stored with its hash precomputed, hashing a PATHNAME as a
579 ;;; whole entails at most 4 more MIX operations. So using pathnames as keys in
580 ;;; a hash-table pays a small up-front price for later speed improvement.
581 (defun pathname-sxhash (x)
586 (let ((res (length piece
)))
587 (if (<= res
6) ; hash it more thoroughly than (SXHASH string)
588 (dovector (ch piece res
)
589 (setf res
(mix (murmur-hash-word/+fixnum
(char-code ch
)) res
)))
591 (symbol (symbol-name-hash piece
))
592 (pattern (pattern-hash piece
))
593 ;; next case is only for MAKE-PATTERN
594 ((cons (eql :character-set
)) (hash-piece (the string
(cdr piece
))))
595 ((cons (eql :home
) (cons string null
))
596 ;; :HOME has two representations- one is just '(:absolute :home ...)
597 ;; and the other '(:absolute (:home "user") ...)
598 (sxhash (second piece
))))))
601 (let* ((host (%pathname-host x
))
602 ;; NAME-HASH is based on SXHASH of a string
603 (hash (if (typep host
'logical-host
) (logical-host-name-hash host
) 0)))
604 (mixf hash
(hash-piece (%pathname-device x
))) ; surely stringlike, right?
605 (awhen (%pathname-dir
+hash x
) (mixf hash
(cdr it
)))
606 (mixf hash
(hash-piece (%pathname-name x
)))
607 (mixf hash
(hash-piece (%pathname-type x
)))
608 ;; The requirement NOT to mix the version into the resulting hash is mandated
609 ;; by bullet point 1 in the SXHASH specification:
610 ;; (equal x y) implies (= (sxhash x) (sxhash y))
611 ;; and the observation that in this implementation of Lisp:
612 ;; (equal (make-pathname :version 1) (make-pathname :version 15)) => T
614 (list ;; a directory, or the PIECES argument to MAKE-PATTERN
616 (dolist (piece x hash
)
617 (mixf hash
(hash-piece piece
))))))))
619 ;;; Convert PATHNAME-DESIGNATOR (a pathname, or string, or
620 ;;; stream), into a pathname in PATHNAME.
621 (defmacro with-pathname
((pathname pathname-designator
) &body body
)
622 (once-only ((pathname-designator pathname-designator
))
623 `(let ((,pathname
(etypecase ,pathname-designator
624 (pathname ,pathname-designator
)
625 (string (parse-namestring ,pathname-designator
))
626 ((or file-stream synonym-stream
)
627 (stream-file-name-or-lose ,pathname-designator
)))))
630 (defmacro with-native-pathname
((pathname pathname-designator
) &body body
)
631 (once-only ((pathname-designator pathname-designator
))
632 `(let ((,pathname
(etypecase ,pathname-designator
633 (pathname ,pathname-designator
)
634 (string (parse-native-namestring ,pathname-designator
))
637 (file-stream (file-name ,pathname-designator
)))))
640 (defmacro with-host
((host host-designator
) &body body
)
641 ;; Generally, redundant specification of information in software,
642 ;; whether in code or in comments, is bad. However, the ANSI spec
643 ;; for this is messy enough that it's hard to hold in short-term
644 ;; memory, so I've recorded these redundant notes on the
645 ;; implications of the ANSI spec.
647 ;; According to the ANSI spec, HOST can be a valid pathname host, or
648 ;; a logical host, or NIL.
650 ;; A valid pathname host can be a valid physical pathname host or a
651 ;; valid logical pathname host.
653 ;; A valid physical pathname host is "any of a string, a list of
654 ;; strings, or the symbol :UNSPECIFIC, that is recognized by the
655 ;; implementation as the name of a host". In SBCL as of 0.6.9.8,
656 ;; that means :UNSPECIFIC: though someday we might want to
657 ;; generalize it to allow strings like "RTFM.MIT.EDU" or lists like
658 ;; '("RTFM" "MIT" "EDU"), that's not supported now.
660 ;; A valid logical pathname host is a string which has been defined as
661 ;; the name of a logical host, as with LOAD-LOGICAL-PATHNAME-TRANSLATIONS.
663 ;; A logical host is an object of implementation-dependent nature. In
664 ;; SBCL, it's a member of the HOST class (a subclass of STRUCTURE-OBJECT).
665 (once-only ((host-designator host-designator
))
666 `(let ((,host
(etypecase ,host-designator
668 ;; This is a special host. It's not valid as a
669 ;; logical host, so it is a sensible thing to
670 ;; designate the physical host object. So we do
674 ;; In general ANSI-compliant Common Lisps, a
675 ;; string might also be a physical pathname
676 ;; host, but ANSI leaves this up to the
677 ;; implementor, and in SBCL we don't do it, so
678 ;; it must be a logical host.
679 (find-logical-host ,host-designator
))
680 (absent-pathname-component
681 ;; CLHS says that HOST=:UNSPECIFIC has
682 ;; implementation-defined behavior. We
683 ;; just turn it into NIL.
686 ;; ANSI also allows LISTs to designate hosts,
687 ;; but leaves its interpretation
688 ;; implementation-defined. Our interpretation
689 ;; is that it's unsupported.:-|
690 (error "A LIST representing a pathname host is not ~
691 supported in this implementation:~% ~S"
693 (host ,host-designator
))))
696 (defun find-host (host-designator &optional
(errorp t
))
697 (with-host (host host-designator
)
698 (when (and errorp
(not host
))
699 (error "Couldn't find host: ~S" host-designator
))
702 (defun pathname (pathspec)
703 "Convert PATHSPEC (a pathname designator) into a pathname."
704 (declare (type pathname-designator pathspec
))
705 (with-pathname (pathname pathspec
)
708 (defun native-pathname (pathspec)
709 "Convert PATHSPEC (a pathname designator) into a pathname, assuming
710 the operating system native pathname conventions."
711 (with-native-pathname (pathname pathspec
)
714 ;;; Recursively (e.g. for the directory component) change the case of
715 ;;; the pathname component THING.
716 (declaim (ftype (sfunction ((or symbol integer string pattern list
))
717 (or symbol integer string pattern list
))
719 (defun diddle-case (thing)
720 (labels ((check-for (pred in
)
723 (some (lambda (piece)
726 (check-for pred piece
))
727 ((cons (eql :character-set
))
728 (check-for pred
(cdr piece
)))))
729 (pattern-pieces in
)))
732 (diddle-with (fun thing
)
736 (mapcar (lambda (piece)
740 ((cons (eql :character-set
))
741 (funcall fun
(cdr piece
)))
744 (pattern-pieces thing
))))
749 (maybe-diddle-part (thing)
751 (mapcar #'maybe-diddle-part thing
)
752 (let ((any-uppers (check-for #'upper-case-p thing
))
753 (any-lowers (check-for #'lower-case-p thing
)))
754 (cond ((and any-uppers any-lowers
) ; mixed case, stays the same
756 (any-uppers ; all uppercase, becomes all lower case
757 (diddle-with 'string-downcase thing
))
758 (any-lowers ; all lowercase, becomes all upper case
759 (diddle-with 'string-upcase thing
))
760 (t ; no letters? I guess just leave it.
762 (if (not (or (symbolp thing
) (integerp thing
)))
763 (maybe-diddle-part thing
)
766 (declaim (inline maybe-diddle-case
))
767 (defun maybe-diddle-case (thing diddle-p
)
772 (defun merge-directories (dir1 dir2 diddle-case
)
773 (if (or (eq (car dir1
) :absolute
)
778 (if (and (eq dir
:back
)
780 (typep (car results
) '(or string pattern
781 (member :wild
:wild-inferiors
))))
783 (push dir results
))))
784 (dolist (dir (maybe-diddle-case dir2 diddle-case
))
786 (dolist (dir (cdr dir1
))
790 (defun merge-pathnames (pathname
792 (defaults *default-pathname-defaults
*)
793 (default-version :newest
))
794 "Construct a filled in pathname by completing the unspecified components
796 (declare (type pathname-designator pathname
)
797 (type pathname-designator defaults
)
799 (with-pathname (defaults defaults
)
800 (let* ((pathname (let ((*default-pathname-defaults
* defaults
))
801 (pathname pathname
)))
802 (default-host (%pathname-host defaults
))
803 (pathname-host (%pathname-host pathname
))
805 (and default-host pathname-host
806 (not (eq (host-customary-case default-host
)
807 (host-customary-case pathname-host
)))))
808 (directory (merge-directories (%pathname-directory pathname
)
809 (%pathname-directory defaults
)
811 (macrolet ((merged-component (component)
812 `(or (,component pathname
)
813 (let ((default (,component defaults
)))
815 (diddle-case default
)
818 (or pathname-host default-host
)
819 ;; The device of ~/ shouldn't be merged, because the
820 ;; expansion may have a different device
821 (unless (typep directory
'(cons (eql :absolute
) (cons (eql :home
))))
822 (merged-component %pathname-device
))
824 (merged-component %pathname-name
)
825 (merged-component %pathname-type
)
826 (or (%pathname-version pathname
)
827 (and (not (%pathname-name pathname
)) (%pathname-version defaults
))
828 default-version
))))))
830 (defun import-directory (directory diddle-case
)
833 ((member :wild
) '(:absolute
:wild-inferiors
))
834 ((member :unspecific
) '(:relative
))
836 (let ((root (pop directory
))
838 (if (member root
'(:relative
:absolute
))
840 (error "List of directory components must start with ~S or ~S."
841 :absolute
:relative
))
843 (let ((next (car directory
)))
844 (when (or (eq :home next
)
845 (typep next
'(cons (eql :home
) (cons string null
))))
846 (push (pop directory
) results
)))
847 (dolist (piece directory
)
849 ((member :wild
:wild-inferiors
:up
)
850 (push piece results
))
852 (if (typep (car results
) '(or string pattern
853 (member :wild
:wild-inferiors
)))
855 (push piece results
)))
857 (when (typep piece
'(and string
(not simple-array
)))
858 (setq piece
(coerce piece
'simple-string
)))
859 ;; Unix namestrings allow embedded "//" within them. Consecutive
860 ;; slashes are treated as one, which is weird but often convenient.
861 ;; However, preserving empty directory components:
863 ;; - makes (NAMESTRING (MAKE-PATHNAME :DIRECTORY '(:RELATIVE "" "d")))
864 ;; visually indistinguishable from the absolute pathname "/d/"
865 ;; - can causes a pathname equality test to return NIL
866 ;; on semantically equivalent pathnames. This can happen for
867 ;; other reasons, but fewer false negatives is better.
868 (unless (and (stringp piece
) (zerop (length piece
)))
869 (push (maybe-diddle-case piece diddle-case
) results
)))
871 (error "~S is not allowed as a directory component." piece
)))))
874 (cond ((zerop (length directory
)) `(:absolute
))
876 (when (typep directory
'(not simple-array
))
877 (setq directory
(coerce directory
'simple-string
)))
878 `(:absolute
,(maybe-diddle-case directory diddle-case
)))))))
880 (defun make-pathname (&key host
885 (version nil versionp
)
888 "Makes a new pathname from the component arguments. Note that host is
889 a host-structure or string."
890 (declare (type (or string host pathname-component-tokens
) host
)
891 (type (or string pathname-component-tokens
) device
)
892 (type (or list string pattern pathname-component-tokens
) directory
)
893 (type (or string pattern pathname-component-tokens
) name type
)
894 (type (or integer pathname-component-tokens
(member :newest
))
896 (type (or pathname-designator null
) defaults
)
897 (type pathname-component-case case
))
898 (let* ((defaults (when defaults
899 (with-pathname (defaults defaults
) defaults
)))
900 (default-host (if defaults
901 (%pathname-host defaults
)
902 (pathname-host *default-pathname-defaults
*)))
903 ;; Raymond Toy writes: CLHS says make-pathname can take a
904 ;; string (as a logical-host) for the host part. We map that
905 ;; string into the corresponding logical host structure.
907 ;; Paul Werkowski writes:
908 ;; HyperSpec says for the arg to MAKE-PATHNAME;
909 ;; "host---a valid physical pathname host. ..."
910 ;; where it probably means -- a valid pathname host.
911 ;; "valid pathname host n. a valid physical pathname host or
912 ;; a valid logical pathname host."
914 ;; "valid physical pathname host n. any of a string,
915 ;; a list of strings, or the symbol :unspecific,
916 ;; that is recognized by the implementation as the name of a host."
917 ;; "valid logical pathname host n. a string that has been defined
918 ;; as the name of a logical host. ..."
919 ;; HS is silent on what happens if the :HOST arg is NOT one of these.
920 ;; It seems an error message is appropriate.
921 (host (or (find-host host nil
) default-host
))
922 (diddle-args (and (eq (host-customary-case host
) :lower
)
925 (not (eq (host-customary-case host
)
926 (host-customary-case default-host
))))
927 (dir (import-directory directory diddle-args
))
930 (defaults (%pathname-version defaults
))
932 (when (and defaults
(not dirp
))
934 (merge-directories dir
935 (%pathname-directory defaults
)
938 (macrolet ((pick (var varp field
)
939 `(cond ((or (simple-string-p ,var
)
941 (maybe-diddle-case ,var diddle-args
))
943 (maybe-diddle-case (coerce ,var
'simple-string
)
946 (maybe-diddle-case ,var diddle-args
))
948 (maybe-diddle-case (,field defaults
)
954 (pick device devp %pathname-device
) ; forced to :UNSPECIFIC when logical
956 (pick name namep %pathname-name
)
957 (pick type typep %pathname-type
)
960 (defun pathname-host (pathname &key
(case :local
))
961 "Return PATHNAME's host."
962 (declare (ignore case
))
963 (with-pathname (pathname pathname
)
964 (%pathname-host pathname
)))
966 (macrolet ((frob (name component docstring
)
967 `(defun ,name
(pathname &key
(case :local
))
969 (with-pathname (pathname pathname
)
970 (let ((effective-case (and (eq case
:common
)
971 (eq (host-customary-case
972 (%pathname-host pathname
))
974 (maybe-diddle-case (,component pathname
) effective-case
))))))
976 (frob pathname-device %pathname-device
"Return PATHNAME's device.")
977 (frob pathname-directory %pathname-directory
"Return PATHNAME's directory.")
978 (frob pathname-name %pathname-name
"Return PATHNAME's name.")
979 (frob pathname-type %pathname-type
"Return PATHNAME's type."))
981 (defun pathname-version (pathname)
982 "Return PATHNAME's version."
983 (with-pathname (pathname pathname
)
984 (%pathname-version pathname
)))
988 ;;; Handle the case for PARSE-NAMESTRING parsing a potentially
989 ;;; syntactically valid logical namestring with an explicit host.
991 ;;; This then isn't fully general -- we are relying on the fact that
992 ;;; we will only pass to parse-namestring namestring with an explicit
993 ;;; logical host, so that we can pass the host return from
994 ;;; parse-logical-namestring through to %PARSE-NAMESTRING as a truth
995 ;;; value. Yeah, this is probably a KLUDGE - CSR, 2002-04-18
996 (defun parseable-logical-namestring-p (namestr start end
)
997 (and (parse-potential-logical-host namestr start end
)
999 (let ((result (parse-logical-namestring namestr start end
)))
1000 ;; if we got this far, we should have an explicit host
1001 ;; (first return value of parse-logical-namestring)
1004 ((or simple-type-error namestring-parse-error
) ()
1007 (defun parse-potential-logical-host (namestr &optional
(start 0) end
)
1009 (let ((colon (position #\
: namestr
:start start
:end end
)))
1011 (let ((potential-host
1012 (logical-word-or-lose (subseq namestr start colon
))))
1013 (values potential-host colon
))))
1014 (namestring-parse-error () nil
)))
1016 ;;; Handle the case where PARSE-NAMESTRING is actually parsing a
1017 ;;; namestring. We pick off the :JUNK-ALLOWED case then find a host to
1018 ;;; use for parsing, call the parser, then check whether the host matches.
1019 (defun %parse-namestring
(namestr host defaults start end junk-allowed
)
1020 (declare (type (or host null
) host
)
1021 (type string namestr
)
1023 (type (or index null
) end
))
1027 (%parse-namestring namestr host defaults start end nil
)
1028 (namestring-parse-error (condition)
1029 (values nil
(namestring-parse-error-offset condition
)))))
1031 (let ((end (%check-vector-sequence-bounds namestr start end
)))
1032 (multiple-value-bind (new-host device directory file type version
)
1033 ;; Comments below are quotes from the HyperSpec
1034 ;; PARSE-NAMESTRING entry, reproduced here to demonstrate
1035 ;; that we actually have to do things this way rather than
1036 ;; some possibly more logical way. - CSR, 2002-04-18
1038 ;; "If host is a logical host then thing is parsed as a
1039 ;; logical pathname namestring on the host."
1040 (host (funcall (host-parse host
) namestr start end
))
1041 ;; "If host is nil and thing is a syntactically valid
1042 ;; logical pathname namestring containing an explicit
1043 ;; host, then it is parsed as a logical pathname
1045 ((parseable-logical-namestring-p namestr start end
)
1046 (parse-logical-namestring namestr start end
))
1047 ;; "If host is nil, default-pathname is a logical
1048 ;; pathname, and thing is a syntactically valid logical
1049 ;; pathname namestring without an explicit host, then it
1050 ;; is parsed as a logical pathname namestring on the
1051 ;; host that is the host component of default-pathname."
1053 ;; "Otherwise, the parsing of thing is
1054 ;; implementation-defined."
1056 ;; Both clauses are handled here, as the default
1057 ;; *DEFAULT-PATHNAME-DEFAULTS* has a SB-IMPL::UNIX-HOST
1059 ((pathname-host defaults
)
1060 (funcall (host-parse (pathname-host defaults
))
1064 ;; I don't think we should ever get here, as the default
1065 ;; host will always have a non-null HOST, given that we
1066 ;; can't create a new pathname without going through
1067 ;; *DEFAULT-PATHNAME-DEFAULTS*, which has a non-null
1069 (t (bug "Fallen through COND in %PARSE-NAMESTRING")))
1070 (when (and host new-host
(not (eq new-host host
)))
1071 (error 'simple-type-error
1073 ;; Note: ANSI requires that this be a TYPE-ERROR,
1074 ;; but there seems to be no completely correct
1075 ;; value to use for TYPE-ERROR-EXPECTED-TYPE.
1076 ;; Instead, we return a sort of "type error allowed
1077 ;; type", trying to say "it would be OK if you
1078 ;; passed NIL as the host value" but not mentioning
1079 ;; that a matching string would be OK too.
1080 :expected-type
'null
1082 "The host in the namestring, ~S,~@
1083 does not match the explicit HOST argument, ~S."
1084 :format-arguments
(list new-host host
)))
1085 (let ((pn-host (or new-host host
(pathname-host defaults
))))
1086 (values (intern-pathname pn-host device directory file type version
)
1089 (defun parse-namestring (thing
1092 (defaults *default-pathname-defaults
*)
1093 &key
(start 0) end junk-allowed
)
1094 (declare (ftype (function * (values (or null pathname
) (or null index
)))
1096 (with-host (found-host host
)
1097 (let (;; According to ANSI defaults may be any valid pathname designator
1098 (defaults (etypecase defaults
1102 (aver (pathnamep *default-pathname-defaults
*))
1103 (parse-namestring defaults
))
1105 (truename defaults
)))))
1106 (declare (type pathname defaults
))
1109 (with-array-data ((thing thing
) (start start
) (end end
)
1110 :check-fill-pointer t
)
1111 (multiple-value-bind (pathname position
)
1112 (%parse-namestring thing found-host defaults start end junk-allowed
)
1113 (values pathname
(- position start
)))))
1115 (let ((defaulted-host (or found-host
(%pathname-host defaults
))))
1116 (declare (type host defaulted-host
))
1117 (unless (eq defaulted-host
(%pathname-host thing
))
1118 (error "The HOST argument doesn't match the pathname host:~% ~
1120 defaulted-host
(%pathname-host thing
))))
1121 (values thing start
))
1122 ((or file-stream synonym-stream
)
1123 (values (stream-file-name-or-lose thing
) nil
))))))
1125 (defun %parse-native-namestring
(namestr host defaults start end junk-allowed
1127 (declare (type (or host null
) host
)
1128 (type string namestr
)
1130 (type (or index null
) end
))
1134 (%parse-native-namestring namestr host defaults start end nil as-directory
)
1135 (namestring-parse-error (condition)
1136 (values nil
(namestring-parse-error-offset condition
)))))
1138 (let* ((end (%check-vector-sequence-bounds namestr start end
)))
1139 (multiple-value-bind (new-host device directory file type version
)
1142 (funcall (host-parse-native host
) namestr start end as-directory
))
1143 ((pathname-host defaults
)
1144 (funcall (host-parse-native (pathname-host defaults
))
1149 ;; I don't think we should ever get here, as the default
1150 ;; host will always have a non-null HOST, given that we
1151 ;; can't create a new pathname without going through
1152 ;; *DEFAULT-PATHNAME-DEFAULTS*, which has a non-null
1154 (t (bug "Fallen through COND in %PARSE-NAMESTRING")))
1155 (when (and host new-host
(not (eq new-host host
)))
1156 (error 'simple-type-error
1158 :expected-type
`(or null
(eql ,host
))
1160 "The host in the namestring, ~S,~@
1161 does not match the explicit HOST argument, ~S."
1162 :format-arguments
(list new-host host
)))
1163 (let ((pn-host (or new-host host
(pathname-host defaults
))))
1164 (values (intern-pathname pn-host device directory file type version
)
1167 (defun parse-native-namestring (thing
1170 (defaults *default-pathname-defaults
*)
1171 &key
(start 0) end junk-allowed
1173 "Convert THING into a pathname, using the native conventions
1174 appropriate for the pathname host HOST, or if not specified the
1175 host of DEFAULTS. If THING is a string, the parse is bounded by
1176 START and END, and error behaviour is controlled by JUNK-ALLOWED,
1177 as with PARSE-NAMESTRING. For file systems whose native
1178 conventions allow directories to be indicated as files, if
1179 AS-DIRECTORY is true, return a pathname denoting THING as a
1181 (declare (type pathname-designator thing defaults
)
1182 (type (or list host string
(member :unspecific
)) host
)
1184 (type (or index null
) end
)
1185 (type (or t null
) junk-allowed
)
1186 (values (or null pathname
) (or null index
)))
1187 (declare (ftype (function * (values (or null pathname
) (or null index
)))
1188 %parse-native-namestring
))
1189 (with-host (found-host host
)
1190 (let ((defaults (etypecase defaults
1194 (aver (pathnamep *default-pathname-defaults
*))
1195 (parse-native-namestring defaults
))
1197 (truename defaults
)))))
1198 (declare (type pathname defaults
))
1201 (with-array-data ((thing thing
) (start start
) (end end
)
1202 :check-fill-pointer t
)
1203 (multiple-value-bind (pathname position
)
1204 (%parse-native-namestring thing
1205 found-host defaults start end junk-allowed
1207 (values pathname
(- position start
)))))
1209 (let ((defaulted-host (or found-host
(%pathname-host defaults
))))
1210 (declare (type host defaulted-host
))
1211 (unless (eq defaulted-host
(%pathname-host thing
))
1212 (error "The HOST argument doesn't match the pathname host:~% ~
1214 defaulted-host
(%pathname-host thing
))))
1215 (values thing start
))
1216 ((or file-stream synonym-stream
)
1217 (values (stream-file-name-or-lose thing
) nil
))))))
1219 (defun native-namestring (pathname &key as-file
)
1220 "Construct the full native (name)string form of PATHNAME. For
1221 file systems whose native conventions allow directories to be
1222 indicated as files, if AS-FILE is true and the name, type, and
1223 version components of PATHNAME are all NIL or :UNSPECIFIC,
1224 construct a string that names the directory according to the file
1225 system's syntax for files."
1226 (declare (type pathname-designator pathname
))
1227 (with-native-pathname (pathname pathname
)
1229 (let ((host (or (%pathname-host pathname
)
1230 (no-native-namestring-error
1231 pathname
"there is no ~S component." :host
))))
1232 (funcall (host-unparse-native host
) pathname as-file
)))))
1234 (flet ((pathname-host-or-no-namestring (pathname)
1235 (or (%pathname-host pathname
)
1236 (no-namestring-error
1237 pathname
"there is no ~S component." :host
))))
1239 (defun namestring (pathname)
1240 "Construct the full (name)string form PATHNAME."
1241 (with-pathname (pathname pathname
)
1243 (or (%pathname-namestring pathname
)
1244 (let ((host (pathname-host-or-no-namestring pathname
)))
1245 (setf (%pathname-namestring pathname
)
1246 (logically-readonlyize
1247 (possibly-base-stringize-to-heap
1248 (funcall (host-unparse host
) pathname
)))))))))
1250 (defun host-namestring (pathname)
1251 "Return a string representation of the name of the host in PATHNAME."
1252 (with-pathname (pathname pathname
)
1253 (let ((host (pathname-host-or-no-namestring pathname
)))
1254 (funcall (host-unparse-host host
) pathname
))))
1256 (defun directory-namestring (pathname)
1257 "Return a string representation of the directory in PATHNAME."
1258 (with-pathname (pathname pathname
)
1259 (let ((host (pathname-host-or-no-namestring pathname
)))
1260 (funcall (host-unparse-directory host
) pathname
))))
1262 (defun file-namestring (pathname)
1263 "Return a string representation of the name in PATHNAME."
1264 (with-pathname (pathname pathname
)
1265 (let ((host (pathname-host-or-no-namestring pathname
)))
1266 (funcall (host-unparse-file host
) pathname
))))
1268 (defun enough-namestring (pathname
1270 (defaults *default-pathname-defaults
*))
1271 "Return an abbreviated pathname sufficient to identify PATHNAME
1272 relative to DEFAULTS."
1273 (with-pathname (pathname pathname
)
1274 (let ((host (pathname-host-or-no-namestring pathname
)))
1275 (with-pathname (defaults defaults
)
1276 (funcall (host-unparse-enough host
) pathname defaults
))))))
1280 (defun wild-pathname-p (pathname &optional field-key
)
1281 "Predicate for determining whether pathname contains any wildcards."
1282 (declare (type pathname-designator pathname
)
1283 (type (member nil
:host
:device
:directory
:name
:type
:version
)
1285 (with-pathname (pathname pathname
)
1287 (or (pattern-p x
) (if (member x
'(:wild
:wild-inferiors
)) t nil
)))
1291 (:host
(%pathname-host pathname
)) ; always NIL
1292 (:device
(%pathname-device pathname
))
1294 (return-from test
(some #'wildp
(%pathname-directory pathname
))))
1295 (:name
(%pathname-name pathname
))
1296 (:type
(%pathname-type pathname
))
1297 (:version
(%pathname-version pathname
))))))
1299 ;; SBCL does not allow :WILD in the host
1300 (or (test :device
) (test :directory
) (test :name
) (test :type
) (test :version
))
1301 (test field-key
)))))
1303 (defun pathname-match-p (in-pathname in-wildname
)
1304 "Pathname matches the wildname template?"
1305 (declare (type pathname-designator in-pathname
))
1306 (with-pathname (pathname in-pathname
)
1307 (with-pathname (wildname in-wildname
)
1308 (macrolet ((frob (field &optional
(op 'components-match
))
1309 `(or (null (,field wildname
))
1310 (,op
(,field pathname
) (,field wildname
)))))
1311 (and (or (null (%pathname-host wildname
))
1312 (eq (%pathname-host wildname
) (%pathname-host pathname
)))
1313 (frob %pathname-device
)
1314 (frob %pathname-directory directory-components-match
)
1315 (frob %pathname-name
)
1316 (frob %pathname-type
)
1317 (or (eq (%pathname-host wildname
) *physical-host
*)
1318 (frob %pathname-version
)))))))
1320 ;;; Place the substitutions into the pattern and return the string or pattern
1321 ;;; that results. If DIDDLE-CASE is true, we diddle the result case as well,
1322 ;;; in case we are translating between hosts with difference conventional case.
1323 ;;; The second value is the tail of subs with all of the values that we used up
1324 ;;; stripped off. Note that PATTERN-MATCHES matches all consecutive wildcards
1325 ;;; as a single string, so we ignore subsequent contiguous wildcards.
1326 (defun substitute-into (pattern subs diddle-case
)
1327 (declare (type pattern pattern
)
1329 (values (or simple-string pattern
) list
))
1330 (let ((in-wildcard nil
)
1333 (dolist (piece (pattern-pieces pattern
))
1334 (cond ((simple-string-p piece
)
1335 (push piece strings
)
1336 (setf in-wildcard nil
))
1339 (setf in-wildcard t
)
1341 (error "not enough wildcards in FROM pattern to match ~
1344 (let ((sub (pop subs
)))
1348 (push (apply #'concatenate
'simple-string
1351 (dolist (piece (pattern-pieces sub
))
1352 (push piece pieces
)))
1356 (error "can't substitute this into the middle of a word:~
1361 (push (apply #'concatenate
'simple-string
(nreverse strings
))
1365 (if (and pieces
(simple-string-p (car pieces
)) (null (cdr pieces
)))
1367 (make-pattern (nreverse pieces
)))
1371 ;;; Called when we can't see how source and from matched.
1372 (defun didnt-match-error (source from
)
1373 (error "Pathname components from SOURCE and FROM args to TRANSLATE-PATHNAME~@
1374 did not match:~% ~S ~S"
1377 ;;; Do TRANSLATE-COMPONENT for all components except host, directory
1379 (defun translate-component (source from to diddle-case
)
1386 (if (pattern= from source
)
1388 (didnt-match-error source from
)))
1390 (multiple-value-bind (won subs
) (pattern-matches from source
)
1392 (values (substitute-into to subs diddle-case
))
1393 (didnt-match-error source from
))))
1395 (maybe-diddle-case source diddle-case
))))
1397 (values (substitute-into to
(list source
) diddle-case
)))
1399 (if (components-match source from
)
1400 (maybe-diddle-case source diddle-case
)
1401 (didnt-match-error source from
)))))
1403 (maybe-diddle-case source diddle-case
))
1405 (if (components-match source from
)
1407 (didnt-match-error source from
)))))
1409 ;;; Return a list of all the things that we want to substitute into the TO
1410 ;;; pattern (the things matched by from on source.) When From contains
1411 ;;; :WILD-INFERIORS, the result contains a sublist of the matched source
1413 (defun compute-directory-substitutions (orig-source orig-from
)
1414 (let ((source orig-source
)
1419 (unless (every (lambda (x) (eq x
:wild-inferiors
)) from
)
1420 (didnt-match-error orig-source orig-from
))
1423 (unless from
(didnt-match-error orig-source orig-from
))
1424 (let ((from-part (pop from
))
1425 (source-part (pop source
)))
1428 (typecase source-part
1430 (if (pattern= from-part source-part
)
1432 (didnt-match-error orig-source orig-from
)))
1434 (multiple-value-bind (won new-subs
)
1435 (pattern-matches from-part source-part
)
1437 (dolist (sub new-subs
)
1439 (didnt-match-error orig-source orig-from
))))
1441 (didnt-match-error orig-source orig-from
))))
1444 ((member :wild-inferiors
)
1445 (let ((remaining-source (cons source-part source
)))
1448 (when (directory-components-match remaining-source from
)
1450 (unless remaining-source
1451 (didnt-match-error orig-source orig-from
))
1452 (res (pop remaining-source
)))
1454 (setq source remaining-source
))))
1456 (unless (and (simple-string-p source-part
)
1457 (string= from-part source-part
))
1458 (didnt-match-error orig-source orig-from
)))
1460 (didnt-match-error orig-source orig-from
)))))
1463 ;;; This is called by TRANSLATE-PATHNAME on the directory components
1464 ;;; of its argument pathnames to produce the result directory
1465 ;;; component. If this leaves the directory NIL, we return the source
1466 ;;; directory. The :RELATIVE or :ABSOLUTE is taken from the source
1467 ;;; directory, except if TO is :ABSOLUTE, in which case the result
1468 ;;; will be :ABSOLUTE.
1469 (defun translate-directories (source from to diddle-case
)
1470 (if (not (and source to from
))
1471 (or (and to
(null source
) (remove :wild-inferiors to
))
1472 (mapcar (lambda (x) (maybe-diddle-case x diddle-case
)) source
))
1474 ;; If TO is :ABSOLUTE, the result should still be :ABSOLUTE.
1475 (res (if (eq (first to
) :absolute
)
1478 (let ((subs-left (compute-directory-substitutions (rest source
)
1480 (dolist (to-part (rest to
))
1484 (let ((match (pop subs-left
)))
1486 (error ":WILD-INFERIORS is not paired in from and to ~
1487 patterns:~% ~S ~S" from to
))
1488 (res (maybe-diddle-case match diddle-case
))))
1489 ((member :wild-inferiors
)
1491 (let ((match (pop subs-left
)))
1492 (unless (listp match
)
1493 (error ":WILD-INFERIORS not paired in from and to ~
1494 patterns:~% ~S ~S" from to
))
1496 (res (maybe-diddle-case x diddle-case
)))))
1498 (multiple-value-bind
1500 (substitute-into to-part subs-left diddle-case
)
1501 (setf subs-left new-subs-left
)
1503 (t (res to-part
)))))
1506 (defun translate-pathname (source from-wildname to-wildname
&key
)
1507 "Use the source pathname to translate the from-wildname's wild and
1508 unspecified elements into a completed to-pathname based on the to-wildname."
1509 (declare (type pathname-designator source from-wildname to-wildname
))
1510 (with-pathname (source source
)
1511 (with-pathname (from from-wildname
)
1512 (with-pathname (to to-wildname
)
1513 (let* ((source-host (%pathname-host source
))
1514 (from-host (%pathname-host from
))
1515 (to-host (%pathname-host to
))
1517 (and source-host to-host
1518 (not (eq (host-customary-case source-host
)
1519 (host-customary-case to-host
))))))
1520 (macrolet ((frob (field &optional
(op 'translate-component
))
1521 `(let ((result (,op
(,field source
)
1525 (if (eq result
:error
)
1526 (error "~S doesn't match ~S." source from
)
1529 (or to-host source-host
)
1530 (frob %pathname-device
)
1531 (frob %pathname-directory translate-directories
)
1532 (frob %pathname-name
)
1533 (frob %pathname-type
)
1534 (if (eq from-host
*physical-host
*)
1535 (if (or (eq (%pathname-version to
) :wild
)
1536 (eq (%pathname-version to
) nil
))
1537 (%pathname-version source
)
1538 (%pathname-version to
))
1539 (frob %pathname-version
)))))))))
1541 ;;;; logical pathname support. ANSI 92-102 specification.
1543 ;;;; As logical-pathname translations are loaded they are
1544 ;;;; canonicalized as patterns to enable rapid efficient translation
1545 ;;;; into physical pathnames.
1549 ;;; Access *DEFAULT-PATHNAME-DEFAULTS*, issuing a warning if its value
1550 ;;; is silly. (Unlike the vaguely-analogous SANE-PACKAGE, we don't
1551 ;;; actually need to reset the variable when it's silly, since even
1552 ;;; crazy values of *DEFAULT-PATHNAME-DEFAULTS* don't leave the system
1553 ;;; in a state where it's hard to recover interactively.)
1554 (defun sane-default-pathname-defaults ()
1555 (let* ((dfd *default-pathname-defaults
*)
1556 (dfd-dir (pathname-directory dfd
)))
1557 ;; It's generally not good to use a relative pathname for
1558 ;; *DEFAULT-PATHNAME-DEFAULTS*, since relative pathnames
1559 ;; are defined by merging into a default pathname (which is,
1560 ;; by default, *DEFAULT-PATHNAME-DEFAULTS*).
1561 (when (and (consp dfd-dir
)
1562 (eql (first dfd-dir
) :relative
))
1564 "~@<~S is a relative pathname. (But we'll try using it anyway.)~@:>"
1565 '*default-pathname-defaults
*))
1568 (defun simplify-namestring (namestring &optional host
)
1569 (funcall (host-simplify-namestring
1571 (pathname-host (sane-default-pathname-defaults))))
1574 (defun lpn-word-char-p (char)
1575 ;; This predicate is just {alpha|digit|dash} but by expressing it as
1576 ;; range comparison we can - I hope - avoid cross-compiling many of
1577 ;; the Unicode tables and particularly MISC-INDEX. Taking them out of
1578 ;; make-host-2 removes some hassle around dumping specialized vectors.
1579 (and (typep (truly-the character char
) 'base-char
)
1580 (let ((code (char-code char
)))
1581 (or (<= (char-code #\a) code
(char-code #\z
))
1582 (<= (char-code #\A
) code
(char-code #\Z
))
1583 (<= (char-code #\
0) code
(char-code #\
9))
1584 (= code
(char-code #\-
))))))
1586 ;;; Canonicalize a logical pathname word by uppercasing it checking that it
1587 ;;; contains only legal characters.
1588 (defun logical-word-or-lose (word)
1589 (declare (string word
))
1590 ;; Maybe this function used to be called only on the HOST part of a namestring,
1591 ;; and so the error message about an empty string made sense in that it mentioned
1592 ;; "logical host", but this is also called by UPCASE-MAYBE via INTERN-PATHNAME
1593 ;; on every part - name, type, and directory.
1594 ;; Maybe INTERN-PATHNAME is the one that's wrong?
1595 (when (string= word
"")
1596 ;; https://www.lispworks.com/documentation/HyperSpec/Body/19_cbb.htm
1597 (error 'namestring-parse-error
1598 :complaint
"A string of length 0 is not a valid value for any ~
1599 component of a logical pathname"
1601 :namestring word
:offset
0))
1602 (dotimes (i (length word
) (string-upcase word
))
1603 ;; um, how do we know it's SIMPLE-STRING when the decl at the top
1604 ;; only says STRING?
1605 (let ((ch (schar word i
)))
1606 (unless (lpn-word-char-p ch
)
1607 (error 'namestring-parse-error
1608 :complaint
"logical namestring character which ~
1609 is not alphanumeric or hyphen:~% ~S"
1611 :namestring word
:offset i
)))))
1613 ;;; Given a logical host or string, return a logical host. If ERROR-P
1614 ;;; is NIL, then return NIL when no such host exists.
1615 (defun find-logical-host (thing &optional
(errorp t
))
1618 (let* ((name (logical-word-or-lose thing
))
1619 (hash (sxhash name
))
1620 ;; Can do better here: binary search, since we maintain sorted order.
1621 (found (dovector (x *logical-hosts
*)
1622 (when (and (eq (logical-host-name-hash x
) hash
)
1623 (string= (logical-host-name x
) name
))
1625 (if (or found
(not errorp
))
1627 ;; This is the error signalled from e.g.
1628 ;; LOGICAL-PATHNAME-TRANSLATIONS when host is not a defined
1629 ;; host, and ANSI specifies that that's a TYPE-ERROR.
1630 (error 'simple-type-error
1632 ;; God only knows what ANSI expects us to use for
1633 ;; the EXPECTED-TYPE here. Maybe this will be OK..
1635 '(and string
(satisfies logical-pathname-translations
))
1636 :format-control
"logical host not yet defined: ~S"
1637 :format-arguments
(list thing
)))))
1638 (logical-host thing
)))
1640 ;;; Given a logical host name or host, return a logical host, creating
1641 ;;; a new one if necessary.
1642 (defun intern-logical-host (thing &aux name host
)
1644 (awhen (find-logical-host thing nil
) (return it
))
1646 (setq name
(logical-word-or-lose thing
)
1647 host
(make-logical-host :name name
:name-hash
(sxhash name
))))
1648 (let* ((old *logical-hosts
*)
1649 (new (merge 'vector old
(list host
) #'string
< :key
#'logical-host-name
)))
1650 (when (eq (cas *logical-hosts
* old new
) old
)
1653 ;;;; logical pathname parsing
1655 ;;; Deal with multi-char wildcards in a logical pathname token.
1656 (defun maybe-make-logical-pattern (namestring chunks
)
1657 (let ((chunk (caar chunks
)))
1658 (collect ((pattern))
1660 (len (length chunk
)))
1661 (declare (fixnum last-pos
))
1663 (when (= last-pos len
) (return))
1664 (let ((pos (or (position #\
* chunk
:start last-pos
) len
)))
1665 (if (= pos last-pos
)
1667 (error 'namestring-parse-error
1668 :complaint
"double asterisk inside of logical ~
1671 :namestring namestring
1672 :offset
(+ (cdar chunks
) pos
)))
1673 (pattern (subseq chunk last-pos pos
)))
1676 (pattern :multi-char-wild
))
1677 (setq last-pos
(1+ pos
)))))
1680 (make-pattern (pattern))
1681 (let ((x (car (pattern))))
1682 (if (eq x
:multi-char-wild
)
1686 ;;; Return a list of conses where the CDR is the start position and
1687 ;;; the CAR is a string (token) or character (punctuation.)
1688 (defun logical-chunkify (namestr start end
)
1690 (do ((i start
(1+ i
))
1694 (chunks (cons (nstring-upcase (subseq namestr prev end
)) prev
))))
1695 (let ((ch (schar namestr i
)))
1696 (unless (or (lpn-word-char-p ch
) (char= ch
#\
*))
1698 (chunks (cons (nstring-upcase (subseq namestr prev i
)) prev
)))
1700 (unless (member ch
'(#\
; #\: #\.))
1701 (error 'namestring-parse-error
1702 :complaint
"illegal character for logical pathname:~% ~S"
1706 (chunks (cons ch i
)))))
1709 ;;; Break up a logical-namestring, always a string, into its
1710 ;;; constituent parts.
1711 (defun parse-logical-namestring (namestr start end
)
1712 (declare (type simple-string namestr
)
1713 (type index start end
))
1714 (collect ((directory))
1719 (labels ((expecting (what chunks
)
1720 (unless (and chunks
(simple-string-p (caar chunks
)))
1721 (error 'namestring-parse-error
1722 :complaint
"expecting ~A, got ~:[nothing~;~S~]."
1723 :args
(list what
(caar chunks
) (caar chunks
))
1725 :offset
(if chunks
(cdar chunks
) end
)))
1727 (parse-host (chunks)
1728 (case (caadr chunks
)
1731 (find-logical-host (expecting "a host name" chunks
)))
1732 (parse-relative (cddr chunks
)))
1734 (parse-relative chunks
))))
1735 (parse-relative (chunks)
1738 (directory :relative
)
1739 (parse-directory (cdr chunks
)))
1741 (directory :absolute
) ; Assumption! Maybe revoked later.
1742 (parse-directory chunks
))))
1743 (parse-directory (chunks)
1744 (case (caadr chunks
)
1747 (let ((res (expecting "a directory name" chunks
)))
1748 (cond ((string= res
"..") :up
)
1749 ((string= res
"**") :wild-inferiors
)
1751 (maybe-make-logical-pattern namestr chunks
)))))
1752 (parse-directory (cddr chunks
)))
1754 (parse-name chunks
))))
1755 (parse-name (chunks)
1757 (expecting "a file name" chunks
)
1758 (setq name
(maybe-make-logical-pattern namestr chunks
))
1759 (expecting-dot (cdr chunks
))))
1760 (expecting-dot (chunks)
1762 (unless (eql (caar chunks
) #\.
)
1763 (error 'namestring-parse-error
1764 :complaint
"expecting a dot, got ~S."
1765 :args
(list (caar chunks
))
1767 :offset
(cdar chunks
)))
1769 (parse-version (cdr chunks
))
1770 (parse-type (cdr chunks
)))))
1771 (parse-type (chunks)
1772 (expecting "a file type" chunks
)
1773 (setq type
(maybe-make-logical-pattern namestr chunks
))
1774 (expecting-dot (cdr chunks
)))
1775 (parse-version (chunks)
1776 (let ((str (expecting "a positive integer, * or NEWEST"
1779 ((string= str
"*") (setq version
:wild
))
1780 ((string= str
"NEWEST") (setq version
:newest
))
1782 (multiple-value-bind (res pos
)
1783 (parse-integer str
:junk-allowed t
)
1784 (unless (and res
(plusp res
))
1785 (error 'namestring-parse-error
1786 :complaint
"expected a positive integer, ~
1790 :offset
(+ pos
(cdar chunks
))))
1791 (setq version res
)))))
1793 (error 'namestring-parse-error
1794 :complaint
"extra stuff after end of file name"
1796 :offset
(cdadr chunks
)))))
1797 (parse-host (logical-chunkify namestr start end
)))
1798 (values host
:unspecific
(directory) name type version
))))
1800 ;;; Return a value suitable, e.g., for preinitializing
1801 ;;; *DEFAULT-PATHNAME-DEFAULTS* before *DEFAULT-PATHNAME-DEFAULTS* is
1802 ;;; initialized (at which time we can't safely call e.g. #'PATHNAME).
1803 (defun make-trivial-default-logical-pathname ()
1804 (intern-pathname (load-time-value (make-logical-host :name
"") t
)
1805 :unspecific nil nil nil nil
))
1807 (defun logical-namestring-p (x)
1810 (typep (pathname x
) 'logical-pathname
))))
1812 (deftype logical-namestring
()
1813 `(satisfies logical-namestring-p
))
1815 (defun logical-pathname (pathspec)
1816 "Converts the pathspec argument to a logical-pathname and returns it."
1817 (declare (type (or logical-pathname string stream
) pathspec
)
1818 (values logical-pathname
))
1819 (if (typep pathspec
'logical-pathname
)
1821 (flet ((oops (problem)
1822 (error 'simple-type-error
1824 :expected-type
'logical-namestring
1825 :format-control
"~S is not a valid logical namestring:~% ~A"
1826 :format-arguments
(list pathspec problem
))))
1828 (if (streamp pathspec
)
1830 (let ((potential-host (parse-potential-logical-host pathspec
)))
1832 (values (parse-namestring
1833 pathspec
(find-logical-host potential-host
)))
1834 (error "no host specified"))))
1835 (error (e) (oops e
))))))
1838 ;;;; logical pathname unparsing
1840 (defun unparse-logical-directory (pathname)
1841 (declare (type pathname pathname
))
1843 (let ((directory (%pathname-directory pathname
)))
1845 (ecase (pop directory
)
1846 (:absolute
) ; nothing special
1847 (:relative
(pieces ";")))
1848 (dolist (dir directory
)
1849 (cond ((or (stringp dir
) (pattern-p dir
))
1850 (pieces (unparse-logical-piece dir
))
1854 ((eq dir
:wild-inferiors
)
1857 (error "invalid directory component: ~S" dir
))))))
1858 (apply #'concatenate
'simple-string
(pieces))))
1860 (defun unparse-logical-piece (thing)
1862 ((member :wild
) "*")
1863 (simple-string thing
)
1865 (collect ((strings))
1866 (dolist (piece (pattern-pieces thing
))
1868 (simple-string (strings piece
))
1870 (cond ((eq piece
:wild-inferiors
)
1872 ((eq piece
:multi-char-wild
)
1874 (t (error "invalid keyword: ~S" piece
))))))
1875 (apply #'concatenate
'simple-string
(strings))))))
1877 (defun unparse-logical-file (pathname)
1878 (declare (type pathname pathname
))
1879 (collect ((strings))
1880 (let* ((name (%pathname-name pathname
))
1881 (type (%pathname-type pathname
))
1882 (version (%pathname-version pathname
))
1883 (type-supplied (pathname-component-present-p type
))
1884 (version-supplied (pathname-component-present-p version
)))
1886 (when (and (null type
)
1887 (typep name
'string
)
1888 (position #\. name
:start
1))
1889 (error "too many dots in the name: ~S" pathname
))
1890 (strings (unparse-logical-piece name
)))
1893 (error "cannot specify the type without a file: ~S" pathname
))
1894 (when (typep type
'string
)
1895 (when (position #\. type
)
1896 (error "type component can't have a #\. inside: ~S" pathname
)))
1898 (strings (unparse-logical-piece type
)))
1899 (when version-supplied
1900 (unless type-supplied
1901 (error "cannot specify the version without a type: ~S" pathname
))
1903 ((member :newest
) (strings ".NEWEST")) ; really? not in LPNIFY-NAMESTRING
1904 ((member :wild
) (strings ".*"))
1905 (fixnum (strings ".") (strings (format nil
"~D" version
))))))
1906 (apply #'concatenate
'simple-string
(strings))))
1908 ;;; Unparse a logical pathname string.
1909 (defun unparse-enough-namestring (pathname defaults
)
1910 (let* ((path-directory (pathname-directory pathname
))
1911 (def-directory (pathname-directory defaults
))
1913 ;; Go down the directory lists to see what matches. What's
1914 ;; left is what we want, more or less.
1915 (cond ((and (eq (first path-directory
) (first def-directory
))
1916 (eq (first path-directory
) :absolute
))
1917 ;; Both paths are :ABSOLUTE, so find where the
1918 ;; common parts end and return what's left
1919 (do* ((p (rest path-directory
) (rest p
))
1920 (d (rest def-directory
) (rest d
)))
1921 ((or (endp p
) (endp d
)
1922 (not (equal (first p
) (first d
))))
1925 ;; At least one path is :RELATIVE, so just return the
1926 ;; original path. If the original path is :RELATIVE,
1927 ;; then that's the right one. If PATH-DIRECTORY is
1928 ;; :ABSOLUTE, we want to return that except when
1929 ;; DEF-DIRECTORY is :ABSOLUTE, as handled above. so return
1930 ;; the original directory.
1932 (unparse-logical-namestring
1933 (make-pathname :host
(pathname-host pathname
)
1934 :directory enough-directory
1935 :name
(pathname-name pathname
)
1936 :type
(pathname-type pathname
)
1937 :version
(pathname-version pathname
)))))
1939 (defun unparse-logical-namestring (pathname)
1940 (declare (type logical-pathname pathname
))
1941 (concatenate 'simple-string
1942 (logical-host-name (%pathname-host pathname
)) ":"
1943 (unparse-logical-directory pathname
)
1944 (unparse-logical-file pathname
)))
1946 ;;;; logical pathname translations
1948 ;;; Verify that the list of translations consists of lists and prepare
1949 ;;; canonical translations. (Parse pathnames and expand out wildcards
1951 (defun canonicalize-logical-pathname-translations (translation-list host
)
1952 (declare (type list translation-list
) (type host host
)
1954 (mapcar (lambda (translation)
1955 (destructuring-bind (from to
) translation
1956 (list (if (typep from
'logical-pathname
)
1958 (parse-namestring from host
))
1962 (defun logical-pathname-translations (host)
1963 "Return the (logical) host object argument's list of translations."
1964 (declare (type (or string logical-host
) host
)
1966 (logical-host-translations (find-logical-host host
)))
1968 (defun (setf logical-pathname-translations
) (translations host
)
1969 "Set the translations list for the logical host argument."
1970 (declare (type (or string logical-host
) host
)
1971 (type list translations
)
1973 (let ((host (intern-logical-host host
)))
1974 (setf (logical-host-canon-transls host
)
1975 (canonicalize-logical-pathname-translations translations host
))
1976 (setf (logical-host-translations host
) translations
)))
1978 (defun translate-logical-pathname (pathname &key
)
1979 "Translate PATHNAME to a physical pathname, which is returned."
1980 (declare (type pathname-designator pathname
)
1981 (values (or null pathname
)))
1984 (dolist (x (logical-host-canon-transls (%pathname-host pathname
))
1985 (error 'simple-file-error
1987 :format-control
"no translation for ~S"
1988 :format-arguments
(list pathname
)))
1989 (destructuring-bind (from to
) x
1990 (when (pathname-match-p pathname from
)
1991 (return (translate-logical-pathname
1992 (translate-pathname pathname from to
)))))))
1994 (t (translate-logical-pathname (pathname pathname
)))))
1996 ;;; Given a pathname, return a corresponding physical pathname.
1997 (defun physicalize-pathname (possibly-logical-pathname)
1998 (if (typep possibly-logical-pathname
'logical-pathname
)
1999 (translate-logical-pathname possibly-logical-pathname
)
2000 possibly-logical-pathname
))
2002 (defun load-logical-pathname-translations (host)
2003 "Reads logical pathname translations from SYS:SITE;HOST.TRANSLATIONS.NEWEST,
2004 with HOST replaced by the supplied parameter. Returns T on success.
2006 If HOST is already defined as logical pathname host, no file is loaded and NIL
2009 The file should contain a single form, suitable for use with
2010 \(SETF LOGICAL-PATHNAME-TRANSLATIONS).
2012 Note: behaviour of this function is highly implementation dependent, and
2013 historically it used to be a no-op in SBCL -- the current approach is somewhat
2014 experimental and subject to change."
2015 (declare (type string host
)
2016 (values (member t nil
)))
2017 (if (find-logical-host host nil
)
2018 ;; This host is already defined, all is well and good.
2020 ;; ANSI: "The specific nature of the search is
2021 ;; implementation-defined."
2023 (setf (logical-pathname-translations host
)
2024 (with-open-file (lpt (make-pathname :host
"SYS"
2025 :directory
'(:absolute
"SITE")
2027 :type
"TRANSLATIONS"
2031 (defun !lpn-cold-init
()
2032 (let* ((sys *default-pathname-defaults
*)
2035 (make-pathname :directory
'(:relative
"src" :wild-inferiors
)
2036 :name
:wild
:type
:wild
)
2040 (make-pathname :directory
'(:relative
"contrib" :wild-inferiors
)
2041 :name
:wild
:type
:wild
)
2045 (make-pathname :directory
'(:relative
"output" :wild-inferiors
)
2046 :name
:wild
:type
:wild
)
2048 (setf (logical-pathname-translations "SYS")
2049 `(("SYS:SRC;**;*.*.*" ,src
)
2050 ("SYS:CONTRIB;**;*.*.*" ,contrib
)
2051 ("SYS:OUTPUT;**;*.*.*" ,output
)))))
2053 (defun set-sbcl-source-location (pathname)
2054 "Initialize the SYS logical host based on PATHNAME, which should be
2055 the top-level directory of the SBCL sources. This will replace any
2056 existing translations for \"SYS:SRC;\", \"SYS:CONTRIB;\", and
2057 \"SYS:OUTPUT;\". Other \"SYS:\" translations are preserved."
2058 (let ((truename (truename pathname
))
2059 (current-translations
2060 (remove-if (lambda (translation)
2061 (or (pathname-match-p "SYS:SRC;" translation
)
2062 (pathname-match-p "SYS:CONTRIB;" translation
)
2063 (pathname-match-p "SYS:OUTPUT;" translation
)))
2064 (logical-pathname-translations "SYS")
2066 (flet ((physical-target (component)
2068 (make-pathname :directory
(list :relative component
2073 (setf (logical-pathname-translations "SYS")
2074 `(("SYS:SRC;**;*.*.*" ,(physical-target "src"))
2075 ("SYS:CONTRIB;**;*.*.*" ,(physical-target "contrib"))
2076 ("SYS:OUTPUT;**;*.*.*" ,(physical-target "output"))
2077 ,@current-translations
)))))
2079 (defmethod make-load-form ((pn pathname
) &optional env
)
2080 (declare (ignore env
))
2081 (labels ((reconstruct (component)
2082 (cond ((pattern-p component
) (patternify component
))
2083 ((and (listp component
) (some #'pattern-p component
))
2084 (cons 'list
(mapcar #'patternify component
)))
2086 (patternify (subcomponent)
2087 (if (pattern-p subcomponent
)
2088 `(make-pattern ',(pattern-pieces subcomponent
))
2090 (values `(intern-pathname
2091 ,(if (typep pn
'logical-pathname
)
2092 `(find-logical-host ',(logical-host-name (%pathname-host pn
)))
2094 ,(reconstruct (%pathname-device pn
))
2095 ,(reconstruct (%pathname-directory pn
))
2096 ,(reconstruct (%pathname-name pn
))
2097 ,(reconstruct (%pathname-type pn
))
2098 ,(reconstruct (%pathname-version pn
))))))