fpp/docs/users-guide/Defining-Constants.adoc
2025-11-02 12:29:49 -08:00

838 lines
21 KiB
Plaintext

== Defining Constants
The simplest FPP model consists of one or more *constant definitions*.
A constant definition associates a name with a value,
so that elsewhere you can use the name instead of re-computing or restating the
value.
Using named constants makes the model easier to understand (the name
says what the value means) and to maintain (changing a constant definition is
easy;
changing all and only the relevant uses of a repeated value is not).
This section covers the following topics:
* Writing an FPP constant definition.
* Writing an *expression*, which is the source text
that defines the value associated with the constant definition.
* Writing multiple constant definitions.
* Writing a constant definition that spans two or more lines of source text.
=== Writing a Constant Definition
To write a constant definition, you write the keyword `constant`,
an equals sign, and an expression.
A <<Defining-Constants_Expressions,later section>>
describes all the expressions you can write.
Here is an example that uses an integer literal expression representing
the value 42:
[source,fpp]
----
constant ultimateAnswer = 42
----
This definition associates the name `ultimateAnswer` with the value 42.
Elsewhere in the FPP model you can use the name `ultimateAnswer` to represent
the value.
You can also generate a {cpp} header file that defines the {cpp} constant
`ultimateAnswer` and gives it the value 42.
As an example, do the following:
* On the command line, run `fpp-check`.
* When the prompt appears, type the text shown above, type return, type control-D, and type return.
You should see something like the following on your console:
----
% fpp-check
constant ultimateAnswer = 42
^D
%
----
As an example of an incorrect model that produces an error message, repeat the
exercise, but omit the value 42.
You should see something like this:
----
% fpp-check
constant ultimateAnswer =
^D
fpp-check
stdin: end of input
error: expression expected
----
Here the `fpp-check` tool is telling you that it could not parse the input:
the input ended where it expected an expression.
=== Names
Names in FPP follow the usual rules for identifiers in a programming language:
* A name must contain at least one character.
* A name must start with a letter or underscore character.
* The characters after the first may be letters, numbers, or underscores.
For example:
* `name`, `Name`, `_name`, and `name1` are valid names.
* `1invalid` is not a valid name, because names may not start with digits.
==== Reserved Words
Certain sequences of letters such as `constant` are called out as *reserved
words* (also called keywords) in FPP.
Each reserved word has a special meaning, such as introducing a constant
declaration.
_The FPP Language Specification_ has a complete list of reserved words.
In this document, we will introduce reserved words as needed to explain
the language features.
Using a reserved word as a name in the ordinary way causes a parsing error.
For example, this code is incorrect:
[source,fpp]
--------
constant constant = 0
--------
To use a reserved word as a name, you must put the character `$` in
front of it with no space.
For example, this code is legal:
[source,fpp]
----
constant $constant = 0
----
The character sequence `$constant` represents the name `constant`,
as opposed to the keyword `constant`.
You can put the character `$` in front of any identifier,
not just a keyword.
If the identifier is not a keyword, then the `$` has no effect.
For example, `$name` has the same meaning as `name`.
==== Name Clashes
FPP will not let you define two different symbols of the same
kind with the same name.
For example, this code will produce an error:
[source,fpp]
--------
constant c = 0
constant c = 1
--------
Two symbols can have the same unqualified name if they
reside in different
<<Defining-Modules,modules>> or
<<Defining-Enums,enums>>; these concepts
are explained below.
Two symbols can also have the same name if the analyzer
can distinguish them based on their kinds.
For example, an
<<Defining-Types_Array-Type-Definitions, array type>> (described below) and a
constant
can have the same name, but an array type and a
<<Defining-Types_Struct-Type-Definitions,struct type>> may not.
_The FPP Language Specification_ has all the details.
=== Expressions
This section describes the expressions that you can write as part of a constant
definition.
Expressions appear in other FPP elements as well, so we will refer back
to this section in later sections of the manual.
==== Primitive Values
A *primitive value expression* represents a primitive machine value, such as an
integer.
It is one of the following:
* A decimal integer literal value such as `1234`.
* A hexadecimal integer literal value such as `0xABCD` or `0xabcd`.
* A floating-point literal value such as `12.34` or `1234e-2`.
* A Boolean literal expression `true` or `false`.
As an exercise, construct some constant definitions with primitive values as their
expressions, and
feed the results to `fpp-check`.
For example:
[source,fpp]
----
constant a = 1234
constant b = 0xABCD
----
If you get an error, make sure you understand why.
==== String Values
A *string value* represents a string of characters.
There are two kinds of string values:
single-line strings and multiline strings.
*Single-line strings:*
A single-line string represents a string of characters
that does not contain a newline character.
It is written as a string of characters enclosed in double quotation
marks `"`.
For example:
[source,fpp]
----
constant s = "This is a string."
----
To put the double-quote character in a string, write the double quote
character as `\"`, like this:
[source,fpp]
----
constant s = "\"This is a quotation within a string,\" he said."
----
To encode the character `\` followed by the character `"`, write
the backslash character as `\\`, like this:
[source,fpp]
----
constant s = "\\\""
----
This string represents the literal character sequence `\`, `"`.
In general, the sequence `\` followed by a character _c_
is translated to _c_.
This sequence is called an *escape sequence*.
*Multiline strings:*
A multiline string represents a string of characters
that may contain a newline character.
It is enclosed in a pair of sequences of three double quotation
marks `"""`.
For example:
[source,fpp]
----
constant s = """
This is a multiline string.
It has three lines.
"""
----
When interpreting a multiline string, FPP ignores any newline
characters at the start and end of the string.
FPP also ignores any blanks to the left of the column where
the first `"""` appears.
For example, the string shown above consists of three lines and starts
with `This`.
Literal quotation marks are allowed inside a multiline string:
[source,fpp]
----
constant s = """
"This is a quotation within a string," he said.
"""
----
Escape sequences work as for single-line strings.
For example:
[source,fpp]
----
constant s = """
Here are three double-quote characters in a row: \"\"\"
"""
----
==== Array Values
An *array value expression* represents a fixed-size array
of values.
To write an array value expression, you write a comma-separated list of one or more values
(the array elements)
enclosed in square brackets.
Here is an example:
[source,fpp]
----
constant a = [ 1, 2, 3 ]
----
This code associates the name `a` with the array of
integers
`[ 1, 2, 3 ]`.
As mentioned in the introduction, an FPP model describes the structure of a FSW
application; the computations are specified in a target
language such as {cpp}.
As a result, FPP does not provide an array indexing operation.
In particular, it does not specify the index of the leftmost array element;
that is up to the target language.
For example, if the target language is {cpp}, then array indices start
at zero.
Here are some rules for writing array values:
. An array value must have at least one element.
That is, `[]` is not a valid array value.
. The types of the elements must match.
For example, the following code is illegal, because the value `1` (which has type `Integer`)
and the value `"abcd"` (which has type `string`) are incompatible:
+
[source,fpp]
--------
constant mismatch = [ 1, "abcd" ]
--------
Try entering this example into `fpp-check` and see what happens.
What does it mean for types to match?
_The FPP Specification_ has all the details, and we won't attempt
to repeat them here.
In general, things work as you would expect: for example, we can convert
an integer value to a floating-point value, so the following code is allowed:
[source,fpp]
----
constant a = [ 1, 2.0 ]
----
It evaluates to an array of two floating-point values.
If you are not sure whether a type conversion is allowed, you can
ask `fpp-check`.
For example: can we convert a Boolean value to an integer value?
In older languages like C and {cpp} we can, but in many newer languages
we can't. Here is the answer in FPP:
----
% fpp-check
constant a = [ 1, true ]
^D
fpp-check
stdin: 1.16
constant a = [ 1, true ]
^
error: cannot compute common type of Integer and bool
----
So no, we can't.
Here are two more points about array values:
. Any legal value can be an element of an array value, so in particular arrays
of arrays
are allowed. For example, this code is allowed:
+
[source,fpp]
----
constant a = [ [ 1, 2 ], [ 3, 4 ] ]
----
+
It represents an array with two elements: the array `[ 1, 2 ]` and the array `[
3, 4 ]`.
. To avoid repeating values, a numeric, string, or Boolean value is automatically promoted to an array
of appropriate size whenever necessary to make the types work.
For example, this code is allowed:
+
[source,fpp]
----
constant a = [ [ 1, 2, 3 ], 0 ]
----
+
It is equivalent to this:
+
[source,fpp]
----
constant a = [ [ 1, 2, 3 ], [ 0, 0, 0 ] ]
----
==== Array Elements
An *array element expression* represents an element of an array value.
To write an array element expression, you write an array value
followed by an index expression enclosed in square brackets.
The index expression must resolve to a number.
The number specifies the index of the array element, where the
array indices start at zero.
Here is an example:
[source,fpp]
----
constant a = [ 1, 2, 3 ]
constant b = a[1]
----
In this example, the constant `b` has the value 2.
The index expression must resolve to a number that is in range
for the array.
For example, this code is incorrect, because `"hello"` is not a number:
[source,fpp]
--------
constant a = [ 1, 2, 3 ]
constant b = a["hello"]
--------
This code is incorrect because 3 is not in the range [0, 2]:
[source,fpp]
--------
constant a = [ 1, 2, 3 ]
constant b = a[3]
--------
==== Struct Values
A *struct value expression* represents a C- or {cpp}-style structure, i.e., a
mapping
of names to values.
To write a struct value expression, you write a comma-separated list of zero or
more *struct members*
enclosed in curly braces.
A struct member consists of a name, an equals sign, and a value.
Here is an example:
[source,fpp]
----
constant s = { x = 1, y = "abc" }
----
This code associates the name `s` with a struct value.
The struct value has two members `x` and `y`.
Member `x` has the integer value 1, and member `y` has the string value `"abc"`.
*The order of members:* When writing a struct value, the order in which the
members appear does not matter.
For example, in the following code, constants `s1` and `s2` denote the same
value:
[source,fpp]
----
constant s1 = { x = 1, y = "abc" }
constant s2 = { y = "abc", x = 1 }
----
*The empty struct:* The empty struct is allowed:
[source,fpp]
----
constant s = {}
----
*Arrays in structs:* You can write an array value as a member of a struct value.
For example, this code is allowed:
[source,fpp]
----
constant s = { x = 1, y = [ 2, 3 ] }
----
*Structs in arrays:* You can write a struct value as a member of an array value.
For example, this code is allowed:
[source,fpp]
----
constant a = [ { x = 1, y = 2 }, { x = 3, y = 4 } ]
----
This code is not allowed, because the element types don't match -- an array is not
compatible with a struct.
[source,fpp]
--------
constant a = [ { x = 1, y = 2 }, [ 3, 4 ] ]
--------
However, this code is allowed:
[source,fpp]
----
constant a = [ { x = 1, y = 2 }, { x = 3 } ]
----
Notice that the first member of `a` is a struct with two members `x` and `y`.
The second member of `a` is also a struct, but it has only one member `x`.
When the FPP analyzer detects that a struct type is missing a member,
it automatically adds the member, giving it a default value.
The default values are the ones you would expect: zero for numeric members, the empty
string for string members, and `false` for Boolean members.
So the code above is equivalent to the following:
[source,fpp]
----
constant a = [ { x = 1, y = 2 }, { x = 3, y = 0 } ]
----
==== Struct Members
A *struct member expression* represents a member of a struct value.
To write a struct member expression, you write a struct value followed
by a dot and the member name.
For example:
[source,fpp]
----
constant a = { x = 1, y = [ 2, 3 ] }
constant b = a.y
----
Here `a` is a struct value with members `x` and `y`
The constant `b` has the array value `[ 2, 3 ]`, which is the
value stored in member `y` of `a`.
This code is incorrect because `a` is not a struct value:
[source,fpp]
--------
constant a = 0
constant b = a.y
--------
This code is incorrect because `z` is not a member of `a`:
[source,fpp]
--------
constant a = { x = 1, y = [ 2, 3 ] }
constant b = a.z
--------
==== Name Expressions
A *name expression* is a use of a name appearing in a constant definition.
It stands for the associated constant value.
For example:
[source,fpp]
----
constant a = 1
constant b = a
----
In this code, constant `b` has the value 1.
The order of definitions does not matter, so this code is equivalent:
[source,fpp]
----
constant b = a
constant a = 1
----
The only requirement is that there may not be any cycles in the graph
consisting of constant definitions and their uses.
For example, this code is illegal, because there is a cycle from `a` to `b` to
`c` and back to `a`:
[source,fpp]
--------
constant a = c
constant b = a
constant c = b
--------
Try submitting this code to `fpp-check`, to see what happens.
Names like `a`, `b`, and `c` are simple or unqualified names.
Names can also be qualified: for example `A.a` is allowed.
We will discuss qualified names further when we introduce
module definitions and enum definitions below.
==== Value Arithmetic Expressions
A *value arithmetic expression* performs arithmetic on values.
It is one of the following:
* A negation expression, for example:
+
[source,fpp]
----
constant a = -1
----
* A binary operation expression, where the binary operation is one of `+` (addition), `-` (subtraction),
`*` (multiplication), and `/` (division). For example:
+
[source,fpp]
----
constant a = 1 + 2
----
* A parenthesis expression, for example:
+
[source,fpp]
----
constant a = (1)
----
The following rules apply to arithmetic expressions:
* The subexpressions must be integer or floating-point values.
* If there are any floating-point subexpressions, then the entire expression is
evaluated using 64-bit floating-point arithmetic.
* Otherwise the expression is evaluated using arbitrary-precision integer
arithmetic.
* In a division operation, the second operand may not be zero or (for
floating-point values) very close to zero.
==== Compound Expressions
Wherever you can write a value inside an expression, you can write
a more complex expression there, so long as the types work out.
For example, these expressions are valid:
[source,fpp]
----
constant a = (1 + 2) * 3
constant b = [ 1 + 2, 3 ]
----
The first example is a binary expression whose first operand is a parentheses
expression;
that parentheses expression in turn has a binary expression as its
subexpression.
The second example is an array expression whose first element is a binary
expression.
This expression is invalid, because `1 + 2.0` evaluates to a floating-point
value, which is incompatible with type `string`:
[source,fpp]
--------
constant a = [ 1 + 2.0, "abc" ]
--------
Compound expressions are evaluated in the obvious way.
For example, the constant definitions above are equivalent to the following:
[source,fpp]
----
constant a = 9
constant b = [ 3, 3 ]
----
For compound arithmetic expressions, the
precedence and associativity rules are the usual ones (evaluate parentheses
first, then multiplication, and so forth).
=== Multiple Definitions and Element Sequences
Typically you want to specify several definitions in a model source file, not
just one.
There are two ways to do this:
. You can separate the definitions by one or more newlines, as shown
in the examples above.
. You can put the definitions on the same line, separated by a semicolon.
For example, the following two code excerpts are equivalent:
[source,fpp]
----
constant a = 1
constant b = 2
----
[source,fpp]
----
constant a = 1; constant b = 2
----
More generally, a collection of several constant definitions is an example
of an *element sequence*, i.e., a sequence of similar syntactic elements.
Here are the rules for writing an element sequence:
. Every kind of element sequence has optional *terminating punctuation*.
The terminating punctuation is either a semicolon or a comma, depending
on the kind of element sequence.
For constant definitions, it is a semicolon.
. When writing elements on separate lines, the terminating punctuation is
optional.
. When writing two or more elements on the same line, the terminating
punctuation is
required between the elements and optional after the last element.
=== Multiline Definitions
Sometimes, especially for long definitions, it is useful to split a
definition across two or more lines.
In FPP there are several ways to do this.
First, FPP ignores newlines that follow opening symbols like `[` and precede
closing symbols like `]`.
For example, this code is allowed:
[source,fpp]
----
constant a = [
1, 2, 3
]
----
Second, the elements of an array or struct form an element sequence (see
the previous section), so you can write each element on its own line,
omitting the commas if you wish:
[source,fpp]
----
constant s = {
x = 1
y = 2
z = 3
}
----
This is a clean way to write arrays and structs.
The assignment of each element to its own line and the lack of
terminating punctuation
make it easy to rearrange the elements.
In particular, one can do a line-by-line sort on the elements (for example, to
sort struct members alphabetically by name) without concern for messing up the
arrangement of commas.
If we assume that the example represents the first five lines of a source file,
then in vi this is easily done as `:2-4!sort`.
Third, FPP ignores newlines that follow connecting symbols such as `=` and `+`
For example, this code is allowed;
[source,fpp]
----
constant a =
1
constant b = 1 +
2
----
Finally, you can always create an explicit line continuation by escaping
one or more newline characters with `\`:
[source,fpp]
----
constant \
a = 1
----
Note that in this example you need the explicit continuation, i.e., this code
is not legal:
[source,fpp]
--------
constant
a = 1
--------
=== Framework Constants
Certain constants defined in FPP have a special meaning in the
F Prime framework.
These constants are called *framework constants*.
For example, the constant `Fw.DpCfg.CONTAINER_USER_DATA_SIZE`
defines the size of the user data field in a data product container.
(Data products are an F Prime feature that we describe
<<Defining-Components_Data-Products,in a later section of this manual>>.)
You typically set these constants by overriding configuration
files provided in the directory `default/config` in the F Prime repository.
For example, the file `default/config/DpCfg.fpp` provides a default value for
`Fw.DpCfg.CONTAINER_USER_DATA_SIZE`.
You can override this default value by providing your own version of
the file `DpCfg.fpp`.
The
https://fprime.jpl.nasa.gov/devel/docs/user-manual/framework/configuring-fprime/[F
Prime User Manual]
explains how to do this configuration.
The FPP analyzer does not require that framework constants be defined
unless they are used.
For example, the following model is valid, because it neither defines nor users
`Fw.DpCfg.CONTAINER_USER_DATA_SIZE`:
[source,fpp]
----
constant a = 0
----
The following model is valid because it defines and uses `Fw.DpCfg.CONTAINER_USER_DATA_SIZE`:
[source,fpp]
----
module Fw {
module DpCfg {
constant CONTAINER_USER_DATA_SIZE = 10
}
}
constant a = Fw.DpCfg.CONTAINER_USER_DATA_SIZE
----
The following model is invalid, because it uses `Fw.DpCfg.CONTAINER_USER_DATA_SIZE`
without defining it:
[source,fpp]
--------
constant a = Fw.DpCfg.CONTAINER_USER_DATA_SIZE
--------
If framework constants are defined in the FPP model, then
then they must conform to certain rules.
These rules are spelled out in detail in the
https://nasa.github.io/fpp/fpp-spec.html#Definitions_Framework-Definitions[_The
FPP Language Specification_].
For example, `Fw.DpCfg.CONTAINER_USER_DATA_SIZE` must have an integer type.
So this model is invalid:
[source,fpp]
--------
module Fw {
module DpCfg {
constant CONTAINER_USER_DATA_SIZE = "abc"
}
}
--------
Here is what happens when you run this model through `fpp-check`:
----
% fpp-check
module Fw {
module DpCfg {
constant CONTAINER_USER_DATA_SIZE = "abc"
}
}
^D
fpp-check
stdin:5.5
constant CONTAINER_USER_DATA_SIZE = "abc"
^
error: the F Prime framework constant Fw.DpCfg.CONTAINER_USER_DATA_SIZE must have an integer type
----