[m-dev.] stream typeclasses (again)
Julien Fischer
juliensf at cs.mu.OZ.AU
Tue Feb 28 18:03:30 AEDT 2006
Hi,
I've spent a bit more time playing around with the stream typeclass proposal.
Attached is the (or at least my) latest version plus a number of example
streams. (Ian, I think you mentioned you'd done some more work on this as
well?)
Major changes from last time:
* The error type is now part of the stream typeclass, (it's functionally
dependent on the stream type).
* The close method has been removed (for the moment at least)
Cheers,
Julien.
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: stream.m.
% Authors: maclarty, juliensf (+ bits from extras/streams by petdr)
% Stability: low
%-----------------------------------------------------------------------------%
:- module stream.
:- interface.
:- import_module string.
%-----------------------------------------------------------------------------%
%
% Stream errors
%
:- type stream.name == string.
:- type stream.result(T, E)
---> ok(T)
; eof
; error(E).
:- typeclass stream.error(Error) where
[
% Convert a stream error into a human-readable format.
% e.g. for use in error messages.
%
func error_message(Error) = string
].
%-----------------------------------------------------------------------------%
%
% Streams
%
% A stream consists of a base, a state to update and an error type.
%
:- typeclass stream.stream(Stream, State, Error)
<= (stream.error(Error), (Stream -> State, Error)) where
[
% A human readable name describing the stream.
%
pred name(Stream::in, stream.name::out, State::di, State::uo) is det
].
%-----------------------------------------------------------------------------%
%
% Input streams
%
:- typeclass stream.input(Stream, Unit, State, Error)
<= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
pred get(Stream::in, stream.result(Unit, Error)::out,
State::di, State::uo) is det
].
%-----------------------------------------------------------------------------%
%
% Output streams
%
:- typeclass stream.output(Stream, Unit, State, Error)
<= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
pred put(Stream::in, Unit::in, State::di, State::uo) is det
].
%-----------------------------------------------------------------------------%
%
% Duplex streams
%
:- typeclass stream.duplex(Stream, Unit, State, Error)
<= ( stream.input(Stream, Unit, State, Error),
stream.output(Stream, Unit, State, Error),
(Stream -> State, Error)) where [].
%----------------------------------------------------------------------------%
%
% Putback streams
%
:- typeclass stream.putback(Stream, Unit, State, Error)
<= (stream.input(Stream, Unit, State, Error),
(Stream -> State, Error)) where
[
pred unget(Stream::in, Unit::in, State::di, State::uo) is det
].
:- typeclass stream.unbounded_putback(Stream, Unit, State, Error)
<= ( stream.putback(Stream, Unit, State, Error),
(Stream -> State, Error)) where [].
%----------------------------------------------------------------------------%
%
% Buffered streams
%
% XXX What about streams where we can control the buffering?
:- typeclass stream.buffered(Stream, State, Error)
<= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
pred flush(Stream::in, State::di, State::uo) is det
].
%----------------------------------------------------------------------------%
%
% Seekable streams
%
:- type stream.whence
---> set
; cur
; end.
% XXX call this random_access?
%
% :- typeclass stream.seekable(Stream, State, Error)
% <= stream(Stream, State, Error) where [
% pred seek(Stream::in, stream.whence::in, int::in, State::di, State::uo)
% is det
% ].
%----------------------------------------------------------------------------%
%
% Line oriented streams
%
:- typeclass stream.text(Stream, State, Error)
<= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
pred get_line(Stream::in, int::out, State::di, State::uo) is det,
pred set_line(Stream::in, int::in, State::di, State::uo) is det
].
%-----------------------------------------------------------------------------%
% It would probably also be useful to have something like the following.
% :- typeclass stream.standard_reader(Stream, Unit, State)
% <= ( stream.input(Stream, Unit, State),
% stream.buffered(Stream, State),
% stream.text(Stream, State)) where [].
%
% :- typeclass stream.standard_writer(Stream, Unit, State)
% <= ( stream.output(Stream, Unit, State),
% stream.putback(Stream, Unit, State),
% stream.text(Stream, State)) where [].
%-----------------------------------------------------------------------------%
:- end_module stream.
%-----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: buffer.m.
% Author: juliensf.
% This file provides a string buffer type that can be used to implement
% string streams.
%-----------------------------------------------------------------------------%
:- module buffer.
:- interface.
:- import_module char.
:- import_module io.
:- import_module std_util.
:- import_module stream.
:- import_module string.
%-----------------------------------------------------------------------------%
:- type buffer.
:- type buffer.name == string.
:- type buffer.error.
:- pred buffer.new(buffer.name::in, string::in, buffer::out,
io::di, io::uo) is det.
% Retrieve the name associated with a string buffer.
%
:- func buffer.name(buffer) = string.
% Get the next char from the front of the buffer.
%
:- pred buffer.get_char(buffer::in, maybe(char)::out, io::di, io::uo) is det.
% Place a character on the front of the buffer.
%
:- pred buffer.unget_char(buffer::in, char::in, io::di, io::uo) is det.
% Add a char to the back of the buffer.
%
:- pred buffer.put_char(buffer::in, char::in, io::di, io::uo) is det.
% Add a string to the back of the buffer.
%
:- pred buffer.put_string(buffer::in, string::in, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%
:- instance stream.error(buffer.error).
:- instance stream.stream(buffer, io.state, buffer.error).
:- instance stream.input(buffer, char, io.state, buffer.error).
:- instance stream.output(buffer, char, io.state, buffer.error).
:- instance stream.output(buffer, string, io.state, buffer.error).
:- instance stream.duplex(buffer, char, io.state, buffer.error).
:- instance stream.putback(buffer, char, io.state, buffer.error).
:- instance stream.unbounded_putback(buffer, char, io.state, buffer.error).
%-----------------------------------------------------------------------------%
%
% Debugging predicates
%
% Dump the contents of the buffer to stdout.
%
:- pragma obsolete(buffer.dump/3).
:- pred buffer.dump(buffer::in, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module exception.
:- import_module store.
:- import_module require.
%-----------------------------------------------------------------------------%
:- type buffer
---> buffer(
buffer_name :: string,
buffer_contents :: io_mutvar(string)
).
:- type buffer.error ---> buffer_error.
%-----------------------------------------------------------------------------%
buffer.new(Name, Contents, Buffer, !IO) :-
store.new_mutvar(Contents, Mutvar, !IO),
Buffer = buffer(Name, Mutvar).
buffer.name(Buffer) = Buffer ^ buffer_name.
buffer.get_char(Buffer, MaybeChar, !IO) :-
store.get_mutvar(Buffer ^ buffer_contents, Contents, !IO),
( string.first_char(Contents, Char, Remainder) ->
MaybeChar = yes(Char),
store.set_mutvar(Buffer ^ buffer_contents, Remainder, !IO)
;
MaybeChar = no
).
buffer.unget_char(Buffer, Char, !IO) :-
store.get_mutvar(Buffer ^ buffer_contents, Contents0, !IO),
Contents = string.char_to_string(Char) ++ Contents0,
store.set_mutvar(Buffer ^ buffer_contents, Contents, !IO).
buffer.put_char(Buffer, Char, !IO) :-
store.get_mutvar(Buffer ^ buffer_contents, Contents0, !IO),
Contents = Contents0 ++ string.char_to_string(Char),
store.set_mutvar(Buffer ^ buffer_contents, Contents, !IO).
buffer.put_string(Buffer, String, !IO) :-
store.get_mutvar(Buffer ^ buffer_contents, Contents0, !IO),
Contents = Contents0 ++ String,
store.set_mutvar(Buffer ^ buffer_contents, Contents, !IO).
%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%
:- instance stream.error(buffer.error) where
[
error_message(_) = throw(software_error("buffer.error"))
].
:- instance stream.stream(buffer, io.state, buffer.error) where
[
name(Buffer, buffer.name(Buffer), !IO)
].
:- instance stream.input(buffer, char, io.state, buffer.error) where
[
( get(Buffer, Result, !IO) :-
buffer.get_char(Buffer, MaybeChar, !IO),
(
MaybeChar = yes(Char),
Result = ok(Char)
;
MaybeChar = no,
Result = eof
)
)
].
:- instance stream.output(buffer, char, io.state, buffer.error) where
[
pred(put/4) is buffer.put_char
].
:- instance stream.output(buffer, string, io.state, buffer.error) where
[
pred(put/4) is buffer.put_string
].
:- instance stream.duplex(buffer, char, io.state, buffer.error) where [].
:- instance stream.putback(buffer, char, io.state, buffer.error) where
[
pred(unget/4) is buffer.unget_char
].
:- instance stream.unbounded_putback(buffer, char, io.state, buffer.error)
where [].
%-----------------------------------------------------------------------------%
%
% Debugging predicates
%
buffer.dump(Buffer, !IO) :-
store.get_mutvar(Buffer ^ buffer_contents, Contents, !IO),
io.write_string(Contents, !IO).
%-----------------------------------------------------------------------------%
:- end_module buffer.
%-----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------r
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: stream_io.m.
% Author: juliensf
% This module provides file streams.
% XXX The io module needs a bit of rewriting for this to work correctly.
%-----------------------------------------------------------------------------%
:- module stream_io.
:- interface.
:- import_module char.
:- import_module io.
:- import_module stream.
%----------------------------------------------------------------------------%
%
% I/O errors
%
:- instance stream.error(io.error).
%----------------------------------------------------------------------------%
%----------------------------------------------------------------------------%
%
% Text streams
:- type line == string.
%----------------------------------------------------------------------------%
%
% Text input streams
%
% XXX Commented out to avoid overlapping instance with io.output_stream,
% since they are both equivalent to io.stream.
% :- instance stream.stream(io.input_stream, io.state, io.error).
% :- instance stream.input(io.input_stream, char, io.state, io.error).
% :- instance stream.input(io.input_stream, line, io.state, io.error).
% :- instance stream.text(io.input_stream, io.state, io.error).
% :- instance stream.putback(io.input_stream, char, io.state, io.error).
%----------------------------------------------------------------------------%
%
% Text output streams
%
:- instance stream.stream(io.output_stream, io.state, io.error).
:- instance stream.output(io.output_stream, char, io.state, io.error).
:- instance stream.output(io.output_stream, float, io.state, io.error).
:- instance stream.output(io.output_stream, int, io.state, io.error).
:- instance stream.output(io.output_stream, string, io.state, io.error).
:- instance stream.text(io.output_stream, io.state, io.error).
:- instance stream.buffered(io.output_stream, io.state, io.error).
%----------------------------------------------------------------------------%
%----------------------------------------------------------------------------%
:- implementation.
%----------------------------------------------------------------------------%
:- instance stream.error(io.error) where
[
func(stream.error_message/1) is io.error_message
].
%----------------------------------------------------------------------------%
%
% Text input streams
%
% :- instance stream(io.input_stream, io.state, io.error) where
% [
% pred(name/4) is io.input_stream_name
% ].
%
% :- instance stream.input(io.input_stream, char, io.state, io.error) where
% [
% ( get(Stream, Result, !IO) :-
% io.read_char(Stream, Result0, !IO),
% (
% Result0 = ok(Char),
% Result = ok(Char)
% ;
% Result0 = eof,
% Result = eof
% ;
% Result0 = error(IOError),
% Result = error(IOError)
% )
% )
% ].
%
% :- instance stream.input(io.input_stream, line, io.state, io.error) where
% [
% ( get(Stream, Result, !IO) :-
% io.read_line_as_string(Stream, Result0, !IO),
% (
% Result0 = ok(Line),
% Result = ok(Line)
% ;
% Result0 = eof,
% Result = eof
% ;
% Result0 = error(IOError),
% Result = error(IOError)
% )
% )
% ].
%
% :- instance stream.text(io.input_stream, io.state, io.error) where
% [
% pred(get_line/4) is io.get_line_number,
% pred(set_line/4) is io.set_line_number
% ].
%
% :- instance stream.putback(io.input_stream, char, io.state, io.error) where
% [
% pred(unget/4) is io.putback_char
% ].
%
%----------------------------------------------------------------------------%
%
% Text output streams
%
:- instance stream(io.output_stream, io.state, io.error) where
[
pred(name/4) is io.output_stream_name
].
:- instance stream.output(io.output_stream, char, io.state, io.error) where
[
( put(Stream, Char, !IO) :-
io.write_char(Stream, Char, !IO)
)
].
:- instance stream.output(io.output_stream, float, io.state, io.error) where
[
( put(Stream, Float, !IO) :-
io.write_float(Stream, Float, !IO)
)
].
:- instance stream.output(io.output_stream, int, io.state, io.error) where
[
( put(Stream, Integer, !IO) :-
io.write_int(Stream, Integer, !IO)
)
].
:- instance stream.output(io.output_stream, string, io.state, io.error) where
[
( put(Stream, String, !IO) :-
io.write_string(Stream, String, !IO)
)
].
:- instance stream.buffered(io.output_stream, io.state, io.error) where
[
pred(flush/3) is io.flush_output
].
:- instance stream.text(io.output_stream, io.state, io.error) where
[
pred(get_line/4) is io.get_output_line_number,
pred(set_line/4) is io.set_output_line_number
].
%----------------------------------------------------------------------------%
:- end_module stream_io.
%----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: rot13_stream.m.
% Author: juliensf.
% An example of an encryption stream using the stream module.
%-----------------------------------------------------------------------------%
:- module rot13_stream.
:- interface.
:- import_module char.
:- import_module io.
:- import_module std_util.
:- import_module stream.
:- import_module string.
%-----------------------------------------------------------------------------%
:- type rot13_stream.
:- type rot13_stream.name == string.
:- type rot13_stream.error.
:- pred rot13_stream.new(rot13_stream.name::in, rot13_stream::out,
io::di, io::uo) is det.
:- func rot13_stream.name(rot13_stream) = string.
% Get the next encrypted character. Returns `no' if the stream is empty.
%
:- pred rot13_stream.get(rot13_stream::in, maybe(char)::out,
io::di, io::uo) is det.
% Encrypt a character.
%
:- pred rot13_stream.put_char(rot13_stream::in, char::in,
io::di, io::uo) is det.
% Encrypt a string.
%
:- pred rot13_stream.put_string(rot13_stream::in, string::in,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%
:- instance stream.error(rot13_stream.error).
:- instance stream.stream(rot13_stream, io, rot13_stream.error).
:- instance stream.input(rot13_stream, char, io, rot13_stream.error).
:- instance stream.output(rot13_stream, char, io, rot13_stream.error).
:- instance stream.output(rot13_stream, string, io, rot13_stream.error).
:- instance stream.duplex(rot13_stream, char, io, rot13_stream.error).
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module exception.
:- import_module int.
:- import_module list.
:- import_module queue.
:- import_module require.
:- import_module store.
:- import_module svqueue.
%-----------------------------------------------------------------------------%
:- type rot13_stream.error ---> rot13_stream_error.
% This isn't going to win any prizes for efficiency ...
%
:- type rot13_stream
---> rot13(
rot13_name :: string,
rot13_queue :: io_mutvar(queue(char))
).
%-----------------------------------------------------------------------------%
rot13_stream.new(Name, Rot13Stream, !IO) :-
store.new_mutvar(queue.init, Mutvar, !IO),
Rot13Stream = rot13(Name, Mutvar).
rot13_stream.name(Rot13Stream) = Rot13Stream ^ rot13_name.
rot13_stream.get(Rot13Stream, Result, !IO) :-
store.get_mutvar(Rot13Stream ^ rot13_queue, Queue0, !IO),
( queue.get(Queue0, Char, Queue) ->
Result = yes(Char),
store.set_mutvar(Rot13Stream ^ rot13_queue, Queue, !IO)
;
Result = no
).
rot13_stream.put_char(Rot13Stream, Char, !IO) :-
some [!Queue] (
store.get_mutvar(Rot13Stream ^ rot13_queue, !:Queue, !IO),
EncryptedChar = rot13(Char),
svqueue.put(EncryptedChar, !Queue),
store.set_mutvar(Rot13Stream ^ rot13_queue, !.Queue, !IO)
).
rot13_stream.put_string(Rot13Stream, String, !IO) :-
some [!Queue] (
store.get_mutvar(Rot13Stream ^ rot13_queue, !:Queue, !IO),
EncryptedChars = list.map(rot13, string.to_char_list(String)),
svqueue.put_list(EncryptedChars, !Queue),
store.set_mutvar(Rot13Stream ^ rot13_queue, !.Queue, !IO)
).
%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%
:- instance stream.error(rot13_stream.error) where
[
error_message(_) = throw(software_error("rot13_stream.error"))
].
:- instance stream.stream(rot13_stream, io, rot13_stream.error) where
[
name(Rot13Stream, rot13_stream.name(Rot13Stream), !IO)
].
:- instance stream.input(rot13_stream, char, io, rot13_stream.error) where
[
( get(Stream, Result, !IO) :-
rot13_stream.get(Stream, Result0, !IO),
(
Result0 = yes(Char),
Result = ok(Char)
;
Result0 = no,
Result = eof
)
)
].
:- instance stream.output(rot13_stream, char, io, rot13_stream.error) where
[
pred(put/4) is rot13_stream.put_char
].
:- instance stream.output(rot13_stream, string, io, rot13_stream.error) where
[
pred(put/4) is rot13_stream.put_string
].
:- instance stream.duplex(rot13_stream, char, io, rot13_stream.error)
where [].
%-----------------------------------------------------------------------------%
%
% Pinched from samples/rot13/rot13_concise.m
%
:- func rot13(char) = char.
rot13(Char) = rot_n(13, Char).
:- func rot_n(int, char) = char.
rot_n(N, Char) = RotChar :-
char_to_string(Char, CharString),
( sub_string_search(alphabet, CharString, Index) ->
NewIndex = (Index + N) mod cycle + cycle * (Index // cycle),
index_det(alphabet, NewIndex, RotChar)
;
RotChar = Char
).
% The length of `alphabet' should be a multiple of `cycle'.
%
:- func alphabet = string.
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".
:- func cycle = int.
cycle = 26.
%-----------------------------------------------------------------------------%
:- end_module rot13_stream.
%-----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: test_streams.m.
% Author: juliensf.
% A simple testcase for the stream typeclass.
%-----------------------------------------------------------------------------%
:- module test_streams.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module char.
:- import_module stream.
:- import_module buffer.
:- import_module stream_io.
:- import_module string.
:- import_module rot13_stream.
%-----------------------------------------------------------------------------%
main(!IO) :-
buffer.new("<<string_buffer>>", "Hello", StringBuffer, !IO),
io.stdout_stream(Stdout, !IO),
stream.put(StringBuffer, " World", !IO),
stream.put(StringBuffer, '!', !IO),
stream.put(StringBuffer, '\n', !IO),
rot13_stream.new("<<rot13_stream>>", Rot13Stream, !IO),
stream.put(Rot13Stream, "Hello World!\n", !IO),
io.write_string("StringBuffer = \n", !IO),
write('a', StringBuffer, Stdout, !IO),
io.write_string("Rot13Stream = \n", !IO),
write('a', Rot13Stream, Stdout, !IO).
:- pred write(Unit::unused, Input::in, Output::in, io::di, io::uo) is det
<= (
stream.input(Input, Unit, io, InputError),
stream.output(Output, Unit, io, OutputError)
).
write(Unit, InputStream, OutputStream, !IO) :-
get(InputStream, Result, !IO),
(
Result = ok(Char : Unit),
stream.put(OutputStream, Char, !IO),
write(Unit, InputStream, OutputStream, !IO)
;
Result = eof
;
Result = error(_)
).
%-----------------------------------------------------------------------------%
:- end_module test_streams.
%-----------------------------------------------------------------------------%
More information about the developers
mailing list