fprime-gds 4.0.0a8__py3-none-any.whl → 4.0.0a10__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/fpy/README.md +56 -0
- fprime_gds/common/fpy/SPEC.md +69 -0
- fprime_gds/common/fpy/bytecode/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/directives.py +490 -0
- fprime_gds/common/fpy/codegen.py +1687 -0
- fprime_gds/common/fpy/grammar.lark +88 -0
- fprime_gds/common/fpy/main.py +40 -0
- fprime_gds/common/fpy/parser.py +239 -0
- fprime_gds/common/templates/cmd_template.py +8 -0
- fprime_gds/executables/cli.py +31 -16
- fprime_gds/executables/data_product_writer.py +66 -30
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/METADATA +2 -1
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/RECORD +18 -12
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/entry_points.txt +1 -1
- fprime_gds/common/fpy/serialize_bytecode.py +0 -229
- fprime_gds/common/fpy/types.py +0 -203
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/WHEEL +0 -0
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/licenses/LICENSE.txt +0 -0
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/licenses/NOTICE.txt +0 -0
- {fprime_gds-4.0.0a8.dist-info → fprime_gds-4.0.0a10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
# Adapted from: https://docs.python.org/3/reference/grammar.html and the Lark Python grammar
|
2
|
+
|
3
|
+
input: (_NEWLINE | _stmt)*
|
4
|
+
|
5
|
+
_literal: number | string | boolean
|
6
|
+
|
7
|
+
_stmt: _small_stmt [_NEWLINE] | _compound_stmt
|
8
|
+
_small_stmt: (_expr_stmt | assign | pass_stmt )
|
9
|
+
_compound_stmt: if_stmt
|
10
|
+
pass_stmt: "pass"
|
11
|
+
|
12
|
+
_expr_stmt: _expr
|
13
|
+
|
14
|
+
|
15
|
+
# assignment
|
16
|
+
|
17
|
+
# can currently only assign constant values to vars
|
18
|
+
# for some reason if i use _reference here i don't get a None in the optional spot
|
19
|
+
assign: var [":" (get_attr | get_item | var)] "=" _expr
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
# branching
|
24
|
+
|
25
|
+
if_stmt: "if" _expr ":" body elifs ["else" ":" body]
|
26
|
+
elifs: elif_*
|
27
|
+
elif_: "elif" _expr ":" body
|
28
|
+
body: _NEWLINE _INDENT _stmt+ _DEDENT
|
29
|
+
|
30
|
+
_expr: _test
|
31
|
+
|
32
|
+
# logical tests
|
33
|
+
_test: or_test
|
34
|
+
?or_test: and_test ("or" and_test)*
|
35
|
+
?and_test: not_test_ ("and" not_test_)*
|
36
|
+
?not_test_: "not" not_test_ -> not_test
|
37
|
+
| comparison
|
38
|
+
| atom
|
39
|
+
comparison: atom comp_op atom
|
40
|
+
comp_op: COMPARISON_OP
|
41
|
+
|
42
|
+
arguments: _expr ("," _expr)*
|
43
|
+
|
44
|
+
?atom: _reference "(" [arguments] ")" -> func_call
|
45
|
+
| _reference
|
46
|
+
| _literal
|
47
|
+
| "(" _expr ")"
|
48
|
+
|
49
|
+
get_item: _reference "[" number "]"
|
50
|
+
get_attr: _reference "." name
|
51
|
+
var: name
|
52
|
+
|
53
|
+
_reference: get_item | get_attr | var
|
54
|
+
|
55
|
+
# not used in grammar, but may appear in "node" passed from Parser to Compiler
|
56
|
+
encoding_decl: name
|
57
|
+
|
58
|
+
number: DEC_NUMBER | FLOAT_NUMBER
|
59
|
+
string: STRING
|
60
|
+
boolean: CONST_FALSE | CONST_TRUE
|
61
|
+
|
62
|
+
# Other terminals
|
63
|
+
|
64
|
+
_NEWLINE: ( /\r?\n[\t ]*/ | COMMENT )+
|
65
|
+
|
66
|
+
%ignore /[\t \f]+/ // WS
|
67
|
+
%ignore /\\[\t \f]*\r?\n/ // LINE_CONT
|
68
|
+
%ignore COMMENT
|
69
|
+
%declare _INDENT _DEDENT
|
70
|
+
|
71
|
+
|
72
|
+
# Python terminals
|
73
|
+
|
74
|
+
!name: NAME
|
75
|
+
NAME: /[^\W\d]\w*/
|
76
|
+
COMMENT: /#[^\n]*/
|
77
|
+
CONST_TRUE: "True"
|
78
|
+
CONST_FALSE: "False"
|
79
|
+
COMPARISON_OP: ">" | "<" | "<=" | ">=" | "==" | "!="
|
80
|
+
|
81
|
+
STRING: /("(?!"").*?(?<!\\)(\\\\)*?"|'(?!'').*?(?<!\\)(\\\\)*?')/i
|
82
|
+
|
83
|
+
_SPECIAL_DEC: "0".."9" ("_"? "0".."9" )*
|
84
|
+
DEC_NUMBER: "-"? "1".."9" ("_"? "0".."9" )*
|
85
|
+
| "-"? "0" ("_"? "0" )* /(?![1-9])/
|
86
|
+
|
87
|
+
DECIMAL: "." _SPECIAL_DEC | _SPECIAL_DEC "." _SPECIAL_DEC?
|
88
|
+
FLOAT_NUMBER.2: "-"? _SPECIAL_DEC DECIMAL ["e" ["-"] _SPECIAL_DEC]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import argparse
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
from fprime_gds.common.fpy.parser import parse
|
5
|
+
from fprime_gds.common.fpy.codegen import compile
|
6
|
+
from fprime_gds.common.fpy.bytecode.directives import serialize_directives
|
7
|
+
|
8
|
+
|
9
|
+
def main():
|
10
|
+
arg_parser = argparse.ArgumentParser()
|
11
|
+
arg_parser.add_argument("input", type=Path, help="The input .fpy file")
|
12
|
+
arg_parser.add_argument(
|
13
|
+
"-o",
|
14
|
+
"--output",
|
15
|
+
type=Path,
|
16
|
+
required=False,
|
17
|
+
default=None,
|
18
|
+
help="The output .bin path",
|
19
|
+
)
|
20
|
+
arg_parser.add_argument(
|
21
|
+
"-d",
|
22
|
+
"--dictionary",
|
23
|
+
type=Path,
|
24
|
+
required=True,
|
25
|
+
help="The FPrime dictionary .json file",
|
26
|
+
)
|
27
|
+
|
28
|
+
args = arg_parser.parse_args()
|
29
|
+
|
30
|
+
if not args.input.exists():
|
31
|
+
print(f"Input file {args.input} does not exist")
|
32
|
+
exit(-1)
|
33
|
+
|
34
|
+
body = parse(args.input.read_text())
|
35
|
+
directives = compile(body, args.dictionary)
|
36
|
+
output = args.output
|
37
|
+
if output is None:
|
38
|
+
output = args.input.with_suffix(".bin")
|
39
|
+
serialize_directives(directives, output)
|
40
|
+
print("Done")
|
@@ -0,0 +1,239 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Literal as TypingLiteral, Union
|
5
|
+
from lark.indenter import PythonIndenter
|
6
|
+
from lark import Lark, Transformer, v_args
|
7
|
+
from lark.tree import Meta
|
8
|
+
|
9
|
+
fpy_grammar_str = (Path(__file__).parent / "grammar.lark").read_text()
|
10
|
+
|
11
|
+
input_text = None
|
12
|
+
|
13
|
+
|
14
|
+
def parse(text: str):
|
15
|
+
parser = Lark(
|
16
|
+
fpy_grammar_str,
|
17
|
+
start="input",
|
18
|
+
parser="lalr",
|
19
|
+
postlex=PythonIndenter(),
|
20
|
+
propagate_positions=True,
|
21
|
+
maybe_placeholders=True,
|
22
|
+
)
|
23
|
+
|
24
|
+
global input_text
|
25
|
+
input_text = text
|
26
|
+
tree = parser.parse(text, on_error=lambda x: print("Error"))
|
27
|
+
transformed = FpyTransformer().transform(tree)
|
28
|
+
return transformed
|
29
|
+
|
30
|
+
|
31
|
+
@dataclass
|
32
|
+
class Ast:
|
33
|
+
meta: Meta = field(repr=False)
|
34
|
+
id: int = field(init=False, repr=False, default=None)
|
35
|
+
node_text: str = field(init=False, repr=False, default=None)
|
36
|
+
|
37
|
+
def __post_init__(self):
|
38
|
+
if not hasattr(self.meta, "start_pos"):
|
39
|
+
self.node_text = ""
|
40
|
+
return
|
41
|
+
self.node_text = (
|
42
|
+
input_text[self.meta.start_pos : self.meta.end_pos]
|
43
|
+
.replace("\n", " ")
|
44
|
+
.strip()
|
45
|
+
)
|
46
|
+
|
47
|
+
def __hash__(self):
|
48
|
+
return hash(self.id)
|
49
|
+
|
50
|
+
def __repr__(self):
|
51
|
+
return f"{self.__class__.__name__}({self.node_text})"
|
52
|
+
|
53
|
+
|
54
|
+
@dataclass
|
55
|
+
class AstVar(Ast):
|
56
|
+
var: str
|
57
|
+
|
58
|
+
|
59
|
+
@dataclass()
|
60
|
+
class AstString(Ast):
|
61
|
+
value: str
|
62
|
+
|
63
|
+
|
64
|
+
@dataclass
|
65
|
+
class AstNumber(Ast):
|
66
|
+
value: int | float
|
67
|
+
|
68
|
+
|
69
|
+
@dataclass
|
70
|
+
class AstBoolean(Ast):
|
71
|
+
value: TypingLiteral[True] | TypingLiteral[False]
|
72
|
+
|
73
|
+
|
74
|
+
AstLiteral = AstString | AstNumber | AstBoolean
|
75
|
+
|
76
|
+
|
77
|
+
@dataclass
|
78
|
+
class AstGetAttr(Ast):
|
79
|
+
parent: "AstReference"
|
80
|
+
attr: str
|
81
|
+
|
82
|
+
|
83
|
+
@dataclass
|
84
|
+
class AstGetItem(Ast):
|
85
|
+
parent: "AstReference"
|
86
|
+
item: AstNumber
|
87
|
+
|
88
|
+
|
89
|
+
@dataclass
|
90
|
+
class AstFuncCall(Ast):
|
91
|
+
func: "AstReference"
|
92
|
+
args: list["AstExpr"] | None
|
93
|
+
|
94
|
+
|
95
|
+
@dataclass
|
96
|
+
class AstInfixOp(Ast):
|
97
|
+
value: str
|
98
|
+
|
99
|
+
|
100
|
+
@dataclass()
|
101
|
+
class AstPass(Ast):
|
102
|
+
pass
|
103
|
+
|
104
|
+
|
105
|
+
@dataclass
|
106
|
+
class AstComparison(Ast):
|
107
|
+
lhs: "AstExpr"
|
108
|
+
op: AstInfixOp
|
109
|
+
rhs: "AstExpr"
|
110
|
+
|
111
|
+
|
112
|
+
@dataclass
|
113
|
+
class AstNot(Ast):
|
114
|
+
value: "AstExpr"
|
115
|
+
|
116
|
+
|
117
|
+
@dataclass
|
118
|
+
class AstAnd(Ast):
|
119
|
+
values: list["AstExpr"]
|
120
|
+
|
121
|
+
|
122
|
+
@dataclass
|
123
|
+
class AstOr(Ast):
|
124
|
+
values: list["AstExpr"]
|
125
|
+
|
126
|
+
|
127
|
+
AstTest = AstOr | AstAnd | AstNot | AstComparison
|
128
|
+
|
129
|
+
|
130
|
+
AstReference = AstGetAttr | AstGetItem | AstVar
|
131
|
+
AstExpr = Union[AstFuncCall, AstTest, AstLiteral, AstReference]
|
132
|
+
|
133
|
+
|
134
|
+
@dataclass
|
135
|
+
class AstAssign(Ast):
|
136
|
+
variable: AstVar
|
137
|
+
var_type: AstReference | None
|
138
|
+
value: AstExpr
|
139
|
+
|
140
|
+
|
141
|
+
@dataclass
|
142
|
+
class AstElif(Ast):
|
143
|
+
condition: AstExpr
|
144
|
+
body: "AstBody"
|
145
|
+
|
146
|
+
|
147
|
+
@dataclass
|
148
|
+
class AstElifs(Ast):
|
149
|
+
cases: list[AstElif]
|
150
|
+
|
151
|
+
|
152
|
+
@dataclass()
|
153
|
+
class AstIf(Ast):
|
154
|
+
condition: AstExpr
|
155
|
+
body: "AstBody"
|
156
|
+
elifs: AstElifs | None
|
157
|
+
els: Union["AstBody", None]
|
158
|
+
|
159
|
+
|
160
|
+
AstStmt = Union[AstExpr, AstAssign, AstPass, AstIf]
|
161
|
+
|
162
|
+
|
163
|
+
@dataclass
|
164
|
+
class AstBody(Ast):
|
165
|
+
stmts: list[AstStmt]
|
166
|
+
|
167
|
+
|
168
|
+
for cls in Ast.__subclasses__():
|
169
|
+
cls.__hash__ = Ast.__hash__
|
170
|
+
# cls.__repr__ = Ast.__repr__
|
171
|
+
|
172
|
+
|
173
|
+
@v_args(meta=False, inline=False)
|
174
|
+
def as_list(self, tree):
|
175
|
+
return list(tree)
|
176
|
+
|
177
|
+
|
178
|
+
def no_inline_or_meta(type):
|
179
|
+
@v_args(meta=False, inline=False)
|
180
|
+
def wrapper(self, tree):
|
181
|
+
return type(tree)
|
182
|
+
|
183
|
+
return wrapper
|
184
|
+
|
185
|
+
|
186
|
+
def no_inline(type):
|
187
|
+
@v_args(meta=True, inline=False)
|
188
|
+
def wrapper(self, meta, tree):
|
189
|
+
return type(meta, tree)
|
190
|
+
|
191
|
+
return wrapper
|
192
|
+
|
193
|
+
|
194
|
+
def no_meta(type):
|
195
|
+
@v_args(meta=False, inline=True)
|
196
|
+
def wrapper(self, tree):
|
197
|
+
return type(tree)
|
198
|
+
|
199
|
+
return wrapper
|
200
|
+
|
201
|
+
def handle_str(meta, s: str):
|
202
|
+
return s.strip("'").strip('"')
|
203
|
+
|
204
|
+
|
205
|
+
@v_args(meta=True, inline=True)
|
206
|
+
class FpyTransformer(Transformer):
|
207
|
+
input = no_inline(AstBody)
|
208
|
+
pass_stmt = AstPass
|
209
|
+
|
210
|
+
assign = AstAssign
|
211
|
+
|
212
|
+
if_stmt = AstIf
|
213
|
+
elifs = no_inline(AstElifs)
|
214
|
+
elif_ = AstElif
|
215
|
+
body = no_inline(AstBody)
|
216
|
+
or_test = no_inline(AstOr)
|
217
|
+
and_test = no_inline(AstAnd)
|
218
|
+
not_test = AstNot
|
219
|
+
comparison = AstComparison
|
220
|
+
comp_op = AstInfixOp
|
221
|
+
|
222
|
+
func_call = AstFuncCall
|
223
|
+
arguments = no_inline_or_meta(list)
|
224
|
+
|
225
|
+
string = AstString
|
226
|
+
number = AstNumber
|
227
|
+
boolean = AstBoolean
|
228
|
+
name = no_meta(str)
|
229
|
+
get_attr = AstGetAttr
|
230
|
+
get_item = AstGetItem
|
231
|
+
var = AstVar
|
232
|
+
|
233
|
+
NAME = str
|
234
|
+
DEC_NUMBER = int
|
235
|
+
FLOAT_NUMBER = float
|
236
|
+
COMPARISON_OP = str
|
237
|
+
STRING = handle_str
|
238
|
+
CONST_TRUE = lambda a, b: True
|
239
|
+
CONST_FALSE = lambda a, b: False
|
@@ -175,3 +175,11 @@ class CmdTemplate(data_template.DataTemplate):
|
|
175
175
|
|
176
176
|
def getArgs(self):
|
177
177
|
return self.get_args()
|
178
|
+
|
179
|
+
def __repr__(self):
|
180
|
+
arg_strs = []
|
181
|
+
for arg in self.arguments:
|
182
|
+
arg_strs.append(arg[0] + ": " + str(arg[2]))
|
183
|
+
|
184
|
+
args_str = ", ".join(arg_strs)
|
185
|
+
return f"CmdTemplate({self.comp_name}.{self.mnemonic}, args: ({args_str}))"
|
fprime_gds/executables/cli.py
CHANGED
@@ -209,7 +209,9 @@ class ParserBase(ABC):
|
|
209
209
|
arguments: arguments to process, None to use command line input
|
210
210
|
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
211
211
|
"""
|
212
|
-
return cls._parse_args(
|
212
|
+
return cls._parse_args(
|
213
|
+
parser_classes, description, arguments, use_parse_known=True, **kwargs
|
214
|
+
)
|
213
215
|
|
214
216
|
@classmethod
|
215
217
|
def parse_args(
|
@@ -234,7 +236,6 @@ class ParserBase(ABC):
|
|
234
236
|
"""
|
235
237
|
return cls._parse_args(parser_classes, description, arguments, **kwargs)
|
236
238
|
|
237
|
-
|
238
239
|
@staticmethod
|
239
240
|
def _parse_args(
|
240
241
|
parser_classes,
|
@@ -297,16 +298,17 @@ class ParserBase(ABC):
|
|
297
298
|
|
298
299
|
|
299
300
|
class ConfigDrivenParser(ParserBase):
|
300
|
-
"""
|
301
|
+
"""Parser that allows options from configuration and command line
|
301
302
|
|
302
303
|
This parser reads a configuration file (if supplied) and uses the values to drive the inputs to arguments. Command
|
303
304
|
line arguments will still take precedence over the configured values.
|
304
305
|
"""
|
306
|
+
|
305
307
|
DEFAULT_CONFIGURATION_PATH = Path("fprime-gds.yml")
|
306
308
|
|
307
309
|
@classmethod
|
308
310
|
def set_default_configuration(cls, path: Path):
|
309
|
-
"""
|
311
|
+
"""Set path for (global) default configuration file
|
310
312
|
|
311
313
|
Set the path for default configuration file. If unset, will use 'fprime-gds.yml'. Set to None to disable default
|
312
314
|
configuration.
|
@@ -321,7 +323,7 @@ class ConfigDrivenParser(ParserBase):
|
|
321
323
|
arguments=None,
|
322
324
|
**kwargs,
|
323
325
|
):
|
324
|
-
"""
|
326
|
+
"""Parse and post-process arguments using inputs and config
|
325
327
|
|
326
328
|
Parse the arguments in two stages: first parse the configuration data, ignoring unknown inputs, then parse the
|
327
329
|
full argument set with the supplied configuration to fill in additional options.
|
@@ -343,23 +345,29 @@ class ConfigDrivenParser(ParserBase):
|
|
343
345
|
|
344
346
|
# Custom flow involving parsing the arguments of this parser first, then passing the configured values
|
345
347
|
# as part of the argument source
|
346
|
-
ns_config, _, remaining = ParserBase.parse_known_args(
|
348
|
+
ns_config, _, remaining = ParserBase.parse_known_args(
|
349
|
+
[ConfigDrivenParser], description, arguments, **kwargs
|
350
|
+
)
|
347
351
|
config_options = ns_config.config_values.get("command-line-options", {})
|
348
352
|
config_args = cls.flatten_options(config_options)
|
349
353
|
# Argparse allows repeated (overridden) arguments, thus the CLI override is accomplished by providing
|
350
354
|
# remaining arguments after the configured ones
|
351
|
-
ns_full, parser = ParserBase.parse_args(
|
352
|
-
|
355
|
+
ns_full, parser = ParserBase.parse_args(
|
356
|
+
parser_classes, description, config_args + remaining, **kwargs
|
357
|
+
)
|
358
|
+
ns_final = argparse.Namespace(**vars(ns_config), **vars(ns_full))
|
353
359
|
return ns_final, parser
|
354
360
|
|
355
361
|
@staticmethod
|
356
362
|
def flatten_options(configured_options):
|
357
|
-
"""
|
363
|
+
"""Flatten options down to arguments"""
|
358
364
|
flattened = []
|
359
365
|
for option, value in configured_options.items():
|
360
366
|
flattened.append(f"--{option}")
|
361
367
|
if value is not None:
|
362
|
-
flattened.extend(
|
368
|
+
flattened.extend(
|
369
|
+
value if isinstance(value, (list, tuple)) else [f"{value}"]
|
370
|
+
)
|
363
371
|
return flattened
|
364
372
|
|
365
373
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
@@ -375,24 +383,30 @@ class ConfigDrivenParser(ParserBase):
|
|
375
383
|
}
|
376
384
|
|
377
385
|
def handle_arguments(self, args, **kwargs):
|
378
|
-
"""
|
386
|
+
"""Handle the arguments
|
379
387
|
|
380
388
|
Loads the configuration file specified and fills in the `config_values` attribute of the namespace with the
|
381
389
|
loaded configuration dictionary.
|
382
390
|
"""
|
383
391
|
args.config_values = {}
|
384
392
|
# Specified but non-existent config file is a hard error
|
385
|
-
if (
|
386
|
-
|
393
|
+
if (
|
394
|
+
"-c" in sys.argv[1:] or "--config" in sys.argv[1:]
|
395
|
+
) and not args.config.exists():
|
396
|
+
raise ValueError(
|
397
|
+
f"Specified configuration file '{args.config}' does not exist"
|
398
|
+
)
|
387
399
|
# Read configuration if the file was set and exists
|
388
|
-
if args.config is not None
|
400
|
+
if args.config is not None and args.config.exists():
|
389
401
|
print(f"[INFO] Reading command-line configuration from: {args.config}")
|
390
402
|
with open(args.config, "r") as file_handle:
|
391
403
|
try:
|
392
404
|
loaded = yaml.safe_load(file_handle)
|
393
405
|
args.config_values = loaded if loaded is not None else {}
|
394
406
|
except Exception as exc:
|
395
|
-
raise ValueError(
|
407
|
+
raise ValueError(
|
408
|
+
f"Malformed configuration {args.config}: {exc}", exc
|
409
|
+
)
|
396
410
|
return args
|
397
411
|
|
398
412
|
|
@@ -576,8 +590,9 @@ class PluginArgumentParser(ParserBase):
|
|
576
590
|
"""Parser for arguments coming from plugins"""
|
577
591
|
|
578
592
|
DESCRIPTION = "Plugin options"
|
593
|
+
# Defaults:
|
579
594
|
FPRIME_CHOICES = {
|
580
|
-
"framing": "
|
595
|
+
"framing": "space-packet-space-data-link",
|
581
596
|
"communication": "ip",
|
582
597
|
}
|
583
598
|
|