Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

COMPILE-FILE argument :OUTPUT-FILE

15 views
Skip to first unread message

sds

unread,
Aug 12, 2010, 5:14:39 PM8/12/10
to
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.

Joshua Taylor

unread,
Aug 12, 2010, 6:57:07 PM8/12/10
to

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

sds

unread,
Aug 13, 2010, 12:23:42 PM8/13/10
to
On Aug 12, 6:57 pm, Joshua Taylor <tay...@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.

Joshua Taylor

unread,
Aug 13, 2010, 2:27:28 PM8/13/10
to

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

Joshua Taylor

unread,
Aug 13, 2010, 3:31:45 PM8/13/10
to

...

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

Joshua Taylor

unread,
Aug 13, 2010, 3:48:27 PM8/13/10
to
On 2010.08.13 3:31 PM, Joshua Taylor wrote:
> 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).

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

0 new messages