Skip to main content

Squirrel Programming Guide

How to develop software for the Electric Imp Platform

About this Guide

Electric Imp’s ‘Squirrel Programming Guide’ provides software developers new to the Squirrel language with the information they need to begin creating the applications that will power their connected products based on the Electric Imp Platform. The Guide assumes some familiarity with languages like C, C++ and JavaScript with which Squirrel shares its syntax, and with the fundamentals of computer programming in general. It is intended to help experienced programmers and amateur coders to quickly add Squirrel to the list of languages in which they are already fluent. It is not intended as an introduction to programming in general, but where appropriate it includes notes for new programmers to help them through some of the trickier concepts.

Contents

Introduction to Squirrel

If you’ve programmed before, Squirrel is not a hard language to adopt. It ultimately derives from C, so it has a structure that will be familiar to anyone coding with today’s most commonly used programming languages. It is also object-oriented, so its concepts will not be alien to programmers who work with C++, C#, Objective C, Java or Swift. Squirrel’s use of asynchronous functions (‘callbacks’) will be very familiar to JavaScript programmers.

Similarities between Squirrel and other languages include: the availability of all of the standard mathematical, logical and bitwise operators, plus ?:, the ternary operator. Single-line comments are marked with the familiar // token, or with /* and */ to, respectively, begin and end blocks of multi-line comments. Statements may be completed with a semi-colon, but this is not required by Squirrel. However, we recommend the use of semi-colons nonetheless to avoid ambiguity in certain circumstances, as discussed in our code style guide.

Braces — { and } — are used to mark out blocks of code in Squirrel. Variables declared within a block are scoped to that block. Functions can be defined with input parameters and a return value.

Squirrel also supports standard flow-control structures: if… elseif… else; for; while; do… while; and switch… case. The slightly less commonplace foreach loop mechanism is provided too; see the Program Control section below if it’s unfamiliar to you.

However, Squirrel does expose some standard functionality in an idiosyncratic way, such as collections of key-value pairs, which Squirrel calls ‘tables’, similar to JavaScript ‘objects’ and likewise declared and written as literals using braces and JSON formatting. Squirrel also provides features that are unique to the language. This document is therefore primarily concerned with the points at which Squirrel diverges from other languages you may be familiar with. How distant it is from your own experience will depend on which particular language or languages you use.

You should also be aware that this document covers Squirrel as it is specifically implemented on the Electric Imp Platform. Electric Imp’s implementation of the language is a slightly modified form of Squirrel 3.0.4. Other implementations, including the language’s standard version, many include features absent here, and vice versa. If you have experience working with Squirrel on other platforms, please see ‘Electric Imp Squirrel and Standard Squirrel’.

We’re always keen to hear about specific gotchas and points of similarity we may have missed, so please tell us about any you find. Visit the Electric Imp Forum to inform us and your fellow Squirrel users (registration required).
 

Variables and Values

Variable Types

Variables must be declared before they are used. Local 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. However, Squirrel is dynamically typed, so the language doesn’t require a variable’s type to be included in the declaration (but see ‘Variable Scope’, below).

Dynamic typing 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 stay as an integer, and so precision will not be lost.

Squirrel has seven data types: integer, float, bool, string, array, table and blob. The first three are scalar values, equivalent to JavaScript’s ‘primitives’. Integers are 32-bit signed whole numbers; floats are 32-bit 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.

Arrays and tables are collection objects: respectively, ordered sequences of values, and unordered key-value pairs. Blobs are stores of user-defined binary data. Strings are collections of characters. Both strings and blobs can be accessed using as arrays of characters or bytes.

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") a = a * 1.0;

Null

Squirrel defines null to represent an uninitialized, undefined, empty or meaningless value, eg. a reference to a non-existent object:

if (sensorObject == null) {
  sensorObject = SensorClass();
  sensorObject.init();
}

local reading = sensorObject.getReading();

Naming Variables

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.

Incidentally, numeric literals can’t start with a decimal point:

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

Every type of variable, including scalars, has an associated object which acts as a ‘delegate’ to that variable and provides a number of methods which may be applied to the variable through its namespace using standard dot notation. For example, integers have a delegate method tostring() which returns the value as a numeric string:

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

// Displays '45 degrees Celsius'

Delegates can only be applied to variables, not to literals. For more information on delegate methods, see Delegation, below.

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.

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 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 literals with a zero, or Squirrel will treat them as octal values.

Squirrel does not support binary literals — you will need to convert binary values to hex

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.

Characters

Squirrel supports characters as integer literals. The declared variable takes the Ascii code of the character literal, which must be placed within single quote marks. For example:

local aValue = '*';
server.log(aValue);

// Displays '42'

Variable Scope

Squirrel enforces variable scope, either global or local to a given function or class, including the program’s root function, which might be called main() in another language but is unnamed in Squirrel. Local variables must be declared with the keyword local — Squirrel will otherwise assume the variable is higher in scope or a property of the current class. The scope of variables marked local is the rest of the block, ie. everything in the same block after but not including the declaration.

Index variables used with for loops and which are expected to be local to the loop can be declared with in the for statement:

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

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

Global variables are discussed in the Global Variables section, below.

Note for newcomers to programming: Scope

Variables are essentially locations in an imp’s memory used for to store temporary data. Each variable’s memory is allocated when the variable is ‘declared’, ie. the first time it is encountered in a running program. To help ensure that memory is used efficiently, programming languages impose ‘scope’ on variables: the part of a program for which the variable exists. That can be anything from the entire program (these are called ‘global variables’) or local to just one small block of code. For example:

for (local i = 0 ; i < 10 ; i++) {
  // Perform some work
}

The above code is a classic for loop: it runs over and over again; every time the loop begins again a test is made and if the test criterion is met, the program continues past the loop. Here the test is that the value if the variable i is 10 or higher. i changes every time the code loops: 1 is added to the current value. This is set in the for statement’s third term; the first sets the initial value of i. It also marks i as local to this unit of code. Once the program has finished looping and moved on, i is no longer required and its memory can be freed for other uses.

What that means in practice is that you can’t access this i outside of the loop. You can redeclare i later on in your program, but it won’t be the same as this one. If you need to keep this value of i for some reason, you can place its value in a variable with a wider scope: variables declared outside a block of code can be accessed from within that block, but not vice versa. The following example demonstrates this:

// Declare 'a' to be local to the program as a whole and to any
// code blocks nested within it (the for... and if... blocks)
local a = 0;

// Establish a for loop which declares 'i' to be local
// to that loop and any code blocks nested within it
for (local i = 0 ; i < 10 ; i++) {
  if (i = 5) {
    a = i;

    // Declare 'b' to be local only to the if... block
    local b = i;
  }

  // The following line will display an error because 'b' is local to the if... block,
  // but not to the outer for... block
  server.log(b);
}

// The following line will work and display '5' in the log
// This is because 'a' is local to the outermost block, the program itself
server.log(a);

// The following line will display an error because 'i' is local to the for... block,
// but not the outermost block, the program itself, in which this line is located
server.log(i);

Strings

Strings are immutable sequences of alphanumeric symbols which may include any of the standard escape characters — though it is a quirk of Squirrel’s that the hexadecimal digit escape code, \x, takes up to four digits not two. Unlike C strings, Squirrel strings are not null terminated — Squirrel stores their length — and may contain null bytes.

Squirrel strings may be treated as an array of integers, each the Ascii value of a character within the string. This allows fast access to numerical data returned by imp API methods via strings — bytes read via I²C, for example.

// Read one byte value via I2C; returns a string
readString = i2c.read(i2cAddress, i2cSubAddress, 2);

// Index 0 of the string, ie. the first character, gives the most significant byte
// Index 1 of the string, ie. character two, gives the least significant byte
// Put them together
local sensorValue = readString[0] << 8 + readString[1];

Verbatim strings can be assigned by adding @ before Squirrel’s standard string delimiter, ". Verbatim strings are intended to be displayed literally: they are printed exactly as they appear between the delimiters, including line breaks, tabs and such, for which escape characters are not needed. There is one exception: the "" escape sequence, to represent double quote marks, for which ' can also be used.

const html = @"<!DOCTYPE html>
<html>
  <head>
    <meta charset=""utf8"">
    <title>Send some text to an LCD</title>
    <script type=""text/javascript"" src=""https://code.jquery.com/jquery.js""></script>
    <script type=""text/javascript"" src=""https://www.google.com/jsapi""></script>
  </head>
  <body>
    <form name='textinput' action='' method='POST'>
    <p><input type='text' id='textbox' placeholder='Enter text to be displayed!'></p>
    <p><button class='btn btn-inverse' onclick='sendText()'>Send</button></p>
    </form>
  </body>
</html>";

Indexing a string with Electric Imp Squirrel always produces character values in the range 0-255, ie. unsigned. By contrast, Standard Squirrel exposes the underlying C char type, so is signed on some platforms.

Strings are technically reference-type variables, but because they’re also immutable, it is not possible to write code that can tell that strings behave differently from scalars.

Arrays

Array variables are specified using square brackets: [ and ]. Component elements are accessed through their indices; the first item is always placed at index 0.

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

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;

local displayArray = array(SCREEN_VERTICAL_PIXELS);

for (local i = 0 ; i < SCREEN_VERTICAL_PIXELS ; i++) {
  displayArray[i] = array(SCREEN_HORIZONTAL_PIXELS, 0xFF);
}

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

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

See Program Control for examples of using the foreach keyword to iterate through an array’s elements.

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

local myArray = array(2000);

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. As such, we do not recommend the use of spaces as array separators: you should always use commas to avoid ambiguity.

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

Tables

Tables are collections of key-value pairs, and are specified with braces: { and }. Tables can be nested. One key-value pair is called a ‘slot’, and 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. Once a slot has been created, it can be reassigned using the assignment operator, =:

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'

Tables may also be populated with the following syntax:

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

Tables may also be populated using JSON syntax:

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

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

local removedValue = delete myTable.firstKey;

These examples 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 is placed within square brackets:

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

conversionTable[2] = 370;

This 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.

See Program Control for examples of using the foreach keyword to iterate through a table’s keys and their values.

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"

Global Variables

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.

There is no global keyword, as there is local. Instead, globals are simply slots in the root table. The slot’s key is the variable’s name, so globals must be initially assigned according to standard Squirrel table syntax:

// Create a global and assign an initial value
aGlobalVariable <- 42.0;

// Assign a new value to the global
aGlobalVariable = 999;

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;

Blobs

A blob is a block of arbitrary binary data represented by an object instanced from the Blob class:

local myBlob = blob();

Squirrel treats blobs like files. Each blob maintains a read/write pointer, and the class offers writeblob() and readblob() methods to add data to the blob and to view its contents. Individual data types can be written into the blob, or read from it, with the writen() and readn() methods. These take a string parameter which identifies the type of data been added or read:

myBlob.writen(myFloatVariable, 'f');

The type identification strings are as follows:

ID Type
'c' 8-bit signed integer
'b' 8-bit unsigned integer
's' 16-bit signed integer
'w' 16-bit unsigned integer
'i' 32-bit signed integer
'f' 32-bit float

Because blob data may have originated in from outside the imp environment, such as a sensor device, blobs support a greater range of data types than Squirrel itself provides the programmer. Data types supported by blobs include 8-, 16- and 32-bit signed and unsigned integers; and 32-bit floats. The blob converts these types to and from Squirrel types automatically.

All imps are little endian, so multi-byte values will be written into a blob, or read from it in order of least byte significance. For example, if a 16-bit unsigned integer is written into a blob (the 'w' option, above), the least-significant byte is written first (at blob pointer location n) and followed by the most-significant byte (at blob pointer location n+1).

Blobs can be implicitly coerced to type string or explicitly converted: blob.tostring(). This is not a feature of standard Squirrel.

Blobs can be accessed byte by byte by treating them as an array:

local firstByte = myBlob[0];

Blobs are mutable, and adding data to them will cause them to expand. However, this causes Squirrel to reallocate them in memory every time this takes place. It is more efficient, provided you know how much space you require, to allocate the blob’s size (in bytes) when it is created:

// Allocate 1KB
local myBlob = blob(1024);

See the Squirrel Language Reference for more information on blob methods.

Constants and Enumerations

Constant values are defined using the const keyword:

const VON_KLITZING_CONSTANT = 25812.8074434;

Enumerations are generated using the enum keyword and produce a sequence of constants with the enumerated value. The structure’s name is followed by its members in braces:

enum list {
  first,
  second,
  third
};

server.log(list.third);  // Displays '2'

The first value is zero. Like C, the first item on the list may be assigned an index value from which the other constants will be counted.

Constants and enums are evaluated when the Squirrel is compiled into the bytecode which is run in the device and agent virtual machines. As such, the constant does not exist until the compiler reaches the declaration. From this point, any subsequent code can reference the constant, but code prior to the definition will not be able to access the value as it has not at that time been resolved by the compiler. For example, this code:

function displayValue() {
  local a = MY_CONSTANT;
  server.log(a);
}

const MY_CONSTANT = 42;

displayValue();

will fail with the error "ERROR: the index 'MY_CONSTANT' does not exist" because the reference to MY_CONSTANT within the function declaration was left untouched by the compiler — it didn’t yet know that MY_CONSTANT is a constant.

The following code will run successfully because the constant declaration comes first, allowing the compiler to correctly resolve the statement within the subsequent function declaration:

const MY_CONSTANT = 42;

function displayValue() {
  local a = MY_CONSTANT;
  server.log(a);
}

displayValue();

This circumstance is particularly relevant when declaring classes. Constants that will be used in class instances must be declared before the class is itself declared:

const CONSTANT_ONE = "Forty-two";
const CONSTANT_TWO = 42;

class Universe {

  constructor(answer = CONSTANT_TWO) {
    server.log(answer);
  }
}

Memory Management

Squirrel incorporates an automatic reference-counting memory manager, so you generally don’t need to allocate memory for variables and data structures yourself, or deallocate that memory when it is no longer required. If you’re new to programming, or just to object-oriented coding, you can read more about references and reference counting in the Objects and Classes section.

In some instances, it may be useful to force Squirrel to allocate specific amounts of memory for an entity, for instance when instantiating a blob object. These cases are covered in this document under the appropriate sections.

Functions

Functions are declared using the function keyword, followed by a name, input parameters in brackets and then the function code block in braces. Parameter and return data types do not need to be provided. Function names may not start with a numeral. Defining a function essentially adds a method to the current context object, be it root or a class instance.

Functions are first-class objects and may be stored in tables and arrays, and passed as parameters in other functions. A function may also be returned by another function; Squirrel is essentially passing references in these cases. Functions can’t be serialized — see the Developer Guide ‘Data Serialization in Squirrel’.

Even if a function lacks parameters, a statement in which it is called must still use brackets:

local area = areaOfStandardCircle();

Default Parameter Values

Function parameters may be supplied with default values, to be used if no parameter value is provided in the function call:

function areaOfCircle(radius = 10) {
  return (PI * radius * radius);
}

server.log(areaOfCircle(20));   // Displays "1256.48"
server.log(areaOfCircle());     // Displays "314.12"

It’s important to note that the expression given for a default parameter is evaluated once, when Squirrel first executes the program, and the same value is assigned to the parameter every time it’s needed. This means that if the value of the expression is a reference variable type, such as a table or an array, a reference to the same table is passed every time. As such, default parameters are primarily for scalar variable types — Booleans, integers, floats and strings.

For example, the following function sets a default table containing two empty tables:

function aFunction(data = { "readings": {}, "timestamps: {} }) {
  . . .
}

Every time the function is called without a parameter being passed in, a new data table is created that is local to the function. However, readings and timestamps are evaluated once, and so each new data table contains references to the same readings and timestamps tables.

The way around this is to re-code the function without the expression that establishes the two subsidiary tables, and conditionally initialize them within the function:

function aFunction(data = null) {
  if (data == null) {
    data = {};
    data.readings <- {};
    data.timestamps <- {};
  }

  . . .
}

Optional parameters may be added by placing three dots as an item at the end of the parameter list. Passed values are then accessible through an implicit array parameter, vargv. Named parameters, which should be placed before the dots, are accessed in the usual way.

function areaOfCircle(lineColor, ...) {
  local circleColor = lineColor;
  local circleInnerRadius = vargv[0];
  local circleOuterRadius = vargv[1];
}

Function References

Many imp API methods take function references as parameters. These methods register callback functions that will be called when an event, such as a timer firing or requested data being received from a remote server, occurs. References to functions that have already been declared are passed by providing the function’s name without brackets. If you include the brackets, the function will be called and its return value registered as the callback reference. This may be what you intend — for example, if you are using a factory function which returns references to functions stored in a table — but may lead to unexpected results if you if you included the brackets in error:

The following code will cause timerTriggeredFunction() to be called when the the timer fires:

function timerTriggeredFunction() {
  // Perform some work when timer fires
  return null;
}

imp.wakeup(TIMER_DURATION_IN_SECONDS, timerTriggeredFunction);

Squirrel allows you to use a function declaration in place of a function reference:

imp.wakeup(TIMER_DURATION_IN_SECONDS, function() {
  // Perform some work when timer fires
});

Lambda Functions

Single-expression, ‘lambda’ functions can be defined simply:

local addFunc = @(a, b) a + b;

which is equivalent to:

local addFunc = function(a, b) { 
  return a + b;
}

This is a particularly handy way to pass one-off functions as function parameters and return values.

Generator Functions

Generators are a special type of function, specified by including one or more yield statements. When called, generators will establish their data structures in memory but not execute; they will not run until another part of the program includes a resume statement targeting that specific generator. When this happens, the generator begins to run until it reaches a yield statement. At this point, execution is again suspended, but the function’s internal state, including its local variables, is retained. Code elsewhere in the program uses another resume to bring the function back to life. The generator function’s life ends when it returns.

Generators can be a hard concept to grasp, but are a powerful tool for enabling parallelism in Squirrel code. Please see the Developer Guide ‘Squirrel Generator Functions’, which covers generators in more depth.

Classes and Instances

Object classes are defined using the keyword class and comprise methods and properties respectively defined with functions and variables. Class and instance names may not start with a numeral. Instances are created by calling the class as if it were a function, but note the convention that the class name is capitalized:

local deviceInstance = Peripheral();

Classes may also contain a special function, constructor(), which is run when the class is instantiated to supply default property values, some of which may be passed as parameters. If a class definition contains a constructor() function, properties that will set by that function should be assigned to null. By convention, this assignment should appear before the constructor. Note that class property declarations do not require the local keyword.

Class properties can have their initial values set using an initializer. The initializer is evaluated only once, and that one value is assigned to all instances. So using an array or table initializer, including [] and {}, results in all instances being initialized with references to the same shared array or table. This is not usually what is intended, so you should instead use the initializer = null for array and table properties, and then set them up as needed in the constructor() function. This ensures that class properties are not immediately shared between instanced:

class Peripheral {

  pins = null;

  constructor() {
    pins = {};
  }
}

local deviceOne = Peripheral();
local deviceTwo = Peripheral();

Scalar variables may be prefixed with the keyword static, and this causes them to be shared among instances. They are also read-only, unlike C/C++ usage. Attempts to change their value will generate a runtime error. This is why you can’t mark class properties as const.

Reference-type variables may also be prefixed with static, in which case all instances will not only share the referent (as is the case with initializer-assigned non-static references) but they can’t be changed:

class Peripheral {

  static pins = {};
}

local deviceOne = Peripheral();
deviceOne.pins = {};
// The above line will fail with an "index 'pins' does not exist" error

However, the properties of the object referred to can be changed:

class Peripheral {

  static pins = {};

  constructor() {
    pins.count <- 4;
  }
}

Summary

  • Non-static, scalar property — Can be assigned to; each instance has its own independent copy but the initializer (if any) is only evaluated once.
  • Static, scalar property — Cannot be assigned to; is shared between all instances.
  • Non-static, reference property — Can be assigned to; the referent can be modified; each instance has its own independent reference but the initializer (if any) is only evaluated once, so if the initializer is a literal, then each instance starts out referring to a single shared referent.
  • Static, reference property — Cannot be assigned to; referent can be modified; the instance is shared between all instances.

Class Properties

Squirrel disallows the modification of properties when they are accessed via the class. For example:

class Peripheral {
  pins = {};
  id = 0;

  function setID(value) {
    if (value > -1 && value < 256) id = value;
  }
}

Peripheral.setID(4);

will fail with a “cannot set property of object of type ‘class’” error. It will not fail if the method is called on an instance of the class. You can work around this if you use a table instead of a class:

local Peripheral = {
  pins = {},
  id = 0,

  function setID(value) {
    if (value > -1 && value < 256) id = value;
  }
}

Peripheral.setID(4);

This also has the benefit of using less memory: see ‘Writing Efficient Squirrel’.

Classes can be expanded on the fly. For example, you can add member functions with the slot operator (<-):

pixels = WS2812(hardware.spi257, 5);

WS2812.len <- function() {
  local size = _bits.len() + _frame.len();
  return size;
};

server.log("Pixels size: " + pixels.len());

Inheritance

A class may be derived from another class by using the extends keyword with the original, or the ‘base’, class name:

class Device extends Peripheral

The new class ‘inherits’ all the methods and properties of the base class, even if they are overridden — the new class contains code which replaces a method or methods defined by its parent. While an overriding method will be used if it is called in the usual way, the overridden version may also be called from within the child class by prefixing the method’s name with base. For example:

class Parent {
  function functionOne() {
    return 42;
  }

  function functionTwo() {
    return "Mega-City One";
  }
}

class Child extends Parent {
  // Child overrides Parent's functionTwo() with its own version of the method

  function functionTwo() {
    return "Brit Cit";
  }

  function functionThree() {
    return base.functionTwo();
  }
}

local achild = Child();
local x = achild.functionOne();   // x = 42
local y = achild.functionTwo();    // y = "Brit Cit"
local z = achild.functionThree();  // z = "Mega-City One"

Squirrel’s instanceof keyword may be used to test whether the passed object is an instance of a given class.

Standard Squirrel allows metadata strings to be added to classes using the </ and /> markers. However, this functionality is not currently supported by impOS.

Delegation

If an object lacks a certain method, it may delegate that call to another object. To do so, the object uses its setDelegate() method to specify the delegate, which will be called automatically if the targeted object is asked to do something it can’t.

Squirrel has built-in delegates for all its key entities — integer, float, bool, string, array, table, function, generator, class, instances of that class, threads and weak references — which may be called through an entity’s namespace:

local myInt = 10;
local myIntAsString = myInt.tostring();
local myArray = ["Death", "Fear", "Fire", "Mortis"];
local arraySize = myArray.len();
local lastTtemInArray = myArray.top();
myArray.append("Anderson");

Delegation is the mechanism by which a scalar property is manipulated as if it were an object. An integer, for example, is a pure value, but it is given methods through its delegate object.

An instance’s delegate is always its Squirrel type or custom class. Tables have no delegate by default, but you can set one (always another table) using its setdelegate() method.

See the Squirrel Language Reference for a full list of Squirrel’s standard delegate methods for default variable types and other entities.

Duplicating Objects

Squirrel provides a keyword, clone, to duplicate entities — objects, tables or arrays — which are usually accessed by reference rather than value. Any such entities nested within the clone’d object will not by copied, but accessed by reference. However, they themselves may be clone’d separately — and, indeed, any objects, tables arrays nested within them — and added to the clone.

Note for newcomers to programming: References

When created, the above entities’ variable names actually hold a reference to those structures in memory, not the values themselves. So, in the following example, changing bTable’s slot values will also change those in aTable. The variable aTable contains a reference to the declared data in memory. In the second line, the variable bTable is given the same reference, ie. both variables point to the same data structure, the table declared in line one.

local aTable = { "first_key" : "Max Normal", 
                 "second_key" : 42, 
                 "third_key" : true};

local bTable = aTable;
server.log(aTable.firstKey);   // Displays "Max Normal"
bTable.firstKey = "PJ Maybe";
server.log(aTable.firstKey);   // Displays "PJ Maybe"

To create a fresh copy, clone the original:

local bTable = clone(aTable);
server.log(aTable.firstKey);   // Displays "Max Normal"
bTable.firstKey = "PJ Maybe";
server.log(aTable.firstKey);   // Displays "Max Normal"

These are ‘strong references’ because the variable is said to ‘own’ the entity to which it is referring. This ownership ensures that the referred entity isn’t cleared from memory by Squirrel before the variable holding the reference is done with it. If the variable is given a reference to another entity or a scalar value — in other words, it is no longer interested in the first entity — the original object can be removed from memory. Unless, of course, another variable has a reference to it.

The number of variables with a reference to the same object is called that object’s ‘reference count’. Squirrel keeps track of each object’s reference count and only disposes of an object (clears it from memory) when the object’s reference count falls to zero. Every time a variable is given a reference to an object, the object’s reference count is incremented.

In the example above, when aTable is declared, the table object is set up in memory, a reference to it is placed in aTable and its reference count becomes 1. In line two, a second variable is given a reference to the table, so the table’s reference count becomes 2. This ensures that if, say, aTable is redeclared to something else (causing the table’s reference count to be reduced by 1), the table is still kept in memory for access via bTable (because its count has fallen to 1 not zero).

Contrast this with ordinary, scalar variables integer, float and bool. These variables hold actual values not pointers to those values. If we have:

local anInt = 45;
local anotherInt = anInt;
anotherInt = anotherInt + 100;
server.log(anInt);       // Displays "45"
server.log(anotherInt);  // Displays "145"

changing the value of anotherInt, as we do in the last line, does not change the value of anInt.

Weak References

Squirrel allows you to create ‘weak’ references to objects, functions, tables and arrays, ie. references which do not claim ownership over those objects. Objects use their weakref() method to generate a weak reference to them. You can test whether a variable contains a weak reference by using the typeof command:

local aTable = {};

// Get a weak reference to 'aTable'
local weakRefToTable = aTable.weakref();

// Confirm the reference type
server.log(typeof weakRefToTable);
// Displays 'weakref' -- 'weakRefToTable' is indeed a weak reference

It is also possible to call weakref() on an Integer, Float or Boolean variable, but in this case the call returns a copy of the variable (because those are scalar types that pass by value not by reference).

The method ref() returns a strong reference to the object indicated by the weak reference, or null if the object no longer has any other strong references to it.

If you store a weak reference in a container (ie. a table slot [including the root table], an array, a class or an instance), when you fetch it back you will receive the underlying object, not the weak reference itself, ie. this involves an implicit ref() call.

local aTable = {};
local weakRefToTable = aTable.weakref();

// Store the weak reference in the root table
globalWeakRefToTable <- weakRefToTable;

// Duplicate the reference
local strongRefToTable = globalWeakRefToTable;
server.log(typeof strongRefToTable);
// Displays 'table' -- 'strongRefToTable' is indeed a strong reference

This allow you to ignore the fact that the value handled is a weak reference. When the object pointed by weak reference is destroyed, the weak reference is automatically set to null.

Note In standard Squirrel, bindenv keeps a weak reference to the object, but in Electric Imp Squirrel, it is a strong reference.

Note for newcomers to programming: Weak References

Variables that represent entities other than integers, floats and bools store a reference to the entity, not its value (see ‘Note for newcomers to programming: References’, above).

A weak reference allows one entity to store a reference to another entity and change that reference without automatically affecting the entity that was first referenced. This is useful if a reference variable needs to be zero’d without deleting the object it originally pointed to. Consider this code:

local myTable = {};
local myArray = [1, 2, 3, 4, 5];
myTable.weakslot <- myArray.weakref();  // Add array to table by weak reference

If we subsequently write:

myTable.weakslot = null;

then the variable myArray will not be purged from memory. Since weakslot stores a weak reference, it makes no ownership claim on myArray. However, if the code says:

myArray = null;

then myArray may be purged, because the variable is a strong reference to the array object. Squirrel counts strong references made to an object and, if that count falls to zero as references are removed, then the object is removed from memory. If there are no references to an object, it’s reasonable to assume no part of the program is interested in that object, and the memory it is using can be freed for other purposes. Of course, if another entity has a strong reference to myArray when that variable is assigned null, the original array is retained in memory because its reference count has yet to fall to zero.

local myTable = {};
local myArray = [1, 2, 3, 4, 5];           // array reference count is 1
myTable.slot <- myArray;                   // array reference count is 2
myArray = null;                            // array reference count is 1, ie. it still exists
                                           // (because there's a reference to it in 'myTable')
local myWeakRef = myTable.slot.weakref();  // array reference count is 1
                                           // (because a weak reference doesn't increment the count)

Program Control

Squirrel provides a number of structures for program flow control and looping in addition to the standard for, do... while, while and if... elseif... else structures. The foreach keyword is an explicit equivalent of the use of for with the in keyword in other languages, to iterate through Squirrel’s collection data types: strings, arrays and tables. For example:

foreach (slot in myTable) {
  // Perform loop code...
}
foreach (item in myArray) {
  // Perform loop code...
}

In the above examples, the variables slot and item are automatically given a local scope; this is not the case with for loops. In the first example, slot takes the value of each key in the table; in the second, item takles the value of each item in the array. This will be an actual value in the case of integers, floats, bools and strings, but references to arrays, functions and tables. Incidentally, the in keyword may be used in other types of statement:

if (slot in table) {
  // Perform conditional code...
}

The foreach loop may also be used to separate each slot’s key and value into separate variables:

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

The same structure can be applied to arrays, but this time the first value provides the index of the current iteration through the loop:

foreach (index, item in myArray) {
  server.log("The array has the value: " + item + " at the index: " + index);
}

Error Handling

Squirrel provides a try… catch structure to trap and handle exceptions that may be thrown by the code placed in the try block:

local aTable = {};

try {
  // Do something that generates an error: mis-assign a new table slot
  // Should be 'aTable.newSlot <- 1234'
  aTable.newSlot = 1234;
} catch(exception) {
  server.error(exception);
  // Displays "ERROR: the index 'newslot' does not exist" in the log
}

Exceptions can be issued manually with the throw keyword. The command takes a single value of any type. It is possible to catch an exception within, say, a class method and throw it for a high level catch to receive. However, the exception is presented simply as a string and as such lacks state data. For more details see the Developer Guide ‘Error Handling in Squirrel Code’.

Operators

The Three-Way Operator

In addition to all the standard arithmetic, relational, bitwise and logical operators, Squirrel also provides a three-way comparison operator, <=>, which compares two values and returns a third according the result of that comparison, specifically the sign of the difference between the first and second values:

local result = valueOne <=> valueTwo;

// result =  1 if valueOne > valueTwo
//          -1 if valueOne < valueTwo
//           0 if valueOne = valueTwo

To get the magnitude of the difference, you can use:

local difference = math.abs(valueOne - valueTwo);

The Ternary Operator

The ternary operator, ?: evaluates the expression to the left of the question mark and returns either of the two values (or expression results) separated by the colon depending on the result of the evaluation: the value or expression result on the left of the colon for true, the the value or expression result on the right of the colon for false.

For example, if the value of the bool onlineFlag in the line below is true, the ternary operator will return the string "online" and *messageString* will become "The Device is online."; if onlineFlag is false, messageString becomes "The Device is offline.":

local messageString = "The Device is " + ( onlineFlag ? "online" : "offline" ) + ".";

The In Operator

The in operator is used to test for the existence of a slot in a table, ie. whether the table contains the specified key. Use a string literal or string variable for the name of the key you’re testing for:

if ("firstKey" in myTable) {
  // 'myTable' has the key 'firstKey' so set it
  myTable.firstKey = "Rowdy Yates Block";
}
local searchKey = "firstKey";
if (searchKey in myTable) {
  // 'myTable' has the key 'firstKey' so set it
  myTable.firstKey = "Rowdy Yates Block";
}

The if ("X" in Y) construct works in Electric Imp Squirrel even if Y has a delegate or a _get metamethod. This is what enables if ("pinA" in hardware), but is not a features of standard Squirrel.

The typeof operator returns a lower-case string indicating the type of value held (or pointed to) by the specified variable.

local a = {};
server.log(typeof a);

// Displays "table"

The Instanceof Operator

The instanceof operator tests if the named object is an instance of the specified class.

if (anInstance instanceof Test) {
  // Perform conditional code...
}

Bit-Shift Operators

Squirrel integers are signed and the usual bitwise operators >> and << can be used to shift the value one or more bits right and left, respectively, ie. multiply or divide by two. This is useful to ‘sign extend’ 8- or 16-bit signed values retrieved from sensors or other external devices to Squirrel’s native 32-bit signed integers:

// Get sensor temperature reading, which is a signed
// 16-bit value issued as two successive bytes
local tempMSB = onewire.readByte();
local tempLSB = onewire.readByte();

// Convert to 16-bit value
local temp = (tempMSB << 8) + tempLSB;

// 'temp' is 32-bit, so we move the lower 16 bits
// up to set the sign bit (bit 31) appropriately and
// move the value itself back to the correct bits
// (the sign bit is not changed by the down-shift)
temp = ((temp << 16) >> 16);

// Convert to Celsius
local tempCelsius = temp * 0.0625;
server.log("The temperature is " + format("%.2f", tempCelsius) + "C");

The language also provides >>> to shift the unsigned reading of the integer to the right.

Metamethods

Metamethods are a means by which Squirrel programmers can override existing language operators. Essentially, each metamethod provides a way to substitute your functionality for Squirrel’s own, standard operations whenever it encounters one of a specific set of operators.

For example, Squirrel encounters any one of the relative comparison operators — <=, >=, < and > — to the right of an object, it immediately looks in the object’s specified delegate (if it has one; tables do not, be default) for the _cmp metamethod. If Squirrel finds a function named _cmp(), it will call that function, otherwise it runs code of its own.

Metamethods only work with certain entities — tables, class instances and class objects — and a given metamethod will not necessarily work with all of those entities. For example, _nexti only works with class instances, not tables, while _newslot is only relevant to tables, and _newmember to class objects.

The following table lists the metamethods available to programmers, including the entities for which they are applicable:

Metamethod Operator(s) Parameter(s) Use With Notes
_cmp <, >, <=, >= Operand to the right of the operator Table, class instance Perform a relative comparison, eg. if (a > b) { ... }
Function should return an integer:
1, if a > b
0, if a == b
-1, if a < b
_mul * Operand to the right of the operator Table, class instance Perform a multiplication, eg. local a = b * c
Returns the result
_add + Operand to the right of the operator Table, class instance Perform an addition, eg. local a = b + c
Returns the result
_sub - Operand to the right of the operator Table, class instance Perform a subtraction, eg, local a = b - c
Returns the result
_div / Operand to the right of the operator Table, class instance Perform a division, eg. local a = b / c
Returns the result
_mod % Operand to the right of the operator Table, class instance Perform a modulo, eg. local a = b % c
Returns the result
_unm - Operand to the right of the operator Table, class instance Perform a unary minus, eg. local a = -b
Returns the result
_newslot <- Key and value Table Creates and adds a new slot to a table
_delslot delete Key Table Removes a slot from a table
_set = Key and value Table, class instance Called when code attempts to set a non-existent slot’s key,
eg. table.a = b
_get = Key Table, class instance Called when code attempts to get the value of a non-existent slot,
eg. local a = table.b
_typeof typeof None Table, class instance Returns type of object or class instance as a string,
eg. local a = typeof b
_tostring .tostring() None Table, class instance Returns the value of the object or class instance as a string,
eg. local a = b.tostring()
_nexti foreach… in… Previous iteration index Class instance Called at each iteration of a foreach loop.
Parameter value will be null at the first iteration.
Function must return the next index value
_cloned clone The original instance or table Table, class instance Called when an instance or table is cloned
_inherited New class (as this) and its attributes Class object A parent class method is overridden by a child class
_newmember index, value, attributes, isstatic Class object Called when a new class member is declared. If implemented, members will not be added to the class
_call this and the function’s other (visible) parameters Table, class instance Called when the table or class instance is itself called as a function

For more detailed guidance on metamethods and how (and for what) they are employed in code, please see the Developer Guide ‘Using Squirrel Metamethods’.

Squirrel Standard Libraries

Squirrel maintains a number of libraries, provided automatically without the need to import them into your code. However, not all of these are included in the Electric Imp implementation of the language, and some of those that are need to be handled in a slightly non-standard way. For instance all the functions provided by Squirrel’s math library are available to be used in device and agent code, but they must be called by prefixing their names with math. to indicate their namespace.

Squirrel’s string and blob libraries are implemented in their entirety but do not require their namespace to be included. Ditto the system library, though only the date() and time() functions are provided. Squirrel’s IO library is absent with the exception of blob streaming.

Squirrel Library Present on the imp Absent from the imp
Base getroottable, assert, print, error,
array, type, callee, dummy
setroottable, getconsttable, setconsttable, compilestring, newthread,
suspend, setdebughook, seterrorhandler, getstackinfos
Blob All
IO All except for stream methods on blobs
Math All, in namespace math srand is present, but ineffective
String All
System clock, time, date getenv, system, remove, rename

Electric Imp Libraries

Electric Imp provides a set of code libraries that may be included in your own code — they are all provided under the MIT License — using the #require directive. For more information on how these libraries can be used, and what libraries are available, please see the libraries page.

Directives

Squirrel generally treats # as an alternative single-line comment marker, not to indicate a preprocessor or compiler directive, for example #define or #import, as other languages do. Use the const keyword in place of #define.

There are two exceptions to this rule. Electric Imp’s impCentral™ uses #require to load code libraries, and #line to set the line number of the code following the directive, to aid debugging. For example:

#line 5000
throw ("Error!");

will generate the following output in the log:

2018-01-15 14:30:03.205 "Action": [agent.error] ERROR: Error!
2018-01-15 14:30:03.205 "Action": [agent.error] ERROR:   in main agent_code:5000

This directive allows you to mark sections of your code by line number, the better to help you locate the code causing errors.

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 quirk per se, we’ve covered these rules elsewhere — see the Developer Guide ‘Data Serialization in Squirrel’ — but we encourage all programmers starting to write Squirrel code for the imp to read this document.

Threads

Squirrel provides parallel operation using threads — or ‘coroutines’ in its own terminology. However, the Electric Imp version of Squirrel does not currently support thread usage, for memory utilization reasons and because the same effect can be achieved with less complexity using generators.

Regular Expressions

Squirrel includes a special object, regexp, for managing string searches using regular expressions. Such an object is created using the regexp() function, which takes a string as a parameter: the regular expression itself. Comparisons with this pattern are made using the regexp delegate methods all of which take as a parameter the string against which Squirrel will attempt to match the pattern.

Two methods — search() and capture() — respectively find the first or all matches generated by the pattern and return a table (or an array of tables in the second case) containing the location and end of the matched sub-string. They return null if no match can be made.

Finally, the method match() yields a simple boolean true or false depending on whether or not the regular expression matches characters within the passed string.

Electric Imp has developed much-improved regular expression functionality. This is currently only available to agents. Make use of it via the regexp2 object.