From: sds on
http://www.lispworks.com/documentation/HyperSpec/Body/f_cmp_fi.htm
compile-file input-file &key output-file verbose print external-format
output-file---a pathname designator.
output-file can be used to specify an output pathname; the actual
pathname of the compiled file to which compiled code will be output is
computed as if by calling compile-file-pathname.

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.
From: Joshua Taylor on
On 2010.08.12 5:14 PM, sds wrote:
> http://www.lispworks.com/documentation/HyperSpec/Body/f_cmp_fi.htm
> compile-file input-file &key output-file verbose print external-format
> output-file---a pathname designator.
> output-file can be used to specify an output pathname; the actual
> pathname of the compiled file to which compiled code will be output is
> computed as if by calling compile-file-pathname.
>
> 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)))))

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
From: sds on
On Aug 12, 6:57 pm, Joshua Taylor <tay...(a)cs.rpi.edu> wrote:
> On 2010.08.12 5:14 PM, sds wrote:
>
> >http://www.lispworks.com/documentation/HyperSpec/Body/f_cmp_fi.htm
> > compile-file input-file &key output-file verbose print external-format
> > output-file---a pathname designator.
> > output-file can be used to specify an output pathname; the actual
> > pathname of the compiled file to which compiled code will be output is
> > computed as if by calling compile-file-pathname.
>
> > 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)
> >      (open-stream-p fs))
> >    (with-open-file (fs f :direction :input)
> >      (string= #1# (read-line fs)))))

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.
From: Joshua Taylor on
On 2010.08.13 12:23 PM, sds wrote:
> On Aug 12, 6:57 pm, Joshua Taylor <tay...(a)cs.rpi.edu> wrote:
>> On 2010.08.12 5:14 PM, sds wrote:
>>
>>> http://www.lispworks.com/documentation/HyperSpec/Body/f_cmp_fi.htm
>>> compile-file input-file &key output-file verbose print external-format
>>> output-file---a pathname designator.
>>> output-file can be used to specify an output pathname; the actual
>>> pathname of the compiled file to which compiled code will be output is
>>> computed as if by calling compile-file-pathname.
>>
>>> 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)
>>> (open-stream-p fs))
>>> (with-open-file (fs f :direction :input)
>>> (string= #1# (read-line fs)))))
>
> 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?

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
From: Joshua Taylor on
On 2010.08.13 2:27 PM, Joshua Taylor wrote:
> On 2010.08.13 12:23 PM, sds wrote:
>> On Aug 12, 6:57 pm, Joshua Taylor <tay...(a)cs.rpi.edu> wrote:
>>> On 2010.08.12 5:14 PM, sds wrote:
>>>
>>>> http://www.lispworks.com/documentation/HyperSpec/Body/f_cmp_fi.htm
>>>> compile-file input-file &key output-file verbose print external-format
>>>> output-file---a pathname designator.
>>>> output-file can be used to specify an output pathname; the actual
>>>> pathname of the compiled file to which compiled code will be output is
>>>> computed as if by calling compile-file-pathname.
>>>
>>>> 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)
>>>> (open-stream-p fs))
>>>> (with-open-file (fs f :direction :input)
>>>> (string= #1# (read-line fs)))))
>>
>> 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?
>
> Interestingly, look what happens if a (FORCE-OUTPUT fs) is added (in
> SBCL, at least, on OS X):

....

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