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.
Files changed (36) hide show
  1. fprime_gds/common/communication/ccsds/apid.py +12 -10
  2. fprime_gds/common/communication/ccsds/space_packet.py +7 -2
  3. fprime_gds/common/communication/framing.py +2 -2
  4. fprime_gds/common/distributor/distributor.py +9 -6
  5. fprime_gds/common/encoders/encoder.py +1 -0
  6. fprime_gds/common/fpy/README.md +190 -42
  7. fprime_gds/common/fpy/SPEC.md +153 -39
  8. fprime_gds/common/fpy/bytecode/assembler.py +62 -0
  9. fprime_gds/common/fpy/bytecode/directives.py +620 -294
  10. fprime_gds/common/fpy/codegen.py +687 -910
  11. fprime_gds/common/fpy/grammar.lark +44 -9
  12. fprime_gds/common/fpy/main.py +27 -4
  13. fprime_gds/common/fpy/model.py +799 -0
  14. fprime_gds/common/fpy/parser.py +29 -34
  15. fprime_gds/common/fpy/test_helpers.py +119 -0
  16. fprime_gds/common/fpy/types.py +563 -0
  17. fprime_gds/common/pipeline/standard.py +1 -1
  18. fprime_gds/common/testing_fw/pytest_integration.py +2 -1
  19. fprime_gds/common/utils/data_desc_type.py +1 -0
  20. fprime_gds/executables/cli.py +11 -2
  21. fprime_gds/executables/comm.py +0 -2
  22. fprime_gds/executables/run_deployment.py +2 -0
  23. fprime_gds/flask/app.py +17 -2
  24. fprime_gds/flask/default_settings.py +1 -0
  25. fprime_gds/flask/static/index.html +7 -4
  26. fprime_gds/flask/static/js/config.js +9 -1
  27. fprime_gds/flask/static/js/vue-support/channel.js +16 -0
  28. fprime_gds/flask/static/js/vue-support/event.js +1 -0
  29. fprime_gds/flask/static/js/vue-support/fp-row.js +25 -1
  30. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/METADATA +1 -1
  31. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/RECORD +36 -32
  32. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/entry_points.txt +2 -1
  33. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/WHEEL +0 -0
  34. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/licenses/LICENSE.txt +0 -0
  35. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/licenses/NOTICE.txt +0 -0
  36. {fprime_gds-4.0.2a3.dist-info → fprime_gds-4.0.2a5.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,21 @@
1
- """ ccsds.apid: APID mapping functions for F´ data """
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 U32Type
4
+ from fprime.common.models.serialize.numerical_types import NumericalType
5
+
4
6
 
5
7
  class APID(object):
6
- """ APID implementations """
7
- #TODO: use the DataDescType configured by loading the dictionary
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
- """ Map from data description type to APID """
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
- """ Map from data bytes to APID """
17
- u32_type = U32Type()
18
- u32_type.deserialize(data, offset=0)
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
- apid = APID.from_data(data)
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: # No more packets available, return aggregate
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
- (length, data_desc, msg) = self.parse_raw_msg_api(raw_msg)
203
-
204
- data_desc_key = data_desc_type.DataDescType(data_desc).name
205
- decoders = self.__decoders[data_desc_key]
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:
@@ -20,6 +20,7 @@ purpose is to define the interface for an encoder.
20
20
 
21
21
  @bug No known bugs
22
22
  """
23
+
23
24
  import abc
24
25
  import logging
25
26
 
@@ -1,56 +1,204 @@
1
- # Fpy Advanced Sequencing Language Version 0.1
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 declaration:
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
- instance exampleInstance: ExampleComponent base id 0x01
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
- Fpy usage:
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
- # reference a telemetry channel
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
- FPrime declaration:
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
- enum TestEnum {
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
- array TestArray = [3] U8
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
- Fpy usage:
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
- # construct a struct
51
- ExampleStruct(0.0)
52
- # reference an enum const
53
- TestEnum.THREE
54
- # construct an array
55
- TestArray(1, 2, 3)
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.
@@ -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
- # `if` statement
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
- ## Syntactical and semantic checks
88
+ Float literals are of type `F64`.
7
89
 
8
- `"if" condition ":" INDENT stmt* DEDENT ("elif" condition ":" INDENT stmt* DEDENT)* ["else" ":" INDENT stmt* DEDENT]`
90
+ ## String literals
91
+ String literals are strings matching:
92
+ ```
93
+ STRING: /("(?!"").*?(?<!\\)(\\\\)*?"|'(?!'').*?(?<!\\)(\\\\)*?')/i
94
+ ```
9
95
 
10
- 1. where `condition` is an expression which evaluates to a boolean
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
- ## Code generation
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
- 1. `if` generates `IF`, followed by the generated code for the first body, followed by `GOTO` to the end of the if statement
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
- ## Syntactical and semantic checks
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
- `"not" value` evaluates to a boolean at runtime
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
- 1. where `value` is an expression which evaluates to a boolean
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
- ## Code generation
125
+ If no rule matches, then the compiler raises an error.
28
126
 
29
- 1. `not` generates `NOT`
127
+ There is currently no support for converting non-internal string expressions to other string expressions.
30
128
 
31
- # `and` and `or` boolean operators
129
+ # Operators
32
130
 
33
- ## Syntactical and semantic checks
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
- `value (op value)+` evaluates to a boolean at runtime
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
- 1. where `op: "and"|"or"`
38
- 2. where `value` is an expression which evaluates to a boolean
141
+ ## Behavior of operators
39
142
 
40
- ## Code generation
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
- # Infix comparisons
155
+ ## Intermediate types
45
156
 
46
- ## Syntactical and semantic checks
157
+ Intermediate types are picked via the following rules:
47
158
 
48
- `lhs op rhs` evaluates to a boolean at runtime
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
- 1. where `op: ">" | "<" | "<=" | ">=" | "==" | "!="`
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
- If either `lhs` or `rhs` evaluate to a float:
54
- 3. both `lhs` and `rhs` must evaluate to floats of the same bit width
169
+ ## Result type
55
170
 
56
- Otherwise, `lhs` and `rhs` evaluate to integer values. All comparisons between integer values are valid.
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
- ## Code generation
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
- 1. `==` or `!=` between two floats generates `FEQ` or `FNE`, respectively
63
- 2. `==` or `!=` between two ints generates `IEQ` or `INE`, respectively
178
+ # Macros
64
179
 
65
- ### Inequality comparisons
66
-
67
- 1. `>`, `<`, `<=`, or `>=` between two floats generates `FGT`, `FLT`, `FLE` or `FGE`, respectively
68
- 2. `>`, `<`, `<=`, or `>=` between two unsigned ints generates `UGT`, `ULT`, `ULE` or `UGE`, respectively
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