Skip to main content

Variables And Values: Introduction

Contents

Variable Types

Squirrel has seven data types: integer, float, bool, string, array, table and blob. The first three are scalar values, equivalent to JavaScript’s primitives; the others are more complex structures that hold collections of values.

Integers are 32-bit (four-byte) signed whole numbers; floats are 32-bit (four-byte) signed floating-point values; bools are binary true or false for logic operations with the value 1 or 0, respectively. Integers and floats can be used with logical operators; all non-zero values are considered true. Squirrel does not provide unsigned integers.

Strings are immutable sequences of characters. While technically accessed by reference rather than value, strings’ immutability means that that can be effectively treated as scalars.

Arrays and tables are collection objects: respectively, ordered sequences of values, and unordered dictionaries of key-value pairs. They too are access by reference.

Blobs are stores of user-defined binary data, accessed by reference. Both strings and blobs can be also accessed as arrays of, respectively, characters or bytes.

Squirrel is dynamically typed, so the language doesn’t require a variable’s type to be included in its declaration. Indeed it’s impossible to specify a variable’s type other than by assigning the variable a literal or another variable of the required type. There are no ‘bool’, ‘str’, ‘int’, ‘uint’, ‘short’, ‘long’, etc. keywords in Squirrel.

Dynamic typing also means that a variable initialized with one type can be arbitrarily re-assigned with a value of another type. A variable’s type will be automatically promoted according to calculations performed upon it: for instance, a variable holding an integer that is then multiplied by a float will become a float; it will not remain an integer, and so precision will not be lost.

To convert from one type to another, you can use the appropriate Squirrel data type delegate method: tointeger(), tofloat(), tostring(), tochar(). Not all of these methods are available to a given data type; available methods and details of their results are listed in relevant sections of this guide, and in the Squirrel area of the Dev Center.

Squirrel’s typeof keyword will return a lower-case string naming the passed variable’s data type and can be used to test a variable’s type:

local a = getSensorReading();
if (typeof a == "integer") {
    // Convert to float
    a = a.tofloat();
}

Note We’ll discuss the local keyword, as used in the example above, in the Variable Scope section below.

Scalars? References?

If you think of a variable as a box in which a value is placed, then assigning a value to a variable puts that value inside the box. If you assign the integer 42 to a variable called test, for example, then test will actually contain the number 42. Variables that store actual values are called scalars.

This is not the case with reference-accessed data types like strings, arrays, tables and blobs. These complex data types bring together multiple values, and since a variable can essentially hold only one value, you can’t put, say, a table into a variable. Instead, the variable holds not the data itself but a value that tells Squirrel where to find the data — a reference to it, hence the name.

Once consequence of this is that if you assign a given reference to more than one variables, then all of those variables point to the same data. If you change the data by accessing one of these variables, then you will see the the alteration when you access the data through one of the other variables. This is not the case with scalars.

local anArray = ["Electric", "Imp"];
local anotherArray = anArray.

Here, despite their names, anArray and anotherArray both reference the same array (which contains two strings). If you add an item to the array using anotherArray, then anArray will show the change:

// Add a third item to 'anotherArray'...
anotherArray.append("Inc.");

// ...and it is accessible via 'anArray' too!
server.log(anArray[2]);
// Displays 'Inc.'

But with scalars there is no such connection between variables:

local anInt = 42;
local anotherInt = anInt;
anotherInt = anotherInt + 10;
server.log(anotherInt);
// Displays '52'
server.log(anInt);
// Displays '42'

Why is this the case? It's all about efficiency. Because they are simple values, scalars are easy to move around inside the imp’s computer brain so there's no performance to be lost in copying them (as we do above when we assign anInt’s value to anotherInt). Non-scalars are potentially highly complex, so it’s much more efficient to copy not the data itself but just a simple pointer to the data’s location.

Advanced Usage: Strong And Weak References

Squirrel maintains two types of reference: strong and weak. Most of the references you will implicitly make use of by working with arrays, tables, blobs and class instances are strong, so this distinction will not generally affect you. Indeed, so that you can avoid dealing with weak references, Electric Imp Squirrel uses strong references in places where Standard Squirrel uses weak ones. For example, in standard Squirrel, the bindenv() delegate method keeps a weak reference to the bound object, but in Electric Imp Squirrel, it is a strong reference.

Strong references bind the entity that is being referenced to the variable(s) that hold the reference. What this means in practice is that Squirrel will not dispose of the referenced entity until no remaining variable references it. Variables cease to reference an entity if they go out of scope or you assign them a different reference or value. When an entity is no longer referenced by any variables, Squirrel will automatically dispose of it to free the memory it occupies. This process is called garbage collection.

So as long as you can access the variable, you can access the data it is referencing. However, not all references work this way. Squirrel’s weak references do not claim ownership over the entities they reference. In other words, Squirrel can remove the referenced entity even if it is referenced by a variable holding a weak reference to it.

Making use of weak references is a technique available for advanced memory management, and we will briefly touch on them again when we cover classes and instances.

Declaring And Naming Variables

Variables must be declared before they are used. Variables can be declared anywhere in the program and will exist from this point until the end of the code block in which the declaration is made. Global variables are available to all parts of the code. See Variable Scope, below, for more details.

Variable names may not start with a number but may start with an underscore symbol. They may not match a defined Squirrel keyword. Squirrel is case-sensitive: the lower-case and upper-case representations of the same character are considered to be different. For instance, “electricimp”, “ElectricImp” and “electricImp” are all considered different identifiers by Squirrel.

Null

Squirrel defines null to represent an uninitialized, undefined, empty or meaningless value, eg. a reference to a non-existent object. For example, variables are set to null if they are declared but have not yet been assigned a value.

local sensorObject;

if (sensorObject == null) {
    // Object not yet instantiated and initialized, so do it now
    sensorObject = SensorClass();
    sensorObject.init();
}

local reading = sensorObject.getReading();

Assigning Values To Variables

You assign local values to variables with the = symbol (it’s called the assignment operator). This is the case whether you are providing an initial value when the variable is declared, or you are assigning a new value to the variable. As the example in the Null section, above, shows, you can declare a local variable without also assigning it a value.

Global variables (see Variable Scope, below) work slightly differently:

  1. They must be assigned a value when they are declared, even if it is null.
  2. You mark a variable as global upon declaration by using the <- operator. If you subsequently assign a new value to a global variable, use the = symbol.

For example:

// Declare and initialize two globals with the <- operator
globalVariableOne <- 42;
globalVariableTwo <- null;

// Assign a new value to 'globalVariableTwo'
globalVariableTwo = "Forty-two";

Literal Values Number Bases

Squirrel supports base-8 (octal), base-10 (decimal) and base-16 (hexadecimal) values. Literal decimal values are written without a prefix; literal hexadecimal (‘hex’) values with the prefix ‘0x’; octal values are always prefixed with a zero:

local decimalValue = 42;
local hexValue = 0x2A;
local octalValue = 052;

if (decimalValue == hexValue && hexValue == octalValue) server.log("All equal");
// Displays 'All equal'

Do not prefix decimal integer literals with a zero, or Squirrel will treat them as octal values. However, you must prefix decimal float literals with a zero:

local aFloat = 0.01;  // This will work
local bFloat = .02;   // This will fail

Squirrel does not support binary literals — you will need to convert binary values to hex, decimal or octal first. This table will help you do so.

Decimal Binary Hex
0 0000 0x0
1 0001 0x1
2 0010 0x2
3 0011 0x3
4 0100 0x4
5 0101 0x5
6 0110 0x6
7 0111 0x7
8 1000 0x8
9 1001 0x9
10 1010 0xA
11 1011 0xB
12 1100 0xC
13 1101 0xD
14 1110 0xE
15 1111 0xF

The table gives the nibble (four-bit) value for a single hex character. For an eight-bit value, just place the two four-bit values alongside each other. For example, 11111001 (249) splits to 1111 (0xF) and 1001 (0x9), which gives 0xF9 as the 8-bit hex value of the binary number you started with.

Variable Scope

Squirrel enforces variable scope to a given function, class or code block, including the program’s root function, which might be called main() in another language but is unnamed in Squirrel.

Variables that are local to a given block of code must be declared with the keyword local — Squirrel will otherwise assume the variable is higher in scope or a property of the current class, and will throw an exception if the variable has not been declared there. The scope of variables marked local is the rest of the block, ie. everything in the same block after but not including the declaration.

Variables used as for loop counters and which are expected to be local to the loop can be declared within the for statement:

for (local i = 0 ; i < 100 ; i++) {
    // i is local only to code between the braces
}

or

// i is local to the entire code block containing the loop
local i;
for (i = 0 ; i < 100 ; i++) {
    . . .
}

There is no ‘global’ keyword, as there is local. Instead, as we saw above, globals are declared using the <- operator. To find out why that is the case, you’ll need to wait until we look at Squirrel tables in more depth.

Variable Delegation

Every type of variable, including scalars, has an associated delegate object to which certain operations the entity can’t handle itself will be delegated. The delegate to that variable therefore provides a number of methods which may be applied to the variable through its namespace using standard dot notation. For example, integers have the delegate method tostring() which returns the value of the integer as a numeric string:

local value = 45;
local vString = value.tostring() + " degrees Celsius";
server.log(vString);

// Displays '45 degrees Celsius'

Delegates can generally only be applied to variables, not to literals.

As we explore Squirrel’s various data types in later chapters, we’ll also look at the delegate methods that are available to them.

Endian-ness

All imps are little endian, so values are stored in memory in order of lowest byte significance. For example, when an integer is written to memory, the least-significant byte is written first (at address n) through to the most-significant byte (at address n+3).

To help you communicate with systems that a big endian (most-significant byte comes first), Squirrel provides the methods swap2() and swap4() for integer byte reordering, and swapfloat() to provide the same facility for floating-point numbers.

Serialization

The imp API includes a number of methods which transmit data between device and server, and back again. Other API methods cache data in persistent storage. All of these functions involve converting Squirrel entities and data types into streams of bits. Not all Squirrel entities can be serialized, however, so it is important to understand the rules of Squirrel data serialization.

As this is more of an imp issue than a Squirrel one, we’ve covered these rules elsewhere. Please see the guide Squirrel Data Serialization.


Back to the top