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.
@@ -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}))"
@@ -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(parser_classes, description, arguments, use_parse_known=True, **kwargs)
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
- """ Parser that allows options from configuration and command line
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
- """ Set path for (global) default configuration file
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
- """ Parse and post-process arguments using inputs and config
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([ConfigDrivenParser], description, arguments, **kwargs)
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(parser_classes, description, config_args + remaining, **kwargs)
352
- ns_final = argparse.Namespace(**vars(ns_config), **vars(ns_full))
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
- """ Flatten options down to arguments """
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(value if isinstance(value, (list, tuple)) else [f"{value}"])
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
- """ Handle the arguments
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 ("-c" in sys.argv[1:] or "--config" in sys.argv[1:]) and not args.config.exists():
386
- raise ValueError(f"Specified configuration file '{args.config}' does not exist")
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 and args.config.exists():
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(f"Malformed configuration {args.config}: {exc}", exc)
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": "fprime",
595
+ "framing": "space-packet-space-data-link",
581
596
  "communication": "ip",
582
597
  }
583
598