this seems to suggest that if output-stream is an open file stream,
its content is overwritten, not appended to.
however, both clisp and sbcl treat an open file stream given as the
output-file as the stream where the binary is written:
(let* ((l "path-tst-compile-file-pathname.lisp")
(f (compile-file-pathname l)))
(with-open-file (ls l :direction :output :if-exists :supersede)
(format ls "(defun f () t)~%"))
(list
(with-open-file (fs f :direction :output :if-exists :supersede)
(format fs #1="first line") (terpri fs)
(compile-file l :output-file fs)
(prin1-to-string fs))
(with-open-file (fs f :direction :input)
(string= #1# (read-line fs)))))
==> (T T)
Is this the "correct" (most useful? required by the standard?)
behavior?
Thanks.
Sam.
Output-file is a pathname designator. If you provide a file-stream as
output-file, the designated pathname is that of the file. I wouldn't
expect compile-file to do anything at all with the stream except find
out what pathname it designates, and then to do something with the
associated file.
You're opening a file-stream A, writing some things to it, then calling
a function that will open a file-stream B, write some things to it,
close B, return, and then you're writing more things to A. I think any
issues you're observing are probably related to that, rather than
anything specific to COMPILE-FILE. It seems like it would be similar to
the following:
(defun test (&optional (pathname #p"~/testing" pathnamep))
(with-open-file (out pathname :direction :output
:if-exists :supersede
:if-does-not-exist :create)
(write-line "hello" out)
(if pathnamep
(write-line "recursive call" out)
(test (pathname pathname)))
(write-line "goodbye" out)))
After running (test) (in LispWorks 6.0.1), the contents of ~/testing are:
hello
goodbye
e call
goodbye
I don't expect that that's a portable result, but it shows some of the
issues with having multiple open output file-streams associated with a
file at a given time.
//JT
clisp and abcl return (T T)
gcl lacks open-stream-p but, apparently, would return (T NIL) if it
had it.
what about other lisps?
> Output-file is a pathname designator. If you provide a file-stream as
> output-file, the designated pathname is that of the file. I wouldn't
> expect compile-file to do anything at all with the stream except find
> out what pathname it designates, and then to do something with the
> associated file.
Yes, this is my understanding of the standard too.
However, it appears that _both_ SBCL and CLISP who have very different
origins but both "purport to conform" to the ANSI standard (and both
are known to take a huge effort to actually comform) interpret an open
stream output-file argument the _same_ way which is _different_ from
what you and I expect: namely, they just write to the stream.
I.e.,
(compile-file f :output-file o)
== (with-open-file (s o :direction :output)
(compile-file f :output-file s))
So, I was wondering if this behavior is somehow more useful? natural?
than my interpretation of the standard.
Or maybe this behavior is actually what the standard demands? :-)
Have anyone here ever passed an open stream as the :output-file
argument to compile-file on purpose?
> I don't expect that that's a portable result, but it shows some of the
> issues with having multiple open output file-streams associated with a
> file at a given time.
CLISP can actually catch attempts to do that and (if you so desire)
prevent it.
http://clisp.cons.org/impnotes/open.html#reopen
Sam.
Interestingly, look what happens if a (FORCE-OUTPUT fs) is added (in
SBCL, at least, on OS X):
(let* ((l "path-tst-compile-file-pathname.lisp")
(f (compile-file-pathname l)))
(with-open-file (ls l :direction :output :if-exists :supersede)
(format ls "(defun f () t)~%"))
(list
(with-open-file (fs f :direction :output :if-exists :supersede)
(format fs #1="first line") (terpri fs)
(force-output fs)
(compile-file l :output-file fs)
(open-stream-p fs))
(with-open-file (fs f :direction :input)
(string= #1# (read-line fs)))))
=> (T NIL)
I don't think SBCL is using the stream directly. I'm not an SBCL
hacker, but I think the relevant portions of the source (1.0.30) are:
(defun sb!xc:compile-file
(input-file &key ...
(output-file (cfp-output-file-default input-file))
... )
...
(let* (... (output-file-name nil) ... )
...
(when output-file
(setq output-file-name
(sb!xc:compile-file-pathname input-file
:output-file output-file))
(setq fasl-output
(open-fasl-output output-file-name
(namestring input-pathname))))
...))
The value of cfp-output-file-default is produced by make-pathname
which returns a pathname:
(defun cfp-output-file-default (input-file)
(let* ((defaults (merge-pathnames input-file *default-pathname-defaults*))
(retyped (make-pathname :type *fasl-file-type* :defaults defaults)))
retyped))
And sb!xc:compile-file-pathname returns either a value from
cft-output-file-default or merge-pathnames, and each of those
return a pathname:
(defun sb!xc:compile-file-pathname (input-file
&key
(output-file nil output-file-p)
&allow-other-keys)
#!+sb-doc
"Return a pathname describing what file COMPILE-FILE would write to given
these arguments."
(if output-file-p
(merge-pathnames output-file (cfp-output-file-default input-file))
(cfp-output-file-default input-file)))
It doesn't look like SBCL is using the stream directly, but rather getting a
pathname and opening the file again.
//JT
...
I decided to take a look at what CLISP does too.
(defun compile-file (file &key (output-file 'T) listing
((:warnings *compile-warnings*)
*compile-warnings*)
((:verbose *compile-verbose*) *compile-verbose*)
((:print *compile-print*) *compile-print*)
(external-format :default)
&aux liboutput-file (*coutput-file* nil) input-file
(*compile-file-directory*
(if (eq t output-file)
nil
(make-pathname :name nil :type nil
:defaults output-file)))
(new-output-stream nil)
(new-listing-stream nil))
(multiple-value-setq (output-file input-file)
(compile-file-pathname-helper file output-file))
....
(unwind-protect
(let* (...
(*fasoutput-stream* ; a Stream or NIL
(if new-output-stream
(open output-file :direction :output)
(if (streamp output-file) output-file nil)))
...)
It looks like CLISP (2.49) will actually return the stream:
;; Common part of COMPILE-FILE and COMPILE-FILE-PATHNAME.
;; Returns two values:
;; 1. the output file (pathname or stream or NIL),
;; 2. the input file pathname.
(defun compile-file-pathname-helper (file output-file)
(let ((input-file
(or (and (not (logical-pathname-p (pathname file)))
(first (search-file file *source-file-types*)))
(merge-pathnames file #.(make-pathname :type "lisp")))))
(values
(if (or (null output-file)
(and (streamp output-file)
(open-stream-p output-file)
(output-stream-p output-file)))
output-file
...)
...)
I'm not a CLISP maintainer either, but it looks like passing an output stream
to COMPILE-FILE will cause the fasl to be written directly to the stream (without
opening up another output-stream associated with the file).
//JT
Indeed, here's a case that illustrates the difference in CLISP:
(let* ((l "path-tst-compile-file-pathname.lisp")
(f (compile-file-pathname l)))
(with-open-file (ls l :direction :output :if-exists :supersede)
(format ls "(defun f () t)~%"))
(list
(with-open-file (fs f :direction :output :if-exists :supersede)
(format fs #1="first line") (terpri fs)
(compile-file l :output-file fs)
(prin1-to-string fs))
(with-open-file (fs f :direction :input)
(string= #1# (read-line fs)))))
results in no errors. However, if we pass (pathname fs) to compile-file
rather than fs, i.e.,
(let* ((l "path-tst-compile-file-pathname.lisp")
(f (compile-file-pathname l)))
(with-open-file (ls l :direction :output :if-exists :supersede)
(format ls "(defun f () t)~%"))
(list
(with-open-file (fs f :direction :output :if-exists :supersede)
(format fs #1="first line") (terpri fs)
(compile-file l :output-file (pathname fs)) ; ****
(prin1-to-string fs))
(with-open-file (fs f :direction :input)
(string= #1# (read-line fs)))))
OPEN: #<OUTPUT BUFFERED FILE-STREAM CHARACTER
#P"/Users/tayloj/path-tst-compile-file-pathname.fas"> already points to file #1="/Users/tayloj/path-tst-compile-file-pathname.fas", opening the file
again for :OUTPUT may produce unexpected results
[Condition of type SYSTEM::SIMPLE-FILE-ERROR]
This seems like a useful behavior in practice (it avoids the strange
output you'd see otherwise), but it does complicate the notion of
pathname designator a bit, since fs and (pathname fs) designate the same
pathname. On designators the HS says (1.4.1.5):
"Except as otherwise noted, in a situation where the denoted object might
be used multiple times, it is implementation-dependent whether the object
is coerced only once or whether the coercion occurs each time the object
must be used."
This is an interesting case since the designator apparently isn't coerced
even once---the designated pathname is never even produced (not even "only
once", heh).
//JT