/*	PIRL Parameter Value Logic

PIRL Implementation of the Parameter Value Language interpreter.


Description:

* Parameter Value Language

The Parameter Value Language is commonly used in the label section of image 
files found on Planetary Data System CDs as well as other data products from
the Planeteary Science and Astronomical communities. The language specification
is provided by the Consultative Committee for Space Data Systems in the Blue
Book ("Parameter Value Language Specification (CCSDS0006)", May 1992 [CCSDS
640.1-B-1]) and Green Book ("Parameter Value Language - A Tutorial", May 1992
[CCSDS 640.1-G-1]) documents.

The language has a deceptively simple syntax for a parameter defining PVL 
statement:

	name = value <units>

A parameter is just a token name (i.e. a string) that is used to refer to the
corresponding value. The optional value is a single datum representing a
number (integer or real) or string, or an array of zero or more of these 
values. The optional units string which may follow any value, including an 
array, is a string enclosed in angle braces.

Without getting into the details (which are in the references), there are 
complexities to the value representation that make managing the resultant data
structures somewhat problematical. The parameter value statement is not
required to be on a single line (a string terminated by a line-feed and/or
carriage-return character). Comments (C-language style) may be provided on the
same or separate lines. An integer may be represented with a specified-base
notation in addition to the usual decimal notation. A string may be unquoted,
single quoted, or double quoted and may represent a special date/time
notation. Quoted strings may extanded across multiple lines with the line
separators, any optional preceding hyphen, and any following white space being
replaced with a single space character. Arrays may be unordered sets contained
in curly braces or ordered sequences contained in parentheses, with a possible
mix of value types.


* PIRL Parameter Value Logic

It is an unfortunate fact of life that the Parameter Value Language has not 
always been used with strict adherence to the syntax rules. Therefore, this 
implementation of the language parser is designed to be very tolerant, rather
than requiring strict adherance to a rigid specification; i.e. the intent is
to "get the information" if possible, and let the user decide what to do with
it. For example: Any string may be quoted (with single or double quotation
marks), including the parameter name. Sets and sequences are treated
identically as arrays. A list of values that is not enclosed in curly braces
or parentheses is treated as a set. Arrays may be nested to any depth. Base
notation (N#MMM#) allows any base value that can be handled by strtol. Date
and time values are treated as strings (they are not interpreted). Comments
may be anywhere in a statement (except inside quoted strings where they are
treated as part of the string), including crossing record/line boundaries.
Comments that lack termination will be assumed to end at the next line break.
Unterminated units strings will be assumed to end at the next parameter value
separator.

The PPVL_strict global variable controls whether the PVL will be interpreted
strictly or with tolerance. By default this module is compiled with
PPVL_strict initialized to FALSE (i.e. use tolerance). Whenever strict PVL
interpretation would produce an ERROR condition, a WARNING is registered (see
Error Handling, below, for more details).


Parameters -

The parser stores the information it gathers in a PIRL Parameter Value Logic
Parameter structure:

typedef int		PPVL_Class;

typedef struct PPVL_parameter
	{
	PPVL_Class	classification;	Classification of parameter
	char		*name;			Parameter name (keyword)
	union
		{
			For all classes of parameters EXCEPT begin aggregate:
		
		struct PPVL_value		*value;

			ONLY for begin aggregate class parameters:
		
		struct PPVL_parameter	**parameters;
		}
				content;		Parameter content (value)

	char		*comments;		Leading comments
	void		*user_data;		Link to user-defined data
	}
	PPVL_Parameter;

Each parameter is of a particluar classification:

	Unknown (PPVL_CLASS_UNKNOWN) - this should only occur in the case of a
		parsing error.
	Token (PPVL_CLASS_TOKEN) - a parameter with no values (this does not
		include parameters with empty sets or sequences).
	End (PPVL_CLASS_END) - a token with the special name "END".
	Assignment (PPVL_CLASS_ASSIGNMENT) - the normal parameter value assignment.
	Aggregate (PPVL_CLASS_AGGREGATE or PPVL_CLASS_BEGIN_AGGREGATE) - One of the
		special classifications indicated by a parameter with a special
		(reserved) name:

		OBJECT or BEGIN_OBJECT (PPVL_CLASS_OBJECT or PPVL_CLASS_BEGIN_OBJECT)
		END_OBJECT (PPVL_CLASS_END_OBJECT)
		GROUP or BEGIN_GROUP (PPVL_CLASS_GROUP or PPVL_CLASS_BEGIN_GROUP)
		END_GROUP (PPVL_CLASS_END_GROUP

Both Objects and Groups are in the Aggregate classification. Special
names are not case sensitive. Aggregate classification parameters (both begin
and end) may optionally be assigned a value - which is expected to be a string
which names the aggregate - that is used as the parameter name when
available. The distinction between an Object and a Group may be meaningful to
the user (e.g. an Object may include parameters that collectively form a
larger object, while the parameters of a Group may only have some common
quality), but they are otherwise identical.

Note: End-class parameters are not retained in parameter lists (i.e. aggregate
values). They are processed on input (PPVL_scan_parameter) and output
(PPVL_write_parameter) only. Except for specialized application purposes, they
are not needed.

The classification code of a parameter is actually a collection of bit flags.
Only specific aggregate classification codes (group, object), as opposed to
general codes (aggregate) can be meaningfully used directly. A set of
PPVL_Class_Is_<Class> macros are provided for ease in evaluating classification
codes. For Class substitute any (except Unknown) of the classification names
(as in PPVL_CLASS_<Class>) in initial caps form. For example:

	if (PPVL_Class_Is_Assignment (The_Parameter->classification))
		{
		Process assignment parameter ...
		
		}
	else if (PPVL_Class_Is_Aggregate (The_Parameter->classification))
		{
		if (PPVL_Class_Is_Group (The_Parameter->classification))
			{
			Group specific processing ...
			
			}

The parameter's name symbolically identifies the parameter. When the parameter
is in the aggregate classification, the name is reset to the identifier string
value, if it has one (otherwise the name is left as the special aggregate
name). Parameter names (or aggregate identifiers) need not be unique within
the aggregate in which they occur (but then it is left to the user to
distinguish them).

Any comments that occur immediately preceding a PVL statement are collected 
together in the parameter's "comments" string. A comment is identified as a 
string enclosed in C-language style comment characters. Multiple comments are
delimited by a single new-line ('\n') character in the "comments" string.
Comments that are embedded within a parameter statement are ignored.

A "user" pointer is provided for the user to link any desired data to a 
parameter. This pointer is not used in this module in any way (other than as a
potential selection to PPVL_find_parameter).


Values -

The "content" element is either a "value" - a PPVL_Value pointer - or, for 
aggregate parameters ONLY, a list of "parameters" - a pointer to an array of
PPVL_Parameter pointers (see the Aggregates section below). A PPVL_Value 
contains the value portion of the PVL statement:

typedef int		PPVL_Type;

typedef struct PPVL_value
	{
	PPVL_Type	type;				Data type
	union
		{
		unsigned long		integer;
		double				real;
		char				*string;
		struct PPVL_value	**array;
		}
				data;				Actual data value
	int			base;				Number system base (radix)
	char		*units;				Measurement units
	}
	PPVL_Value;

The data for each value is either an integer number (long precision), a real
number (double precision), a string pointer, or itself an array of value
structures. In this latter case, data.array points to a NULL terminated list
of PPVL_Value pointers.

The data type of each value is indicated by a type code:

	PPVL_TYPE_UNKNOWN - only occur in the case of a parsing error.
	PPVL_TYPE_NUMERIC - includes these specific types:
		PPVL_TYPE_INTEGER (unsigned long)
		PPVL_TYPE_REAL (double)
	PPVL_TYPE_STRING - includes these specific types:
		PPVL_TYPE_IDENTIFIER - an unquoted string.
		PPVL_TYPE_SYMBOL - single quoted (') string.
		PPVL_TYPE_TEXT - double quoted (") string.
		PPVL_TYPE_DATE_TIME - a guess (contains "-" and/or ":").
	PPVL_TYPE_ARRAY - includes these specific types:
		PPVL_TYPE_SET - contained within curly braces,
			or an uncontained values list.
		PPVL_TYPE_SEQUENCE - contained within parentheses.

The type code of a parameter is actually a collection of bit flags. Only
specific type codes (e.g. integer or text), as opposed to general codes (e.g.
numeric or string) can be meaningfully used directly. A set of
PPVL_Type_Is_<Type> macros are provided for ease in evaluating type codes. For
Type substitute any (except Unknown) of the type names (as in PPVL_TYPE_<Type>)
in initial caps form. For example:

	if (PPVL_Type_Is_String (The_Value->type))
		{
		Process string value ...
		
		}
	else if (PPVL_Type_Is_Numeric (The_Value->type))
		{
		if (PPVL_Type_Is_Real (The_Value->type))
			{
			Real value processing ...
			
			}

Each value may have a units string. The string is typically used to provide 
the units of measurement for the corresponding value, but its interpretation
is left to the user. An array (set or sequence) value may have a units string
in addition to a possible units string for each value of the array. The units
string of an array value may be taken as being applied to each value of the
array unless the array value has its own units string, however, this is just a
suggested use.


Aggregates -

When a parameter is of the begin aggregate classification (OBJECT or GROUP)
its content.value is a pointer to a NULL terminated list of PPVL_Parameter 
pointers (PPVL_Parameter **). Each parameter in the list is a member of the 
aggregate parameter. The PVL statement that ends the list of aggregate 
parameter members is not in this list (the NULL implicitly represents the 
end-aggregate parameter); it is swallowed on input (PPVL_read_aggregate), and
recreated on output (PPVL_write_parameter).

The previous value of the aggregate parameter (as returned from 
PPVL_scan_parameter), if any, is assigned to the parameter's name. Aggregate
parameters without a single string value retain their original name.
Inappropriate values (not a single string) will generate a warning (or error
in strict mode).


* Dynamic memory

The PPVL module uses dynamically allocated memory throughout. All PPVL objects
(parameter structures and aggregate lists, value structures and array lists)
and any string contents are stored in dynamic memory. Some (actually not much)
effort has been made to be efficient in the use of the system's dynamic memory
functions. It is unnecessary, and potentially unsafe, to use static or stack
memory for PPVL objects. Parameter and value structures, once created
(directly or indirectly), will remain stable until freed (only use the
PPVL_free functions), but parameter and value lists will may be reallocated
whenever a PPVL function feels the need. The use of dynamic memory is not
expected to produce any hardship for the application as long as the PPVL
functions are used exclusively to manipulate its objects.


Use:

	#include	"PPVL.h"

	PPVL_Parameter *
	PPVL_read_aggregate
		(
		FILE			*File,
		unsigned long	Read_Limit,
		unsigned long	*Total_Scanned
		);

	PPVL_Parameter *
	PPVL_scan_aggregate
		(
		char			*The_String,
		char			**Next_Character
		);

	PPVL_Parameter *
	PPVL_scan_parameter
		(
		char			*The_String,
		char			**Next_Character
		);

	char *
	PPVL_scan_comments
		(
		char			*The_String,
		char			**Next_Character
		);

	PPVL_Value *
	PPVL_scan_value
		(
		char			*The_String,
		char			**Next_Character
		);

	PPVL_Error_Code
	PPVL_scan_datum
		(
		PPVL_Value		*The_Value,
		char			*The_String,
		char			**Next_Character
		);

	char *
	PPVL_scan_quoted_string
		(
		char			*The_String,
		char			**Next_Character
		);

	char *
	PPVL_scan_units
		(
		char			*The_String,
		char			**Next_Character
		);

	char *
	PPVL_skip_white_space_and_comments
		(
		char			*The_String
		);

	char *
	PPVL_comb_symbol
		(
		char			*The_String
		);

	PPVL_Parameter *
	PPVL_new_parameter
		(
		char			*name,
		PPVL_Class		classification,
		PPVL_Object		*content,
		char			*comments,
		void			*user_data
		);

	PPVL_Value *
	PPVL_new_value
		(
		PPVL_Type		type,
		PPVL_Object		*data,
		int				base,
		char			*units
		);

	PPVL_Parameter *
	PPVL_add_parameter
		(
		PPVL_Parameter	*The_Aggregate,
		PPVL_Parameter	*The_Parameter
		);

	PPVL_Value *
	PPVL_add_value
		(
		PPVL_Value		*The_Array,
		PPVL_Value		*The_Value
		);

	PPVL_Parameter *
	PPVL_duplicate_parameter
		(
		PPVL_Parameter	*The_Parameter
		);

	PPVL_Value *
	PPVL_duplicate_value
		(
		PPVL_Value		*The_Value
		);

	PPVL_Parameter *
	PPVL_remove_parameter
		(
		PPVL_Parameter	*The_Aggregate,
		PPVL_Parameter	*The_Parameter
		);

	PPVL_Value *
	PPVL_remove_value
		(
		PPVL_Value		*The_Array,
		PPVL_Value		*The_Value
		);

	void
	PPVL_free_parameter
		(
		PPVL_Parameter	*The_Parameter
		);

	void
	PPVL_free_value
		(
		PPVL_Value		*The_Value
		);

	PPVL_Parameter *
	PPVL_find_parameter
		(
		PPVL_Parameter	*The_Aggregate,
		PPVL_Parameter	*Last_Parameter,
		PPVL_Select		Select,
		PPVL_Object		*The_Selection,
		PPVL_Parameter	**The_Parent
		);

	PPVL_Value *
	PPVL_find_value
		(
		PPVL_Value		*The_Array,
		PPVL_Value		*Last_Value,
		PPVL_Type		Type,
		PPVL_Object		*Data,
		PPVL_Value		**The_Parent
		);

	int
	PPVL_count_all_parameters
		(
		PPVL_Parameter	*The_Aggregate
		);

	int
	PPVL_count_all_values
		(
		PPVL_Value		*The_Array
		);

	int
	PPVL_count_parameters
		(
		PPVL_Parameter	*The_Aggregate
		);

	int
	PPVL_count_values
		(
		PPVL_Value		*The_Array
		);

	int
	PPVL_index_parameter
		(
		PPVL_Parameter	*The_Aggregate,
		PPVL_Parameter	*The_Parameter
		);

	int
	PPVL_index_value
		(
		PPVL_Value		*The_Array,
		PPVL_Value		*The_Value
		);

	PPVL_Error_Code
	PPVL_write_parameter
		(
		PPVL_Parameter	*The_Parameter,
		FILE			*The_File,
		int				Indent_Level
		);

	PPVL_Error_Code
	PPVL_write_value
		(
		PPVL_Value		*The_Value,
		FILE			*The_File
		);


A pseudo-object-oriented interface is also provided. These functions (actually
macros to the specific functions above) operate on PPVL_Object handles, which
may be PPVL_Parameter or PPVL_Value pointers:

	PPVL_Object *
	PPVL_new
		(
		int				Type,			Class or type code
		PPVL_Object		*Content,
		char			*Name,			Parameter name or data radix
		char			*Comments,		Parameter comments or value units
		void			*User_Data
		);

	PPVL_Object *
	PPVL_add
		(
		PPVL_Object		*The_Container,
		PPVL_Object		*The_Object
		);

	PPVL_Object *
	PPVL_duplicate
		(
		PPVL_Object		*The_Object
		);

	PPVL_Object *
	PPVL_remove
		(
		PPVL_Object		*The_Container,
		PPVL_Object		*The_Object
		);

	void
	PPVL_free
		(
		PPVL_Object		*The_Object
		);

	PPVL_Object *
	PPVL_find
		(
		PPVL_Object		*The_Container,
		PPVL_Object		*Last_Object,
		int				Select,				PPVL_Select or PPVL_Type
		PPVL_Object		*The_Selection,		Parameter selection or value data
		PPVL_Object		*The_Parent
		);

	int
	PPVL_count_all
		(
		PPVL_Object		*The_Container
		);

	int
	PPVL_count
		(
		PPVL_Object		*The_Container
		);

	int
	PPVL_index
		(
		PPVL_Object		*The_Container,
		PPVL_Object		*The_Object
		);


The PPVL functions are organized in such a manner that applications usually 
only need to call a very few functions. This becomes clearer when the 
relationship amongst the functions is understood:

* Input Functions

	PPVL_read_aggregate
	PPVL_scan_aggregate
		PPVL_scan_parameter
			PPVL_scan_comments
			PPVL_scan_value
				PPVL_scan_datum
				PPVL_scan_quoted_string
				PPVL_scan_units
					PPVL_skip_white_space_and_comments
					PPVL_comb_symbol

These functions manage the conversion of external PVL statments to internal 
PPVL objects. Usually only the PPVL_read_aggregate function is needed, though
the PPVL_scan_aggregate function is used for special circumstances where the
application itself reads the PVL text into a buffer for processing. In
general, the lower level functions are not needed by the application. They are
made available for applications that need to handle the PVL input process in
some unique, specialized manner.


PPVL_read_aggregate -

An aggregate parameter (PPVL_Parameter), given the name "The Container" (but
use the symbol PPVL_CONTAINER_NAME), is assembled from all of the PVL
parameters that can be read from the specified File. The total number of
characters scanned to produce the aggregate parameter is returned through
Total_Scanned (if not NULL). Thus Total_Scanned will provide the offset from
the position of the file when the function was called to the location of the
byte that ended the last PVL parameter statement scan (i.e. the next byte
after those that were used by the function). If the function fails to produce
an aggregate parameter, then Total_Scanned returns the location of the
character that caused the failure. If the file is seekable (a disk file), then
it will be repositioned to the location specified by Total_Scanned before the
function returns (regardless of whether the function succeeds or not, or if
Total_Scanned is NULL or not).

The specified file reference (i.e. a stream opened for input) is used to read
characters starting at the file's current position into a dynamically sized
sliding window buffer. This buffer is used as the source of The_String to
repeatedly pass to the PPVL_scan_parameter function. An internal function is
recursively called to assemble any aggregate parameters that are encountered
during the file scan.

The scan of the file contents ends when any one of these conditions occurs 
(only the first is strictly legal):

	An END (PPVL_CLASS_END) parameter is encountered.
	The Read_Limit (measured in number of bytes) is reached.
	The end of the file is reached.
	No parameter can be formed due to uninterpretable syntax
		or an empty PVL statement.

Sized (a.k.a. variable) record formatted files, in which each record is 
preceeded with a binary count value and possibly padded with a null byte, are
handled correctly. Because some files do not provide a PVL END statement (or
any other mark for the end of the PVL statements list!), it is possible for
file scanning to proceed into non-PVL file data. This can be controlled by a
user-provided Read_Limit (see the Examples section below), but a default limit
(256 kb) will be used if unlimited file scanning is specified (Read_Limit set
to PPVL_NO_READ_LIMIT). It is possible that one or more bogus parameters can
thus be appended to the list returned, however this is usually not a problem
as they will probably be distinctly outrageous to the application.

On success a pointer to The_Container aggregate parameter is returned. On 
failure a NULL is returned. A failure occurs if, and only if, a fatal error 
occurs during the file scan (see the Error handling section for a list of 
fatal errors). When sized records are encountered the warning status
PPVL_ERROR_SIZED_RECORDS is set in PPVL_errno.


The "scan" functions scan a character string searching for and collecting 
their appropriate PVL element. Each function takes an argument called 
The_String which indicates the starting location of the string to be scanned.
And each function will return through the Next_Character argument (if it is
not NULL) where the scan left off. The_String is never modified in any way.
When the function successfully acquires its PVL element the Next_Character
will be the beginning of the next PVL element (or the end of the string). This
allows the user to repeatedly call the function to obtain all contiguous PVL
elements, setting The_String to the value returned through Next_Character
after each call. When no corresponding PVL element is available from
The_String the function will return a failure condition and the location in
The_String where the scan failed will be returned through the Next_Character
argument. Note that for some PVL elements, only one element is expected from
The_String (e.g. the datum or units for a value), but the Next_Character still
correctly indicates where the scan left off for use by the next PPVL function.


PPVL_scan_aggregate -

Just like PPVL_read_aggregate, but operating on a string instead of a file,
this function returns an aggregate named "The Container" that has a list of all
parameters found in the string.

On success a pointer to The_Container aggregate parameter is returned. On 
failure a NULL is returned. A failure occurs if, and only if, a fatal error 
occurs during The_String scan.


PPVL_scan_parameter -

A parameter (PPVL_Parameter) is assembled from the contents of The_String. The
next character after the the PVL statement is returned through Next_Character
(if it is not NULL) on success; the location in The_String where the error
occured otherwise.

After collecting the leading comments and the parameter name, if an equals 
sign character is present the remainder of The_String is passed to 
PPVL_scan_value to abtain the value of the parameter.

When the parameter name is not a quoted string it is checked for reserved 
characters (from the PVL specification) and a warning (or error in strict 
mode) is generated if any are found. When the parameter's name is one of the
special names - OBJECT, BEGIN_OBJECT, END_OBJECT, GROUP, BEGIN_GROUP, or
END_GROUP - it becomes one of the corresponding aggregate classifications. In 
this case when the value obtained, if any, is a single string type this string
is reassigned as the parameter name, and the remainder of the PPVL_value is
freed. When there is no string value, or the value is inappropriate, the
parameter retains its special name (and a warning is generated).

On success a pointer to the new parameter is returned. On failure a NULL is 
returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_String is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory for the new parameter structure, or any of its
			components, could not be allocated.
	PPVL_ERROR_EMPTY_STATEMENT
		The_String is empty (nothing to parse).
	PPVL_ERROR_ILLEGAL_SYNTAX
		The parameter name is quoted (strict mode only).
	PPVL_ERROR_RESERVED_CHARACTER
		The unquoted parameter name contains a reserved character or
			unprintable character.
	PPVL_ERROR_GROUP_VALUE
		The parameter is an aggregate with a value that is not a single string.


PPVL_scan_value -

A value (PPVL_Value) is assembled from the contents of The_String. The next 
character after the the PVL statement is returned through Next_Character (if
it is not NULL) on success; the location in The_String where the error occured
otherwise.

It is initially assumed that the value being assembled will be an array, so a
list of PPVL_Value pointers is built and attached to a new array value. 
The_String is scanned for potential datum symbols, which are passed as they 
are encountered to the PPVL_scan_datum function along with an empty PPVL_Value
structure. If, however, the beginning of an array enclosure (the '(' and ')'
characters enclose sequences; the '{' and '}' characters enclose sets) is
encountered, then the PPVL_scan_value function is recursively called to
assemble the array value.

After each datum has been scanned (whether an array or not) the 
PPVL_scan_units function is passed the remainder of the The_String in an 
attempt to obtain a units of measurement string for the value. If a syntax 
error results it is ignored and the scan for datum symbols continues where it
left off.

When there are no more datum symbols, or an end of array enclosure character,
end of PVL statement character (';'), or the end of string is encountered the
scan ends.

When only one value has been found and it is not inside an array enclosure, 
the initial array value and its value list are freed and the single value 
found is returned instead.

On success a pointer to the new value structure is returned. On failure a NULL
is returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_String is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory for the new value structure, or any of its components,
			could not be allocated.
	PPVL_ERROR_ILLEGAL_SYNTAX
		A parameter name delimiter ('='), value delimiter (','), units start or
			end delimiter ('<', '>'), or number base (radix) delimiter ('#')
			was encountered when a value datum was expected. These cases are
			treated as non-recoverable since they are so egregious.
		A set or sequence array opening ('{', '(') was encountered when a datum
			was expected. 
	PPVL_ERROR_ARRAY_CLOSURE_MISMATCH
		The matching set or sequence array closure ('}', ')') is missing.


PPVL_scan_datum -

The_String is expected to contain a single datum symbol to be converted and 
assigned as the appropriate data of The_Value.

A datum symbol with enclosing single or double quotation marks is duplicated
(without the enclosing marks) as the data.string of The_Value and the data
type code is set accordingly. For unadorned symbols an attempt is first made
to convert the symbol as an integer (unsigned long) numeric value. An integer
may be represented in decimal, hexadecimal (with a leading "0X" or "0x"), or
octal (with a leading '0') form. If the entire symbol is not converted and it
failed on the special base notation symbol then an attempt is made to convert
it assuming base notation:

	[Sign]Base#Radix#

	Where:
		Sign is either '+' or '-'.
		Base is an integer (2-36) that is the base of the value.
		Radix is an integer value with the specified Base.

	For example:
		2#101101# is base 2 (binary) for decimal 45.
		16#2D# is base 16 (hexadecimal) for the same decimal 45.

When an integer value can not be converted an attempt is made to convert the
symbol into a real (double) value. If the entire symbol is not converted in
this way it is taken to be an unquoted string. The latter is checked for
characters that might indicate that it is a date/time string, and if so the
data type code is set accordingly but no attempt is made to interpret this
string. Unquoted string symbols are also checked for the presence of reserved
characters.

This function returns a PPVL_Error_Code. It will be zero (PPVL_SUCCESS) on 
success; non-zero otherwise. The character immediately following the datum 
symbol is returned through Next_Character (if non-NULL) on success; the 
location in The_String where the error occured otherwise. Possible 
error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Value or The_String is NULL.
	PPVL_ERROR_EMPTY_STATEMENT
		No datum symbol was found.
		A datum symbol is missing.
			These are non-recoverable errors.
	PPVL_ERROR_ILLEGAL_SYNTAX
		A parameter or value delimiter; a set, sequence, or units open or close
			delimiter; or a number base delimiter was found at the beginning of
			a datum symbol. Instead of tolerantly taking these to be the
			beginning of a string they are treated as non-recoverable errors.
	PPVL_ERROR_VALUE_OVERFLOW
		Numeric conversion of the datum string produced an overflow condition
			(the value will not fit in the available precision).
		A base value is out of range (2-36).
	PPVL_ERROR_RESERVED_CHARACTER
		An uquoted string value contains a reserved character. The presence of
			an unprintable character always produces a non-recoverable error.


PPVL_scan_quoted_string -

The first character of The_String is used as the enclosing quotation mark 
(this is usually a single or double quote character, but it is the caller's 
responsibility to decide what constitutes a quote mark). Line delimiters and
any following white space (leading white space on the next line) are replaced
by a single space character. If, however, the last character before the line
delimiters is a dash ('-') then the line delimiters and following white space
are simply compressed out entirely.

Normally string formatting for appearance when printed is controlled by 
embedded format characters which are processed on output:

	\n - line break.
	\t - horizontal tab.
	\f - form feed (page break).
	\\ - backslash character.
	\v - verbatim (no formatting) till next \v.

The quotation mark may also be included in the quoted string by escaping its
normal meaning with a preceeding backslash character.

Because some applications format a string value to appear as written (without
the use of embedded format characters), the global variable 
PPVL_verbatim_strings is provided to override the normal hypen and leading 
white space elimination when its value is set to TRUE.

On success a pointer to a copy (in dynamically allocated memory) of the string
(without the enclosing quote characters) is returned. On failure a NULL is
returned. If The_String is empty (no characters at all) NULL will be returned
but PPVL_errno will indicate PPVL_ERORR_NO_ERROR (0). The character
immediately following the next occurance of the quote character is returned
through Next_Character (if non-NULL) on success; the location in The_String
where the error occured otherwise. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_String is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory could not be allocated for a copy of the string.
	PPVL_ERROR_MISSING_QUOTE_END
		The terminating quote character can not be found. This is a
			non-recoverable error.


PPVL_scan_units -

The_String is scanned for a units of measure string. This string must be 
enclosed in angle brackets ('<' and '>'). Leading and trailing white space and
comments inside this enclosure are ignored and all other white space sequences
are reduced to a single space character.

On success a pointer to a copy (in dynamically allocated memory) of the units
string is returned. On failure a NULL is returned. Note that the inability to
find a units string enclosure is a syntax error (PPVL_errno is set to
PPVL_ERROR_ILLEGAL_SYNTAX). However, this is likely to be an expected outcome
when the function is just used in an attempt to see if a units string is
present. The character immediately following the units string enclosure is
returned through Next_Character (if non-NULL) on success; the location in
The_String where the error occured otherwise. Possible error/warning
conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_String is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory could not be allocated for a copy of the units string.
	PPVL_ERROR_ILLEGAL_SYNTAX
		No units start delimiter was found. This always a warning.
	PPVL_ERROR_MISSING_UNITS_END
		No units end delimiter was found. This is only an error in strict mode,
			otherwise the units string is taken to be the single word after the
			units start delimiter.


PPVL_scan_comments -

The_String is scanned for C-language style comments. A comment string starts
at the character after a forward slash ('/') that is immediately followed by
an asterisk ('*'), and ends just before an asterisk that is immediately
followed by a forward slash.

Sequential comments, with nothing but white space intervening, are accumulated
with a single new-line ('\n') chararacter separating them in the resulting
string that is returned. In strict mode comments are not allowed to wrap
across line breaks. Tolerant interpretation allows this but replaces sequences
of line delimiter characters with a single new-line character. Tolerant mode
also allows comments to be implicitly terminated at the next line break if
normal termination can not be found.

On success a pointer to a copy (in dynamically allocated memory) of the 
comment string is returned. If no comments are found before a non-comment 
sequence is encounterd, the function returns NULL to indicate a failure, but
PPVL_errno is set to PPVL_SUCCESS (0). A failure for any other reason will set
PPVL_errno to a non-zero error code. The location of the next non-comment
string (after any white space) is returned through Next_Character (if
non-NULL) on success; the location in The_String where the error occured
otherwise. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_String is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory could not be allocated for a copy of the comments
			string.
	PPVL_ERROR_MISSING_COMMENT_END
		The comment termination sequence was not found. This is an error in
			strict mode, otherwise the comment is taken to end at the next line
			break.


PPVL_skip_white_space_and_comments -

The_String is scanned for contiguous white space characters and comment 
strings in search of the next occurance of a character that is not a either 
type. White space characters are the space (' ') and horizontal tab ('\t') 
characters plus the line delimiter characters - carriage return ('\r'), new 
line ('\n'), formfeed ('\f'), and vertical tab ('\v'; note that this is a 
single character not to be confused with the verbatim string formatting 
character pair). Comment strings are identified in the same manner as in 
PPVL_scan_comments.

On success a pointer to the next character after any white space or comments
is returned (note that this may be a pointer to the end of The_String). On
failure a NULL is returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_String is NULL.
	PPVL_ERROR_MISSING_COMMENT_END
		The comment termination sequence was not found. This is an error
			in strict mode, otherwise the comment is taken to end at the
			next line break.


PPVL_comb_symbol -

The_String is checked for a reserved character or a non-printable character.
The reserved characters are:

			"{}()[]<>&\"',=;#%~|+! \t\r\n\f\v"

A pointer to the first illegal character found is returned, or NULL if none is
found. This function can not produce any error/warning conditions and does not
set PPVL_errno to PPVL_SUCCESS.


* Object manipulation functions

	PPVL_new_parameter
	PPVL_new_value
		PPVL_add_parameter
		PPVL_add_value
		PPVL_duplicate_parameter
		PPVL_duplicate_value
		PPVL_remove_parameter
		PPVL_remove_value
			PPVL_free_parameter
			PPVL_free_value

These functions are used to manage the complete life cycle of PPVL_Parameter
and PPVL_Value objects.


PPVL_new_parameter -

A new PPVL_Parameter structure is created in dynamic memory with contents 
derived from the function arguments. Cross checks for consistency are made and
parameter elements that are not specified are filled in, when possible, from
implicit information in other arguments. The "name" argument is the copied as
the name element of the parameter. When the name is one of the special names
indicating an aggregate parameter, then the parameter classification is set
accordingly. Normally the name for an aggregate is its identifier string and
the classification of the parameter is determined from the "classification" 
argument or derived from the "content" argument. The "classification" argument,
when not zero, must be for a specific classification (e.g. PPVL_CLASS_GROUP),
not a general classification (e.g. PPVL_CLASS_AGGREGATE). The "content"
argument is a pointer to a PPVL_Parameter or PPVL_Value, or NULL. The
"content" is not duplicated, and sharing a parameter or value already
associated with another parameter or value, while possible, runs the risk of
being "lost" when removed or freed from one context and thus becoming invalid
in the other context. It is recommended that "content" be either new or
duplicated objects. Comments are copied into dynamic memory. Embedded
new-line characters ('\n') will be interpreted on output as separate
sequential comment lines preceeding the parameter statement. The "user_data"
is anything the user wants to attach to the parameter. Not that it is user's 
responsibility to manage the user_data. It is possible to create a new empty
parameter, but this rarely is useful.

On success a pointer to the new parameter structure is returned. On failure a
NULL is returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		A "classification" argument is specified that is not a recognized
			parameter classification.
		This is a warning if comments or user_data is specified but no
			classification can be determined.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory could not be allocated for the parameter structure or
			any of its elements.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The "classification" (explicit or implicit) conflicts with the
			"content".


PPVL_new_value -

Like PPVL_new_parameter, a new PPVL_Value structure is created in dynamic 
memory with contents provided by the function arguments. It is possible to 
create an empty value structure, but this is rarely useful. The "type" must be
a specific type (e.g. PPVL_TYPE_INTEGER), not a general type (e.g. 
PPVL_TYPE_NUMERIC). The interpretation of the "data" argument is based on the
"type" argument:

	PPVL_TYPE_INTEGER - (unsigned long *)data
	PPVL_TYPE_REAL    - (double *)data
	Any string type   - (char *)data
	Any array type    - (PPVL_Value *)data

Carefully note the difference in how numeric and other "data" arguments are 
treated: For numeric data the "data" argument is the address of the variable,
not the variable value itself. For other string types the "data" argument is
the string pointer variable value (char *), not the address of the string
pointer variable. The same applies to array types for which the "data"
arguments are "PPVL_Value *", not "PPVL_Value **' (see the Examples section
below). Note that value data for array types should not be shared by multiple
arrays (see the discussion for PPVL_new_parameter above).

The "base" argument is only meaningful for integer data. If the base is not 10
(or zero which is implicitly 10), then radix notation will be used when the
value is printed. Note that the base may be negative, in which case the 
integer value is taken to be negative (this offers one more bit of precision).
The "units" argument string is copied into dynamic memory.

On success a pointer to the new value structure is returned. On failure a NULL
is returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		A non-zero "data", "base", or "units" argument is specified, but the
			"type" argument is zero.
		The "type" argument is not a recognized value type.
		The "base" argument is non-zero and its absolute value is not in the
			range 2-36 inclusive.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory could not be allocated for the value structure or any of
			its elements.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The "type" is for an array, but the "data" is not a value object.


PPVL_add_parameter -

The_Parameter object is added to the list of parameters for The_Aggregate 
parameter. If the The_Aggregate is empty, a new parameter list is created. 
Note that the parameter list is in dynamic memory and adding a new parameter
pointer to the list requires the reallocation of its memory.

On success The_Aggregate pointer is returned. This allows this function to be
an argument to other parameter manipulation functions such as 
PPVL_new_parameter or other PPVL_add_parameter calls (see the Examples section
below). On failure a NULL is returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Aggregate or The_Parameter is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory to create or enlarge the parameter list could not be
			allocated.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Aggregate is not a pointer to a valid aggregate parameter.
		The_Parameter is not a pointer to a valid PPVL_Parameter structure
			(e.g. an empty parameter structure).


PPVL_add_value -

Just like PPVL_add_parameter, but for PPVL_Value structures.


PPVL_duplicate_parameter -

A new parameter structure is created in dynamic memory and the contents of 
The_Parameter are copied into it. If The_Parameter is an aggregate, all of its
constituent parameters are duplicated recursively. The user_data element of
the parameter is not duplicated, but its value is copied.

On success a pointer to the new parameter structure is returned. On failure a
NULL is returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Parameter is NULL.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory for the new parameter or any of its consituents could
			not be allocated.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Parameter is not a pointer to a valid PPVL_Parameter structure.


PPVL_duplicate_value -

Just like PPVL_duplicate_parameter, but for PPVL_Value structures.


PPVL_remove_parameter -

The_Parameter is found in the parameter list of The_Aggregate, or any
aggregates in the parameter list, recursively. The parameter list where
The_Parameter is found is shortened so as to remove the specified parameter
pointer from the list. The dynamic memory for the removed parameter structure
and all its contents are freed.

On success The_Aggregate pointer is returned. On failure a NULL is returned.
Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Aggregate or The_Parameter is NULL.
	PPVL_ERROR_NO_MEMORY
		The shortened parameter list could not be reallocated.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Aggregate is not a begin aggregate classification parameter.
	PPVL_ERROR_EMPTY_STATEMENT
		The_Parameter could not be found in any parameter list.


PPVL_remove_value -

Just like PPVL_remove_parameter, but for PPVL_Value structures.


PPVL_free_parameter -

The_Parameter and all its contents are freed. This includes the parameter name
and comments strings. If The_Parameter is an aggregate all the parameters on
its list are recursively freed as is the list itself.

This function has no return value. It does not modify the PPVL_errno value.


PPVL_free_value -

Just like PPVL_free_parameter, but for PPVL_Value structures. String data and
units strings are freed. For array type values all values in the array are
recursively freed along with the arrays value list.


* Search Functions

	PPVL_find_parameter
	PPVL_find_value

These functions are used to search PPVL_Parameter or PPVL_Value objects for
selected elements with specified values.


PPVL_find_parameter -

The parameter list of The_Aggregate is first searched, recursively, for the
parameter pointer with the Last_Parameter value. This identifies the starting
point for The_Selection search which follows. If Last_Parameter is
PPVL_SEARCH_FROM_THE_TOP (NULL), then The_Selection search starts with the 
first parameter in The_Aggregate list. The search process is depth-wise; i.e.
whenever an aggregate is encountered in a parameter list it is searched before
the remainder of the parameters in the list. This corresponds to the order in
which PVL statements appear. When the The_Selection is found a pointer the
parameter that matched is returned. A pointer to the parent aggregate that
contains the selected parameter in its list is returned through The_Parent
- an address of a parameter pointer (PPVL_Parameter **) - if not NULL.

Select indicates how The_Selection is to be interpreted:

	PPVL_SELECT_ANY (0)

		The next parameter after Last_Parameter (or the first parameter if 
		Last_Parameter is PPVL_SEARCH_FROM_THE_TOP) is selected. This allows a
		parameter hierarchy to be stepped through in the order in which the
		corresponding PVL statements appear externally. This could be done as
		follows:

			for (The_Parameter = PPVL_find_parameter
					(
					The_Aggregate,
					PPVL_SEARCH_FROM_THE_TOP,
					PPVL_SELECT_ANY,
					NULL,
					&The_Parent
					);
				The_Parameter;
				The_Parameter = PPVL_find_parameter
					(
					The_Aggregate,
					The_Parameter,
					PPVL_SELECT_ANY,
					NULL,
					&The_Parent
					))
				{
				Process The_Parameter...
				}

	PPVL_SELECT_NAME (1)

		The_Selection is taken to be a string (char *) that is compared with
		the name element. The_Selection string may be a simple name which will
		match with the first occurance of a parameter with the same (but case
		insensitive) name. It may also be a pathname of the form:

			[/]Name[/Name[...]]

		A pathname beginning with a forward slash ('/') is "absolute" in that
		the first Name must be found in The_Aggregate parameter list before
		the next Name, delimited by a forward slash, will qualify for a match
		in a parameter aggregate in this list; etc. This is the same as file
		pathnames in Unix.

		A pathname that does not begin with a forward slash is "relative" in
		that the first Name is searched for like a simple name and then the
		remainder of the path is searched for relative to the location of the
		first parameter found.

	PPVL_SELECT_CLASS (2)

		The_Selection is taken to be a PPVL_Class code (note that this is the
		classification code value itself, not a pointer to the value). Both 
		specific and general classification codes are valid. The first
		occurance of a parameter having the selected classification will
		match.

	PPVL_SELECT_PARAMETER (3)

		The_Selection is taken to be a parameter pointer (PPVL_Parameter *).
		The parameter with the identical pointer value in a paramter list is
		selected. This is typically used to traverse back up the parameter
		heirarchy by searching for the parent parameter returned from a
		successful search, which returns its parent, until the parent returned
		is The_Aggregate itself. This could be done as follows:

			The_Parameter = PPVL_find_parameter
				(
				The_Aggregate,
				PPVL_SEARCH_FROM_THE_TOP,
				PPVL_SELECT_NAME,
				"deeply/nested/parameter",
				&The_Parent
				);
			for (PPVL_find_parameter
					(
					The_Aggregate,
					PPVL_SEARCH_FROM_THE_TOP,
					PPVL_SELECT_PARAMETER,
					The_Parameter,
					&The_Parent
					);
				The_Parent != The_Aggregate;
				PPVL_find_parameter
					(
					The_Aggregate,
					PPVL_SEARCH_FROM_THE_TOP,
					PPVL_SELECT_PARAMETER,
					The_Parent,
					&The_Parent
					))
				{
				Process The_Parent...
				}
			Process The_Aggregate...

	PPVL_SELECT_USER_DATA (4)

		The_Selection is taken to be a void pointer (void *). The first
		parameter that has user_data with the identical value is selected.

On success a pointer to the selected parameter is returned. If The_Parent 
argument is not NULL a pointer to the parent aggregate that has the list where
the selected parameter was found is returned there. On failure a NULL is
returned. A failure status may be returned with PPVL_errno set to PPVL_SUCCESS
(0) which indicates that the Last_Parameter was found, but not The_Selection.
Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Aggregate is NULL.
		Select is not one of the valid values.
		The_Selectionis NULL and Select is either PPVL_SELECT_NAME or
			PPVL_SELECT_PARAMETER.
	PPVL_ERROR_NO_MEMORY
		Dynamic memory for parsable copy of the pathname (thus Select is set to
			PPVL_SELECT_NAME) could not be allocated.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Aggregate is not a valid begin aggregate parameter.
	PPVL_ERROR_EMPTY_STATEMENT
		The Last_Parameter could not be found.


PPVL_find_value -

The value list of The_Array is first searched, recursively, for the value 
pointer with the Last_Value value. This identifies the starting point for the
Type search which follows. If Last_Value is PPVL_SEARCH_FROM_THE_TOP (NULL),
then the Type search starts with the first value in The_Array list. The search
process is depth-wise; i.e. whenever an array is encountered in a value list
it is searched before the remainder of the values in the list. This
corresponds to the order in which values appear in PVL statements. When the
the value with the specified Type is found it is checked for a match with the
Data argument. When a successful match is made a pointer the value that
matched is returned. A pointer to the parent array that contains the selected
value in its list is returned through The_Parent - an address of a value
pointer (PPVL_Value **) - if not NULL.

The Type argument may be for a specific numeric type or any (specific or
general) string or array type. It may also be the value PPVL_TYPE_ANY (0) in
which case the next value, if any, after Last_Value is selected. When the value
with the specified Type is found, its data value is compared against the Data
argument (this does not occurs when Type is PPVL_TYPE_ANY). If The Data
argument is PPVL_SELECT_ANY no data comparison is done (the value, regardless
of its data, is selected). Otherwise the Data value is interpreted according
the Type code:

	PPVL_TYPE_INTEGER - (unsigned long *)Data
	PPVL_TYPE_REAL    - (double *)Data
	Any string type   - (char *)Data
	Any array type    - (PPVL_Value *)Data

Carefully not the difference in how numeric and other Data arguments are 
treated: For numeric data the Data argument is the address of the variable, 
not the variable value itself. For other string types the Data argument is the
string pointer variable value (char *), not the address of the string pointer
variable. The same applies to array types for which the Data arguments are
"PPVL_Value *", not "PPVL_Value **'.

On success a pointer to the value with matching Type and Data is returned. If
The_Parent is not NULL, a pointer to the array value that contains the 
matching value in its list is returned there. On failure NULL is returned. A
failure status may be returned with PPVL_errno set to PPVL_SUCCESS (0) which
indicates that the Last_Value was found but the value with matching Type and
Data. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Array is NULL.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Array is not a valid array type value.
	PPVL_ERROR_EMPTY_STATEMENT
		The Last_Value could not be found. 


* Output Functions

	PPVL_write_parameter
		PPVL_write_value

These functions translate internal PPVL_Parameter or PPVL_Value data 
structures into external Parameter Value Language statements. The PVL that is
generated conforms strictly to the PVL standards documents.


PPVL_write_parameter -

The contents of The_Parameter are written to The_File (stdout if NULL). If the
content of The_Parameter is a value (PPVL_Value *) then PPVL_write_value is
used to write its contents. If The_Parameter is an aggregate, then each
parameter in its parameter list is recursively written as well.

Any comments associated with the parameter are written before the PVL 
statement proper, also indented. Occurances of new-line characters ('\n') in
the comment string cause separate comment lines to be written.

The PVL statement is preceeded by Indent_Level horizontal tab characters. Each
recursively processed parameter list causes the Indent_Level to be 
incremented. If, however, Indent_Level is negative no indenting is applied.

Parameter names for aggregates are restored to their proper names according to
their specific classification, and their internal names are written as the PVL 
string value identifying the aggregate. Parameter names with any embedded 
white space are quoted.

At the end of each parameter list for an aggregate parameter a corresponding
END_GROUP or END_OBJECT PVL statment is provided. When the parameter is an
aggregate named "The Container", as returned from PPVL_read_aggregate and
PPVL_scan_aggregate, a PVL END statement is written after the entire contents
of the aggregate have been written. Thus writing out one of these aggregates
produces a PVL statement list that is formally the same, if not exactly
identical, to what was read in.

On success a PPVL_Error_Code of PPVL_SUCCESS (0) is returned. On failure 
(which may be inherited from PPVL_write_value) the value of PPVL_errno is 
returned (which will be non-zero). A failure condition will also generate a 
comment line containing the error message describing the error code. Possible
error conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Parameter is NULL or its name is NULL.


PPVL_write_value -

The contents of The_Value are written to The_File (stdout if NULL). If 
The_Value is an array type, then each value in its list is recursively 
written. Proper set or sequence enclosures are provided for arrays, and 
between writing each parameter in an array list the comma (',') separator 
followed by a single space (' ') is written.

Simple (non-array) values are written according to their type. Integer data is
written in signed decimal notation unless the value's base is not 10, in which
case radix notation is used:

	Base#Radix#

The Base is The_Value's base value as a signed decimal integer. The radix is
The_Value's data value represented in the corresponding base notation. Real
data is written using the PPVL_real_number_format string to format the output.
By default this is "%#G", but, since control of precision representation
varies from system to system, PPVL_real_number_format is a global variable
(char *) that may be assigned a different format string by the user. For
example, "%#.15E" might be used to provide scientific notation with 15 digits
of precision. The format string must be used cautiously, and only to format
the appearance of the double precision real number data, or illegal PVL output
could result.

String data is enclosed in double quotes ('"') if the value is type 
PPVL_TYPE_TEXT, single quotes (''') if the value is type PPVL_TYPE_SYMBOL, and
undecorated otherwise.

On success a PPVL_Error_Code of PPVL_SUCCESS (0) is returned. On failure the
value of PPVL_errno is returned (which will be non-zero). Possible error
conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Value is NULL or its type is NULL.
	PPVL_ERROR_VALUE_OVERFLOW
		The base of an integer type value is not in the range 2-36 inclusive
		(absolute value).


* List Counting Functions

	PPVL_count_all_parameters
		PPVL_count_parameters
	PPVL_count_all_values
		PPVL_count_values
	PPVL_index_parameter
	PPVL_index_value

These functions are used to obtain the number of entries in parameter or value
lists, or the index to a particular parameter or value in a list.


PPVL_count_all_parameters -

The total number of parameters in The_Aggregate's parameter list, and 
recursively all aggregate parameters in that list, is returned. This function
uses the PPVL_count_parameters function. It does not modify the value of
PPVL_errno.


PPVL_count_parameters -

The number of parameters in The_Aggregate's parameter list, and only that 
list, is returned.

On success a non-negative count value is returned. On failure a value of -1 is
returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Aggregate is NULL.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Aggregate is not an aggregate parameter.
	PPVL_ERROR_EMPTY_STATEMENT
		The_Aggregate has no parameters. This is just a warning. A count of
			zero is returned.


PPVL_count_all_values -

The total number of values in The_Array's value list, and recursively all 
array values in that list, is returned. This function uses the 
PPVL_count_values function. It does not modify the value of PPVL_errno.


PPVL_count_values -

The number of values in The_Array's value list, and only that list, is 
returned.

On success a non-negative count value is returned. On failure a value of -1 is
returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Array is NULL.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Array is not an array type value. This is just a warning. A count
			of 1 is returned.
	PPVL_ERROR_EMPTY_STATEMENT
		The_Array has no values. This is just a warning. A count of 0 is
			returned.


PPVL_index_parameter -

The_Parameter is located in the parameter list of The_Aggregate and its index
in this list is returned. Thus The_Parameter is found at 
The_Aggregate->content.parameters[index]. Parameter lists are always NULL 
terminated and so are usually processed with handle (pointer to parameter 
pointer) references, but there may be a need to use array index references for
some applications.

On success a non-negative index value is returned. On failure a value of -1 is
returned. Possible error/warning conditions:

	PPVL_ERROR_BAD_ARGUMENT
		The_Aggregate is NULL.
	PPVL_ERROR_ILLEGAL_SYNTAX
		The_Aggregate is not an aggregate parameter.
	PPVL_ERROR_EMPTY_STATEMENT
		The_Parameter was not found in The_Aggregate parameter list.


PPVL_index_value -

This function works just like PPVL_index_parameter, but searches for The_Value
in The_Array.


* Error handling

Every PPVL function sets the global int variable PPVL_errno. Initially it is
set to PPVL_SUCCESS on entering the function. If an error condition is 
detected, it is set to an appropriate value before the function cleans up and
returns an error status value. The condition, however, may be considered to be
only a warning (this may depend on PPVL_strict being FALSE) in which case
PPVL_errno is set accordingly but no error return is taken (though some
recovery action may be taken). Note that only the first warning condition in a
function is registered. If an error condition occurs after a warning condition
has been encountered the error condition takes precedence over the warning.

There are three levels of error codes (four if you count PPVL_SUCCESS, a.k.a
PPVL_ERROR_NO_ERROR):

typedef enum	PPVL_error_code
	{
	PPVL_ERROR_NO_ERROR,

	Fatal errors:

	PPVL_ERROR_NO_MEMORY,
	PPVL_ERROR_BAD_ARGUMENT,
	PPVL_ERROR_READING_FILE,

	Syntax errors:

	PPVL_ERROR_MISSING_QUOTE_END,
	PPVL_ERROR_MISSING_COMMENT_END,
	PPVL_ERROR_MISSING_UNITS_END,
	PPVL_ERROR_RESERVED_CHARACTER,
	PPVL_ERROR_ILLEGAL_SYNTAX,
	PPVL_ERROR_EMPTY_STATEMENT,
	PPVL_ERROR_VALUE_OVERFLOW,
	PPVL_ERROR_ARRAY_CLOSURE_MISMATCH,

	Abstract (high level) usage problems:

	PPVL_ERROR_GROUP_VALUE,
	PPVL_ERROR_MISSING_AGGREGATE_END,
	PPVL_ERROR_AGGREGATE_CLOSURE_MISMATCH,
	PPVL_ERROR_SIZED_RECORDS,

	PPVL_ILLEGAL_ERROR_CODE
	}
	PPVL_Error_Code;

Fatal errors are non-recoverable and always result in a failure status value
from the function. Syntax errors are usually recoverable and are treated as
warnings if PPVL_strict is FALSE. Abstract errors are treated like syntax
errors.

Every error code has a corresponding brief (one short line) descriptive error
message contained in the global PPVL_errmsg strings array. The macro 
PPVL_ERROR_MESSAGE provides the description string for the current value of 
PPVL_errno (this is commonly used as a printf-type function argrument).


* Examples

Here is a program that creates a new aggregate parameter containing a few
parameters with different types of values:

#include	"PPVL.h"

main ()
{
PPVL_Parameter
	*The_Aggregate;
int
	value;
double
	number;

The_Aggregate =
	PPVL_add_parameter (
	PPVL_add_parameter (
	PPVL_add_parameter (
	PPVL_add_parameter (
		PPVL_new_parameter
			(
			"The_Aggregate",
			PPVL_CLASS_BEGIN_GROUP,
			NULL,
			"**\n** The base aggregate. **\n**",
			NULL
			),
			PPVL_new_parameter
				(
				"Parameter_0",
				PPVL_CLASS_TOKEN,
				NULL,
				" A token ",
				NULL
				)),
			PPVL_new_parameter
				(
				"Parameter_1",
				PPVL_CLASS_ASSIGNMENT,
				PPVL_new_value
					(
					PPVL_TYPE_INTEGER,
					(value = 1, &value),
					0,
					"int decimal"
					),
				" A single decimal integer value = 1 ",
				NULL
				)),
			PPVL_new_parameter
				(
				"Parameter_2",
				PPVL_CLASS_ASSIGNMENT,
				PPVL_add_value (
				PPVL_add_value (
				PPVL_add_value (
					PPVL_new_value
						(
						PPVL_TYPE_SET,
						NULL,
						0,
						"set"
						),
						PPVL_new_value
							(
							PPVL_TYPE_IDENTIFIER,
							"IDENTIFIER",
							0,
							NULL
							)),
						PPVL_new_value
							(
							PPVL_TYPE_SYMBOL,
							"SYMBOL",
							0,
							NULL
							)),
						PPVL_new_value
							(
							PPVL_TYPE_TEXT,
							"A text value",
							0,
							NULL
							)),
				" An array of three string values. ",
				NULL
				)),
			PPVL_new_parameter
				(
				"Parameter_3",
				PPVL_CLASS_ASSIGNMENT,
				PPVL_new_value
					(
					PPVL_TYPE_REAL,
					(number = 3.3, &number),
					0,
					"double"
					),
				" A single double number = 3.3 ",
				NULL
				)
		);

PPVL_write_parameter (The_Aggregate, stdout, 0);
exit (0);
}

The program, above, generates the following output (comment delimiters have
been changed to "/-" and "-/" for this comment block):

/-**-/
/-** The base aggregate. **-/
/-**-/
BEGIN_GROUP = The_Aggregate;
        /- A token -/
        Parameter_0;
        /- A single decimal integer value = 1 -/
        Parameter_1 = 1 <int decimal>;
        /- An array of three string values. -/
        Parameter_2 = {IDENTIFIER, 'SYMBOL', "A text value"} <set>;
        /- A single double number = 3.3 -/
        Parameter_3 = 3.30000 <double>;
END_GROUP = The_Aggregate;


A common practice is to have a couple of PVL parameters near the beginning of
the file label that indicate the record size and number for the label. These
are obtained from a little bite of the file, and then used to read the entire
label. Here is some sample code (sans error checking for clarity) that
demonstrates this procedure:

file = fopen (filename, "r");
The_Aggregate = PPVL_read_aggregate
	(
	file,
	512,
	NULL
	);
The_Parameter = PPVL_find_parameter
	(
	The_Aggregate,
	PPVL_SEARCH_FROM_THE_TOP,
	PPVL_SELECT_NAME,
	"RECORD_BYTES",
	NULL
	);
count = The_Parameter->content.value->data.integer;
The_Parameter = PPVL_find_parameter
	(
	The_Aggregate,
	PPVL_SEARCH_FROM_THE_TOP,
	PPVL_SELECT_NAME,
	"LABEL_RECORDS",
	NULL
	);
count *= The_Parameter->content.value->data.integer;
PPVL_free_parameter (The_Aggregate);
rewind (file);
The_Aggregate = PPVL_read_aggregate (file, count, NULL);
PPVL_write_parameter (The_Aggregate, stdout, 0);


Here's an even simpler example that (should) produce the same result (along
with some error checking and processing info):

file = fopen (filename, "r");
if (The_Aggregate = PPVL_read_aggregate
		(
		file,
		PPVL_NO_READ_LIMIT,
		&count
		))
	{
	printf ("PPVL_read_aggregate scanned %d characters.\n",
		count);
	if (PPVL_errno)
		printf ("Warning: %s\n",
			PPVL_ERROR_MESSAGE);
	PPVL_write_parameter (The_Aggregate, stdout, 0);
	}
else
	fprintf (stderr, "PPVL_read_aggregate failed at character %d: %s\n",
		count, PPVL_ERROR_MESSAGE);


Author:

Bradford Castalia                       Castalia@Arizona.edu
Senior Systems Analyst                  (520) 621-6946
Planetary Image Research Laboratory     (520) 621-4824
Department of Planetary Sciences        FAX: (520) 621-9628
University of Arizona                   Space Sciences Bldg.
1629 E. University Blvd.                Room 429
Tucson, Arizona  85721-0092

"Build an image in your mind, fit yourself into it."
    The log of Cyradis seeress of Kell.


PIRL SCCS ID: @(#)PPVL.c	1.11 12/10/01
*/
static char		*SCCS_ID = "@(#)PPVL.c	1.11 12/10/01";

/*******************************************************************************

Copyright (C) 2000  Bradford Castalia, University of Arizona

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at your
option) any later version.

This program 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.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


Bradford Castalia                       Castalia@Arizona.edu
Senior Systems Analyst                  http://PIRL.LPL.Arizona.edu/~castalia
Planetary Image Research Laboratory     520-621-4824
Department of Planetary Sciences        FAX: 520-621-9628
University of Arizona                   Space Sciences Bldg.
1629 E. University Blvd.                Room 429
Tucson, Arizona  85721-0092

"Build an image in your mind, fit yourself into it."
    The Log of Cyradis, Seeress of Kell.

*******************************************************************************/


/*******************************************************************************

	Included header files:
*/
#if ! defined (USE_PARAMETER_VALUE_UNION)
#define USE_PARAMETER_VALUE_UNION
#endif
#include	"PPVL.h"

#include	<stdarg.h>
#include	<stdlib.h>
#include	<errno.h>
#include	<string.h>

#include	"PIRL_strings.h"


/*******************************************************************************

	Global variables
*/
/*	The format to use for output of real values.

	>>> CAUTION <<<
	The precision (number of places after the decimal point)
	is very dependent on the host system's ability.

#define REAL_NUMBER_FORMAT				"%#.15E"
*/
#define REAL_NUMBER_FORMAT				"%#G"
char	*PPVL_real_number_format		= REAL_NUMBER_FORMAT;


/*	Verbatim quoted strings.

	Normally multi-line quoted strings in PVL statements have white
	space surrounding the line breaks compressed to a single space
	character (except when the last non-white space character on the
	line is a dash ("-"), in which case no space is included). This
	is because output formatting is expected to be controlled by
	embedded format characters which are processed on output:

		\n - line break.
		\t - horizontal tab.
		\f - form feed (page break).
		\\ - backslash character.
		\v - vebatim (no formatting) till next \v.
	
	If quoted strings in PVL statements are not to be compressed,
	set the following variable to TRUE.
*/
int		PPVL_verbatim_strings			= 0;

/*	Parameter pathname delimiter.
*/
char 	*PPVL_parameter_path_delimiter	= "/";


/*	Error handling:
*/
#ifndef PPVL_STRICT
#define PPVL_STRICT	0
#endif
int		PPVL_strict = PPVL_STRICT;

int		PPVL_errno = 0;

char	*PPVL_errmsg[] =
	{
	"(No error)",
	"Out of memory.",
	"Bad function argument.",
	"Problem reading the file.",

	"Missing end of quoted string.",
	"Missing end of comment string.",
	"Missing end of units string.",
	"Reserved character in symbol.",
	"Illegal syntax.",
	"Empty statement or list.",
	"Numeric value out of range (overflow).",
	"Array enclosure characters do not match.",

	"Inappropriate aggregate identifier (not a string).",
	"Missing end of aggregate parameters.",
	"Aggregate enclosure parameters do not match.",
	"Sized (variable) records file.",
	/*	The following entry must be last */
	"Illegal error code!"
	};

#define ERROR(condition)        \
	{                           \
	if (condition)              \
		PPVL_errno = condition; \
	goto Error_condition;       \
	}

#define WARNING(condition)      \
	{                           \
	if (! PPVL_errno)           \
		PPVL_errno = condition; \
	}


/*******************************************************************************

	Local variables
*/
/*	These reserved characters should not occur in identifiers
	and symbols, though they may be tolerated and only flagged
	if they occur. Some of these characters have special meaning
	in specific contexts as delimiters of abstract objects, which
	are called on in the delimiter list that follows.
*/
static char		*RESERVED_CHARACTERS = "{}()[]<>&\"',=;#%~|+! \t\r\n\f\v";

/*	Delimiter characters:
*/
static char		*LINE_DELIMITERS					= "\r\n\f\v";
static char		*LINE_BREAK							= "\r\n";

/*	Note that white space includes the line delimiters.
*/
static char		*WHITE_SPACE						= " \t\r\n\f\v";

/*	Note that parameter delimiters include white space
	and the statement end delimiter in addition to their
	special characters.
*/
static char		*PARAMETER_NAME_DELIMITERS			= "= \t\r\n\f\v;";
static char		*PARAMETER_VALUE_DELIMITERS			= ",{}()< \t\r\n\f\v;";

static char		*COMMENT_START_DELIMITERS			= "/*";
static char		*COMMENT_END_DELIMITERS				= "*/";
static char		*DATE_TIME_DELIMITERS				= "-:";
static char		*VERBATIM_STRING_DELIMITERS			= "\\v";

/*	Symbol definitions are provided instead of variables
	so they can be used as switch cases.
*/
#define			PARAMETER_NAME_DELIMITER			'='
#define			PARAMETER_VALUE_DELIMITER			','
#define			TEXT_DELIMITER						'"'
#define			SYMBOL_DELIMITER					'\''
#define			SET_START_DELIMITER					'{'
#define			SET_END_DELIMITER					'}'
#define			SEQUENCE_START_DELIMITER			'('
#define			SEQUENCE_END_DELIMITER				')'
#define			UNITS_START_DELIMITER				'<'
#define			UNITS_END_DELIMITER					'>'
#define			NUMBER_BASE_DELIMITER				'#'
#define			STATEMENT_END_DELIMITER				';'
#define			STATEMENT_CONTINUATION_DELIMITER	'&'
#define			STRING_CONTINUATION_DELIMITER		'-'


/*	Each reserved parameter name is associated with a specific
	parameter classification in this list:
*/
typedef struct
	{
	char			*name;
	PPVL_Class		classification;
	}
	Name_Code;

static Name_Code special_name_classification[] =
	{
	/*	BEGIN_XXXX classifications are identical to XXXX classifications,
		but the former is more "proper" than the latter
		so put them first in the list.
	*/
/* Dyer Lytle changed the order of these two lines on 11-28-01 to make the
 * output compatible with ISIS.
        {"BEGIN_OBJECT",        PPVL_CLASS_BEGIN_OBJECT},
        {"OBJECT",                      PPVL_CLASS_OBJECT},
*/
        {"OBJECT",                      PPVL_CLASS_OBJECT},
        {"BEGIN_OBJECT",        PPVL_CLASS_BEGIN_OBJECT},
        {"END_OBJECT",          PPVL_CLASS_END_OBJECT},

/* Dyer Lytle changed the order of these two lines on 11-30-01 to make the
 * output compatible with ISIS.
        {"BEGIN_GROUP",         PPVL_CLASS_BEGIN_GROUP},
        {"GROUP",                       PPVL_CLASS_GROUP},
*/
        {"GROUP",                       PPVL_CLASS_GROUP},
        {"BEGIN_GROUP",         PPVL_CLASS_BEGIN_GROUP},
        {"END_GROUP",           PPVL_CLASS_END_GROUP},


	/*	END must be at the end. 8^}
	*/
	{"END",				PPVL_CLASS_END},
	{NULL,				0}
	};

#define END_SPECIAL_NAME	((sizeof (special_name_classification) / sizeof (Name_Code)) - 2)
#define END_NAME			(special_name_classification[END_SPECIAL_NAME].name)


/*******************************************************************************

	Local symbols and tunable parameters
*/
/*	Memory allocation parameters -

	When allocating memory for an array of PPVL_Value structures,
	the amount of increase is determined by the current size of
	the array relative to a series of thresholds. Initially the
	array size increases slowly since most arrays are expected
	to be small. However, for efficiency (to avoid a large number
	of small reallocations for large arrays), when the array size
	crosses the size increment it is increased by the threshold
	amount. Additional thresholds allow the size increment to
	be increased as the array size increases.
*/
#define VALUES_ARRAY_SIZE_INCREMENT		16
#define VALUES_ARRAY_SIZE_THRESHOLD_1	256
#define VALUES_ARRAY_SIZE_THRESHOLD_2	1024

/*	The amount to increase the size of the string buffer
	when it needs to be enlarged while reading a file. Make it
	at least 8k to allow for the largest possible sized record
	on first read.
*/
#define STRING_BUFFER_SIZE_INCREMENT	8192

/*	The default file read limit.

	This value sets a limit on the amount of a file to read
	when working through a file label. When the limit is reached
	it is presumed that there are no more PVL statements to be
	processed. Set it to -1 for no limit.

	>>> WARNING <<< Without a recognizable end-of-label marker it is
	quite possible for non-label file data to be interpreted as PVL
	statements. Thus it is advisable to set a reasonable limit on
	the amount of file data to read. It is typical for a PVL label
	to contain as one of its parameters the size of the label. This
	suggests a strategy of using a reasonably small limit (enough
	to ensure that the label size parameter, usually near the start
	of the PVL statements, will be read), finding this parameter,
	rewinding the file, and ingesting the PVL statements again using
	the parameter value as the limiting value. A less "intelligent"
	(but likely to be easier) approach is to use the default limit
	and simply check the validity of parameters, since applications
	are likely to know what parameters are valid for them.
*/
#define DEFAULT_READ_LIMIT				(32 * STRING_BUFFER_SIZE_INCREMENT)


/*	String buffer management for reading from a file.
*/
typedef struct
	{
	FILE	*file;			/* stream to read */
	char	*buffer;		/* pointer to dynamic buffer */
	unsigned long
			size,			/* buffer size (not including trailing EOS) */
			next,			/* index to string scan start */
			last,			/* index to EOS after last character read */
			read_limit,		/* maximum amount to read */
			total_read;		/* accumulator for total amount read */

	/*	Special variables for handling sized record format files:
	*/
	int		sized_record;	/* flags sized record reading */
	unsigned char
			padded,
			LSB;
	}
	String_Buffer;


/*	Tests whether potential record size bytes
	are taken to really be characters or not.
*/
#define Is_Record_Size(data)	\
	(*((data) + 1) < 32)

/*	Converts a 16-bit (2 bytes), LSB-first value at the data address
	to a record size value.
*/
#define Record_Size(data)	\
	((*((data) + 1) << 8) + *(unsigned char *)(data))
#define Record_Size_Assembly(LSB, MSB)	\
	(((MSB) << 8) + (unsigned char)(LSB))


/*	Macros for common string and numerical operations.
*/
/*	Returns pointer to first character not in list (or end of string).
*/
#define Skip_Over(string, list)			((string) + strspn  (string, list))

/*	Returns pointer to first character in list (or end of string).
*/
#define Skip_Until(string, list)		((string) + strcspn (string, list))

#define Max_Value(a, b)					((a) > (b) ? (a) : (b))
#define Min_Value(a, b)					((a) < (b) ? (a) : (b))

#define Absolute_Value(value)			((value) < 0 ? -(value) : (value))

#define Sign_Value(sign, value)			\
	((sign) < 0 ? \
		((value) < 0 ?  (value) : -(value)) : \
		((value) < 0 ? -(value) :  (value)))


/*	Makes a new object.
*/
#define New_Parameter \
	((PPVL_Parameter *)calloc (1, sizeof (PPVL_Parameter)))

#define New_Value \
	((PPVL_Value *)calloc (1, sizeof (PPVL_Value)))


/*	A name with white space needs to be quoted.
*/
#define Write_Name(name)	                           \
	{                                                  \
	if (*(Skip_Until (name, WHITE_SPACE)))             \
		fprintf (The_File, "%c%s%c",                   \
			SYMBOL_DELIMITER, name, SYMBOL_DELIMITER); \
	else                                               \
		fprintf (The_File, "%s", name);                \
	}


/*	Miscellaneous common symbols.
*/
#ifndef NULL
#define NULL							((void *)0)
#endif

#ifndef TRUE
#define TRUE							1
#endif
#ifndef FALSE
#define FALSE							0
#endif

#define END_OF_STRING					'\0'


/*******************************************************************

	Local function prototypes
*/
static PPVL_Error_Code
read_aggregate
	(
	PPVL_Parameter	*The_Aggregate,
	String_Buffer	*The_String_Buffer
	);

static PPVL_Error_Code
scan_aggregate
	(
	PPVL_Parameter	*The_Aggregate,
	char			*The_String,
	char			**Next_Character
	);

static PPVL_Error_Code
enlarge_list
	(
	void			***List,
	int				*Size
	);

static PPVL_Error_Code
reduce_list
	(
	void			***List
	);


/*******************************************************************

	Function Definitions
*/

PPVL_Parameter *
PPVL_read_aggregate
	(
	FILE			*The_File,
	unsigned long	Read_Limit,
	unsigned long	*Total_Scanned
	)
{
PPVL_Parameter
	*The_Aggregate = NULL;
String_Buffer
	The_String_Buffer;
int
	record_size;
unsigned long
	index;


PPVL_errno = PPVL_SUCCESS;

if (! The_File)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);


/*	Allocate the file's string buffer and initialize it.

	>>> WARNING <<<
	The_String_Buffer.size does NOT include the extra terminating EOS.

	Note that The_String_Buffer->size does not include this byte
	so it is never used when reading from the file, thus ensuring
	that there will always be a place for the EOS put in the
	Last_Character (below) which guarantees that The_String is
	terminated regardless of what is read from the file.
*/
if (! (The_String_Buffer.read_limit = Read_Limit))
	The_String_Buffer.read_limit = DEFAULT_READ_LIMIT;

The_String_Buffer.size = Min_Value
	(STRING_BUFFER_SIZE_INCREMENT, The_String_Buffer.read_limit);

if (! (The_String_Buffer.buffer = (char *)malloc
		(The_String_Buffer.size + 1)))
	ERROR (PPVL_ERROR_NO_MEMORY);

The_String_Buffer.file = The_File;
The_String_Buffer.next = 0;

/*	Prime the pump.
*/
if (! (The_String_Buffer.total_read = fread
		(
		The_String_Buffer.buffer,
		1,
		The_String_Buffer.size,
		The_String_Buffer.file
		)))
	ERROR (PPVL_ERROR_READING_FILE);

The_String_Buffer.last = The_String_Buffer.total_read;


/*	The "sized record" problem:

Some files use a record structure which is composed of a leading size value
(16-bits, LSB first) followed by size bytes of data. This is like Pascal
strings (and just as cumbersome). Unfortunately, sized records add another
layer of processing logic for the tolerant PVL design goal.

The solution, here, is to first test for the size value in the first two bytes
read and, if present, flag this condition so that string buffer refills will
be cleaned up. The test for the presence of the size value depends on two
assumptions: 1) the file is currently positioned at the beginning of a size
value, and 2) the MSB of the size value maps to an unprintable character (i.e.
< 32). The first assumption depends on the user. The second assumption depends
on the first record having less than 8k bytes.

The string buffer will continue to be processed as usual, but the string buffer
will be cleaned up after each read by replacing the record size values with
LINE_BREAK characters. To keep track of where the record size values are
located in the string buffer, the offset from "last" to the next size value is
recorded in "sized_record". Since this is also used as a flag for the sized
records format of the data, this value must not be zero. Therefore, if this
value would be 0, it is set to -1 instead. It's possible for a size value to be
split across a buffer read. In this case the recorded value is set to -2, the
first (LSB) byte of the size value is saved in the String_Buffer "LSB" entry,
and the first byte of the next read is known to be the value of the next (MSB)
byte (see the padded record exception below). Thus the interpretation of
sized_record is as follows:

	  0 - Not a sized record file.
	> 0 - Offset from last to next size value (see padded exception).
	 -1 - Offset is 0.
	 -2 - First byte of buffer is second byte of size value.
			The value of the first byte is stored in "LSB".

While one might think that because all sized records are guaranteed to be of
even size - by padding, when needed, which adds it's own problems - that an
even sized buffer would prevent the possibility of a split size value, the
unpredictable nature of the PVL statements and the manner of processing in a
sliding window buffer could result in the buffer becoming non-aligned with
respect to file records. Therefore, the possibility of split size values must
be taken into account.

The record size value is the number of characters in the next PVL record (whcih
many, of course, not be a complete PVL statement). This value does not include
the two bytes of the size value itself, or a possible zero valued byte that is
appended to the record whenever its size is odd. This pad byte presents the
problem of prematurely terminating the string presumed to contain all of the
PVL statements. Therefore it is plugged with a space character whenever it
occurs. To keep track of odd-sized, and thus padded records, across buffer
refills, a "padded" flag entry is set accordingly at the end of cleaning up
each new buffer refill. This value (0 if no pad byte, 1 otherwise) allows the
sized_record value to be adjusted accordingly.
*/
if (Is_Record_Size (The_String_Buffer.buffer))
	{
	/*	It's a sized record file.
	*/
	for (index = record_size = The_String_Buffer.padded = 0;
		(index + The_String_Buffer.padded + 1) < The_String_Buffer.last;
		 index += record_size)
		{
		if (The_String_Buffer.padded)
			{
			The_String_Buffer.buffer[index++] = ' ';	/* patch the pad byte */
			}
		record_size = Record_Size (&The_String_Buffer.buffer[index]);
		The_String_Buffer.padded = record_size % 2;
		The_String_Buffer.buffer[index++] = LINE_BREAK[0];
		The_String_Buffer.buffer[index++] = LINE_BREAK[1];
		}

	/*	Check for possible dangling pad byte.
	*/
	if (index < The_String_Buffer.last &&
		The_String_Buffer.padded)
		{
		The_String_Buffer.buffer[index++] = ' ';
		}

	/*	Check for (unlikely) split size value.
	*/
	if ((index + 1) == The_String_Buffer.last)
		{
		/*	Note that it's not possible to have a split size value
			on the first read if the buffer size is even. But since
			some careless programmer might set the buffer size odd
			the appropriate code is provide just-in-case.
		*/
		The_String_Buffer.sized_record = - 2;
		The_String_Buffer.LSB = The_String_Buffer.buffer[index];
		The_String_Buffer.buffer[index] = LINE_BREAK[0];
		}

	/*	Set the offset from last to where this process
		will pick up again after the next buffer refill.
	*/
	else if (! (The_String_Buffer.sized_record =
				index - The_String_Buffer.last))
		The_String_Buffer.sized_record = -1;
	}
else
	The_String_Buffer.sized_record = 0;


The_String_Buffer.buffer[The_String_Buffer.last] = END_OF_STRING;


/*	Allocate an empty PPVL_Parameter structure and initialize it.
*/
if (! (The_Aggregate = New_Parameter))
	ERROR (PPVL_ERROR_NO_MEMORY);

if (! (The_Aggregate->name = strdup (PPVL_CONTAINER_NAME)))
	ERROR (PPVL_ERROR_NO_MEMORY);

The_Aggregate->classification = PPVL_CLASS_GROUP;


/*------------------------------------------------------------------------------
	Read the parameters from The_File.
*/
if (read_aggregate
		(
		The_Aggregate,
		&The_String_Buffer
		)
	!= PPVL_SUCCESS &&
	PPVL_errno <= PPVL_FATAL_ERROR)
	ERROR (0);


/*------------------------------------------------------------------------------
	Return the results:
*/
/*	Warn the user if the file was found to contain sized records.
*/
if (The_String_Buffer.sized_record)
	PPVL_errno = PPVL_ERROR_SIZED_RECORDS;

/*	Try to reposition the file stream back to the location of
	the next character in The_String_Buffer.

	Note: The_String_Buffer.last is converted to the amount
	of data remaining in the buffer.
*/
if (The_String_Buffer.last -= The_String_Buffer.next)
	fseek (The_String_Buffer.file, -The_String_Buffer.last, SEEK_CUR);

/*	Report the number of characters scanned up to, but not including,
	The_Delimiter character that terminated the scan; i.e. the offset
	from the file position when this function was entered to the
	location of the next character returned from read_aggregate.
*/
if (Total_Scanned)
	*Total_Scanned =
		The_String_Buffer.total_read - The_String_Buffer.last;

free (The_String_Buffer.buffer);

return (The_Aggregate);


Error_condition:

PPVL_free_parameter (The_Aggregate);

/*	Try to reposition the file stream back to the location of
	the next character in The_String_Buffer.
*/
if (The_String_Buffer.last -= The_String_Buffer.next)
	fseek (The_String_Buffer.file, -The_String_Buffer.next, SEEK_CUR);

/*	Report the number of characters scanned up to, but not including,
	The_Delimiter character that terminated the scan; i.e. the offset
	from the file position when this function was entered to the
	location of the next character returned from read_aggregate.
*/
if (Total_Scanned)
	*Total_Scanned =
		The_String_Buffer.total_read - The_String_Buffer.last;

if (The_String_Buffer.buffer)
	{
	free (The_String_Buffer.buffer);
	}

return (NULL);
}

/******************************************************************************/

static PPVL_Error_Code
read_aggregate
	(
	PPVL_Parameter	*The_Aggregate,
	String_Buffer	*The_String_Buffer
	)
{
PPVL_Parameter
	**parameters = NULL;
char
	*The_Delimiter;
int
	number_of_parameters = 0,
	parameters_array_size = 0,
	read_amount,
	record_size,
	warning = 0;

/*
	Note: The following symbols are defined so as to appear the same 
	as variables with the same, or similar, name when they have a
	corresponding function in other parts of this module.
*/
/*	Pointer to the current PPVL_Parameter.

PPVL_Parameter	*The_Parameter;
*/
#define The_Parameter	(parameters[number_of_parameters])

/*	Pointer to the next string to scan.

char			*The_String;
*/
#define The_String		(&The_String_Buffer->buffer[The_String_Buffer->next])

/*	Pointer to the EOS after the last character read.

char			*Last_Character;
*/
#define Last_Character	(&The_String_Buffer->buffer[The_String_Buffer->last])

/*	Pointer to the EOS at the end of the buffer.

char			*End_of_Buffer
*/
#define End_of_Buffer	(&The_String_Buffer->buffer[The_String_Buffer->size])


The_Delimiter = The_String;	/* starting point */

/*------------------------------------------------------------------------------
	Collect the aggregate's parameters from the file:
*/
while (TRUE)
	{
	if (number_of_parameters == parameters_array_size)
		{
		/*	Enlarge the parameter array list.
		*/
		if (enlarge_list
				(
				(PPVL_Object ***)&parameters,
				&parameters_array_size
				)
			!= PPVL_SUCCESS)
			ERROR (0);

		/*	Update the parameter list address.
		*/
		The_Aggregate->content.parameters = parameters;
		}
	
	/*--------------------------------------------------------------------------
		Get the next parameter:
	*/
	The_Parameter = PPVL_scan_parameter
		(
		The_String,
		&The_Delimiter
		);


	if (The_Delimiter == End_of_Buffer &&
		The_String_Buffer->total_read < The_String_Buffer->read_limit &&
		! feof (The_String_Buffer->file))
		{
		/*----------------------------------------------------------------------
			Hit the end of the buffer. Refresh it, then retry the parameter.

			The chances are very high that the buffer will only contain a
			partial PVL statement at the end. The problem is how to recognize
			an incomplete PVL statement, because it is very easy for this to
			appear as complete. The tactic used here is that hitting the
			end of the buffer is always suspect and will force a retry after
			reloading the buffer, if possible.
		*/
		if (The_Parameter)
			{
			/*	Dispose of the suspect parameter.
			*/
			PPVL_free_parameter (The_Parameter);
			The_Parameter = NULL;	/* reset the end of list */
			}

		if (! The_String_Buffer->next)
			{
			/*	The buffer is already full. Try enlarging it by the lesser of the
				size increment or the amount up to the read limit.
			*/
			read_amount =
				((The_String_Buffer->total_read + STRING_BUFFER_SIZE_INCREMENT)
					> The_String_Buffer->read_limit) ?
						(The_String_Buffer->read_limit - The_String_Buffer->total_read) :
						STRING_BUFFER_SIZE_INCREMENT;
			The_String_Buffer->size += read_amount;

			if (! (The_Delimiter = realloc (The_String_Buffer->buffer,
					The_String_Buffer->size + 1)))
				ERROR (PPVL_ERROR_NO_MEMORY);
			The_String_Buffer->buffer = The_Delimiter;
			*End_of_Buffer = END_OF_STRING;
			}
		else
			{
			/*	Move the remaining characters to the front of the buffer.
			*/
			The_String_Buffer->last -= The_String_Buffer->next;
			memcpy (The_String_Buffer->buffer, The_String,
				The_String_Buffer->last);
			The_String_Buffer->next = 0;	/* new next character location */

			/*	Calculate the amount of buffer free space.
			*/
			read_amount = The_String_Buffer->size - The_String_Buffer->last;

			/*	Refill with the lesser of the free space
				or the amount up to the read limit.
			*/
			if ((The_String_Buffer->total_read + read_amount)
				> The_String_Buffer->read_limit)
				read_amount = The_String_Buffer->read_limit - The_String_Buffer->total_read;
			}

		/*......................................................................
			Refill the buffer.
		*/
		if (! (read_amount = fread
				(
				Last_Character,
				1,
				read_amount,
				The_String_Buffer->file
				)))
			{
			if (! feof (The_String_Buffer->file))
				ERROR (PPVL_ERROR_READING_FILE);
			}

		if (The_String_Buffer->sized_record)
			{
			/*	It's a sized record file.
				Find the location of the next record size value
				BEFORE the location of last is moved.
			*/
			if (The_String_Buffer->sized_record < 0)
				{
				if (The_String_Buffer->sized_record == -1)
					The_Delimiter = Last_Character;
				else
					{
					/*	Split record size value.
						Reassemble it and fill the MSB hole.
					*/
					The_Delimiter = Last_Character +
						Record_Size_Assembly (The_String_Buffer->LSB, *Last_Character);
					*Last_Character = LINE_BREAK[1];		/* plug this leak */
					}
				}
			else
				The_Delimiter = Last_Character +
					The_String_Buffer->sized_record;
			}

		/*	Update The_String_Buffer variables.
		*/
		The_String_Buffer->total_read += read_amount;
		The_String_Buffer->last += read_amount;
		*Last_Character = END_OF_STRING;

		if (The_String_Buffer->sized_record)
			{
			/*	It's a sized record file.
				Plug the record size values with LINE_BREAK characters.
			*/
			for (;
				(The_Delimiter + The_String_Buffer->padded + 1) < Last_Character;
				 The_Delimiter += record_size)
				{
				if (The_String_Buffer->padded)
					{
					*The_Delimiter++ = ' ';		/* patch the pad byte */
					}
				record_size = Record_Size (The_Delimiter);
				The_String_Buffer->padded = record_size % 2;
				*The_Delimiter++ = LINE_BREAK[0];
				*The_Delimiter++ = LINE_BREAK[1];
				}

			/*	Check for possible dangling pad byte.
			*/
			if (The_Delimiter < Last_Character &&
				The_String_Buffer->padded)
				{
				*The_Delimiter++ = ' ';			/* patch the pad byte */
				}

			/*	Check for split record size value.
			*/
			if ((The_Delimiter + 1) == Last_Character)
				{
				The_String_Buffer->sized_record = - 2;
				The_String_Buffer->LSB = *The_Delimiter;
				*The_Delimiter = LINE_BREAK[0];
				}

			/*	Set the offset from last to where this process
				will pick up again after the next buffer refill.
			*/
			else if (! (The_String_Buffer->sized_record =
						The_Delimiter - Last_Character))
				The_String_Buffer->sized_record = -1;
			}

		continue;	/* don't move forward, just try again */
		}


	if (The_Parameter)
		{
		/*----------------------------------------------------------------------
			The_Parameter is valid.
		*/
		/*	Move The_String_Buffer forward.
		*/
		The_String_Buffer->next += The_Delimiter - The_String;
		The_Delimiter = The_String;

		if (PPVL_errno &&
			! warning)

			/*	The_Parameter has a WARNING condition.
				Hold on to it for eventual possible return.
			*/
			warning = PPVL_errno;

		/*......................................................................
			Check for END parameters:
		*/
		if (PPVL_Class_Is_End_Aggregate (The_Parameter->classification))
			{
			if (The_Aggregate->classification
				!= (The_Parameter->classification & ~PPVL_END))

				/*	The begin aggregate parameter doesn't match
					the end aggregate parameter.
				*/
				WARNING (PPVL_ERROR_AGGREGATE_CLOSURE_MISMATCH);

			/*	Dispose of this parameter.
			*/
			PPVL_free_parameter (The_Parameter);
			The_Parameter = NULL;	/* reset the end of list */
			break;
			}

		if (PPVL_Class_Is_End (The_Parameter->classification))
			{
			if (strcmp (The_Aggregate->name, PPVL_CONTAINER_NAME))

				/*	This isn't the last parameter for the FILE aggregate.
				*/
				WARNING (PPVL_ERROR_MISSING_AGGREGATE_END);

			/*	Dispose of this parameter.
			*/
			PPVL_free_parameter (The_Parameter);
			The_Parameter = NULL;	/* reset the end of list */
			ERROR (0);
			}

		if (! *The_String)
			{
			/*	That's all folks!
			*/
			number_of_parameters++;
			ERROR (0);
			}

		if (PPVL_Class_Is_Aggregate (The_Parameter->classification))
			{
			/*..................................................................
				The parameter is an aggregate of parameters.
			*/
			if (read_aggregate
					(
					The_Parameter,
					The_String_Buffer
					)
				!= PPVL_SUCCESS)
				{
				number_of_parameters++;
				ERROR (0);
				}
			}

		/*	Move forward to the next parameter list slot.
		*/
		number_of_parameters++;
		}
	else
		{
		/*----------------------------------------------------------------------
			No parameter.
		*/
		if (*The_Delimiter)

			/*	Not end of string.

				The failure to obtain a parameter may be due to due the lack
				of an identifiable end of label marker which has resulted in
				scanning past the end of the PVL statement list into the
				file data area. In any case, stop processing the file.
			*/
				ERROR (0);

		/*......................................................................
			End of string, but not end of buffer.

			End of file/label encountered without aggregate end parameter.
			There are no more characters to read (the last read hit the
			end-of-file, or the file contained its own EOS).

			Note: This could be the expected end of the PVL statement list
			for some implementations: Strict PVL syntax requires an END
			parameter to terminate the statement list. However, some
			implementations (e.g. VICAR) do not provide this.
		*/
		ERROR (PPVL_ERROR_MISSING_AGGREGATE_END);
		}
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
/*	Reduce the parameter list size to only the amount used.
*/
if (number_of_parameters < parameters_array_size &&
	reduce_list ((PPVL_Object ***)&The_Aggregate->content.parameters)
		!= PPVL_SUCCESS)
	ERROR (0);

/*	Apply any WARNING that was caught.
*/
if (warning)
	PPVL_errno = warning;

return (PPVL_SUCCESS);


Error_condition:

/*	An error condition means that processing is halted.
	Any parameters gathered are returned. It is the caller's
	responsibility to decide what to do with them.
*/

/*	Dispose of any dangling parameter.
*/
if (The_Parameter)
	{
	PPVL_free_parameter (The_Parameter);
	The_Parameter = NULL;
	}

/*	Reduce the parameter list size to only the amount used.
*/
if (number_of_parameters < parameters_array_size)
	reduce_list ((PPVL_Object ***)&The_Aggregate->content.parameters);

/*	Return the location of The_Delimiter.
*/
The_String_Buffer->next += The_Delimiter - The_String;

return (PPVL_errno);


#undef The_Parameter
#undef The_String
#undef Last_Character
#undef End_of_Buffer
}

/******************************************************************************/

PPVL_Parameter *
PPVL_scan_aggregate
	(
	char			*The_String,
	char			**Next_Character
	)
{
PPVL_Parameter
	*The_Aggregate = NULL;


PPVL_errno = PPVL_SUCCESS;

if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

/*	Allocate an empty PPVL_Parameter structure and initialize it.
*/
if (! (The_Aggregate = New_Parameter))
	ERROR (PPVL_ERROR_NO_MEMORY);

if (! (The_Aggregate->name = strdup (PPVL_CONTAINER_NAME)))
	ERROR (PPVL_ERROR_NO_MEMORY);

The_Aggregate->classification = PPVL_CLASS_GROUP;


/*------------------------------------------------------------------------------
	Scan the parameters from The_String.
*/
if (scan_aggregate
		(
		The_Aggregate,
		The_String,
		Next_Character
		)
	!= PPVL_SUCCESS &&
	PPVL_errno <= PPVL_FATAL_ERROR)
	ERROR (0);


/*------------------------------------------------------------------------------
	Return the results:
*/
return (The_Aggregate);


Error_condition:

if (The_Aggregate)
	PPVL_free_parameter (The_Aggregate);

return (NULL);
}

/******************************************************************************/

static PPVL_Error_Code
scan_aggregate
	(
	PPVL_Parameter	*The_Aggregate,
	char			*The_String,
	char			**Next_Character
	)
{
PPVL_Parameter
	**parameters = NULL;
char
	*The_Delimiter = The_String;
int
	number_of_parameters = 0,
	parameters_array_size = 0,
	warning = 0;

/*	Pointer to the current PPVL_Parameter.

PPVL_Parameter	*The_Parameter;
*/
#define The_Parameter	(parameters[number_of_parameters])




/*------------------------------------------------------------------------------
	Collect the aggregate's parameters from the string:
*/
while (*The_String)
	{
	if (number_of_parameters == parameters_array_size)
		{
		/*	Enlarge the parameter array list.
		*/
		if (enlarge_list
				(
				(PPVL_Object ***)&parameters,
				&parameters_array_size
				)
			!= PPVL_SUCCESS)
			ERROR (0);

		/*	Update the parameter array address.
		*/
		The_Aggregate->content.parameters = parameters;
		}
	
	/*--------------------------------------------------------------------------
		Get the next parameter:
	*/
	if (! (The_Parameter = PPVL_scan_parameter
			(
			The_String,
			&The_Delimiter
			)))
			ERROR (0);

	if (PPVL_errno &&
		! warning)

		/*	The_Parameter produced a WARNING.
			Hold on to it for eventual possible return.
		*/
		warning = PPVL_errno;

	/*--------------------------------------------------------------------------
		Check the classification of the parameter:
	*/
	/*	Move The_String forward to The_Delimiter.
	*/
	The_String = The_Delimiter;

	if (PPVL_Class_Is_End_Aggregate (The_Parameter->classification))
		{
		if (The_Aggregate->classification
			!= (The_Parameter->classification & ~PPVL_END))
			{
			/*	The begin aggregate parameter doesn't match
				the end aggregate parameter.
			*/
			WARNING (PPVL_ERROR_AGGREGATE_CLOSURE_MISMATCH);
			}

		/*	Dispose of this end aggregate parameter.
		*/
		PPVL_free_parameter (The_Parameter);
		The_Parameter = NULL;	/* reset the end of list */
		break;
		}

	if (PPVL_Class_Is_End (The_Parameter->classification))
		{
		/*	Dispose of this END parameter.
		*/
		if (strcmp (The_Aggregate->name, PPVL_CONTAINER_NAME))

			/*	This isn't the last parameter for the STRING aggregate.
			*/
			WARNING (PPVL_ERROR_MISSING_AGGREGATE_END);

		PPVL_free_parameter (The_Parameter);
		The_Parameter = NULL;	/* reset the end of list */
		ERROR (0);
		}

	if (PPVL_Class_Is_Aggregate (The_Parameter->classification))
		{
		/*......................................................................
			The parameter is an aggregate of parameters.
		*/
		if (scan_aggregate
				(
				The_Parameter,
				The_String,
				&The_Delimiter
				)
			!= PPVL_SUCCESS)
			{
			number_of_parameters++;
			ERROR (0);
			}
		The_String = The_Delimiter;
		}

	/*	Move forward to the next parameter list slot.
	*/
	number_of_parameters++;
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
/*	Reduce the parameter list size to only the amount used.
*/
if (number_of_parameters < parameters_array_size &&
	reduce_list ((PPVL_Object ***)&The_Aggregate->content.parameters)
		!= PPVL_SUCCESS)
	ERROR (0);

/*	Return the location of The_Delimiter.
*/
if (Next_Character)
	*Next_Character = The_Delimiter;

/*	Apply any WARNING that was caught.
*/
if (warning)
	PPVL_errno = warning;

return (PPVL_SUCCESS);


Error_condition:

/*	Reduce the parameter list size to only the amount used.
*/
if (number_of_parameters < parameters_array_size)
	reduce_list ((PPVL_Object ***)&The_Aggregate->content.parameters);

/*	Return the location of The_Delimiter.
*/
if (Next_Character)
	*Next_Character = The_Delimiter;

return (PPVL_errno);


#undef The_Parameter
}

/******************************************************************************/

PPVL_Parameter *
PPVL_scan_parameter
	(
	char			*The_String,
	char			**Next_Character
	)
{
PPVL_Parameter
	*The_Parameter = NULL;
char
	*The_Delimiter,
	*character;
Name_Code
	*special_name;


PPVL_errno = PPVL_SUCCESS;
The_Delimiter = The_String;

if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

/*	Allocate an empty PPVL_Parameter structure.
*/
if (! (The_Parameter = New_Parameter))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*------------------------------------------------------------------------------
	Collect any leading comments before the parameter name:
*/
The_String = Skip_Over (The_String, WHITE_SPACE);

while (TRUE)
	{
	if (! (The_Parameter->comments = PPVL_scan_comments
			(
			The_String,
			&The_Delimiter
			))
		&& PPVL_errno)
		ERROR (0);

	The_String = Skip_Over (The_Delimiter, WHITE_SPACE);
	
	/*	Check for premature end of statement.
	*/
	if (! *The_String ||
		*The_String == STATEMENT_END_DELIMITER)
		{
		if (! *The_String ||	/* the order here matters */
			PPVL_strict)
			/*
				Note: Not finding anything is, at worst, a WARNING,
				but it will still look like an error to the user
				since there is no PPVL_Parameter to return. There's
				nothing for it but to assume the user will check for
				an empty statement and do the right thing.
			*/
			ERROR (PPVL_ERROR_EMPTY_STATEMENT);

		/*	Just ignore the statement end delimiter.
		*/
		The_String++;
		WARNING (PPVL_ERROR_EMPTY_STATEMENT);
		The_String = Skip_Over (The_String, WHITE_SPACE);
		continue;
		}
	else
		break;
	}

/*------------------------------------------------------------------------------
	Get the parameter name:
*/
if (*The_String == TEXT_DELIMITER ||
	*The_String == SYMBOL_DELIMITER)
	{
	/*	It's a quoted string.
	*/
	if (PPVL_strict)
		ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

	if (! (The_Parameter->name = PPVL_scan_quoted_string
			(
			The_String,
			&The_Delimiter
			)))
		ERROR (0);
	}
else
	{
	/*	Find the parameter name's trailing delimiter.
	*/
	The_Delimiter = Skip_Until (The_String, PARAMETER_NAME_DELIMITERS);

	/*	Store a copy of the parameter name string.
	*/
	if (! (The_Parameter->name = strndup
			(
			The_String,
			The_Delimiter - The_String
			)))
		ERROR (PPVL_ERROR_NO_MEMORY);

	/*	Check for reserved characters in the parameter name.
	*/
	if (character = PPVL_comb_symbol (The_Parameter->name))
		{
		if (PPVL_strict ||
			! isprint (*character))
			{
			/*	Indicate where the reserved character is located
				in the The_String.
			*/
			The_Delimiter = The_String + (character - The_Parameter->name);
			ERROR (PPVL_ERROR_RESERVED_CHARACTER);
			}
		WARNING (PPVL_ERROR_RESERVED_CHARACTER);
		}
	}

/*------------------------------------------------------------------------------
	Set the initial parameter classification:
*/
/*	At this point all we have is a token.
*/
The_Parameter->classification = PPVL_CLASS_TOKEN;

/*	Check for special names for special classifications.
*/
for (special_name = special_name_classification;
	 special_name->name;
	 special_name++
	)
	{
	if (strcasecmp (special_name->name, The_Parameter->name) == 0)
		{
		The_Parameter->classification = special_name->classification;
		break;
		}
	}

/*------------------------------------------------------------------------------
	Get the parameter value(s):
*/
/*	Find the parameter name delimiter character
	that separates the parameter name
	from the parameter values list.
*/
if (! (The_String = PPVL_skip_white_space_and_comments (The_Delimiter)))
	ERROR (0);

if (*The_String == PARAMETER_NAME_DELIMITER)
	{
	/* This is where the values string starts.
	*/
	The_String++;

	/*	Elevate the parameter classification to assignment
		unless it's already been identified with
		one of the special classifications.
	*/
	if (PPVL_Class_Is_Token (The_Parameter->classification))
		{
		The_Parameter->classification = PPVL_CLASS_ASSIGNMENT;
		}

	if (! (The_Parameter->content.value = PPVL_scan_value
			(
			The_String,
			&The_Delimiter
			)))
		ERROR (0);

	if (PPVL_Class_Is_Aggregate (The_Parameter->classification))
		{
		if (PPVL_Type_Is_String (The_Parameter->content.value->type))
			{
			/*	Set the parameter name to the string value.
			*/
			free (The_Parameter->name);
			The_Parameter->name = The_Parameter->content.value->data.string;

			/*	Dispose of the value.
			*/
			The_Parameter->content.value->data.string = NULL;	/* or lose it! */
			PPVL_free_value (The_Parameter->content.value);
			The_Parameter->content.value = NULL;
			}
		else
			{
			/*	The value is inappropriate for an aggregate.
				It should be an identifier.
			*/
			/*	Dispose of the value.

				>>> WARNING <<< PPVL_free_parameter would take
				this value as a parameter list.
			*/
			PPVL_free_value (The_Parameter->content.value);
			The_Parameter->content.value = NULL;

			if (PPVL_strict)
				ERROR (PPVL_ERROR_GROUP_VALUE);
			WARNING (PPVL_ERROR_GROUP_VALUE);
			}

		}
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
while (*The_Delimiter)
	{
	/*	Skip any trailing white space and statement end delimiters.
	*/
	The_Delimiter = Skip_Over (The_Delimiter, WHITE_SPACE);
	if (*The_Delimiter == STATEMENT_END_DELIMITER)
		The_Delimiter++;
	else
		break;
	}

if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (The_Parameter);


Error_condition:

PPVL_free_parameter (The_Parameter);

if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (NULL);
}

/******************************************************************************/

PPVL_Value *
PPVL_scan_value
	(
	char			*The_String,
	char			**Next_Character
	)
{
PPVL_Value
	*The_Array = NULL,
	**values = NULL;
char
	*The_Delimiter,
	*word,
	*character;
int
	number_of_values = 0,
	values_array_size = 0;
PPVL_Type
	start_type = 0,
	end_type = 0;

/*	Pointer to the current PPVL_Value.

PPVL_Value	*The_Value;
*/
#define The_Value	(values[number_of_values])


PPVL_errno = PPVL_SUCCESS;
The_Delimiter = The_String;

if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);


/*	Allocate and initialize The_Array value.
*/
if (! (The_Array = New_Value))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*	Check for the likey initial array start character.
*/
if (! (The_String = PPVL_skip_white_space_and_comments (The_String)))
	ERROR (0);

if (*The_String == SET_START_DELIMITER)
	The_Array->type = start_type = PPVL_TYPE_SET;
else if (*The_String == SEQUENCE_START_DELIMITER)
	The_Array->type = start_type = PPVL_TYPE_SEQUENCE;
if (! The_Array->type)
	The_Array->type = PPVL_TYPE_SET;	/* the default */
else
	{
	The_String++;
	}
The_Delimiter = The_String;


while (TRUE)
	{
	if (number_of_values == values_array_size)
		{
		/*	Enlarge the value array list.
		*/
		if (enlarge_list
				(
				(PPVL_Object ***)&values,
				&values_array_size
				)
			!= PPVL_SUCCESS)
			ERROR (0);
	
		/*	Update the values array address.
		*/
		The_Array->data.array = values;
		}
	/*--------------------------------------------------------------------------
		Find the values string.
	*/
	if (! (The_String = PPVL_skip_white_space_and_comments (The_Delimiter)))
		ERROR (0);
	The_Delimiter = The_String;

	/*	Check for a valid values string.
	*/
	switch (*The_String)
		{
		/*----------------------------------------------------------------------
			End of array cases.

			Note: An empty array is valid.
		*/
		case SET_END_DELIMITER:
			end_type = PPVL_TYPE_SET;
		case SEQUENCE_END_DELIMITER:
			if (! end_type)
				end_type = PPVL_TYPE_SEQUENCE;
			The_Delimiter++;

		case STATEMENT_END_DELIMITER:
		case END_OF_STRING:
			goto Done;

		/*----------------------------------------------------------------------
			Syntax error cases.
		*/
		case PARAMETER_NAME_DELIMITER:
		case PARAMETER_VALUE_DELIMITER:
		case UNITS_START_DELIMITER:
		case UNITS_END_DELIMITER:
		case NUMBER_BASE_DELIMITER:
			/*
				Note: It would be possible here to be very tolerant
				and just take these illegal characters as the first
				character of an identifier string, but this seems
				unlikely in any case.
			*/
			ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

		/*----------------------------------------------------------------------
			Possible value:
		*/
		default:
			break;
		}

	/*--------------------------------------------------------------------------
		Get the value.
	*/
	if (*The_String == SET_START_DELIMITER ||
		*The_String == SEQUENCE_START_DELIMITER)
		{
		/*......................................................................
			The value is an array of values.
		*/
		if (! (The_Value = PPVL_scan_value
				(
				The_String,
				&The_Delimiter
				)))
			ERROR (0);
		}
	else
		{
		/*......................................................................
			Allocate The_Value.
		*/
		if (! (The_Value = New_Value))
			ERROR (PPVL_ERROR_NO_MEMORY);

		/*......................................................................
			Get the datum for The_Value.
		*/
		if (PPVL_scan_datum
				(
				The_Value,
				The_String,
				&The_Delimiter
				)
			!= PPVL_SUCCESS)
			ERROR (0);
		}
	The_String = The_Delimiter;

	/*......................................................................
		Get any units string.
	*/
	if (! (The_Value->units = PPVL_scan_units
			(
			The_String,
			&The_Delimiter
			)))
		{
		if (PPVL_errno != PPVL_ERROR_ILLEGAL_SYNTAX)
			ERROR (0);

		/*	PPVL_scan_units only fails with PPVL_ERROR_ILLEGAL_SYNTAX
			when the next word isn't a units string.
		*/
		PPVL_errno = PPVL_SUCCESS;
		The_Delimiter = The_String;
		}

	/*--------------------------------------------------------------------------
		Increment to the next value, now that The_Value is valid.
	*/
	number_of_values++;

	/*	Find the next word.

		Note: Leave the delimiter before any comments in case they
		occur as lead-in to the next PVL statement (i.e. this may
		be the end of the current statement).
	*/
	if (! (The_String = PPVL_skip_white_space_and_comments (The_Delimiter)))
		ERROR (0);

	/*	Check what comes next.
	*/
	switch (*The_String)
		{
		/*----------------------------------------------------------------------
			Another datum is expected:
		*/
		case PARAMETER_VALUE_DELIMITER:
			The_Delimiter = ++The_String;
			continue;

		case SET_START_DELIMITER:
		case SEQUENCE_START_DELIMITER:
			if (PPVL_strict)
				ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);
			WARNING (PPVL_ERROR_ILLEGAL_SYNTAX);
			The_Delimiter = The_String;
			continue;

		/*----------------------------------------------------------------------
			Syntax error cases:
		*/
		case PARAMETER_NAME_DELIMITER:
		case UNITS_START_DELIMITER:
		case UNITS_END_DELIMITER:
		case NUMBER_BASE_DELIMITER:
			The_Delimiter = The_String;
			ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

		/*----------------------------------------------------------------------
			End of array cases:
		*/
		case SET_END_DELIMITER:
			end_type = PPVL_TYPE_SET;

		case SEQUENCE_END_DELIMITER:
			if (! end_type)
				end_type = PPVL_TYPE_SEQUENCE;
			The_Delimiter = ++The_String;

		default:
			break;
		}
	break;
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
Done:

if (start_type != end_type)
	{
	if (PPVL_strict)
		ERROR (PPVL_ERROR_ARRAY_CLOSURE_MISMATCH);
	WARNING (PPVL_ERROR_ARRAY_CLOSURE_MISMATCH);
	}

if (! start_type &&
	! end_type &&
	number_of_values == 1)
	{
	/*	Demote The_Array with a single, undecorated value
		to The_Value. Note that this case can only occur
		at the root level for a PVL statement with a single
		non-array value.
	*/
	The_Array->data.array = NULL;
	PPVL_free_value (The_Array);
	number_of_values = 0;
	The_Array = The_Value;
	free (values);
	}
else if (number_of_values < values_array_size &&
	reduce_list ((PPVL_Object ***)&The_Array->data.array)
		!= PPVL_SUCCESS)
	ERROR (0);

if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (The_Array);


Error_condition:

if (The_Array)
	PPVL_free_value (The_Array);

if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (NULL);


#undef The_Value
}

/******************************************************************************/

PPVL_Error_Code
PPVL_scan_datum
	(
	PPVL_Value		*The_Value,
	char			*The_String,
	char			**Next_Character
	)
{
char
	*The_Delimiter,
	*character,
	*word;
int
	base;


PPVL_errno = PPVL_SUCCESS;
The_Delimiter = The_String;
errno = 0;

if (! The_Value ||
	! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

/*------------------------------------------------------------------------------
	Find the beginning of the datum string:
*/
if (! (The_String = PPVL_skip_white_space_and_comments (The_String)))
	ERROR (0);
The_Delimiter = The_String;

/*	Check the beginning of the datum string.
*/
switch (*The_String)
	{
	/*--------------------------------------------------------------------------
		End of PVL statement cases:
	*/
	case STATEMENT_END_DELIMITER:
	case END_OF_STRING:
		/*
			Note: In the context of expecting to get the datum
			for a PPVL_Value, not finding anything seems to be
			an ERROR instead of a WARNING, even when tolerant.
		*/
		ERROR (PPVL_ERROR_EMPTY_STATEMENT);
		break;

	/*--------------------------------------------------------------------------
		Syntax error cases:
	*/
	case PARAMETER_NAME_DELIMITER:
	case PARAMETER_VALUE_DELIMITER:
	case SET_START_DELIMITER:
	case SET_END_DELIMITER:
	case SEQUENCE_START_DELIMITER:
	case SEQUENCE_END_DELIMITER:
	case UNITS_START_DELIMITER:
	case UNITS_END_DELIMITER:
	case NUMBER_BASE_DELIMITER:
		/*
			Note: It would be possible here to be very tolerant
			and just take these illegal characters as the first
			character of an identifier string, but this seems
			unlikely in any case.
		*/
		ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);
		break;

	/*--------------------------------------------------------------------------
		Quoted string cases:
	*/
	case TEXT_DELIMITER:
	case SYMBOL_DELIMITER:

		if (! (The_Value->data.string = PPVL_scan_quoted_string
				(
				The_String,
				&The_Delimiter
				)))
			ERROR (0);

		if (*The_String == TEXT_DELIMITER)
			The_Value->type = PPVL_TYPE_TEXT;
		else
			The_Value->type = PPVL_TYPE_SYMBOL;
		break;

	/*--------------------------------------------------------------------------
		Numeric value or symbol:
	*/
	default:
		/*
			Find the value string delimiter.
		*/
		if ((The_Delimiter = Skip_Until (The_String, PARAMETER_VALUE_DELIMITERS))
			== The_String)
			/*	As explained above, not a WARNING.
			*/
			ERROR (PPVL_ERROR_EMPTY_STATEMENT);
		
		/*......................................................................
			Try for an integer number.
		*/
		The_Value->data.integer = strtol (The_String, &character, 0);
		if (character == The_Delimiter)
			{
			if (errno)
				{
				if (PPVL_strict)
					ERROR (PPVL_ERROR_VALUE_OVERFLOW);
				WARNING (PPVL_ERROR_VALUE_OVERFLOW);
				}
			The_Value->type = PPVL_TYPE_INTEGER;
			The_Value->base = 10;
			}
		else if (*character == NUMBER_BASE_DELIMITER)
			{
			/*..................................................................
				Probable base notation value ([sign]Base#Radix#).
			*/
			base = The_Value->data.integer;
			word = ++character;
			The_Value->data.integer = strtoul
				(word, &character, Absolute_Value (base));
			if (*character == NUMBER_BASE_DELIMITER &&
				++character == The_Delimiter)
				{
				if (errno)
					{
					if (PPVL_strict)
						ERROR (PPVL_ERROR_VALUE_OVERFLOW);
					WARNING (PPVL_ERROR_VALUE_OVERFLOW);
					}

				The_Value->type = PPVL_TYPE_INTEGER;
				The_Value->base = base;
				break;
				}
			}
		else
			{
			/*..................................................................
				Try for a real number.
			*/
			The_Value->data.real = strtod (The_String, &character);
			if (character == The_Delimiter)
				{
				if (errno)
					{
					if (PPVL_strict)
						ERROR (PPVL_ERROR_VALUE_OVERFLOW);
					WARNING (PPVL_ERROR_VALUE_OVERFLOW);
					}
				The_Value->type = PPVL_TYPE_REAL;
				}
			else
				{
				/*..............................................................
					Couldn't make a number. It's a string.
				*/
				if (! (The_Value->data.string = strndup
						(The_String, The_Delimiter - The_String)))
					ERROR (PPVL_ERROR_NO_MEMORY);
				/*	Check for possible date and time string.
					>>> WARNING <<< This is a cursory check,
					it's not determinate.
				*/
				if (*(character = Skip_Until
						(The_Value->data.string, DATE_TIME_DELIMITERS)))
					The_Value->type = PPVL_TYPE_DATE_TIME;
				else
					The_Value->type = PPVL_TYPE_IDENTIFIER;
				/*	Check for reserved characters.
				*/
				if (character = PPVL_comb_symbol (The_Value->data.string))
					{
					if (PPVL_strict ||
						! isprint (*character))
						{
						/*	Indicate where the reserved character is located
							in the The_String.
						*/
						The_Delimiter = The_String + (character - The_Value->data.string);
						ERROR (PPVL_ERROR_RESERVED_CHARACTER);
						}
					else
						WARNING (PPVL_ERROR_RESERVED_CHARACTER);
					}
				}
			}
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (PPVL_SUCCESS);


Error_condition:

if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (PPVL_errno);
}

/******************************************************************************/

char *
PPVL_scan_comments
	(
	char			*The_String,
	char			**Next_Character
	)
{
char
	*The_Delimiter,
	*comments = NULL,
	*new_comments = NULL,
	*character,
	*next_character;
int
	comment_start_length,
	comment_end_length;


PPVL_errno = PPVL_SUCCESS;

The_Delimiter = The_String;

if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! *The_String)

	/*	No string, no comments, no error.
	*/
	goto Done;


comment_start_length = strlen (COMMENT_START_DELIMITERS);
comment_end_length   = strlen (COMMENT_END_DELIMITERS);

while (TRUE)
	{
	/*	Accumulate all sequential comments.
	*/
	/*	Find the beginning of the comment string.
	*/
	The_Delimiter = Skip_Over (The_Delimiter, WHITE_SPACE);
	if (strncmp (The_Delimiter, COMMENT_START_DELIMITERS,
			comment_start_length))
		goto Done;
	The_String = The_Delimiter + comment_start_length;


	/*	Find the end of the comment string.
	*/
	if (! (The_Delimiter = strstr (The_String, COMMENT_END_DELIMITERS)))
		{
		/*	No comment end.
		*/
		if (PPVL_strict)
			{
			The_Delimiter = The_String + strlen (The_String);
			ERROR (PPVL_ERROR_MISSING_COMMENT_END);
			}
		/*
			Assume it ends at the end of the line.
		*/
		if (! *(The_Delimiter = Skip_Until (The_String, LINE_DELIMITERS)))

			/*	The end of string is the Next_Character.
			*/
			ERROR (PPVL_ERROR_MISSING_COMMENT_END);
		}

	/*	Make a copy of the comment string.
	*/
	if (! (new_comments = strndup (The_String, The_Delimiter - The_String)))
		{
		The_Delimiter += comment_end_length;
		ERROR (PPVL_ERROR_NO_MEMORY);
		}
	The_Delimiter += comment_end_length;


	/*	Compress LINE_DELIMITERS to a single new-line character.
	*/
	for (character = Skip_Until (new_comments, LINE_DELIMITERS);
		*character;
	 	character = Skip_Until (character, LINE_DELIMITERS))
		{
		*character++ = '\n';
		if ((next_character = Skip_Over (character, LINE_DELIMITERS))
			!= character)
			strcpy (character, next_character);	/* slip back */
		}

	if (comments)
		{
		/*	Append the new_comments to the comments.
		*/
		if (! (character = (char *)realloc (comments,
				strlen (comments) + strlen (new_comments) + 2)))
			{
			free (new_comments);
			ERROR (PPVL_ERROR_NO_MEMORY);
			}

		comments = character;
		character += strlen (comments);
		*character++ = '\n';
		strcpy (character, new_comments);
		free (new_comments);
		}
	else
		comments = new_comments;
	}

Done:

/*	Report the location of the Next_Character.
*/
if (Next_Character)
	*Next_Character = The_Delimiter;

return (comments);


Error_condition:

if (comments)
	free (comments);

if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (NULL);
}

/******************************************************************************/

char *
PPVL_scan_quoted_string
	(
	char			*The_String,
	char			**Next_Character
	)
{
char
	*quote = NULL,
	*quote_end,
	*next_sequence,
	*verbatim_delimiter,
	*The_Delimiter;
int
	verbatim_delimiters_length;
	

PPVL_errno = PPVL_SUCCESS;

if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! *The_String)

	/*	No string, no quote, no error.
	*/
	ERROR (0);

The_String = Skip_Over (The_String, WHITE_SPACE);

/*
	Find the matching closing quote.

	>>> WARNING <<< The first character of The_String is taken as the
	quotation mark; any character will do.
*/
quote = The_String++;
for (The_Delimiter = strchr (The_String, *quote);
	 The_Delimiter;
	 The_Delimiter = strchr (The_Delimiter, *quote))
	{
	/*	Allow for escaped quotation mark.
	*/
	if (*(The_Delimiter - 1) == '\\')
		The_Delimiter++;
	else
		break;
	}
if (! The_Delimiter)
	{
	/*	No end quote. The Next_Character is the end of The_String.
	*/
	The_String += strlen (The_String);
	ERROR (PPVL_ERROR_MISSING_QUOTE_END);
	}

/*
	Make a duplicate of the quoted string.
*/
if (! (quote = strndup (The_String, The_Delimiter - The_String)))
	{
	The_String = The_Delimiter + 1;
	ERROR (PPVL_ERROR_NO_MEMORY);
	}
quote_end = quote + strlen (quote);

/*------------------------------------------------------------------------------
	Tell the user where the next character after
	the end of the quoted string is located.
*/
if (Next_Character)
	*Next_Character = The_Delimiter + 1;


if (! PPVL_verbatim_strings)
	{
	/*--------------------------------------------------------------------------
		Compress out line wrap effects:
	*/
	/*	Find any verbatim marks and break the string there.
	*/
	for (The_Delimiter = strstr (quote, VERBATIM_STRING_DELIMITERS);
		 The_Delimiter;
		 The_Delimiter = strstr (The_Delimiter, VERBATIM_STRING_DELIMITERS))
		{
		for (verbatim_delimiter = VERBATIM_STRING_DELIMITERS;
			*verbatim_delimiter;
			 verbatim_delimiter++)
			*The_Delimiter++ = END_OF_STRING;
		}

	The_String = The_Delimiter = quote;
	next_sequence = quote + strlen (quote);
	verbatim_delimiters_length = strlen (VERBATIM_STRING_DELIMITERS);

	while (TRUE)
		{
		for (The_Delimiter = Skip_Until (The_Delimiter, LINE_DELIMITERS);
			*The_Delimiter;
		 	 The_Delimiter = Skip_Until (The_Delimiter, LINE_DELIMITERS))
			{
			/*	Backup over any trailing white space.
			*/
			if (The_Delimiter = skip_back_over (The_String, The_Delimiter, WHITE_SPACE))
				{
				if (*The_Delimiter == STRING_CONTINUATION_DELIMITER)

					/*	Remove the string continuation character.
					*/
					*The_Delimiter = ' ';

				else
					{
					/*	Insert a single space.
					*/
					*(++The_Delimiter) = ' ';
					The_Delimiter++;
					}
				}
			else
				/*	The_String is an empty line. Skip it.
				*/
				The_Delimiter = The_String;

			/*	Note that skipping over white space here will
				also skip over any redundant line breaks.
			*/
			if (! *(The_String = Skip_Over (The_Delimiter, WHITE_SPACE)))
				{
				/*	Only white space left.
				*/
				*The_Delimiter = END_OF_STRING;
				break;
				}

			/*	Concatenate the remainder of the string after the
				white space with the first part of the string.
			*/
			strcpy (The_Delimiter, The_String);
			The_String = The_Delimiter;
			}

		/*	Concatenate the next two sequences (verbatim, compress).

			Note that The_Delimiter now points at the end of the
			quote string collected so far.
		*/
		if (next_sequence != quote_end &&
			(next_sequence += verbatim_delimiters_length) != quote_end)
			{
			/*	The next sequence is a verbatim sequence.
				Restore the delimiters and copy over the sequence.
			*/
			The_String = next_sequence;
			next_sequence += strlen (next_sequence);	/* stay ahead */

			for (verbatim_delimiter = VERBATIM_STRING_DELIMITERS;
				*verbatim_delimiter;
				 verbatim_delimiter++)
				*The_Delimiter++ = *verbatim_delimiter;

			if (The_Delimiter != The_String)
				strcpy (The_Delimiter, The_String);

			/*	Go to the next sequence.
			*/
			if (next_sequence != quote_end &&
				(next_sequence += verbatim_delimiters_length) != quote_end)
				{
				/*	The next sequence is a compress sequence.
					Restore the delimiters and copy over the sequence.
				*/
				The_String = next_sequence;
				next_sequence += strlen (next_sequence);	/* stay ahead */

				The_Delimiter += strlen (The_Delimiter);
				for (verbatim_delimiter = VERBATIM_STRING_DELIMITERS;
					*verbatim_delimiter;
					 verbatim_delimiter++)
					*The_Delimiter++ = *verbatim_delimiter;

				if (The_Delimiter != The_String)
					strcpy (The_Delimiter, The_String);

				The_String = The_Delimiter;
				continue;
				}
			}
		break;
		}
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
return (quote);


Error_condition:

/*	N.B.: The quote is not freed here because no error condition should
	occur after it has been allocate.
*/

if (Next_Character && The_String)
	*Next_Character = The_String;

return (NULL);
}

/******************************************************************************/

char *
PPVL_scan_units
	(
	char			*The_String,
	char			**Next_Character
	)
{
char
	*The_Delimiter,
	*character,
	*units = NULL;


PPVL_errno = PPVL_SUCCESS;
The_Delimiter = The_String;

if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

/*------------------------------------------------------------------------------
	Find the beginning of the units string.
*/
if (! (The_String = PPVL_skip_white_space_and_comments (The_String)))
	ERROR (0);
The_Delimiter = The_String;

if (*The_String != UNITS_START_DELIMITER)
	/*
		Note: While this is really just a warning, no string is
		going to be returned anyway. Since PPVL_ERROR_ILLEGAL_SYNTAX
		only occurs here when there is no units string, leave it
		to the user to catch this specific case.
	*/
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

/*------------------------------------------------------------------------------
	Find the end of the units string.
*/
if (! (character = strchr (++The_String, UNITS_END_DELIMITER)))
	{
	if (PPVL_strict)
		ERROR (PPVL_ERROR_MISSING_UNITS_END);

	/*	Lacking a formal units string end marker
		just find the next non-white_space parameter value delimiter.
	*/
	for ( character = The_String;
		*(character = Skip_Until (character, PARAMETER_VALUE_DELIMITERS));
		  character = Skip_Over  (character, WHITE_SPACE))
		if (character != Skip_Until (character, WHITE_SPACE))
			break;
	character--;
	WARNING (PPVL_ERROR_MISSING_UNITS_END);
	}

/*	Tell the user where the next character after
	the end of the units string is located.
*/
if (Next_Character)
	*Next_Character = character + 1;

/*	Ignore leading white space and comments in the units string.
*/
if (! (The_String = PPVL_skip_white_space_and_comments (The_String)))
	ERROR (0);

/*	Make a copy of the units string.
*/
if (! (units = strndup (The_String, character - The_String)))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*------------------------------------------------------------------------------
	Collapse each white space and comments sequence,
	except the first (already skipped) and last, to a single space.
*/
for (The_String = character = units;
	*(The_Delimiter = Skip_Until (character, WHITE_SPACE));
	The_String = The_Delimiter)
	{
	/*	Shift the remainder of the raw units string
		to the end of the collapsed units string.
	*/
	if (character != The_String)
		strcpy (The_String, character);

	/*	Find the next word.
	*/
	if (! (character = PPVL_skip_white_space_and_comments (The_Delimiter)))
		ERROR (0);
	if (! *character)
		{
		/*	No more words. Mark the end of the new units string.
		*/
		*The_Delimiter = END_OF_STRING;
		break;
		}
	*The_Delimiter++ = ' ';		/* use a single space delimiter */
	}

/*------------------------------------------------------------------------------
	Return the results:
*/
return (units);


Error_condition:

if (units)
	free (units);
if (Next_Character && The_Delimiter)
	*Next_Character = The_Delimiter;

return (NULL);
}

/******************************************************************************/

char *
PPVL_skip_white_space_and_comments
	(
	char			*The_String
	)
{
char
	*next_character;
int
	comment_start_length,
	comment_end_length;


if (! The_String)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

comment_start_length = strlen (COMMENT_START_DELIMITERS);
comment_end_length   = strlen (COMMENT_END_DELIMITERS);

while (TRUE)
	{
	The_String = Skip_Over (The_String, WHITE_SPACE);

	if (*The_String == STATEMENT_CONTINUATION_DELIMITER)
		The_String++;	/* it's superfluous */

	else if (! strncmp (The_String, COMMENT_START_DELIMITERS,
						comment_start_length))
		{
		/*	Skip the comment.
		*/
		if (next_character = strstr (The_String, COMMENT_END_DELIMITERS))
			The_String = next_character + comment_end_length;

		else
			{
			if (PPVL_strict)
				ERROR (PPVL_ERROR_MISSING_COMMENT_END);

			/*	Assume the comment ends at the next line break.
			*/
			The_String = Skip_Until (The_String, LINE_DELIMITERS);
			WARNING (PPVL_ERROR_MISSING_COMMENT_END);
			}
		}
	else
		{
		return (The_String);
		}
	}

Error_condition:

return (NULL);
}

/******************************************************************************/

char *
PPVL_comb_symbol
	(
	char			*The_String
	)
{
char
	*character;


/*	Check for a reserved character.
*/
if (*(character = Skip_Until (The_String, RESERVED_CHARACTERS)))
	return (character);

/*	Check for a non-printable (illegal) character.
*/
for (character = The_String;
	*character;
	 character++)
	if (! isprint (*character))
		return (character);

return (NULL);
}

/******************************************************************************/

PPVL_Parameter *
PPVL_new_parameter
	(
	char			*name,
	PPVL_Class		classification,
	PPVL_Object		*content,
	char			*comments,
	void			*user_data
	)
{
PPVL_Parameter
	*The_Parameter = NULL;
Name_Code
	*special_name;


PPVL_errno = PPVL_SUCCESS;

if (! (The_Parameter = New_Parameter))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*------------------------------------------------------------------------------
	Confirm classification-content compatibility.
*/
if (! (The_Parameter->classification = classification))
	{
	/*	Use the name or content, if any, to determine the classification.
	*/
	if (name)
		{
		The_Parameter->classification = PPVL_CLASS_TOKEN;	/* provisional */

		/*	Check for special names for special classifications.
		*/
		for (special_name = special_name_classification;
			 special_name->name;
			 special_name++
			)
			{
			if (strcasecmp (special_name->name, name) == 0)
				{
				The_Parameter->classification = special_name->classification;
				break;
				}
			}
		}
	else if (content)
		{
		if (PPVL_Is_Parameter (content))
			The_Parameter->classification = PPVL_CLASS_GROUP;
		else if (PPVL_Is_Value (content))
			The_Parameter->classification = PPVL_CLASS_ASSIGNMENT;
		}
	else
		{
		/*	No classification, name, or content. Must be empty.
		*/
		if (comments || user_data)
			WARNING (PPVL_ERROR_BAD_ARGUMENT);
		return (The_Parameter);
		}
	}
else if (! PPVL_Is_Parameter (The_Parameter))
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

/*	Make duplicates of any name or comments.
*/
if (name &&
	! (The_Parameter->name = strdup (name)))
	ERROR (PPVL_ERROR_NO_MEMORY);
if (comments &&
	! (The_Parameter->comments = strdup (comments)))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*	The classification has been established.
*/
if (content)
	{
	/*	>>> WARNING <<< The content is NOT DUPLICATED.
	*/
	if (PPVL_Is_Value (content))
		{
		if (! PPVL_Class_Is_Assignment (The_Parameter->classification))
			ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

		/*	Assign the value content to the parameter.

			Careful: Make sure no ERROR condition can occur
			after this or the content will be freed.
		*/
		The_Parameter->content.value = (PPVL_Value *)content;
		}
	else	/* PPVL_Is_Parameter */
		{
		if (! PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification))
			ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

		/*	Provide a parameter list for the parameter content.

			Careful: Make sure no ERROR condition can occur
			after this or the content will be freed.
		*/
		if (! (The_Parameter->content.parameters = (PPVL_Parameter **)calloc
			(2, sizeof (PPVL_Parameter *))))
			ERROR (PPVL_ERROR_NO_MEMORY);

		*The_Parameter->content.parameters = (PPVL_Parameter *)content;
		}
	}

/*	>>> WARNING <<< The user_data is NOT DUPLICATED.
*/
The_Parameter->user_data = user_data;

return (The_Parameter);


Error_condition:

PPVL_free_parameter (The_Parameter);

return (NULL);
}

/******************************************************************************/

PPVL_Value *
PPVL_new_value
	(
	PPVL_Type		type,
	PPVL_Object		*data,
	int				base,
	char			*units
	)
{
PPVL_Value
	*The_Value;


PPVL_errno = PPVL_SUCCESS;

if (! (The_Value = New_Value))
	ERROR (PPVL_ERROR_NO_MEMORY);

if (! (The_Value->type = type))
	{
	if (data || base || units)
		WARNING (PPVL_ERROR_BAD_ARGUMENT);
	return (The_Value);
	}
else if (! PPVL_Is_Value (The_Value))
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (units &&
	! (The_Value->units = strdup (units)))
	ERROR (PPVL_ERROR_NO_MEMORY);

if (data)
	{
	if (PPVL_Type_Is_Integer (type))
		{
		The_Value->data.integer = *(unsigned long *)data;

		if (base)
			{
			if (Absolute_Value (base) > 1 &&
				Absolute_Value (base) < 37)
				The_Value->base = base;
			else
				ERROR (PPVL_ERROR_BAD_ARGUMENT);
			}
		else
			The_Value->base = 10;
		}

	else if (PPVL_Type_Is_Real (type))
			The_Value->data.real = *(double *)data;

	else if (PPVL_Type_Is_String (type))
		{
		/*	>>> CAUTION <<< The data for a string value
			is the string pointer (char *), NOT the address
			of the string pointer (char **).

			NOTE: The string is duplicated.
		*/
		if (! (The_Value->data.string = strdup ((char *)data)))
			ERROR (PPVL_ERROR_NO_MEMORY);
		}

	else if (PPVL_Type_Is_Array (type))
		{
		if (data)
			{
			if (! PPVL_Is_Value (data))
				ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

			/*	Provide a value list.
			*/
			if (! (The_Value->data.array = (PPVL_Value **)calloc
					(2, sizeof (PPVL_Value *))))
				ERROR (PPVL_ERROR_NO_MEMORY);

			/*	>>> CAUTION <<< The data for an array value
				is The_Value (PPVL_Value *), NOT the address
				of The_Value (PPVL_Value **).

				>>> WARNING <<< The value is NOT DUPLICATED.
			*/
			/*	Careful: Make sure no ERROR condition can occur
				after this or the data will be freed.
			*/
			*(The_Value->data.array) = (PPVL_Value *)data;
			}
		}
	}

return (The_Value);


Error_condition:

PPVL_free_value (The_Value);

return (NULL);
}

/******************************************************************************/

PPVL_Parameter *
PPVL_add_parameter
	(
	PPVL_Parameter	*The_Aggregate,
	PPVL_Parameter	*The_Parameter
	)
{
PPVL_Parameter
	**parameters;
int
	number_of_parameters;


if (! The_Parameter)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! PPVL_Is_Parameter (The_Parameter))
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

if ((number_of_parameters = PPVL_count_parameters (The_Aggregate)) > 0)
	{
	/*	Enlarge the existing parameter array list.
	*/
	if (! (parameters = (PPVL_Parameter **)realloc (The_Aggregate->content.parameters,
			(number_of_parameters + 2) * sizeof (PPVL_Parameter *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	}
else if (PPVL_errno == PPVL_ERROR_EMPTY_STATEMENT)
	{
	/*	Create a new parameter array list.
	*/
	if (! (parameters = (PPVL_Parameter **)malloc (2 * sizeof (PPVL_Parameter *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	PPVL_errno = PPVL_ERROR_NO_ERROR;
	}
else
	{
	ERROR (0);
	}


/*	Update the parameter list address.
*/
The_Aggregate->content.parameters = parameters;
	
/*	Add the specified parameter to the parameter list.
*/
parameters += number_of_parameters;	/* end of list */
*parameters++ = The_Parameter;
*parameters = NULL;

return (The_Aggregate);


Error_condition:

return (NULL);
}

/******************************************************************************/

PPVL_Value *
PPVL_add_value
	(
	PPVL_Value		*The_Array,
	PPVL_Value		*The_Value
	)
{
PPVL_Value
	**values;
int
	number_of_values;


if (! The_Value)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! PPVL_Is_Value (The_Value))
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

if ((number_of_values = PPVL_count_values (The_Array)) > 0)
	{
	if (PPVL_errno)
		ERROR (0);

	/*	Enlarge the existing value array list.
	*/
	if (! (values = (PPVL_Value **)realloc (The_Array->data.array,
			(number_of_values + 2) * sizeof (PPVL_Value *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	}
else if (PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT)
	{
	/*	Create a new value array list.
	*/
	if (! (values = (PPVL_Value **)malloc (2 * sizeof (PPVL_Value *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	PPVL_errno = PPVL_ERROR_NO_ERROR;
	}
else
	{
	ERROR (0);
	}


/*	Update the value array address.
*/
The_Array->data.array = values;

/*	Add the specified value to the value list.
*/
values += number_of_values;	/* end of list */
*values++ = The_Value;
*values = NULL;

return (The_Array);


Error_condition:

return (NULL);
}

/******************************************************************************/

PPVL_Parameter *
PPVL_duplicate_parameter
	(
	PPVL_Parameter	*The_Old_Parameter
	)
{
PPVL_Parameter
	*The_New_Parameter = NULL,
	**old_parameters,
	**new_parameters;
int
	number_of_parameters;

/*	Start with an empty parameter.
*/
if (! (The_New_Parameter = New_Parameter))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*	Copy the classification.
*/
The_New_Parameter->classification = The_Old_Parameter->classification;

/*	Duplicate the name.
*/
if (The_Old_Parameter->name &&
	! (The_New_Parameter->name = strdup (The_Old_Parameter->name)))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*	Duplicate any comments.
*/
if (The_Old_Parameter->comments &&
	! (The_New_Parameter->comments = strdup (The_Old_Parameter->comments)))
	ERROR (PPVL_ERROR_NO_MEMORY);

if ((number_of_parameters = PPVL_count_parameters (The_Old_Parameter)) > 0)
	{
	/*	Duplicate the parameter list.
	*/
	if (! (new_parameters = (PPVL_Parameter **)calloc
			(number_of_parameters + 1, sizeof (PPVL_Parameter *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	The_New_Parameter->content.parameters = new_parameters;

	for (old_parameters = The_Old_Parameter->content.parameters;
		*old_parameters;
		 old_parameters++, new_parameters++)
		if (! (*new_parameters = PPVL_duplicate_parameter
				(
				*old_parameters
				)))
			ERROR (0);
	}
else
	{
	/*	Duplicate the parameter value.
	*/
	PPVL_errno = PPVL_SUCCESS;
	if (The_Old_Parameter->content.value &&
	   ! (The_New_Parameter->content.value = PPVL_duplicate_value
			(
			The_Old_Parameter->content.value
			)))
		ERROR (0);
	}

/*	>>> WARNING <<< the user_data is not duplicated.
*/
The_New_Parameter->user_data = The_Old_Parameter->user_data;

return (The_New_Parameter);


Error_condition:

PPVL_free_parameter (The_New_Parameter);

return (NULL);
}

/******************************************************************************/

PPVL_Value *
PPVL_duplicate_value
	(
	PPVL_Value		*The_Old_Value
	)
{
PPVL_Value
	*The_New_Value = NULL,
	**old_values,
	**new_values;
int
	number_of_values;

/*	Start with an empty value.
*/
if (! (The_New_Value = New_Value))
	ERROR (PPVL_ERROR_NO_MEMORY);

/*	Copy the type and base.
*/
The_New_Value->type = The_Old_Value->type;
The_New_Value->base = The_Old_Value->base;

/*	Duplicate any units.
*/
if (The_Old_Value->units &&
	! (The_New_Value->units = strdup (The_Old_Value->units)))
	ERROR (PPVL_ERROR_NO_MEMORY);

if ((number_of_values = PPVL_count_values (The_Old_Value)) > 0 &&
	PPVL_Type_Is_Array (The_Old_Value->type))
	{
	/*	Duplicate the value list.
	*/
	if (! (new_values = (PPVL_Value **)calloc
			(number_of_values + 1, sizeof (PPVL_Value *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	The_New_Value->data.array = new_values;

	for (old_values = The_Old_Value->data.array;
		*old_values;
		 old_values++, new_values++)
		if (! (*new_values = PPVL_duplicate_value
				(
				*old_values
				)))
			ERROR (0);
	}
else
	{
	/*	Duplicate the data.
	*/
	PPVL_errno = PPVL_SUCCESS;
	if (PPVL_Type_Is_String (The_Old_Value->type))
		{
		if (The_Old_Value->data.string &&
			! (The_New_Value->data.string = strdup (The_Old_Value->data.string)))
			ERROR (PPVL_ERROR_NO_MEMORY);
		}
	else
		/*	Duplicate the numerical value.
		*/
		The_New_Value->data.real = The_Old_Value->data.real;
	}

return (The_New_Value);


Error_condition:

PPVL_free_value (The_New_Value);

return (NULL);
}

/******************************************************************************/

PPVL_Parameter *
PPVL_remove_parameter
	(
	PPVL_Parameter	*The_Aggregate,
	PPVL_Parameter	*Remove_Parameter
	)
{
PPVL_Parameter
	**parameters;

PPVL_errno = PPVL_SUCCESS;

if (! The_Aggregate ||
	! Remove_Parameter)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! PPVL_Class_Is_Begin_Aggregate (The_Aggregate->classification))
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

if (! The_Aggregate->content.parameters)
	ERROR (PPVL_ERROR_EMPTY_STATEMENT);

/*
	Note: The alogorithm of this function searches depth-wise;
	i.e. each aggregate encountered in the parameter list is
	searched when it is encountered.
*/
for (parameters = The_Aggregate->content.parameters;
	*parameters;
	 parameters++)
	{
	/*	Find the Remove_Parameter:
	*/
	if (*parameters == Remove_Parameter)
		{
		/*	Free this parameter
			and compress it's pointer out of the parameter list.
		*/
		PPVL_free_parameter (*parameters);

		/*	Copy the remainder of the list up over the
			the parameter (pointer) that was removed.
		*/
		do
			*parameters = *(parameters + 1);
			while (*(++parameters));

		/*	Reduce the size of the parameter list.
		*/
		if (reduce_list ((PPVL_Object ***)&The_Aggregate->content.parameters)
			!= PPVL_SUCCESS)
			ERROR (0);

		break;
		}

	if (PPVL_Class_Is_Begin_Aggregate ((*parameters)->classification))
		{
		if (PPVL_remove_parameter
				(
				*parameters,
				Remove_Parameter
				))
			break;

		if (PPVL_errno != PPVL_ERROR_EMPTY_STATEMENT)
			ERROR (0);	/* something went wrong */
		}
	}
if (*parameters)
	{
	PPVL_errno = PPVL_SUCCESS;
	return (The_Aggregate);
	}

/*	Didn't find the Remove_Parameter.
*/
PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT;


Error_condition:

return (NULL);
}

/******************************************************************************/

PPVL_Value *
PPVL_remove_value
	(
	PPVL_Value		*The_Array,
	PPVL_Value		*Remove_Value
	)
{
PPVL_Value
	**values;


if (! The_Array ||
	! Remove_Value)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! PPVL_Type_Is_Array (The_Array->type))
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

/*
	Note: The alogorithm of this function searches depth-wise;
	i.e. each aggregate encountered in the parameter list is
	searched when it is encountered.
*/
for (values = The_Array->data.array;
	*values;
	 values++)
	{
	/*	Find the Remove_Value:
	*/
	if (*values == Remove_Value)
		{
		/*	Free this value
			and compress it's pointer out of the value list.
		*/
		PPVL_free_value (*values);

		do
			*values = *(values + 1);
			while (*(++values));

		/*	Reduce the size of the value list.
		*/
		if (reduce_list ((PPVL_Object ***)&The_Array->data.array)
			!= PPVL_SUCCESS)
			ERROR (0);

		break;
		}

	if (PPVL_Type_Is_Array ((*values)->type))
		{
		if (PPVL_remove_value
				(
				*values,
				Remove_Value
				))
			break;

		if (PPVL_errno != PPVL_ERROR_EMPTY_STATEMENT)
			ERROR (0);	/* something went wrong */
		}
	}
if (*values)
	{
	PPVL_errno = PPVL_SUCCESS;
	return (The_Array);
	}

/*	Didn't find the Remove_Value.
*/
PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT;


Error_condition:

return (NULL);
}

/******************************************************************************/

void
PPVL_free_parameter
	(
	PPVL_Parameter	*The_Parameter
	)
{
PPVL_Parameter
	**parameters;


if (The_Parameter)
	{
	if (The_Parameter->name)
		{
		free (The_Parameter->name);
		}
	if (The_Parameter->content.value)
		{
		if (PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification))
			{
			/*	Free all the parameters in the parameter list, and the list.
			*/
			for (parameters = The_Parameter->content.parameters;
				*parameters;
				 parameters++)
				PPVL_free_parameter (*parameters);
			free (The_Parameter->content.parameters);
			}
		else
			{
			/*	Free all value arrays associated with the value.
			*/
			PPVL_free_value (The_Parameter->content.value);
			}
		}
	if (The_Parameter->comments)
		{
		free (The_Parameter->comments);
		}
	free (The_Parameter);
	}
return;
}

/******************************************************************************/

void
PPVL_free_value
	(
	PPVL_Value		*The_Value
	)
{
PPVL_Value
	**value;


if (The_Value)
	{
	if (PPVL_Type_Is_Array (The_Value->type) &&
		The_Value->data.array)
		{
		/*	Recursively free each PPVL_Value in the array
			and then the array itself.
		*/
		for (value = The_Value->data.array;
			*value;
			 value++)
			{
			PPVL_free_value (*value);
			}
		free (The_Value->data.array);
		}
	else if (PPVL_Type_Is_String (The_Value->type) &&
			 The_Value->data.string)
		{
		free (The_Value->data.string);
		}

	if (The_Value->units)
		{
		free (The_Value->units);
		}
	free (The_Value);
	}
return;
}

/******************************************************************************/

PPVL_Parameter *
PPVL_find_parameter
	(
	PPVL_Parameter	*The_Aggregate,
	PPVL_Parameter	*Last_Parameter,
	PPVL_Select		Select,
	PPVL_Object		*The_Selection,
	PPVL_Parameter	**The_Parent
	)
{
PPVL_Parameter
	*The_Parameter,
	**parameters;
char
	*pathname,
	*path = NULL,
	*name = NULL;
int
	find_anywhere = TRUE;

#define OFF_THE_PATH	-1


/*	>>> WARNING <<<
	The_Aggregate is NOT checked for a match with The_Selection,
	only the parameters in The_Aggregate parameter list are checked.
*/

PPVL_errno = PPVL_SUCCESS;

if (! The_Aggregate ||
	Select < PPVL_SELECT_ANY ||
	Select > PPVL_SELECT_USER_DATA)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! The_Selection &&
	(Select == PPVL_SELECT_NAME ||
	 Select == PPVL_SELECT_PARAMETER))
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! PPVL_Class_Is_Begin_Aggregate (The_Aggregate->classification))
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

if (! (parameters = The_Aggregate->content.parameters))
	ERROR (PPVL_ERROR_EMPTY_STATEMENT);

if (Select == PPVL_SELECT_NAME)
	{
	/*	Parameter path name.
	*/
	if (The_Selection == (PPVL_Object *)OFF_THE_PATH)
		{
		if (! Last_Parameter)
			ERROR (PPVL_ERROR_BAD_ARGUMENT);

		/*	Just looking for the Last_Parameter.
		*/
		path = (char *)The_Selection;
		}
	else
		{
		/*	Pathname tracking.
		*/
		if (*(name = (char *)The_Selection)
			== *PPVL_parameter_path_delimiter)
			{
			name++;
			find_anywhere = FALSE;
			}

		/*	Separate the name to find in the parameter list of The_Aggregate
			from the remainder of the path, if any, to be passed on to any
			aggregate parameters encountered in the parameter list.
		*/
		path = Skip_Until (name, PPVL_parameter_path_delimiter);
		if (! (name = strndup (name, path - name)))
			ERROR (PPVL_ERROR_NO_MEMORY);
		}
	}


/*--------------------------------------------------------------------------
	Find The_Selection.

	Note: The algorithm of this function searches depth-wise;
	i.e. each aggregate encountered in the parameter list is
	searched (conditionally) when it is encountered.
*/
for (;
	 The_Parameter = *parameters;
	 parameters++)
	{
	if (Last_Parameter)
		{
		/*......................................................................
			Check for the Last_Parameter.
		*/
		if (The_Parameter == Last_Parameter)
			{
			Last_Parameter = NULL;	/* mark as found */

			if (path == (char *)OFF_THE_PATH)
				/*	Can't find The_Selection from here.
				*/
				ERROR (0);

			if (PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification))
				{
				/*	This aggregate may need to be searched.
				*/
				if ((pathname = path) &&
					*pathname &&
					The_Parameter->name &&
					! strcasecmp (The_Parameter->name, name))

					/*	Still on the path.
					*/
					goto Search_aggregate_for_pathname;
				else
					goto Search_aggregate_for_The_Selection;
				}
			else
				continue;
			}
		}
	else
		{
		/*......................................................................
			Check for The_Selection.

			Note that The_Selection comparison only occurs after
			the Last_Parameter has been found.
		*/
		if (Select == PPVL_SELECT_ANY)
			break;

		if (Select == PPVL_SELECT_NAME)
			{
			if (The_Parameter->name &&
				! strcasecmp (The_Parameter->name, name))
				{
				if (*path)
					{
					if (PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification))

						/*	Continue the search for the rest of the pathname.

							Note: If the parameter just found is an aggregate
							continue the search for the path in it, otherwise
							it can't be the right parameter since the existence
							of more path by definition means that the current
							parameter name must be for an aggregate. If the
							search in the aggregate fails, then it's the wrong
							aggregate, so keep searching.
						*/
						goto Search_aggregate;
					else
						continue;
					}
				else
					break;
				}
			}
		else if (Select == PPVL_SELECT_CLASS)
			{
			/*	>>> CAUTION <<< The_Seletion for a classification is the
				classification code itself, NOT a pointer to the code.

				Note: Because end classification parameters can not
				(shouldn't!) occur in aggregate parameter lists, no check is
				made for this special case.
			*/
			if (PPVL_Class_Is (The_Parameter->classification, (PPVL_Type)The_Selection))
				break;
			}
		else if (Select == PPVL_SELECT_PARAMETER)
			{
			if (The_Parameter == (PPVL_Parameter *)The_Selection)
				break;
			}
		else if (Select == PPVL_SELECT_USER_DATA)
			{
			if (The_Parameter->user_data == The_Selection)
				break;
			}
		}

	if (PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification))
		{
		/*----------------------------------------------------------------------
			Aggregate search.

			The_Parameter is an aggregate and a decision needs to be
			made on whether to extend the search into it's parameter
			list and, if so, how to conduct the search there.

			The inclusion of pathname tracking when searching for
			a parameter name complicates the logic, which would ordinarily
			be to always extend the search into aggregates. Only pathname
			tracking must be evaluated specially.
		*/
		Search_aggregate:
		if (pathname = path)
			{
			/*	Pathname tracking.
				(Select == PPVL_SELECT_NAME)
			*/
			if (pathname != (char *)OFF_THE_PATH)
				{
				/*	The_Aggregate is on the path.
					Is this aggregate also on the path?
				*/
				if (The_Parameter->name &&
					! strcasecmp (The_Parameter->name, name))
					{
					/*	Still on the path.
						Is there more path?
					*/
					if (! *pathname)
						{
						/*	End of path at this aggregate.
						*/
						pathname = (char *)OFF_THE_PATH;

						/*	Still searching for the Last_Parameter?
						*/
						if (! Last_Parameter)
							break;	/* end of the trail! */
						}
					}
				else
					pathname = (char *)OFF_THE_PATH;
				}

			if (Last_Parameter ||
				pathname != (char *)OFF_THE_PATH)
				{
				/*	Search this aggregate for the pathname.
				
					Note that the search of this aggregate
					doesn't happen if the Last_Parameter has
					already been found and this aggregate is
					off the path.
				*/
				Search_aggregate_for_pathname:
				if (The_Parameter = PPVL_find_parameter
						(
						The_Parameter,
						Last_Parameter,
						Select,
						pathname,
						The_Parent
						))
				goto Done;		/* found the pathname */

				if (! PPVL_errno)
					{
					/*	Found the Last_Parameter,
						but not The_Selection.
					*/
					if (path == (char *)OFF_THE_PATH)
						/*	Nothing more to do here.
						*/
						ERROR (0);

					if (! find_anywhere)
						/*	Mark Last_Parameter as found.
						
							Note: For a find_anywhere name the search
							will be retried using the full pathname
							(The_Selection), so Last_Parameter needs
							to remain in effect.
						*/
						Last_Parameter = NULL;
					}
				else if (PPVL_errno == PPVL_ERROR_EMPTY_STATEMENT)
					{
					/*	No parameters in aggregate
						or couldn't find Last_Parameter.
					*/
					PPVL_errno = PPVL_ERROR_NO_ERROR;

					/*	It's no use retrying in any case since the
						Last_Parameter couldn't be found.
					*/
					continue;
					}
				else
					{
					ERROR (0);	/* something went wrong */
					}
				/*
					Note that the search will be retried with the full
					pathname (The_Selection) if a find_anywhere pathname
					is used.
				*/
				}
			}

		Search_aggregate_for_The_Selection:
		if (find_anywhere)
			{
			/*	Search this aggregate for The_Selection.
			*/
			if (The_Parameter = PPVL_find_parameter
					(
					*parameters,
					Last_Parameter,
					Select,
					The_Selection,
					The_Parent
					))
				goto Done;

			if (! PPVL_errno)
				/*	Found the Last_Parameter,
					but not The_Selection.
				*/
				Last_Parameter = NULL;

			else if (PPVL_errno == PPVL_ERROR_EMPTY_STATEMENT)
				/*	No parameters in aggregate
					or couldn't find Last_Parameter.
				*/
				PPVL_errno = PPVL_ERROR_NO_ERROR;

			else
				{
				ERROR (0);	/* something went wrong */
				}
			}
		}
	}

if (Last_Parameter)
	/*	Didn't find the Last_Parameter.
	*/
	ERROR (PPVL_ERROR_EMPTY_STATEMENT);

if (! The_Parameter)
	/*	Didn't find The_Selection.
	*/
	ERROR (0);

if (The_Parent)
	*The_Parent = The_Aggregate;

Done:

if (name)
	free (name);

return (The_Parameter);


Error_condition:

if (name)
	free (name);

return (NULL);
}

/******************************************************************************/

PPVL_Value *
PPVL_find_value
	(
	PPVL_Value		*The_Array,
	PPVL_Value		*Last_Value,
	PPVL_Type		Type,
	PPVL_Object		*Data,
	PPVL_Value		**The_Parent
	)
{
PPVL_Value
	*The_Value,
	**values;


/*	>>> WARNING <<<
	The_Array is NOT checked for a match with Type,
	only the values in The_Array value list are checked.
*/

PPVL_errno = PPVL_SUCCESS;

if (! The_Array)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! PPVL_Type_Is_Array (The_Array->type))
	ERROR (PPVL_ERROR_ILLEGAL_SYNTAX);

if (! (values = The_Array->data.array))
	ERROR (PPVL_ERROR_EMPTY_STATEMENT);


/*------------------------------------------------------------------------------
	Find Type and Data.

	Note: The algorithm of this function searches depth-wise;
	i.e. each aggregate encountered in the parameter list is
	searched (conditionally) when it is encountered.
*/
for (;
	 The_Value = *values;
	 values++)
	{
	if (Last_Value)
		{
		/*......................................................................
			Check for the Last_Value.
		*/
		if (The_Value == Last_Value)
			{
			Last_Value = NULL;	/* mark as found */
			}
		}
	else
		{
		if (Type == PPVL_TYPE_ANY)
			break;

		if (PPVL_Type_Is (The_Value->type, Type))
			{
			/*..................................................................
				Check for Type.
			*/
			if (Data)
				{
				/*	Match the data, too.
				*/
				if (PPVL_Type_Is_Array (Type))
					{
					/*	Data for an Array is a value pointer (PPVL_Value *),
						NOT a value pointer address (PPVL_Value **).
					*/
					if (The_Value == Data)
						break;
					}
				else if (PPVL_Type_Is_String (Type))
					{
					/*	Data for a string is the string pointer (char *),
						NOT the string pointer address (char **).
					*/
					if (! strcasecmp (The_Value->data.string, (char *)Data))
						break;
					}
				else if (PPVL_Type_Is_Integer (Type))
					{
					if (The_Value->data.integer == *(unsigned long *)Data)
						break;
					}
				else if (PPVL_Type_Is_Real (Type))
					{
					if (The_Value->data.real == *(double *)Data)
						break;
					}
				}
			else
				break;
			}
		}

	if (PPVL_Type_Is_Array (The_Value->type))
		{
		/*----------------------------------------------------------------------
			Array search.
		*/
		if (The_Value = PPVL_find_value
				(
				The_Value,
				Last_Value,
				Type,
				Data,
				The_Parent
				))
			goto Done;	/* found */

		if (! PPVL_errno)
			/*	Found the Last_Value,
				but not Type/Data.
			*/
			Last_Value = NULL;

		else if (PPVL_errno == PPVL_ERROR_EMPTY_STATEMENT)
			/*	No values in this array
				or couldn't find Last_Value.
			*/
			PPVL_errno = PPVL_ERROR_NO_ERROR;

		else
			{
			ERROR (0);	/* something went wrong */
			}
		}
	}

if (Last_Value)
	/*	Didn't find the Last_Value.
	*/
	ERROR (PPVL_ERROR_EMPTY_STATEMENT);

if (! The_Value)
	/*	Didn't find the Type/Data.
	*/
	ERROR (0);

if (The_Parent)
	*The_Parent = The_Array;

Done:

return (The_Value);


Error_condition:

return (NULL);
}

/******************************************************************************/

PPVL_Error_Code
PPVL_write_parameter
	(
	PPVL_Parameter	*The_Parameter,
	FILE			*The_File,
	int				Indent_Level
	)
{
PPVL_Parameter
	**parameters,
	end_aggregate;
Name_Code
	*special_name;
char
	*character;
int
	count,
	file_aggregate = FALSE;


PPVL_errno = PPVL_SUCCESS;

if (! The_Parameter ||
	! The_Parameter->name)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification) &&
	! strcmp (The_Parameter->name, PPVL_CONTAINER_NAME))
	{
	/*	THE_FILE aggregate is a pseudo-parameter acting as a container.
	*/
	file_aggregate = TRUE;
	}

if (! The_File)
	The_File = stdout;	/* the default */

if (The_Parameter->comments)
	{
	/*--------------------------------------------------------------------------
		Parameter comments.
	*/
	character = The_Parameter->comments;
	while (TRUE)
		{
		/*	Indent to the appropriate level.
		*/
		for (count = 0;
			 count < Indent_Level;
			 count++)
			fprintf (The_File, "\t");

		fprintf (The_File, "%s",
			COMMENT_START_DELIMITERS);

		while (*character && *character != '\n')
			fputc (*character++, The_File);

		fprintf (The_File, "%s%s",
			COMMENT_END_DELIMITERS, LINE_BREAK);

		if (*character)
			 character++;
		else
			break;
		}
	}

if (! file_aggregate)
	{
	/*--------------------------------------------------------------------------
		Parameter name.
	*/
	/*	Indent to the appropriate level.
	*/
	for (count = 0;
	 	count < Indent_Level;
	 	count++)
		fprintf (The_File, "\t");

	/*	Check for special names for special classifications.
	*/
	for (special_name = special_name_classification;
	 	special_name->name;
	 	special_name++
		)
		{
		if (The_Parameter->classification == special_name->classification)
			{
			fprintf (The_File, "%s", special_name->name);
			break;
			}
		}

	if (! special_name->name)
		Write_Name (The_Parameter->name);

	/*--------------------------------------------------------------------------
		Parameter value:
	*/
	if (! PPVL_Class_Is_Token (The_Parameter->classification))
		{
		fprintf (The_File, " %c ", PARAMETER_NAME_DELIMITER);

		if (PPVL_Class_Is_Aggregate (The_Parameter->classification))
			{
			/*	The parameter name is the value of aggregates.
	 		*/
			Write_Name (The_Parameter->name);
			}
		else
			{
			if (PPVL_write_value
					(
					The_Parameter->content.value,
					The_File
					)
				!= PPVL_SUCCESS)
				ERROR (0);
			}
		}

	/*--------------------------------------------------------------------------
		PVL statement termination.
	*/
/* Dyer Lytle changed this line to make the output more compatible
 * with ISIS, 11-28-01.
        fprintf (The_File, "%c%s", STATEMENT_END_DELIMITER, LINE_BREAK);
*/
        fprintf (The_File, "%s", LINE_BREAK);

	}


if (PPVL_Class_Is_Begin_Aggregate (The_Parameter->classification))
	{
	/*--------------------------------------------------------------------------
		Recursively write each parameter in the aggregate.
	*/
	if (The_Parameter->content.parameters)
		{
		/*	The value of aggregates is a list of parameters.
		*/
		for (parameters = The_Parameter->content.parameters;
			*parameters;
			 parameters++)
			{
			if (PPVL_write_parameter
					(
					*parameters,
					The_File,
					(Indent_Level >= 0 &&
					! file_aggregate) ?
						Indent_Level + 1 :
						Indent_Level
					)
				!= PPVL_SUCCESS)
				ERROR (0);
			}
		}

	if (file_aggregate)
		{
		/*	Indent to the appropriate level.
		*/
		for (count = 0;
		 	count < Indent_Level;
		 	count++)
			fprintf (The_File, "\t");

		/*	The END.
		*/
		fprintf (The_File, "%s%s", END_NAME, LINE_BREAK);
		}
	else
		{
		/*	Add the end aggregate parameter.
		*/
		end_aggregate.name = The_Parameter->name;
		end_aggregate.classification = The_Parameter->classification | PPVL_END;
		end_aggregate.content.parameters = NULL;
		end_aggregate.comments = NULL;
		if (PPVL_write_parameter
				(
				&end_aggregate,
				The_File,
				Indent_Level
				)
			!= PPVL_SUCCESS)
			ERROR (0);
		}
	}

return (PPVL_SUCCESS);


Error_condition:

fprintf (The_File, "%s%s%s PPVL data error: %s %s%s%s",
	LINE_BREAK, LINE_BREAK,
		COMMENT_START_DELIMITERS,
			PPVL_ERROR_MESSAGE,
		COMMENT_END_DELIMITERS,
	LINE_BREAK, LINE_BREAK);

return (PPVL_errno);
}

/******************************************************************************/

PPVL_Error_Code
PPVL_write_value
	(
	PPVL_Value		*The_Value,
	FILE			*The_File
	)
{
PPVL_Value
	**values;
char
	number_string[256];
long
	number;
int
	base;


PPVL_errno = PPVL_SUCCESS;

if (! The_Value ||
	! The_Value->type)
	ERROR (PPVL_ERROR_BAD_ARGUMENT);

if (! The_File)
	The_File = stdout;	/* the default */

/*------------------------------------------------------------------------------
	Array value:
*/
if (PPVL_Type_Is_Array (The_Value->type))
	{
	if (PPVL_Type_Is_Set (The_Value->type))
		fprintf (The_File, "%c", SET_START_DELIMITER);
	else
		fprintf (The_File, "%c", SEQUENCE_START_DELIMITER);

	if (The_Value->data.array)
		{
		/*	Recursively output each value in the array.
		*/
		for (values = The_Value->data.array;
			*values;
			 values++)
			{
			/* Provide the values delimiter after the first.
			*/
			if (values != The_Value->data.array)
				fprintf (The_File, "%c ", PARAMETER_VALUE_DELIMITER);
			if (PPVL_write_value
					(
					*values,
					The_File
					)
				!= PPVL_SUCCESS)
				ERROR (0);
			}
		}

	if (PPVL_Type_Is_Set (The_Value->type))
		fprintf (The_File, "%c", SET_END_DELIMITER);
	else
		fprintf (The_File, "%c", SEQUENCE_END_DELIMITER);
	}

/*------------------------------------------------------------------------------
	String value:
*/
else if (PPVL_Type_Is_String (The_Value->type))
	{
	/*	Print the leading delimiter, if any.
	*/
	if (PPVL_Type_Is_Text (The_Value->type))
		putc (TEXT_DELIMITER, The_File);
	else if (PPVL_Type_Is_Symbol (The_Value->type))
		putc (SYMBOL_DELIMITER, The_File);

	if (The_Value->data.string)
		/*	Print the string.
		*/
		fprintf (The_File, The_Value->data.string);

	/* Print the trailing delimiter, if any.
	*/
	if (PPVL_Type_Is_Text (The_Value->type))
		putc (TEXT_DELIMITER, The_File);
	else if (PPVL_Type_Is_Symbol (The_Value->type))
		putc (SYMBOL_DELIMITER, The_File);
	}

/*------------------------------------------------------------------------------
	Real number value:
*/
else if (PPVL_Type_Is_Real (The_Value->type))
	fprintf (The_File, PPVL_real_number_format,
		The_Value->data.real);

/*------------------------------------------------------------------------------
	Integer number value:
*/
else
	{
	number = The_Value->data.integer;
	base = Absolute_Value (The_Value->base);
	if (base && base != 10)
		{
		if (base < 2 || base > 36)
			ERROR (PPVL_ERROR_VALUE_OVERFLOW);

		base = The_Value->base;
		if (number < 0)
			{
			/*	Abolute value of the number;
				the base presents any sign.
			*/
			base = -base;
			number = -number;
			}
		fprintf (The_File, "%d#", base);
		fprintf (The_File, "%s#",
			ultostring (number, Absolute_Value (base), number_string));
		}
	else
		{
		if (The_Value->base < 0)
			number = -number;
		fprintf (The_File, "%ld", number);
		}
	}

/*------------------------------------------------------------------------------
	Units string:
*/
if (The_Value->units)
	fprintf (The_File, " %c%s%c",
		UNITS_START_DELIMITER, The_Value->units, UNITS_END_DELIMITER);

return (PPVL_SUCCESS);
		

Error_condition:

return (PPVL_errno);
}

/******************************************************************************/

int
PPVL_count_all_parameters
	(
	PPVL_Parameter	*The_Aggregate
	)
{
PPVL_Parameter
	**parameters;
int
	count = 0;


if ((count = PPVL_count_parameters (The_Aggregate)) > 0)
	{
	for (parameters = The_Aggregate->content.parameters;
		*parameters;
		 parameters++)
		{
		if (PPVL_Class_Is_Begin_Aggregate ((*parameters)->classification))
			count += PPVL_count_all_parameters (*parameters);
		}
	}

return (count);
}

/******************************************************************************/

int
PPVL_count_parameters
	(
	PPVL_Parameter	*The_Aggregate
	)
{
PPVL_Parameter
	**parameters;
int
	count = 0;


PPVL_errno = PPVL_SUCCESS;

if (The_Aggregate)
	{
	if (PPVL_Class_Is_Begin_Aggregate (The_Aggregate->classification))
		{
		if (parameters = The_Aggregate->content.parameters)
			while (*parameters++)
				count++;
		else
			/*	WARNING: No parameter list.
			*/
			PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT;
		return (count);
		}
	else
		/*	ERROR: Not a begin aggregate parameter.

			Can't count the number of parameters
			in a parameter that can't have a list.
		*/
		PPVL_errno = PPVL_ERROR_ILLEGAL_SYNTAX;
	}
else
	/*	ERROR: The_Aggregate is NULL.
	*/
	PPVL_errno = PPVL_ERROR_BAD_ARGUMENT;

return (-1);
}

/******************************************************************************/

int
PPVL_count_all_values
	(
	PPVL_Value		*The_Array
	)
{
PPVL_Value
	**values;
int
	count = 0;


if ((count = PPVL_count_values (The_Array)) > 0 &&
	PPVL_errno != PPVL_ERROR_ILLEGAL_SYNTAX)
	{
	for (values = The_Array->data.array;
		*values;
		 values++)
		{
		if (PPVL_Type_Is_Array ((*values)->type))
			count += PPVL_count_all_values (*values);
		}
	}

return (count);
}

/******************************************************************************/

int
PPVL_count_values
	(
	PPVL_Value		*The_Array
	)
{
PPVL_Value
	**values;
int
	count = 0;


PPVL_errno = PPVL_SUCCESS;

if (The_Array)
	{
	if (PPVL_Type_Is_Array (The_Array->type))
		{
		if (values = The_Array->data.array)
			while (*values++)
				count++;
		else
			/*	WARNING: No values list.
			*/
			PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT;
		}
	else
		{
		/*	WARNING: Not an array value.
			Count the number of values as the single datum.
		*/
		PPVL_errno = PPVL_ERROR_ILLEGAL_SYNTAX;
		if (PPVL_Is_Value (The_Array))
			count = 1;
		else
			goto Error_condition;	/* ERROR */
		}
	return (count);
	}
else
	/*	ERROR: The_Array is NULL.
	*/
	PPVL_errno = PPVL_ERROR_BAD_ARGUMENT;


Error_condition:

return (-1);
}

/******************************************************************************/

int
PPVL_index_parameter
	(
	PPVL_Parameter	*The_Aggregate,
	PPVL_Parameter	*The_Parameter
	)
{
PPVL_Parameter
	**parameters;
int
	index = 0;


PPVL_errno = PPVL_SUCCESS;

if (The_Aggregate)
	{
	if (PPVL_Class_Is_Begin_Aggregate (The_Aggregate->classification))
		{
		if (parameters = The_Aggregate->content.parameters)
			{
			while (*parameters)
				{
				if (*parameters++ == The_Parameter)
					{
					return (index);
					}
				index++;
				}
			}
		PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT;
		}
	else
		PPVL_errno = PPVL_ERROR_ILLEGAL_SYNTAX;
	}
else
	PPVL_errno = PPVL_ERROR_BAD_ARGUMENT;

return (-1);
}

/******************************************************************************/

int
PPVL_index_value
	(
	PPVL_Value		*The_Array,
	PPVL_Value		*The_Value
	)
{
PPVL_Value
	**values;
int
	index = 0;


PPVL_errno = PPVL_SUCCESS;

if (The_Array)
	{
	if (PPVL_Type_Is_Array (The_Array->type))
		{
		if (values = The_Array->data.array)
			{
			while (*values)
				{
				if (*values++ == The_Value)
					{
					return (index);
					}
				index++;
				}
			}
		PPVL_errno = PPVL_ERROR_EMPTY_STATEMENT;
		}
	else
		PPVL_errno = PPVL_ERROR_ILLEGAL_SYNTAX;
	}
else
	PPVL_errno = PPVL_ERROR_BAD_ARGUMENT;

return (-1);
}

/******************************************************************************/

static PPVL_Error_Code
enlarge_list
	(
	PPVL_Object		***The_List,
	int				*Size
	)
{
PPVL_Object
	**list;
int
	size,
	count;


/*	>>> CAUTION <<<
	The *Size does NOT include the terminating NULL.
*/
if (list = *The_List)
	{
	size = *Size +
		((*Size < VALUES_ARRAY_SIZE_THRESHOLD_1) ?
			VALUES_ARRAY_SIZE_INCREMENT :
		((*Size < VALUES_ARRAY_SIZE_THRESHOLD_2) ?
			VALUES_ARRAY_SIZE_THRESHOLD_1 :
			VALUES_ARRAY_SIZE_THRESHOLD_2));

	if (! (list = (PPVL_Object **)realloc (list,
			(size + 1) * sizeof (PPVL_Object *))))
		ERROR (PPVL_ERROR_NO_MEMORY);

	/*	Clear the new pointers.
	*/
	for (count = *Size + 1;
		 count <= size;
		 count++)
		list[count] = NULL;
	}
else
	{
	size = VALUES_ARRAY_SIZE_INCREMENT;

	if (! (list = (PPVL_Object **)calloc (size + 1, sizeof (PPVL_Object *))))
		ERROR (PPVL_ERROR_NO_MEMORY);
	}

/*	Update the list size and address variables.
*/
*Size = size;
*The_List = list;

return (PPVL_SUCCESS);


Error_condition:

return (PPVL_errno);
}

/******************************************************************************/

static PPVL_Error_Code
reduce_list
	(
	PPVL_Object		***The_List
	)
{
PPVL_Object
	**list;
int
	size = 0;


if (list = *The_List)
	{
	while (*list++)
		size++;		/* the number of entries */

	if (size)
		{
		/*	Reallocate only the storage actually used.
		*/
		if (list = (PPVL_Object **)realloc (*The_List,
				(size + 1) * sizeof (PPVL_Object *)))
			{
			*The_List = list;
			}
		else
			{
			PPVL_errno = PPVL_ERROR_NO_MEMORY;
			return (PPVL_errno);
			}
		}
	else
		{
		/*	Empty list.
		*/
		free (*The_List);
		*The_List = NULL;
		}
	}

return (PPVL_SUCCESS);
}

