################################################################################
# TSL-LIBRARY:	EMOS_DDT_ACCESS_lib
################################################################################
# Copyright (C) 2000  EMOS Computer Consulting GmbH
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# For further information please contact:
#
#	Dean Rajovic
#	EMOS Computer Consulting GmbH
#	Oskar-Messter-Straße 25
#	85737 Ismaning
#	Germany
#	tel.: +49 89 608 765-0
#	mailto:drajovic@emos.de
#	http://www.emos.de
################################################################################
# $Revision: 1.2 $
# $Author: drajovic $
# $Date: 2005/01/28 11:18:42 $
# $Archive: /MERCURY/Projects/EMOS_GPL/DDT/emos_ddt_access_lib/script $
# $NoKeywords: $
################################################################################

#**# 
#*	This library defines a sequential forward "iterator" (as much as one can 
#*	emulate a real iterator in a language such as TSL) for data tables 
#*	(ddt-interface).
#*	The main benefits of this iterator are:
#*	   1.) you can loop through any table in an uniform way,
#*	   2.) you can elegantly define a set of entries to be iterated through,
#*	   3.) you can simultaneously have iterators for multiple data tables
#*	       (ane at a time per table!)
#*	   4.) you can use it to iterate anything, not only data tables.
#*<p>NOTE!
#*	To understand how this iterator works use the analogy of selecting pages to
#*	be printed in a word-processing program. This sort of dialogs typically
#*	allow you to specify the RANGE of pages by connecting a first and the last
#*	page in the range with a hyphen (e.g. 3-9 ) and INDIVIDUAL pages by
#*	separating them with commas (e.g. 1,5,9,15-17,20). Exactly the same syntax
#*	is used with this iterator. So, if you call 
#*<pre>		DDT_ACCESS_init( table, "1-3,7,9-11" );</pre>
#*	the
#*<pre>		DDT_ACCESS_get_next( table );</pre>
#*	will return you the sequence 1,2,3,7,9,10,11.
#*
#*	This iterator also accepts non-numeric expressions such as
#*<pre>		DDT_ACCESS_init( table, "foo,bar,anyway" );</pre>
#*	Note however that this iterator does not read a data table in any way. So
#*	if your table contains following five columns
#*<pre>		aaa;foo;ccc;bar;eee</pre>
#*	the expression
#*<pre>		DDT_ACCESS_init( table, "foo-bar" );</pre>
#*	will return you "foo-bar" instead of "foo", "ccc", "bar" as you might have
#*	expected. If you use alpha names you, should know them in advance, i.e.
#*<pre>		DDT_ACCESS_init( table, "foo,ccc,bar" );</pre>
#*
#*	You might think of this as a serious limitation, but once you learn how
#*	EMOS uses data tables in its framework, you will probably apreciate the
#*/

#____________________________________________________
# local variables
#____________________________________________________

static testNames[];
static testCount[];
static testIdx[];

#**
#*	Initialises the standard access algorithm. The <code>tests</code> can be specified 
#*	with syntax described on library-level (e.g. "1-5,7,9,aaa,xxx,12-35).
#* @param table (in)	unique name for the iterator (normaly, name of the data table)
#* @param tests (in)	test names (e.g. "1-15" or "1-3,5,7" or "a,b,c", etc.)
#* @return
#*	E_OK:	initialisation succeeded
#*	!E_OK:	invalid test set
#*/

public function DDT_ACCESS_init( in table, in tests )
{
	auto count = init_count( table, tests );
	if ( count == 0 )
		return E_ILLEGAL_PARAMETER;
	testCount[table] = count;
	testIdx[table] = 0;
	return E_OK;
}

#**
#*	Removes access for the given <code<table</code> name and frees the internal buffers.
#* @param table (in)	the name od the iterator (data table)
#*/

public function DDT_ACCESS_clean ( in table )
{
	delete_dimension( testNames, table );
	delete_dimension( testCount, table );
	delete_dimension( testIdx, table );
}

#**
#*	Indicates whether a subsequent call to DDT_ACCESS_get_next() is about to 
#*	succeed or not.
#* @param table (in)	the name od the iterator (data table)
#* @return
#*	TRUE:	there are still some tests left to be processed
#*	FALSE:	all tests processed
#*/

public function DDT_ACCESS_has_more( in table )
{
	return testIdx[table] < testCount[table];
}

#**
#*	Returns the name of the next test to be processed. More specifically, it
#*	returns the next available name from the set of names that were specified 
#*	by the DDT_ACCES_init() command.
#* @param table (in)	the name od the iterator (data table)
#* @return
#*	!"":	a test name
#*	"":		error ocurred
#*/

public function DDT_ACCESS_get_next( in table )
{
	if ( DDT_ACCESS_has_more( table ) )
		return testNames[table, testIdx[table]++];
	else
		return "";
}

#**
#*	Analyses the given string <code>tests</code> and builds an array that contains all
#*	individual values that were specified.
#* @param table (in)	table dimension
#* @param tests (in)	test names in format (1,3-9) 
#* @return
#	number of tests to be processed
#*/

static function init_count( in table, in tests )
{
	auto i, j;
	auto x, y, z, b;
	auto t1, tests1[];
	auto t2, tests2[];

	delete_dimension( testNames, table );

	t1 = split( tests, tests1, "," );

	j = 0;
	for( i=1; i <= t1; i++ )
	{
		t2 = split( tests1[i], tests2, "-" );
		switch ( t2 ) 
		{
			case 0:
				break;
			case 1:
				testNames[table, j++] = tests2[1];
				break;
			default:
				if ( get_range( tests2[1], tests2[2], x, y, b ) != E_OK )
				{
					testNames[table, j++] = tests1[i];
					break;
				}
				if ( x < y )
					while ( x <= y )
						testNames[table, j++] = b & x++;
				else
					while ( x >= y )
						testNames[table, j++] = b & x--;
				break;
		}
		for( z=1; z<=t2; z++ ) delete tests2[z];
	}
	return j;
}

static function get_range( in from, in to, out x, out y, out b )
{
	extern RSTART, RLENGTH;
	
	y = to*1;
	if ( !y ) return E_OUT_OF_RANGE;

	if ( match( from, "[0-9][0-9]*" ) )
	{
		if ( RSTART == 1 )
		{
			x = substr( from, RSTART, RLENGTH )*1;
			b = substr( from, RSTART+RLENGTH );
			return E_OK;
		}
		if ( RSTART+RLENGTH-1 == length( from ) )
		{
			b = substr( from, 1, RSTART-1 );
			x = substr( from, RSTART )*1;
			return E_OK;
		}
	}
	return E_OUT_OF_RANGE;
}

#**
#*	This function deletes a dimension from a multidimensional array.
#*<p>WARNING!<p>
#*	It will delete ALL dimensions with the same name. For example
#*   assume your three-dimensional array "arr" has the following content:
#*<pre>		arr["x",1,"x"] = "x1x";
#*		arr["x",1,"y"] = "x1y";
#*		arr["y",1,"x"] = "y1x";
#*		arr["y",1,"y"] = "y1Y";
#*</pre>
#*	then the following command
#*<pre>		delete_dimension( arr, "x" );</pre>
#*	will delete everything BUT
#*<pre>		arr["y",1,"y"] = "y1Y";</pre>
#* @param arr (inout)	array to be processed
#* @param dim (in)	dimension(s) to be removed
#*/

static function delete_dimension( inout arr[], in dim )
{
	auto i;
	for ( i in arr )
		if ( match (i, dim) )
			delete arr[i];
}

#**
#*	Test with valid values
#*/

static function col_test_valid()
{
	auto i, valid[], tab = "tab";
	valid[0] = "1";
	valid[1] = "1,2";
	valid[2] = "1-3";
	valid[3] = "1,3-5,7";
	valid[4] = "30-33,40-42";
	valid[5] = "abc";
	valid[6] = "aaa,bbb,ccc";
	valid[7] = "11-11";
	valid[8] = "11-5";

	for ( i = 0; i < 9; i++ )
	{
		print( "test: " & valid[i] );
		DDT_ACCESS_init( tab, valid[i] );
		while( DDT_ACCESS_has_more( tab ) )
			print( "	" & DDT_ACCESS_get_next( tab ) );
		DDT_ACCESS_clean( tab );
	}
}

#**
#*	Test with invalid values
#*/

static function col_test_invalid()
{
	auto i, invalid[], tab = "tab";
	invalid[0] = "";
	invalid[1] = ",-,";
	invalid[2] = "-3";
	invalid[3] = "1-, 3-5 ,-";
	invalid[4] = "1-3-5,42";
	invalid[5] = "abc-xxx";
	invalid[6] = "3-1";

	for ( i = 0; i < 7; i++ )
	{
		print( "test: " & invalid[i] );
		DDT_ACCESS_init( tab, invalid[i] );
		while( DDT_ACCESS_has_more( tab ) )
			print( "	" & DDT_ACCESS_get_next( tab ) );
		DDT_ACCESS_clean( tab );
	}
}

# Uncomment the following lines to run the test

#static const x = col_test_valid();
#static const y = col_test_invalid();

###########################################################################
# TSL-LIBRARY:	EMOS_DDT_ACCESS_lib
###########################################################################