5. Classes

Syntax Summary
[aligned[(alength)]]class name[(parameter list)] [extends base]
     [: type(length) id = [value|range] ] {
    [element; ...]
}; // trailing ';' optional

Flavor uses the notion of classes in exactly the same way as C++ and Java do. It is the fundamental structure in which object data is organized. Keeping in line with the support of both C++ and Java-style programming, classes in Flavor cannot be nested, and only single inheritance is supported. In addition, due to the declarative nature of Flavor, methods are not allowed (this includes constructors and destructors).

5.1 General

The following is an example of a simple class declaration with just two parsable member variables.

    class SimpleClass {
        int(3) a;
        unsigned int(4) b;
    }; // trailing ';' optional

The trailing ';' character is optional accommodating both C++ and Java-style class declarations.

This class defines objects which contain two parsable variables. They will be present in the bitstream in the same order they are declared. After this class is defined, we can declare objects of this type:

    SimpleClass a;

A class is considered parsable if it contains at least one variable that is parsable. Declaration of parsable class variables can be prepended by the aligned modifier in the same way as simple parsable variables.

Class member variables in Flavor do not require access modifiers (public, protected, private). In essence, all such variables are considered public.

5.2 Parameter Types

As Flavor classes cannot have constructors, it is necessary to have a mechanism to pass external information to a class. This is accomplished using parameter types. These act the same way as formal arguments in function or method declarations do. They are placed in parenthesis after the name of the class. For example:

    class SimpleClass(int i[2]) {
        int(3) a=i[0];
        unsigned int(3) b=i[1];
    }

When declaring variables of parameter type classes, it is required that actual arguments are provided in place of the formal ones:

    int(2) v[2];
    SimpleClass a(v);

Of course the types of the formal and actual parameters must match. For arrays, only their dimensions are relevant; their actual sizes are not significant as they can by dynamically varying. Note that class types are allowed in parameter declarations as well.

5.3 Inheritance

Flavor supports single inheritance so that compatibility with Java is maintained. Although Java can "simulate" multiple inheritance through the use of interfaces, Flavor has no such facility (it would be meaningless since methods do not exist in Flavor).

Derivation in C++ and Java is accomplished using a different syntax (extends versus ‘:’). Here we opted for the Java notation (also ‘:’ is used for object identifier declarations as explained in Polymorphic Parsable Classes).

The following shows a simple example of a derived class declaration.

    class A {
        int(2) a;
    }
   
    class B extends A {
        int(3) b;
    }

Derivation from a bitstream representation point of view means that B is an A with some additional information. In other words, the behavior would be almost identical if we just copied the statements between the braces in the declaration of A in the beginning of B. We say almost here because scoping rules of variable declarations also come into play here (see Scoping Rules).

Note that if a class is derived from a parsable class, it is considered parsable as well.

5.4 Polymorphic Parsable Classes

Polymorphism is the capability to use a derived object in a place where an object of a base class is expected. Flavor allows objects residing in a bitstream to have polymorphic behavior by using object identifiers or IDs.

The concept of IDs is rather simple: in order to detect which object we should parse/generate, there must be a parsable variable that will identify it. This variable must have a different expected value for any class derived from the originating base class, so that object resolution can be uniquely performed in a well-defined way. Object ID values must be constant expressions.

In order to signify the importance of ID variables, they are declared immediately after the class name (including any derivation declaration) and before the class body. They are separated from the class name declaration using a colon (‘:’). For example:

    class A : int(1) id=0 {
        int(2) a;
    }
   
    class B extends A : int(1) id=1 {
        int(3) b;
    }

The name and the type of the ID variable is irrelevant, and can be anything that the user chooses. It cannot, however, be an array or a class variable (only built-in types are allowed). Also, the name, type, and parse size must be identical between the base and derived classes.

The semantics of the object identifiers are the following. Upon reading the bitstream, if the next 1 bit has the value 0 an object of type A will be parsed; if the value is 1 then an object of type B will be parsed. (Note: For output purposes, it is up to the user to set up the right object type in preparation for output.)

Object identifiers are not required for all derived classes of a base class that has a declared ID. This allows, for example, having the following inheritance tree.

  

Here only the classes represented by the black circles have IDs. As a result, only classes A, B, C, and D can be used wherever an A can appear; the intermediate classes cannot.

The ID of a class is also possible to have a range of possible values which is specified as start_id .. end_id, inclusive of both bounds. For example: (NOTE: ID ranges are not supported in Version 2.1 of the translator.)

    class slice 
        : aligned bit(32) slice_start_code
               = 0x00000101 .. 0x000001AF {
        ...
    }

ID variables are always considered constant, i.e., they cannot be redeclared within the class. This is the same as if the keyword const was prepended in their declaration.