Ada Programming/Libraries/Ada.Streams
This language feature is available from Ada 95 on.
Ada.Streams is a unit of the Predefined Language Environment since Ada 95.
Description
[edit | edit source]Ada streams are a powerful I/O mechanism that allows reading and writing any type of object to any type of "medium" (e.g., a network connection, a file on a disk, a magnetic tape, a memory buffer). Streams are somewhat obscure for a beginner; this is because of the "double generality": generality about the object to be written/read and generality about the medium involved. The objective of this section is to give an intuitive introduction to Ada streams, skipping some of the finer details. The reader is referred for a more precise and detailed description of Ada streams to the Ada Reference Manual, in particular 13.13: Streams [Annotated].
Concepts
[edit | edit source]The language designers split the problem of writing an object over a medium into two sub-problems:
- Convert the object in a sequence of bits
- Write the bits over the stream
Note that the first step depends only on the object to be sent and not on the actual medium. On the other hand, the details of the second step depend only on the employed medium and not on the object type.
Similarly, in order to "read" an object from a network connection one must
- Read a block of bits from the stream
- Parse the read block and convert it into an object
Note again that the first step depends only on the medium, while the second one depends only on the object type.
Abstract streams
[edit | edit source]The abstract model for an Ada stream is, basically, a sequence of raw data (Stream_Element) that can be read and written in blocks. This abstract view is formalized in the Stream package definition (from RM 13.13.1: The Package Streams. [Annotated] with some omissions and comments added)
package
Ada.Streamsis
type
Root_Stream_Typeis
abstract
tagged
limited
private
; -- Elementary piece of data. A Stream is a sequence of Stream_Elementtype
Stream_Elementis
mod
implementation defined;type
Stream_Element_Offsetis
range
implementation defined; -- A block of datatype
Stream_Element_Arrayis
array
(Stream_Element_Offsetrange
<>)of
aliased
Stream_Element; -- Abstract procedure that reads a block of dataprocedure
Read ( Stream :in
out
Root_Stream_Type; Item :out
Stream_Element_Array; Last :out
Stream_Element_Offset)is
abstract
; -- Abstract procedure that writes a block of dataprocedure
Write ( Stream :in
out
Root_Stream_Type; Item :in
Stream_Element_Array)is
abstract
;private
implementation defined...end
Ada.Streams;
Since the type Root_Stream_Type is abstract
, one cannot create objects of type Root_Stream_Type, but must first derive a new type from Root_Stream_Type. Ada.Streams just specifies the minimal interface that a stream must grant: with a stream we must be able to
- read a block of data (with the procedure Read) and
- write a block of data (with Write).
Typically for every new medium (for example, network connections, disk files, memory buffers) one will derive a new type specialized to read and write to that medium. Note that both Read and Write are abstract, so that any non-abstract
type must necessarily override them with new procedures that will take care of the details of reading/writing from/to a specific medium.
Note that the minimal interface of Ada.Streams does not include, for example, functions to open or close a stream, nor functions to check, say, an End-Of-Stream condition. This is reasonable since the details of the interfaces of those functions depend on the specific medium: a function that opens a stream associated to a file will expect a file name as argument, a function for opening a network stream will probably expect a network address and a function for opening the stream associated to a memory buffer will probably need the address and size of the buffer. It will be the duty of the package that derives from Root_Stream_Type to define those "auxiliary" functions.
Serialization functions
[edit | edit source]The second ingredient in the Ada stream system is what we called serialization functions, that is, the functions whose duty is to convert an Ada object to a sequence of Stream_Elements and vice-versa. Actually, we will see in a moment that the serialization functions do not interact with the caller by passing back and forth arrays of Stream_Element's, rather they interact directly with the streams.
The serialization functions associated to a given type are defined as type attributes. For every subtype S of a type T, Ada defines the following attributes associated to stream-related functions and procedures
Type | Input | Output |
---|---|---|
Simple | S'Read | S'Write |
Simple, class-wide | S'Class'Read | S'Class'Write |
Composite | S'Input | S'Output |
Composite, class-wide | S'Class'Input | S'Class'Output |
We will first describe S'Read and S'Write since they are the simplest and, in some sense, the most "primitive" ones.
Write attribute
[edit | edit source]Procedure S'Write is defined in 13.13.2: Stream-Oriented Attributes (3) [Annotated] as follows (remember that S is a subtype of type T)
procedure
S'Write( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :in
T);
The duty of S'Write is to convert Item to a sequence of Stream_Elements and write the result on Stream. Note that Stream is an access to class-wide type Root_Stream_Type'Class, therefore the programmer can use S'Write with any stream type derived from Root_Stream_Type.
According to 13.13.2: Stream-Oriented Attributes (9) [Annotated], Ada defines default implementations for S'Write as follows
- For elementary types (e.g., Integers, Character, Float) the default implementations write a suitable representation of Item to Stream. That representation is implementation dependent but, most of the time, this corresponds simply to the in-memory representation.
- For composite types (e.g., record and array) the default implementation writes each component (array entry or record component) using the corresponding S'Write procedure. Note that no other information is written. For example, if Item is an array, the array dimensions are not written; if Item has a discriminant with no default value, the discriminant is not written. In some sense, S'Write writes a very "raw" representation of Item to Stream.
Clearly, the default implementation, being dependent on the machine and compiler, can be useful only if the data is written and read by programs compiled with the same compiler. If the data, for example, is to be sent across the network and read by a program written in another language, running on an unknown architecture, it is important for the programmer to control the format of the data sent over the wire. Because of this exigence, Ada allows the programmer to override S'Write (and the other stream-related functions described in the following), using an attribute definition clause (RM 13.3 [Annotated]):
for
S'Writeuse
user_defined_subprogram;
Suppose, for example, a network protocol requires to format data in the following textual length-type-value format
- Integer values are formatted as "<len> i <value>", where <len> is the number of digits used to represent the integer and <value> is the integer expressed in base 10. (For example, the integer 42 would be represented as "2i42")
The following code defines a suitable S'Write procedure for the integer case (Note: for the sake of simplicity, the following code supposes that each Stream_Element is 8 bits long)
package
Exampleis
type
Intis
new
Integer;type
Int_Arrayis
array
(Intrange
<>) of Int;procedure
Print ( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :in
Int);for
Int'WriteUse
Print;end
Example;
package
body
Exampleis
procedure
Print ( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :in
Int)is
-- Convert Item to String (with no trailing space) Value : String := Trim(Int'Image(Item), Left); -- Convert Value'Length to String (with no trailing space) Len : String := Trim(Integer'Image(Value'Length), Left); Descr : String := Len & 'i' & Value; Buffer : Stream_Element_Array (1 .. Stream_Element_Offset (Descr'Length));begin
-- Copy Descr to Bufferfor
Iin
Buffer'Rangeloop
Buffer (I) := Stream_Element (Character'Pos (Descr (Integer (I))));end
loop
; -- Write the result to Stream Stream.Write(Buffer);end
Print;end
Example;
Note the structure of Print: first Item is "serialized" in a sequence of Stream_Element (contained in Buffer), then such a sequence is written to Stream by calling the Write method (that will take care of the details of writing on Stream). Suppose now that one wants to print the description of 42 to the standard output. The following code can be used
with
Ada.Text_IO.Text_Streams;use
Ada.Text_IO; -- defines Current_Output. See RM A.10.1 [Annotated] -- Text_Streams.Stream (Current_Output) returns a stream access -- associated with the file given as parameter Int'Write (Text_Streams.Stream (Current_Output), 42);
The result will be "2i42" printed on the standard output. Note that the following code
Int_Array'Write (Text_Streams.Stream (Current_Output), (1=>42, 2=>128, 3=>6));
would write on standard output the string "2i42_3i128_1i6" (the '_' are not actually present; they have been added for readability) corresponding to calling Int'Write on 42, 128 and 6 in sequence. Note that the array dimensions are not written.
If one wanted to send the same description across a TCP connection, the following code could be used (with GNAT)
with
GNAT.Sockets;use
GNAT; ... Sock : Sockets.Socket_Type; Server : Sockets.Sock_Addr_Type := server address; ... Sockets.Create_Socket (Sock); Sockets.Connect_Socket (Sock, Server); -- Here Sock is connected to the remote server -- Use Sockets.Stream to convert Sock to a stream -- First send the integer 42 Int'Write (Sockets.Stream (Sock), 42); -- Now send the array Int_Array'Write (Sockets.Stream (Sock), (1=>42, 2=>128, 3=>6));
Read attribute
[edit | edit source]Procedure S'Read is defined in 13.13.2: Stream-Oriented Attributes (6) [Annotated] as follows
procedure
S'Read( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :out
T);
Its behavior is clearly symmetric to the one of S'Write: S'Read reads one or more Stream_Element from Stream and "parse" them to construct Item. Similarly to the case of S'Write, Ada defines default implementations for S'Read that the programmer can override by using the attribute definition clause
for
S'Readuse
...
For example, the following procedure could be assigned to type Int with for
Int'Read use
Parse;.
procedure
Parse ( Stream :not
null
access
Root_Stream_Type'Class; Item :out
Int)is
Len : Integer := 0; Buffer : Stream_Element_Array (1 .. 1); Last : Stream_Element_Offset; Zero : Stream_Element := Stream_Element (Character'Pos ('0')); Nine : Stream_Element := Stream_Element (Character'Pos ('9'));begin
-- Extract the length from the streamloop
-- Read one element from the stream Stream.Read (Buffer, Last);exit
when
not
(Buffer (1)in
Zero .. Nine); Len := Len * 10 + Integer (Buffer (1) - Zero);end
loop
; -- Check for the correct delimiterif
Character'Val (Integer (Buffer (1))) /= 'i'then
raise
Data_Error;end
if
; -- Now convert the following Len characters Item := 0;for
Iin
1 .. Lenloop
Stream.Read (Buffer, Last); Item := 10 * Item + Int (Buffer (1) - Zero);end
loop
;end
Parse;
Output attribute
[edit | edit source]Procedure S'Output is defined in 13.13.2: Stream-Oriented Attributes (19) [Annotated] as follows
procedure
S'Output( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :in
T);
S'Output differs from S'Write in that its default implementation
- first it writes arrays bound (if S is an array) and discriminants (if S is a record).
- then it calls S'Write to write Item itself
Note that the bounds or the discriminant are written by calling the respective S'Write procedures. Therefore, since Int_Array was defined above as an array of Int indexed by Int, the following line
Int_Array'Output (Text_Streams.Stream (Current_Output), (1 => 42, 2 => 128, 3 => 6));
would produce (the '_' are added for readability and are not actually present in the output)
1i1_1i3_2i42_3i128_1i6
Note the array bounds "1i1" and "1i3" at the beginning of the line.
Input attribute
[edit | edit source]Function S'Input is defined in 13.13.2: Stream-Oriented Attributes [Annotated] as follows
function
S'Input( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class)return
T;
S'Input is for S'Read what S'Output is for S'Write in the sense that S'Read
- first it reads the bounds or the discriminants (using the corresponding S'Read)
- it uses the read values to create the object to be returned
- it calls the corresponding S'Read to initialize the object
Note that S'Input is a function, while S'Read is a procedure. This is coherent with the fact when S'Read is called any bound and/or discriminant must be already known, so that the caller can create the object and pass it to S'Read. With S'Input, on the other hand, the bounds/discriminants are not known, but read from the stream; therefore, the burden of creating the object is on S'Input.
Class-wide Read and Write
[edit | edit source]Note that S'Read and S'Write are not primitive subprograms of S and they cannot be dynamically dispatching, even if S is a tagged type. In order to allow for dynamical dispatching of S'Read and S'Write methods, 13.13.2: Stream-Oriented Attributes [Annotated] defines procedures
procedure
S'Class'Write( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :in
T'Class);procedure
S'Class'Read( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :out
T'Class);
Note that in both cases the type of Item is T'Class, so Item can be of any type derived from T. The behavior of those procedures is to dispatch to the actual S'Write or S'Read identified by the tag of Item. See Ada Programming/Input Output/Stream Tutorial/Example for an example of usage of the class-wide stream attributes S'Class'Read and S'Class'Write.
Class-wide Input and Output
[edit | edit source]Similarly to the case of S'Read and S'Write, 13.13.2: Stream-Oriented Attributes [Annotated] defines the class-wide versions of S'Output and S'Input
procedure
S'Class'Output( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class; Item :in
T'Class)function
S'Class'Input( Stream :not
null
access
Ada.Streams.Root_Stream_Type'Class)return
T'Class;
Their default behavior is almost obvious when one remembers that a tagged type can actually be considered as a record with an "hidden discriminant"
- S'Class'Output first writes the tag to Stream by first converting it to string and then calling String'Output on the result. Successively, S'Class'Output dispatches to the subprogram S'Output of the specific type identified by the tag.
- S'Class'Input first reads the tag from Stream by first calling String'Input and converting the result to a tag. Successively, S'Class'Input dispatches to the subprogram S'Input of the specific type identified by the tag.
See 13.13.2: Stream-Oriented Attributes [Annotated] for a more detailed and precise explanation. See Ada Programming/Libraries/Ada.Streams/Example for an example of usage of the class-wide stream attributes.
Specification
[edit | edit source]-- Standard Ada library specification -- Copyright (c) 2003-2018 Maxim Reznik <reznikmm@gmail.com> -- Copyright (c) 2004-2016 AXE Consultants -- Copyright (c) 2004, 2005, 2006 Ada-Europe -- Copyright (c) 2000 The MITRE Corporation, Inc. -- Copyright (c) 1992, 1993, 1994, 1995 Intermetrics, Inc. -- SPDX-License-Identifier: BSD-3-Clause and LicenseRef-AdaReferenceManual -- -------------------------------------------------------------------------package
Ada.Streamsis
pragma
Pure (Streams);type
Root_Stream_Typeis
abstract
tagged
limited
private
;pragma
Preelaborable_Initialization (Root_Stream_Type);type
Stream_Elementis
mod
implementation_defined;type
Stream_Element_Offsetis
range
implementation_defined .. implementation_defined;subtype
Stream_Element_Countis
Stream_Element_Offsetrange
0..Stream_Element_Offset'Last;type
Stream_Element_Arrayis
array
(Stream_Element_Offsetrange
<>)of
aliased
Stream_Element;procedure
Read (Stream :in
out
Root_Stream_Type; Item :out
Stream_Element_Array; Last :out
Stream_Element_Offset)is
abstract
;procedure
Write (Stream :in
out
Root_Stream_Type; Item :in
Stream_Element_Array)is
abstract
;private
pragma
Import (Ada, Root_Stream_Type);end
Ada.Streams;
See also
[edit | edit source]Wikibook
[edit | edit source]External examples
[edit source]- Search for examples of
Ada.Streams
in: Rosetta Code, GitHub (gists), any Alire crate or this Wikibook. - Search for posts related to
Ada.Streams
in: Stack Overflow, comp.lang.ada or any Ada related page.
Ada Reference Manual
[edit | edit source]Ada 95
[edit | edit source]Ada 2005
[edit | edit source]Ada 2012
[edit | edit source]Open-Source Implementations
[edit | edit source]FSF GNAT
- Specification: a-stream.ads
- Body: a-stream.adb
drake
- Specification: a-stream.ads