fprime-gds 4.0.2a3__py3-none-any.whl → 4.0.2a5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fprime_gds/common/communication/ccsds/apid.py +12 -10
- fprime_gds/common/communication/ccsds/space_packet.py +7 -2
- fprime_gds/common/communication/framing.py +2 -2
- fprime_gds/common/distributor/distributor.py +9 -6
- fprime_gds/common/encoders/encoder.py +1 -0
- fprime_gds/common/fpy/README.md +190 -42
- fprime_gds/common/fpy/SPEC.md +153 -39
- fprime_gds/common/fpy/bytecode/assembler.py +62 -0
- fprime_gds/common/fpy/bytecode/directives.py +620 -294
- fprime_gds/common/fpy/codegen.py +687 -910
- fprime_gds/common/fpy/grammar.lark +44 -9
- fprime_gds/common/fpy/main.py +27 -4
- fprime_gds/common/fpy/model.py +799 -0
- fprime_gds/common/fpy/parser.py +29 -34
- fprime_gds/common/fpy/test_helpers.py +119 -0
- fprime_gds/common/fpy/types.py +563 -0
- fprime_gds/common/pipeline/standard.py +1 -1
- fprime_gds/common/testing_fw/pytest_integration.py +2 -1
- fprime_gds/common/utils/data_desc_type.py +1 -0
- fprime_gds/executables/cli.py +11 -2
- fprime_gds/executables/comm.py +0 -2
- fprime_gds/executables/run_deployment.py +2 -0
- fprime_gds/flask/app.py +17 -2
- fprime_gds/flask/default_settings.py +1 -0
- fprime_gds/flask/static/index.html +7 -4
- fprime_gds/flask/static/js/config.js +9 -1
- fprime_gds/flask/static/js/vue-support/channel.js +16 -0
- fprime_gds/flask/static/js/vue-support/event.js +1 -0
- fprime_gds/flask/static/js/vue-support/fp-row.js +25 -1
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/METADATA +1 -1
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/RECORD +36 -32
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/entry_points.txt +2 -1
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/WHEEL +0 -0
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/licenses/LICENSE.txt +0 -0
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/licenses/NOTICE.txt +0 -0
- {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,21 @@
|
|
1
|
-
"""
|
1
|
+
"""ccsds.apid: APID mapping functions for F´ data"""
|
2
|
+
|
2
3
|
from fprime_gds.common.utils.data_desc_type import DataDescType
|
3
|
-
from fprime.common.models.serialize.numerical_types import
|
4
|
+
from fprime.common.models.serialize.numerical_types import NumericalType
|
5
|
+
|
4
6
|
|
5
7
|
class APID(object):
|
6
|
-
"""
|
7
|
-
|
8
|
+
"""APID implementations"""
|
9
|
+
|
10
|
+
# TODO: use the DataDescType configured by loading the dictionary
|
8
11
|
|
9
12
|
@classmethod
|
10
13
|
def from_type(cls, data_type: DataDescType):
|
11
|
-
"""
|
14
|
+
"""Map from data description type to APID"""
|
12
15
|
return data_type.value
|
13
16
|
|
14
17
|
@classmethod
|
15
|
-
def from_data(cls, data):
|
16
|
-
"""
|
17
|
-
|
18
|
-
|
19
|
-
return cls.from_type(DataDescType(u32_type.val))
|
18
|
+
def from_data(cls, data, packet_descriptor_type: NumericalType):
|
19
|
+
"""Map from data bytes to APID"""
|
20
|
+
packet_descriptor_type.deserialize(data, offset=0)
|
21
|
+
return cls.from_type(DataDescType(packet_descriptor_type.val))
|
@@ -10,6 +10,7 @@ from spacepackets.ccsds.spacepacket import SpacePacketHeader, PacketType, SpaceP
|
|
10
10
|
from fprime_gds.common.communication.framing import FramerDeframer
|
11
11
|
from fprime_gds.plugin.definitions import gds_plugin_implementation, gds_plugin
|
12
12
|
from fprime_gds.common.utils.data_desc_type import DataDescType
|
13
|
+
from fprime_gds.common.utils.config_manager import ConfigManager
|
13
14
|
|
14
15
|
from .apid import APID
|
15
16
|
import logging
|
@@ -29,17 +30,21 @@ class SpacePacketFramerDeframer(FramerDeframer):
|
|
29
30
|
IDLE_APID = 0x7FF # max 11 bit value per protocol specification
|
30
31
|
|
31
32
|
def __init__(self):
|
32
|
-
# self.sequence_number = 0
|
33
33
|
# Map APID to sequence counts
|
34
34
|
self.apid_to_sequence_count_map = dict()
|
35
35
|
for key in DataDescType:
|
36
36
|
self.apid_to_sequence_count_map[key.value] = 0
|
37
|
+
self.packet_descriptor_type = ConfigManager.get_instance().get_type(
|
38
|
+
"FwPacketDescriptorType"
|
39
|
+
)
|
37
40
|
|
38
41
|
def frame(self, data):
|
39
42
|
"""Frame the supplied data in Space Packet"""
|
40
43
|
# The protocol defines length token to be number of bytes minus 1
|
41
44
|
data_length_token = len(data) - 1
|
42
|
-
|
45
|
+
# Extract the APID from the data
|
46
|
+
# F' has the packet descriptor (= APID currently) as first n bytes of the data
|
47
|
+
apid = APID.from_data(data, self.packet_descriptor_type)
|
43
48
|
space_header = SpacePacketHeader(
|
44
49
|
packet_type=PacketType.TC,
|
45
50
|
apid=apid,
|
@@ -47,7 +47,7 @@ class FramerDeframer(abc.ABC):
|
|
47
47
|
self, data: bytes, no_copy=False
|
48
48
|
) -> tuple[(bytes | None), bytes, bytes]:
|
49
49
|
"""
|
50
|
-
Deframes the incoming data from the specified format.
|
50
|
+
Deframes the incoming data from the specified format.
|
51
51
|
Produces:
|
52
52
|
- One packet, or None if no packet found
|
53
53
|
- leftover bytes (not consumed yet)
|
@@ -80,7 +80,7 @@ class FramerDeframer(abc.ABC):
|
|
80
80
|
# Deframe and return only on None
|
81
81
|
(deframed, data, discarded) = self.deframe(data, no_copy=True)
|
82
82
|
discarded_aggregate += discarded
|
83
|
-
if deframed is None:
|
83
|
+
if deframed is None: # No more packets available, return aggregate
|
84
84
|
return packets, data, discarded_aggregate
|
85
85
|
packets.append(deframed)
|
86
86
|
|
@@ -16,6 +16,7 @@ descriptor header will be passed on to the registered objects.
|
|
16
16
|
|
17
17
|
import logging
|
18
18
|
|
19
|
+
from fprime.common.models.serialize.type_exceptions import DeserializeException
|
19
20
|
from fprime_gds.common.decoders.decoder import DecodingException
|
20
21
|
from fprime_gds.common.handlers import DataHandler
|
21
22
|
from fprime_gds.common.utils import config_manager, data_desc_type
|
@@ -147,7 +148,6 @@ class Distributor(DataHandler):
|
|
147
148
|
# | ... | |
|
148
149
|
# | .. | |
|
149
150
|
# . :
|
150
|
-
|
151
151
|
offset = 0
|
152
152
|
|
153
153
|
# Parse length
|
@@ -199,13 +199,16 @@ class Distributor(DataHandler):
|
|
199
199
|
), "Leftover data is not equivalent to the remaining data in buffer"
|
200
200
|
|
201
201
|
for raw_msg in raw_msgs:
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
202
|
+
try:
|
203
|
+
(length, data_desc, msg) = self.parse_raw_msg_api(raw_msg)
|
204
|
+
data_desc_key = data_desc_type.DataDescType(data_desc).name
|
205
|
+
except DeserializeException as deserialize_exception:
|
206
|
+
LOGGER.warning(f"Invalid message: {deserialize_exception}")
|
207
|
+
return
|
208
|
+
decoders = self.__decoders.get(data_desc_key, None)
|
207
209
|
if not decoders:
|
208
210
|
LOGGER.warning(f"No decoder registered for: {data_desc_key}")
|
211
|
+
return
|
209
212
|
|
210
213
|
for d in decoders:
|
211
214
|
try:
|
fprime_gds/common/fpy/README.md
CHANGED
@@ -1,56 +1,204 @@
|
|
1
|
-
# Fpy
|
2
|
-
The Fpy advanced sequencing language is a combination of a high-level scripting language and a low-level bytecode language for running complex command sequences on spacecraft flight software.
|
3
|
-
## Fpy Syntax
|
4
|
-
### Modules, components, channels, commands and types
|
5
|
-
You can imagine the Fpy syntax as Python with the following mappings:
|
6
|
-
1. FPrime modules become Python namespaces
|
7
|
-
2. FPrime components become Python classes
|
8
|
-
3. FPrime types (structs, arrays and enums) become Python classes
|
9
|
-
4. FPrime component instances become Python object instances
|
10
|
-
5. FPrime commands become member functions of Python object instances
|
11
|
-
6. FPrime telemetry channels become member properties of Python object instances
|
1
|
+
# Fpy Guide
|
12
2
|
|
13
|
-
FPrime
|
3
|
+
Fpy is an easy to learn, powerful spacecraft scripting language backed by decades of JPL heritage. It is designed to work with the FPrime flight software framework. The syntax is inspired by Python, and it compiles to an efficient binary format.
|
4
|
+
|
5
|
+
This guide is a quick overview of the most important features of Fpy. It should be easy to follow for someone who has used Python and FPrime before.
|
6
|
+
|
7
|
+
## 1. Compiling and Running a Sequence
|
8
|
+
|
9
|
+
First, make sure `fprime-gds` is installed.
|
10
|
+
|
11
|
+
Fpy sequences are suffixed with `.fpy`. Let's make a test sequence that dispatches a no-op:
|
12
|
+
```py
|
13
|
+
# hash denotes a comment
|
14
|
+
# assume this file is named "test.fpy"
|
15
|
+
|
16
|
+
# use the full name of the no-op command:
|
17
|
+
CdhCore.cmdDisp.CMD_NO_OP() # empty parentheses indicate no arguments
|
14
18
|
```
|
15
|
-
module Ref {
|
16
|
-
passive component ExampleComponent {
|
17
|
-
telemetry testChannel: U8
|
18
|
-
sync command TEST_COMMAND(arg: string size 40)
|
19
|
-
}
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
You can compile it with `fprime-fpyc test.fpy --dictionary Ref/build-artifacts/Linux/dict/RefTopologyDictionary.json`
|
21
|
+
|
22
|
+
Make sure your deployment topology has an instance of the `Svc.FpySequencer` component. You can run the sequence by passing it in as an argument to the `Svc.FpySequencer.RUN` command.
|
23
|
+
|
24
|
+
## 2. Variables and Basic Types
|
25
|
+
|
26
|
+
Fpy supports statically-typed, mutable local variables. You can change their value, but the type of the variable can't change.
|
27
|
+
|
28
|
+
This is how you declare a variable, and change its value:
|
29
|
+
```py
|
30
|
+
unsigned_var: U8 = 0
|
31
|
+
# this is a variable named unsigned_var with a type of unsigned 8-bit integer and a value of 0
|
32
|
+
|
33
|
+
unsigned_var = 123
|
34
|
+
# now it has a value of 123
|
35
|
+
```
|
36
|
+
|
37
|
+
For types, Fpy has most of the same basic ones that FPP does:
|
38
|
+
* Signed integers: `I8, I16, I32, I64`
|
39
|
+
* Unsigned integers: `U8, U16, U32, U64`
|
40
|
+
* Floats: `F32, F64`
|
41
|
+
* Boolean: `bool`
|
42
|
+
|
43
|
+
Float literals are denoted with a decimal point (`5.0`, `0.123`) and Boolean literals have a capitalized first letter: `True`, `False`. There is no way to differentiate between signed and unsigned integer literals, so the compiler looks at where the literal is used to determine the signedness.
|
44
|
+
|
45
|
+
Note there is currently no built-in `string` type. See [Strings](#13-strings).
|
46
|
+
|
47
|
+
## 3. Dictionary Types
|
48
|
+
|
49
|
+
Fpy also has access to all structs, arrays and enums in the FPrime dictionary:
|
50
|
+
```py
|
51
|
+
# you can access enum constants by name:
|
52
|
+
enum_var: Fw.Success = Fw.Success.SUCCESS
|
53
|
+
|
54
|
+
# you can construct arrays:
|
55
|
+
array_var: Ref.DpDemo.U32Array = Ref.DpDemo.U32Array(0, 1, 2, 3, 4)
|
56
|
+
|
57
|
+
# you can construct structs:
|
58
|
+
struct_var: Ref.SignalPair = Ref.SignalPair(0.0, 1.0)
|
23
59
|
```
|
24
|
-
|
60
|
+
|
61
|
+
In general, the syntax for instantiating a struct or array type is `Full.Type.Name(arg, ..., arg)`.
|
62
|
+
|
63
|
+
## 4. Math
|
64
|
+
You can do basic math and store the result in variables in Fpy:
|
25
65
|
```py
|
26
|
-
|
27
|
-
Ref.exampleInstance.testChannel
|
28
|
-
# call a command
|
29
|
-
Ref.exampleInstance.TEST_COMMAND("arg value")
|
66
|
+
pemdas: F32 = 1 - 2 + 3 * 4 + 10 / 5 * 2 # == 15.0
|
30
67
|
```
|
31
68
|
|
69
|
+
Fpy supports the following math operations:
|
70
|
+
* Basic arithmetic: `+, -, *, /`
|
71
|
+
* Modulo: `%`
|
72
|
+
* Exponentiation: `**`
|
73
|
+
* Floor division: `//`
|
74
|
+
* Natural logarithm: `log(x)`
|
75
|
+
|
76
|
+
The behavior of these operators is designed to mimic Python. Note that **division always returns a float**. This means that `5 / 2 == 2.5`, not `2`. This may be confusing coming from C++, but it is consistent with Python.
|
32
77
|
|
33
|
-
|
78
|
+
## 5. Variable Arguments to Commands
|
79
|
+
|
80
|
+
Where this really gets interesting is when you pass variables or expressions into commands:
|
81
|
+
```py
|
82
|
+
# this is a command that takes an F32
|
83
|
+
Ref.recvBuffComp.PARAMETER4_PRM_SET(1 - 2 + 3 * 4 + 10 / 5 * 2)
|
84
|
+
# alternatively:
|
85
|
+
param4: F32 = 15.0
|
86
|
+
Ref.recvBuffComp.PARAMETER4_PRM_SET(param4)
|
34
87
|
```
|
35
|
-
struct ExampleStruct {
|
36
|
-
member: F32
|
37
|
-
}
|
38
88
|
|
39
|
-
|
40
|
-
ONE
|
41
|
-
TWO
|
42
|
-
THREE
|
43
|
-
}
|
89
|
+
The same syntax works with the [`sleep`](#11-relative-and-absolute-sleep), [`exit`](#12-exit-macro), and `log` macros.
|
44
90
|
|
45
|
-
|
91
|
+
There are some restrictions on passing string values, or complex types containing string values, to commands. See [Strings](#13-strings).
|
92
|
+
|
93
|
+
## 6. Getting Telemetry Channels
|
94
|
+
|
95
|
+
Fpy supports getting the value of telemetry channels:
|
96
|
+
```py
|
97
|
+
cmds_dispatched: U32 = CdhCore.cmdDisp.CommandsDispatched
|
98
|
+
|
99
|
+
signal_pair: Ref.SignalPair = Ref.SG1.PairOutput
|
46
100
|
```
|
47
101
|
|
48
|
-
|
102
|
+
It's important to note that if your component hasn't written telemetry to the telemetry database (`TlmPacketizer` or `TlmChan`) in a while, the value the sequence sees may be old. Make sure to regularly write your telemetry!
|
103
|
+
|
104
|
+
## 7. Getting Parameters
|
105
|
+
|
106
|
+
Fpy supports getting the value of parameters:
|
49
107
|
```py
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
108
|
+
prm_3: U8 = Ref.sendBuffComp.parameter3
|
109
|
+
```
|
110
|
+
|
111
|
+
A significant limitation of this is that it will only return the value most recently saved to the parameter database. This means you must command `_PRM_SAVE` before the sequence will see the new value.
|
112
|
+
|
113
|
+
## 8. Conditionals
|
114
|
+
Fpy supports comparison operators:
|
115
|
+
```py
|
116
|
+
value: bool = 1 > 2 and (3 + 4) != 5
|
117
|
+
```
|
118
|
+
* Inequalities: `>, <, >=, <=`
|
119
|
+
* Equalities: `==, !=`
|
120
|
+
* Boolean functions: `and, or, not`
|
121
|
+
|
122
|
+
|
123
|
+
The inequality operators can compare two numbers of any type together. The equality operators, in addition to comparing numbers, can check for equality between two of the same complex type:
|
124
|
+
```py
|
125
|
+
record1: Svc.DpRecord = Svc.DpRecord(0, 1, 2, 3, 4, 5, Fw.DpState.UNTRANSMITTED)
|
126
|
+
record2: Svc.DpRecord = Svc.DpRecord(0, 1, 2, 3, 4, 5, Fw.DpState.UNTRANSMITTED)
|
127
|
+
records_equal: bool = record1 == record2 # == True
|
128
|
+
```
|
129
|
+
## 9. If/elif/else
|
130
|
+
|
131
|
+
You can branch off of conditionals with `if`, `elif` and `else`:
|
132
|
+
```py
|
133
|
+
random_value: I8 = 4 # chosen by fair dice roll. guaranteed to be random
|
134
|
+
|
135
|
+
if random_value < 0:
|
136
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("won't happen")
|
137
|
+
elif random_value > 0 and random_value <= 6:
|
138
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("should happen!")
|
139
|
+
else:
|
140
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("uh oh...")
|
141
|
+
```
|
142
|
+
|
143
|
+
This is particularly useful for checking telemetry channel values:
|
144
|
+
```py
|
145
|
+
# dispatch a no-op
|
146
|
+
CdhCore.cmdDisp.CMD_NO_OP()
|
147
|
+
# the commands dispatched count should be >= 1
|
148
|
+
if CdhCore.cmdDisp.CommandsDispatched >= 1:
|
149
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("should happen")
|
150
|
+
```
|
151
|
+
|
152
|
+
## 10. Getting Struct Members and Array Items
|
153
|
+
|
154
|
+
You can access members of structs by name, or array elements by index:
|
155
|
+
```py
|
156
|
+
# access struct members with "." syntax
|
157
|
+
signal_pair_time: F32 = Ref.SG1.PairOutput.time
|
158
|
+
|
159
|
+
# access array elements with "[]" syntax
|
160
|
+
com_queue_depth_0: U32 = ComCcsds.comQueue.comQueueDepth[0]
|
161
|
+
```
|
162
|
+
|
163
|
+
You cannot reassign struct members or array elements however:
|
164
|
+
```py
|
165
|
+
# Ref.SignalPair is a struct type
|
166
|
+
signal_pair: Ref.SignalPair = Ref.SG1.PairOutput
|
167
|
+
# compiler error:
|
168
|
+
signal_pair.time = 0.2
|
169
|
+
|
170
|
+
# Svc.ComQueueDepth is an array type
|
171
|
+
com_queue_depth: Svc.ComQueueDepth = ComCcsds.comQueue.comQueueDepth
|
172
|
+
# compiler error:
|
173
|
+
com_queue_depth[0] = 1
|
174
|
+
```
|
175
|
+
|
176
|
+
## 11. Relative and Absolute Sleep
|
177
|
+
You can pause the execution of a sequence for a relative duration, or until an absolute time:
|
178
|
+
```py
|
179
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("second 0")
|
180
|
+
# sleep for 1 second and 0 microseconds
|
181
|
+
sleep(1, 0)
|
182
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("second 1")
|
183
|
+
|
184
|
+
|
185
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("today")
|
186
|
+
# sleep until 12345678900 seconds and 0 microseconds after the epoch
|
187
|
+
sleep_until(0, 0, 12345678900, 0)
|
188
|
+
CdhCore.cmdDisp.CMD_NO_OP_STRING("much later")
|
189
|
+
```
|
190
|
+
|
191
|
+
Make sure that the `Svc.FpySequencer.checkTimers` port is connected to a rate group. The sequencer only checks if a sleep is done when the port is called, so the more frequently you call it, the more accurate the wakeup time.
|
192
|
+
|
193
|
+
## 12. Exit Macro
|
194
|
+
You can end the execution of the sequence early by calling the `exit` macro:
|
195
|
+
```py
|
196
|
+
# exit takes a boolean argument
|
197
|
+
# True means "end the sequence without an error"
|
198
|
+
exit(True)
|
199
|
+
# False means "end the sequence and raise an error"
|
200
|
+
exit(False)
|
201
|
+
```
|
202
|
+
|
203
|
+
## 13. Strings
|
204
|
+
Fpy does not support a fully-fledged `string` type yet. You can pass a string literal as an argument to a command, but you cannot pass a string from a telemetry channel. You also cannot store a string in a variable, or perform any string manipulation. These features will be added in a later Fpy update.
|
fprime_gds/common/fpy/SPEC.md
CHANGED
@@ -1,69 +1,183 @@
|
|
1
1
|
Nothing type is a type whose set of values is an empty set
|
2
2
|
Unit type is a type whose set of values is a set with one element
|
3
|
+
BIG question: what if we made an arbitrary precision int type? and float type?
|
4
|
+
|
5
|
+
# Types
|
6
|
+
|
7
|
+
The following types are built into Fpy, and the developer can directly refer to them by name:
|
8
|
+
* Numeric types: `U8, U16, U32, U64, I8, I16, I32, I64, F32, F64`
|
9
|
+
* Boolean type: `bool`
|
10
|
+
* Time type: `Fw.Time`
|
11
|
+
|
12
|
+
In addition, the developer can directly refer to any displayable type defined in FPP via its fully-qualified name. This includes user-defined structs, arrays and enums.
|
13
|
+
|
14
|
+
There are some types which exist in Fpy but cannot be directly referenced by name by the developer. These are the internal *Int*, and *String* types. See [literals](#literals).
|
15
|
+
|
16
|
+
## Structs
|
17
|
+
You can instantiate a new struct at runtime by calling its constructor. A struct's constructor is a function with the same name as the type, with arguments corresponding to the type and position of the struct's members. For example, a struct defined as:
|
18
|
+
```
|
19
|
+
module Fw {
|
20
|
+
struct Example {
|
21
|
+
intValue: U8
|
22
|
+
boolValue: bool
|
23
|
+
}
|
24
|
+
}
|
25
|
+
```
|
26
|
+
can be constructed in Fpy like:
|
27
|
+
```
|
28
|
+
Fw.Example(0, True)
|
29
|
+
```
|
30
|
+
|
31
|
+
|
32
|
+
# Literals
|
33
|
+
The following literals are supported by Fpy:
|
34
|
+
* Integer literals: `123`, `-456_879`
|
35
|
+
* Float literals: `0.123`, `1e-5`
|
36
|
+
* String literals: `"hello world"`, `'example string'`
|
37
|
+
* Boolean literals: `True` and `False`
|
38
|
+
|
39
|
+
## Integer literals
|
40
|
+
Integer literals are strings matching:
|
41
|
+
```
|
42
|
+
DEC_NUMBER: "1".."9" ("_"? "0".."9" )*
|
43
|
+
| "0" ("_"? "0" )* /(?![1-9])/
|
44
|
+
```
|
45
|
+
|
46
|
+
The first rule of this syntax allows for integers without leading zeroes, separated by underscores. So this is okay:
|
47
|
+
```
|
48
|
+
123_456
|
49
|
+
```
|
50
|
+
but this is not:
|
51
|
+
```
|
52
|
+
0123_456
|
53
|
+
```
|
54
|
+
|
55
|
+
The second rule allows you to write any number of zeroes, separated by underscores:
|
56
|
+
```
|
57
|
+
00_000_0
|
58
|
+
```
|
59
|
+
|
60
|
+
Integer literals have a internal type *Int*, which is not directly referenceable by the user. The *Int* type supports integers of arbitrary size.
|
61
|
+
|
62
|
+
## Float literals
|
63
|
+
Float literals are strings matching:
|
64
|
+
```
|
65
|
+
FLOAT_NUMBER: _SPECIAL_DEC _EXP | DECIMAL _EXP?
|
66
|
+
```
|
67
|
+
where `_SPECIAL_DEC`, `_EXP` and `DECIMAL` are defined as:
|
68
|
+
|
69
|
+
```
|
70
|
+
_SPECIAL_DEC: "0".."9" ("_"? "0".."9")*
|
71
|
+
_EXP: ("e"|"E") ["+" | "-"] _SPECIAL_DEC
|
72
|
+
DECIMAL: "." _SPECIAL_DEC | _SPECIAL_DEC "." _SPECIAL_DEC?
|
73
|
+
```
|
74
|
+
|
75
|
+
A `FLOAT_NUMBER` can be any string of digits suffixed with an exponent, like these:
|
76
|
+
```
|
77
|
+
1e-5
|
78
|
+
100_200e10
|
79
|
+
```
|
3
80
|
|
4
|
-
|
81
|
+
or it can be a `DECIMAL` optionally suffixed by an exponent, like these:
|
82
|
+
```
|
83
|
+
1.
|
84
|
+
2.123
|
85
|
+
100.5e+10
|
86
|
+
```
|
5
87
|
|
6
|
-
|
88
|
+
Float literals are of type `F64`.
|
7
89
|
|
8
|
-
|
90
|
+
## String literals
|
91
|
+
String literals are strings matching:
|
92
|
+
```
|
93
|
+
STRING: /("(?!"").*?(?<!\\)(\\\\)*?"|'(?!'').*?(?<!\\)(\\\\)*?')/i
|
94
|
+
```
|
9
95
|
|
10
|
-
|
11
|
-
2. where `stmt` is a statement
|
96
|
+
They have a internal type *String*, which is not directly referenceable by the user. The *String* type supports strings of arbitrary length.
|
12
97
|
|
13
|
-
|
98
|
+
# Functions
|
99
|
+
Functions have arguments and a return type. You can call a function like:
|
100
|
+
```
|
101
|
+
function_name(arg_1, arg_2, arg_3)
|
102
|
+
```
|
14
103
|
|
15
|
-
|
16
|
-
2. `elif` generates `IF`, followed by the generated code for its body, followed by `GOTO` to the end of the if statement
|
17
|
-
3. `else` generates the code for its body
|
104
|
+
## Commands
|
18
105
|
|
19
|
-
# `not` boolean operator
|
20
106
|
|
21
|
-
|
107
|
+
# Type conversion
|
108
|
+
|
109
|
+
Type conversion is the process of converting an expression from one type to another. It can either be implicit, in which case it is called coercion, or explicit, in which case it is called casting.
|
22
110
|
|
23
|
-
|
111
|
+
## Coercion
|
112
|
+
Coercion happens when an expression of type *A* is used in a syntactic element which requires an expression of type *B*. For example, functions, operators and variable assignments all require specific input types, so type coercion happens in each of these.
|
24
113
|
|
25
|
-
|
114
|
+
When type coercion happens, the following type conversion rules are applied:
|
115
|
+
|
116
|
+
1. Expressions of any integer type can be converted to any signed or unsigned integer or float type.
|
117
|
+
2. Expressions of any float type can be converted to any float type.
|
118
|
+
3. Expressions of internal type *String* can be converted to any string type.
|
119
|
+
|
120
|
+
TODO try out with forcing type casting--see what the FF's think
|
121
|
+
TODO require explicit narrowing casts? or have a compiler warning?
|
122
|
+
TODO consider float to int conversion?
|
123
|
+
TODO consider adding constants
|
26
124
|
|
27
|
-
|
125
|
+
If no rule matches, then the compiler raises an error.
|
28
126
|
|
29
|
-
|
127
|
+
There is currently no support for converting non-internal string expressions to other string expressions.
|
30
128
|
|
31
|
-
#
|
129
|
+
# Operators
|
32
130
|
|
33
|
-
|
131
|
+
Fpy supports the following operators:
|
132
|
+
* Basic arithmetic: `+, -, *, /`
|
133
|
+
* Modulo: `%`
|
134
|
+
* Exponentiation: `**`
|
135
|
+
* Floor division: `//`
|
136
|
+
* Boolean: `and, or, not`
|
137
|
+
* Comparison: `<, >, <=, >=, ==, !=`
|
34
138
|
|
35
|
-
|
139
|
+
Each time an operator is used, an intermediate type must be picked and both args must be converted to that type.
|
36
140
|
|
37
|
-
|
38
|
-
2. where `value` is an expression which evaluates to a boolean
|
141
|
+
## Behavior of operators
|
39
142
|
|
40
|
-
|
143
|
+
### Addition (`+`)
|
144
|
+
### Subtraction (`-`)
|
145
|
+
### Multiplication (`*`)
|
146
|
+
### Division (`/`)
|
147
|
+
### Modulo (`%`)
|
148
|
+
### Exponentiation (`**`)
|
149
|
+
### Floor division (`//`)
|
150
|
+
### And (`and`)
|
151
|
+
### Or (`or`)
|
152
|
+
### Not (`not`)
|
41
153
|
|
42
|
-
1. Each `and` or `or` between two `value`s generates an `AND` or `OR`, respectively
|
43
154
|
|
44
|
-
|
155
|
+
## Intermediate types
|
45
156
|
|
46
|
-
|
157
|
+
Intermediate types are picked via the following rules:
|
47
158
|
|
48
|
-
|
159
|
+
1. The intermediate type of Boolean operators is always `bool`.
|
160
|
+
2. The intermediate type of `==` and `!=` may be any type, so long as the left and right hand sides are the same type. If both are numeric then continue.
|
161
|
+
3. If either argument is non-numeric, raise an error.
|
162
|
+
4. If the operator is `/` or `**`, the intermediate type is always `F64`.
|
163
|
+
5. If either argument is a float, the intermediate type is `F64`.
|
164
|
+
6. If either argument is an unsigned integer, the intermediate type is `U64`.
|
165
|
+
7. Otherwise, the intermediate type is `I64`.
|
49
166
|
|
50
|
-
|
51
|
-
2. and `lhs`, `rhs` are expressions which evaluate to a number
|
167
|
+
If the expressions given to the operator are not of the intermediate type, type coercion rules are applied.
|
52
168
|
|
53
|
-
|
54
|
-
3. both `lhs` and `rhs` must evaluate to floats of the same bit width
|
169
|
+
## Result type
|
55
170
|
|
56
|
-
|
171
|
+
The result type is the type of the value produced by the operator.
|
172
|
+
1. For numeric operators, the result type is the intermediate type.
|
173
|
+
2. For boolean and comparison operators, the result type is `bool`.
|
57
174
|
|
58
|
-
|
175
|
+
Normal type coercion rules apply to the result, of course. Once the operator has produced a value, it may be coerced into some other type depending on context.
|
59
176
|
|
60
|
-
### Equality comparisons
|
61
177
|
|
62
|
-
|
63
|
-
2. `==` or `!=` between two ints generates `IEQ` or `INE`, respectively
|
178
|
+
# Macros
|
64
179
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
2. `>`, `<`, `<=`, or `>=` between two ints, where at least one is signed, generates `SGT`, `SLT`, `SLE` or `SGE`, respectively
|
180
|
+
## exit
|
181
|
+
## log
|
182
|
+
## sleep
|
183
|
+
## sleep_until
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from dataclasses import astuple, dataclass
|
2
|
+
import struct
|
3
|
+
import zlib
|
4
|
+
from fprime_gds.common.fpy.bytecode.directives import Directive
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
HEADER_FORMAT = "!BBBBBHI"
|
8
|
+
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class Header:
|
13
|
+
majorVersion: int
|
14
|
+
minorVersion: int
|
15
|
+
patchVersion: int
|
16
|
+
schemaVersion: int
|
17
|
+
argumentCount: int
|
18
|
+
statementCount: int
|
19
|
+
bodySize: int
|
20
|
+
|
21
|
+
|
22
|
+
FOOTER_FORMAT = "!I"
|
23
|
+
FOOTER_SIZE = struct.calcsize(FOOTER_FORMAT)
|
24
|
+
|
25
|
+
SCHEMA_VERSION = 2
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class Footer:
|
30
|
+
crc: int
|
31
|
+
|
32
|
+
|
33
|
+
def serialize_directives(dirs: list[Directive], output: Path):
|
34
|
+
output_bytes = bytes()
|
35
|
+
|
36
|
+
for dir in dirs:
|
37
|
+
output_bytes += dir.serialize()
|
38
|
+
|
39
|
+
header = Header(0, 0, 0, SCHEMA_VERSION, 0, len(dirs), len(output_bytes))
|
40
|
+
output_bytes = struct.pack(HEADER_FORMAT, *astuple(header)) + output_bytes
|
41
|
+
|
42
|
+
crc = zlib.crc32(output_bytes) % (1 << 32)
|
43
|
+
footer = Footer(crc)
|
44
|
+
output_bytes += struct.pack(FOOTER_FORMAT, *astuple(footer))
|
45
|
+
output.write_bytes(output_bytes)
|
46
|
+
|
47
|
+
|
48
|
+
def deserialize_directives(bytes: bytes) -> list[Directive]:
|
49
|
+
header = Header(*struct.unpack_from(HEADER_FORMAT, bytes))
|
50
|
+
|
51
|
+
dirs = []
|
52
|
+
idx = 0
|
53
|
+
offset = HEADER_SIZE
|
54
|
+
while idx < header.statementCount:
|
55
|
+
offset_and_dir = Directive.deserialize(bytes, offset)
|
56
|
+
if offset_and_dir is None:
|
57
|
+
raise RuntimeError("Unable to deserialize sequence")
|
58
|
+
offset, dir = offset_and_dir
|
59
|
+
dirs.append(dir)
|
60
|
+
idx += 1
|
61
|
+
|
62
|
+
return dirs
|