Structure and scoping

Structure

The top-level of a Roto script can contain definitions of blocks. The type of these blocks can be:

  • filter-map

  • filter

  • rib

  • table

  • output-stream

The syntax for all definitions of blocks looks like this:

filter my-filter { }

In this example, the type of the block is filter, the name is my-filter, and its body (the code between the curly braces) is empty.

The top-level can also contain type definitions for user-defined types. These look very similar to block definitions:

A type definition looks like this:

type MyRec {
    asn: Asn
}

Im this example, a type with the name MyRec is created, and is defined as Record with a field called asn, which is of type Asn.

Lastly, the top-level can contain single-line comments. Comments start with //.

Roto Global namespace and Block Scopes

For now, Roto only has one namespace, namely the Global namespace. Rotonda loads all available Roto source code from all files in a directory on the local file system specified in a Rotonda configuration file and puts all that source code in said global namespace. For the roto user this means that all identifiers, i.e. names of blocks, in the top-level of all loaded scripts must be unique across all these files. A failure to do so will result in a compilation error, and the compilation process will abort.

Although Roto only has one namespace, it does have multiple scopes. It has a global scope, which lives in the global namespace. All built-in types and user-defined types are situated in this global scope. Moreover, some constants are defined in the global namespace. All other scopes are nested inside the global scope, and nothing that is defined in the global scope with a name (“identifier”), be it block definitions, or type definitions, or constants, or built-in types, can be redefined (“shadowed”) in the nested scopes. An attempt to do so will result in a compilation error, and the compilation process will be aborted.

Each block defined in the global namespace will have its own nested scope in its body. This means that a variable assignment in one block has no effect whatsoever on other blocks.

Consider this example:

// Global Scope is active here

filter a {
    // Nested Scope for filter a is active here
    define {
        pfx = 192.0.2.0/24;
        my_rec = MyRec {
            asn: 65534,
            prefix: pfx
        };
    }
}

filter b {
    // Nested Scope for filter b is active here
    define {
        pfx = 192.0.3.0/24;
        my_rec = MyRec {
            asn: 65535,
            prefix: pfx
        };
    }
}

// Global Scope is active here
type MyRec {
    asn: Asn,
    prefix: Prefix,
}

In this example we defined a type MyRec. That type is defined in the global scope and is therefore usable in all scopes. So in the nested scopes of filter a and filter b we can create new instances of this type. Filter a and filter b use the same variable names while creating these instances, but that does not matter: they are scoped to their own block, so they don’t interfere.