1. Introduction
This document provides a detailed description of F Prime Prime, also known as FPP or F Double Prime. FPP is a modeling language for the F Prime flight software framework. A paper presented at SmallSat 2018 provides an overview of F Prime.
The goals of FPP are as follows:
-
To provide a modeling language for F Prime that is simple, easy to use, and well-tailored to the domain.
-
To provide semantic checking and error reporting for F Prime models.
-
To generate code in the various formats that F Prime uses, e.g., C++, Python, XML.
The purpose of this document is to describe FPP with enough precision and detail to guide an implementation. It is primarily aimed at language implementers, although FPP users may also benefit from the level of precision and detail that it provides. It is recommended that FPP users read The FPP User’s Guide before consulting this document.
2. Syntax Notation
Throughout this document, we use the following notation to describe language syntax:
-
fixed-width font
denotes literal model text. -
italics denote grammar nonterminals.
-
Italicized brackets [ ] enclose optional elements.
3. Lexical Elements
Before parsing an FPP model, the compiler converts the source text into a list of tokens. This process is called lexing.
A token is one of the following:
-
A symbol.
-
An identifier.
-
An integer literal.
-
A line of an annotation beginning with
@
or@<
.
3.1. Reserved Words
The following are reserved words in FPP.
They may not be used as identifiers, except
when preceded by the $
character, as discussed below.
F32
F64
I16
I32
I64
I8
U16
U32
U64
U8
active
activity
always
array
assert
async
at
base
block
bool
change
command
component
connections
constant
cpu
default
diagnostic
drop
enum
event
false
fatal
format
get
guarded
health
high
id
import
include
input
instance
internal
locate
low
match
module
on
opcode
orange
output
param
passive
phase
port
priority
private
queue
queued
recv
red
ref
reg
resp
save
serial
set
severity
size
stack
string
struct
sync
telemetry
text
throttle
time
topology
true
type
update
warning
with
yellow
3.2. Symbols
The following sequences of characters are symbol tokens in FPP:
(
)
*
+
,
-
->
.
/
:
;
=
[
]
{
}
3.3. Identifiers
Definition: An identifier is an unqualified name consisting of one or more characters. The first character must be a letter or an underscore. Characters after the first character may be letters, digits, and underscores.
For example:
-
identifier
,identifier3
, and__identifier3
are valid identifiers. -
3identifier
is not a valid identifier, because it starts with a digit. -
identifier%
is not a valid identifier, because it contains the character%
.
Escaped keywords: Any identifier may be preceded by the character $
, with
no intervening space.
An identifier $
I has the same meaning as I, except that if I is a
reserved word, then I is scanned
as an identifier and not as a reserved word.
For example:
-
$identifier
is a valid identifier. It has the same meaning asidentifier
. -
$time
is a valid identifier. It represents the character sequencetime
treated as an identifier and not as a reserved word. -
time
is a reserved word. It may not be used as an identifier.
3.4. End-of-Line Tokens
An end-of-line token is a sequence of one or more newlines. A newline (or line break) is the NL character (ASCII code 0x0A), optionally preceded by a CR character (ASCII code 0x0D). End-of-line tokens separate the elements of element sequences.
3.5. Comments
The lexer ignores comments. Specifically:
-
A comment followed by a newline is treated as a newline.
-
A comment at the end of a file, not followed by a newline, is treated as no text.
3.6. Whitespace and Non-Printable Characters
Apart from end-of-line tokens, the lexer treats whitespace as follows:
-
Space characters are ignored, except to separate tokens.
-
No other whitespace or non-printable characters are allowed outside of a string, comment, or annotation. In particular, the tab character may not appear in an FPP model outside of a string, comment, or annotation.
3.7. Explicit Line Continuations
The character \
, when appearing before a
newline,
suppresses the newline.
Both the \
and the following newline are ignored, and no
end-of-line token,
is created.
For example, this
constant a \
= 1
is equivalent to this:
constant a = 1
Note that \
is required in this case.
For example, the following is not syntactically correct:
constant a # Error
= 1
The newline indicates the end of an element sequence, but
constant a
is not a valid element sequence.
3.8. Automatic Suppression of Newlines
The following symbols consume sequences of newlines that follow them, without creating an end-of-line token:
(
*
+
,
-
->
/
:
;
=
[
{
For example, the following code is legal:
module M {
constant a = 0
}
It is equivalent to this code:
module M { constant a = 0 }
The newline after the {
symbol is consumed by the symbol.
The newline after the constant definition is consumed
by the element sequence member.
The following code is also legal, because the newline is
consumed by the =
symbol:
constant a =
0
Similarly, the following code is legal, because the newline
is consumed by the +
symbol:
constant a = 1 +
2
3.9. Collapsing of Newlines
To simplify parsing, the lexer may collapse a sequence of one or more end-of-line tokens into a single token, or into no token, if the operation does not change the meaning of the result according to the parse rules. For example, the lexer may treat this code
constant a = [
1
2
3
]
as if it were this
constant a = [
1
2
3
]
or this
constant a = [
1
2
3 ]
According to the rules for parsing element sequences, all three code sections are equivalent.
4. Element Sequences
An element sequence is a sequence of similar elements, e.g., the elements of a translation unit.
Each element of an element sequence has optional terminating punctuation. The punctuation is either a comma or a semicolon, depending on the kind of sequence.
For each element e in the sequence:
-
You can always terminate e with a sequence of one or more newlines. In this case no terminating punctuation is required.
-
You can omit the newline. In this case the terminating punctuation is required, unless the element is last in the sequence or the element is followed by a post-annotation.
Examples:
A translation unit is a kind of element sequence. Here are some examples of element sequences using constant definitions as the elements of a translation unit:
# No terminating punctuation
constant a = 0
constant b = 1
# Optional terminating punctuation present
constant a = 0;
constant b = 1;
# Terminating punctuation required after the first element but not the second
constant a = 0; constant b = 1
# Error, because terminating punctuation is missing
constant a = 0 constant b = 1
# No terminating punctuation required because of the post-annotation
constant a = 0 @< This is OK
constant b = 1
An enum constant sequence is another example of an element sequence. Here are some element sequences using enumerated constant definitions as elements of an enum constant sequence:
# No terminating punctuation
enum E {
X = 0
Y = 1
}
# Optional terminating punctuation
enum E {
X = 0,
Y = 1,
}
# Terminating punctuation required after the first element but not the second
enum E { X = 0, Y = 1 }
5. Definitions
A definition is a unit of syntax that introduces a name N and associates some code with N.
5.1. Abstract Type Definitions
An abstract type definition associates a name with a type without specifying the type. In generated code, the type is specified externally (e.g., as a C++ class).
5.1.1. Syntax
type
identifier
5.1.2. Semantics
The identifier is the name N of the type. The definition associates the name N with an unspecified type T'. The name N can be used as a type elsewhere in the model. There are no explicit values associated with N in the model.
5.1.3. Examples
# Defines an abstract type A
type A
# Defines a struct type B whose member x has type A
struct B {
x: A
y: F32
} default { y = 1 }
5.2. Array Definitions
An array definition defines a new array type and associates a name with it.
5.2.1. Syntax
array
identifier =
[
expression ]
type-name
[
default
expression
]
[
format
string-literal
]
5.2.2. Semantics
The identifier is the name N of the new type. The first expression must evaluate to a compile-time constant numeric value n whose value is greater than zero and less than or equal to 256. type-name names the type T of each array element.
The definition associates the name N with a new type T' that represents an array of n elements of type T.
The expression following the keyword default
is optional.
If present, it specifies the default value associated
with the type.
The type of the expression must be
convertible to T'.
The optional format specifier specifies a format string. When displaying the array, the format is applied to each element of the array. There is one argument to the format string, which is an array member.
5.2.3. Examples
# Defines an array type A of 3 U8 elements with default value [ 0, 0, 0 ]
array A = [3] U8
# Defines an array type B of 2 A elements with default value
# [ [ 0, 0, 0 ], [ 0, 0, 0 ] ]
array B = [3] A
# Defines an array type C of 3 F32 elements with default value [ 1, 2, 3 ]
array C = [3] F32 default [ 1, 2, 3 ]
# Defines an array type C of 3 U32 values with default value
# [ 1, 1, 1 ] after promoting 1 to [ 1, 1, 1 ]
array D = [3] U32 default 1
# Defines an array type E of 3 U32 values with default value
# [ 1, 1, 1, ] and element format {.03f}
array E = [3] U32 default 1 format "{.03f}"
5.3. Component Definitions
A component definition defines an F Prime component. A component is a unit of function in the F Prime framework. Component instances communicate with each other across ports.
5.3.1. Syntax
component-kind component
identifier
{
component-member-sequence }
component-kind is one of the following:
-
active
-
passive
-
queued
component-member-sequence is an element sequence in which each element is a component member, and the terminating punctuation is a semicolon. A component member is one of the following:
-
A command specifier. All commands specified in a component must have distinct names and opcodes.
-
A parameter specifier. All parameters specified in a component must have distinct names and identifier values.
-
A telemetry channel specifier. All telemetry channels specified in a component must have distinct names and identifier values.
-
An event specifier. All events specified in a component must have distinct names and identifier values.
The command, event, parameter, and telemetry specifiers are collectively referred to as dictionary specifiers.
5.3.2. Semantics
The identifier is the name of the component. The definitions inside the body of the component are qualified with the component name, as for module definitions.
The component members must satisfy the following rules:
-
All names appearing in any general port specifiers, special port specifiers, and internal port specifiers must be distinct.
-
No passive component may have an
async
input
port specifier, an internal port specifier, or anasync
command specifier. -
Each active or queued component must have at least one
async
input
port specifier, internal port specifier, orasync
command specifier. -
Each component must provide ports as shown in the table below.
| Condition | Requirement |
|---|---|
If there are any command specifiers |
There must be specifiers for ports |
If there are any event specifiers |
There must be specifiers for ports |
If there are any parameter specifiers |
There must be specifiers for ports |
If there are any telemetry specifiers |
There must be specifiers for ports |
The dictionary specifiers must satisfy the following rules:
-
All commands must have distinct names and opcodes.
-
All events must have distinct names and identifier values.
-
All parameters must have distinct names and identifier values.
-
All telemetry channels must have distinct names and identifier values.
5.3.3. Examples
@ Receives commands from the ground or from a sequencer
@ Dispatches commands to other components
active component CommandDispatcher {
# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------
@ The number of com command input ports
constant numComCmdInPorts = 10
@ The number of command output ports
constant numCmdOutPorts = 20
# ----------------------------------------------------------------------
# General ports
# ----------------------------------------------------------------------
@ Dispatches commands
output port cmdOut: [numCmdOutPorts] Fw.Cmd
@ Forwards received command responses
output port cmdResponseInOut: [numComCmdInPorts] Fw.CmdResponse
@ Receives com packets containing serialized commands
async input port comCmdIn: [numComCmdInPorts] Fw.Com
@ Receives command registration requests
guarded input port cmdRegIn: [numCmdOutPorts] Fw.CmdReg
@ Receives command responses
async input port cmdResponseIn: Fw.CmdResponse
# ----------------------------------------------------------------------
# Special ports
# ----------------------------------------------------------------------
@ Command receive port
command recv port cmdIn
@ Command registration port
command reg port cmdRegOut
@ Command response port
command resp cmdResponseOut
@ Event port
event port eventOut
@ Telemetry port
telemetry port tlmOut
@ Text event port
text event port textEventOut
@ Time get port
time get port timeGetOut
# ----------------------------------------------------------------------
# Commands
# ----------------------------------------------------------------------
@ No-op command
async command NO_OP
@ No-op string command
async command NO_OP_STRING(
arg1: string size 256 @< The string command argument
)
...
# ----------------------------------------------------------------------
# Events
# ----------------------------------------------------------------------
@ Opcode registered event
event OpcodeRegistered(
regOpcode: Opcode @< The opcode to register
regPort: U32 @< The registration port
dispatchSlot: U32 @< The dispatch slot
) severity diagnostic \
format "Opcode {} registered to port {} slot {}"
@ Opcode dispatched event
event OpcodeDispatched(
dispatchOpcode: Opcode @< The opcode dispatched
dispatchPort: U32 @< The dispatch port
) severity command \
format "Opcode {} dispatched to port {}"
...
# ----------------------------------------------------------------------
# Telemetry
# ----------------------------------------------------------------------
@ Number of commands dispatched
telemetry CommandsDispatched: U32 update on change
@ Number of command errors
telemetry CommandErrors: U32 update on change
}
5.4. Component Instance Definitions
A component instance definition defines an instance of a component that you can refer to in a topology definition.
5.4.1. Syntax
instance
identifier
:
qual-ident
base
id
expression
[
type
string-literal
]
[
at
string-literal
]
[
queue
size
expression
]
[
stack
size
expression
]
[
priority
expression
]
[
cpu
expression
]
[
{
init-specifier-sequence }
]
init-specifier-sequence is an element sequence in which each element is an init specifier, and the terminating punctuation is a semicolon.
5.4.2. Semantics
-
The identifier names the component instance.
-
The qualified identifier must refer to a component definition D. This component definition D is called the component definition associated with the component instance I. We also say that I is an instance of D.
-
The expression following the keywords
base
id
must have a numeric type. It associates a base identifier with the component instance.-
The base identifier must evaluate to a nonnegative integer after type conversion.
-
For each component instance, for each command, event, telemetry, or parameter identifier, the identifier associated with the instance is computed by adding the base identifier specified here to the relative identifier specified in the component. For this purpose, command opcodes are identifiers.
-
For each instance, this procedure creates a range of identifiers, from the base identifier to the largest identifier associated with the instance. If the component has no identifiers, then this range is empty.
-
No instance may have a base identifier that lies within the identifier range of another instance.
-
-
If present, the string literal following the keyword
type
names the implementation type of the instance. The type must be a valid type in the target language (e.g., a type name in C++). If the implementation type is not present, then the code generator infers the name of the implementation type from the component name when generating the constructor for the instance. For example:-
Suppose an FPP model has a component
C
defined in moduleM
. Suppose I is an instance of componentM.C
. By default, the implementation type associated with I isM::C
. -
Specifying
type "M::D"
causes FPP to generate a constructor for I with name"M::D"
instead of"M::C"
.
Specifying an implementation type is useful in cases where the name of the component implementation does not match the component name, e.g., because there are several implementations of the same FPP component.
-
-
If present, the string literal following the keyword
at
must specify a file path, relative to the location of the component instance definition. The file path must name a file in the target language (e.g., a C++ header file) that provides the implementation associated with the instance. If no such path is given, then the translator uses the location of the component instance and the name of the component to generate a default implementation path. -
If present, the expression following the keywords
queue
size
must have a numeric type and must evaluate to a nonnegative integer after type conversion. It specifies the queue size for active and queued components. The queue size is required for active and queued components and is not allowed for passive components. -
If present, the expression following the keywords
stack
size
must have a numeric type and must evaluate to a nonnegative integer after type conversion. It specifies the stack size in bytes for active components. The stack size is optional for active components and is not allowed for queued or passive components. -
If present, the expression following the keyword
priority
must have a numeric type. It specifies the thread priority for active components. The priority is optional for active components and is not allowed for queued or passive components. -
If present, the expression following the keyword
cpu
must have a numeric type. It specifies the CPU affinity for active components. The CPU affinity is optional for active components and is not allowed for queued or passive components. -
If present, the init specifiers govern C++ code generation for the component instance being defined. See the section on init specifiers for further information.
5.4.3. Examples
instance commandDispatcher: Svc.CommandDispatcher \
base id 0x100 \
queue size 10 \
stack size 4096 \
priority 30 \
cpu 0
This example defines an instance commandDispatcher
of component Svc.CommandDispatcher
.
It specifies the base identifier, queue size, stack size,
priority, and CPU.
instance timer: Svc.Timer at "../../Timers/HardwareTimer.hpp"
This example defines an instance timer
of component Svc.Timer
.
It specifies that the component implementation is located at
path ../../Timers/HardwareTimer.hpp
instead of in the default location for the
component.
The path is resolved relative to the location of the instance definition.
5.5. Constant Definitions
A constant definition associates a name with a compile-time constant value. You can use the name in place of the value elsewhere in the model.
5.5.1. Syntax
constant
identifier
=
expression
5.5.2. Semantics
expression must evaluate to a compile-time constant value v. At any model point where the qualified identifier Q refers to the constant definition according to the scoping rules for names, you can use Q as a name for v.
5.5.3. Examples
constant a = 0 # a has value 0
constant b = 1.0 # b has value 1.0
constant c = a # c has value 0
5.6. Enum Definitions
An enum definition does two things:
-
It defines a type \$T\$ and associates a name \$N\$ with \$T\$. Elsewhere in the model, you can use \$N\$ to refer to \$T\$.
-
It associates several named constants \$C_1, ..., C_n\$ with \$T\$. These constants, called the enumerated constants of \$T\$, are the values that an expression of type \$T\$ may attain. Elsewhere in the model, you can use the qualified identifiers \$N\$
.
\$C_1, ..., N\$.
\$C_n\$ to refer to the enumerated constants.
5.6.1. Syntax
enum
identifier
[ :
type-name ]
{
enum-constant-sequence }
[
default
expression
]
enum-constant-sequence is an element sequence in which the elements are enumerated constant definitions, and the terminating punctuation is a comma.
5.6.2. Semantics
The enumerated constants have the enum type defined in the enum definition. During analysis, they are represented as values of a primitive integer type, called the representation type.
There must be at least one enumerated constant. No two enumerated constants may have the same identifier. Each enumerated constant must evaluate to a compile-time constant whose type is convertible to the representation type. No two enumerated constants may have the same value after the conversion.
The expression following the keyword default
is optional.
If present, it specifies the default value associated
with the enum definition.
The type of the expression must be the type of the enum definition.
5.6.3. Inferred Representation Type
If type-name does not appear after the identifier, then
the implied representation type is I32
.
5.6.4. Explicit Representation Type
If type-name appears after the identifier, then the semantic analyzer does the following:
-
Check that type-name names a primitive integer type. If not, throw an error.
-
Use \$T\$ as the representation type.
5.6.5. Examples
The following example shows two definitions. In the first one, the implied
representation type is I32
.
In the second one, the representation type is explicitly given as U8
.
enum Gunfighters {
IL_BUONO
IL_BRUTTO
IL_CATTIVO
}
enum U8Gunfighters: U8 {
IL_BUONO
IL_BRUTTO
IL_CATTIVO
}
The next example shows an enum definition with an explicit default value.
enum Status {
YES
NO
MAYBE
} default MAYBE
5.7. Enumerated Constant Definitions
An enumerated constant definition is an element of an enum definition. Like a constant definition, it associates a value with a named constant. It also establishes that the constant is one of the values of the type defined in the enum definition.
5.7.1. Syntax
identifier
[
=
expression
]
5.7.2. Semantics
If present, expression must evaluate to a compile-time constant value v. At any model point where the qualified identifier Q refers to the enumerated constant definition according to the scoping rules for names, you can use Q as a name for the value that results from converting v to the type of the enclosing enum definition.
The expression must be present or absent for all enumerated constant definitions appearing in an enum definitions. If there are no expressions, then the enumerated constant definitions are assigned increasing values starting with zero, as for enumerations in C.
Note that the type of an enumerated constant value is the
enum
type defined by the enclosing enum definition. This may be
converted
to the representation type of the enum. However, the reverse conversion
is not allowed: you can convert an enum type to a U32
(for example),
but not a U32
to an enum type. Nor can you convert one enum type to a
different one.
5.7.3. Example
The examples given for enum definitions include enumerated constant definitions.
5.8. Module Definitions
A module definition provides a named scope that encloses and qualifies other definitions, including other module definitions. It is similar to a namespace in C++ or a package in Java or Scala.
5.8.1. Syntax
module
identifier
{
module-member-sequence }
module-member-sequence is an element sequence in which each element is a module member, and the terminating punctuation is a semicolon. A module member is one of the following:
5.8.2. Semantics
A module definition D qualifies the names of all the definitions inside it with its own name. Inside D, you can refer to definitions in D by their unqualified name (the identifier appearing in the definition) or by their qualified name. Outside D, you have to use the qualified name. We say that the scope of the identifiers in the definitions in D is limited to the inside of D.
For further information about name scoping and qualification, see the section on Scoping of Names.
5.8.3. Example
module M {
constant a = 0
constant b = a # Inside M, we can refer to M.a as a
constant c = M.a # We can also say M.a here
}
constant d = M.a # Outside M, we have to say M.a
constant e = a # Error: a is not in scope here
5.9. Port Definitions
An port definition defines an F Prime port. A port is the endpoint of a connection between F Prime components.
5.9.1. Syntax
port
identifier
[
(
param-list
)
]
[
->
type-name
]
5.9.2. Semantics
-
The identifier is the name of the port.
-
The optional parameter list specifies the formal parameters of the port. Each formal parameter is a piece of data carried on the port. If there are no formal parameters, then the parameter list may be omitted.
-
If the annotation
ref
appears in a formal parameter P, then P is passed by reference in a synchronous port invocation and by value in an asynchronous port invocation. -
The optional type name appearing after the arrow specifies an optional return type for the port. If present, the return type is the type of the data returned when the port is invoked in a synchronous context.
5.9.3. Examples
@ Port 1
port Port1(
a: U32 @< Parameter a
b: F64 @< Parameter b
)
@ Port 2
port Port2(
ref a: Fw.Com @< Ref parameter a
)
@ Port 3
port Port3(
a: U32 @< Parameter a
) -> U32
5.10. Struct Definitions
A struct definition defines a new structure type and associates a name with it.
5.10.1. Syntax
struct
identifier
{
struct-type-member-sequence }
[ default
expression ]
struct-type-member-sequence is an element sequence in which the elements are struct type members, and the terminating punctuation is a comma. A struct type member has the following syntax:
identifier :
[
[
expression ]
]
type-name
[
format
string-literal
]
5.10.2. Semantics
The identifier is the name N of the type. The definition associates the name N with a struct type T representing a structure with named members, each of the specified type. Each identifier appearing in the struct type member sequence must be distinct.
The optional expression [
e ]
for a struct member specifies the
size of the member, i.e., the number of elements stored in the member.
If present, e must have a
numeric type, and it must
evaluate to a value greater than zero after conversion to
Integer.
If e is not present, then default size is one.
If a member m has size greater than one, then m
is translated to an array in the implementation language.
The optional format specifier for a struct member specifies a format string. The format is applied when displaying the struct member. There is one argument to the format string, which is the member value. If the size of the member is greater than one, then the translator applies the format to each element.
The expression following the keyword default
is optional.
If present, it specifies the default value
associated with the type.
The type of the expression must be
convertible to T.
If the expression specifies a value for a member with size
greater than one, then the value is applied to each element.
5.10.3. Examples
# Defines a struct type A with members x and y
# and default value { x = 0, y = 0 }
struct A {
x: U32
y: F32
}
# Defines a struct type B with members x and y
# and default value { x = 0, y = 1 }
struct B {
x: U32
y: F32
} default { y = 1 }
# Defines a struct type C with format specifiers
struct C {
x: U32 format "{} counts"
y: F32 format "{} m/s"
}
# Defines a struct type D with member x
# After translation, x is an array of 3 U32 elements
# The associated FPP struct type is { x: U32 }
# The default value of D stores 1 into each of the 3 elements of x
struct D {
x: [3] U32
} default { x = 1 }
5.11. Topology Definitions
A topology definition defines an F Prime topology, that is, a set of component instances and the connections between their ports. The connections are divided into named connection graphs. The different graphs capture different aspects of FSW function, such as commanding and telemetry.
One topology can be imported into another one. The connections are merged graph by graph.
5.11.1. Syntax
topology
identifier
{
topology-member-sequence }
topology-member-sequence is an element sequence in which each element is a topology member, and the terminating punctuation is a semicolon. A topology member is one of the following:
5.11.2. Semantics
A topology definition D must be resolvable to a topology T, according to the following algorithm:
-
Resolve D to a partially numbered topology T'.
-
Apply automatic numbering of ports to T'.
Resolving to a Partially Numbered Topology
A partially numbered topology is a topology in which port number assignments may or may not exist for each connection. A port number assignment exists if it is specified in the model source.
-
For each endpoint E (output or input) in each connection C appearing in D, if E has a port number expression e in the FPP source, then evaluate e, convert the result to a value n of type Integer, and assign the port number n at that position. Check that n is in bounds for the port instance being numbered.
-
Recursively resolve all the topologies directly imported into D.
-
Construct the connection graphs of T given by the direct connection graphs appearing in D, after merging each set of connection graphs with the same name into a single graph. For example, the two connection graphs
connections C { a.b -> c.d }
and
connections C { e.f -> g.h }
are treated as the single graph
connections C { a.b -> c.d, e.f -> g.h }
-
Compute the set S of topologies transitively imported into T.
-
For each topology T' in S, import the instances of T' into T.
-
Check that the connections discovered in step 3 are between port instances present in T.
-
For each topology T' in S, import the connections of T' into T.
-
Resolve graph pattern specifiers, adding connections to T. Add only connections that are not already present in T. For example, if a command pattern indicates a command registration connection between two ports, and there is already a command registration connection between those ports, then do not add the connection.
Automatic Numbering of Ports
FPP automatically assigns port numbers as follows.
Check output port numbers: At each output port p of each component instance I, check that
-
The number of connections is in bounds for the size of I
.
p. -
There is no pair of connections \$c_1\$ and \$c_2\$ at the same port number of I
.
p. For example, the following pair of connections is not allowed:a.p[0] -> b.p a.p[0] -> c.p
Apply matched numbering: Assign matching numbers to matched pairs of ports. For each instance I in the topology:
-
Let C be the component type of I.
-
For each port matching specifier
match
\$p_1\$with
\$p_2\$ appearing in the definition of C:-
For each connection \$c_1\$ with an endpoint at I
.
\$p_1\$:-
Let I' be the component instance at the other endpoint of \$c_1\$.
-
Check that there is one and only one connection \$c_2\$ from I' to I
.
\$p_2\$.
-
-
Check that the connections \$c_2\$ computed in the previous step are all the connections at I
.
\$p_2\$. -
For each pair \$(c_1,c_2)\$ that has both port numbers assigned, check that the port numbers match. For each pair \$(c_1,c_2)\$ that has exactly one port number assigned, assign the other one to match.
-
Traverse the pairs \$(c_1,c_2)\$ according to the order of the connections \$c_1\$, least to greatest. For each pair \$(c_1,c_2)\$ that does not yet have assigned port numbers, find the lowest available port number and assign it at I
.
\$p_1\$ and I.
\$p_2\$. A port number is available if (a) it is in bounds for the port instance being numbered; and (b) it is not already assigned to the same port instance in the topology.
-
Note that in the last step,
the two ports I .
\$p_1\$ and I .
\$p_2\$.
have the same array size and
the same port numbers assigned so far, so the lowest
available port number is the same for both.
Apply general numbering: Fill in any remaining port numbers.
-
Traverse the connections in order, least to greatest.
-
For each output endpoint P in each connection C, if no port number is already assigned, then assign the lowest available port number at position P.
-
For each input endpoint P in each connection C, if no port number is already assigned, then assign the port number zero.
See Example 4 in the Examples section.
Ordering of Connections
For purposes of port numbering, FPP orders connections as follows.
Connection endpoints:
A connection endpoint is I .
p or I .
p [
n ]
, where
-
I refers to a component instance.
-
p is an identifier that names a port instance specified in the component definition associated with I.
-
n is an optional port number that is present if and only if it appears in the model source.
When a connection endpoint e has the form I .
p [
n ]
,
we say that the endpoint has source port number n.
Each connection endpoint has a fully qualified name.
The fully qualified name is Q .
p, where Q is the
fully qualified name
of the instance I.
FPP orders connection endpoints \$e_1\$ and \$e_2\$ as follows:
-
If the fully qualified name of \$e_1\$ is lexically less than (respectively greater than) the fully qualified name of \$e_2\$, then \$e_1\$ is less than (respectively greater than) \$e_2\$.
-
Otherwise if \$e_1\$ and \$e_2\$ have source port numbers port numbers \$n_1\$ and \$n_2\$, then the ordering of \$e_1\$ and \$e_2\$ is the same as the numerical ordering of \$n_1\$ and \$n_2\$.
-
Otherwise \$e_1\$ and \$e_2\$ are equal in the ordering.
Connections: A connection is \$e_1\$ ->
\$e_2\$,
where \$e_1\$ and \$e_2\$ are the connection endpoints.
FPP orders connections \$c_1\$ and \$c_2\$ as follows:
-
Let connection \$c_1\$ be \$e_1\$
->
\$e'_1\$. -
Let connection \$c_2\$ be \$e_2\$
->
\$e'_2\$. -
If \$e_1\$ is less than (respectively greater than) \$e_2\$, then \$c_1\$ is less than (respectively greater than) \$c_2\$.
-
Otherwise if \$e'_1\$ is less than (respectively greater than) \$e'_2\$, then \$c_1\$ is less than (respectively greater than) \$c_2\$.
-
Otherwise \$c_1\$ and \$c_2\$ are equal in the ordering.
5.11.3. Examples
Example 1.
@ Command and data handling topology
topology CDH {
# ----------------------------------------------------------------------
# Public instances
# ----------------------------------------------------------------------
instance commandDispatcher
instance commandSequencer
instance engineeringRateGroup
instance engineeringTelemetryLogger
instance engineeringTelemetryConverter
instance engineeringTelemetrySplitter
instance eventLogger
instance rateGroupDriver
instance telemetryDatabase
instance timeSource
# ----------------------------------------------------------------------
# Private instances
# ----------------------------------------------------------------------
private instance socketGroundInterface
# ----------------------------------------------------------------------
# Connection patterns
# ----------------------------------------------------------------------
command connections instance commandDispatcher
event connections instance eventLogger
time connections instance timeSource
# ----------------------------------------------------------------------
# Connection graphs
# ----------------------------------------------------------------------
connections CommandSequences {
commandSequencer.comCmdOut -> commandDispatcher.comCmdIn
}
connections Downlink {
eventLogger.comOut -> socketGroundInterface.comEventIn
telemetryDatabase.comOut -> socketGroundInterface.comTlmIn
}
connections EngineeringTelemetry {
commandDispatcher.tlmOut -> engineeringTelemetrySplitter.tlmIn
commandSequencer.tlmOut -> telemetryDatabase.tlmIn
engineeringRateGroup.tlmOut -> engineeringTelemetrySplitter.tlmIn
engineeringTelmetryConverter.comTlmOut -> engineeringTelemetryLogger.comTlmIn
engineeringTelemetrySplitter.tlmOut -> engineeringTelemetryConverter.tlmIn
engineeringTelemetrySplitter.tlmOut -> telemetryDatabase.tlmIn
}
connections RateGroups {
engineeringRateGroup.schedOut -> commandSequencer.schedIn
engineeringRateGroup.schedOut -> telemetryDatabase.schedIn
rateGroupDriver.cycleOut -> engineeringRateGroup.cycleIn
}
connections Uplink {
socketGroundInterface.comCmdOut -> commandDispatcher.comCmdIn
}
}
Example 2.
@ Attitude control topology
topology AttitudeControl {
# ----------------------------------------------------------------------
# Imported topologies
# ----------------------------------------------------------------------
import CDH
# ----------------------------------------------------------------------
# Public instances
# ----------------------------------------------------------------------
instance acsRateGroup
instance attitudeControl
...
# ----------------------------------------------------------------------
# Private instances
# ----------------------------------------------------------------------
instance socketGroundInterface
# ----------------------------------------------------------------------
# Connection patterns
# ----------------------------------------------------------------------
command connections instance commandDispatcher
event connections instance eventLogger
time connections instance timeSource
# ----------------------------------------------------------------------
# Connection graphs
# ----------------------------------------------------------------------
connections AttitudeTelemetry {
...
}
connections Downlink {
eventLogger.comOut -> socketGroundInterface.comEventIn
telemetryDatabase.comOut -> socketGroundInterface.comTlmIn
}
connections EngineeringTelemetry {
acsRateGroup.tlmOut -> engineeringTelemetrySplitter.tlmIn
...
}
connections RateGroups {
acsRateGroup.schedOut -> attitudeControl.schedIn
}
connections Uplink {
socketGroundInterface.comCmdOut -> commandDispatcher.comCmdIn
}
}
Example 3.
@ Release topology
topology Release {
# ----------------------------------------------------------------------
# Imported topologies
# ----------------------------------------------------------------------
import AttitudeControl
import CDH
import Communication
...
}
Example 4.
Here is the topology that results from automatic numbering of ports
applied to topology B
in the
example for topology import
specifiers:
topology B {
instance a
instance c
instance d
instance e
instance f
connections C1 {
a.p1[0] -> c.p[0]
a.p1[1] -> d.p[0]
}
connections C2 {
a.p2[0] -> e.p[0]
}
connections C3 {
a.p3[0] -> f.p[0]
}
}
6. Specifiers
A specifier is a unit of syntax that specifies some information about an FPP model. It may associate a local name with the specification. However, unlike a definition, it does not create a globally unique qualified name.
6.1. Command Specifiers
A command specifier specifies a command as part of a component definition.
6.1.1. Syntax
command-kind command
identifier
[
(
param-list )
]
[
opcode
expression
]
[
priority
expression
]
[
queue-full-behavior
]
command-kind is one of the following:
-
async
-
guarded
-
sync
queue-full-behavior is as for port instance specifiers.
6.1.2. Semantics
-
The command kind specifies the kind of the command. It is similar to the kind of a port instance specifier, except that different commands on the same port can have different kinds.
-
The identifier names the command.
-
The parameter list specifies the command parameters. If there are no parameters, the list may be omitted.
ref
may not appear in any of the parameters. -
The optional expression e following
opcode
specifies the numeric opcode for the command. If e is present, its type must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default opcode is either zero (for the first opcode appearing in a component) or the previous opcode plus one. -
The optional expression e appearing after the keyword
priority
specifies a priority for the command on the input queue. The type of e must be convertible to Integer. The priority expression is valid only if the kind of the command isasync
. -
The optional queue-full-behavior specifies the behavior of the command when the input full is queue. This specifier is valid only if the kind of the command is
async
. If no specifier appears, then the default behavior isassert
.
6.1.3.  Component Instance Specifiers
A component instance specifier specifies that a component instance is part of a topology.
6.2.1. Syntax
[ private
]
instance
qual-ident
6.2.2. Semantics
-
The qualified identifier must refer to a component instance.
-
The optional keyword
private
, if present, specifies a private instance of a topology. This means that the instance is private to the topology T in which the specifier appears. A private instance appears only in T; it does not appear in any topology T' into which T is imported. -
If an instance is not declared private, then it is implicitly a public instance. This means the instance appears in each topology T' into which T is imported.
6.2.3. Example
component A { ... }
component B { ... }
component C { ... }
instance a: A base id 0x100 ...
instance b: B base id 0x200 ...
instance c: B base id 0x300 ...
Topology T {
# ----------------------------------------------------------------------
# Public instances
# ----------------------------------------------------------------------
instance a
instance b
# ----------------------------------------------------------------------
# Private instances
# ----------------------------------------------------------------------
private instance c
...
}
6.3. Connection Graph Specifiers
A connection graph specifier specifies one or more connection graphs as part of a topology definition. A connection graph is a named set of connections. A connection connects an output port instance of one component instance to an input port instance of another.
6.3.1. Syntax
A connection graph specifier is one of the following:
-
A direct graph specifier:
connections
identifier{
connection-sequence}
-
A pattern graph specifier: pattern-kind
connections
instance
qual-ident [{
instance-sequence}
]
connection-sequence is an element sequence in which each element is a connection, and the terminating punctuation is a comma. A connection is the following:
port-instance-id
[
[
expression
]
]
->
port-instance-id
[
[
expression
]
]
pattern-kind is one of the following:
-
command
-
event
-
health
-
param
-
telemetry
-
time
instance-sequence is an element sequence in which each element is a qualified identifier, and the terminating punctuation is a comma.
6.3.2. Semantics
Direct graph specifiers. A direct graph specifier directly specifies a named connection graph.
-
The identifier following the keyword
connections
names the connection graph. -
The connection sequence specifies the set of connections in the graph. For each connection C:
-
For each of the two port instance identifiers I appearing in C:
-
The component instance named in I must be available in the enclosing topology, either through direct specification or through import.
-
The optional expression e following the identifier, if it is present, represents a port number, i.e., an index into an array of port instances. The type of e must be a numeric type, and e must evaluate to a compile-time constant that becomes a non-negative integer n when converted to type Integer. n must lie in the range [0,231) and must be in bounds for the array size of the port instance specifier named in I.
-
-
The arrow represents the direction of the connection (left to right).
-
The connection must go from an output port instance to an input port instance.
-
The port instance types specified in the two port instances must match, except that a
serial
port at either end can be connected to any port at the other end. -
If a
serial
port instance S is connected to a typed port instance T, then the port type specified in T may not have a return type.
-
Pattern graph specifiers. A pattern graph specifier indirectly specifies one or more named connection graphs by specifying a source component instance, a set of target component instances, and a pattern for connecting the source instance to each of the target instances.
-
Each topology may contain at most one of each kind of graph pattern.
-
The qualified identifier following the keyword
instance
must refer to a component instance that is available in the enclosing topology, either through direct specification or through import. -
If the instance sequence I appears, then each qualified identifier Q appearing in I must refer to a component instance that is available in the enclosing topology, and each instance must be valid for the pattern (i.e., must have a port of the type required for the pattern). The instances in the sequence name the target instances for the pattern. If no instance sequence appears, then the target instances are all instances specified directly in the enclosing topology (not via import) that are valid for the pattern.
The meaning of specifier depends on the pattern kind, as follows:
-
command
: The source instance I is a command dispatcher. The following connection graphs are generated:-
A connection graph named
Command
consisting of all connections from the output port of typeFw::Cmd
of I to the command input port of each target component. -
A connection graph named
CommandRegistration
consisting of all connections from the command registration output port of each target component to the command registration input port of I. -
A connection graph named
CommandResponse
consisting of all connections from the command response output port of each target component to the command response input port of I.
-
-
event
: The source instance I is an event logger with an input port of typeFw.Log
. The generated connection graph has nameEvents
and contains all connections for sending events to I through an event output port. -
health
: The source instance I is a health component. The generated connection graph has nameHealth
and contains all connections between the health component and ping ports of the target components of typeSvc.Ping
. -
param
: The source instance I is a parameter database component. The generate connection graph has nameParameters
and contains all connections for (a) getting parameters from the database and (b) saving parameters to the database. -
telemetry
: The source instance I is a telemetry database with an input port of typeFw.Tlm
. The generated connection graph has nameTelemetry
and contains all connections for sending telemetry to I through a telemetry output port. -
text event
: The source instance I is a text event logger with an input port of typeFw.LogText
. The generated connection graph has nameTextEvents
and contains all connections for sending events to I through an event output port. -
time
: The source instance I is a time component. The generated connection graph has nameTime
and contains all connections for getting the time from I through a time get output port.
6.3.3. Example
Assume the following instances are available in the enclosing topology, and all have command ports:
commandDispatcher
commandSequencer
engineeringTelemetryLogger
eventLogger
telemetryDatabase
timeSource
Here is a pattern graph specifier:
command connections instance commandDispatcher
It is equivalent to the following direct graph specifiers:
connections CommandRegistration {
commandDispatcher.cmdRegOut -> commandDispatcher.cmdRegIn
commandSequencer.cmdRegOut -> commandDispatcher.cmdRegIn
engineeringTelemetryLogger.cmdRegOut -> commandDispatcher.cmdRegIn
eventLogger.cmdRegOut -> commandDispatcher.cmdRegIn
telemetryDatabase.cmdRegOut -> commandDispatcher.cmdRegIn
timeSource.cmdRegOut -> commandDispatcher.cmdRegIn
}
connections Command {
commandDispatcher.cmdOut -> commandDispatcher.cmdIn
commandDispatcher.cmdOut -> commandSequencer.cmdIn
commandDispatcher.cmdOut -> engineeringTelemetryLogger.cmdIn
commandDispatcher.cmdOut -> eventLogger.cmdIn
commandDispatcher.cmdOut -> telemetryDatabase.cmdIn
commandDispatcher.cmdOut -> timeSource.cmdIn
}
connections CommandResponse {
commandDispatcher.cmdRespOut -> commandDispatcher.cmdRespIn
commandSequencer.cmdRespOut -> commandDispatcher.cmdRespIn
engineeringTelemetryLogger.cmdRespOut -> commandDispatcher.cmdRespIn
eventLogger.cmdRespOut -> commandDispatcher.cmdRespIn
telemetryDatabase.cmdRespOut -> commandDispatcher.cmdRespIn
timeSource.cmdRespOut -> commandDispatcher.cmdRespIn
}
See also the examples for topology definitions.
6.4. Event Specifiers
An event specifier specifies an event report as part of a component definition.
6.4.1. Syntax
event
identifier
[
(
param-list )
]
severity
severity
[
id
expression
]
format
string-literal
[
throttle
expression
]
severity is one of the following:
-
activity
high
-
activity
low
-
command
-
diagnostic
-
fatal
-
warning
high
-
warning
low
6.4.2. Semantics
-
The identifier names the event.
-
severity specifies the severity of the event.
-
The parameter list specifies the event parameters. If there are no parameters, the list may be omitted.
ref
may not appear in any of the parameters. -
The optional expression e following
id
specifies the numeric identifier for the event. If e is present, then type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first event appearing in a component) or the previous event identifier plus one. -
The string following
format
is a format string that formats the event for display on the ground. The arguments to the format string are the values bound to the event parameters. A numeric format is allowed for any argument whose type is a numeric type. -
The optional expression e following
throttle
specifies the maximum number of times to emit the event before throttling it. The type of e must be convertible to Integer and must evaluate to an integer in the range [0,231).
6.4.3. Examples
@ An array of 3 F64 values
array F64x3 = [3] F64
@ An enumeration of cases
enum Case { A, B, C }
@ Event 0
event Event0 \
severity activity low \
id 0x00 \
format "Event 0 occurred"
@ Event 1
@ Sample output: "Event 1 occurred with argument 42"
event Event1(
arg1: U32 @< Argument 1
) \
severity activity high \
id 0x01 \
format "Event 1 occurred with argument {}"
@ Event 2
@ Sample output: "Saw value [ 0.001, 0.002, 0.003 ] for case A"
event Event2(
case: Case @< The case
value: F64x3 @< The value
) \
severity warning low \
id 0x02 \
format "Saw value {} for case {}" \
throttle 10
6.5. Include Specifiers
An include specifier specifies that a file should be included in a translation unit.
6.5.1. Syntax
include
string-literal
6.5.2. Semantics
The string literal specifies a file path relative to the location of the include specifier.
During parsing, the translator does the following:
-
Resolve the path to an absolute file name that refers to a file F.
-
Parse F, recursively resolving any include specifiers that appear in F.
-
Include its parsed elements as if they appeared in the enclosing translation unit at the point where the include specifier appears.
The suffix .fppi
is conventional for included files, to distinguish them
from files presented directly for analysis or translation.
6.5.3. Examples
Example 1.
File a.fppi
contains the following:
constant a = 1
File b.fpp
contains the following:
include "a.fppi"
constant b = a
File b.fpp
is translated identically to this translation unit:
constant a = 1
constant b = a
Example 2.
File a.fppi
contains the following:
constant a = 1
File b.fpp
contains the following:
module M { include "a.fppi" }
b = M.a
File b.fpp
is translated identically to this translation unit:
module M { constant a = 1 }
b = M.a
6.6. Init Specifiers
An init specifier associates some code with a component instance. Usually this is initialization code, hence the name. It may also serve another purpose (e.g., teardown).
6.6.1. Syntax
phase
expression
string-literal
6.6.2. Semantics
-
The expression following the keyword
phase
must have a numeric type. It provides an integer identifier for an initialization phase. -
Each component instance may have at most one init specifier for each distinct numeric phase.
-
The code string specifies some code in a target language that is associated with the instance.
The meaning of the initialization phase and the code depends on the translation context.
6.6.3. Example
@ Phases of initialization
enum Phases {
@ When components are constructed
CONSTRUCTION
@ After components are constructed, and before connections are established
BEFORE_CONNECTIONS
@ After connections are established
AFTER_CONNECTIONS
@ When components are deallocated
TEARDOWN
}
instance commandDispatcher: Svc.CommandDispatcher \
base id 0x100 \
queue size 10 \
stack size 4096 \
priority 30 \
{
phase BEFORE_CONNECTIONS """
commandDispatcher.init(QueueDepth::commandDispatcher);
"""
}
In this example, the code generator provides three phases,
CONSTRUCTION
, BEFORE_CONNECTIONS
, AFTER_CONNECTIONS
,
and TEARDOWN
.
In the CONSTRUCTION
phase, the code generator generates
a default constructor invocation that looks like this:
Svc::CommandDispatcher commandDispatcher("commandDispatcher");
By default, the code generator might generate this code to run before connections are established:
commandDispatcher.init(
QueueDepth::commandDispatcher,
InstanceID::commandDispatcher
);
The code shown above overrides the code generated for the second
phase to remove the instance ID from the arguments of the init
method.
6.7. Internal Port Specifiers
An internal port specifier specifies a single-use port for use in handlers of the enclosing component. A component can use an internal port to send a message to itself.
6.7.1. Syntax
internal
port
identifier
[
(
param-list
)
]
[
priority
expression
]
[
queue-full-behavior
]
queue-full-behavior has the same syntax as for port instance specifiers.
6.7.2. Semantics
The identifier is the name of the port.
The parameter list specifies the formal parameters of the
port.
Each formal parameter is a piece of data carried on the port.
The names of the formal parameters must be distinct.
No formal parameter may be a ref
parameter.
The optional priority and queue full behavior have the same semantics as in async input port instance specifiers.
6.7.3. Examples
@ Port 1
internal port Port1(
a: U32 @< Parameter a
b: F64 @< Parameter b
)
@ Port 2
internal port Port2(
a: U8 @< Parameter a
b: I32 @< Parameter b
) priority 10 drop
6.8. Location Specifiers
A location specifier specifies the location of a definition.
6.8.1.  Semantics
-
The qualified identifier Q is resolved like a use that refers to a definition as follows:
-
A constant location specifier refers to a constant definition.
-
A type location specifier refers to an array definition, enum definition, struct definition, or abstract type definition.
-
A port location specifier refers to a port definition.
-
A component location specifier refers to a component definition.
-
A component instance location specifier refers to a component instance definition.
-
A topology location specifier refers to a topology definition.
-
-
When a location specifier appears inside a module definition M, Q is implicitly qualified by the qualified name of M.
-
Q need not actually refer to any definition. This rule allows the specification of dependencies for a larger set of files than the ones involved in a particular analysis or translation.
-
The string literal must specify the path of an FPP source file, relative to the location of the specifier. The file must exist. After resolving include specifiers, the file must contain the definition referred to in the location specifier.
-
Multiple location specifiers for the same definition are allowed in a single model, so long as the locations are all consistent.
6.8.3. Examples
Example 1:
File a.fpp
contains the following:
constant a = 1
File b.fpp
contains the following:
locate constant a at "a.fpp"
constant b = a
When analyzing b.fpp
, the analyzer knows that the definition of constant
a
is available in a.fpp
.
Example 2:
File a.fpp
is as in the previous example.
File b.fpp
contains the following:
module M { constant b = 0 }
File c.fpp
contains the following:
locate constant a at "a.fpp"
module M { locate constant b at "b.fpp" }
The first location specifier refers to the constant a
.
The second location specifier refers to the constant M.b
.
6.9. Parameter Specifiers
A parameter specifier specifies a parameter as part of a component definition.
6.9.1. Syntax
param
identifier
:
type-name
[
default
expression
]
[
id
expression
]
[
set
opcode
expression
]
[
save
opcode
expression
]
6.9.2. Semantics
-
The identifier names the parameter.
-
The type name specifies the type T of the parameter.
-
The optional expression e following the keyword
default
specifies a default value for the parameter. If e is present, then the type of e must be convertible to T. If e is not present, then there is no default value for the parameter: When the parameter is used, then either (1) a value is available in the parameter database or (2) the use is invalid. -
The optional expression e after the keyword
id
specifies the numeric identifier for the parameter. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first parameter appearing in a component) or the previous parameter identifier plus one. -
The optional expression e after the keywords
set
opcode
specifies the opcode of the command for setting the parameter. If e is present, then type of e must be convertible to Integer. If e is not present, then the default value is either zero (for the first command appearing in a component) or the previous opcode plus one. -
The optional expression e after the keywords
save
opcode
specifies the opcode of the command for saving the parameter. If e is present, then the type of e must be convertible to Integer. If e is not present, then the default value is either zero (for the first command appearing in a component) or the previous opcode plus one.
6.9.3.  Port Instance Specifiers
A port instance specifier specifies an instantiated port as part of a component definition.
6.10.1. Syntax
A port instance specifier is one of the following:
-
general-port-kind
port
identifier:
[[
expression]
] port-instance-type [priority
expression ] [ queue-full-behavior ] -
special-port-kind
port
identifier
general-port-kind is one of the following:
-
async
input
-
guarded
input
-
output
-
sync
input
port-instance-type is one of the following:
-
serial
queue-full-behavior is one of the following:
-
assert
-
block
-
drop
special-port-kind is one of the following:
-
command
recv
-
command
reg
-
command
resp
-
event
-
param
get
-
param
set
-
telemetry
-
text
event
-
time
get
The following port instances are input port instances:
-
Any general port with
input
in its name. -
Special port
command recv
.
The others are output port instances.
6.10.2. Semantics
General port instances:
-
The kind specifies the kind of the port instance.
-
The identifier specifies the name of the port instance.
-
The optional expression e enclosed in brackets specifies the number of port instances in the port instance array. If no such expression appears, the default value is 1. The type of e must be convertible to Integer. e must evaluate to a value n after conversion to Integer. n must be greater than zero.
-
port-instance-type specifies the type of the port instance.
-
If the type is a qualified identifier q, then q must refer to a port definition D. If the kind is
async input
, then D may not specify a return type. -
If the type is
serial
, then the port instance is a serial port instance. A serial port instance does not specify a type. It may be connected to a port of any type. Serial data passes through the port. The data may be converted to or from a specific type at the other end of the connection.
-
-
The optional expression e appearing after the keyword
priority
specifies a priority for the port instance. The type of e must be convertible to Integer. The priority applies to the component’s message queue and may appear only forasync input
ports. The meaning of the priority value is operating system-dependent. -
The optional queue-full-behavior specifies the behavior when a message is received and the queue is full:
-
assert
means that an assertion fails, terminating FSW. -
block
means that the sender is blocked until there is space on the queue for the message. -
drop
means that the message is dropped.
This specifier is valid only for
async input
ports. If no specifier appears, then the default behavior isassert
. -
Special port instances: These port instances correspond to the "role ports" in the F Prime XML model. Their types and other attributes are specified by F Prime. The identifier specifies the name of the port.
There must be at most one of each kind of special port instance.
Each special port instance represents an implied use of a port as shown in the table below.  Examples
General ports:
@ Async input port of type Fw.Com
@ It has priority 10.
@ It drops input received when the queue is full.
async input port asyncComIn: Fw.Com priority 10 drop
@ Async input serial port
async input port serialIn: serial
@ Guarded input port of type Fw.Com
guarded input port guardedComIn: Fw.Com
@ Output port array of 10 Fw.Com ports
output port comOut: [10] Fw.Com
@ Sync input port of type Fw.Com
sync input port syncComIn: Fw.Com
Special ports:
@ A port for receiving commands from the command dispatcher
command recv port cmdIn
@ A port for sending command registration requests to the command dispatcher
command reg port cmdRegOut
@ A port for sending responses to the command dispatcher
command resp port cmdRespOut
@ A port for emitting events
event port eventOut
@ A port for emitting text events
text event port textEventOut
@ A port for getting parameter values from the parameter database
param get port paramGetOut
@ A port for sending parameter values to the parameter database
param set port paramSetOut
@ A port for emitting telemetry channels
telemetry port tlmOut
@ A port for getting the current time
time get port timeGetOut
6.11. Port Matching Specifiers
A port matching specifier is part of a component definition. It specifies that when the component is instantiated and connected into a topology, the corresponding port numbers of two of its port instances should match. For example, a port matching specifier could specify that for a health monitoring component, any component connected to its ping output port at port number n should also be connected to its ping input port at the same port number n.
6.11.1. Syntax
match
identifier
with
identifier
6.11.2. Semantics
-
Each of the identifiers must name a general port instance specified in the enclosing component.
-
The two port instances must be distinct and must have the same array size.
6.11.3. Example
queued component Health {
@ Number of ping ports
constant numPingPorts = 10
@ Ping output port
output port pingOut: [numPingPorts] Svc.Ping
@ Ping input port
async input port pingIn: [numPingPorts] Svc.Ping
@ Corresponding port numbers of pingOut and pingIn must match
match pingOut with pingIn
}
6.12. Telemetry Channel Specifiers
A telemetry channel definition defines a telemetry channel as part of a component definition.
6.12.1. Syntax
telemetry
identifier
:
type-name
[
id
expression
]
[
update
telemetry-update
]
[
format
string-literal
]
[
low
{
telemetry-limit-sequence }
]
[
high
{
telemetry-limit-sequence }
]
telemetry-update is one of the following:
-
always
-
on
change
telemetry-limit-sequence is an element sequence in which the elements are telemetry limits, and the terminating punctuation is a comma. A telemetry limit is one of the following:
-
red
expression -
orange
expression -
yellow
expression
6.12.2. Semantics
-
The identifier names the telemetry channel.
-
The type name specifies the type of the telemetry channel.
-
The optional expression e after the keyword
id
specifies the numeric identifier for the channel. If e is present, then the type of e must be convertible to Integer, and e must evaluate to a nonnegative integer. If e is not present, then the default identifier is either zero (for the first channel appearing in a component) or the previous channel identifier plus one. -
The optional update specifier specifies whether the telemetry channel is emitted always or on change. If the specifier is not present, the default behavior is always.
-
The optional format specifier specifies a format string. There is one argument to the format string, which is the channel value.
-
The optional high and low limit specifiers specify the high and low limits for the channel. The following rules apply:
-
At most one of each kind of limit (red, orange, yellow) may appear in each specifier.
-
The type of the expression in each limit must be a numeric type and must be convertible to the type of the channel.
-
The limit is applied to each telemetry channel with type T and value v as follows:
-
If T is a numeric type, then the limit is applied directly to v.
-
If T is not itself a numeric type (e.g., it is an array), then the limit is applied recursively to each member value of v.
-
-
6.12.3. Examples
@ An array of 3 F64 values
array F64x3 = [3] F64
@ Telemetry channel 0
telemetry Channel0: U32 id 0x00
@ Telemetry channel 1
telemetry Channel1: U32 \
id 0x01 \
update on change
@ Telemetry channel 2
telemetry Channel2: F64 \
id 0x02 \
format "{.3f}"
@ Telemetry channel 3
telemetry Channel3: F64x3 \
id 0x03 \
low { yellow -1, orange -2, red -3 } \
high { yellow 1, orange 2, red 3 }
6.13. Topology Import Specifiers
A topology import specifier imports one topology into another one.
6.13.1. Syntax
import
qual-ident
6.13.2. Semantics
The qualified identifier must refer to a topology definition.
Importing instances: FPP uses the following algorithm to import the instances of topology T' into topology T:
-
Let I be the set of instances of T.
-
Let I' be the set of public instances of T'.
-
Let I'' be the set union of I and I'. That means that if either or both of I and I' contain the instance S, then I'' contains the instance S once. Each instance in I'' has private visibility if it is private in either I or I', otherwise public visibility.
Importing connections: FPP uses the following algorithm to import the connections of topology T' into topology T.
-
For each connection graph name \$N_i\$ that appears in either T or T':
-
Let \$G_i\$ be the connection graph named \$N_i\$ in T. If no such graph exists, then let \$G_i\$ be the empty connection graph with name \$N_i\$.
-
Let \$G'_i\$ be the connection graph named \$N_i\$ in T'. If no such graph exists, then let \$G'_i\$ be the empty connection graph with name \$N_i\$.
-
Let \$C_i\$ be the set of connections of \$G_i\$ such that each of the two instances at the ends of the connection is in I''.
-
Let \$C'_i\$ be the set of connections of \$G'_i\$ such that each of the two the instances at the ends of the connection is in I'', and the connection is defined by a direct or pattern specifier in T' (i.e., not imported into T' from another topology).
-
Let \$C''_i\$ be the disjoint union of \$C_i\$ and \$C'_i\$. That means that if \$C_i\$ contains n connections between port p and port p', and \$C'_i\$ contains m connections between port p and port p', then \$C_i\$ contains n + m connections between port p and port p'.
-
Let \$G''_i\$ be the connection graph with name \$N_i\$ and connections \$C''_i\$.
-
-
Return the connection graphs \$G''_i\$.
6.13.3. Example
topology A {
instance a
private instance b
instance c
connections C1 {
a.p1 -> c.p
}
connections C2 {
b.p -> c.p
}
}
topology B {
import A
instance d
instance e
instance f
connections C1 {
a.p1 -> d.p
}
connections C2 {
a.p2 -> e.p
}
connections C3 {
a.p3 -> f.p
}
}
After importing, topology B
is equivalent to this topology:
topology B {
instance a
instance c
instance d
instance e
instance f
connections C1 {
a.p1 -> c.p
a.p2 -> d.p
}
connections C2 {
a.p2 -> e.p
}
connections C3 {
a.p3 -> f.p
}
}
Note the following:
-
The connections from topologies
A
andB
are merged graph by graph. -
Because instance b is private to topology
A
, neither it nor any of its connections appear in topologyB
.
7. Port Instance Identifiers
A port instance identifier identifies a port instance that is part of a component instance. Port instance identifiers appear in connection graph specifiers.
7.1. Syntax
7.2. Semantics
For each port instance identifier Q .
P:
-
The qualified identifier Q must refer to a component instance I.
-
I must refer to a component instance definition I'.
-
I' must refer to a component definition C.
-
The identifier P must refer to a port instance specifier of C.
7.3. Examples
a.b
A.b.c
In the first example, a
names a component instance, and b
names a port
instance.
In the second example, A.b
names a component instance, and c
names a
port instance.
8. Type Names
Type names are the syntactic names of
types.
In some cases, e.g., U32
, there is a one-to-one correspondence
between type names and types.
In other cases, e.g., array types, there is no one-to-one correspondence.
For example, depending on the enclosing scope, the name
a.b
and the name b
may refer to the same array type.
8.1. Primitive Integer Type Names
Primitive integer type names are the names of the primitive integer types. There are two kinds of primitive integer types: unsigned integer types and signed integer types.
-
The names of the unsigned integer types are
U8
,U16
,U32
, andU64
. TheU
stands for “unsigned.” The number after the U is the width of the representation in bits, using the standard binary representation of an unsigned integer. -
The names of the signed integer types are
I8
,I16
,I32
, andI64
. TheI
stands for “[signed] integer.” The number after the I is the width of the representation in bits, using the standard binary two’s complement representation of a signed integer.
8.2. Floating-Point Type Names
The floating-point type names are F32
and F64
. These refer to the types
of IEEE
floating-point values of width 32 and 64 bits, respectively.
8.3. The Boolean Type Name
The type name bool
represents the type of the two Boolean values true
and
false
.
8.4. String Type Names
A string type name consists of the keyword string
optionally
followed by the keyword size
and an
expression. For example:
string
string size 256
The optional expression following the keyword size
is called the size
expression of the string type name. If present, it must have a
numeric type, and it must
evaluate to a value in the range [0,231) after conversion to
Integer.
The size expression specifies the maximum
length of a string value represented by the type. If the size expression is
not present, then the translator uses a default maximum string length.
8.5. Qualified Identifier Type Names
A qualified identifier type name is a qualified identifier that refers to an abstract type definition, array definition, enum definition, or struct definition via the rules for name scoping. It names the type defined in the definition to which it refers.
For example:
module M {
type T
array A = [3] T # T is a qualified identifier t type name. It names the type M.T.
}
module M {
array A = [3] U8
array B = [3] A # A is an array type name. It names the type M.A.
}
module M {
enum E { X = 0, Y = 1 }
array A = [3] E # E is a qualified identifier type name. It names the type M.E.
}
module M {
struct S {
x: U32
y: F32
}
struct T {
s: S # S is a qualified identifier type name. It names the type M.S.
}
}
9. Expressions
9.1. Arithmetic Expressions
FPP includes the following arithmetic expressions:
| Syntax | Meaning |
|---|---|
|
Negate \$e\$ |
\$e_1\$ |
Add \$e_1\$ and \$e_2\$ |
\$e_1\$ |
Subtract \$e_2\$ from \$e_1\$ |
\$e_1\$ |
Multiply \$e_1\$ by \$e_2\$ |
\$e_1\$ |
Divide \$e_1\$ by \$e_2\$ |
Each subexpression of an arithmetic expression must have numeric type. Evaluation is by either standard integer arithmetic or standard floating-point arithmetic, depending on the type of the expression.
Example:
constant a = -1
constant b = 2 + 3
constant c = a * b
9.2. Array Expressions
An array expression is an expression that represents an array value.
9.2.1. Syntax
[
array-element-sequence ]
array-element-sequence is an element sequence in which the elements are expressions, and the terminating punctuation is a comma.
9.2.2. Semantics
-
The sequence must have at least one element.
-
The types of the expressions in the sequence must be convertible to a common type.
-
The value of the expression is formed by computing the value of each element and then converting all the elements to the common type.
9.2.3. Example
constant a = [ 0, 1, 2 ] # a is an array value with elements 0, 1, 2
9.3. Boolean Literals
A Boolean literal expression is one of the values true
and false
.
9.4. Dot Expressions
A dot expression is a use that refers to a constant definition or enumerated constant definition.
9.4.1. Syntax
9.4.2. Semantics
The following rules give the meaning of a dot expression \$e\$.x
:
-
If \$e\$
.x
is a qualified identifier that represents one of the uses listed above according to the rules for resolving qualified identifiers, then it evaluates to the value stored in the corresponding definition. -
Otherwise \$e\$
.x
is invalid.
9.4.3. Examples
Example 1
module M {
constant a = 1
}
constant b = M.a # M.a evaluates to 1
Example 2
enum E { X = 0, Y = 1 }
constant a = E.X # E.X evaluates to 0
9.5. Floating-Point Literals
A floating-point literal expression is a C-style representation of an IEEE floating-point number.
Examples:
1e-10
0.001
3.14159
6.02E23
9.6. Identifier Expressions
An identifier expression is an identifier that refers to a constant definition or enumerated constant definition, according to the rules for resolving identifiers.
Example:
constant a = 42
constant b = a # a is an identifier expression
9.7. Integer Literals
An integer literal expression is one of the following:
-
A sequence of decimal digits
0
through9
denoting the decimal representation of a nonnegative integer. -
0x
or0X
followed by a sequence of hexadecimal digits0
through9
,A
throughF
, ora
throughf
denoting the hexadecimal representation of a nonnegative integer.
An integer literal value \$v\$ is represented in the model as an integer of arbitrary width. During code generation, the width is narrowed (if necessary) to some machine width less than or equal to 64 bits.
Examples:
1234
0xABCD
9.8. Parenthesis Expressions
A parenthesis expression is an expression surrounded by parentheses in order to group subexpressions and to force evaluation order.
9.8.1. Syntax
(
expression
)
9.8.2. Semantics
The type and value of the expression are the type and value of the subexpression.
9.8.3. Example
constant a = (1 + 2) * 3
The expression on the right-hand side of the constant definition evaluates to 9.
9.9. String Literals
A string literal expression is a single-line string literal or a multiline string literal. In this section
-
Literal string means a literal string appearing in the FPP source text.
-
Interpreted string means the sequence of characters represented by the literal string.
-
String interpretation is the process of converting a literal string to an interpreted string.
-
A newline character is the NL character (ASCII code 0x0A).
-
An extended newline character is the NL character (ASCII code 0x0A), optionally preceded by a CR character (ASCII code 0x0D).
9.9.1. Single-Line String Literals
A single-line string literal is a sequence of single-line literal characters
enclosed in double
quote characters "
.
A single-line literal character is one of the following:
-
Any character other than the newline character,
"
, or\
. -
\
followed by any character c other than the newline character. In this case we say that the character c is escaped.
The characters of the interpreted string correspond one-to-one
to the literal characters of the string, in the same order,
according to the following rule:
if the literal character is c or \
c, then the corresponding character
of the interpreted string is c.
Examples:
-
"abc"
is a valid string consisting of the charactersa
,b
, andc
. -
"ab\""
is a valid string consisting of the charactersa
,b
, and"
. -
"ab\\\""
is a valid string consisting of the charactersa
,b
,\
, and"
. -
"ab\c"
is a valid string consisting of the charactersa
,b
,c
. -
"abc
is not a valid string, because it is missing the terminating"
. -
"\"
is not a valid string, because it is missing the terminating"
. -
"ab"c"
is the valid string"ab"
followed by the identifierc
and an unmatched double quote character.
9.9.2. Multiline String Literals
A multiline string literal is a sequence of multiline literal characters enclosed
in sequences """
of three double quote characters.
A multiline literal character is one of the following:
-
Any character other than
"
or\
. -
\
followed by any character.
Before trimming whitespace,
the characters of the interpreted string correspond one-to-one
to the literal characters of the string, in the same order,
according to the following rule:
if the literal character is c or \
c, then the corresponding character
of the interpreted string is c.
Whitespace inside a multiline string literal is trimmed as follows:
-
The first extended newline character, if any, after the first
"""
is deleted from the interpreted string. -
Any whitespace occurring in columns to the left of the first
"""
is deleted from the result of step 1. -
Any leading and trailing extended newline characters are deleted from the result of step 2.
Example 1
constant s = """\"\"\""""
The string literal uses escaped quotation marks.
The value of the string constant s
is """
.
Example 2
"""
// This is a multiline string literal
// It represents some C++ code
instance.create(0, 1);
"""
The interpreted string consists of the following lines, each terminated by a newline:
-
// This is a multiline string literal
-
// It represents some C++ code
-
instance.create(0, 1);
9.10. Struct Expressions
An struct expression is an expression that represents a struct value.
9.10.1. Syntax
{
struct-element-sequence }
struct-element-sequence is an element sequence in which the elements are struct elements, and the terminating punctuation is a comma. A struct element has the following syntax:
9.10.2. Semantics
The following must be true of the struct element sequence S:
-
No two identifiers appearing in S may be the same.
-
Each expression appearing in S must have a valid type.
The expression is evaluated by evaluating each member expression to a value and then constructing the struct value with the corresponding member names and values.
9.10.3. Example
# s is a struct value with members x = 0, y = 1
constant s = {
x = 0
y = 1
}
9.11. Precedence and Associativity
Ambiguity in parsing expressions is resolved with the following
precedence table. Expressions appearing earlier in the table
have higher precedence. For example, -a.b
is parsed as -(a.b)
and not (-a).b
. Where necessary, each element in the ordering provides an
associativity for resolving expressions with equal precedence.
| Expression | Associativity |
|---|---|
Dot expressions \$e\$ |
None |
Unary negation expressions |
None |
Multiplication expressions \$e_1\$ |
Left |
Addition expressions \$e_1\$ |
Left |
10. Formal Parameter Lists
A formal parameter list is an element sequence in which the elements are formal parameters, and the terminating punctuation is a comma.
A formal parameter is a typed symbol to which a value becomes bound at runtime. Formal parameters appear, for example, in port definitions.
10.2. Semantics
If present, the keyword ref
specifies that the value
bound to the formal parameter is to be passed by reference
when it is used in a synchronous port invocation.
The identifier is the name of the formal parameter. No two parameters in the same formal parameter list may have the same name.
The type name specifies the type T of the formal parameter P.
10.3. Examples
a: U32
b: string size 100
ref c: Fw.Com
11. Format Strings
A format string is a string that specifies the display format of a struct member, array element, event report, or telemetry channel.
An FPP format string is similar to a
Python format
string.
As in python, a format string consists of text containing one or more
replacement fields surrounded by curly braces {
}
.
In the output, each replacement field is replaced by the value of an
argument.
Characters outside of replacement fields are copied to the output unchanged,
except that the escape sequences {{
and }}
are converted to single
braces.
The number of arguments must match the number of replacement fields. Each replacement field must also match the type of its argument, as discussed below.
The following replacement fields are allowed:
-
You can use the default replacement field
{}
to convert any argument. It causes the argument to be displayed in a standard way for F Prime according to its type. -
If the type of an argument a is an integer type, then you can use an integer replacement field to convert a. An integer replacement field is one of the following:
-
{c}
: Display a as a character value. -
{d}
: Display a as a decimal integer value -
{x}
: Display a as a hexadecimal integer value. -
{o}
: Display a as an octal integer value.
-
-
If the type of an argument a is a floating-point type, then you can use a rational replacement field to convert a. A rational replacement field is one of the following:
-
{e}
: Display a as a rational number using exponent notation, e.g.,1.234e2
-
{f}
: Display a as a rational number using fixed-point notation, e.g.,123.4
. -
{g}
: Display a as a rational number using general format. This format uses fixed-point notation for numbers up to some size s and uses exponent notation for numbers larger than s. The value of s is implementation-dependent. -
One of the replacement fields denoted above, but with a period and a literal decimal integer after the opening brace. The integer value n specifies the precision, i.e., the number of digits after the decimal point for fixed-point notation, or before the
e
for exponent notation. For example, the replacement field{.3f}
, specifies fixed-point notation with a precision of 3. n must be in the range [0,100].
-
No other replacement fields are allowed.
No other use of {
or }
is allowed, except in the escape sequences
{{
and }}
.
12. Comments and Annotations
12.1. Comments
A comment is model text that is ignored by the translator. It provides information to human readers of the source model.
Comments begin with the character #
and go to the end of the line. For
example:
# This is a comment
To write a multiline comment, precede each line with #
:
# This is a multiline comment.
# It has two lines.
12.2. Annotations
An annotation is similar to a comment, but it is attached to a syntactic element of a model, and it is preserved during analysis and translation. The precise use of annotations depends on the translator. A typical use is to include annotations as comments in generated code.
12.2.1. Where Annotations Can Occur
Annotations can occur at the following syntax elements:
These elements are called the annotatable elements of an FPP model.
12.2.2. Kinds of Annotations
There are two kinds of annotations: pre-annotations and post-annotations.
A pre-annotation starts with @
and goes to the end of the line.
Whitespace characters after the initial @
are ignored. A
pre-annotation must occur immediately before an
annotatable element e. It is attached to e during translation.
A post-annotation starts with @<
and goes to the end of the line.
Whitespace characters after the initial @<
are ignored. A
post-annotation must occur immediately after an
annotatable element e. It is attached to e during translation.
You may follow either kind of annotation by one or more blank lines. In this case the blank lines are ignored.
Example
@ This is module M
module M {
constant a = 0 @< This is constant M.a
constant b = 1 @< This is constant M.b
@ This is an enum
enum E {
a @< This is enumerated constant M.E.a
b @< This is enumerated constant M.E.b
}
}
12.2.3. Multiline Annotations
You can write several pre-annotations in a row before an annotatable element e. In this case, all the pre-annotations are attached to the element, in the order that they appear.
Similarly, you can write several post-annotations in a row after an annotatable element e. In this case, all the post-annotations are attached to the element, in the order that they appear.
Example
@ This is module M
@ Its pre-annotation has two lines
module M {
constant a = 0 @< This is constant M.a
@< Its post-annotation has two lines
constant b = 1 @< This is constant M.b
@< Its post-annotation has two lines
@ This is an enum
@ Its pre-annotation has two lines
enum E {
a @< This is enumerated constant M.E.a
@< Its post-annotation has two lines
b @< This is enumerated constant M.E.b
@< Its post-annotation has two lines
}
}
13. Translation Units and Models
13.1. Translation Units
A translation unit forms part of a model at the top level. Translation units usually correspond to source files of a program. However, translation units may be specified in other ways, e.g., on standard input.
13.1.1. Syntax
A translation unit is an element sequence in which each element is a translation unit member, and the terminating punctuation is a semicolon. A translation unit member is syntactically identical to a module member.
13.1.2. Example
Here is a translation unit:
module M1 { constant a = 0 }
And here is another one:
module M1 { constant b = 0 }
And here is a third one:
module M2 {
constant a = M1.a
constant b = M1.b
}
Call these translation units 1, 2, and 3 for purposes of the example in the following section.
13.2. Models
A model is a collection of one or more translation units. A model is presented to one or more analyzers or translators for analysis or translation. How this is done depends on the analyzer or translator. Typically, you ask a translator to read a single translation unit from standard input and/or to read one or more translation units stored in files, one unit per file.
Example:
Translation units 1-3 in the previous section, taken together, form a single model. That model is equivalent to the following single translation unit:
module M1 { constant a = 0 }
module M1 { constant b = 1 }
module M2 {
constant a = M1.a
constant b = M1.b
}
According to the semantics of module definitions, this is also equivalent:
module M1 {
constant a = 0
constant b = 1
}
module M2 {
constant a = M1.a
constant b = M1.b
}
Note that translation unit 3 alone is not a valid model, because it uses free symbols defined in the other translation units. Similarly, the translation unit 3 together with just translation unit 1 or translation unit 2 is not a valid model.
13.3. Locations
Every syntactic element E in a source model has an associated location L. The location is used when resolving location specifiers and include specifiers during analysis.
-
If E is read in from a file, then L is the absolute path of the file.
-
If E is read from standard input, then L is the absolute path of the current directory in the environment where the analysis occurs.
14. Scoping of Names
14.1. Qualified Identifiers
A qualified identifier is one of the following:
-
An identifier.
-
Q
.
I, where Q is a qualified identifier and I is an identifier.
Examples:
a
a.b
a.b.c
14.2. Names of Definitions
Every definition D appearing in an FPP model has a unique qualified name. The qualified name is a qualified identifier formed as follows:
-
If D appears outside any definition with a brace-delimited body, then the qualified name is the identifier I appearing in D.
-
Otherwise, the qualified name is N
.
I, where N is the qualified name of the enclosing definition, and I is the identifier appearing in D.
For example:
module M { # Qualified name is M
module N { # Qualified name is M.N
enum Maybe { # Qualified name is M.N.Maybe
NO # Qualified name is M.N.Maybe.NO
YES # Qualified name is M.N.Maybe.YES
}
}
}
14.3. Name Groups
The qualified names of definitions and reside in the following name groups:
-
The component instance name group
-
The component name group
-
The port name group
-
The topology name group
-
The type name group
-
The value name group
Definitions reside in name groups as follows:
-
Abstract type definitions reside in the type name group.
-
Array type definitions reside in the type name group.
-
Component definitions reside in the component name group, the value name group, and the type name group.
-
Component instance definitions reside in the component instance name group.
-
Constant definitions reside in the value name group.
-
Enum definitions reside in both the value name group and the type name group.
-
Enumerated constant definitions reside in the value name group.
-
Module definitions reside in all the name groups.
-
Port definitions reside in the port name group.
-
Struct type definitions reside in the type name group.
-
Topology definitions reside in the topology name group.
Notice that elements with scopes reside in the name groups of items that they may enclose. For example, enums reside in the value name group because they enclose constant definitions.
14.4. Multiple Definitions with the Same Qualified Name
14.4.1. Different Name Groups
Two definitions with the same qualified name are allowed if they are in different name groups. For example:
struct s { x: U32 } # Defines s in the type name group
constant s = 0 # Defines s in the value name group
14.4.2. Module Definitions
Multiple syntactic module definitions with the same qualified name are allowed. The semantic analysis combines all such definitions into a single module definition with that qualified name. For example, this model is legal
module M { constant a = 0 }
module M { constant b = 1 }
a = M.a
b = M.b
It is equivalent to this model:
module M {
constant a = 0
constant b = 1
}
a = M.a
b = M.b
Because the order of definitions is irrelevant, this is also equivalent:
module M { constant a = 0 }
a = M.a
b = M.b
module M { constant b = 1 }
14.4.3. Conflicting Definitions
Within the same name group, two definitions with the same qualified name are not allowed, unless they are both module definitions as described above. For example:
module M {
constant a = 0
constant a = 1 # Error: Name M.a is redefined
}
Two definitions with the same identifier are allowed if they have different qualified names, for example:
constant a = 0
module M {
constant a = 1 # OK, qualified name is M.a =/= a
}
14.5. Resolution of Identifiers
The following rules govern the resolution of identifiers, i.e., associating identifiers with definitions:
-
Use the context to determine which name group S to use. For example, if we are expecting a type name, then use the type name group.
-
At the top level (outside the brace-delimited body of any definition), the identifier I refers to the unique definition with qualified name I if it exists in name group S. Otherwise an error results.
-
Inside the brace-delimited body of a definition with qualified name N appearing at the top level:
-
The identifier I refers to the definition with qualified name N
.
I if it exists in name group S. -
Otherwise I refers to the definition with qualified name I if it exists in name group S.
-
Otherwise an error results.
-
-
Inside the brace-delimited body of a definition with qualified name N appearing inside the body of a definition D:
-
The identifier I refers to the definition with qualified name N
.
I if it exists in name group S. -
Otherwise proceed as if I were appearing inside D.
-
Example:
S refers to the value name group.
# Identifier M is in scope in S and refers to the qualified name M
# Identifier a is in scope in S and refers to qualified name a
constant a = 1 # Unique definition in S with qualified name a
module M {
# Identifier M is in scope in S and refers to the qualified name M
# Identifier N is in scope in S and refers to the qualified name N
# Identifier a is in scope in S and refers to qualified name a
# Identifier b is in scope in S and refers to qualified name M.b
constant b = 2 # Unique definition in S with qualified name M.b
}
# Identifier M is in scope in S and refers to the qualified name M
# Identifier a is in scope in S and refers to qualified name a
module M {
# Identifier M is in scope in S and refers to the qualified name M
# Identifier N is in scope in S and refers to the qualified name M.N
# Identifier a is in scope and refers to qualified name a
# Identifier b is in scope and refers to qualified name M.b
module N {
# Identifier M is in scope in S and refers to the qualified name M
# Identifier N is in scope in S and refers to the qualified name M.N
# Identifier a is in scope in S and refers to qualified name a
# Identifier b is in scope in S and refers to qualified name M.N.b
constant b = 3 # Unique definition in S with qualified name M.N.b
}
}
# Identifier M is in scope in S and refers to the qualified name M
# Identifier a is in scope in S and refers to qualified name a
14.6. Resolution of Qualified Identifiers
The following rules govern the resolution of qualified identifiers, i.e., associating qualified identifiers with definitions:
-
If a qualified identifier is an identifier, then resolve it as stated in the previous section.
-
Otherwise, the qualified identifier has the form Q
.
I, where Q is a qualified identifier and I is an identifier. Do the following:-
Recursively resolve Q.
-
If Q refers to a definition with a brace-delimited body, then do the following:
-
Determine the name group S of Q
.
I. -
Look in D for a definition with identifier I in name group S. If there is none, issue an error.
-
-
Otherwise the qualified identifier is invalid. Issue an error.
-
Example:
module M {
constant a = 0
enum E {
b = 2
c = b # Refers to M.E.b
d = E.b # Refers to M.E.b
e = M.E.b # Refers to M.E.b
}
constant f = a # Refers to M.a
constant g = M.a # Refers to M.a
constant h = E.b # Refers to M.E.b
constant i = M.E.b # Refers to M.E.b
}
15. Definitions and Uses
15.1. Uses
A use is a qualified identifier that refers to a definition according to the scoping rules for names.
A use is a qualifying use if it qualifies another use. For example, in the following code
-
The expression
M.a
contains the expressionM
, which is a qualifying use of the moduleM
. It qualifies the useM.a
. -
The expression
M.a
is not a qualifying use.
module M {
constant a = 0
constant b = M.a
}
If a use u is not a qualifying use, then we say it is a non-qualifying use.
15.2. Use-Def Graph
The set of definitions and non-qualifying uses in an FPP model induces a directed graph called the use-def graph. In this graph,
-
The nodes are the definitions.
-
There is an edge from each definition d to the definitions \$d_1, ..., d_n\$ corresponding to the non-qualifying uses \$u_1, ..., u_n\$ appearing in d.
For example, in the following code, the use-def graph has two nodes a
and
b
and one edge b
\$rarr\$ a
.
constant a = 0
constant b = a
In a legal FPP model, the use-def graph must be acyclic. For example, this model is illegal:
constant a = b
constant b = a
This model is also illegal:
constant a = a
This model is legal:
module M {
constant a = 0
constant b = M.a
}
The use M
appears inside the definition of M
.
However, it is a qualifying use.
15.3. Order of Definitions and Uses
So long as the
use-def graph is acyclic, there is no
constraint either on the ordering of
definitions and uses within a
translation unit,
or on the distribution of definitions and uses among translation
units. For example, if the definition constant c = 0
appears anywhere
in any translation unit of a model M, then the use of c
as a
constant value of type U8 is legal anywhere in any translation unit of
M. In particular, this model is legal:
constant b = a
constant a = 0
The model consisting of two translation units
constant b = a
and
constant a = 0
is also legal, and the order in which the units are presented to the translator does not matter.
16. Types
16.1. Primitive Integer Types
The primitive integer types correspond to the
primitive integer type names
and are written in the same way, e.g., U32
.
16.2. Floating-Point Types
The floating-point types correspond to the
floating-point type names
and are written in the same way, e.g., F64
.
16.3. Primitive Numeric Types
The primitive integer types together with the floating-point types are called the primitive numeric types.
16.4. The Boolean Type
The Boolean type corresponds to the
Boolean type name.
It is written bool
.
16.5. Primitive Types
Together, the primitive numeric types and the Boolean type are called the primitive types.
16.6. String Types
The string types correspond to the
string type names.
A string type is written string
or string size
n,
where n is an integer value in the range [0,231].
There is one string type string
and one string type string
n
for each legal value of n.
16.7. Array Types
An array type is a type associated with an array definition. There is one array type per array definition. Each array type is computed after resolving constants, so an array type has a constant integer value for its size. An array type is written using its unique qualified name.
16.8. Enum Types
An enum type is a type associated with an enum definition. There is one enum type per enum definition. An enum type is written using its unique qualified name.
16.9. Struct Types
A struct type is a type associated with a struct definition. There is one struct type per struct definition. A struct type is written using its unique qualified name.
16.10. Abstract Types
An abstract type is a type associated with an abstract type definition. There is is one abstract type per abstract type definition. An abstract type is written using its unique qualified name.
16.11. Internal Types
Internal types do not have syntactic names in FPP source models. The compiler assigns these types to expressions during type checking.
16.11.1. Integer
The type Integer represents all integer values, without regard to bit width.
16.11.2. Integer Types
Integer together with the primitive integer types are called the integer types.
16.11.3. Numeric Types
Integer together with the primitive numeric types are called the numeric types.
16.11.4. Anonymous Array Types
The type [ n ] T, where n is an integer and T is a type, represents an array of n elements, each of type T.
16.11.5. Anonymous Struct Types
The type { \$m_1\$ : \$T_1, ...,\$ \$m_n\$ : \$T_n\$ }, where each \$m_i\$ is an identifier and each \$T_i\$ is a type, represents a struct with members \$m_i\$ : \$T_i\$.
The order of the members is not significant.
For example, { a: U32, b: F32 }
represents the same
type as { b : F32, a: U32 }
.
16.12. Types with Numeric Members
A type has numeric members if it is one of the following:
-
A numeric type.
-
An array type or anonymous array type whose member type has numeric members.
-
A struct type or anonymous struct type whose member types all have numeric members.
16.13. Default Values
Every type T with a syntactic name in FPP has an associated default value. In generated C++ code, this is the value that is used to initialize a variable of type T when no other initializer is specified. Default values are important, because they ensure that in generated code, every variable is initialized when it is created.
-
The default value associated with each primitive numeric type is zero.
-
The default value associated with
bool
isfalse
. -
The default value associated with any string type is the empty string.
-
The default value associated with an array type T is (1) the default value specified in the array definition, if one is given; otherwise (2) the unique value of type T that has the default value of the member type of T at each member. See the section on array definitions for examples.
-
The default value associated with an enum type is (1) the default value specified in the enum definition, if one is given; otherwise (2) the first enumerated constant appearing in the enum definition.
-
The default value associated with a struct type T is (1) the default value specified in the struct definition, if one is given; otherwise (2) the unique value of type T that has the default value of the member type \$T_i\$ for each member \$m_i\$
:
\$T_i\$ of T. See the section on struct definitions for examples. -
The default value associated with an abstract type T is the single value associated with T. This value is left abstract in the FPP model; the implementation of T must provide a concrete value.
17. Type Checking
In this section, we explain the rules used to assign types to expressions. FPP is a statically typed language. That means the following:
-
Type checking of expressions occurs during analysis.
-
If the type checking phase detects a violation of any of these rules, then translation halts with an error message and does not produce any code.
Each type represents a collection of values. The type checking rules exist to ensure that whenever an expression of type \$T\$ is evaluated, the result is a value of type \$T\$.
17.1. Integer Literals
To type an integer literal expression, the semantic analyzer does the following:
-
Evaluate the expression to an unsigned integer value \$v\$.
-
Assign the type Integer to the expression.
Examples:
constant a = 0 # Type is Integer
constant b = 1 # Type is Integer
constant c = 256 # Type is Integer
constant d = 65536 # Type is Integer
constant e = 0x100000000 # Type is Integer
17.2. Floating-Point Literals
The type of a
floating-point
literal expression is F64
.
17.3. Boolean Literals
The type of a
boolean
literal expression is bool
.
17.4. String Literals
The type of a
string
literal expression is string
.
17.5. Identifier Expressions
To type an identifier expression \$e\$, the semantic analyzer resolves the identifier to a definition and uses the type given in the definition.
Example:
constant a = 42 # a is a constant with type Integer
constant b = a # The expression a refers to the constant a and has type Integer
17.6. Dot Expressions
To type a
dot
expression \$e\$ .x
, the semantic analyzer does the following:
-
If \$e\$
.x
is a qualified identifier that represents the use of a definition according to the rules for resolving qualified identifiers, and the use is a valid dot expression, then use the type given in the definition. -
Otherwise \$e\$
.x
is invalid. Throw an error.
Example:
module M {
constant a = 0 # The constant M.a has type Integer
}
constant b = M.a # Expression M.a represents a use of the definition M.a.
# Its type is Integer.
17.7. Array Expressions
To type an
array expression
[
\$e_1\$ ,
\$...\$ ,
\$e_n\$ ]
,
the semantic analyzer does the following:
-
For each \$i in [1,n\$], compute the type \$T_i\$ of \$e_i\$.
-
Compute the common type \$T\$ of the list of types \$T_i\$.
-
Assign the type [ \$n\$ ] \$T\$ to the expression.
Examples:
constant a = [ 1, 2, 3 ] # Type is [3] Integer
constant b = [ 1, 2, 3.0 ] # Type is [3] F64
17.8. Struct Expressions
To type a
struct expression
{
\$m_1\$ =
\$e_1\$ ,
\$...\$ ,
\$m_n\$ =
\$e_n\$ }
,
the semantic analyzer does the following:
-
Check that the member names \$m_i\$ are distinct.
-
For each \$i in [1,n\$], compute the type \$T_i\$ of \$e_i\$.
-
Assign the type { \$m_1\$ = \$T_1\$ , \$...\$ , \$m_n\$ = \$T_n\$ } to the expression.
Examples:
constant a = { x = 1, y = 2.0 } # Type is { x: Integer, y: F64 }
constant b = { x = 1, x = 2 } # Error
17.9. Unary Negation Expressions
To type a
unary
negation expression -
\$e\$, the semantic analyzer does the following:
-
Compute the type \$T\$ of \$e\$.
-
If \$T\$ is a numeric type, then use \$T\$.
-
Otherwise if \$T\$ may be converted to Integer, then use Integer.
-
Otherwise throw an error.
Examples:
constant a = -1.0 # Type is F64
constant b = -1 # Type is Integer
constant c = -c # Type is Integer
constant d = -0xFFFFFFFF # Type is Integer
enum E { X = 1 }
constant e = -E.X # Type is Integer
constant f = -true # Error
17.10. Binary Arithmetic Expressions
To type a binary arithmetic expression \$e_1\$ op \$e_2\$, the semantic analyzer does the following:
-
Compute the common type \$T\$ of \$e_1\$ and \$e_2\$.
-
If \$T\$ is a numeric type, then use \$T\$.
-
Otherwise if \$T\$ may be converted to Integer, then use Integer.
-
Otherwise throw an error.
Examples:
constant a = 1 + 2 # Type is Integer
constant b = 3 + 4 # Type is Integer
constant c = -0xFFFFFFFF # Type is Integer
enum E { X = 1, Y = 2 }
constant d = X + Y # Type is Integer
constant e = true + "abcd" # Error
17.11. Parenthesis Expressions
To type a
parenthesis
expression (
\$e\$ )
, the semantic analyzer does the following:
-
Compute the type \$T\$ of \$e\$.
-
Use \$T\$ as the type of the expression.
Examples:
constant a = (1.0 + 2) # Type is F64
constant b = (3 + 4) # Type is Integer
constant c = (true) # Type is bool
constant d = ("abcd") # Type is string
constant e = ([ 1, 2, 3]) # Type is [3] Integer
17.12. Identical Types
We say that types \$T_1\$ and \$T_2\$ are identical if one of the following holds:
-
\$T_1\$ and \$T_2\$ are numeric types with the same name, e.g.,
U32
or Integer. In each case of a numeric type, the name of the type uniquely identifies the type. -
\$T_1\$ and \$T_2\$ are both the Boolean type.
-
\$T_1\$ and \$T_2\$ are both the same string type.
-
Each of \$T_1\$ and \$T_2\$ is an array type, enum type, or struct type, and both types refer to the same definition.
17.13. Type Conversion
We say that a type \$T_1\$ may be converted to another type \$T_2\$ if every value represented by type \$T_1\$ can be converted into a value of type \$T_2\$.
Here are the rules for type conversion:
-
\$T_1\$ may be converted to \$T_2\$ if \$T_1\$ and \$T_2\$ are identical types.
-
Any string type may be converted to any other string type.
-
Any numeric type may be converted to any other numeric type.
-
An enum type may be converted to a numeric type.
-
If \$T_1\$ or \$T_2\$ or both are array types, then \$T_1\$ may be converted to \$T_2\$ if the conversion is allowed after replacing the array type or types with structurally equivalent anonymous array types.
-
An anonymous array type \$T_1 =\$ [ \$n\$ ] \$T'_1\$ may be converted to the anonymous array type \$T_2 =\$ [ \$m\$ ] \$T'_2\$ if \$n = m\$ and \$T'_1\$ may be converted to \$T'_2\$.
-
A numeric type type, Boolean type, enum type, or string types \$T\$ may be converted to an anonymous array type \$T_1\$ may be converted to the member type of \$T_2\$.
-
If \$T_1\$ or \$T_2\$ or both are struct types, then \$T_1\$ may be converted to \$T_2\$ if the conversion is allowed after replacing the struct type or types with structurally equivalent anonymous struct types. For purposes of structural equivalence, the member sizes of struct types are ignored. For example, the struct type
S
defined bystruct S { x: [3] U32 }
is structurally equivalent to the anonymous struct type{ x: U32 }
. -
An anonymous struct type \$T =\${ \$m_1\$ : \$T_1\$ , \$...\$ , \$m_1\$ : \$T_n\$ } may be converted to the anonymous struct type \$T'\$ if for each \$i in [1,n]\$,
-
\$m_i\$ : \$T'_i\$ is a member of \$T'\$; and
-
\$T_i\$ may be converted to \$T'_i\$.
-
-
A numeric type type, Boolean type, enum type, or string type \$T\$ may be converted to an anonymous struct type \$T'\$ if for each member \$m_i\$
:
\$T_i\$ of \$T'\$, \$T\$ may be converted to \$T_i\$. -
Type convertibility is transitive: if \$T_1\$ may be converted to \$T_2\$ and \$T_2\$ may be converted to \$T_3\$, then \$T_1\$ may be converted to \$T_3\$.
17.14. Computing a Common Type
17.14.1. Pairs of Types
Here are the rules for resolving two types \$T_1\$ and \$T_2\$ (e.g., the types of two subexpressions) to a common type \$T\$ (e.g., the type of the whole expression):
-
If \$T_1\$ and \$T_2\$ are identical types, then let \$T\$ be \$T_1\$.
-
Otherwise if \$T_1\$ and \$T_2\$ are both numeric types, then do the following:
-
If \$T_1\$ and \$T_2\$ are both floating-point types, then use
F64
. -
Otherwise use Integer.
-
-
Otherwise if \$T_1\$ and \$T_2\$ are both string types, then use
string
. -
Otherwise if \$T_1\$ or \$T_2\$ or both are enum types, then replace the enum type or types with the representation type specified in the enum definitions and and reapply these rules.
-
Otherwise if \$T_1\$ or \$T_2\$ or both are array types, then replace the array type or types with structurally equivalent anonymous array types and reapply these rules.
-
Otherwise if \$T_1\$ and \$T_2\$ are anonymous array types with the same size \$n\$ and with member types \$T'_1\$ and \$T'_2\$, then apply these rules to resolve \$T'_1\$ and \$T'_2\$ to \$T'\$ and let \$T\$ be [ \$n\$ ] \$T'\$.
-
Otherwise if one of \$T_1\$ and \$T_2\$ is a type \$T'\$ that is convertible to an anonymous array type and the other one is an anonymous array type [ \$n\$ ] \$T''\$, then apply these rules to resolve \$T'\$ and \$T''\$ to a common type \$T'''\$. Let \$T\$ be the array type [ \$n\$ ] \$T'''\$.
-
Otherwise if \$T_1\$ or \$T_2\$ or both are struct types, then replace the struct type or types with structurally equivalent anonymous struct types and reapply these rules.
-
Otherwise if \$T_1\$ and \$T_2\$ are both anonymous struct types, then use the anonymous struct type \$T\$ with the following members:
-
For each member \$m_1\$ : \$T'_1\$ of \$T_1\$, if \$T_2\$ has a member \$m_1\$ : \$T'_2\$, then apply these rules to convert \$T'_1\$ and \$T'_2\$ to a common type \$T'\$ and use \$m_1\$ : \$T'\$. Otherwise use \$m_1\$ : \$T'_1\$.
-
For each member \$m_2\$ : \$T'_2\$ of \$T_2\$ such that \$T_1\$ has no member named \$m_2\$, use \$m_2\$ : \$T'_2\$.
-
-
Otherwise if one of \$T_1\$ and \$T_2\$ is a type \$T'\$ that is convertible to an anonymous struct type and the other one is an anonymous struct type \$S\$, then apply these rules to resolve \$T'\$ and each of the struct member types to a common type. Let \$T\$ be the struct type whose member names are the member names of \$S\$ and whose member types are the corresponding common types.
-
Otherwise the attempted resolution is invalid. Throw an error.
17.14.2. Lists of Types
To compute a common type for a list of types \$T_1, ... , T_n\$, do the following:
-
Check that \$n > 0\$. If not, then throw an error.
-
Compute the type \$T_1\$ of \$e_1\$.
-
For each \$i in [2,n\$]
-
Compute the type \$T\$ of \$e_i\$.
-
Compute the common type \$T_i\$ of \$T_(i-1)\$ and \$T\$.
-
-
Use \$T_n\$ as the common type of the list.
18. Values
A value is one of the following:
-
A primitive integer value
-
An integer value
-
A floating-point value
-
A Boolean value
-
A string value
-
An abstract type value
-
An anonymous array value
-
An array value
-
An enumeration value
-
An anonymous struct value
-
A struct value
Every value belongs to exactly one type.
18.1. Primitive Integer Values
A primitive integer value is an ordinary (mathematical) integer value together with a primitive integer type. Formally, the set of primitive integer values is the disjoint union over the integer types of the values represented by each type:
-
An unsigned integer type of width \$w\$ represents integers in the range \$[0, 2^w-1]\$. For example,
U8
represents the integers \$[0, 255]\$. -
A signed integer type of width \$w\$ represents integers in the range \$[-2^(w-1), 2^(w-1)-1]\$. For example,
I8
represents the integers \$[-128, 127]\$.
We represent a primitive integer value as an expression followed by a colon and a type.
For example, we write the value 1 at type U32
as 1: U32
. The value 1:
U32
is distinct from the value 1: U8
.
18.2. Integer Values
An integer value is an ordinary (mathematical) integer value.
It has type Integer.
We represent an integer value as an integer number, with no explicit type.
For example, 1
is an integer value.
18.3. Floating-Point Values
A floating-point value is an IEEE floating-point value of 4- or 8-byte
width. Formally, the set of floating-point values is the disjoint union
over the types F32
and F64
of the values represented by each type:
-
The type
F32
represents all IEEE 4-byte floating-point values. -
The type
F64
represents all IEEE 8-byte floating-point values.
We write a floating-point values analogously to primitive integer values. For
example, we write the value 1.0 at type F32
as 1.0: F32
.
18.4. Boolean Values
A Boolean value is one of the values true
and false
.
Its type is bool
.
18.5. String Values
A string value is a sequence of characters that can be
represented as a string literal expression.
It is written in the same way as a string literal expression,
e.g., "abc"
.
Its type is string
.
18.6. Abstract Type Values
An abstract type value is a value associated with an abstract
type.
There is one value associated with each abstract type \$T\$.
We write the value value of type
\$T\$.
18.7. Anonymous Array Values
An anonymous array value is a value associated with an anonymous
array type.
We write an anonymous array value similarly to an
array expression:
an anonymous array value has the form [
\$v_1\$ ,
\$...\$ ,
\$v_n\$ ]
, where for each \$i in [1,n\$], \$v_i\$ is a value of type
\$T\$ for some \$T\$.
The type of the value is [ \$n\$ ] \$T\$.
18.8. Array Values
An array value is a value associated with an array type. We write an array value like an anonymous array value, except that the value is annotated with an array type.
An array value has the form [
\$v_1\$ ,
\$...\$ ,
\$v_n\$ ]
:
\$Q\$,
where
-
\$Q\$ is a qualified identifier that refers to a array definition with member type \$T\$.
-
For each \$i in [1,n\$], \$v_i\$ is a value of type \$T\$.
The type of the value is \$Q\$.
18.9. Enumeration Values
An enumeration value is a value associated with an enumerated constant definition. It is a pair consisting of the name and the integer value specified in the enumerated constant definition. Its type is the type associated with the enum definition in which the enumerated constant definition appears.
18.10. Anonymous Struct Values
An anonymous struct value is a value associated with an
anonymous struct
type.
We write an anonymous struct value \$v\$ similarly to a
struct expression:
a struct value has the form {
\$m_1\$ =
\$v_1\$ ,
\$...\$ ,
\$m_n\$ =
\$v_n\$ }
,
where for each \$i in [1,n\$], \$v_i\$ is a value of type \$T_i\$.
The type of \$v\$ is { \$m_1\$ : \$T_1\$ , \$...\$ ,
\$m_n\$ : \$T_n\$ }.
18.11. Struct Values
A struct value is a value associated with a
struct type.
We write a struct value similarly to an
anonymous struct value,
except that we annotate the value with a struct type:
a struct value has the form {
\$m_1\$ :
\$v_1\$ ,
\$...\$ ,
\$m_n\$ :
\$v_n\$ }
:
\$Q\$,
where
-
\$Q\$ is a qualified identifier that refers to a struct definition.
-
The members of \$Q\$ are \$m_i\$
:
\$T_i\$ for \$i in [1,n]\$. -
For each \$i in [1,n\$], \$v_i\$ is a value of type \$T_i\$.
All the members must be explicitly assigned values.
18.12. Serialized Sizes
Every value v whose type has a syntactic representation in FPP has a serialized size. This is the number of bytes required to represent v in the standard F Prime serialized format. The serialized size s of a value v depends on the type T of v:
-
If T is a primitive numeric type, then s is the byte width of the type. For example, the serialized size of a value of type
F64
is 8. -
If T is
bool
, then s is 1. -
If T is a string type, then s is the number of bytes used to represent the length of a string plus the length of the string in characters. The number of bytes used to represent the length of a string is implementation-specific.
-
If T is an array type, then s is sum of the serialized sizes of the elements of v.
-
If T is an enum type, then s is the byte width of the representation type of T.
-
If T is a struct type, then s the sum of the serialized sizes of the members of v
-
If T is an abstract type, then s is not specified in FPP. It is up to the implementer of T to provide the serialized size.
19. Evaluation
Evaluation is the process of transforming an expression into a value. In FPP, all evaluation happens during analysis, in resolving expressions to compile-time constant values.
19.1. Evaluating Expressions
Evaluation of expressions occurs as stated in the expression descriptions. Evaluation of integer expressions occurs at type Integer, using enough bits to represent the result without overflow. Evaluation of floating-point expressions occurs using 64-bit floating-point arithmetic.
19.2. Type Conversion
The following rules govern the conversion of a value \$v_1\$ of type \$T_1\$ to a value \$v_2\$ of type \$T_2\$.
19.2.1. Unsigned Primitive Integer Values
-
If \$T_1\$ and \$T_2\$ are both unsigned primitive integer types and \$T_2\$ is narrower than \$T_1\$, then construct \$v_2\$ by truncating the unsigned binary representation of \$v_1\$ to the width of \$v_2\$. For example, converting
0x1234: U16
toU8
yields0x34: U8
. -
Otherwise if \$T_1\$ and \$T_2\$ are both unsigned primitive integer types, then \$v_2\$ is the integer value of \$v_1\$ at the type of \$v_2\$. For example, converting
0x12: U8
toU16
yields0x12: U16
.
19.2.2. Signed Primitive Integer Values
-
If \$T_1\$ and \$T_2\$ are both signed primitive integer types and \$T_2\$ is narrower than \$T_1\$, then construct \$v_2\$ by truncating the two’s complement binary representation of \$v_1\$ to the width of \$v_2\$. For example, converting
-0x1234: I16
toI8
yields-0x34: I8
. -
Otherwise if \$T_1\$ and \$T_2\$ are both signed primitive integer types, then \$v_2\$ is the integer value of \$v_1\$ at the type of \$v_2\$. For example, converting
-0x12: I8
toI16
yields-0x12: I16
.
19.2.3. Primitive Integer Values of Mixed Sign
If \$T_1\$ and \$T_2\$ are primitive integer types with one signed and one unsigned, then do the following:
-
Construct the value \$v\$ by converting \$v_1\$ to the type \$T\$, where \$T\$ is signed if \$T_1\$ is signed and unsigned if \$T_1\$ is unsigned, and \$T\$ has the same width as \$T_2\$.
-
Construct \$v_2\$ by converting \$v\$ to \$T_2\$.
For example converting -1: I8
to U16
yields 0xFFFF: U16
19.2.4. Primitive and Non-Primitive Integer Values
If \$T_1\$ is Integer and \$T_2\$ is a primitive integer type, then
proceed as if \$T_1\$ were a signed primitive integer
type of the narrowest bit width that will hold \$v_1\$.
For example, converting -0x1234
to I8
yields -0x34: I8
.
If \$T_1\$ is a primitive integer type and \$T_2\$ is
Integer, then \$v_2\$ is the integer value of \$v_1\$
at type Integer. For example, converting
0xFFFF: U32
to Integer yields 0xFFFF:
Integer.
19.2.5. Floating-Point Values
We use the standard rules for IEEE floating-point values to convert among integer values to and from floating-point values and floating-point values to and from each other.
19.2.6. Array Values
If \$T_2\$ is an array type and \$T_1 = T_2\$, then let \$v_2 = v_1\$.
Otherwise if \$T_1\$ is an anonymous array type and \$T_2\$ is an anonymous array type or array type, both with \$n\$ elements, then
-
Let \$T'_2\$ be the element type of \$T_2\$.
-
For each \$i in [1,n]\$, \$v'_i\$ be the result of converting \$v_i\$ to type \$T'_2\$.
-
Let \$v_2\$ be the unique array value of type \$T_2\$ with value \$v'_i\$ at each element.
Otherwise the conversion is not valid.
19.2.7. Structure Values
If \$T_2\$ is a struct type and \$T_1 = T_2\$, then let \$v_2 = v_1\$.
Otherwise if \$T_1\$ is an anonymous struct type and \$T_2\$ is
an anonymous struct type or struct type
such that for each member \$m\$ :
\$v_m\$ of \$T_1\$ there is a member
\$m\$ :
\$T_m\$ in \$T_2\$, then use the value of \$T_2\$ with
the following members:
-
For each member \$m\$
:
\$T_m\$ of \$T_2\$ such that there is a member \$m\$:
\$v_m\$ in \$v_1\$, add the member \$m\$:
\$v'_m\$, where \$v'_m\$ is the result of converting \$v_m\$ to \$T_m\$. -
For each member \$m\$
:
\$T_m\$ of \$T_2\$ such that there is no member \$m\$:
\$v_m\$ in \$v_1\$, add the member \$m\$:
\$v'_m\$, where \$v'_m\$ is the default value at type \$T_m\$.
Otherwise the conversion is invalid.
20. Analysis and Translation
20.1. Analysis
Analysis is the process of checking a source model. It usually involves the following steps:
-
Lexing and parsing to generate an abstract syntax tree (AST).
-
If step (1) succeeded, semantic analysis of the AST.
20.2. Analysis Tools
An analysis tool is a tool that reads in and analyzes FPP source files, but does not generate any code. Source files for analysis are provided in one of two ways:
-
Via command-line arguments; or
-
On standard input, if there are no arguments.
For example, the command analyze file1.fpp file2.fpp
says to read in the translation units file1.fpp
and file2.fpp
and perform
analysis on the model consisting of these two translation units.
The command cat file1.fpp file2.fpp | analyze
is functionally equivalent.
20.3. Translation
Translation is the process of performing analysis and generating code.
Translation usually involves the following steps:
-
If step (1) succeeded, code generation.
FPP is intended to support a variety of translation strategies. For example, we need to generate (1) C++ code for FSW and (2) XML, Python, or other code to export to ground tools.
20.4. Translation Tools
A translation tool is a tool that translates FPP source files. A translation tool typically accepts the following two kinds of input:
-
Source files to be translated.
-
Source files that are imported for their symbol definitions, but are not translated.
For example, when translating a component C, a tool may read in files containing the definitions of the ports used in C, but not translate those files.
Source files for translation are provided as for
analysis tools.
Imported source files are specified as arguments to a -i
flag.
For example, the command translate -i file1.fpp,file2.fpp file3.fpp
says to import file1.fpp
and file2.fpp
and translate file3.fpp
.
The command translate -i file1.fpp,file2.fpp < file3.fpp
is functionally
equivalent.