~kb/ncl

59629ec04e4067f60d0ca4db402a7a760c1b68ed — Tom Haskell 8 years ago 39883e1 + 59f2a01
Merge branch 'upstream_changes'
11 files changed, 647 insertions(+), 462 deletions(-)

M LICENSE
M README.md
M array.axi
M debug.axi -rwxr-xr-x => -rw-r--r--
M graph.axi
R ncl.axi => netlinx-common-libraries.axi
M string.axi
A test/test_array.axi
A test/test_string.axi
D tp.axi
M unixtime.axi
M LICENSE => LICENSE +19 -19
@@ 1,19 1,19 @@
Copyright (C) 2011 Queensland Department of Justice and Attorney General.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Copyright (C) 2011 Queensland Department of Justice and Attorney General.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

M README.md => README.md +37 -33
@@ 1,33 1,37 @@
´╗┐This project contains globally useful, portable includes for augmenting the base functionality of the proprietary AMX NetLinx language used for programming [AMX NetLinx integrated controllers](http://www.amx.com/products/categoryCentralControllers.asp).

Currently the project provides a math library, string manipulation library, time and date library, array utils, a managed debug messaging library and an associated console output library.

# Contributors
[Kim Burgess](http://kimburgess.info)

[true](mailto:amx@trueserve.org)

[Jeff Spire](http://spireintegrated.com/)

[Jorde Vorstenbosch](mailto:jordevorstenbosch@gmail.com)

[Andy Dixon](https://github.com/PsyenceFact)

# Contributing

Want to help out? Awesome.

1. Fork it.
2. Make your changes / improvements / bug fixes etc (and ensure that you adhere to the [project style guide](https://github.com/KimBurgess/NetLinx-Common-Libraries/wiki/Code-Format-and-Commenting) whilst doing so).
3. Submit a pull request.
4. Win.

# Usage

A convenience include has been provided to simplify usage. Ensure that the libraries are placed within your project's compile path then include prior to utilizing provided functionality within your project code.

    include 'netlinx-common-libraries'

Alternatively, individually specify the library components that you require. Any cross include dependencies within the NetLinx Common Libraries are handled internally.

This project is licensed under the MIT License. Feel free to use it, sell it, modify it, re-distribute - basically whatever the hell you like. See [LICENSE](https://github.com/KimBurgess/NetLinx-Common-Libraries/blob/master/LICENSE) for more info.
This project contains globally useful, portable includes for augmenting the base functionality of the proprietary AMX NetLinx language used for programming [AMX NetLinx integrated controllers](http://www.amx.com/products/categoryCentralControllers.asp).

Currently the project provides a math library, string manipulation library, time and date library, array utils, a managed debug messaging library and an associated console output library.

# Contributors
[Kim Burgess](http://kimburgess.info)

[true](mailto:amx@trueserve.org)

[Jeff Spire](http://spireintegrated.com/)

[Jorde Vorstenbosch](mailto:jordevorstenbosch@gmail.com)

[Andy Dixon](https://github.com/PsyenceFact)

[Motaz Abuthiab](mailto:moty66@gmail.com)

[Vanti](https://www.vanti.co.uk)

# Contributing

Want to help out? Awesome.

1. Fork it.
2. Make your changes / improvements / bug fixes etc (and ensure that you adhere to the [project style guide](https://github.com/KimBurgess/NetLinx-Common-Libraries/wiki/Code-Format-and-Commenting) whilst doing so).
3. Submit a pull request.
4. Win.

# Usage

A convenience include has been provided to simplify usage. Ensure that the libraries are placed within your project's compile path then include prior to utilizing provided functionality within your project code.

    include 'netlinx-common-libraries'

Alternatively, individually specify the library components that you require. Any cross include dependencies within the NetLinx Common Libraries are handled internally.

This project is licensed under the MIT License. Feel free to use it, sell it, modify it, re-distribute - basically whatever the hell you like. See [LICENSE](https://github.com/KimBurgess/NetLinx-Common-Libraries/blob/master/LICENSE) for more info.

M array.axi => array.axi +87 -2
@@ 4,11 4,19 @@ program_name='array'


include 'math'
include 'io'


define_function array_function_deprecated(char old[64], char new[64]) {
	
	println("'Warning, the array function ',old,'() is deprecated',
				', please use ', new, '()'");
	
}

/**
 * Finds the index for an matching entry in an array.
 *
 * Finds the index for any matching device entry in an array.
 * @deprecated
 * @param	item		item to find in the array
 * @param	list		array of items
 *


@@ 16,8 24,85 @@ include 'math'
 */
define_function integer array_index(dev item, dev list[])
{
	array_function_deprecated('array_index', 'array_device_index');
	return array_device_index(item, list);
}

/**
 * Finds the index for any matching device entry in an array.
 *
 * @param	item		item to find in the array
 * @param	list		array of items
 *
 * @return				the index of the matching value, or 0 if no match
 */
define_function integer array_device_index(dev item, dev list[])
{
    stack_var integer i
	
    for (i = 1; i <= max_length_array(list); i++) {
		if (item == list[i]) {
			return i
		}
    }

    return 0
}

/**
 * Finds the index for any matching integer entry in an array.
 *
 * @param	item		item to find in the array
 * @param	list		array of items
 *
 * @return				the index of the matching value, or 0 if no match
 */
define_function integer array_integer_index(integer item, integer list[])
{
    stack_var integer i
	
    for (i = 1; i <= max_length_array(list); i++) {
		if (item == list[i]) {
			return i
		}
    }

    return 0
}

/**
 * Finds the index for any matching double entry in an array.
 *
 * @param	item		item to find in the array
 * @param	list		array of items
 *
 * @return				the index of the matching value, or 0 if no match
 */
define_function integer array_double_index(double item, double list[])
{
    stack_var integer i
	
    for (i = 1; i <= max_length_array(list); i++) {
		if (item == list[i]) {
			return i
		}
    }

    return 0
}

/**
 * Finds the index for any matching string entry in an array.
 *
 * @param	item		item to find in the array
 * @param	list		array of items
 *
 * @return				the index of the matching value, or 0 if no match
 */
define_function integer array_string_index(char item[], char list[][])
{
    stack_var integer i
	
    for (i = 1; i <= max_length_array(list); i++) {
		if (item == list[i]) {
			return i

M debug.axi => debug.axi +27 -0
@@ 40,6 40,33 @@ define_function char[5] debug_get_level_string(char x)
}

/**
 * Gets a numerical debug level based on it's equivalent string representation.
 *
 * @param	x		a character array containing the string to parse
 * @return			a character containing the numerical debug level represented
 *					by the content of x
 */
define_function char debug_get_level_from_string(char x[]) {
	stack_var char lvl;
	
	if (length_string(x) == 1) {
		lvl = atoi(x);
		if (lvl > 4) {
			lvl = DEBUG_OFF;
		}
	}
	
	for (lvl = length_array(DEBUG_LEVEL_STRINGS); lvl; lvl--) {
		if (lower_string(x) == lower_string(DEBUG_LEVEL_STRINGS[lvl])) {
			lvl = lvl - 1;
			break;
		}
	}
	
	return lvl;
}

/**
 * Sets the current system debugging level for controlling debug message
 * verbosity.
 *

M graph.axi => graph.axi +236 -236
@@ 1,237 1,237 @@
program_name='graph'
#if_not_defined __NCL_LIB_GRAPH
#define __NCL_LIB_GRAPH


define_constant
// Bounds for array sizes returned internally. These may be tweaked to optimise
// memory utilization.
GRAPH_MAX_NODES = 128
GRAPH_MAX_EDGES = 1024
GRAPH_MAX_ADJACENT_NODES = 16
GRAPH_MAX_HOPS = 8

// Internal constants
GRAPH_NULL_NODE_ID = 0
GRAPH_MAX_DISTANCE = $FFFF


define_type
structure graph_node {
	integer id
	char settled
	integer distance
	integer previous
}

structure graph_edge {
	integer id
	integer source
	integer destination
	integer weight
}

structure graph {
	integer nextNodeID
	integer nextEdgeID
	graph_node nodes[GRAPH_MAX_NODES]
	graph_edge edges[GRAPH_MAX_EDGES]
}


/**
 * Creates a new node in the passed graph.
 *
 * @param	g		the graph to create the node in
 * @return			an integer containing the node ID
 */
define_function integer graph_create_node(graph g)
{
	stack_var graph_node newNode
	g.nextNodeID++
	newNode.id = g.nextNodeID
	g.nodes[newNode.id] = newNode
	set_length_array(g.nodes, g.nextNodeID + 1)
	return newNode.id
}

/**
 * Defines a directed edge in the passed graph connecting the supplied edges.
 *
 * @param	g				the graph to create the edge in
 * @param	source			the node ID of the edge source
 * @param	desitination	the node ID of the edge desitination
 * @param	weight			the initial weighting to assign to the edge
 * @return					an integer containing the edge ID
 */
define_function integer graph_create_edge(graph g, integer source,
		integer destination, integer weight)
{
	stack_var graph_edge newEdge
	g.nextEdgeID++
	newEdge.id = g.nextEdgeID
	newEdge.source = source
	newEdge.destination = destination
	newEdge.weight = weight
	g.edges[newEdge.id] = newEdge
	set_length_array(g.edges, g.nextEdgeID + 1)
	return newEdge.id
}

/**
 * Finds the closest unsettled node in the passed graph.
 *
 * @param	g		the graph to search
 * @return			an integer containg the closest unsettled node ID
 */
define_function integer graph_get_closest_unsettled_node(graph g) {
	stack_var integer i
	stack_var graph_node n
	stack_var graph_node closest

	closest.distance = GRAPH_MAX_DISTANCE

	for (i = 1; i <= length_array(g.nodes); i++) {
		n = g.nodes[i]
		if (n.settled == false && (n.distance < closest.distance)) {
			closest = n
		}
	}

	return closest.id
}

/**
 * Finds the unsettled neighbours of the passed node.
 *
 * @param	g		the graph to search
 * @param	node	the node ID of the node of interest
 * @return			an array containing the node ID's of adjacent unsettled
 *					nodes
 */
define_function integer[GRAPH_MAX_ADJACENT_NODES] graph_get_neighbors(graph g,
		integer node)
{
	stack_var integer i
	stack_var integer j
	stack_var integer neighbors[GRAPH_MAX_ADJACENT_NODES]
	stack_var graph_edge e

	for (i = length_array(g.edges); i > 0; i--) {
		e = g.edges[i]
		if (e.destination != GRAPH_NULL_NODE_ID) {
			if (e.source == node && g.nodes[e.destination].settled == false) {
				j++
				neighbors[j] = e.destination
			}
		}
	}

	set_length_array(neighbors, j)
	return neighbors
}

/**
 * Finds the distance (/weight) of the edge connecting the passed nodes.
 *
 * @param	g			the graph to search
 * @param	source		the edge source node ID
 * @param	destination	the edge destination node ID
 * @return				the weight of the joining edge
 */
define_function integer graph_get_distance(graph g, integer source,
		integer destination)
{
	stack_var integer i
	stack_var graph_edge e

	for (i = 1; i <= length_array(g.edges); i++) {
		e = g.edges[i]
		if (e.source == source && e.destination == destination) {
			return e.weight
		}
	}

	return GRAPH_MAX_DISTANCE
}

/**
 * Traverse the passed graph and compute all paths from the passed source node.
 *
 * This uses an implementation of Dijkstra's algorithm. After traversal paths
 * are cached within the graph.
 *
 * @param	g		the graph to traverse
 * @param	source	the node ID of the source to calculate paths from
 */
define_function graph_compute_paths(graph g, integer source)
{
	stack_var integer i
	stack_var integer n
	stack_var integer altDist

	for (i = length_array(g.nodes); i > 0; i--) {
		g.nodes[i].settled = false
		g.nodes[i].distance = GRAPH_MAX_DISTANCE
		g.nodes[i].previous = GRAPH_NULL_NODE_ID
	}

	g.nodes[source].distance = 0

	while (true) {
		stack_var integer adjacentNodes[GRAPH_MAX_ADJACENT_NODES]

		n = graph_get_closest_unsettled_node(g)
		if (n == GRAPH_NULL_NODE_ID) break
		if (g.nodes[n].distance == GRAPH_MAX_DISTANCE) break

		g.nodes[n].settled = true

		adjacentNodes = graph_get_neighbors(g, n)

		for (i = 1; i <= length_array(adjacentNodes); i++) {
			if (adjacentNodes[i] == GRAPH_NULL_NODE_ID) break

			altDist = g.nodes[n].distance + graph_get_distance(g, n,
					adjacentNodes[i])
			if (g.nodes[adjacentNodes[i]].distance > altDist) {
				g.nodes[adjacentNodes[i]].distance = altDist
				g.nodes[adjacentNodes[i]].previous = n
				g.nodes[adjacentNodes[i]].settled = false
			}
		}
	}
}

/**
 * Find the optimum path to the passed destination node based on a previously
 * computed graph.
 *
 * @param	g			a previously computed (using graph_compute_paths())
 *						graph
 * @param	destination	the ID of the destination node to find the path to
 * @return				an array containing the nodes that form the optimum path
 *						to the destination node
 */
define_function integer[GRAPH_MAX_HOPS] graph_get_shortest_path(graph g,
		integer destination)
{
	stack_var integer path[GRAPH_MAX_HOPS]
	stack_var integer step
	stack_var integer hop

	step = destination
	hop++
	path[hop] = step

	while (g.nodes[step].previous != GRAPH_NULL_NODE_ID) {
		step = g.nodes[step].previous
		hop++
		path[hop] = step
	}

	set_length_array(path, hop)
	return path
}

program_name='graph'
#if_not_defined __NCL_LIB_GRAPH
#define __NCL_LIB_GRAPH


define_constant
// Bounds for array sizes returned internally. These may be tweaked to optimise
// memory utilization.
GRAPH_MAX_NODES = 128
GRAPH_MAX_EDGES = 1024
GRAPH_MAX_ADJACENT_NODES = 16
GRAPH_MAX_HOPS = 8

// Internal constants
GRAPH_NULL_NODE_ID = 0
GRAPH_MAX_DISTANCE = $FFFF


define_type
structure graph_node {
	integer id
	char settled
	integer distance
	integer previous
}

structure graph_edge {
	integer id
	integer source
	integer destination
	integer weight
}

structure graph {
	integer nextNodeID
	integer nextEdgeID
	graph_node nodes[GRAPH_MAX_NODES]
	graph_edge edges[GRAPH_MAX_EDGES]
}


/**
 * Creates a new node in the passed graph.
 *
 * @param	g		the graph to create the node in
 * @return			an integer containing the node ID
 */
define_function integer graph_create_node(graph g)
{
	stack_var graph_node newNode
	g.nextNodeID++
	newNode.id = g.nextNodeID
	g.nodes[newNode.id] = newNode
	set_length_array(g.nodes, g.nextNodeID + 1)
	return newNode.id
}

/**
 * Defines a directed edge in the passed graph connecting the supplied edges.
 *
 * @param	g				the graph to create the edge in
 * @param	source			the node ID of the edge source
 * @param	desitination	the node ID of the edge desitination
 * @param	weight			the initial weighting to assign to the edge
 * @return					an integer containing the edge ID
 */
define_function integer graph_create_edge(graph g, integer source,
		integer destination, integer weight)
{
	stack_var graph_edge newEdge
	g.nextEdgeID++
	newEdge.id = g.nextEdgeID
	newEdge.source = source
	newEdge.destination = destination
	newEdge.weight = weight
	g.edges[newEdge.id] = newEdge
	set_length_array(g.edges, g.nextEdgeID + 1)
	return newEdge.id
}

/**
 * Finds the closest unsettled node in the passed graph.
 *
 * @param	g		the graph to search
 * @return			an integer containg the closest unsettled node ID
 */
define_function integer graph_get_closest_unsettled_node(graph g) {
	stack_var integer i
	stack_var graph_node n
	stack_var graph_node closest

	closest.distance = GRAPH_MAX_DISTANCE

	for (i = 1; i <= length_array(g.nodes); i++) {
		n = g.nodes[i]
		if (n.settled == false && (n.distance < closest.distance)) {
			closest = n
		}
	}

	return closest.id
}

/**
 * Finds the unsettled neighbours of the passed node.
 *
 * @param	g		the graph to search
 * @param	node	the node ID of the node of interest
 * @return			an array containing the node ID's of adjacent unsettled
 *					nodes
 */
define_function integer[GRAPH_MAX_ADJACENT_NODES] graph_get_neighbors(graph g,
		integer node)
{
	stack_var integer i
	stack_var integer j
	stack_var integer neighbors[GRAPH_MAX_ADJACENT_NODES]
	stack_var graph_edge e

	for (i = length_array(g.edges); i > 0; i--) {
		e = g.edges[i]
		if (e.destination != GRAPH_NULL_NODE_ID) {
			if (e.source == node && g.nodes[e.destination].settled == false) {
				j++
				neighbors[j] = e.destination
			}
		}
	}

	set_length_array(neighbors, j)
	return neighbors
}

/**
 * Finds the distance (/weight) of the edge connecting the passed nodes.
 *
 * @param	g			the graph to search
 * @param	source		the edge source node ID
 * @param	destination	the edge destination node ID
 * @return				the weight of the joining edge
 */
define_function integer graph_get_distance(graph g, integer source,
		integer destination)
{
	stack_var integer i
	stack_var graph_edge e

	for (i = 1; i <= length_array(g.edges); i++) {
		e = g.edges[i]
		if (e.source == source && e.destination == destination) {
			return e.weight
		}
	}

	return GRAPH_MAX_DISTANCE
}

/**
 * Traverse the passed graph and compute all paths from the passed source node.
 *
 * This uses an implementation of Dijkstra's algorithm. After traversal paths
 * are cached within the graph.
 *
 * @param	g		the graph to traverse
 * @param	source	the node ID of the source to calculate paths from
 */
define_function graph_compute_paths(graph g, integer source)
{
	stack_var integer i
	stack_var integer n
	stack_var integer altDist

	for (i = length_array(g.nodes); i > 0; i--) {
		g.nodes[i].settled = false
		g.nodes[i].distance = GRAPH_MAX_DISTANCE
		g.nodes[i].previous = GRAPH_NULL_NODE_ID
	}

	g.nodes[source].distance = 0

	while (true) {
		stack_var integer adjacentNodes[GRAPH_MAX_ADJACENT_NODES]

		n = graph_get_closest_unsettled_node(g)
		if (n == GRAPH_NULL_NODE_ID) break
		if (g.nodes[n].distance == GRAPH_MAX_DISTANCE) break

		g.nodes[n].settled = true

		adjacentNodes = graph_get_neighbors(g, n)

		for (i = 1; i <= length_array(adjacentNodes); i++) {
			if (adjacentNodes[i] == GRAPH_NULL_NODE_ID) break

			altDist = g.nodes[n].distance + graph_get_distance(g, n,
					adjacentNodes[i])
			if (g.nodes[adjacentNodes[i]].distance > altDist) {
				g.nodes[adjacentNodes[i]].distance = altDist
				g.nodes[adjacentNodes[i]].previous = n
				g.nodes[adjacentNodes[i]].settled = false
			}
		}
	}
}

/**
 * Find the optimum path to the passed destination node based on a previously
 * computed graph.
 *
 * @param	g			a previously computed (using graph_compute_paths())
 *						graph
 * @param	destination	the ID of the destination node to find the path to
 * @return				an array containing the nodes that form the optimum path
 *						to the destination node
 */
define_function integer[GRAPH_MAX_HOPS] graph_get_shortest_path(graph g,
		integer destination)
{
	stack_var integer path[GRAPH_MAX_HOPS]
	stack_var integer step
	stack_var integer hop

	step = destination
	hop++
	path[hop] = step

	while (g.nodes[step].previous != GRAPH_NULL_NODE_ID) {
		step = g.nodes[step].previous
		hop++
		path[hop] = step
	}

	set_length_array(path, hop)
	return path
}

#end_if
\ No newline at end of file

R ncl.axi => netlinx-common-libraries.axi +1 -2
@@ 1,4 1,4 @@
program_name='ncl'
program_name='netlinx-common-libraries'

include 'io'
include 'debug'


@@ 7,4 7,3 @@ include 'string'
include 'math'
include 'unixtime'
include 'graph'
include 'tp'
\ No newline at end of file

M string.axi => string.axi +119 -25
@@ 21,12 21,12 @@ STRING_RETURN_SIZE_LIMIT	= 1024	// Maximum string return size
 *
 * @return		An error string.
 */
define_function char[STRING_RETURN_SIZE_LIMIT] string_size_error()
define_function char[1] string_size_error()
{
    // handle, alert, ignore etc here
    println("'Maximum return size too small in String.axi'")

    return 'error'
    return ''
}

/**


@@ 76,7 76,7 @@ define_function char[STRING_RETURN_SIZE_LIMIT] implode(char strings[][],
						care about sanitizing ret[][]
 * @return				the amount of entries stuffed into ret[][]
 */
define_function integer explode(char delim, char a[], char ret[][], 
define_function integer explode(char delim, char a[], char ret[][],
		integer ret_len)
{
	return explode_quoted(delim, a, ret, ret_len, 0)


@@ 413,9 413,10 @@ define_function char[10] string_date_invert(char a[])
    return "comp[2], '/', comp[1], '/', comp[3]"
}


/**
 * Gets the first instance of a string contained within the bounds of two
 * substrings
 * substrings case sensitive
 *
 * @param	a		a string to split
 * @param	left	the character sequence marking the left bound


@@ 425,20 426,48 @@ define_function char[10] string_date_invert(char a[])
define_function char[STRING_RETURN_SIZE_LIMIT] string_get_between(char a[],
		char left[], char right[])
{
    return string_get_between_ex(a, left, right, true)
}

/**
 * Gets the first instance of a string contained within the bounds of two
 * substrings case insensitive
 *
 * @param	a		a string to split, max size is 100 kilobytes
 * @param	left	the character sequence marking the left bound
 * @param	right	the character sequence marking the right bound
 * @return			a string contained within the boundary sequences
 */
define_function char[STRING_RETURN_SIZE_LIMIT] string_ci_get_between(char a[],
		char left[], char right[])
{
	return string_get_between_ex(a, left, right, false)
}

/**
 * Gets the first instance of a string contained within the bounds of two
 * substrings case sensitive
 *
 * @param	a		a string to split
 * @param	left	the character sequence marking the left bound
 * @param	right	the character sequence marking the right bound
 * @param	cs		TRUE for case sensitive search
 * @return			a string contained within the boundary sequences
 */
define_function char[STRING_RETURN_SIZE_LIMIT] string_get_between_ex(char a[],
		char left[], char right[], char cs)
{
    stack_var integer start
    stack_var integer end
    stack_var integer retlen

    start = find_string(a, left, 1)
	if (start) {
		start = start + length_string(left)
	} else {
		return ''
    if(true == cs) {
		start = find_string(a, left, 1) + length_string(left)
		end = find_string(a, right, start)
	}

	end = find_string(a, right, start)
    if (!end) {
		return ''
	else {
		start = find_string(lower_string(a), lower_string(left), 1) + length_string(left)
		end = find_string(lower_string(a), lower_string(right), start)
	}

	retlen = end - start


@@ 451,6 480,7 @@ define_function char[STRING_RETURN_SIZE_LIMIT] string_get_between(char a[],
}



/**
 * Returns a copy of a string with the first alpha character capitalized.
 * Non alpha characters are not modified. Pass a LOWER_STRING()'d string


@@ 567,6 597,46 @@ define_function char[STRING_RETURN_SIZE_LIMIT] string_suffix_to_length(
}

/**
 * Returns a string truncated to a specific length. If the string is less than
 * the length specified the original string is returned. If it is truncated an
 * elipsis will be appended.
 *
 * @param	a		the string to truncate
 * @param	len		the requested length of the string
 * @return			a string truncated to a maximum len characters
 */
define_function char[STRING_RETURN_SIZE_LIMIT] string_truncate(char a[],
		integer len)
{
	return string_truncate_ex(a, "$85", len)
}

/**
 * Returns a string truncated to a specific length. If the string is less than
 * the length specified the original string is return. If it is truncated to
 * contents of value is appended to the truncated string.
 *
 * @param	a		the string to truncate
 * @param	value	the value to suffix on the string if truncated
 * @param	len		the requested length of the string
 * @return			a string truncated to a maximum len characters
 */
define_function char[STRING_RETURN_SIZE_LIMIT] string_truncate_ex(char a[],
		char value[], integer len)
{
	if (len > STRING_RETURN_SIZE_LIMIT ||
			length_string(a) > STRING_RETURN_SIZE_LIMIT) {
		return string_size_error()
	}

	if (length_string(a) > len) {
		return "left_string(a, len - length_string(value)), value"
	} else {
		return a
	}
}

/**
 * Returns the left substring of a string up to the specified number of
 * characters.
 * WARNING: this is a destructive removal - the returned substring will be


@@ 658,7 728,7 @@ define_function integer find_string_multi(char haystack[], char needles[][],
 * @return				'a' with all occurances of 'search' replaced by the
 *						contents of 'replace'
 */
define_function char[STRING_RETURN_SIZE_LIMIT] string_replace(char a[], 
define_function char[STRING_RETURN_SIZE_LIMIT] string_replace(char a[],
		char search[], char replace[])
{
	stack_var integer start


@@ 710,15 780,39 @@ define_function char[STRING_RETURN_SIZE_LIMIT] string_reverse(char a[])
	return ret
}

/**
 * Check is a string starts with another string.
 *
 * @param	a			the string to check
 * @param	search		the substring to search for
 * @return				a boolean, true if 'a' begins with 'search'
 */
define_function char string_starts_with(char a[], char search[])
{
	return left_string(a, length_string(search)) == search;
}

/**
 * Check is a string end with another string.
 *
 * @param	a			the string to check
 * @param	search		the substring to search for
 * @return				a boolean, true if 'a' ends with 'search'
 */
define_function char string_ends_with(char a[], char search[])
{
	return right_string(a, length_string(search)) == search;
}


/**
 * Remove characters from the end of the string.
 * 
 *
 * @param	a			the input string
 * @param	count		the number of characters to remove
 * @return				the contents of 'a' with the characters removed
 */
define_function char[STRING_RETURN_SIZE_LIMIT] strip_chars_right(char a[], 
define_function char[STRING_RETURN_SIZE_LIMIT] strip_chars_right(char a[],
		integer count)
{
	return left_string(a, length_string(a) - count)


@@ 727,12 821,12 @@ define_function char[STRING_RETURN_SIZE_LIMIT] strip_chars_right(char a[],
/**
 * Wrapper method for mid_string to bring inline with other programming
 * languages.
 * 
 *
 * @param	a			the input string
 * @param	start		the start location of the substring
 * @param	count		the number of characters to extract
 */
define_function char[STRING_RETURN_SIZE_LIMIT] substr(char a[], integer start, 
define_function char[STRING_RETURN_SIZE_LIMIT] substr(char a[], integer start,
		integer count)
{
	return mid_string(a, start, count);


@@ 746,26 840,26 @@ define_function char[STRING_RETURN_SIZE_LIMIT] substr(char a[], integer start,
 * @param	start		the start location of the substring
 * @param	end			the end location of the substring
 */
define_function char[STRING_RETURN_SIZE_LIMIT] substring(char a[], 
define_function char[STRING_RETURN_SIZE_LIMIT] substring(char a[],
		integer start, integer end)
{
	return substr(a, start, end-start+1);
}

define_function CHAR[STRING_RETURN_SIZE_LIMIT] pad_leading_chars(char a[], char pad, 
define_function CHAR[STRING_RETURN_SIZE_LIMIT] pad_leading_chars(char a[], char pad,
		integer count)
{
	stack_var char ret[STRING_RETURN_SIZE_LIMIT]
	

	ret = a;
	if (count == 0) {
		return ''						// Emergency Exit
	}
	while(length_string(ret) <  count){ 
		ret = "pad, ret" 
	while(length_string(ret) <  count){
		ret = "pad, ret"
	}
	

	return ret;
}

#end_if
\ No newline at end of file
#end_if

A test/test_array.axi => test/test_array.axi +65 -0
@@ 0,0 1,65 @@
program_name='test_array'

#if_not_defined __NCL_LIB_TEST_ARRAY
#define __NCL_LIB_TEST_ARRAY

#include 'io'
#include 'array'

define_device
	dev1 	= 5001:1:3;
	dev2	= 5001:2:3;
	dev3	= 5001:3:3;
	dev4	= 5001:4:3;
	dev5	= 5001:5:3;
	
	

define_variable
	dev 	devices[5] 	= { dev1, dev2, dev3, dev4, dev5}
	dev 	devS		= 5001:1:3;
	integer integers[5]	= {10,20,30,40,50}
	double 	doubles[5] 	= {10.1,20.2,30.3,40.4,50.5}
	char	strings[5][5]={'foo','bar','baz','qux','quux'}
	
/**
 * Test functionality of some array functions
 */

define_function test_array()
{
	println("':::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::'")
	println("'Running array library test suite.'")
	println("' '")
	println("'Devices = {5001:1:3, 5001:2:3, 5001:3:3, 5001:4:3, 5001:5:3}'")
	println("'Searching index of 5001:1:3'")
	println("itoa(array_device_index(devS, devices))")
	println("'Searching index of 1001:1:0'")
	println("itoa(array_device_index(1001:1:1, devices))")
	println("'Searching index of 5001:1:3 using deprecated function'")
	println("itoa(array_index(devS, devices))")
	
	println("' '")
	println("'integers = {10,20,30,40,50}'")
	println("'Searching index of 20'")
	println("itoa(array_integer_index(20, integers))")
	println("'Searching index of 200'")
	println("itoa(array_integer_index(200, integers))")
	
	println("' '")
	println("'doubles = {10.1,20.2,30.3,40.4,50.5}'")
	println("'Searching index of 30.3'")
	println("itoa(array_double_index(30.3, doubles))")
	println("'Searching index of 30.33'")
	println("itoa(array_double_index(30.33, doubles))")
	
	println("' '")
	println("'strings = {''foo'',''bar'',''baz'',''qux'',''quux''}'")
	println("'Searching index of qux'")
	println("itoa(array_string_index("'qux'", strings))")
	println("'Searching index of fo'")
	println("itoa(array_string_index("'fo'", strings))")
	
}

#end_if
\ No newline at end of file

A test/test_string.axi => test/test_string.axi +54 -0
@@ 0,0 1,54 @@
program_name='test_string'
#if_not_defined __NCL_LIB_TEST_STRING
#define __NCL_LIB_TEST_STRING

include 'string'
include 'io'
include 'test_utils'


define_constant
	long TEST_STRING_ITERATIONS = 100	// number of times to execute each
										// test for speed testing

define_function char test_string_get_between(char a[],
		char left[], char right[]) 
{
	stack_var long i
	
	println("'Running string_get_between(',a, ',',left,', ',right,')'")
	test_timer_start()
	for (i = TEST_STRING_ITERATIONS; i; i--) {
		string_get_between(a, left, right)
	}
	test_timer_stop(TEST_STRING_ITERATIONS)
	return test_end();
}

define_function char test_string_ci_get_between(char a[],
		char left[], char right[])
{
	stack_var long i
	
	println("'Running string_ci_get_between(',a, ',',left,', ',right,')'")
	test_timer_start()
	for (i = TEST_STRING_ITERATIONS; i; i--) {
		string_ci_get_between(a, left, right)
	}
	test_timer_stop(TEST_STRING_ITERATIONS)
	return test_end();
}

/**
 * Test functionality of some string functions
 */
define_function test_string()
{
	println("':::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::'")
	println("'Running string library test suite.'")
	println("' '")
	test_string_get_between('http://site.com/', 'http://', '/');
	test_string_ci_get_between('http://site.com/', 'HTTP://', '/');
}

#end_if
\ No newline at end of file

D tp.axi => tp.axi +0 -143
@@ 1,143 0,0 @@
PROGRAM_NAME='tp'
#if_not_defined __NCL_LIB_TP
#define __NCL_LIB_TP
/******************************************************************************
Include File: tp.axi
Provides common touch panel functionality

Provided by Vanti <www.vanti.co.uk>
******************************************************************************/
#include 'math'

define_function send_tp_command(dev panel, char cmd[]){
	debug_msg(DEBUG_INFO,"'PANEL[',panel.number,':',panel.port,':',panel.system,']: ',cmd");
	send_command panel,"cmd";
}
define_function m_send_tp_command(dev panels[], char cmd[]){
	debug_msg(DEBUG_INFO,"'PANELS: ',cmd");
	send_command panels,"cmd";
}

define_function update_text(dev panel, integer textAddress, char text[]){
	send_tp_command(panel, "'^TXT-',itoa(textAddress),',0,',text");
}
define_function m_updateText(dev panels[], integer textAddress, char text[]){
	m_send_tp_command(panels, "'^TXT-',itoa(textAddress),',0,',text");
}
define_function update_off_text(dev panel, integer textAddress, char text[]){
	send_tp_command(panel, "'^TXT-',itoa(textAddress),',1,',text");
}
define_function update_on_text(dev panel, integer textAddress, char text[]){
	send_tp_command(panel, "'^TXT-',itoa(textAddress),',2,',text");
}

define_function update_fader(dev panel, integer fader, integer lev){
	send_level panel, fader, lev;
}
define_function m_update_fader(dev panels[], integer fader, integer lev){
	send_level panels, fader, lev;
}

define_function show_page(dev panel, char pageName[]){
	send_tp_command(panel,"'PAGE-',pageName");
}

define_function show_popup(dev panel, char popupName[]){
	send_tp_command(panel,"'@PPN-',popupName");
}

define_function hide_popup(dev panel, char popupName[]){
	send_tp_command(panel,"'@PPK-',popupName");
}
define_function hide_all_popups(dev panel){
	send_tp_command(panel,"'@PPX'");
}

define_function disable_button(dev panel, char textAddress[]){
	send_tp_command(panel,"'^ENA-',textAddress,',0'");
}
define_function enable_button(dev panel, char textAddress[]){
	send_tp_command(panel,"'^ENA-',textAddress,',1'");
}

define_function hide_button(dev panel, char textAddress[]){
	send_tp_command(panel,"'^SHO-',textAddress,',0'");
}
define_function m_hide_button(dev panels[], char textAddress[]){
	m_send_tp_command(panels,"'^SHO-',textAddress,',0'");
}
define_function show_button(dev panel, char textAddress[]){
	send_tp_command(panel,"'^SHO-',textAddress,',1'");
}
define_function m_show_button(dev panels[], char textAddress[]){
	m_send_tp_command(panels,"'^SHO-',textAddress,',1'");
}

define_function size_button(dev panel, char textAddress[],integer left,integer top,integer width,integer height){
	send_tp_command(panel,"'^BSP-',textAddress,',',itoa(left),',',itoa(top),',',itoa(left+width),',',itoa(top+height)");
}

define_function set_button_state(dev panel, char textAddress[], integer state){
	send_tp_command(panel,"'^ANI-',textAddress,',',itoa(state),',',itoa(state),',1'");
}

define_function update_button_fill(dev panel, integer btn, integer rgb[3]){
	stack_var char r[2],g[2],b[2];
	r = itohex(rgb[1]);
	if(length_string(r)<2){
		r="'0',r";
	}
	g = itohex(rgb[2]);
	if(length_string(g)<2){
		g="'0',g";
	}
	b = itohex(rgb[3]);
	if(length_string(b)<2){
		b="'0',b";
	}
	send_command panel, "'^BCF-',itoa(btn),',0,#',r,g,b,'FF'";
}

define_function position_fader_handle(dev tp, integer handleChannel, integer value, 
		double max_val, integer faderHeight, integer left_offset, integer top_offset, 
		integer handleWidth, integer handleHeight){
	stack_var double p_val;
	stack_var double scaled_val;
	stack_var integer y;
	
	p_val = ( max_val - value) / max_val;
	scaled_val = round(faderHeight * p_val);
	y = atoi(ftoa(top_offset + scaled_val - handleHeight/2));
	
	size_button(tp, "itoa(handleChannel)", left_offset, y, handleWidth, handleHeight);
}

define_function position_fader_handle_horizontal(dev tp, integer handleChannel, 
		integer value, double max_val, integer faderWidth, integer left_offset, 
		integer top_offset, integer handleWidth, integer handleHeight){
	stack_var double p_val;
	stack_var double scaled_val;
	stack_var integer left;
	
	p_val = value / max_val;
	scaled_val = round(faderWidth * p_val);
	left = atoi(ftoa(left_offset + scaled_val - handleWidth/2));
	
	size_button(tp, "itoa(handleChannel)", left, top_offset, handleWidth, handleHeight);
}

define_function position_fader_handle_inverse(dev tp, integer handleChannel, integer value, 
		double max_val, integer faderHeight, integer left_offset, integer top_offset, 
		integer handleWidth, integer handleHeight){
	stack_var double p_val;
	stack_var double scaled_val;
	stack_var integer y;
	
	p_val = value / max_val;
	scaled_val = round(faderHeight * p_val);
	y = atoi(ftoa(top_offset + scaled_val - handleHeight/2));
	
	size_button(tp, "itoa(handleChannel)", left_offset, y, handleWidth, handleHeight);
}

#end_if
\ No newline at end of file

M unixtime.axi => unixtime.axi +2 -2
@@ 260,7 260,7 @@ define_function char[8] unixtime_to_netlinx_time(slong u)
 */
define_function char[8] unixtime_to_netlinx_date(slong u)
{
	return fmt_date('m-d-y', u)
	return fmt_date('m/d/y', u)
}

/**


@@ 271,7 271,7 @@ define_function char[8] unixtime_to_netlinx_date(slong u)
 */
define_function char[10] unixtime_to_netlinx_ldate(slong u)
{
	return fmt_date('m-d-Y', u)
	return fmt_date('m/d/Y', u)
}

/**