english
version "1.0"
identify "xyz"

#: Copyright (c) 1998-2005 by Wayne C. Gramlich.
#, All rights reserved.

module io_dispatcher

#: This module implements an I/O dispatcher.
#,
#, The concept behind the I/O dispatcher is that it allows a program
#, to deal with multiple channels of I/O simultaineously.  This is
#, accomplished via having a separate call back routine for each I/O
#, channel.  Whenever there is input available (for an input channel) or
#, output capacity is available (for an output channel), its corresponding
#, call back routine is invoked to deal with the I/O activity.  In addition
#, to standard file/network input/output, the I/O dispatcher can listen
#, for network connections on one and/or more ports and perform time
#, delayed wake up calls.  Finally, it is possible to have one or more
#, call backs that are invoked when no I/O activity is present; these are
#, called idle call backs.  An any call back routine can poll the
#, {io_dispatcher} object to determine whether there is any pending
#, I/O avaiable.  Before each callback routine returns, it has the option
#, of performing some operations control when (or if) the call back
#, routine will next be invoked.  If every callback routine in the
#, {io_dispatcher} is careful to never perform a blocking I/O opertaion
#, the {io_dispatcher} will never `freeze up'.
#,
#, Until co-types are implemented in STIPPLE, the io_dispatcher object is
#, based around a single parameterized type called {state}.  Each I/O
#, channel has potentially its own instantiation of a {state} object.  While
#, I/O channels can share the same {state} object, there is no requirement
#, that they do so.  One common usage of the {state} type is share a
#, {state} record across all I/O channels.  Another common usage of the
#, {state} type is that is will be implemented as a variant type and each
#, I/O channel will get its own instantiation of the variant type.  The
#, callback routine will use a STIPPLE extract statement to obtain the
#, necessary information for the callback.
#,
#, Ultimately, the {io_dispatcher} module is organized around the
#, capabilities of the Unix select(2) system call.

define io_dispatcher[state]		#: Input/Output dispatcher
    record
	actual_read_file_set file_set	#: Actual files ready for reading
	actual_write_file_set file_set	#: Actual files ready for writing
	actual_exception_file_set file_set #: Actual files with exceptions
	desired_read_file_set file_set	#: Desired files for reading
	desired_write_file_set file_set	#: Desired files for writing
	desired_exception_file_set file_set #: Desired files for exceptions
	io_listens vector[io_listen[state]] #: List of ports to listen to
	read_channels vector[read_channel[state]] #: List of read channels
	result string			#: Return result
	run logical			#: {true} => keep running
	trace_stream out_stream		#: Tracing stream
	unix_system unix_system		#: Unix system to use
	write_channels vector[write_channel[state]] #: List of write channels
    generate address_get, allocate, erase, identical, print

define io_listen[state]			#: One network listen `channel'
    routine_types
	procedure listen_call_back
	    takes channel_pair[state], state
	    returns_nothing
	procedure read_call_back
	    takes read_channel[state]
	    returns_nothing
	procedure write_call_back
	    takes write_channel[state]
	    returns_nothing
    record
	io_dispatcher io_dispatcher[state] #: Parent I/O Dispatcher
	internet_address unsigned	#: The internet address to listen on
	listen_call_back listen_call_back #: The listen call back
	listen_socket_number unsigned	#: Socket number to listen on
	listen_state state		#: Listen call back {state} object
	port unsigned			#: The port address to listen to
	read_buffer_size unsigned	#: Read buffer size
	read_call_back read_call_back	#: Read call back routine
	read_state state		#: Read call back {state} object
	write_buffer_size unsigned	#: Write buffer size
	write_call_back write_call_back	#: Write call back routine
	write_state state		#: Write call back {state} object
    generate address_get, allocate, erase, identical, print

define channel_pair[state]		#: A read/write channel pair
    record
	read_channel read_channel[state] #: The {read_channel} object
	write_channel write_channel[state] #: The {write_channel} object
    generate address_get, allocate, erase, identical, print

define read_channel[state]		#: One read channel
    routine_types
	procedure call_back
	    takes read_channel[state]
	    returns_nothing
    record
	buffer memory			#: I/O buffer
	call_back call_back		#: Call back procedure
	contains unsigned		#: Good bytes contained in {buffer}
	done logical			#: {true}=>connection out of data
	file_descriptor_number unsigned	#: File descriptor number
	id unsigned			#: Identifier
	index unsigned			#: Index into {read_channels}
	io_dispatcher io_dispatcher[state] #: Parent {io_dispatcher} object
	label string			#: String for label
	needed unsigned 		#: Needed number of bytes
	offset unsigned			#: Offset to first byte in {buffer}
	state state			#: I/O channel {state} object
	total_in unsigned		#: Total bytes read into buffer
	total_out unsigned		#: Total bytes read out of buffer
    generate address_get, allocate, erase, identical, print

define write_channel[state]		#: One Input/Output channel
    routine_types
	procedure call_back
	    takes write_channel[state]
	    returns_nothing
    record
	buffer memory			#: I/O buffer
	call_back call_back		#: Call back procedure
	contains unsigned		#: Good bytes contained in {buffer}
	file_descriptor_number unsigned	#: File descriptor number
	id unsigned			#: Identifier
	index unsigned			#: Index into {write_channels}
	io_dispatcher io_dispatcher[state] #: Parent {io_dispatcher} object
	label string			#: String for label
	needed unsigned 		#: Needed number of bytes
	offset unsigned			#: Offset to first byte in {buffer}
	state state			#: I/O channel {state} object
    generate address_get, allocate,erase, identical, print



#: {io_dispatcher} routines:

procedure activate@io_dispatcher[state]
    takes
	io_dispatcher io_dispatcher[state]
    returns string

    #: This procedure will activate the {io_dispatcher} object so that
    #, it will start performing I/O via the various activated call back
    #, procedures.  Whenever there is no more pending I/O (i.e. all
    #, files and/or sockets are closed and no more listens are active),
    #, this procedure will return with a return value of ??@{string}.
    #, Alternatively, a call to {terminate}@{io_dispatcher}() will cause
    #, an immediate return with with a return value that is specified as
    #, an argument to {terminate}().


procedure connect@io_dispatcher[state]
    routine_types
	procedure read_call_back
	    takes read_channel[state]
	    returns_nothing
	procedure write_call_back
	    takes write_channel[state]
	    returns_nothing
    takes
	io_dispatcher io_dispatcher[state]
	internet_address unsigned
	port unsigned
	read_buffer_size unsigned
	read_call_back read_call_back
	read_state state
	write_buffer_size unsigned
	write_call_back write_call_back
	write_state state
    returns channel_pair[state]

    #: This procedure will cause {io_dispatcher} to establish a network
    #, connection port {port} of internet address {internet_address}
    #, return a {channel_pair} object that contains the corresponding
    #, {read_channel} and {write_channel} objects.  The {read_channel}
    #, has a read buffer size of {read_buffer_size} and a call back
    #, procedure of {read_call_back}.  The {write_channel} has a write
    #, buffer size of {write_buffer_size} and a call back procedure of
    #, {write_call_back}.  The {read_state} object is passed into the
    #, read {read_channel} object and the {write_state} object is passed
    #, into the write {write_channel} object.  If the network connection
    #, is not successfully established ??@{channel_pair}[{state}] is
    #, returned.  Please invoke {unix_system}@{status_get}() to potentially
    #, find out why the connection did not succeed.
    #,
    #, Unfortunately, the current implementation of this operation can
    #, cause the I/O dispatcher to block for 10's of seconds.  Sorry.


procedure channel_pair_allocate@io_dispatcher[state]
    routine_types
	procedure read_call_back
	    takes read_channel[state]
	    returns_nothing
	procedure write_call_back
	    takes write_channel[state]
	    returns_nothing
    takes
	io_dispatcher io_dispatcher[state]
	file_descriptor_number unsigned
	read_call_back read_call_back
	read_state state
	read_buffer_size unsigned
	write_call_back write_call_back
	write_state state
	write_buffer_size unsigned
    returns channel_pair[state]

    #: This procedure will allocate a {channel_pair} object that
    #, contains a {read_channel} and {write_channel} initialized
    #,  with {file_descriptor}, {read_call_back}, {read_state},
    #, {read_buffer_size}, {write_call_back}, {write_state}, and
    #, {write_buffer_size}.


procedure create@io_dispatcher[state]
    takes
	unix_system unix_system
    returns io_dispatcher[state]

    #: This procedure will create and new unactivated {io_dispatcher} object.


procedure listen@io_dispatcher[state]
    routine_types
	procedure listen_call_back
	    takes channel_pair[state], state
	    returns_nothing
	procedure read_call_back
	    takes read_channel[state]
	    returns_nothing
	procedure write_call_back
	    takes write_channel[state]
	    returns_nothing
    takes
	io_dispatcher io_dispatcher[state]
	internet_address unsigned
	port unsigned
	queue_length unsigned
	listen_call_back listen_call_back
	listen_state state
	read_buffer_size unsigned
	read_call_back read_call_back
	read_state state
	write_buffer_size unsigned
	write_call_back write_call_back
	write_state state
    returns logical

    #: This procedure will cause {io_dispatcher} to listent for network
    #, connections on port {port} of internet address {internet_address}
    #, and a queue length of {queue_length} (see {listen}@{unix_system}().)
    #, Whenever a connection is requested, it will create a {read_channel}
    #, and a {write_channel} object and add them to the active channel list
    #, for {io_dispatcher}.  The {read_channel} will be created to have
    #, buffer size of {read_buffer_size}, a call back routine of
    #, {read_call_back}, and a state value of {read_state}.  Similarly,
    #, the {write_channel} object will be created to have buffer size of
    #, {write_buffer_size}, a call back routine of {write_call_back},
    #, and a state value of {read_state}.  Both I/O channels will be
    #, created with ??@{string} as the channel label and 0 as the
    #, channel identifier.   After the two channels have been created,
    #, {listen_call_back} is invoked with the newly created {read_channel},
    #, the newly created {write_channel}, and {listen_state} as its three
    #, arguments.


procedure read_channel_allocate@io_dispatcher[state]
    routine_types
	procedure call_back
	    takes read_channel[state]
	    returns_nothing
    takes
	io_dispatcher io_dispatcher[state]
	file_descriptor_number unsigned
	call_back call_back
	state state
	buffer_size unsigned
	label string
	id unsigned
    returns read_channel[state]

    #: This procedure cause an read I/O channel object to be allocated to
    #, read data from {file_descriptor_number}.  Whenever, there is data
    #, to be read, {call_back} will be invoked with with the {read_channel}
    #, that is returned by this procedure.  A memory buffer of size
    #, {buffer_size} bytes is allocated and stored in the returned
    #, {read_channel} object.   {state}, {label}, and {id} are stored
    #, in the returned {read_channel} object as well.  The returned
    #, {read_channel} is returned in the activated state.


procedure read_channel_deallocate@io_dispatcher[state]
    takes
	io_dispatcher io_dispatcher[state]
	read_channel read_channel[state]
    returns_nothing

    #: This procedure will deallocate {read_channel} from {io_dispatcher}.


procedure standard_out_allocate@io_dispatcher[state]
    routine_types
	procedure call_back
	    takes write_channel[state]
	    returns_nothing
    takes
	io_dispatcher io_dispatcher[state]
	call_back call_back
	state state
	buffer_size unsigned
	label string
	id unsigned
    returns write_channel[state]

    #: This will return a {write_channel} object for writing to the
    #, standard output file descriptor.  The the return {write_channel}
    #, object will contain {call_back}, {state}, {label}, {id}, and a
    #, memory buffer of size {buffer_size}.
    #,
    #, IMPORTANT: This procdure will change the standard output file
    #, descriptor to be non-blocking.  This effect propogates to the
    #, standard blocking style output streams.  If that is not your
    #, goal, you can open "/dev/tty" instead.


procedure terminate@io_dispatcher[state]
    takes
	io_dispatcher io_dispatcher[state]
	result string
    returns_nothing

    #: This procedure cause the {io_dispatcher} to immediately terminate
    #, with a return value of {return_result}.


procedure unlisten@io_dispatcher[state]
    takes
	io_dispatcher io_dispatcher[state]
	internet_address unsigned
	port unsigned
    returns_nothing

    #: This procedure will cause {io_dispatcher} to stop listening for
    #, network connections on port {port} on internet address
    #, {internet_address}.


procedure write_channel_allocate@io_dispatcher[state]
    routine_types
	procedure call_back
	    takes write_channel[state]
	    returns_nothing
    takes
	io_dispatcher io_dispatcher[state]
	file_descriptor_number unsigned
	call_back call_back
	state state
	buffer_size unsigned
	label string
	id unsigned
    returns write_channel[state]

    #: This procedure cause a write I/O channel object to be allocated to
    #, write data to {file_descriptor_number}.  Whenever, there is data
    #, capacity to be write, {call_back} will be invoked with with the
    #, {write_channel} that is returned by this procedure.  A memory
    #, buffer of size {buffer_size} bytes is allocated d and stored in
    #, the returned {write_channel} object.  {state}, {label},
    #, and {id} are stored in the returned {write_channel} object as well.
    #, The returned {write_object} is returned in the activated state.


procedure write_channel_deallocate@io_dispatcher[state]
    takes
	io_dispatcher io_dispatcher[state]
	write_channel write_channel[state]
    returns_nothing

    #: This procedure will deallocate {write_channel} from {io_dispatcher}.


#: {channel_pair} routines:

procedure close@channel_pair[state]
    takes
	channel_pair channel_pair[state]
    returns_nothing

    #: This procedure will close {channel_pair}.


procedure create@channel_pair[state]
    takes
	read_channel read_channel[state]
	write_channel write_channel[state]
    returns channel_pair[state]

    #: This procedure will create and return a new {channel_pair} object
    #, that contains {read_channel} and {write_channel}.


procedure idle@channel_pair[state]
    takes
	channel_pair channel_pair[state]
    returns logical

    #: This procdure will return {true}@{logical} if {channel_pair} has
    #, no pending input or output and {false} otherwise.


procedure no_more_data@channel_pair[state]
    takes
	channel_pair channel_pair[state]
    returns logical

    #: This procedure will return {true}@{logical} if {channel_pair} has
    #, no more data to be read (i.e. end-of-file or connection closed.)
    #, Otherwise, {false}@{logical} is returned.


procedure state_set@channel_pair[state]
    takes
	channel_pair channel_pair[state]
	state state
    returns_nothing

    #: This procedure will set the state of the {read_channel} and
    #, {write_channel} objects to {state}.


procedure transfer@channel_pair[state]
    takes
	to_channel_pair channel_pair[state]
	from_channel_pair channel_pair[state]
	amount unsigned
    returns unsigned

    #: This procedure will cause up to {amount} bytes from the the
    #, {read_channel} object in {from_channel_pair} to be transfered
    #, to the {write_channel} object in {to_channel_pair}.  The number
    #, of bytes actually transfered is returned.


procedure transfer_all@channel_pair[state]
    takes
	to_channel_pair channel_pair[state]
	from_channel_pair channel_pair[state]
    returns unsigned

    #: This procedure will transfer as many  bytes from the the {read_channel}
    #, object in {from_channel_pair} to the {write_channel} object in
    #, {to_channel_pair}.  The number of bytes actually transfered is returned.


#: {read_channel} routines:

procedure absorb@read_channel[state]
    takes
	read_channel read_channel[state]
	amount unsigned
    returns unsigned

    #: This procedure will cause {amount} bytes from {read_channel}
    #, to be marked as read.  The number of bytes actually marked
    #, as read is returned.


procedure deallocate@read_channel[state]
    takes
	read_channel read_channel[state]
    returns_nothing

    #: This procedure will deallocate {read_channel} from its {io_dispatcher}
    #, scheduler.


procedure fetch1@read_channel[state]
    takes
	read_channel read_channel[state]
	index unsigned
    returns unsigned

    #: This procedure will return the {index}'th byte from {read_channel}'s
    #, active content.


procedure idle@read_channel[state]
    takes
	read_channel read_channel[state]
    returns logical

    #: This procedure will return {true} if there is currently
    #, no data in {read_channel} waiting to be read out.


procedure no_more_data@read_channel[state]
    takes
	read_channel read_channel[state]
    returns logical

    #: This procedure will return {true}@{logical} if {read_channel}
    #, will return no more data and {false} otherwise.


procedure update@read_channel[state]
    takes
	read_channel read_channel[state]
    returns_nothing

    #: This procedure will update whether or not the select statement
    #, should care about this {read_channel}.


#: {write_channel} routines:

procedure available_get@write_channel[state]
    takes
	write_channel write_channel[state]
    returns unsigned

    #: This procedure will return the number of available bytes in
    #, the {write_channel} buffer.


procedure character_append@write_channel[state]
    takes
	write_channel write_channel[state]
	character character
    returns_nothing

    #: This procedure will append {character} to the end of {write_channel}.


procedure string_append@write_channel[state]
    takes
	write_channel write_channel[state]
	text string
    returns_nothing

    #: This procedure will append {text} to the end of {write_channel}.


procedure deallocate@write_channel[state]
    takes
	write_channel write_channel[state]
    returns_nothing

    #: This procedure will deallocate {write_channel} from its {io_dispatcher}
    #, scheduler.


procedure disable@write_channel[state]
    takes
	write_channel write_channel[state]
    returns_nothing

    #: This procedure will disable call backs from {write_channel}.


procedure enable@write_channel[state]
    takes
	write_channel write_channel[state]
    returns_nothing

    #: This procedure will enable call backs from {write_channel}.


procedure idle@write_channel[state]
    takes
	write_channel write_channel[state]
    returns logical

    #: This procedure will return {true} if there is currently
    #, no data in {write_channel} waiting to be written out.


procedure transfer@write_channel[state]
    takes
	write_channel write_channel[state]
	read_channel read_channel[state]
	amount unsigned
    returns unsigned

    #: This procedure will attempt to transfer {amount} bytes from
    #, {read_channel} to {write_channel}.  The actual number of
    #, bytes transfered is returned.


procedure transfer_all@write_channel[state]
    takes
	write_channel write_channel[state]
	read_channel read_channel[state]
    returns unsigned

    #: This procedure will attempt to transfer as many bytes from
    #, {read_channel} to {write_channel} as it possible can.
    #, The actual number of bytes transfered is returned.


procedure update@write_channel[state]
    takes
	write_channel write_channel[state]
    returns_nothing

    #: This procedure will update whether or not the select statement
    #, should care about this {write_channel}.