Variables

Buzz is a dynamically typed language. This means that the type of a variable can change during the execution of a script, and it depends on the type of the value stored by a variable at any moment.

Buzz does not include the concept of constant. Every variable is always mutable.

Buzz supports the following primitive types:

  • nil encodes an unknown value for a variable, or a false condition
  • integer a 32-bit signed integer (e.g., 90)
  • float a 32-bit floating-point value (e.g., 1.0, 0.25, .5)
  • string a string of characters (e.g., "hello, world!")
  • table a structured type to encode various kinds of data structures (see Tables)
  • user data a pointer to a user-defined memory region managed externally to Buzz. See User Data for more information.

Buzz variables can be global or local. To declare a variable as local to a code block (e.g., a function), you need to prepend its first definition with the keyword var:

# A local variable - by default its value is nil
var x
# Another local variable with initialization
var i = 5
# Assignment for a local variable
x = 7.8

To declare a global variable, you simply assign its value without using the var keyword:

# A global variable definition
j = 5

Control Structures

Tables

Tables are the only structured type available in Buzz. Tables are quite flexible: you can use them both as a hash map or as a classical array.

To create an empty table, use this syntax:

t = {}

Internally, Buzz tables are implemented as hash maps, that is, a collection of (key, value) pairs. While in principle you could use any Buzz primitive type as key, the most common types are integers, floats, and strings. In this case, you can use this syntax to populate a table:

t = { .x = 1, .2 = 5.6, .4.5 = "k" }

This syntax creates a table that contains three pairs: ("x",1), (2,5.6), and (4.5,"k"). The table is stored in variable t.

To add new values to a table, or to set new values for existing keys (remember that in hash maps keys can't be duplicated!), use this syntax:

# If the key is an integer
t[6] = "six"
# If the key is a float
t[1.0] = "one point zero"
# If the key is a string, the following two lines are equivalent
t["hello"] = "this is a greeting"
t.hello = "this is a greeting"

To read the value of a table, the same syntax as above applies:

# If the key is an integer
print(t[6])
# If the key is a float
print(t[1.0])
# If the key is a string, the following two lines are equivalent
print(t["hello"])
print(t.hello)

Finally, to erase an element from a table it is enough to set it to nil:

t[1.0] = nil

Table contents can be handled through a number of dedicated commands.

size(t)

returns the current number of elements in table t:

t = { .x = 4 }
print(size(t)) # prints 1

foreach(t,f)

applies a function f(key,value) to each element of table t:

t = { .x = 4, .y = 5, .z = 6 }
foreach(t,
  function(key,value) {
    print("(", key, ", ", value, ")")
  })
# prints
#   (x, 4)
#   (y, 5)
#   (z, 6)

It is important to notice that foreach(t,f) is not meant to modify the values of the table. It is only meant to go through the elements of t and use its values in a read-only fashion. If you want to modify the elements of a table, use map(t,f).

map(t,f)

applies a function f(key,value) to each element of table t and returns a new table. For each element, function f must return a value, which is used to populate the new table. For instance:

t = { .x = 1, .y = 2 }
u = map(t,
      function(key,value) {
        return value + 100
      })
# now u contains:
#   ("x", 101)
#   ("y", 102)

reduce(t,f,a)

applies a function f(key,value,accumulator) to each element of table t. Function f must accept three parameters: the current key, the corresponding value, and an accumulator. Function f must also return a value, that is passed as accumulator to the invocation of f on the next table element. Parameter a of reduce(t,f,a) is the initial value of the accumulator. For instance, if you want to calculate the average of the values in a table, write the following code:

t = { .1 = 1.0, .2 = 2.0, 3. = 3.0 }
avg = reduce(t,
        function(key,value,accumulator) {
          return value + accumulator
        },
        0.0) / size(t)
# avg is now 2.0

Functions

Math

Math works similarly to most programming languages you are used to. The basic math operations, in decreasing order of precedence, are:

  • Unary minus (e.g., -5)
  • Power (e.g., 3^5)
  • Modulo (e.g., 10 % 4)
  • Multiplication and division (e.g., 2 * 3 / 4)
  • Addition and subtraction (e.g., 2 + 3 - 4)

Analogously to other languages, parentheses are used to modify the natural precedence of the operators:

x = 5^(4%(3*(2+1)))
# x = 5^(4%(3*3))
# x = 5^(4%9)
# x = 5^4
# x = 625.0

As the above example shows, the power operator transforms its operands into float, even if the operands are both integers. The other operators return an integer if both operands are integer, and a float if either or both operands are float. A type error is raised if the operands are not integers nor floats.

A wider set of mathematical functions is available. These functions are stored into the math table. The math table is set up upon initialization of the Buzz VM, so no include statement is necessary to use it.

The math functions work with both integer and float values. The complete list of functions is as follows:

  • math.abs(x) returns the absolute value of x. The type of the result is the same as the type of x.
  • math.log(x) returns the natural logarithm of x as a float.
  • math.log2(x) returns the base 2 logarithm of x as a float.
  • math.log10(x) returns the base 10 logarithm of x as a float.
  • math.exp(x) returns e to the power of x as a float.
  • math.sqrt(x) returns the square root of x as a float.
  • math.sin(x) returns the sine of x as a float.
  • math.cos(x) returns the cosine of x as a float.
  • math.tan(x) returns the tangent of x as a float.
  • math.asin(x) returns the arc sine of x as a float.
  • math.acos(x) returns the arc cosine of x as a float.
  • math.atan(y,x) returns the arc tangent of y,x as a float.
  • math.min(x,y) returns the minimum between x and y. The type of the return value corresponds to the type of the minimum value: min(1.0, 2) is 1.0, and min(1,2.0) is 1.
  • math.max(x,y) returns the maximum between x and y. The type of the return value corresponds to the type of the maximum value: max(1.0, 2) is 2, and max(1,2.0) is 2.0.

In addition to these functions, the math table also includes the constant math.PI.

The math library also includes a collection of functions for random number generation. These functions are stored into the math.rng table and, similarly to math, do not require an include statement to be used.

The random number generator is based on the well-known Mersenne Twister algorithm.

Setting the Seed

Upon initialization, the Buzz VM sets a random seed taken from the current clock. If you wish to set the random seed explicitly to a value s, use the function math.rng.setseed(s). The value of s must be an integer, or a type error is raised.

Uniform Distribution

To draw numbers from a uniform distribution, use math.rng.uniform(...). The behavior of this function depends on the number and type of parameters passed.

  • math.rng.uniform() returns an integer between $-2^{32}$ and $+2^{31}-1$.
  • math.rng.uniform(x) returns a value between 0 and x. The type of the returned value matches the type of x.
  • math.rng.uniform(x,y) returns a value between x and y. If both x and y are integers, the returned value is an integer; if either or both are floats, the returned value is a float.

Gaussian Distribution

To draw numbers from a Gaussian distribution, use math.rng.gaussian(...). The behavior of this function depends on the number and type of parameters passed.

  • math.rng.gaussian() returns a float from a Gaussian with 0 mean and standard deviation 1.
  • math.rng.gaussian(x) returns a float from a Gaussian with 0 mean and standard deviation x.
  • math.rng.gaussian(x,y) returns a float from a Gaussian with mean y and standard deviation x.

Exponential Distribution

To draw numbers from an exponential distribution, use math.rng.exponential(x), where x is the mean. The returned value is a float.

When dealing with the robots, it is often useful to manipulate vectors. Buzz offers a library to handle 2D vectors. The library is stored in INSTALL_PREFIX/ share/buzz/include/vec2.bzz, so to use it a script must first include it. The complete reference of these functions is included in the file.

Strings

  • string.length(s) returns the length of string s
  • string.sub(s,...) returns a substring of the given string. Two signatures are possible: string.sub(s,n) returns the substring starting at character n (0 is the first character); string.sub(s,n,m) returns the substring starting at character n and ending at m.
  • string.concat(s1,s2,...) returns a new string that is the concatenation of the given strings.
  • string.tostring(o) transforms object o into a new string.
  • string.toint(x) converts a string into an integer. If the conversion fails, this function returns nil.
  • string.tofloat(x) converts a string into a float. If the conversion fails, this function returns nil.

A number of additional string operations is available as a library that must be included. The library is stored in INSTALL_PREFIX/share/buzz/include/ string.bzz, so to use it a script must first include it. The complete reference of these functions is included in the file.

The Buzz VM maintains a data structure that stores every string that was ever encountered during the execution of a script. Each string is associated with a unique identifier, which is simply a counter of strings created so far. Every time a string is used in a script, only the identifier is used. The actual value of the string is stored only once in the data structure. To make string lookup operations fast, strings are stored in binary tree ordered by identifier.

String manipulation often creates large numbers of intermediate strings, which are used only once over the lifetime of a script. To save memory, the Buzz VM internally distinguishes between protected and non-protected strings—protected strings are stored permanently, while non-protected strings are erased during garbage collection if no script variable refers to them. Examples of protected strings are function names, constant names, and other strings that are produced during compilation. Strings that result from manipulation with the string operations are typically non-protected.

When strings are communicated between robots, they must be serialized. It is not possible to force the string indentifiers to be the same across different robots. This is because (in general) different robots might execute different parts of a script, and strings might be created in different order. Therefore, when two robots exchange strings, the full value of the string must be communicated, rather then their identifier.

Files

To handle files, Buzz offers a number of built-in functions collected in the io table.

  • f = io.fopen(path,mode) opens a file located at path. Parameter mode encodes how the file is opened:

    • "r" opens the file read-only;

    • "w" opens the file write-only and truncates the file;

    • "a" opens the file write-only for appending data;

    • "w+" opens the file for both reading and writing, and truncates the file;

    • "a+" opens the file for both reading and writing, for appending.

    This function returns nil in case of error, and a table in case of success. The table contains the methods listed in the following.

  • f.fclose() closes file f.

  • f.size() returns the size of file f.

  • f.fforeach(fun) executes function fun for each line of file f.

  • f.fwrite(s1, s2, ...) writes the concatenation of strings s1, s2, … into file f.

Swarm Management

Virtual Stigmergy

Neighbor Management

User Data