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.
@@ -194,6 +194,7 @@ class QualifiedType(BaseModel):
194
194
  class StructMember(BaseModel):
195
195
  type: Union[IntegerType, FloatType, BoolType, StringType, QualifiedType]
196
196
  size: int = 1
197
+ index: int
197
198
 
198
199
  class StructType(BaseModel):
199
200
  kind: str
@@ -209,7 +210,7 @@ class StructType(BaseModel):
209
210
  class AliasType(BaseModel):
210
211
  kind: str
211
212
  qualifiedName: str
212
- type: Union[AliasType, StructType, ArrayType, IntegerType, FloatType, QualifiedType, StringType]
213
+ type: Union[AliasType, StructType, ArrayType, IntegerType, BoolType, FloatType, StringType, QualifiedType]
213
214
  underlyingType: Union[StructType, ArrayType, IntegerType, BoolType, FloatType, StringType, QualifiedType]
214
215
 
215
216
  @field_validator('kind')
@@ -222,7 +223,7 @@ class ArrayType(BaseModel):
222
223
  kind: str
223
224
  qualifiedName: str
224
225
  size: int
225
- elementType: Union[AliasType, StructType, ArrayType, IntegerType, FloatType, QualifiedType, StringType]
226
+ elementType: Union[AliasType, StructType, ArrayType, IntegerType, BoolType, FloatType, StringType, QualifiedType]
226
227
 
227
228
  @field_validator('kind')
228
229
  def kind_qualifiedIdentifier(cls, v):
@@ -297,29 +298,33 @@ class DPHeader(BaseModel):
297
298
  @computed_field
298
299
  @property
299
300
  def header(self) -> Dict[str, Union[Type, ArrayType, EnumType]]:
300
- # All types/constants that make up the DP header
301
- header_dict = {
302
- "FwPacketDescriptorType": None,
303
- "FwDpIdType": None,
304
- "FwDpPriorityType" : None,
305
- "Seconds": None,
306
- "USeconds": None,
307
- "FwTimeBaseStoreType": None,
308
- "FwTimeContextStoreType": None,
309
- "Fw.DpCfg.ProcType": None,
310
- "Fw.DpCfg.CONTAINER_USER_DATA_SIZE": None,
311
- "Fw.DpState": None,
312
- "FwSizeStoreType": None
301
+ # Mapping from type/constant names to header field names
302
+ header_field_names = {
303
+ "FwPacketDescriptorType": "PacketDescriptor",
304
+ "FwDpIdType": "Id",
305
+ "FwDpPriorityType" : "Priority",
306
+ "Seconds": "Seconds",
307
+ "USeconds": "USeconds",
308
+ "FwTimeBaseStoreType": "TimeBase",
309
+ "FwTimeContextStoreType": "Context",
310
+ "Fw.DpCfg.ProcType": "ProcTypes",
311
+ "Fw.DpCfg.CONTAINER_USER_DATA_SIZE": "UserData",
312
+ "Fw.DpState": "DpState",
313
+ "FwSizeStoreType": "DataSize"
313
314
  }
314
- for k in header_dict.keys():
315
+ # All types/constants that make up the DP header
316
+ # Key: Values of above header_field_names
317
+ # Values: Initialized to None, populated below based on type names/constants in the JSON dictionary
318
+ header_dict = {v: None for v in header_field_names.values()}
319
+ for k, v in header_field_names.items():
315
320
  # Seconds and USeconds are not in the dictionary, but are both always U32
316
321
  if k == "Seconds" or k == "USeconds":
317
- header_dict[k] = Type(type=IntegerType(name="U32", kind="integer", size=32, signed=False))
322
+ header_dict[v] = Type(type=IntegerType(name="U32", kind="integer", size=32, signed=False))
318
323
  # According to Fw.Dp SDD, Header::UserData is an array of U8 of size Fw::DpCfg::CONTAINER_USER_DATA_SIZE.
319
324
  elif k == "Fw.DpCfg.CONTAINER_USER_DATA_SIZE":
320
325
  for t in self.constants:
321
326
  if t.qualifiedName == k:
322
- header_dict[k] = ArrayType(
327
+ header_dict[v] = ArrayType(
323
328
  kind="array",
324
329
  qualifiedName="UserData",
325
330
  size=t.value,
@@ -329,7 +334,7 @@ class DPHeader(BaseModel):
329
334
  else:
330
335
  for t in self.typeDefinitions:
331
336
  if t.qualifiedName == k:
332
- header_dict[k] = t
337
+ header_dict[v] = t
333
338
  break
334
339
 
335
340
  return header_dict
@@ -337,12 +342,15 @@ class DPHeader(BaseModel):
337
342
  @computed_field
338
343
  @property
339
344
  def dataId(self) -> AliasType:
340
- return self.header.get("FwDpIdType")
345
+ return self.header.get("Id")
341
346
 
342
347
  @computed_field
343
348
  @property
344
349
  def dataSize(self) -> AliasType:
345
- return self.header.get("FwSizeStoreType")
350
+ return self.header.get("DataSize")
351
+
352
+ def get_size_store_bytes(self) -> int:
353
+ return self.header.get("DataSize").underlyingType.size // 8
346
354
 
347
355
  @model_validator(mode='after')
348
356
  def validate_header(self) -> 'DPHeader':
@@ -371,7 +379,8 @@ type_mapping = {
371
379
  'U64': 'Q', # Unsigned 64-bit integer
372
380
  'F32': 'f', # 32-bit float
373
381
  'F64': 'd', # 64-bit float
374
- 'bool': '?' # An 8 bit boolean
382
+ 'bool': '?', # An 8 bit boolean
383
+ 'string': 's'
375
384
  # Add more mappings as needed
376
385
  }
377
386
 
@@ -485,6 +494,7 @@ class DataProductWriter:
485
494
  self.binaryFileName = binaryFileName
486
495
  self.totalBytesRead = 0
487
496
  self.calculatedCRC = 0
497
+ self.headerJSON = None
488
498
 
489
499
 
490
500
  # ----------------------------------------------------------------------------------------------
@@ -520,8 +530,29 @@ class DataProductWriter:
520
530
  except KeyError:
521
531
  raise KeyError(f"Unrecognized JSON Dictionary Type: {intType}")
522
532
  data = struct.unpack(format_str, bytes_read)[0]
533
+ return data
534
+
535
+ def read_and_deserialize_string(self) -> str:
536
+ size_store_type_bytes = self.headerJSON.get_size_store_bytes()
537
+ bytes_read_store = self.binaryFile.read(size_store_type_bytes)
538
+ if len(bytes_read_store) != size_store_type_bytes:
539
+ raise IOError(f"Tried to read {size_store_type_bytes} bytes from the binary file, but failed.")
540
+
541
+ self.totalBytesRead += size_store_type_bytes
542
+
543
+ format_str = f'{BIG_ENDIAN}H'
544
+ string_size_data = struct.unpack(format_str, bytes_read_store)[0]
545
+
546
+ bytes_read = self.binaryFile.read(string_size_data)
547
+ if len(bytes_read) != string_size_data:
548
+ raise IOError(f"Tried to read {string_size_data} bytes from the binary file, but failed.")
523
549
 
550
+ self.calculatedCRC = crc32(bytes_read_store + bytes_read, self.calculatedCRC) & 0xffffffff
551
+ self.totalBytesRead += string_size_data
524
552
 
553
+ format_str = f'{BIG_ENDIAN}{string_size_data}s'
554
+ data = struct.unpack(format_str, bytes_read)[0]
555
+ data = data.decode()
525
556
  return data
526
557
 
527
558
  # -----------------------------------------------------------------------------------------------------------------------
@@ -568,7 +599,7 @@ class DataProductWriter:
568
599
  # AssertionError: If the field_config is not an IntegerType, FloatType, or BoolType.
569
600
  # -----------------------------------------------------------------------------------------------------------------------
570
601
 
571
- def read_field(self, field_config: Union[IntegerType, FloatType, BoolType]) -> Union[int, float, bool]:
602
+ def read_field(self, field_config: Union[IntegerType, FloatType, BoolType, StringType]) -> Union[int, float, bool]:
572
603
 
573
604
  if type(field_config) is IntegerType:
574
605
  sizeBytes = field_config.size // 8
@@ -579,6 +610,9 @@ class DataProductWriter:
579
610
  elif type(field_config) is BoolType:
580
611
  sizeBytes = field_config.size // 8
581
612
 
613
+ elif type(field_config) is StringType:
614
+ return self.read_and_deserialize_string()
615
+
582
616
  else:
583
617
  assert False, "Unsupported typeKind encountered"
584
618
 
@@ -626,6 +660,8 @@ class DataProductWriter:
626
660
  elif isinstance(typeKind, BoolType):
627
661
  parent_dict[field_name] = self.read_field(typeKind)
628
662
 
663
+ elif isinstance(typeKind, StringType):
664
+ parent_dict[field_name] = self.read_field(typeKind)
629
665
 
630
666
  elif isinstance(typeKind, EnumType):
631
667
  value = self.read_field(typeKind.representationType)
@@ -646,7 +682,8 @@ class DataProductWriter:
646
682
 
647
683
  elif isinstance(typeKind, StructType):
648
684
  array_list = []
649
- for key, member in typeKind.members.items():
685
+ sorted_members = dict(sorted(typeKind.members.items(), key=lambda member: member[1].index))
686
+ for key, member in sorted_members.items():
650
687
  for i in range(member.size):
651
688
  element_dict = {}
652
689
  self.get_struct_item(key, member.type, typeList, element_dict)
@@ -830,7 +867,7 @@ class DataProductWriter:
830
867
  header_json["typeDefinitions"] = dict_json["typeDefinitions"]
831
868
  if "constants" in dict_json:
832
869
  header_json["constants"] = dict_json["constants"]
833
- headerJSON = DPHeader(**header_json)
870
+ self.headerJSON = DPHeader(**header_json)
834
871
 
835
872
  except json.JSONDecodeError as e:
836
873
  raise DictionaryError(self.jsonDict, e.lineno)
@@ -840,10 +877,10 @@ class DataProductWriter:
840
877
  with open(self.binaryFileName, 'rb') as self.binaryFile:
841
878
 
842
879
  # Read the header data up until the Records
843
- headerData = self.get_header_info(headerJSON)
880
+ headerData = self.get_header_info(self.headerJSON)
844
881
 
845
882
  # Read the total data size
846
- dataSize = headerData['FwSizeStoreType']
883
+ dataSize = headerData['DataSize']
847
884
 
848
885
  # Restart the count of bytes read
849
886
  self.totalBytesRead = 0
@@ -851,13 +888,12 @@ class DataProductWriter:
851
888
  recordList = [headerData]
852
889
 
853
890
  while self.totalBytesRead < dataSize:
854
-
855
- recordData = self.get_record_data(headerJSON, dictJSON)
891
+ recordData = self.get_record_data(self.headerJSON, dictJSON)
856
892
  recordList.append(recordData)
857
893
 
858
894
  computedCRC = self.calculatedCRC
859
895
  # Read the data checksum
860
- headerData['dataHash'] = self.read_field(headerJSON.dataHash.type)
896
+ headerData['dataHash'] = self.read_field(self.headerJSON.dataHash.type)
861
897
 
862
898
  if computedCRC != headerData['dataHash']:
863
899
  raise CRCError("Data", headerData['dataHash'], computedCRC)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fprime-gds
3
- Version: 4.0.0a8
3
+ Version: 4.0.0a10
4
4
  Summary: F Prime Flight Software Ground Data System layer
5
5
  Author-email: Michael Starch <Michael.D.Starch@jpl.nasa.gov>, Thomas Boyer-Chammard <Thomas.Boyer.Chammard@jpl.nasa.gov>
6
6
  License:
@@ -240,6 +240,7 @@ Requires-Dist: Jinja2>=2.11.3
240
240
  Requires-Dist: openpyxl>=3.0.10
241
241
  Requires-Dist: pyserial>=3.5
242
242
  Requires-Dist: pydantic>=2.6
243
+ Requires-Dist: lark>=1.2.2
243
244
  Requires-Dist: PyYAML>=6.0.2
244
245
  Requires-Dist: spacepackets>=0.30.0
245
246
  Requires-Dist: crc>=7.0.0
@@ -49,9 +49,15 @@ fprime_gds/common/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
49
49
  fprime_gds/common/files/downlinker.py,sha256=CZPfhH0J9-LNqW5Cv_ryicLTuoedLSWK8OPQmmQDZZY,7498
50
50
  fprime_gds/common/files/helpers.py,sha256=sGaxcczXmZ5_soawT7x_eJ_cC2PZ6KOGBfusAV4QC_g,7219
51
51
  fprime_gds/common/files/uplinker.py,sha256=lgqhlgeipBt3Arx-ohzK8vCdS54fKpv9Rg7SUTocUX8,13244
52
+ fprime_gds/common/fpy/README.md,sha256=AJE9KJDarTTW9nsTJBiZ6cKyPYQbWKbKJbFiNz6M8Z4,1414
53
+ fprime_gds/common/fpy/SPEC.md,sha256=XTMgr96BIVJheOtjAp6Qgu5l5RE16DxxIkPl0WwXfEs,2248
52
54
  fprime_gds/common/fpy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- fprime_gds/common/fpy/serialize_bytecode.py,sha256=RbBojtDGUjZyqGqKfqzJDLzU0Z9UBsNGaKMf8y2Qz7o,7283
54
- fprime_gds/common/fpy/types.py,sha256=XeDG6E_cm57lpG4wEdZuoGgtEJeSO8WmkCsIBbwWoQc,6143
55
+ fprime_gds/common/fpy/codegen.py,sha256=5VdV-f6CdU1LPJx4swB_GV5pvPe3Cqh9E3bkS0dBAo4,59828
56
+ fprime_gds/common/fpy/grammar.lark,sha256=QIQgmj58pW9ou2u7DzE39c0fz55eGNj3X7ridzAHRFk,2128
57
+ fprime_gds/common/fpy/main.py,sha256=U50sQCrwhZgxGzkjKgaJywoQM_cEH78hN9Vy3pDKKMA,1073
58
+ fprime_gds/common/fpy/parser.py,sha256=g-l67oZN_vB1JAdZ5S3u9m8APf6PdJ88GZyz0iX66qo,4418
59
+ fprime_gds/common/fpy/bytecode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
+ fprime_gds/common/fpy/bytecode/directives.py,sha256=tBpPQ2ZnQXA7juB85HhacDdil1vEHUKW8m_AsMf1KdQ,13360
55
61
  fprime_gds/common/gds_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
62
  fprime_gds/common/gds_cli/base_commands.py,sha256=nHnzHRMXs9haRjiUmq-Ciu1DSo1BXtcVscByk1Ypsxo,9432
57
63
  fprime_gds/common/gds_cli/channels.py,sha256=S8y0Mo2JbBFPvMzeW22HFVJ8p-UC-tzpYM7rXqIGWi4,2136
@@ -97,7 +103,7 @@ fprime_gds/common/pipeline/router.py,sha256=-P1wI0KXEh_snOzDaq8CjEoWuM_zRm8vUMR1
97
103
  fprime_gds/common/pipeline/standard.py,sha256=fDSPfyhYPMNhev5IQG2j51sCtQxXZ5PrqmulKH8TNjE,9778
98
104
  fprime_gds/common/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
105
  fprime_gds/common/templates/ch_template.py,sha256=1MoDZsia0dI_CvnIttwyKLhbQhum35OcJnFc50Xohuo,3893
100
- fprime_gds/common/templates/cmd_template.py,sha256=Bdkfgjb9Yqw7zaZyS8fR9ZUebrkFsRofF0g7xpKtkE4,5180
106
+ fprime_gds/common/templates/cmd_template.py,sha256=n91z4WhFgHwTu6_fQqy7JqpkEObAkllIeEy0AR0DvrQ,5455
101
107
  fprime_gds/common/templates/data_template.py,sha256=U87d8oC-BDTDuBRZbNnPkXy6rI_Pr-XnChHWZunw5jo,735
102
108
  fprime_gds/common/templates/event_template.py,sha256=L0hkWB_kEMhTNodPUqBAev76SMmWT9EWdcqxaaQX9ZE,4062
103
109
  fprime_gds/common/templates/pkt_template.py,sha256=5Wi6389m5j8w7JITBGfeUnw6CYE1-hjcVJ42NJmLDcE,1794
@@ -117,9 +123,9 @@ fprime_gds/common/utils/event_severity.py,sha256=7qPXHrDaM_REJ7sKBUEJTZIE0D4qVnV
117
123
  fprime_gds/common/utils/string_util.py,sha256=u_2iahRG3ROu3lAAt_KVcK226gEByElXqrA8mH8eDpI,3584
118
124
  fprime_gds/executables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
125
  fprime_gds/executables/apps.py,sha256=u79T_PlgMmNmA4YwWjs7LvPMCJnrjnURr05NMthOYP0,13350
120
- fprime_gds/executables/cli.py,sha256=SfKL8UZL2aR_YwcG8_4ndZeLxdvgnZOlfIF2yGr1zKo,50729
126
+ fprime_gds/executables/cli.py,sha256=Qqq3JQOqTsfDRsbTALw25xnwN7fCEVlpcKDV4jvGopQ,50961
121
127
  fprime_gds/executables/comm.py,sha256=08rO0o0MJgTRngB7Ygu2IL_gEAWKF7WFvFyro1CqReE,5214
122
- fprime_gds/executables/data_product_writer.py,sha256=PGNju52vjfg7-AUURFojmMV8S83Tzy58Hyti5tmpffc,35821
128
+ fprime_gds/executables/data_product_writer.py,sha256=e1Rp2LT_Cpg08f0Ki8GhirC7Wn6LtYiAef7KLAkZHUY,37773
123
129
  fprime_gds/executables/fprime_cli.py,sha256=CMoT7zWNwM8h2mSZW03AR96wl_XnZXoLNiOZN_sDi38,12431
124
130
  fprime_gds/executables/run_deployment.py,sha256=Zl0Y9-6i6c8tZhcS7XkAeVQtzn0d9fV-3UJQZ0bnBrc,7237
125
131
  fprime_gds/executables/tcpserver.py,sha256=KspVpu5YIuiWKOk5E6UDMKvqXYrRB1j9aX8CkMxysfw,17555
@@ -238,10 +244,10 @@ fprime_gds/flask/static/third-party/webfonts/fa-solid-900.woff2,sha256=mDS4KtJuK
238
244
  fprime_gds/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
239
245
  fprime_gds/plugin/definitions.py,sha256=QlxW1gNvoiqGMslSJjh3dTFZuv0igFHawN__3XJ0Wns,5355
240
246
  fprime_gds/plugin/system.py,sha256=M9xb-8jBhCUUx3X1z2uAP8Wx_v6NkL8JeaFgGcMnQqY,13432
241
- fprime_gds-4.0.0a8.dist-info/licenses/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
242
- fprime_gds-4.0.0a8.dist-info/licenses/NOTICE.txt,sha256=vXjA_xRcQhd83Vfk5D_vXg5kOjnnXvLuMi5vFKDEVmg,1612
243
- fprime_gds-4.0.0a8.dist-info/METADATA,sha256=LQafB-nq76AfTT18Hgog3D2SZVDsSfDTi97SSn8W6_0,24549
244
- fprime_gds-4.0.0a8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
245
- fprime_gds-4.0.0a8.dist-info/entry_points.txt,sha256=qFBHIR7CZ5CEeSEdZ-ZVQN9ZfUOZfm0PvvDZAAheuLk,445
246
- fprime_gds-4.0.0a8.dist-info/top_level.txt,sha256=6vzFLIX6ANfavKaXFHDMSLFtS94a6FaAsIWhjgYuSNE,27
247
- fprime_gds-4.0.0a8.dist-info/RECORD,,
247
+ fprime_gds-4.0.0a10.dist-info/licenses/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
248
+ fprime_gds-4.0.0a10.dist-info/licenses/NOTICE.txt,sha256=vXjA_xRcQhd83Vfk5D_vXg5kOjnnXvLuMi5vFKDEVmg,1612
249
+ fprime_gds-4.0.0a10.dist-info/METADATA,sha256=zD-VViHWpGeATD0SzBD1-yKMFOR25JIEIM3uBqMlY5Q,24577
250
+ fprime_gds-4.0.0a10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
251
+ fprime_gds-4.0.0a10.dist-info/entry_points.txt,sha256=V2XMHMUJUGTVx5s3_kK1jLmoxSKE1vvj2XWHH9y49WQ,423
252
+ fprime_gds-4.0.0a10.dist-info/top_level.txt,sha256=6vzFLIX6ANfavKaXFHDMSLFtS94a6FaAsIWhjgYuSNE,27
253
+ fprime_gds-4.0.0a10.dist-info/RECORD,,
@@ -1,7 +1,7 @@
1
1
  [console_scripts]
2
2
  fprime-cli = fprime_gds.executables.fprime_cli:main
3
3
  fprime-dp-write = fprime_gds.executables.data_product_writer:main
4
- fprime-fpy-bytecode = fprime_gds.common.fpy.serialize_bytecode:main
4
+ fprime-fpyc = fprime_gds.common.fpy.main:main
5
5
  fprime-gds = fprime_gds.executables.run_deployment:main
6
6
  fprime-prm-write = fprime_gds.common.tools.params:main
7
7
  fprime-seqgen = fprime_gds.common.tools.seqgen:main
@@ -1,229 +0,0 @@
1
- from __future__ import annotations
2
- from dataclasses import astuple
3
- import inspect
4
- import json
5
- from pathlib import Path
6
- from argparse import ArgumentParser
7
- import struct
8
- import zlib
9
- from fprime_gds.common.fpy.types import (
10
- StatementTemplate,
11
- StatementData,
12
- Header,
13
- Footer,
14
- HEADER_FORMAT,
15
- FOOTER_FORMAT,
16
- StatementType,
17
- FPY_DIRECTIVES,
18
- BytecodeParseContext,
19
- get_type_obj_for,
20
- )
21
- from fprime_gds.common.loaders.ch_json_loader import ChJsonLoader
22
- from fprime_gds.common.loaders.cmd_json_loader import CmdJsonLoader
23
- from fprime.common.models.serialize.numerical_types import (
24
- U8Type,
25
- )
26
-
27
- from fprime_gds.common.loaders.prm_json_loader import PrmJsonLoader
28
-
29
-
30
- def serialize_statement(stmt: StatementData) -> bytes:
31
- """converts a StatementData object into bytes that the FpySequencer can read"""
32
- # see https://github.com/nasa/fprime/issues/3023#issuecomment-2693051677
33
- # TODO replace this with actual documentation
34
-
35
- # type: U8 (0 if directive, 1 if cmd)
36
- # opcode: FwOpcodeType (default U32)
37
- # argBufSize: FwSizeStoreType (default U16)
38
- # argBuf: X bytes
39
-
40
- output = bytes()
41
- output += U8Type(stmt.template.statement_type.value).serialize()
42
- output += get_type_obj_for("FwOpcodeType")(stmt.template.opcode).serialize()
43
-
44
- arg_bytes = bytes()
45
- for arg in stmt.arg_values:
46
- arg_bytes += arg.serialize()
47
-
48
- output += get_type_obj_for("FwSizeStoreType")(len(arg_bytes)).serialize()
49
- output += arg_bytes
50
-
51
- return output
52
-
53
-
54
- def parse_str_as_statement(
55
- stmt: str, templates: list[StatementTemplate], context: BytecodeParseContext
56
- ) -> StatementData:
57
- """Converts a human-readable line of bytecode into a StatementData instance, given a list of
58
- possible statement templates"""
59
- name = stmt.split()[0]
60
- args = stmt[len(name) :]
61
-
62
- args = json.loads("[" + args + "]")
63
-
64
- matching_template = [t for t in templates if t.name == name]
65
- if len(matching_template) != 1:
66
- # no unique match
67
- if len(matching_template) == 0:
68
- raise RuntimeError("Could not find command or directive " + str(name))
69
- raise RuntimeError(
70
- "Found multiple commands or directives with name " + str(name)
71
- )
72
- matching_template = matching_template[0]
73
-
74
- arg_values = []
75
- if len(args) < len(matching_template.args):
76
- raise RuntimeError(
77
- "Missing arguments for statement "
78
- + str(matching_template.name)
79
- + ": "
80
- + str(matching_template.args[len(args) :])
81
- )
82
- if len(args) > len(matching_template.args):
83
- raise RuntimeError(
84
- "Extra arguments for"
85
- + str(matching_template.name)
86
- + ": "
87
- + str(args[len(matching_template.args) :])
88
- )
89
- for index, arg_json in enumerate(args):
90
- arg_type = matching_template.args[index]
91
- if inspect.isclass(arg_type):
92
- # it's a type. instantiate it with the json
93
- arg_value = arg_type(arg_json)
94
- else:
95
- # it's a function. give it the json and the ctx
96
- arg_value = arg_type(arg_json, context)
97
- arg_values.append(arg_value)
98
-
99
- return StatementData(matching_template, arg_values)
100
-
101
-
102
- def main():
103
- arg_parser = ArgumentParser()
104
- arg_parser.add_argument(
105
- "input", type=Path, help="The path to the input .fpybc file"
106
- )
107
-
108
- arg_parser.add_argument(
109
- "-d",
110
- "--dictionary",
111
- type=Path,
112
- help="The JSON topology dictionary to compile against",
113
- required=True,
114
- )
115
-
116
- arg_parser.add_argument(
117
- "-o",
118
- "--output",
119
- type=Path,
120
- help="The output .bin file path. Defaults to the input file path with a .bin extension",
121
- default=None,
122
- )
123
-
124
- args = arg_parser.parse_args()
125
-
126
- if not args.input.exists():
127
- print("Input file", args.input, "does not exist")
128
- exit(1)
129
-
130
- if not args.dictionary.exists():
131
- print("Dictionary file", args.dictionary, "does not exist")
132
- exit(1)
133
-
134
- serialize_bytecode(args.input, args.dictionary, args.output)
135
-
136
-
137
- def serialize_bytecode(input: Path, dictionary: Path, output: Path = None):
138
- """Given an input .fpybc file, and a dictionary .json file, converts the
139
- bytecode file into binary and writes it to the output file. If the output file
140
- is None, writes it to the input file with a .bin extension"""
141
- cmd_json_dict_loader = CmdJsonLoader(str(dictionary))
142
- (_, cmd_name_dict, _) = cmd_json_dict_loader.construct_dicts(
143
- str(dictionary)
144
- )
145
-
146
- stmt_templates = []
147
- stmt_templates.extend(FPY_DIRECTIVES)
148
- for cmd_template in cmd_name_dict.values():
149
- stmt_template = StatementTemplate(
150
- StatementType.CMD,
151
- cmd_template.opcode,
152
- cmd_template.get_full_name(),
153
- [arg[2] for arg in cmd_template.arguments],
154
- )
155
- stmt_templates.append(stmt_template)
156
-
157
- tlm_json_loader = ChJsonLoader(str(dictionary))
158
- (_, tlm_name_dict, _) = tlm_json_loader.construct_dicts(
159
- str(dictionary)
160
- )
161
-
162
- prm_json_loader = PrmJsonLoader(str(dictionary))
163
- (_, prm_name_dict, _) = prm_json_loader.construct_dicts(
164
- str(dictionary)
165
- )
166
-
167
- context = BytecodeParseContext()
168
- context.types = cmd_json_dict_loader.parsed_types
169
- context.channels = tlm_name_dict
170
- context.params = prm_name_dict
171
-
172
- input_lines = input.read_text().splitlines()
173
- input_lines = [line.strip() for line in input_lines]
174
- # remove comments and empty lines
175
- input_lines = [
176
- line for line in input_lines if not line.startswith(";") and len(line) > 0
177
- ]
178
-
179
- goto_tags = {}
180
- stmt_idx = 0
181
- statement_strs: list[str] = []
182
- for stmt in input_lines:
183
- if stmt.endswith(":"):
184
- # it's a goto tag
185
- goto_tags[stmt[:-1]] = stmt_idx
186
- else:
187
- statement_strs.append(stmt)
188
- stmt_idx += 1
189
-
190
- context.goto_tags = goto_tags
191
-
192
- statements: list[StatementData] = []
193
- for stmt_idx, stmt in enumerate(statement_strs):
194
- try:
195
- stmt_data = parse_str_as_statement(stmt, stmt_templates, context)
196
- statements.append(stmt_data)
197
- except BaseException as e:
198
- raise RuntimeError(
199
- "Exception while parsing statement index " + str(stmt_idx) + ": " + stmt
200
- ) from e
201
-
202
- # perform some checks for things we know will fail
203
- for stmt in statements:
204
- if stmt.template.name == "GOTO":
205
- if stmt.arg_values[0].val > len(statements):
206
- raise RuntimeError(
207
- f"GOTO index is outside the valid range for this sequence (was {stmt.arg_values[0].val}, should be <{len(statements)})"
208
- )
209
-
210
- output_bytes = bytes()
211
-
212
- for stmt in statements:
213
- output_bytes += serialize_statement(stmt)
214
-
215
- header = Header(0, 0, 0, 1, 0, len(statements), len(output_bytes))
216
- output_bytes = struct.pack(HEADER_FORMAT, *astuple(header)) + output_bytes
217
-
218
- crc = zlib.crc32(output_bytes) % (1 << 32)
219
- footer = Footer(crc)
220
- output_bytes += struct.pack(FOOTER_FORMAT, *astuple(footer))
221
-
222
- if output is None:
223
- output = input.with_suffix(".bin")
224
-
225
- output.write_bytes(output_bytes)
226
-
227
-
228
- if __name__ == "__main__":
229
- main()