Hello Squirrel

The Bipscript language is descended from the Squirrel programming language, to which Bipscript adds the Bipscript API as well as other language constructs, e.g. an import statement and new operator types to manage audio and midi connections.

Squirrel is an object-oriented programming language used as the scripting language in several video game titles. It was designed to be performant in real-time applications, this makes it ideal for use in video games and now audio scripting as well. The Squirrel project provides reference documenation

Note Bipscript does not include the Squirrel standard library, similar general language functionality is provided in the Bipscript API.

Basic Types and Data Structures

Primitive types in Squirrel include types for boolean values, floating point numbers, integers and strings. Here we declare some primitive types using the local keyword which designates a variable with local scope:



local foo = "I'm a string"
local n = 10
local x = 0.125


Container data structures include arrays and tables. Arrays are a zero-indexed sequence of any type:



local anArray = [2, "string", 3.03]
print(anArray[1] + "\n") // prints "string" (plus a newline)


Tables are containers that associate each value with a key:



local bob = {
    name = "Bob Johnson",
    active = true,
    instruments = ["bass", "keyboards"]
}
print(bob.instruments[0] + "\n") // prints "bass"


New Slot Operator

When using the table data structure note that attempting to access a table index that does not exist will result in an error, here we use try/catch statements to trap the error:


try {
	print(bob.age)
} catch(e) {
	print(e + "\n")
}
// prints "the index 'age' does not exist"


The same error will be thrown when trying to assign to an unknown index, in this case use the new slot operator:



bob.age <- 42

print(bob.age + "\n")


Note also there is an implicit table associated with the script context, we can assign to its slots as well:



scriptVar <- "hello"


Conditional Statements and Looping

Basic conditional branching is implemented with an if statement similar to other languages:


local x = 5
if(x > 3) {
    print(x + " is greater than 3\n")
}

There are also while loops which work as expected:


local y = 1
while(y <= 10) {
    print("y is now " + y + "\n")
    y++
}

An alternative to the above is the standard for loop:


for(local z = 1; z <= 10; z++) {
    print("z is now " + z + "\n")
}

There is also a foreach loop for arrays and tables:


foreach(key, val in bob) {
    print("key '" + key + "' has value: " + val + "\n")
}



Defining Classes

An object class can be defined by using the class keyword:



class MyClass {

Variables declared inside class definitions are member data


	message = null

A constructor can be defined and will be called for each new instance:


	constructor(mesg) {
		message = mesg
	}

Named functions are member methods of the class and can access member data:


	function print() {
		println(message)
	}
}

We can create a new instance of this class as so:


local instance = MyClass("Hello!")

Once created we can use the member methods:


instance.print()

Note it is also possible to directly access member data if needed:


println("instance message: " + instance.message)

Class Inheritence

It is also possible to define a class that inherits the functionality (and data) of another class.

Here is a subclass that extends the above defined "MyClass":



class SubClass extends MyClass {

Subclasses may define further member data:


	myinteger = 5

A subclass constructor should call the base class constructor directly by using the base keyword.


	constructor() {
		base.constructor("subclass says hello!")
	}

Subclass methods will override base methods of the same name and can call overridden methods by using the base keyword:


	function print() {
		base.print()
		println("my integer value is " + 5)
	}
}

Once defined subclasses are instantiated and used the same way as other classes


SubClass().print()


Class Caveats

While the above class syntax is simple and should be familiar to developers who have used object-oriented scripting languages, there are a couple of things to watch out for. This class demonstrates them.



class Tricky extends MyClass {

Calling the base keyword directly is legal and produces a new object but it is not the parent of the current instance, the effect of this constructor as defined is to create a MyClass object and then immediately forget about it as it goes out of scope:


	constructor() {
		base("foo")
	}

The correct expression in this case is base.constructor("foo") to create that as the base to the current instance

Another issue: when member data is defined with an initial value, that initial value is shared among all instances of this class. This can be confusing when the value is a complex type like a table, array, or another instance, as the value is indeed shared among all instances:


	marray = []

In this case the below function will append to the same array as all other instances:


	function append(mesg) {
		marray.append(mesg)
	}
}

The correct way to initialize a nonshared array or other complex object is to do so in the constructor. Note that this is only an issue when the member data is not a primitive value.

Note the behavior of the above class:



local tricky1 = Tricky()
tricky1.append("tricky1")
tricky1.print() // prints null, not "foo"

local tricky2 = Tricky()
tricky2.append("tricky2")
foreach(mesg in tricky2.marray) {
	println(mesg) // prints tricky1 too
}


More Information

More information about the squirrel language can be found at the following links:



Complete Script

local foo = "I'm a string"
local n = 10
local x = 0.125

local anArray = [2, "string", 3.03]
print(anArray[1] + "\n") // prints "string" (plus a newline)

local bob = {
    name = "Bob Johnson",
    active = true,
    instruments = ["bass", "keyboards"]
}
print(bob.instruments[0] + "\n") // prints "bass"

try {
	print(bob.age)
} catch(e) {
	print(e + "\n")
}
// prints "the index 'age' does not exist"

bob.age <- 42

print(bob.age + "\n")

scriptVar <- "hello"

local x = 5
if(x > 3) {
    print(x + " is greater than 3\n")
}

local y = 1
while(y <= 10) {
    print("y is now " + y + "\n")
    y++
}

for(local z = 1; z <= 10; z++) {
    print("z is now " + z + "\n")
}

foreach(key, val in bob) {
    print("key '" + key + "' has value: " + val + "\n")
}

class MyClass {

	message = null

	constructor(mesg) {
		message = mesg
	}

	function print() {
		println(message)
	}
}

local instance = MyClass("Hello!")

instance.print()

println("instance message: " + instance.message)

class SubClass extends MyClass {

	myinteger = 5

	constructor() {
		base.constructor("subclass says hello!")
	}

	function print() {
		base.print()
		println("my integer value is " + 5)
	}
}

SubClass().print()

class Tricky extends MyClass {

	constructor() {
		base("foo")
	}

	marray = []

	function append(mesg) {
		marray.append(mesg)
	}
}

local tricky1 = Tricky()
tricky1.append("tricky1")
tricky1.print() // prints null, not "foo"

local tricky2 = Tricky()
tricky2.append("tricky2")
foreach(mesg in tricky2.marray) {
	println(mesg) // prints tricky1 too
}


List of All ExamplesDemo Applications


Creative Commons License This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.