Numeric_IO & Name_IO:
Ada packages supporting a computational users’ interface

 

 

Author:             John P Woodruff

jpwoodruff@gmail.com

 

Copyright         (c) 1987 John Woodruff

                        Additional materials 2007

 

Original work performed at

Lawrence Livermore National Laboratory

Material was released by LLNL in 1992

 

Numeric_IO & Name_IO: Ada packages supporting a computational users’ interface

Introduction

Formatting Real Number input

Usage

Numeric_IO Abstract

Multiple-choice menus

Digits “Aft” of decimal

Matrix_IO Abstract

Name_IO Abstract:

Name_IO Usage  Overview

Getting Values

A suggestion about names

More on Object-Oriented input

Name_IO.Matrix_Name_IO Abstract

Materials added 2007: Measures & Units

Name_IO.Measure_Name_IO Abstract

Input_Collection

Facts about the implementation

License & Disclaimer

Lines of Source Code

Name_IO Implementation technique

Anachronism Alert

Items that have been reused from public sources

History

Known Defects

 


Introduction

The packages Numeric_IO and Name_IO, together with their children and support, assist a program to read a user’s input.  The packages are intended to support numerical computation by providing “Get” and “Put” procedures for floating numbers and for vectors and matrices of floating numbers.

 

The procedures ease an end-user’s burden in preparing inputs for computational programs.  The rules for input of floating numbers are relaxed so that program inputs need not conform to the strict Ada syntax for floating numbers. Facilities allow input either from files or interactively. Consistent policies throughout all the services allow programs to address input errors, to prompt the end-user interactively or to specify optional default values.

 

The signatures of corresponding procedures are the same as the Ada.Text_IO subprograms. Additional overloaded procedures use parameters Prompt and Default.  Programs that employ an advanced user interface (eg windowing) can supply interacting string primitives to replace Ada.Text_IO.

 

There are three layers of service provided by the product:

                 1 0 0

                 0 1 0

                 0 0 1;

 

 

Formatting Real Number input

Ada.Float_Text_IO recognizes ONLY numbers formed as Ada literals, viz  i.i[Ei] for i an integer.  The services here return a floating value (the type given by the generic parameter) of any of the following input notations (including optional sign):

 

     i.i  |  i.iei  |  i  |  i.  |  .i  |  iEi  |  i.Ei  |  .iei


Usage

The product was built to serve applications such as simulations that are written in an object-oriented idiom.  Input data are grouped into blocks that are named for the classes that consume their data.  Data might look like this example:

 

Some_Class_Name

Instance_Name := “Romeo” ;

Some_Data := 11.3 ;

More_Data := 42.9

End ;

 

Some_Class_Name

Instance_Name := “Ethel, the Pirate’s Daughter” ;

Some_Data := 14.75 ;

More_Data := 39.6

End ;

 

Other_Class_Name

Different_Data  := -3.2E-3 ;

-- Ada style comments can live in the data file

End ;

 

This data would be consumed when the program starts up. In the example we presume that two instances of Some_Class_Name would be created by the logic of the program. The instances would call “Get” entries in their instance of the generic Name_IO during their respective creations, and then the Romeo and Ethel objects would be initialized.  During the creation of the one instance of Other_Class_Name, its create procedure would use its own instance of the Name_IO package to read and initialize that (presumably anonymous) object with Different_Data.

 

Numeric_IO Abstract

This generic package provides a polite, refined input organ for interactive data input.

 

 The procedures are polite because they

 

Generic Polite_Get interacts with user to obtain a single Item.  The type of Item, and a decoder that parses an Item from an input stream, are provided at instantiation.

 

For each of integer and floating types, there are two overloaded Get procedures.  One prompts and returns the value, raising No_Entry in case nothing is entered; and the other allows the program to offer a default that is returned in case the user types no value.

 

Multiple-choice menus

A Menu procedure returns an upper case letter which is member of a specified set of choices.  Choices are letters in an input string.  For Choices = "" (an empty set of choices), any letter is acceptable.  Cases of the letters are not significant: neither the case of the set of choices, nor the case of the user's response affects the menu.

 

Digits “Aft” of decimal

Aft is a function that yields the number of digits aft of the decimal that are needed to render just the significant fraction of a floating number.

Give Exp that will be the parameter of Float_Text_IO . Put

 Aft (10.001, exp => 0) == 3; Put (10.001, aft=>3, exp=>0) == "10.001"

 Aft (10.001, exp => 2) == 4; Put (10.001, aft=>4, exp=>2) == "1.0001E+1"

 

Real_Decode is a procedure that adds value to the standard GET entry which decodes a string to a floating number.  The standard version recognizes ONLY numbers formed as Ada literals, viz i.i[Ei] for i an integer.  The Real_Decode entry here returns a floating value (the type given by the generic parameter) of any of the following input notations (including optional sign):

 

     i.i  |  i.iei  |  i  |  i.  |  .i  |  iEi  |  i.Ei  |  .iei

 

Matrix_IO Abstract

Matrix_IO provides a full set of IO services based on Ada.Text_IO that can read and write Vectors and Matrices.  The interface entry names and parameter signatures exactly duplicate the procedures in Ada.Text_IO.Float_Io.

 

Input is read using the Numeric_IO.Real_Decode procedure that defines a user-friendly representation for real numbers.  Any of the following notations return a legal Ada real number for any integer i:

     i.i  |  i.iei  |  i  |  i.  |  .i  |  iEi  |  i.Ei  |  .iei

 

This package reads a vector as a sequence of real numbers.  In case the parameter Width is non-zero, then every element occupies exactly Width characters in the input.  Use this tactic when reading input files written by some other program and formatted into columns.

 

In the default case, Width = 0, and each element is read regardless of its spacing.  Elements of the vector are optionally delimited by commas.  Individual elements may be defaulted by elision:

the input stream 1,,,4

results in the vector (1.0, 0.0, 0.0, 4.0).

 

Rows in a matrix are defined to be vectors separated by line terminators.  The exception Input_Too_Small is raised if too few rows are input to a matrix Get from a string.

 

Additional procedures are included that extend the interactive Get procedures found in the parent of this package, including prompting and optional default values.

 

The function Aft, which computes the number of digits needed to represent the significant values of its argument, is provided for both vector and matrix types.

 

Name_IO Abstract:

Name_IO implements name-directed input in a generic package.  The package is able to deliver values bound to variables from an input stream that looks like:

A = 1.0 ;                               -- a floating scalar

V := 3, 4, 5 ;                          -- a three-vector

Filename => "Test.dat" ;                -- a string

Enum  = Users_Enumerated_Constant ;     -- Enumeration value

 

The format for the input to be parsed is a sequence of
{  <Ada_Id> [assignment] <Value> ; }.

·        <Ada_Id> is any legal Ada identifier. 

·        The optional [assignment] can be any of "=", ":=", or "=>". 

·        Semicolon separators are required between assignments. 

·        The form of <Value> is determined by the type of the variable, and is defined by Numeric_IO.Real_Decode for floating  values, by Matrix_IO.Get for vectors and matrices and by Kazakov’s Measures_Edit for dimensioned physical units..  (The semicolon forms a terminator on a string that will be parsed as a value without placing any lexical requirement on value.   This allows the generic get_item to deliver an enumerated value.)

 

Name_IO Usage  Overview

The package is a finite state machine which can be used successively to recognize different streams of data all with the same collection of <Ada_Id>'s.  The sequence of actions on the state machine are:

1) Initialize the input data by either

1a) Accept_Input – called by user to provide a dynamic string of type String_Pkg.String_Type which is the input to be parsed.  Use this procedure when there is a unique string of input to be parsed.

1b) Acquire_Input – given a Class_Name, this procedure will invoke Input_Collection.Get_Contents to get its own dynamic string of data that are to be parsed.  Use this method when this package is used in an object-oriented program in which arbitrarily many distinct objects of some type each need the same kind of input. (There is more discussion of Object-Oriented input further on).

2) Arbitrarily many calls of Get, each called to obtain the value that was bound to a particular <Ada_Id> in the input;

3) Forget_Contents – called by user to signal that user has obtained all the values that s/he intends to obtain.  After forgetting, calls to Get raise No_Input until a new call of Accept_Input or Acquire_Input has provided a new string.

 

Getting Values

Five [*] overloaded calls to Get are provided, for any of Integer, Floating number, Vector, Matrix, and String values. Each is called with a value of Name_Type (which is represented by a literal that is expected to occur as <Ada_Id> in the input).

[*] well as of 2007, there are more than five, but who’s counting ;-)

 

There are two ways to use any of the Get procedures

 

     (a)  Get (Name => Name_List.A,

               Item => A ) ;

 

Returns a value for the variable A which appeared in the input as a binding like 'A := 10.3';   If no binding exists in the input, then raise No_Input.

·        Use this way in a program that has default values for some input, which are allowed to be replaced at run time.  Assign the default value of A in the exception handler for exception No_Input.

 

     (b)  Get (Name   => Name_List.B,

               Item   => B,

               Prompt => "Please give value for B") ;

 

Returns the value of B which appeared in the input if the binding exists, else prompt Standard-Input for a value.

·        Use this way if your program will not run unless user input is given for some variable (and you expect to have a user handy ;-)

 

The Get which returns string has an additional out parameter, Last : Natural, which means the index of the last character in the Item which was copied from the input. This Get resembles Ada.Text_IO . Get_Line in its handling of the arbitrary length of an input string with a fixed length parameter. In case Last = Item'Length, successive calls of Get return sequential pieces of the input.

 

The generic procedure Get_Item may be instantiated with the user's enumerated type, and calls to the instance will return values of that type.  (Get_Item has a distinct name because otherwise it is a homograph of the Get procedure already defined for Integer.)

 

A suggestion about names

A program that instantiates this package might wish to declare

      DECLARE

          TYPE Input_Names_Type  IS  ( A, B, C ) ;

          A   : Integer ;

      --  Et Cetera

 

This program is not a legal Ada program!  The declaration A  : Integer is an illegal redeclaration of the enumeral A that occurs in the preceding line.  (ref LRM 8.3(17) in the Ada83 manual)

 

      An acceptable alternative that retains the appearance of the name A in agreement with the variable A is as follows:

      DECLARE

          PACKAGE Name_List IS

                TYPE Input_Names_Type IS ( A, B, C ) ;

          END Name_List ;

 

          A   : Integer ;

          PACKAGE NIO IS NEW Name_IO  (

              Names_Type   => Name_List . Input_Names_Type ) ;

      --  Et Cetera

 

More on Object-Oriented input

This package may be used in conjunction with Package Input_Collection to acquire input in a program where there are arbitrarily many distinct objects of some (application defined) type which each needs some input.

Suppose you have an Object_Package with an interface procedure Create that you call to create an instance of a private type.  You don't know a priori how many instances there will be.  Nor do you wish to constrain how the input will be taken.

Your Object_Package should instantiate this package with names that define the input needed to specify an object of its private type.

Some part of your program (not necessarily the object package) calls Input_Collection.Read_File arbitrarily many times to read some input files (refer to the documentation in Input_Collection).

Let your Object_Package.Create call Name_IO.Acquire_Input ("OBJECT") followed by calls to Get.  The effect of this call to Create will be to process all the input for "OBJECT" that was in one of the input files. Call Name_IO.Forget_Contents after the instance has been created.  If you call create repeatedly, it will process successive instances of OBJECT that were defined in all the input files that had earlier been given to Input_Collection.Read_File.

You may wish to call Set_Object_Instance_Name after a call to Acquire_Input.  This will bind an instance name to the error messages and prompts so the user can know which of several object inputs is delivering a particular message.

<Well, that wasn't too bad for 1987.>

 

Name_IO.Matrix_Name_IO Abstract

Matrix_Name_IO is a child of the package Name_IO, which delivers input from a file to callers of procedures Get.  Name_IO gets its input from a file that is formatted like:

 

       A  := 4.2 ;

       Label := "Data for a computation" ;

 

The present child package extends the types that can be read to include vector and matrix.

 

      v := 1,,3 ;

 

      mat :=

            1 0 0

            0 1 0

            0 0 1 ;

 

Of course this package exploits the services of Numeric_IO.Matrix_IO in exactly the same way as Name_IO uses Numeric_IO.

Materials added 2007: Measures & Units

New child packages have been introduced that extend the Name_IO service to the Measure type constructed by Dmitry Kazakov.  Kazakov’s work (at version 2.2 as of this writing) defines types corresponding to physical quantities and implements computations that carry units for each physical quantity. Programs written using Kazakov’s Units and Measures abstractions can be sure that their computations implement dimensionally sound physics.  Physics text exercises written in this idiom can be wonderously instructive, and they look great attached to a resume.

 

The type Unit denotes the dimension of a physical entity. The type Measure represents a dimensioned value.  This product offers child package Name_IO.Measure_Name_IO (added in 2007) that extends the name services to all the subtypes defined by Measure. Measure_Name_IO reads input such as

       P  := 14.9 psi ;   -- one standard atmosphere

 

The source files for this feature are located in the subdirectory Measures to allow factoring of the compilation context. Apart from procedure Name_IO.Measure_Name_IO.Get, every other operation is provided by Kazakov’s original work.

Name_IO.Measure_Name_IO Abstract

Measure_Name_IO extends the service to computational values that carry dimensionality information.  The decoding function is implemented in Kazakov’s Measures_Edit package.   That package defines its input syntax as follows

 

    <measure> ::= (<measure>)

    <measure> ::= <measure> [<dyadic-operation>] <measure>

    <measure> ::= <prefix-operation> <measure>

    <measure> ::= <measure> <postfix-operation>

    <measure> ::= <number>

    <measure> ::= <unit>

    <dyadic-operation>  ::= ** | ^ | * | · | / | + | - | and

    <prefix-operation>  ::= + | -

 

Here  <unit>  is  a  name  denoting a measurement unit, such as foot.  The domain of unit is specified by several packages in Kazakov’s product.   Multiplication has higher priority than division.  Hence:  m/s*kg  is interpreted as m/(s*kg).

 

Examples :

force  := 10.0N ;

pressure := 32.0N/m^2 ;

energy  := 9.0J ;

distance := 19.1 mi ;   -- one pico-parsec

 

Input_Collection

Input_Collection is a repository of user inputs that are to be dispensed to individual objects when entries of instances of the generic package Name_IO are invoked.  The input is obtained from some file(s) and retained in this package until called for by some object.

 

The package reads datasets from files (one file for each call to read_file) and stores them on a list. When a user package calls get_contents, the package replies with the text that was stored under the requested name

 

The user provides one or more text files structured like -->

        <<    Class_Name

              {Any "contents" readable by the Object}

              END     >>

 

This package collects these and indexes them by Class_Name. Usual operation might have numerous of these blocks with the same Class_Name, with each of the blocks holding the input data for one instance of the class.

 

Both Class_Name and END must be on a line by itself; otherwise there are no rules imposed by this package about the form of the input file(s). Case is not significant in Class_Name or in "End". Optional semicolons delimiting Name and End are immaterial.

The contents are delivered exactly as read from the file (except that blank lines and Ada-style comments are removed).

 

This package imposes no rules on the content text, except that blank lines and Ada-style comments are discarded.  Lines in the input file retain their identity in the content text, with "ASCII.CR ASCII.LF" at places in the content where line terminator was found.

 

The package defines procedure Read_Collection_Files to provide a more interactive operation for collecting files. If called, Read_Collection_Files either reads every file that is named on the program's command line, or else interacts (using Ada.Text_IO) with the user to read additionally named files.  If no command line files were given, Read_Collection_Files terminates when the user responds to "Please give some file names ..." with a carriage-return.

 

The intended user of this repository is an instance of Name_IO, however any client package may call the Get_Contents procedure, citing some Class_Name and receive the contents text. If the same Class_Name occurs twice in the input file(s), then successive calls to give_contents will deliver the inputs in FIFO order.  If no input has been collected for a Class_Name, the exception No_Input_For_Object is raised.  The Contents that is delivered is a PERSISTENT string_Type.  When the user has finished with it, s/he should do String_Pkg.Flush (Contents).  (Name_IO does follow this advice.)

 

Facts about the implementation

License & Disclaimer

This is free software; you can redistribute it and/or modify it under terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.  This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 

As a special exception, if other files instantiate generics from this unit, or you link this unit with other files to produce an executable, this unit does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Public License.

 

This software was prepared as an account of work sponsored by an agency of the United States Government.  Neither the United States Government nor the University of California nor any of their employees makes any warranty, express or implied, or assumes any liability or responsibility for the accuracy, completeness, or usefulness of any information disclosed.

 

Lines of Source Code

Lines of code were counted by the oldest such program I know, originated by Whittaker.  The Ada statement is defined by a semicolon terminator outside of comments, parentheses, or string or character literals.

 

File name                                     Ada Stmts      Comments

_____________________________________________________________________

numeric_io.ads                                       28           125

numeric_io.adb                                      180           118

numeric_io-matrix_io.ads                             29           104

numeric_io-matrix_io.adb                            145           103

name_io.ads                                          27           217

name_io.adb                                         237           249

name_io-matrix_name_io.ads                           10            59

name_io-matrix_name_io.adb                           19            84

numeric_io-matrix_io-implementation.adb              96            76

input_collection.ads                                  7            95

input_collection.adb                                105           117

_____________________________________________________________________

 

***TOTALS***                                        883          1347

 

Name_IO Implementation technique

Step 1: Accept (or acquire) input

 

Step 2: Deliver value to caller of Get

 

I first learned the technique used here in the package NAMELIST, written by David Kwong...finished 10/86.

Received: from SIMTEL20.ARPA in 1987.

 

Anachronism Alert

Back in '86 and '87 the best string scanning packages I knew were the collection in Simtel20, later PAL, later (oh who knows).  I call it "ASR_String_Utilities" after the "Ada Software Repository" but it was "marketed" under the name String_Utilities, and it is actually pretty cool.  Nowadays a controlled type Ada_Strings_Unbounded gets worked over using its own standard-compliant operations.  So don't read this package for esthetics, but it seems to work OK...  JPW

 

Items that have been reused from public sources

All the items in the “Reused” directory (except for set_of) originally were publicly available in a variety of 1980’s vintage Ada reuse repositories.  Starting with the “SIMTEL20.ARPA” repository, also known as the “Ada Software Repository”, then in its successors the Public Ada Library (PAL) library and the Walnut Creek disk that has been distributed by SigAda.  In those good old days, just about everybody had access to a single collection of abstract data types.

 

The package asr_string_utilities in this product was known as “string_utilities” in the public repository;  I re-labeled it to resolve a clash with other packages using the same name.

 

These items have served well, although they are somewhat anachronistic in Ada95’s day, what with Ada.Strings and his children, child packages and controlled types.  (I have corrected a minor defect in asr_string_utilities.get_segment).

 

The package Set_Of is a transcription of an example from Habermann’s Ada for Experienced Programmers.  The package is a straightforward rendition of the “set” semantics from the Pascal language.

 

History

These materials were first built and used in a simulation program that was active in the late 1980’s.  That simulation solved a set of continuous equations after building their initial states from scalars, vectors and matrices with these packages.  Object classes for radars, satellites, and other “rocket-science” stuff all had “Create” methods, and each of these made calls on the “Get” services of different Name_IO instances.

Over the years these packages were used with DEC Ada, Rational Apex, and Gnat compilers.  Programs have run on different DEC Vaxen, on Sun workstations, and on Windows.

Packages in this product were publicly available in the PAL library and on the Walnut Creek CD’s.  Since that distribution, I upgraded the package specs to exploit Ada95’s generic child packages.

 

Known Defects
  1. The Real_Decode procedure will interpret a free-standing decimal point as a floating point 0.0.  Therefore, if a string that begins with decimal-point followed by junk, the interpreter will not report an error, but will return 0.0 after one character is consumed.
    ”.junk”  =>  0.0  --  output Last = From’First + 1

 

  1. If a vector input ends with numerous commas (more commas than needed to fill elements into the output Item), a correct vector that ends in the right number of zeros will be returned but the parameter “Last” will place the scanner point at the end of the string.

 

  1. The removal of Ada-style comments is not quite as careful as the language specification.  If a quoted string contains double-hyphen the input data will be spoiled (and give lots of uninformative error messages).