fprime-gds 4.0.2a2__py3-none-any.whl → 4.0.2a4__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/distributor/distributor.py +9 -6
- 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/models/dictionaries.py +23 -0
- fprime_gds/common/pipeline/standard.py +5 -0
- fprime_gds/common/testing_fw/api.py +8 -1
- fprime_gds/executables/cli.py +9 -0
- 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.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/METADATA +1 -1
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/RECORD +31 -27
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/entry_points.txt +2 -1
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/WHEEL +0 -0
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/licenses/LICENSE.txt +0 -0
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/licenses/NOTICE.txt +0 -0
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.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)
|
@@ -49,6 +49,9 @@ class Dictionaries:
|
|
49
49
|
self._fw_type_name_dict = None
|
50
50
|
self._versions = None
|
51
51
|
self._metadata = None
|
52
|
+
self._dictionary_path = None
|
53
|
+
self._packet_spec_path = None
|
54
|
+
self._packet_set_name = None
|
52
55
|
|
53
56
|
def load_dictionaries(self, dictionary, packet_spec, packet_set_name):
|
54
57
|
"""
|
@@ -59,6 +62,11 @@ class Dictionaries:
|
|
59
62
|
:param packet_spec: specification for packets, or None, for packetized telemetry
|
60
63
|
:param packet_set_name: name of packet set in case multiple are available
|
61
64
|
"""
|
65
|
+
# Update the "from" values
|
66
|
+
self._dictionary_path = dictionary
|
67
|
+
self._packet_spec_path = packet_spec
|
68
|
+
self._packet_set_name = packet_set_name
|
69
|
+
|
62
70
|
if Path(dictionary).is_file() and ".json" in Path(dictionary).suffixes:
|
63
71
|
# Events
|
64
72
|
json_event_loader = (
|
@@ -204,6 +212,21 @@ class Dictionaries:
|
|
204
212
|
for legacy reasons. New code should use the metadata property."""
|
205
213
|
return self._metadata
|
206
214
|
|
215
|
+
@property
|
216
|
+
def dictionary_path(self):
|
217
|
+
""" Dictionary Path """
|
218
|
+
return self._dictionary_path
|
219
|
+
|
220
|
+
@property
|
221
|
+
def packet_spec_path(self):
|
222
|
+
""" Dictionary Path """
|
223
|
+
return self._packet_spec_path
|
224
|
+
|
225
|
+
@property
|
226
|
+
def packet_set_name(self):
|
227
|
+
""" Dictionary Path """
|
228
|
+
return self._packet_set_name
|
229
|
+
|
207
230
|
@property
|
208
231
|
def packet(self):
|
209
232
|
"""Packet dictionary"""
|
@@ -71,7 +71,7 @@ class IntegrationTestAPI(DataHandler):
|
|
71
71
|
|
72
72
|
# Copy dictionaries and binary file to output directory
|
73
73
|
if logpath is not None:
|
74
|
-
base_dir = Path(self.
|
74
|
+
base_dir = Path(self.dictionaries.dictionary_path).parents[1]
|
75
75
|
for subdir in ["bin", "dict"]:
|
76
76
|
dir_path = base_dir / subdir
|
77
77
|
if dir_path.is_dir():
|
@@ -85,6 +85,13 @@ class IntegrationTestAPI(DataHandler):
|
|
85
85
|
# Used by the data_callback method to detect if events have been received out of order.
|
86
86
|
self.last_evr = None
|
87
87
|
|
88
|
+
@property
|
89
|
+
def dictionaries(self):
|
90
|
+
""" Return the dictionaries """
|
91
|
+
# Pipeline should not be exposed to the user, but it stores the dictionary
|
92
|
+
# thus delegate to it.
|
93
|
+
return self.pipeline.dictionaries
|
94
|
+
|
88
95
|
def setup(self):
|
89
96
|
"""Set up the API, assumes pipeline is now setup"""
|
90
97
|
self.pipeline.coders.register_event_consumer(self)
|
fprime_gds/executables/cli.py
CHANGED
@@ -318,6 +318,7 @@ class ConfigDrivenParser(ParserBase):
|
|
318
318
|
|
319
319
|
DEFAULT_CONFIGURATION_PATH = Path("fprime-gds.yml")
|
320
320
|
|
321
|
+
|
321
322
|
@classmethod
|
322
323
|
def set_default_configuration(cls, path: Path):
|
323
324
|
"""Set path for (global) default configuration file
|
@@ -413,6 +414,14 @@ class ConfigDrivenParser(ParserBase):
|
|
413
414
|
print(f"[INFO] Reading command-line configuration from: {args.config}")
|
414
415
|
with open(args.config, "r") as file_handle:
|
415
416
|
try:
|
417
|
+
relative_base = args.config.parent.absolute()
|
418
|
+
|
419
|
+
def path_constructor(loader, node):
|
420
|
+
""" Processes !PATH annotations as relative to current file """
|
421
|
+
calculated_path = relative_base / loader.construct_scalar(node)
|
422
|
+
return calculated_path
|
423
|
+
|
424
|
+
yaml.SafeLoader.add_constructor("!PATH", path_constructor)
|
416
425
|
loaded = yaml.safe_load(file_handle)
|
417
426
|
args.config_values = loaded if loaded is not None else {}
|
418
427
|
except Exception as exc:
|
@@ -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"]
|