# ****************
# Some basic array related functions that are useful for TSL 
# programmers out there
#
# Created by: Ferry Firmansjah (firmanf@itechnologist.com)
#
# More information: http://www.itechnologist.com/tech/func_ra.html
#
# Revisions: March 15, 2004 (corrected function array_isempty - thanks Andrew Bleach)
#            September 11, 2002 (corrected function array_append to work better with empty array, optimized array_isempty)
#			 September 8, 2002 (corrected function array_get_first_index, array_get_last_index, array_append)
#			 May 10, 2002 (corrected function array_get_first_index) 
#            February 13, 2002 (corrected function array_get_last_index)
#            October 11, 2001 (corrected function array_get_last_index name)
#            December 4, 2000 (updated array_pick)
#            November 13, 2000
#            November 8, 2000
#            September 18, 2000
#            April 25, 2000
#            November 1, 1998
#
# Copyright (C) 2000-2002 Ferry Firmansjah
# 
# 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.
# ****************

####
# Syntax: array_add (arrayname[], element);
# Return values: E_OK for success, E_GENERAL_ERROR for failure
# Parameters:
#    arrayname[]: name of the array.  The array should be
#        indexed numerically sequentially to ensure proper
#        adding the element.
#    element: the element to add to the array.
# Description: Adds the specified element at the end of
#    the array.  If the array is empty, the element will be added
#    as having an index of 1.
#    E.g. public ra[] = {1, 2, 3};
#         array_add (ra, 4);
#         -> ra[] is now: {1, 2, 3, 4}.
#         public ra2[];
#         array_add (ra2, 4);
#         -> ra[1] = 4;
####
public function array_add (inout arrayname[], in element) {
	auto tmp, i, startIndex = 0, lastIndex;

	if (nargs() != 2) {
		return (report_param_msg ("array_add"));
	}

	# find out the first index in array.
	if (0 in arrayname) {
		startIndex = 0;
	} else if (1 in arrayname) {
		startIndex = 1;
	} else {
		startIndex = array_get_first_index (arrayname);
	}

	# get the last index location
	for (i = startIndex; i in arrayname; i++) {
		lastIndex = i;
	}

	# add element to the end
	arrayname[++lastIndex] = element;
}


####
# Syntax: array_append (new_elements[], base_array[] [, new_elements_startIndex
#    [, new_elements_lastIndex [, base_array_last_index]]]);
# Return values: E_OK;
# Parameters:
#    new_elements: array containing new elements to add, indexed sequentially.
#    base_array: base array to append new elements to.
#    new_elements_startIndex: start index of the new_elements 
#        array to append (Optional).
#    base_array_last_index: the last index of the base array. (helps performance)
# Description: Appends the elements from the new_elements array
#    to the end of the base_array.  If the array to be appended is empty, then
#    the starting index will be 1.
#    You can specify the start and end position of the new_elements array
#    to append.
#    ** Updated 2002/09/11: Now can handle empty array, thanks Gedaliah Friedenberg. **
#    E.g. public ra1[] = {1, 2, 3, 4};
#         public ra2[] = {5, 6, 7, 8};
#         public ra3[];
#         array_append (ra2, ra1, 1, 1);
#         -> ra1 is now: {1, 2, 3, 4, 6}.
#         -> ra2 is unchanged at {5, 6, 7, 8}.
#         array_append (ra1, ra2);
#         -> ra1 is unchanged at {1, 2, 3, 4, 6}.
#         -> ra2 is now: {5, 6, 7, 8, 1, 2, 3, 4, 6}.
#         array_append (ra1, ra3);
#         -> ra1 is unchanged at {1, 2, 3, 4, 6}.
#         -> ra3 is now (starting with index 1): {1, 2, 3, 4, 6}.
####
function array_append (inout new_elements[], inout base_array[], in startIndex, in lastIndex, in base_lastIndex) {
	auto i;

	if (nargs() < 2 || nargs() > 5) {
		return (report_param_msg ("array_append"));
	}

	
	if (nargs() < 3)  {
		if (0 in new_elements) {startIndex = 0;}
		else if (1 in new_elements) {startIndex = 1;}

		# 9/11/2002, 9/8/2002: added check for empty array to append
		else if (array_isempty(new_elements) == TRUE) {return;}

		else {startIndex = array_get_first_index (new_elements);}
	}
	if (nargs() < 4) {
		lastIndex = array_get_last_index (new_elements, startIndex);
	}
	if (nargs() < 5) {
		base_lastIndex = array_get_last_index (base_array);
		# 9/11/2002: When appending to an empty array, start at index 1 
		if(base_lastIndex == "") {base_lastIndex=0;}
	}

	for (i = startIndex; i <= lastIndex; i++) {
		base_array[++base_lastIndex] = new_elements[i];
	}
}

####
# Syntax: array_assign (target_ra[], source_ra[]);
# Return values: none
# Parameters:
#    target_ra: target to be filled with the values
#    source_ra: array that contain the values to enter into the target_ra
# Description: Fills the target_ra with the values from the source_ra.
#    e.g.: if source_ra[] = {"One", "Two", "Three"}.
#          and target_ra[] contains six items
#          then after an array_assign (target_ra, source_ra),
#            target_ra[] will be: {"One", "Three", "Two", "Three", "Two", "One"}.
#    Note that the target_ra[] must already contain elements.
#    The function will randomly fill in the target_array with all
#    of the values of the source_ra.
#    E.g. public ra1[] = {1, 2, 3};
#         public ra2[] = {"a", "b", "c", "d", "e"};
#         array_assign (ra2, ra1);
#         -> ra1 is unchanged at {1, 2, 3}.
#         -> ra2 is now: {2, 1, 3, 1, 2}.
####
public function array_assign (inout target_ra[], inout source_ra[]) {
	auto i, j, sracount;
	if (nargs() != 2) return (report_param_msg ("array_assign"));
	sracount = array_length (source_ra);
	#srand (get_time ());
	j = 0;
	for (i in target_ra) {
		#target_ra[i] = source_ra[int (rand () * sracount)];
		target_ra[i] = source_ra[j % sracount];
		j++;
	}
}

####
# Syntax: array_clear (array_to_clear);
# Return: E_OK
# Parameters:
#    array_to_clear: The array to clear.
# Description: Cleans the content of the array.
#    ** Updated 2000/09/18: change parameter type from inout to out.
#       Thanks Andrew McFarlane. **
#    E.g. public ra[] = {1, 2, 3};
#         array_clear (ra);
#         -> ra[] is now: {}.
####
public function array_clear (out array_to_clear[]) {
	auto i;
	# Keep doing this, in case the out parameter type
	# feature doesn't clear out the array.
	for (i in array_to_clear) {delete array_to_clear[i];}
	return E_OK;
}


####
# Syntax: array_compare (ra1, ra2[, detailed_report]);
# Return values: default return values.  E_OK if both arrays are the same,
#    E_GENERAL_ERROR if they are different.
# Parameters:
#    ra1: Array 1 to compare with.
#    ra2: Array 2 to compare to.
#    detailed_report: TRUE or FALSE, whether to generate a detailed 
#        report of the comparison (Optional).  Defaults to FALSE.
# Description:
#    Compares 2 arrays, and verify whether they are the same.
#    The function will compare the element count, as well as the
#    key/value pairs.
#    E.g. public ra1[] = {1, 2, 3};
#         public ra2[] = {1, 3, 3};
#         public ra3[] = {1, 2};
#         x = array_compare (ra1, ra2, TRUE);
#         -> x returns E_GENERAL ERROR, and the report will show that
#            the second element of the arrays are different.
#         x = array_compare (ra1, ra3, TRUE);
#         -> x returns E_GENERAL_ERROR, and the report will show that
#            the array sizes are different;
####
public function array_compare (inout ra1 [], inout ra2 [], in detailed_report) {
	auto i;
	if (nargs() > 3 || nargs() < 2) return (report_param_msg ("array_compare"));

	if (detailed_report != TRUE) detailed_report = FALSE;

	if (array_length (ra1) != array_length (ra2)) {
		if (detailed_report) {
			report_msg ("array_compare: Arrays of different length ("& array_length (ra1) &" and "& array_length (ra2) &")");
		}
		return E_GENERAL_ERROR;
	}

	for (i in ra1) {
		if (i in ra2) {
			if (ra1 [i] != ra2[i]) {
				if (detailed_report) {
					report_msg ("array_compare: Different value for index '"& i &"' ('"& ra1[i] &"' and '"& ra2[i] &"')");
				}

				return E_GENERAL_ERROR;
			}
		} else {
			return E_GENERAL_ERROR;
		}
	}

}

####
# Syntax: array_copy (source_array[], target_array[]);
# Return values: the number of items copied.
# Parameters:
#    source_array[]: array to copy from.
#    target_array[]: array to copy to (if this is an existing
#        array, it's contents will be erased).
# Description: Makes a replica of the source_array into
#    the target_array.
#    E.g. public ra1[] = {1, 2, 3};
#         array_copy (ra1, ra2);
#         -> ra1[] is unchanged at {1, 2, 3}.
#         -> ra2[] is now: {1, 2, 3}.
####
public function array_copy (source_array [], target_array []) {
	auto i, icount = 0;
	if (nargs() != 2) return (report_param_msg ("array_copy"));
	array_clear (target_array);
	for (i in source_array) {
		icount ++;
		target_array[i] = source_array[i];
	}
	return icount;
}

####
# Syntax: array_get_first_index (inout arrayname[] [, in pivot]);
# Return values: index of the first element, -1 if array is empty.
# Parameters:
#    arrayname: name of the array.
#    pivot: an index in the array to use as a starting point
#        for the search.
# Description: Finds the index of the first element in the
#    specified arrayname array.
#    ** NOTE **: This function has been updated to run faster
#        and more efficiently.
#    ** Updated 2002/09/11: fixed infinite loop under certain condition, thanks Gedaliah Friedenberg. **
#    E.g. ra[3] = "hi";
#         ra[4] = "there";
#         ra[5] = "qa";
#         x = array_get_first_index (ra);
#         -> x is now: 3.
####
function array_get_first_index (inout arrayname[], in i) {
	auto j, m = 4;
	if (!(i in arrayname)) {
		for (i in arrayname) {break;}
	}

	# 9/8/2002: added check for empty array
	if (i == "") {return i;}

	# 5/10/2002: corrected to work correctly with small arrays now
	# jump
	for (i; i in arrayname; i -= (2 ^ m++)) {}
	for (i; !(i in arrayname); i += (2 ^ --m)) {}
	for (i; i in arrayname; i -= (2 ^ --m)) {j = i;}

	for (i = j; i in arrayname; i--) {}
	return i + 1;
}

####
# Syntax: array_get_first_last_index (inout ra[], out first_idx, out last_idx);
# Return values: E_OK if successful, E_GENERAL_ERROR if array is empty.
# Parameters:
#    ra: name of the array.
#    first_idx: variable to store the first index.
#    last_idx: variable to store the last index.
# Description: Finds the index of the first and last elements in the
#    specified arrayname array.
#    ** NOTE **: This function has been updated to run faster
#        and more efficiently.
#    E.g. ra[3] = "hi";
#         ra[4] = "there";
#         ra[5] = "qa";
#         array_get_first_last_index (ra, fidx, lidx);
#         -> fidx is now: 3.
#         -> lidx is now: 5.
####
function array_get_first_last_index (inout ra[], out first_idx, out last_idx) {
	auto i;
	for (i in ra) {break;}
	first_idx = array_get_first_index (ra, i);
	last_idx = array_get_last_index (ra, i);
}

####
# Syntax: array_get_last_index (inout arrayname[] [, startIndex]);
# Return values: index of the last element, -1 if array is empty.
# Parameters:
#    arrayname: name of the array.
#    startIndex: start of the index or location to check (Optional)
# Description: Finds the index of the last element in the
#    specified arrayname array.
#    ** NOTE **: This function has been updated to run faster
#        and more efficiently.
#    ** Updated 2002/09/11: fixed infinite loop under certain condition, thanks Gedaliah Friedenberg. **
#    E.g. ra[3] = "hi";
#         ra[4] = "there";
#         ra[5] = "qa";
#         x = array_get_last_index (ra);
#         -> x is now: 5.
####
function array_get_last_index (inout arrayname[], in i) {
	auto j, m = 4;
	if (!(i in arrayname)) {
		for (i in arrayname) {break;}
	}

	# 9/8/2002: added check for empty array
	if (i == "") {return i;}
	
	
	# 2/13/2002: corrected, changed from 'i=j' to just 'i'
	# jump
	for (i; i in arrayname; i += (2 ^ ++m)) {}
	
	# 9/8/2002: corrected this to avoid infinite loop
	#for (i; !(i in arrayname); i -= (2 ^ --m)) {}
	for (i; !(i in arrayname); i -= (2 ^ m--)) {}

	for (i; i in arrayname; i += (2 ^ --m)) {j = i;}
	for (i = j; i in arrayname; i++) {}
	return i - 1;
}

####
# Syntax: array_insert (arrayname[], arrayindex, element);
# Return values: E_OK for success, E_GENERAL_ERROR for failure
# Parameters:
#    arrayname[]: name of the array.  The array should be
#        indexed numerically sequentially to ensure proper
#        shifting of the elements.
#    arrayindex: array index entry to insert to
#    element: the element to insert into the array.
# Description: Inserts the specified element into the array
#    at the specified index location.  If the specified
#    index is already populated, then it will moves 
#    the element at the index location backward and insert
#    the new element at the index location.
#    E.g. public ra[] = {1, 2, 3, 4};
#         array_insert (ra, 1, 5);
#         -> ra[] is now: {1, 5, 2, 3, 4}.
####
public function array_insert (inout arrayname[], in arrayindex, in element) {
	auto tmp, i, lastIndex;

	if (nargs() != 3) {
		return (report_param_msg ("array_insert"));
	}

	if (arrayindex in arrayname) {
		#move everything backward first.
		# get the last index location
		for (i = arrayindex; i in arrayname; i++) {
			lastIndex = i;
		}

		# move everything after the index location back 1 position
		for (i = lastIndex; i >= arrayindex; i--) {
			arrayname[i + 1] = arrayname[i];
		}

		arrayname[arrayindex] = element;
	} else {
		arrayname[arrayindex] = element;
	}
}

####
# Syntax: array_inverse (inout array[] [, firstindex [, lastindex]]);
# Return value: E_OK if succeeds.
# Parameters:
#    array: the array that you want to inverse the content of
#    firstindex: the index of the first element (optional)
#    lastindex: the index of the last element (optional)
# Description: Inverses the order of a sequential array.
#    E.g. public ra[] = {1, 2, 3, 4};
#         array_inverse (ra);
#         -> ra[] is now: {4, 3, 2, 1}.
#         array_inverse (ra, 1, 2);
#         -> ra[] is now: {4, 2, 3, 1}.
####
function array_inverse (inout ra[] , firstindex, lastindex) {
	auto mid, i;
	if (nargs() == 3) {
		# nothing, we're set
	} else if (nargs() == 2) {
		# find the lastindex
		lastindex = array_get_last_index (ra, firstindex);
	} else if (nargs() == 1) {
		# find first and last indices
		firstindex = array_get_first_index (ra);
		lastindex = array_get_last_index (ra, firstindex);
	} else {
		return (report_param_msg ("array_inverse"));
	}

	mid = int ((lastindex - firstindex) / 2);

	for (i = 0; i <= mid; i++) {
		array_swap (ra, firstindex + i, lastindex - i);
	}
}

####
# Syntax: array_isempty (inout ra[]);
# Return value: TRUE if ra[] is empty, FALSE if ra[] is not empty.
# Parameters:
#    ra[]: the array to check.
# Description: Determines whether an array is empty or not.
#    E.g. public ra1[] = {1, 2, 3};
#         public ra2[];
#         x = array_isempty (ra1);
#         y = array_isempty (ra2);
#         -> x is now: FALSE.
#         -> y is now: TRUE.
####
function array_isempty (inout ra[]) {
	auto i;
	# 9/11/2002: updated to be more efficient.
	# 3/15/2004: updated to correct reversed result (thanks Andrew Bleach)
	for (i in ra) {
		return FALSE;
	}
	return TRUE;
}


####
# Syntax: array_length (array);
# Return value: length of array.
# Parameters:
#    array: the array that you want to return the length of
# Description: Returns the length of an array.
#    E.g. public ra1[] = {1, 2, 3};
#         ra2["one"] = 1;
#         ra2["two"] = 2;
#         x = array_length (ra1);
#         -> x is now: 3.
#         x= array_length (ra2);
#         -> x is now: 2.
####
public function array_length (inout in_ra[]) {
	auto counter = 0, i;
	if (nargs() != 1)
		return (report_param_msg ("array_length"));

	for (i in in_ra) {counter++;}
	return counter;
}

####
# Syntax: array_pick (pool_ra[][, seed]);
# Return values: the value of the selected array item
# Parameters:
#    pool_ra: array to pick from
#    seed: seed to give to the srand() function (Optional).
# Description: Returns a randomly picked array item.  Does not modify
#    the array's content at all.
#    E.g. public ra[] = {1, 2, 3, 4};
#         x = array_pick (ra);
#         -> x can be 1, 2, 3 or 4.
#         -> ra[] is unchanged at {1, 2, 3, 4}.
####
public function array_pick (pool_ra[], seed) {
	auto i, j = 0, pick, ra_length = array_length (pool_ra);
	if (nargs() == 2) {
		srand (seed);
	}
	pick = (int (rand () * ra_length));
	for (i in pool_ra) {
		if (j == pick) return pool_ra[i];
		else j++;
	}
}


####
# Syntax: array_pick_remove (pool_ra[][, seed]);
# Return values: the value of the array item removed randomly from 
#    the pool_ra. If the pool_ra is empty, it will return 
#    an E_GENERAL_ERROR.
# Parameters:
#    pool_ra: array to pick from
#    seed: seed to give to the srand() function (Optional).
#        Uses the time is left empty.
# Description: Returns a randomly picked array item.  ** Modify
#    the array's content by removing the selected item. **
#    E.g. public ra[] = {1, 2, 3, 4};
#         x = array_pick_remove (ra);
#         -> x can be 1, 2, 3 or 4.  Let's say x is 3.
#         -> ra[] will now contain one less element: {1, 2, 4}.
####
public function array_pick_remove (pool_ra[], seed) {
	auto i, j = 0, pick, sel, ra_length;
	if (nargs() == 1) { srand (get_time()); }
	else if (nargs() == 2) { srand (seed); }
	else {return (report_param_msg ("array_pick_remove")); }

	ra_length = array_length (pool_ra);
	if (ra_length < 1) { return (E_GENERAL_ERROR); }

	pick = (int (rand () * ra_length));
	for (i in pool_ra) {
		if (j == pick) {
			sel = array_remove (pool_ra, i);
			return sel;
		}
		else j++;
	}
}



####
# Syntax: array_remove (arrayname[], arrayindex);
# Return values: the removed array's value
# Parameters:
#    arrayname[]: name of the array
#    arrayindex: array index entry to remove (0 is the first item)
# Description: Removes the specified index entry from
#    the array arrayname, and moves subsequent array
#    element's index down.
#    E.g. public ra[] = {1, 2, 3, 4};
#         x = array_remove (ra, 2);
#         -> x is now: 3.
#         -> ra[] is now: {1, 2, 4}.
####
public function array_remove (inout arrayname[], arrayindex) {
	auto tmp, i, ralength = array_length (arrayname);
	if (arrayindex > ralength - 1) {
		return;
	}

	tmp = arrayname[arrayindex];
	for (i = arrayindex; i < ralength - 1; i++) {
		arrayname[i] = arrayname[i + 1];
	}
	delete arrayname[ralength - 1];
	return tmp;
}


####
# Syntax: array_shuffle (array[]);
# Return values: E_OK.
# Parameters:
#    array: the array to shuffle
# Description: Shuffles the content of the array.
#    E.g. public ra[] = {1, 2, 3, 4};
#         array_shuffle (ra);
#         -> ra[] is now: {2, 4, 1, 3}.
####
public function array_shuffle (inout in_ra[]) {
	auto tmpra[], tmpracount = 0, i, tmp, a, b;
	if (nargs() != 1) return (report_param_msg ("array_shuffle"));
	if (array_length (in_ra) > 1) {
		srand (get_time ());
		for (i in in_ra) tmpra[tmpracount++] = i;
		for (i = 0; i < tmpracount; i++) {
			a = int (rand() * tmpracount);
			if (a != i) {
				tmp = in_ra[tmpra[a]];
				in_ra[tmpra[a]] = in_ra[tmpra[i]];
				in_ra[tmpra[i]] = tmp;
			}
		}
	}
	#for (i in tmpra) delete tmpra[i];
	array_clear (tmpra);
	return (E_OK);
}

####
# Syntax: array_subset (inout original_array[], out target_array, 
#    begin_index, end_index [, target_start_index]);
# Return values: 0 (E_OK) if succeeds, -1 if the end_index is 
#    less than the begin_index,
#    -2 if the beginning index (begin_index) is invalid, 
#    -3 if the ending index (end_index) is invalid, 
#    and -4 if the array is not sequentially ordered (missing an 
#    index value between begin_index and end_index).
# Parameters:
#    original_array: the original array (will not be modified)
#    target_array: the target array to store the subset to 
#        (content will be cleared)
#    begin_index: the starting index (inclusive) of the 
#        original_array to get a subset of
#    end_index: the ending index (inclusive) of the original_array 
#        to get a subset of
#    target_start_index: Optional beginning index of the target array.
#        (defaults to the same index as the original)
# Description: Copy a subset of an array to another array.
#    Also allows the user to specify the starting index in the
#    target array.
#    E.g. public ra1[] = {1, 3, 5, 2, 8, 12, 9};
#         array_subset (ra1, ra2, 2, 5);
#         -> ra2[] is now: {5, 2, 8, 12}.
####
function array_subset (inout orig[], inout tgt[], in begidx, in endidx, in tgt_start_idx) {
	auto i;
	if (nargs() < 4 || nargs() > 5) {return (report_param_msg ("array_subset"));}
	if (endidx < begidx) { return -1; }
	if (!(begidx in orig)) { return -2; }
	if (!(endidx in orig)) { return -3; }
	if (nargs() == 4) { tgt_start_idx = begidx; }
	for (i = begidx; i <= endidx; i++) {
		if (i in orig) {
			tgt[tgt_start_idx++] = orig[i];
		} else {
			# invalid index
			return -4;
		};
	}
	return 0;
}




####
# Syntax: array_swap (arrayname[], arrayindex1, arrayindex2);
# Return values: E_OK if success, E_GENERAL_ERROR if fails 
#    (e.g. if index doesn't exist)
# Parameters:
#    arrayname[]: name of the array
#    arrayindex1, arrayindex2: array index entries to swap
# Description: Swap the element content of the specified
#    array index locations.
#    E.g. public ra[] = {1, 2, 3, 4, 5};
#         array_swap (ra, 0, 4);
#         -> ra[] is now: {5, 2, 3, 4, 1}.
####
public function array_swap (inout arrayname[], in arrayindex1, in arrayindex2) {
	auto tmp;
	if (nargs() != 3) {
		return (report_param_msg ("array_swap"));
	}

	if ((arrayindex1 in arrayname) && (arrayindex2 in arrayname)) {
		tmp = arrayname[arrayindex1];
		arrayname[arrayindex1] = arrayname[arrayindex2];
		arrayname[arrayindex2] = tmp;
		return E_OK;
	}
	return E_GENERAL_ERROR;
}


####
# Syntax: join (array_to_join[], concatenator[, join_start[, join_end] ] ]);
# Return values: E_OK;
# Parameters:
#    array_to_join[]: the array to join together.  Make sure that the
#        array is indexed sequentially from the join_start index
#        to the join_end index.
#    join_start: the start index of the array to join (Optional, default to 0).
#    join_end: the last index of the array to join (Optional, default 
#        to the last item in the array).
# Description: Returns a concatenated string of the array elements.
#    ** Note: ** if the join_start or join_end index value is not in the
#    array, then the resulting string will take these inexistent
#    elements as containing empty string.
#    E.g. public ra[] = {"hello", 30, "geese"};
#         x = join (ra, " ");
#         -> x is now: "hello 30 geese".
####
public function join (array_to_join[], concatenator, join_start, join_end) {
	auto i, s, temp_ra[], temp_racount;
	if (nargs() < 2 || nargs() > 4) return (report_param_msg ("join"));

	# make a copy so that the original is not messed up
	temp_racount = array_copy (array_to_join, temp_ra);

	if (join_start == "") {
		join_start = 0;
	}


	if (join_end == "") {
		join_end = temp_racount + join_start;
	}

	for (i = join_start; i < join_end; i++) {
		if (i != join_start) {
			s = s & concatenator & temp_ra [i];
		} else {
			s = temp_ra [i];
		}
	}
	return s;
}


####
# Syntax: match_array_to_array (inout actual_array[], inout expected_array[]
#    , out matched_array[], out unmatched_act_array[], out unmatched_exp_array[]) 
# Return value: E_OK for success.
# Parameters:
#    actual_array: array containing the actual elements to compare.
#    expected_array: array containing expected elements.  The element
#        in the expected_array can contain regular expression
#        that will be evaluated during the compare.
#    matched_array: Output array for matched msg_array elements.
#    unmatched_act_text_array: Output array for elements that exists 
#        in actual_array, but not in expected_array.
#    unmatched_exp_array: Output array for elements that exists in 
#        expected_array, but not in actual_array.
# Description: Compares the actual_array to the expected_array
#    and populates the appropriate arrays with information on the 
#    matching elements or lack thereof.
#    ** NOTE **: This matching is not order sensitive.  If you are 
#        looking for element by element comparison, use the 
#        array_compare() function.
#    E.g. public act_ra[] = {1, 2, 3, 4, "hello", "world"};
#         public exp_ra[] = {"[12]", "10", ".*lo.*"};
#         match_array_to_array (act_ra, exp_ra, match_ra, unmatched__act_ra, missing_exp_ra);
#         -> act_ra[] and exp_ra[] are unchanged.
#         -> match_ra[] is now: {"[12]", ".*lo.*"};
#         -> missing_act_ra[] is now: [1]-> 2, [2]-> 3, [3]-> 4, [5]-> "world".
#         -> missing_exp_ra[] is now: [1]-> 10.
####
public function match_array_to_array (inout actual_array[], inout expected_array[]
		, out matched_array[], out unmatched_act_array[], out unmatched_exp_array[]) {
	auto i, j, matchcount = 0;

	if (nargs() != 5) {
		return (report_param_msg ("match_array_to_array"));
	}

	array_clear (matched_array);
	array_clear (unmatched_exp_array);
	array_clear (unmatched_act_array);

	array_copy (expected_array, unmatched_exp_array);
	array_copy (actual_array, unmatched_act_array);

	for (i in unmatched_exp_array) {
		for (j in unmatched_act_array) {
			if (match (unmatched_act_array[j], unmatched_exp_array[i])) {
				matched_array [matchcount++] = unmatched_exp_array[i];
				delete unmatched_act_array[j];
				delete unmatched_exp_array[i];
				break;
			}
		}
	}

	return E_OK;
}


####
# Syntax: split2 (string, array[, separator]);
# Return value: number of elements
# Parameters:
#    string: A valid string expression.
#    array: The name of the storage array.
#    separator: The string containing the separator "string" (Optional).
#        Unlike the regular split() function, this function 
#        will treat the whole string as the separator, not as 
#        separate separators.
# Description: A modified form of the split() function that allows the
#    user to specify multiple character separator (string as opposed to a 
#    character only).
#    E.g. x = "Hello--there--people--how-are--you";
#         y = split2 (x, ra, "--");
#         -> y is now: 5.
#         -> ra is now: {"Hello", "there", "people", "how-are", "you"}.
####
public function split2 (in in_str, inout out_ra[], in separator) {
	auto i, ind, currind = 1, len, str;
	if (nargs() == 2) {
		separator = " ";
	} else {
		if (nargs() != 3) {
			report_param_msg ("split2");
		}
	}

	for (i in out_ra) delete out_ra[i];

	str = in_str;
	len = length (separator);

	i = 0;
	do {
		ind = index (str, separator);
		if (ind > 0) {
			# split index starts at 1
			out_ra[++i] = substr (str, 1, ind - 1);
			str = substr (str, ind + len);
		} else {
			out_ra[++i] = str;
		}
	} while (ind > 0);
	return i;
}


####
# Syntax: split2_append (string, array[, separator);
# Return value: number of elements added
# Parameters:
#    string: A valid string expression.
#    array: The name of the storage array to append to.
#    separator: The string containing the separator "string" (Optional).
#        Unlike the regular split() function, this function will 
#        treat the whole string as the separator, not as separate 
#        separators.
# Description: A modified form of the split() function that allows the
#    user to specify multiple character separator, and *appends* the new
#    entries to the array instead of replacing the array content.
#    ** NOTE **: the array must contain sequential elements, with the first
#                index having the value of 1.
#    E.g. x = "Hello--there--people--how-are--you";
#         public ra[] = {"This", "is", "it"};
#         y = split2 (x, ra, "--");
#         -> y is now: 5.
#         -> ra is now: {"This", "is", "it", "Hello", "there", "people", "how-are", "you"}.
####
public function split2_append (in in_str, inout out_ra[], in separator) {
	auto i, tmpra[];
	if (nargs() == 2) {
		separator = " ";
	} else {
		if (nargs() != 3) {
			report_param_msg ("split2_append");
		}
	}

	i = split2 (in_str, tmpra, separator);

	array_append (tmpra, out_ra, 1, i);
	return i;
}


# Signifies that this module has been loaded
# Can be used by calling test to minimize reloading
# of the function.
# e.g.  
# if (!func_ra_loaded) {
#   reload ("func_ra", 1, 1);
# }
const func_ra = TRUE;
