[trivial-gray-streams-devel] trivial-gray-stream-mixin in cl+ssl

Anton Vodonosov avodonosov at yandex.ru
Wed Mar 13 11:01:58 UTC 2013


10.03.2013, 00:10, "James M. Lawrence" <llmjjmll at gmail.com>:
> Hello, I noticed that cl+ssl has the same issue that I recently had
> with gray streams.
>
> (defclass my-stream
>     (trivial-gray-streams:fundamental-binary-input-stream
>      trivial-gray-streams:fundamental-binary-output-stream
>      trivial-gray-streams:trivial-gray-stream-mixin)
>   ())
>
> (defmethod trivial-gray-streams:stream-write-byte
>     ((stream my-stream) byte)
>   (format t "~&write-byte ~s~%" byte))
>
> (defmethod trivial-gray-streams:stream-write-sequence
>     ((stream my-stream) seq start end &key)
>   (format t "~&write-sequence ~s ~s ~s~%" seq start end))
>
> (defun test ()
>   (write-sequence #(3 4 5) (make-instance 'my-stream)))
>
> CL-USER> (test)
> write-byte 3
> write-byte 4
> write-byte 5
>
> The mixin should be the first superclass. With that change, we get
>
> CL-USER> (test)
> write-sequence #(3 4 5) 0 3
>
> Best,
> James

I am fixing that in trivial-gray-streams.

First, let me describe the cause of the problem.

One thing to know is that tirival-gray-streams classes are just reexported from
implementation specific package. For example 
trivial-gray-streams:fundamental-stream is EQ to sb-gray:fundamental-stream.

Next, stream-write-sequence is absent in Gray streams proposal (because write-sequence is
absent in CLTL2 I think). Lips implementations provide similar functions, like
sb-gray:stream-write-sequence on SBCL, 
gray:stream-write-char-sequence, gray:stream-write-byte-sequence on CLISP
or ccl:stream-write-vector on CCL.

Trivial-gray-streams defines a method for the implementation specific write-sequence analogue function,
and from this method calls trivial-gray-streams:write-sequence.

Besides of the method defined by trivial-gray-streams, the lisp implementation provides
a default method. So, there are two methods for implementation specific write-sequence
analogue:

For example on SBCL:

;; SBCL's default method:
(defmethod sb-gray:stream-write-sequence ((stream fundamental-binary-output-stream) (seq sequence) &optional (start 0) (end nil))

;; trivial gray streams
(defmethod sb-gray:stream-write-sequence ((s trivial-gray-stream-mixin) seq &optional start end)

The class precedence-list for the my-stream class defined above (on SBCL):
    COMMON-LISP-USER::MY-STREAM,
    SB-GRAY:FUNDAMENTAL-BINARY-INPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-INPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-BINARY-OUTPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-OUTPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-BINARY-STREAM,
    SB-GRAY:FUNDAMENTAL-STREAM,
    TRIVIAL-GRAY-STREAMS:TRIVIAL-GRAY-STREAM-MIXIN,
    COMMON-LISP:STANDARD-OBJECT,
    SB-PCL::SLOT-OBJECT, COMMON-LISP:STREAM,
    COMMON-LISP:T

As you can see, with this class precedence list the SBCL's default method
is choosen (this method calls write-byte repeatedly, as we see in the above test)

If we put mixin first in defclass:

    (defclass my-stream
         (trivial-gray-streams:fundamental-binary-input-stream
          trivial-gray-streams:fundamental-binary-output-stream
          trivial-gray-streams:trivial-gray-stream-mixin)
       ())
then the class precedence list becomes:

    COMMON-LISP-USER::MY-STREAM,
    TRIVIAL-GRAY-STREAMS:TRIVIAL-GRAY-STREAM-MIXIN,
    SB-GRAY:FUNDAMENTAL-BINARY-INPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-INPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-BINARY-OUTPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-OUTPUT-STREAM,
    SB-GRAY:FUNDAMENTAL-BINARY-STREAM,
    SB-GRAY:FUNDAMENTAL-STREAM,
    COMMON-LISP:STANDARD-OBJECT,
    SB-PCL::SLOT-OBJECT, COMMON-LISP:STREAM,
    COMMON-LISP:T

With this class precedence list the trivial-gray-streams method is chosen.

Summary: in presence of implementation-specific default methods specialized to
sb-gray:* classes, and when user inherits his stream from sb-gray:* classes,
trivial-gray-streams can not ensure that its method is called, but not the default method.

The solution I chosen is that tirival-gray-streams package does not re-export
implementation specific gray classes, but defines and exports its own gray class hierarchy.
Each class in this hierarchy is also inherited from corresponding implementation specific class.

    (defclass fundamental-stream (impl-specific-gray:fundamental-stream) ())

    (defclass fundamental-input-stream
        (fundamental-stream impl-specific-gray:fundamental-input-stream) ())

    (defclass fundamental-output-stream
        (fundamental-stream impl-specific-gray:fundamental-output-stream) ())

    ... and so on

That way any user defined class has trivial-gray-stream class in the precedence list
going before the corresponding implementation specific class, and so trivial-gray-streams
methods are always chosen by method dispatch.

This solution makes trivial-gray-stream-mixin class unnecessary.

I have already implemented this in the dev branch of trivial-gray-streams:
https://gitorious.org/trivial-gray-streams/trivial-gray-streams/trees/dev

Also added test-suite. Results seem to be the same as with old version of trivial-gray-streams.
Now I am running cl-test-grid tests of the whole Quicklisp. If there will be no regressions,
I will merge the dev branch to master.

New trivial-gray-stream test suite is not 100% exhaustive, but it's a beginning.
It already shows that all the of the implementations I tested have minor bugs.
One ticket is already fixed by SBCL: https://bugs.launchpad.net/sbcl/+bug/1153257
More tickets are to be reported.
The most often failure is that no one invokes stream-advance-to-column function
when we call (format my-stream "~10,t").

Here is the mapping with failures for various lisps:

(("sbcl-1.1.0.45-win-x86" ("stream-clear-output" "stream-advance-to-column"))
 ("sbcl-1.1.0.36.mswinmt.1201-284e340-win-x64" ("stream-clear-output" "stream-advance-to-column"))
 ("sbcl-1.1.0.36.mswinmt.1201-284e340-win-x86" ("stream-clear-output" "stream-advance-to-column"))
 ("abcl-1.1.0-fasl39-win-x64" ("stream-line-column" "stream-advance-to-column"))
 ("clisp-2.49-win-x86" ("stream-start-line-p" "stream-write-string" "stream-terpri" "stream-fresh-line" "stream-advance-to-column"))
 ("ccl-1.8-f95-win-x86" ("stream-advance-to-column"))
 ("ccl-1.8-f95-win-x64" ("stream-advance-to-column"))
 ("ecl-12.12.1-ab933fa5-win-x86-bytecode" ("stream-advance-to-column" "stream-file-position" "setf-stream-file-position"))
 ("acl-9.0a-win-x86" ("stream-advance-to-column")))

If you see any problems with my solution, please let me know.

Best regards,
- Anton




More information about the trivial-gray-streams-devel mailing list