luaarray is a module that can serve as a C array to Lua table bridge. It allows the creation of C arrays of any dimensionality from within Lua and its subsequent treatment as if they were normal Lua tables. You can also use the C api to expose a C array from within the host application as a Lua table so it can be of use in the efficient representation and manipulation of structures which are array-like by nature, such as images, sounds or vectors and matrices.
luaarray works with Lua versions 5.1 and 5.2.
To use the module you must first require it as usual:
array = require "array"
You can explicitly create an array of some type like this:
a = array.doubles {{1, 2},
{3, 4}}
This creates an array encapsulating a C array of type double[2][2] and initializes it with the elements in a give table. The returned array is a userdata with suitable metamethods so that you can use it in almost any way you would use a normal Lua table, including indexing of specific elements and iteration via pairs and ipairs. You can create arrays of other types by using one of the following istead of array.doubles: array.floats, array.ulongs, array.longs, array.uints, array.ints, array.ushorts, array.shorts, array.uchars, array.chars. The table can also be of any dimensionality providing that its dimensions are consistent. You cannot convert this table to an array for example as it can't be directly and unambiguously mapped to a C array:
> a = array.doubles {{1, 2}, {1, 2, 3}}
Inconsistent array structure (subarray at depth 1 should have 2 elements but has 3).
stack traceback:
[C]: in function 'doubles'
stdin:1: in main chunk
[C]: in ?
In order to facilitate interoperation with modules which pass around arrays as binary data enclosed in strings or light userdata you can convert both of these types to arrays as well but in this case you must explicitly specify the dimensions. For example:
> s = "\010\020\030\040"
> a = array.chars (2, 2, s)
> =array.pretty (a)
{
{10, 20},
{30, 40}
}
You can ommit the dimensions in the case of strings. The returned array will have one dimension (it will be a vector) of size equal to the size of the array divided by the size in bytes of the data type.
Finally you can also use the most generic form of the array constructors with tables as well. If the dimensionality of the table doesn't match the dimensions specified you'll get an error so this allows easy type checking. For example:
> t = {1, 2, 3, 4}
> a = array.doubles (2, 2, t)
Initialization from incompatible table (dimensions don't match).
stack traceback:
[C]: in function 'doubles'
stdin:1: in main chunk
[C]: in ?
It is not always necessary to explicitly create an array. Most if not all functions operating on such arrays in this as well as other modules that will be built on this accept a normal table in the place of an array. The table is then implicitly promoted to an array of a suitable type. As an example the divide function which divides two arrays element by element can be used in any of the following ways.
-- Operate on two explicitly defined arrays.
a, b = array.floats {1, 2, 3}, array.floats {2, 2, 2}
c = array.divide (a, b)
-- Use a normal table in place of the second argument. This is
-- implicitly promoted to a float array.
a = array.floats {1, 2, 3}
b = array.divide (a, {2, 2, 2})
-- Specify both arguments as normal tables which are both implicitly
-- promoted to double arrays (since it's the only type guaranteed not
-- to lead to loss of precision.
a = array.divide ({1, 2, 3}, {2, 2, 2})
The result is always an array of the same type as the arguments, so a float array in the first two cases but a double array in the last.
Apart from division you can perform element-by-element
multiplication, addition, substraction, scaling by a constant and
raising to a power with array.multiply, array.add, array.subtract,
array.scale
and array.raise respectively.
The latter two accept the scalar as the second argument
so array.scale (a, 2)
doubles each element of array a
, while
array.raise (a, 2)
squares it. In any case, operating on arrays of incompatible
types results in an error.
You can also copy an array with array.copy
, which
creates an entirely separate copy of the array, allocating new
memory for it. To set all elements of an array to some number use
array.set. For example array.set (a, 0)
initializes
the array a
, which can be of any dimensionality, to all
zero elements.
An array can also be cast to some other dimensionality (in the C
sense) with array.cast
. This creates a new array of
different dimensionality which references the same memory as the
initial array. Changing an element in either will be reflected in
the other. For example:
> a = array.doubles {1, 2, 3, 4}
> b = array.cast (2, 2, a)
> =array.pretty (a)
{1, 2, 3, 4}
> =array.pretty (b)
{
{1, 2},
{3, 4}
}
> b[2][2] = 5
> =array.pretty (a)
{1, 2, 3, 5}
You can also use array.slice to slice an array which creates a copy (so it won't reference the elements of the initial array) of the same dimensionality but with only a subrange within each dimension. The array is specified as the first argument and the lowest and highest index of each dimension should be specified in subsequent arguments. For example:
> a = array.doubles {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
> =array.pretty (a)
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}
> b = array.slice (a, 2, 3, 2, 3)
> =array.pretty (b)
{
{5, 6},
{8, 9}
}
Similarly it is possible to use array.adjust to adjust an array to a predefined dimensionality. If an array is adjusted to a higher dimension it will be padded with extra values which are normally zero. If it needs to be adjusted to a lower dimension some values will be ommited. This may be useful if you need to accept data of a specific dimension but want to be flexible about it.
For instance consider the case where you want to accept colors in RGBA format but you want to be able to accept RGB triplets just as well. In this case one can use:
> input = {1, 0, 0.3}
> color = array.adjust (4, input)
> =array.pretty (color)
{1, 0, 0.3, 0}
Of course this will give all RGB triplets a zero alpha value which may not be desirable. In this case it is possible to specify a default array. If the array to be adjusted needs to be padded with extra values then instead of using zeros the respective values of the default array will be used. For instance:
> color = array.adjust (4, input, {0, 0, 0, 1})
> =array.pretty (color)
{1, 0, 0.3, 1}
It is possible to adjust arrays of any rank but the rank must remain constant. For example one can use the following code to accept any matrix and pad it to 4 rows by 4 columns:
> input = {{0.7071, -0.7071}, {0.7071, 0.7071}} -- A 2x2 rotation matrix
> homogeneous = array.adjust array.adjust (4, 4, input, {{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}})
> =array.pretty (homogeneous)
{
{0.7071, -0.7071, 0, 0},
{0.7071, 0.7071, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}
}
Note that the array returned by array.adjust is a copy of the original. Adjustment is not carried out in place.
This module can be used by a host application which embeds Lua as an extension and configuration language as well allowing efficient exporting of internal C arrays for manipulation by the Lua environment. The API is essentially the same as the Lua API outlined above:
void array_toarray (lua_State *L, int index,
array_Type type, int rank, ...);
void array_toarrayv (lua_State *L, int index,
array_Type type, int rank, int *size);
Push onto the stack an array of the given rank and type initialized by the value at location index in the stack which can be a table, string or light userdata. The rank is the number of dimensions of the array and the length along each dimension is given either as an array of integers (in the case of array_toarrayv or as additional arguments passed after rank. The type can be any of the following constants: ARRAY_TDOUBLE, ARRAY_TFLOAT, ARRAY_TULONG, ARRAY_TLONG, ARRAY_TUINT ARRAY_TINT, ARRAY_TUSHORT, ARRAY_TSHORT, ARRAY_TUCHAR, ARRAY_TCHAR.
array_Array *array_createarrayv (lua_State *L, array_Type type, void *values,
int rank, int *size);
array_Array *array_createarray (lua_State *L, array_Type type, void *values,
int rank, ...);
Push onto the stack a newly created array of the specified rank, type and size and initialize it with the C array (which must be of the same type and size) specified in values. If values is NULL memory is still allocated to hold the elements of the array but it is not initialized to anything. Return a pointer to the newly created array.
void array_copy (lua_State *L, int index);
Push onto the stack a copy of the array at stack location index.
void array_set (lua_State *L, int index, lua_Number c);
Set all elements of the array at stack location index to the number c.
void array_cast (lua_State *L, int index, int rank, ...);
void array_castv (lua_State *L, int index, int rank, int *size);
Push onto the stack an new array which is a cast of the array at location index in the stack. The new array references the same memory as the original array but has different rank and lengths.
void array_slicev (lua_State *L, int index, int *slices);
Push onto the stack an new array which is a slice of the array at location index in the stack. The range of elements along each dimension of the slice is give in the array slices.
void array_adjust (lua_State *L, int index, void *defaults, int rank, ...);
void array_adjustv (lua_State *L, int index, void *defaults, int rank, int *size);
Push onto the stack an new array which is an adjusted version of the array at location index in the stack. The new size of the array is either given in the array size (for or array_adjustv) or as a variable (depending on rank) number of arguments (for or array_adjust). Default values are given in the array defaults which should match the array to be adjusted in rank and type.
The following functions are useful if your C API needs to accepts specific types of arrays. They can be used to both check that the supplied arrays are of a specific rank, type or size and in converting arrays supplied as normal Lua tables to proper array objects of the requested kind. The latter behavior facilitates interchangeability between arrays and Lua tables: the user can specify an array as either kind of value and it will be implicitly converted to the right kind of array so that the C API only has to deal with a specific kind of data type.
array_Array *array_testarray (lua_State *L, int index);
Check whether the value at stack location index is an array or a table that can be converted to an array. Return the array in that case otherwise NULL.
array_Array *array_checkarray (lua_State *L, int index);
This is similar to array_testarray only instead of silently returning NULL if the value is not an array it throws an error.
array_Array *array_checkcompatible (lua_State *L, int index, int what, ...);
array_Array *array_testcompatible (lua_State *L, int index, int what, ...);
These are similar to the previous functions only in addition to checking whether the value at the location index is an array they can also check whether the supplied array has a specific type, rank or size. What to check for is given in the argument what which is a bitwise OR of any of the following constants: ARRAY_TYPE, ARRAY_RANK, ARRAY_SIZE.
If ARRAY_TYPE is specified, an argument of type array_Type should be passed after what to specify the type of the array (or the type to which a Lua table will be converted). If ARRAY_RANK is specified an integer should be passed with the expected rank of the array and similarly if ARRAY_SIZE is specified a number of integers equal to the expected rank of the array must be passed containing the expected size of each dimension of the table.
Note that if size checking is required rank checking must be required as well, that is you can't specify ARRAY_SIZE, without specifying ARRAY_RANK. Also if more than one kind of checking is required the extra arguments must be passed in the order: type, rank, size.
As an example, to check that a value at location i is an array representing a 4x4 table:
array_testcompatible (L, i, ARRAY_RANK | ARRAY_SIZE, 2, 4, 4);
The latest release of luaarray is 0.1, released on April the 12th 2012. You can download the source here.
luaarray is released under the terms and conditions of the MIT/X11 license.