Skip to main content

Variable Types: Arrays And Tables

Contents

Introduction

Arrays and tables are Squirrel’s two primary collection data types: that is, they are intended to help you gather and manage multiple, related values. Some of those values may be collections themselves.

Arrays collect values together in a strict order. Elements in the array are therefore accessed by their position within that sequence, called the element’s index.

Tables do not hold the values they collect in any order. Instead, each value is assigned an identifier (unique within the table), called a key, which is used by Squirrel to locate and retrieve the requested value. In Squirrel terminology, each key-value pair is called a slot.

Arrays

Array variables are specified using either the function array() or a literal array within square brackets: [ and ].

local arrayOne = array();                // Array creator function, initially empty
local arrayTwo = [];                     // Array literal, initially empty
local arrayThree = [1, 2, 3, "four"];    // Array literal, four mixed-type elements
local arrayFour = array(10);             // Array creator function, set for ten elements, each null
local arrayFive = array(10, "four");     // Array creator function, set for ten elements, each the string "four"

As some of the examples above show, arrays are not limited to holding values of a single type. They are also mutable, including those created using literals: in other words, they are not fixed and can be subsequently modified. For example, the following is valid Squirrel code:

local numberArray = [1, 2, 3];
numberArray.append(4);

Arrays may be mutable, but extending an array will cause it to be reallocated in memory. So it is more efficient, if you know what the size of the array you require will be, to specify its size at the outset:

local myArray = array(2000);

A value in an array is accessed by specifying its index within square brackets:

local value = anArray[4];

The value need not be a literal, as shown above, but could be a variable holding an integer value, or an expression that resolves to an integer:

local value = anArray[reading >= midPoint ? 1 : 0];

The expression inside the square brackets resolves to 1 if the value of reading is greater than or equal to the value of midPoint; otherwise it resolves to 0. So value will end up taking the value of anArray[1] or anArray[0], depending on the values of reading and midPoint.

Squirrel supports only single-dimensional arrays but it is possible to construct arrays of arrays to simulate multi-dimensional structures. For example, a two-dimensional array might be created as follows:

const SCREEN_HORIZONTAL_PIXELS = 128;
const SCREEN_VERTICAL_PIXELS = 32;

// Make an array SCREEN_VERTICAL_PIXELS long
local displayArray = array(SCREEN_VERTICAL_PIXELS);

// Make each element in that array an array SCREEN_HORIZONTAL_PIXELS long
// and each sub-array's elements to the number 255
for (local i = 0 ; i < SCREEN_VERTICAL_PIXELS ; i++) {
    displayArray[i] = array(SCREEN_HORIZONTAL_PIXELS, 0xFF);
}

This code generates an array of 32 elements, each of which is an array of 128 elements, each of which is initially an integer of value 255 (the second argument in the array() call). Accessing a element of this matrix would require the following code:

local oldPixelValue = displayArray[rowNumber][columnNumber];
displayArray[rowNumber][columnNumber] = newPixelValue;

Arrays are accessed by reference, so adding an array to another collection type, such as a table or another array, or passing it into a function parameter, does not pass a copy of the source array but a reference to the original array.

Squirrel permits the use of whitespace as a separator between array elements. For example, the following code treats the array as containing two items:

server.log([3 4].len());
// Displays '2'

However, adding a sign to the second element causes Squirrel to treat the two elements as one: it calculates the sum and then adds the answer to the array. As such, we do not recommend the use of spaces as array separators: you should always use commas to avoid ambiguity.

server.log([5 -1].len());
// Displays '1' because the array holds one value, 4

Calling typeof on an array returns the string "array".

Iterating Through The Items In An Array

Component elements are accessed through their indices; the first item is always placed at index 0:

local numberStringArray;
numberStringArray[5] = "five";

You can use the foreach keyword to iterate through an array’s elements one by one:

local anArray = [1, 2, 3, 4, 5, 6];
local outputString = "";

foreach (item in anArray) {
    outputString += (item.tostring() + "-");
}

outputString = outputString.slice(0, outputString.len() - 1);
server.log(outputString);
// Displays '1-2-3-4-5-6'

Squirrel allows you to alter the size of the array during these operations. For example:

local anArray = [1, 2, 3, 4, 5, 6, 7];
server.log(anArray.len());
// Displays '7'
foreach (index, item in anArray) {
    // Remove even numbers
    if (item % 2 == 0) anArray.remove(index);
}
server.log(anArray.len());
// Displays '4'

The above also demonstrates a second form of the foreach, one with an index variable that starts at zero and increments by one every pass through the loop. In each pass of the loop, item is the element in anArray at index index.

Array Delegate Methods

Arrays have access to many delegate methods to help with array manipulation (eg. sorting, resizing, clearing, adding and removing elements).

  • append() — See push(), below.
  • push() — These two methods are identical: they both add the supplied item to the end of the target array, increasing the array’s size by one item. For example:

    local anArray = [1, 2, 3, 4];
    anArray.append(5);
    server.log(anArray[anArray.len() - 1]);
    // Displays '5'
    
  • apply() — Passes each item in the target array into the specified function, which returns a value that then replaces the original item. If you don’t want to modify the target array this way, use the map() method instead, which returns the processed values as a new array. For example:

    local anArray = [true, true, false, false, true];
    anArray.apply(function(value){
        return !value;
    });
    // anArray now equals [false, false, true, true, false]
    
  • clear() — Removes all if the items from the target array leaving it empty. For example:

    local anArray = [true, true, false, false, true];
    anArray.clear();
    server.log(anArray.len());
    // Displays '0'
    
  • extend() — Adds all of the items in the supplied array one by one to the target array. Compare this to using append(), which would set the supplied array as the target’s last item. For example:

    local anArray = [1, 2, 3, 4];
    local anotherArray = [5, 6, 7, 8];
    anArray.extend(anotherArray)
    server.log(anArray.len());
    // Displays '8'
    
  • filter() — Applies the supplied filter function to each of the target array’s items, storing the results in a new array which it returns. The passed function must take two arguments: the index of the item being passed to it, and the item itself. For example:

    local sourceArray = [10, 21, 30, 43];
    local resultArray = sourceArray.filter(function(index, value){
        // Return array items that are multiples of ten
        return (value % 10 == 0);
    });
    
    foreach (item in resultArray) server.log(item);
    // Displays '10' and '30'
    
  • find() — Finds the specified value within the target array and returns its index, or null if the item isn’t present in the array. If the item is present multiple times, only the index of the first occurrence will be returned. For example:

    local anArray = [1, 2, 3, 4, "five", "six", "seven", "five"];
    local r = anArray.find("five");
    server.log("The string \'five\' is " + (r == null ? "not present" : ("present at index " + r)));
    // Displays "The string 'five' is present at index 4."
     r = anArray.find("eight");
    server.log("The string \'eight\' is " + (r == null ? "not present" : ("present at index " + r)));
    // Displays "The string 'eight' is not present."
    
  • insert() — Adds the specified item to the target array at the specified index. If the index passed is greater than the number of objects in the array, Squirrel will throw an ‘idx out of range’ exception. For example:

    local anArray = [1, 2, 3, "four", "five", true];
    // Insert an item into the array at index 2, ie. the third item
    anArray.insert(2, 2.5);
    // anArray is now [1, 2, 2.5, 3,  "four", "five", true]
    
  • len() — Returns the number of items in the array. See clear(), above, for an example.

  • map() — Applies a function to all of the target array’s items. The supplied function receives each item in turn and returns a value which is placed a new array which the method returns. Contrast this with apply() which doesn’t return a new array but modifies the target array. For example:

    local anArray = [true, true, false, false, true];
    local newArray = anArray.apply(function(value){
        return !value;
    });
    // newArray equals [false, false, true, true, false]; anArray is unchanged
    
  • pop() — Removes the last item in the target array and returns it. For example:

    local anArray = [1, 2, 3, "four", "five", true];
    server.log(anArray.len());
    // Displays '6'
    server.log(anArray.pop());
    // Displays 'true'
    server.log(anArray.len());
    // Displays '5'
    
  • reduce() — Reduces the target array to a single value by applying the specified function to the array’s items one by one. The reduction function must include two parameters into which are passed, respectively, the result returned by its previous iteration and the next item in the array. If the array contains just a single item, that item will be the value that is returned. If the target contains no items, reduce() will return null. For example:

    local anArray = ["The", "answer", "is", "42"];
    local reduction = sourceArray.reduce(function(previousValue, currentValue){
        // Combine all the values in the array
        return (previousValue + " " + currentValue);
    });
    server.log(reduction);
    // Displays 'The answer is 42'
    
  • remove() — Removes the item at the specified index from the target array and returns it. If the index is beyond the length of the array, Squirrel throws a ‘idx out of range’ exception. For example:

    local anArray = [1, 2, 3, "four", "five", true];
    server.log(anArray.len());
    // Displays '6'
    server.log(anArray.remove(3));
    // Displays 'four'
    server.log(anArray.len());
    // Displays '5'
    
  • resize() — Changes size of the target array to the specified size. Optionally, you can set a value to use if items need to be created; otherwise each added item will be null. If the resized array is smaller than the array’s initial size, items will be lost. For example:

    local anArray = [1, 2, 3];
    server.log(anArray.len());
    // Displays '3'
    anArray.resize(6, "42");
    server.log(anArray.len());
    // Displays '6'
    server.log(anArray[3]);
    // Displays '42'
    anArray.resize(2);
    foreach (item in anArray) server.log(item);
    // Displays '1' and '2'
    
  • reverse() — Reverses the order of the elements in the target array. For example:

    local anArray = [1, 2, 3];
    anArray.reverse();
    // anArray is now [3, 2, 1];
    
  • slice() — Creates a new sub-array from the target array using the specified start and end indices. The item at the end index is not included. If the provided end index lies beyond the end of the source array, a ‘slice out of range’ exception will be thrown. If no second argument is provided, slice() copies into the new array all of the items up to and including the last item in the source array. For example:

    local anArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    local newArray = anArray.slice(2, 6);
    // newArray equals [3, 4, 5, 6]
    newArray = anArray.slice(5);
    // newArray now equals [6, 7, 8, 9]
    newArray = anArray.slice(2, 20);
    // Throws 'slice out of range' error
    
  • sort() — Re-orders the items within the target array using the specified sort function. This function takes two arguments: two values which will be compared in some way by the function. It should return the value -1 if the first value should be placed before the second, or 1 if it should follow the second value. Return 0 if the two values are equivalent. If no function is supplied, sort() does a simple sort on numeric values and strings. For example:

    local wlans = imp.scanwifinetworks();
    
    // Sort the list of wireless networks by channel number
    anArray.sort(function sortFunction(first, second) {
        local a = first.channel;
        local b = second.channel;
        if (a > b) return 1;
        if (a < b) return -1;
        return 0;
    });
    
  • top() — Returns the last item in the target array but does not remove it (compare to pop()). For example:

    local anArray = [1, 2, 3, "four", "five", true];
    server.log(anArray.len());
    // Displays '6'
    server.log(anArray.top());
    // Displays 'true'
    server.log(anArray.len());
    // Displays '6'
    

Tables

Tables are collections of key-value pairs, and are specified with braces: { and }. They can be nested, and there is no limitation other than memory on the extent to which a table can be nested, nor how many keys it can contain. Tables can only be created with a table literal, either an empty table:

local myTable = {};               // Create an empty table
myTable.firstKey <- "Max Normal"; // Create a slot with the key 'firstKey' and the assigned value
myTable.firstKey = 42;            // Assign a new value to 'firstKey'

or a full or partial literal:

local myTable = {firstKey = "Max Normal", secondKey = 42, thirdKey = true};

Table literals may also be written using JSON syntax:

local myTable =  { "firstKey":  "Max Normal",
                   "secondKey": 42,
                   "thirdKey":  true };

Slots have to be created before they can be assigned a value. This is achieved using Squirrel’s ‘new slot’ operator, <-, which performs both the slot creation and assignment tasks. You can see the use of the slot operator in the first of the three table-creation examples above. Once a slot has been created, it can be reassigned using the assignment operator, =. Attempting to create a slot with the assignment operator will fail:

local myTable = {};
myTable.firstKey = "Max Normal";
// Displays 'ERROR: the index 'firstKey' does not exist'

Slots can be removed with the delete keyword, which returns the value of the slot that has been removed:

local removedValue = delete myTable.firstKey;

Tables are accessed by reference, and adding a table reference to another entity, such as an array or another table, or passing it into a function parameter, does not add a copy of the table but a reference to it. For example, the following code:

local arrayOne = [];
local arrayTwo = [];
local tableOne = {};

arrayOne.push(tableOne);
arrayTwo.push(tableOne);

causes the two arrays arrayOne and arrayTwo to each contain a reference to a single, shared table, tableOne. If tableOne is subsequently changed in any way, the change will be reflected when you access it through either arrayOne or arrayTwo:

tableOne.newKey <- "new";
if (arrayOne[0].newKey == "new") server.log("TableOne changed");
// Displays 'TableOne changed'

Calling typeof on a table returns the string "table".

Key Types

All of the examples above use strings as keys, but Squirrel allows you to use other data types as keys too. Again, the slot creation/assignment operator is required the first time the slot is used, but this time the non-string key has to be placed within square brackets:

truthTable[true] <- "YES";
truthTable[false] <- "NO";

The non-string key’s value is read back or modified using the same bracket syntax:

server.log(conversionTable[2]);
conversionTable[2] = 370;

Note This syntax allows tables to be used in place of arrays to conserve memory. An array able to hold 2000 items takes up sufficient memory for 2000 items, even if at any one moment it only contains a few elements. An equivalent table takes up only as much memory as it has elements. However, arrays are smaller and faster than tables. If you have a lot of data, the difference can be enormous: one agent whose central data structure was a 3000-entry array of three-entry tables kept running out of memory, because the structure took up 720KB. Switching it around, to a three-entry table of 3000-entry arrays, reduced its memory usage to just 150KB.

String keys may also be applied using this syntax, allowing variables to be substituted for keys — a string variable contains the key name:

local keyString = "firstKey";
myTable = {};
myTable.firstKey <- "Spikes";
myTable[keyString] = "Harvey";
server.log(myTable.firstKey);  // Displays "Harvey"

Iterating Through The Keys And Values In A Table

You can iterate through a table’s slots using a foreach loop, which separates each slot’s key and value into separate variables:

foreach (key, value in myTable) {
    server.log("Key: " + key + " has the value: " + value);
}

or just the keys:

foreach (key in myTable) {
    server.log("The table contains the key: " + key);
}

Note When enumerating a table with foreach, do not add or remove keys within the foreach loop. If you want to do so for the purpose of finding keys to delete, for example, you should record references to those elements (eg. store the keys in an array) and remove them after exiting the foreach loop.

There is no direct means to locate a nested key if you do not know how deep within the structure it is; you will need to iterate through the table’s keys, values and nested keys. If you do know the key’s location, you can access it directly with dot syntax:

local info = imp.net.info();
local isConnected = info.interface[info.active].wifi.state.connected;

Checking Whether A Table Has A Certain Key

The in keyword may be used to determine whether a table contains a specified key:

if (aKey in myTable) {
    // Perform conditional code...
}

Tables And Delegation

Table objects, like all other Squirrel data types, have a delegate. This is an auxiliary table which provides a number of generic functions for manipulating values and, in the case of tables, keys. Delegates provide data type conversion methods to integers, floats and bools, for instance, and item and character manipulation methods to arrays and strings.

Unlike other data types, however, tables have no delegate by default. However, you can set one (always another table) using the target table’s setdelegate() method. Tables can be accessed with or without recourse to their assigned delegate. This is because the table is Squirrel’s fundamental non-scalar data type: classes, their instance objects are implemented using tables. Object methods and properties are essentially functions and variables held in table slots.

Table Delegate Methods

  • clear() — Removes all of the items from a table. For example:

    // User want to reset the app settings table
    if ("clearprefs" in request.query) {
        // Remove all settings table slots
        appSettings.clear();
    
        // Add default settings table slots
        applyDefaultSettings();
    }
    
  • getdelegate() — Returns the table’s delegate, if one has been set; if not, it returns null. For an example, see setdelegate(), below.

  • setdelegate() — Sets the table’s delegate. For example:

    local mt = {};
    local md = {};
    
    // Make MD the delegate of MT
    mt.setdelegate(md);
    
    if (mt.getdelegate() == md) server.log("MD is the delegate of MT");
    // Displays "MD is the delegate of MT"
    
  • len() — Returns the number of slots within the target table. For example:

    local storedSensorReadings = server.load();
    local numberOfReadings = storedSensorReadings.len();
    
  • rawdelete() — See rawset(), below.

  • rawget() — See rawset(), below.

  • rawin() — See rawset(), below.

  • rawset() — Perform tasks (respectively, delete a slot, get a slot’s value, test from the presence of a key, set a value) on the target table, ignoring the table’s delegate if it has one. For example:

    local mt = {};
    local md = {};
    
    // Give MD - but *not* MD - a name slot with a function as a value
    md.name <- function(){
        server.log(this.title);
    };
    
    // Give both tables a title slot with strings as values
    mt.title <- "My Table";
    md.title <- "My Delegate";
    
    // Make MD the delegate of MT
    mt.setdelegate(md);
    
    // MT does not have a slot 'name', so Squirrel will try its delegate
    // when the we ask it to access 'name' in MT:
    mt.name();
    // Displays "My Table"
    
    // Give MT its own name slot and function
    mt.rawset("name", function(){
        server.log("Boo!");
    });
    
    local f = mt.rawget("name");
    f();
    // Displays "Boo!"
    
    md.name();
    // Displays "My Delegate"
    

Global Variables Reconsidered

When we discussed global variables earlier, we noted that they are declared not with a keyword (unlike local variables) but with the <- operator. You should now be able to see why.

The table is a fundamental Squirrel structure: the language uses tables as the basis for all of its objects. Every object method and property is a slot in a table. Squirrel also uses tables for storing environment data and... global variables.

When you declare a global variable, you are essentially adding a new key to Squirrel’s root table, a base-level table it maintains for each application.

Global variables are typically defined in the program’s root context, but they may be defined anywhere within a program, using the syntax described above. One exception is when the global variable is defined within a class: in this case, the scope operator, ::, is used to mark the variable explicitly as global:

::aGlobalVariable <- 42.0001;

Back to the top