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
@@ -0,0 +1,563 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from abc import ABC
|
3
|
+
import inspect
|
4
|
+
from dataclasses import dataclass, field, fields
|
5
|
+
import struct
|
6
|
+
import traceback
|
7
|
+
import typing
|
8
|
+
from typing import Union, get_args, get_origin
|
9
|
+
|
10
|
+
# In Python 3.10+, the `|` operator creates a `types.UnionType`.
|
11
|
+
# We need to handle this for forward compatibility, but it won't exist in 3.9.
|
12
|
+
try:
|
13
|
+
from types import UnionType
|
14
|
+
|
15
|
+
UNION_TYPES = (Union, UnionType)
|
16
|
+
except ImportError:
|
17
|
+
UNION_TYPES = (Union,)
|
18
|
+
|
19
|
+
from fprime_gds.common.fpy.bytecode.directives import (
|
20
|
+
StackOpDirective,
|
21
|
+
FloatLogDirective,
|
22
|
+
Directive,
|
23
|
+
ExitDirective,
|
24
|
+
WaitAbsDirective,
|
25
|
+
WaitRelDirective,
|
26
|
+
)
|
27
|
+
from fprime_gds.common.templates.ch_template import ChTemplate
|
28
|
+
from fprime_gds.common.templates.cmd_template import CmdTemplate
|
29
|
+
from fprime_gds.common.templates.prm_template import PrmTemplate
|
30
|
+
from fprime.common.models.serialize.time_type import TimeType
|
31
|
+
from fprime.common.models.serialize.serializable_type import SerializableType
|
32
|
+
from fprime.common.models.serialize.array_type import ArrayType
|
33
|
+
from fprime.common.models.serialize.numerical_types import (
|
34
|
+
U32Type,
|
35
|
+
U16Type,
|
36
|
+
U64Type,
|
37
|
+
U8Type,
|
38
|
+
I16Type,
|
39
|
+
I32Type,
|
40
|
+
I64Type,
|
41
|
+
I8Type,
|
42
|
+
F32Type,
|
43
|
+
F64Type,
|
44
|
+
FloatType,
|
45
|
+
IntegerType,
|
46
|
+
)
|
47
|
+
from fprime.common.models.serialize.string_type import StringType
|
48
|
+
from fprime.common.models.serialize.bool_type import BoolType
|
49
|
+
from fprime_gds.common.fpy.parser import (
|
50
|
+
AstExpr,
|
51
|
+
AstOp,
|
52
|
+
AstReference,
|
53
|
+
Ast,
|
54
|
+
AstAssign,
|
55
|
+
)
|
56
|
+
from fprime.common.models.serialize.type_base import BaseType as FppType
|
57
|
+
|
58
|
+
MAX_DIRECTIVES_COUNT = 1024
|
59
|
+
MAX_DIRECTIVE_SIZE = 2048
|
60
|
+
MAX_STACK_SIZE = 65535
|
61
|
+
|
62
|
+
COMPILER_MAX_STRING_SIZE = 128
|
63
|
+
|
64
|
+
|
65
|
+
# this is the "internal" integer type that integer literals have by
|
66
|
+
# default. it is arbitrary precision
|
67
|
+
class InternalIntType(IntegerType):
|
68
|
+
@classmethod
|
69
|
+
def range(cls):
|
70
|
+
raise NotImplementedError()
|
71
|
+
|
72
|
+
@staticmethod
|
73
|
+
def get_serialize_format():
|
74
|
+
raise NotImplementedError()
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def get_bits(cls):
|
78
|
+
raise NotImplementedError()
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def validate(cls, val):
|
82
|
+
if not isinstance(val, int):
|
83
|
+
raise RuntimeError()
|
84
|
+
|
85
|
+
|
86
|
+
InternalStringType = StringType.construct_type("InternalStringType", None)
|
87
|
+
|
88
|
+
|
89
|
+
SPECIFIC_NUMERIC_TYPES = (
|
90
|
+
U32Type,
|
91
|
+
U16Type,
|
92
|
+
U64Type,
|
93
|
+
U8Type,
|
94
|
+
I16Type,
|
95
|
+
I32Type,
|
96
|
+
I64Type,
|
97
|
+
I8Type,
|
98
|
+
F32Type,
|
99
|
+
F64Type,
|
100
|
+
)
|
101
|
+
SPECIFIC_INTEGER_TYPES = (
|
102
|
+
U32Type,
|
103
|
+
U16Type,
|
104
|
+
U64Type,
|
105
|
+
U8Type,
|
106
|
+
I16Type,
|
107
|
+
I32Type,
|
108
|
+
I64Type,
|
109
|
+
I8Type,
|
110
|
+
)
|
111
|
+
SIGNED_INTEGER_TYPES = (
|
112
|
+
I16Type,
|
113
|
+
I32Type,
|
114
|
+
I64Type,
|
115
|
+
I8Type,
|
116
|
+
)
|
117
|
+
UNSIGNED_INTEGER_TYPES = (
|
118
|
+
U32Type,
|
119
|
+
U16Type,
|
120
|
+
U64Type,
|
121
|
+
U8Type,
|
122
|
+
)
|
123
|
+
SPECIFIC_FLOAT_TYPES = (
|
124
|
+
F32Type,
|
125
|
+
F64Type,
|
126
|
+
)
|
127
|
+
|
128
|
+
|
129
|
+
def is_instance_compat(obj, cls):
|
130
|
+
"""
|
131
|
+
A wrapper for isinstance() that correctly handles Union types in Python 3.9+.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
obj: The object to check.
|
135
|
+
cls: The class, tuple of classes, or Union type to check against.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
True if the object is an instance of the class or any type in the Union.
|
139
|
+
"""
|
140
|
+
origin = get_origin(cls)
|
141
|
+
if origin in UNION_TYPES:
|
142
|
+
# It's a Union type, so get its arguments.
|
143
|
+
# e.g., get_args(Union[int, str]) returns (int, str)
|
144
|
+
return isinstance(obj, get_args(cls))
|
145
|
+
|
146
|
+
# It's not a Union, so it's a regular type (like int) or a
|
147
|
+
# tuple of types ((int, str)), which isinstance handles natively.
|
148
|
+
return isinstance(obj, cls)
|
149
|
+
|
150
|
+
|
151
|
+
# a value of type FppTypeClass is a Python `type` object representing
|
152
|
+
# the type of an Fprime value
|
153
|
+
FppTypeClass = type[FppType]
|
154
|
+
|
155
|
+
|
156
|
+
class NothingType(ABC):
|
157
|
+
"""a type which has no valid values in fprime. used to denote
|
158
|
+
a function which doesn't return a value"""
|
159
|
+
|
160
|
+
@classmethod
|
161
|
+
def __subclasscheck__(cls, subclass):
|
162
|
+
return False
|
163
|
+
|
164
|
+
|
165
|
+
# the `type` object representing the NothingType class
|
166
|
+
NothingTypeClass = type[NothingType]
|
167
|
+
|
168
|
+
|
169
|
+
class CompileException(BaseException):
|
170
|
+
def __init__(self, msg, node: Ast):
|
171
|
+
self.msg = msg
|
172
|
+
self.node = node
|
173
|
+
self.stack_trace = "\n".join(traceback.format_stack(limit=8)[:-1])
|
174
|
+
|
175
|
+
def __str__(self):
|
176
|
+
if self.node is not None:
|
177
|
+
return f'{self.stack_trace}\nAt line {self.node.meta.line} "{self.node.node_text}": {self.msg}'
|
178
|
+
return f"{self.stack_trace}\n{self.msg}"
|
179
|
+
|
180
|
+
|
181
|
+
@dataclass
|
182
|
+
class FpyCallable:
|
183
|
+
return_type: FppTypeClass | NothingTypeClass
|
184
|
+
args: list[tuple[str, FppTypeClass]]
|
185
|
+
|
186
|
+
|
187
|
+
@dataclass
|
188
|
+
class FpyCmd(FpyCallable):
|
189
|
+
cmd: CmdTemplate
|
190
|
+
|
191
|
+
|
192
|
+
@dataclass
|
193
|
+
class FpyMacro(FpyCallable):
|
194
|
+
dir: type[Directive]
|
195
|
+
"""a function which instantiates the macro given the argument exprs"""
|
196
|
+
|
197
|
+
|
198
|
+
MACROS: dict[str, FpyMacro] = {
|
199
|
+
"sleep": FpyMacro(
|
200
|
+
NothingType,
|
201
|
+
[
|
202
|
+
(
|
203
|
+
"seconds",
|
204
|
+
U32Type,
|
205
|
+
),
|
206
|
+
("microseconds", U32Type),
|
207
|
+
],
|
208
|
+
WaitRelDirective,
|
209
|
+
),
|
210
|
+
"sleep_until": FpyMacro(NothingType, [("wakeup_time", TimeType)], WaitAbsDirective),
|
211
|
+
"exit": FpyMacro(NothingType, [("success", BoolType)], ExitDirective),
|
212
|
+
"log": FpyMacro(F64Type, [("operand", F64Type)], FloatLogDirective),
|
213
|
+
}
|
214
|
+
|
215
|
+
|
216
|
+
@dataclass
|
217
|
+
class FpyTypeCtor(FpyCallable):
|
218
|
+
type: FppTypeClass
|
219
|
+
|
220
|
+
|
221
|
+
@dataclass
|
222
|
+
class FieldReference:
|
223
|
+
"""a reference to a field/index of an fprime type"""
|
224
|
+
|
225
|
+
parent: "FpyReference"
|
226
|
+
"""the qualifier"""
|
227
|
+
type: FppTypeClass
|
228
|
+
"""the fprime type of this reference"""
|
229
|
+
offset: int
|
230
|
+
"""the constant offset in the parent type at which to find this field"""
|
231
|
+
name: str = None
|
232
|
+
"""the name of the field, if applicable"""
|
233
|
+
idx: int = None
|
234
|
+
"""the index of the field, if applicable"""
|
235
|
+
|
236
|
+
def get_from(self, parent_val: FppType) -> FppType:
|
237
|
+
"""gets the field value from the parent value"""
|
238
|
+
assert isinstance(parent_val, self.type)
|
239
|
+
assert self.name is not None or self.idx is not None
|
240
|
+
value = None
|
241
|
+
if self.name is not None:
|
242
|
+
if isinstance(parent_val, SerializableType):
|
243
|
+
value = parent_val.val[self.name]
|
244
|
+
elif isinstance(parent_val, TimeType):
|
245
|
+
if self.name == "seconds":
|
246
|
+
value = parent_val.__secs
|
247
|
+
elif self.name == "useconds":
|
248
|
+
value = parent_val.__usecs
|
249
|
+
elif self.name == "time_base":
|
250
|
+
value = parent_val.__timeBase
|
251
|
+
elif self.name == "time_context":
|
252
|
+
value = parent_val.__timeContext
|
253
|
+
else:
|
254
|
+
assert False, self.name
|
255
|
+
else:
|
256
|
+
assert False, parent_val
|
257
|
+
|
258
|
+
else:
|
259
|
+
|
260
|
+
assert isinstance(parent_val, ArrayType), parent_val
|
261
|
+
|
262
|
+
value = parent_val._val[self.idx]
|
263
|
+
|
264
|
+
assert isinstance(value, self.type), (value, self.type)
|
265
|
+
return value
|
266
|
+
|
267
|
+
|
268
|
+
# named variables can be tlm chans, prms, callables, or directly referenced consts (usually enums)
|
269
|
+
@dataclass
|
270
|
+
class FpyVariable:
|
271
|
+
"""a mutable, typed value referenced by an unqualified name"""
|
272
|
+
|
273
|
+
type_ref: AstExpr
|
274
|
+
"""the expression denoting the var's type"""
|
275
|
+
declaration: AstAssign
|
276
|
+
"""the node where this var is declared"""
|
277
|
+
type: FppTypeClass | None = None
|
278
|
+
"""the resolved type of the variable. None if type unsure at the moment"""
|
279
|
+
lvar_offset: int | None = None
|
280
|
+
"""the offset in the lvar array where this var is stored"""
|
281
|
+
|
282
|
+
|
283
|
+
# a scope
|
284
|
+
FpyScope = dict[str, "FpyReference"]
|
285
|
+
|
286
|
+
|
287
|
+
def create_scope(
|
288
|
+
references: dict[str, "FpyReference"],
|
289
|
+
) -> FpyScope:
|
290
|
+
"""from a flat dict of strs to references, creates a hierarchical, scoped
|
291
|
+
dict. no two leaf nodes may have the same name"""
|
292
|
+
|
293
|
+
base = {}
|
294
|
+
|
295
|
+
for fqn, ref in references.items():
|
296
|
+
names_strs = fqn.split(".")
|
297
|
+
|
298
|
+
ns = base
|
299
|
+
while len(names_strs) > 1:
|
300
|
+
existing_child = ns.get(names_strs[0], None)
|
301
|
+
if existing_child is None:
|
302
|
+
# this scope is not defined atm
|
303
|
+
existing_child = {}
|
304
|
+
ns[names_strs[0]] = existing_child
|
305
|
+
|
306
|
+
if not isinstance(existing_child, dict):
|
307
|
+
# something else already has this name
|
308
|
+
print(
|
309
|
+
f"WARNING: {fqn} is already defined as {existing_child}, tried to redefine it as {ref}"
|
310
|
+
)
|
311
|
+
break
|
312
|
+
|
313
|
+
ns = existing_child
|
314
|
+
names_strs = names_strs[1:]
|
315
|
+
|
316
|
+
if len(names_strs) != 1:
|
317
|
+
# broke early. skip this loop
|
318
|
+
continue
|
319
|
+
|
320
|
+
# okay, now ns is the complete scope of the attribute
|
321
|
+
# i.e. everything up until the last '.'
|
322
|
+
name = names_strs[0]
|
323
|
+
|
324
|
+
existing_child = ns.get(name, None)
|
325
|
+
|
326
|
+
if existing_child is not None:
|
327
|
+
# uh oh, something already had this name with a diff value
|
328
|
+
print(
|
329
|
+
f"WARNING: {fqn} is already defined as {existing_child}, tried to redefine it as {ref}"
|
330
|
+
)
|
331
|
+
continue
|
332
|
+
|
333
|
+
ns[name] = ref
|
334
|
+
|
335
|
+
return base
|
336
|
+
|
337
|
+
|
338
|
+
def union_scope(lhs: FpyScope, rhs: FpyScope) -> FpyScope:
|
339
|
+
"""returns the two scopes, joined into one. if there is a conflict, chooses lhs over rhs"""
|
340
|
+
lhs_keys = set(lhs.keys())
|
341
|
+
rhs_keys = set(rhs.keys())
|
342
|
+
common_keys = lhs_keys.intersection(rhs_keys)
|
343
|
+
|
344
|
+
only_lhs_keys = lhs_keys.difference(common_keys)
|
345
|
+
only_rhs_keys = rhs_keys.difference(common_keys)
|
346
|
+
|
347
|
+
new = FpyScope()
|
348
|
+
|
349
|
+
for key in common_keys:
|
350
|
+
if not isinstance(lhs[key], dict) or not isinstance(rhs[key], dict):
|
351
|
+
# cannot be merged cleanly. one of the two is not a scope
|
352
|
+
print(f"WARNING: {key} is defined as {lhs[key]}, ignoring {rhs[key]}")
|
353
|
+
new[key] = lhs[key]
|
354
|
+
continue
|
355
|
+
|
356
|
+
new[key] = union_scope(lhs[key], rhs[key])
|
357
|
+
|
358
|
+
for key in only_lhs_keys:
|
359
|
+
new[key] = lhs[key]
|
360
|
+
for key in only_rhs_keys:
|
361
|
+
new[key] = rhs[key]
|
362
|
+
|
363
|
+
return new
|
364
|
+
|
365
|
+
|
366
|
+
FpyReference = typing.Union[
|
367
|
+
ChTemplate,
|
368
|
+
PrmTemplate,
|
369
|
+
FppType,
|
370
|
+
FpyCallable,
|
371
|
+
FppTypeClass,
|
372
|
+
FpyVariable,
|
373
|
+
FieldReference,
|
374
|
+
dict, # dict of FpyReference
|
375
|
+
]
|
376
|
+
"""some named concept in fpy"""
|
377
|
+
|
378
|
+
|
379
|
+
def get_ref_fpp_type_class(ref: FpyReference) -> FppTypeClass:
|
380
|
+
"""returns the fprime type of the ref, if it were to be evaluated as an expression"""
|
381
|
+
if isinstance(ref, ChTemplate):
|
382
|
+
result_type = ref.ch_type_obj
|
383
|
+
elif isinstance(ref, PrmTemplate):
|
384
|
+
result_type = ref.prm_type_obj
|
385
|
+
elif isinstance(ref, FppType):
|
386
|
+
# constant value
|
387
|
+
result_type = type(ref)
|
388
|
+
elif isinstance(ref, FpyCallable):
|
389
|
+
# a reference to a callable isn't a type in and of itself
|
390
|
+
# it has a return type but you have to call it (with an AstFuncCall)
|
391
|
+
# consider making a separate "reference" type
|
392
|
+
result_type = NothingType
|
393
|
+
elif isinstance(ref, FpyVariable):
|
394
|
+
result_type = ref.type
|
395
|
+
elif isinstance(ref, type):
|
396
|
+
# a reference to a type doesn't have a value, and so doesn't have a type,
|
397
|
+
# in and of itself. if this were a function call to the type's ctor then
|
398
|
+
# it would have a value and thus a type
|
399
|
+
result_type = NothingType
|
400
|
+
elif isinstance(ref, FieldReference):
|
401
|
+
result_type = ref.type
|
402
|
+
elif isinstance(ref, dict):
|
403
|
+
# reference to a scope. scopes don't have values
|
404
|
+
result_type = NothingType
|
405
|
+
else:
|
406
|
+
assert False, ref
|
407
|
+
|
408
|
+
return result_type
|
409
|
+
|
410
|
+
|
411
|
+
@dataclass
|
412
|
+
class CompileState:
|
413
|
+
"""a collection of input, internal and output state variables and maps"""
|
414
|
+
|
415
|
+
types: FpyScope
|
416
|
+
"""a scope whose leaf nodes are subclasses of BaseType"""
|
417
|
+
callables: FpyScope
|
418
|
+
"""a scope whose leaf nodes are FpyCallable instances"""
|
419
|
+
tlms: FpyScope
|
420
|
+
"""a scope whose leaf nodes are ChTemplates"""
|
421
|
+
prms: FpyScope
|
422
|
+
"""a scope whose leaf nodes are PrmTemplates"""
|
423
|
+
consts: FpyScope
|
424
|
+
"""a scope whose leaf nodes are instances of subclasses of BaseType"""
|
425
|
+
variables: FpyScope = field(default_factory=dict)
|
426
|
+
"""a scope whose leaf nodes are FpyVariables"""
|
427
|
+
runtime_values: FpyScope = None
|
428
|
+
"""a scope whose leaf nodes are tlms/prms/consts/variables, all of which
|
429
|
+
have some value at runtime."""
|
430
|
+
|
431
|
+
def __post_init__(self):
|
432
|
+
self.runtime_values = union_scope(
|
433
|
+
self.tlms,
|
434
|
+
union_scope(self.prms, union_scope(self.consts, self.variables)),
|
435
|
+
)
|
436
|
+
|
437
|
+
resolved_references: dict[AstReference, FpyReference] = field(
|
438
|
+
default_factory=dict, repr=False
|
439
|
+
)
|
440
|
+
"""reference to its singular resolution"""
|
441
|
+
|
442
|
+
expr_types: dict[AstExpr, FppTypeClass | NothingTypeClass] = field(
|
443
|
+
default_factory=dict
|
444
|
+
)
|
445
|
+
"""expr to its fprime type, or nothing type if none"""
|
446
|
+
|
447
|
+
stack_op_directives: dict[AstOp, type[StackOpDirective]] = field(
|
448
|
+
default_factory=dict
|
449
|
+
)
|
450
|
+
"""some stack operation to which directive will be emitted for it"""
|
451
|
+
|
452
|
+
type_coercions: dict[AstExpr, FppTypeClass] = field(default_factory=dict)
|
453
|
+
"""expr to fprime type it must be converted into at runtime"""
|
454
|
+
|
455
|
+
expr_values: dict[AstExpr, FppType | NothingType | None] = field(
|
456
|
+
default_factory=dict
|
457
|
+
)
|
458
|
+
"""expr to its fprime value, or nothing if no value, or None if unsure at compile time"""
|
459
|
+
|
460
|
+
directives: dict[Ast, list[Directive] | None] = field(default_factory=dict)
|
461
|
+
"""a list of code generated by each node, or None/empty list if no directives"""
|
462
|
+
|
463
|
+
node_dir_counts: dict[Ast, int] = field(default_factory=dict)
|
464
|
+
"""node to the number of directives generated by it"""
|
465
|
+
|
466
|
+
lvar_array_size_bytes: int = 0
|
467
|
+
"""the size in bytes of the lvar array"""
|
468
|
+
|
469
|
+
start_line_idx: dict[Ast, int] = field(default_factory=dict)
|
470
|
+
"""the line index at which each node's directives will be included in the output"""
|
471
|
+
|
472
|
+
errors: list[CompileException] = field(default_factory=list)
|
473
|
+
"""a list of all compile exceptions generated by passes"""
|
474
|
+
|
475
|
+
def err(self, msg, n):
|
476
|
+
"""adds a compile exception to internal state"""
|
477
|
+
self.errors.append(CompileException(msg, n))
|
478
|
+
|
479
|
+
|
480
|
+
class Visitor:
|
481
|
+
"""visits each class, calling a custom visit function, if one is defined, for each
|
482
|
+
node type"""
|
483
|
+
|
484
|
+
def _find_custom_visit_func(self, node: Ast):
|
485
|
+
for name, func in inspect.getmembers(type(self), inspect.isfunction):
|
486
|
+
if not name.startswith("visit") or name == "visit_default":
|
487
|
+
# not a visitor, or the default visit func
|
488
|
+
continue
|
489
|
+
signature = inspect.signature(func)
|
490
|
+
params = list(signature.parameters.values())
|
491
|
+
assert len(params) == 3
|
492
|
+
assert params[1].annotation is not None
|
493
|
+
annotations = typing.get_type_hints(func)
|
494
|
+
param_type = annotations[params[1].name]
|
495
|
+
if is_instance_compat(node, param_type):
|
496
|
+
return func
|
497
|
+
else:
|
498
|
+
# call the default
|
499
|
+
return type(self).visit_default
|
500
|
+
|
501
|
+
def _visit(self, node: Ast, state: CompileState):
|
502
|
+
visit_func = self._find_custom_visit_func(node)
|
503
|
+
visit_func(self, node, state)
|
504
|
+
|
505
|
+
def visit_default(self, node: Ast, state: CompileState):
|
506
|
+
pass
|
507
|
+
|
508
|
+
def run(self, start: Ast, state: CompileState):
|
509
|
+
"""runs the visitor, starting at the given node, descending depth-first"""
|
510
|
+
|
511
|
+
def _descend(node: Ast):
|
512
|
+
if not isinstance(node, Ast):
|
513
|
+
return
|
514
|
+
children = []
|
515
|
+
for field in fields(node):
|
516
|
+
field_val = getattr(node, field.name)
|
517
|
+
if isinstance(field_val, list):
|
518
|
+
children.extend(field_val)
|
519
|
+
else:
|
520
|
+
children.append(field_val)
|
521
|
+
|
522
|
+
for child in children:
|
523
|
+
if not isinstance(child, Ast):
|
524
|
+
continue
|
525
|
+
_descend(child)
|
526
|
+
if len(state.errors) != 0:
|
527
|
+
break
|
528
|
+
self._visit(child, state)
|
529
|
+
if len(state.errors) != 0:
|
530
|
+
break
|
531
|
+
|
532
|
+
_descend(start)
|
533
|
+
self._visit(start, state)
|
534
|
+
|
535
|
+
|
536
|
+
class TopDownVisitor(Visitor):
|
537
|
+
|
538
|
+
def run(self, start: Ast, state: CompileState):
|
539
|
+
"""runs the visitor, starting at the given node, descending breadth-first"""
|
540
|
+
|
541
|
+
def _descend(node: Ast):
|
542
|
+
if not isinstance(node, Ast):
|
543
|
+
return
|
544
|
+
children = []
|
545
|
+
for field in fields(node):
|
546
|
+
field_val = getattr(node, field.name)
|
547
|
+
if isinstance(field_val, list):
|
548
|
+
children.extend(field_val)
|
549
|
+
else:
|
550
|
+
children.append(field_val)
|
551
|
+
|
552
|
+
for child in children:
|
553
|
+
if not isinstance(child, Ast):
|
554
|
+
continue
|
555
|
+
self._visit(child, state)
|
556
|
+
if len(state.errors) != 0:
|
557
|
+
break
|
558
|
+
_descend(child)
|
559
|
+
if len(state.errors) != 0:
|
560
|
+
break
|
561
|
+
|
562
|
+
self._visit(start, state)
|
563
|
+
_descend(start)
|
@@ -61,7 +61,7 @@ class StandardPipeline:
|
|
61
61
|
dictionaries,
|
62
62
|
file_store,
|
63
63
|
logging_prefix=None,
|
64
|
-
data_logging_enabled=True
|
64
|
+
data_logging_enabled=True,
|
65
65
|
):
|
66
66
|
"""
|
67
67
|
Setup the standard pipeline for moving data from the middleware layer through the GDS layers using the standard
|
@@ -56,7 +56,8 @@ def pytest_addoption(parser):
|
|
56
56
|
parser.addoption(
|
57
57
|
"--deployment-config",
|
58
58
|
action="store",
|
59
|
-
|
59
|
+
type=Path,
|
60
|
+
help="Path to JSON configuration file for mapping deployment components",
|
60
61
|
)
|
61
62
|
|
62
63
|
def pytest_configure(config):
|
fprime_gds/executables/cli.py
CHANGED
@@ -413,6 +413,14 @@ class ConfigDrivenParser(ParserBase):
|
|
413
413
|
print(f"[INFO] Reading command-line configuration from: {args.config}")
|
414
414
|
with open(args.config, "r") as file_handle:
|
415
415
|
try:
|
416
|
+
relative_base = args.config.parent.absolute()
|
417
|
+
|
418
|
+
def path_constructor(loader, node):
|
419
|
+
"""Processes !PATH annotations as relative to current file"""
|
420
|
+
calculated_path = relative_base / loader.construct_scalar(node)
|
421
|
+
return calculated_path
|
422
|
+
|
423
|
+
yaml.SafeLoader.add_constructor("!PATH", path_constructor)
|
416
424
|
loaded = yaml.safe_load(file_handle)
|
417
425
|
args.config_values = loaded if loaded is not None else {}
|
418
426
|
except Exception as exc:
|
@@ -1019,7 +1027,7 @@ class DictionaryParser(DetectionParser):
|
|
1019
1027
|
elif args.dictionary is None:
|
1020
1028
|
args = super().handle_arguments(args, **kwargs)
|
1021
1029
|
args.dictionary = find_dict(args.deployment)
|
1022
|
-
|
1030
|
+
|
1023
1031
|
# Setup dictionaries encoders and decoders
|
1024
1032
|
dictionaries = Dictionaries()
|
1025
1033
|
|
@@ -1099,7 +1107,7 @@ class StandardPipelineParser(CompositeParser):
|
|
1099
1107
|
"dictionaries": args_ns.dictionaries,
|
1100
1108
|
"file_store": args_ns.files_storage_directory,
|
1101
1109
|
"logging_prefix": args_ns.logs,
|
1102
|
-
"data_logging_enabled": not args_ns.disable_data_logging
|
1110
|
+
"data_logging_enabled": not args_ns.disable_data_logging,
|
1103
1111
|
}
|
1104
1112
|
pipeline = pipeline if pipeline else StandardPipeline()
|
1105
1113
|
pipeline.transport_implementation = args_ns.connection_transport
|
@@ -1122,6 +1130,7 @@ class CommParser(CompositeParser):
|
|
1122
1130
|
CommExtraParser,
|
1123
1131
|
MiddleWareParser,
|
1124
1132
|
LogDeployParser,
|
1133
|
+
DictionaryParser, # needed to get types from dictionary for framing
|
1125
1134
|
]
|
1126
1135
|
|
1127
1136
|
def __init__(self):
|
fprime_gds/executables/comm.py
CHANGED
@@ -51,8 +51,6 @@ def main():
|
|
51
51
|
Plugins.system(["communication", "framing"])
|
52
52
|
args, _ = fprime_gds.executables.cli.ParserBase.parse_args(
|
53
53
|
[
|
54
|
-
fprime_gds.executables.cli.LogDeployParser,
|
55
|
-
fprime_gds.executables.cli.MiddleWareParser,
|
56
54
|
fprime_gds.executables.cli.CommParser,
|
57
55
|
fprime_gds.executables.cli.PluginArgumentParser,
|
58
56
|
],
|
@@ -11,6 +11,7 @@ import webbrowser
|
|
11
11
|
from fprime_gds.executables.cli import (
|
12
12
|
BinaryDeployment,
|
13
13
|
ConfigDrivenParser,
|
14
|
+
CompositeParser,
|
14
15
|
CommParser,
|
15
16
|
GdsParser,
|
16
17
|
ParserBase,
|
@@ -104,6 +105,7 @@ def launch_html(parsed_args):
|
|
104
105
|
Return:
|
105
106
|
launched process
|
106
107
|
"""
|
108
|
+
composite_parser = CompositeParser([StandardPipelineParser, ConfigDrivenParser])
|
107
109
|
reproduced_arguments = StandardPipelineParser().reproduce_cli_args(parsed_args)
|
108
110
|
if "--log-directly" not in reproduced_arguments:
|
109
111
|
reproduced_arguments += ["--log-directly"]
|