== Defining Types An FPP model may include one or more *type definitions*. These definitions describe named types that may be used elsewhere in the model and that may generate code in the target language. For example, an FPP type definition may become a class definition in {cpp}. There are four kinds of type definitions: * Array type definitions * Struct type definitions * Abstract type definitions * Alias type definitions Type definitions may appear at the top level or inside a <>. A type definition is an <>. === Array Type Definitions An *array type definition* associates a name with an *array type*. An array type describes the shape of an <>. It specifies an element type and a size. ==== Writing an Array Type Definition As an example, here is an array type definition that associates the name `A` with an array of three values, each of which is a 32-bit unsigned integer: [source,fpp] ---- array A = [3] U32 ---- In general, to write an array type definition, you write the following: * The keyword `array`. * The <> of the array type. * An equals sign `=`. * An <> enclosed in square brackets `[` ... `]` denoting the size (number of elements) of the array. * A type name denoting the element type. The available type names are discussed below. Notice that the size expression precedes the element type, and the whole type reads left to right. For example, you may read the type `[3] U32` as "array of 3 `U32`." The size may be any legal expression. It doesn't have to be a literal integer. For example: [source,fpp] ---- constant numElements = 10 array A = [numElements] U32 ---- ==== Type Names The following type names are available for the element types: * The type names `U8`, `U16`, `U32`, and `U64`, denoting the type of unsigned integers of width 8, 16, 32, and 64 bits. * The type names `I8`, `I16`, `I32`, and `I64`, denoting the type of signed integers of width 8, 16, 32, and 64 bits. * The type names `F32` and `F64`, denoting the type of floating-point values of width 32 and 64 bits. * The type name `bool`, denoting the type of Boolean values (`true` and `false`). * The type name `string`, denoting the type of <>. This type has a default maximum size. For example: + [source,fpp] ---- # A is an array of 3 strings with default maximum size array A = [3] string ---- * The type name `string size` _e_, where _e_ is a numeric expression specifying a maximum string size. + [source,fpp] ---- # A is an array of 3 strings with maximum size 40 array A = [3] string size 40 ---- * A name associated with another type definition. In particular, an array definition may have another array definition as its element type; this situation is discussed further below. An array type definition may not refer to itself (array type definitions are not recursive). For example, this definition is illegal: [source,fpp] -------- array A = [3] A # Illegal: the definition of A may not refer to itself -------- ==== Default Values Optionally, you can specify a default value for an array type. To do this, you write the keyword `default` and an expression that evaluates to an <>. For example, here is an array type `A` with default value `[ 1, 2, 3 ]`: [source,fpp] ---- array A = [3] U32 default [ 1, 2, 3 ] ---- A default value expression need not be a literal array value; it can be any expression with the correct type. For example, you can create a named constant with an array value and use it multiple times, like this: [source,fpp] ---- constant a = [ 1, 2, 3 ] array A = [3] U8 default a # default value is [ 1, 2, 3 ] array B = [3] U16 default a # default value is [ 1, 2, 3 ] ---- If you don't specify a default value, then the type gets an automatic default value, consisting of the default value for each element. The default numeric value is zero, the default Boolean value is `false`, the default string value is `""`, and the default value of an array type is specified in the type definition. The type of the default expression must match the size and element type of the array, with type conversions allowed as discussed for <>. For example, this default expression is allowed, because we can convert integer values to floating-point values, and we can promote a single value to an array of three values: [source,fpp] ---- array A = [3] F32 default 1 # default value is [ 1.0, 1.0, 1.0 ] ---- However, these default expressions are not allowed: [source,fpp] -------- array A = [3] U32 default [ 1, 2 ] # Error: size does not match -------- [source,fpp] -------- array B = [3] U32 default [ "a", "b", "c" ] # Error: element type does not match -------- ==== Format Strings You can specify an optional *format string* which says how to display each element value and optionally provides some surrounding text. For example, here is an array definition that interprets three integer values as wheel speeds measured in RPMs: [source,fpp] ---- array WheelSpeeds = [3] U32 format "{} RPM" ---- Then an element with value 100 would have the format `100 RPM`. Note that the format string specifies the format for an _element_, not the entire array. The way an entire array is displayed is implementation-specific. A standard way is a comma-separated list enclosed in square brackets. For example, a value `[ 100, 200, 300 ]` of type `WheelSpeeds` might be displayed as `[ 100 RPM, 200 RPM, 300 RPM ]`. Or, since the format is the same for all elements, the implementation could display the array as `[ 100, 200, 300 ] RPM`. The special character sequence `{}` is called a *replacement field*; it says where to put the value in the format text. Each format string must have exactly one replacement field. The following replacement fields are allowed: * The field `{}` for displaying element values in their default format. * The field `{c}` for displaying a character value * The field `{d}` for displaying a decimal value * The field `{x}` for displaying a hexadecimal value * The field `{o}` for displaying an octal value * The field `{e}` for displaying a rational value in exponent notation, e.g., `1.234e2`. * The field `{f}` for displaying a rational value in fixed-point notation, e.g., `123.4`. * The field `{g}` for displaying a rational value in general format (fixed-point notation up to an implementation-dependent size and exponent notation for larger sizes). For field types `c`, `d`, `x`, and `o`, the element type must be an integer type. For field types `e`, `f`, and `g`, the element type must be a floating-point type. For example, the following format string is illegal, because type `string` is not an integer type: [source,fpp] -------- array A = [3] string format "{d}" # Illegal: string is not an integer type -------- For field types `e`, `f`, and `g`, you can optionally specify a precision by writing a decimal point and an integer before the field type. For example, the replacement field `{.3f}`, specifies fixed-point notation with a precision of 3. To include the literal character `{` in the formatted output, you can write `{{`, and similarly for `}` and `}}`. For example, the following definition [source,fpp] ---- array A = [3] U32 format "{{element {}}}" ---- specifies a format string `element {0}` for element value 0. No other use of `{` or `}` in a format string is allowed. For example, this is illegal: [source,fpp] -------- array A = [3] U32 format "{" # Illegal use of { character -------- You can include both a default value and a format; in this case, the default value must come first. For example: [source,fpp] ---- array WheelSpeeds = [3] U32 default 100 format "{} RPM" ---- If you don't specify an element format, then each element is displayed using the default format for its type. Therefore, omitting the format string is equivalent to writing the format string `"{}"`. ==== Arrays of Arrays An array type may have another array type as its element type. In this way you can construct an array of arrays. For example: [source,fpp] ---- array A = [3] U32 array B = [3] A # An array of 3 A, which is an array of 3 U32 ---- When constructing an array of arrays, you may provide any legal default expression, so long as the types are compatible. For example: [source,fpp] ---- array A = [2] U32 default 10 # default value is [ 10, 10 ] array B1 = [2] A # default value is [ [ 10, 10 ], [ 10, 10 ] ] array B2 = [2] A default 1 # default value is [ [ 1, 1 ], [ 1, 1 ] ] array B3 = [2] A default [ 1, 2 ] # default value is [ [ 1, 1 ], [ 2, 2 ] ] array B4 = [2] A default [ [ 1, 2 ], [ 3, 4 ] ] ---- === Struct Type Definitions A *struct type definition* associates a name with a *struct type*. A struct type describes the shape of a <>. It specifies a mapping from element names to their types. As discussed below, it also specifies a serialization order for the struct elements. ==== Writing a Struct Type Definition As an example, here is a struct type definition that associates the name `S` with a struct type containing two members: `x` of type `U32`, and `y` of type `string`: [source,fpp] ---- struct S { x: U32, y: string } ---- In general, to write a struct type definition, you write the following: * The keyword `struct`. * The <> of the struct type. * A sequence of *struct type members* enclosed in curly braces `{` ... `}`. A struct type member consists of a name, a colon, and a <>, for example `x: U32`. The struct type members form an <> in which the optional terminating punctuation is a comma. As usual for element sequences, you can omit the comma and use a newline instead. So, for example, we can write the definition shown above in this alternate way: [source,fpp] ---- struct S { x: U32 y: string } ---- ==== Annotating a Struct Type Definition As noted in the beginning of this section, a type definition is an annotatable element, so you can attach pre and post annotations to it. A struct type member is also an annotatable element, so any struct type member can have pre and post annotations as well. Here is an example: [source,fpp] ---- @ This is a pre annotation for struct S struct S { @ This is a pre annotation for member x x: U32 @< This is a post annotation for member x @ This is a pre annotation for member y y: string @< This is a post annotation for member y } @< This is a post annotation for struct S ---- ==== Default Values You can specify an optional default value for a struct definition. To do this, you write the keyword `default` and an expression that evaluates to a <>. For example, here is a struct type `S` with default value `{ x = 1, y = "abc" }`: [source,fpp] ---- struct S { x: U32, y: string } default { x = 1, y = "abc" } ---- A default value expression need not be a literal struct value; it can be any expression with the correct type. For example, you can create a named constant with a struct value and use it multiple times, like this: [source,fpp] ---- constant s = { x = 1, y = "abc" } struct S1 { x: U8, y: string } default s struct S2 { x: U32, y: string } default s ---- If you don't specify a default value, then the struct type gets an automatic default value, consisting of the default value for each member. The type of the default expression must match the type of the struct, with type conversions allowed as discussed for <>. For example, this default expression is allowed, because we can convert integer values to floating-point values, and we can promote a single value to a struct with numeric members: [source,fpp] ---- struct S { x: F32, y: F32 } default 1 # default value is { x = 1.0, y = 1.0 } ---- And this default expression is allowed, because if we omit a member of a struct, then FPP will fill in the member and give it the default value: [source,fpp] ---- struct S { x: F32, y: F32 } default { x = 1 } # default value is { x = 1.0, y = 0.0 } ---- However, these default expressions are not allowed: [source,fpp] -------- struct S1 { x: U32, y: string } default { z = 1 } # Error: member z does not match -------- [source,fpp] -------- struct S2 { x: U32, y: string } default { x = "abc" } # Error: type of member x does not match -------- ==== Member Arrays For any struct member, you can specify that the member is an array of elements. To do this you, write an array the size enclosed in square brackets before the member type. For example: [source,fpp] ---- struct S { x: [3] U32 } ---- This definition says that struct `S` has one element `x`, which is an array consisting of three `U32` values. We call this array a *member array*. *Member arrays vs. array types:* Member arrays let you include an array of elements as a member of a struct type, without defining a separate <>. Also, member arrays generate less code than named arrays. Whereas a member size array is a native {cpp} array, each named array is a {cpp} class. On the other hand, defining a named array is usually a good choice when * You want to use the array outside of any structure. * You want the convenience of a generated array class, which has a richer interface than the bare {cpp} array. In particular, the generated array class provides *bounds-checked* access operations: it causes a runtime failure if an out-of-bounds access occurs. The bounds checking provides an additional degree of memory safety when accessing array elements. *Member arrays and default values:* FPP ignores member array sizes when checking the types of default values. For example, this code is accepted: [source,fpp] ---- struct S { x: [3] U32 } default { x = 10 } ---- The member `x` of the struct `S` gets three copies of the value 10 specified for `x` in the default value expression. ==== Member Format Strings For any struct member, you can include an optional format. To do this, write the keyword `format` and a format string. The format string for a struct member has the same form as for an <>. For example, the following struct definition specifies that member `x` should be displayed as a hexadecimal value: [source,fpp] ---- struct Channel { name: string offset: U32 format "offset 0x{x}" } ---- How the entire struct is displayed depends on the implementation. As an example, the value of `S` with `name = "momentum"` and `offset = 1024` might look like this when displayed: ---- Channel { name = "momentum", offset = 0x400 } ---- If you don't specify a format for a struct member, then the system uses the default format for the type of that member. If the member has a size greater than one, then the format is applied to each element. For example: [source,fpp] ---- struct Telemetry { velocity: [3] F32 format "{} m/s" } ---- The format string is applied to each of the three elements of the member `velocity`. ==== Struct Types Containing Named Types A struct type may have an array or struct type as a member type. In this way you can define a struct that has arrays or structs as members. For example: [source,fpp] ---- array Speeds = [3] U32 # Member speeds has type Speeds, which is an array of 3 U32 values struct Wheel { name: string, speeds: Speeds } ---- When initializing a struct, you may provide any legal default expression, so long as the types are compatible. For example: [source,fpp] ---- array A = [2] U32 struct S1 { x: U32, y: string } # default value is { s1 = { x = 0, y = "" }, a = [ 0, 0 ] } struct S2 { s1: S1, a: A } # default value is { s1 = { x = 0, y = "abc" }, a = [ 5, 5 ] } struct S3 { s1: S1, a: A } default { s1 = { y = "abc" }, a = 5 } ---- ==== The Order of Members For <>, we said that the order in which the members appear in the value is not significant. For example, the expressions `{ x = 1, y = 2 }` and `{ y = 2, x = 1 }` denote the same value. For struct types, the rule is different. The order in which the members appear is significant, because it governs the order in which the members appear in the generated code. For example, the type `struct S1 { x: U32, y : string }` might generate a {cpp} class `S1` with members `x` and `y` laid out with `x` first; while `struct S2 { y : string, x : U32 }` might generate a {cpp} class `S2` with members `x` and `y` laid out with `y` first. Since class members are generally serialized in the order in which they appear in the class, the members of `S1` would be serialized with `x` first, and the members of `S2` would be serialized with `y` first. Serializing `S1` to data and then trying to deserialize it to `S2` would produce garbage. The order matters only for purposes of defining the type, not for assigning default values to it. For example, this code is legal: [source,fpp] ---- struct S { x: U32, y: string } default { y = "abc", x = 5 } ---- FPP struct _values_ have no inherent order associated with their members. However, once those values are assigned to a named struct _type_, the order becomes fixed. === Abstract Type Definitions An array or struct type definition specifies a complete type: in addition to the name of the type, it provides the names and types of all the members. An *abstract type*, by contrast, has an incomplete or opaque definition. It provides only a name _N_. Its purpose is to tell the analyzer that a type with name _N_ exists and will be defined elsewhere. For example, if the target language is {cpp}, then the type is a {cpp} class. To define an abstract type, you write the keyword `type` followed by the name of the type. For example, you can define an abstract type `T`; then you can construct an array `A` with member type `T`: [source,fpp] ---- type T # T is an abstract type array A = [3] T # A is an array of 3 values of type T ---- This code says the following: * A type `T` exists. It is defined in the implementation, but not in the model. * `A` is an array of three values, each of type `T`. Now suppose that the target language is {cpp}. Then the following happens when generating code: * The definition `type T` does not cause any code to be generated. * The definition `array A =` ... causes a {cpp} class `A` to be generated. By F Prime convention, the generated files are `AArrayAc.hpp` and `AArrayAc.cpp`. * File `AArrayAc.hpp` includes a header file `T.hpp`. It is up to the user to implement a {cpp} class `T` with a header file `T.hpp`. This header file must define `T` in a way that is compatible with the way that `T` is used in `A`. We will have more to say about this topic in the section on <>. In general, an abstract type `T` is opaque in the FPP model and has no values that are expressible in the model. Thus, every use of an abstract type `T` represents the default value for `T`. The implementation of `T` in the target language provides the default value. In particular, when the target language is {cpp}, the default value is the zero-argument constructor `T()`. === Alias Type Definitions An *alias type definition* provides an alternate name for a type that is defined elsewhere. The alternate name is called an *alias* of the original type. For example, here is an alias type definition specifying that the type `T` is an alias of the type `U32`: [source,fpp] ---- type T = U32 ---- Wherever this definition is available, the type `T` may be used interchangeably with the type `U32`. For example: [source,fpp] ---- type T = U32 array A = [3] T ---- An alias type definition may refer to any type, including another alias type, except that it may not refer to itself, either directly or through another alias. For example, here is an alias type definition specifying that the type `T` is an alias of the struct type `S`: [source,fpp] ---- struct S { x: U32, y: I32 } type T = S ---- Here is a pair of definitions specifying that `S` is an alias of `T`, and `T` is an alias of `F32`: [source,fpp] ---- type S = T type T = F32 ---- Here is a pair of alias type definitions that is illegal, because each of the definitions indirectly refers to itself: [source,fpp] -------- type S = T type T = S -------- Alias type definitions are useful for specifying configurations. Part of a model can use a type `T`, without defining `T`; elsewhere, configuration code can define `T` to be the alias of another type. F Prime uses this method to provide basic type whose definitions configure the framework. These types are defined in the file `config/FpConfig.fpp`. === Framework Types Certain types defined in FPP have a special meaning in the F Prime framework. These types are called *framework types*. For example, the type `FwOpcodeType` defines the type of a command opcode. (Commands are an F Prime feature that we describe <>.) Framework types are like <>: you typically define them by overriding configuration files provided by F Prime, and if they are defined, then they must conform to certain rules. https://nasa.github.io/fpp/fpp-spec.html#Definitions_Framework-Definitions_Type-Definitions[_The FPP Language Specification_] has all the details.