@@ -120,6 +120,7 @@ class ValidationError(Exception):
120120# node.ParamDictionary[index] = { N: { ..dict..}, ..dict.. }
121121FIELDS_PARAMS = {'comment' , 'save' , 'buffer_size' }
122122FIELDS_PARAMS_PROMOTE = {'callback' }
123+ FIELDS_PARAMS_EACH = {"start_index" }
123124
124125# Fields representing the dictionary value
125126FIELDS_VALUE = {'value' }
@@ -421,7 +422,7 @@ def generate_node(contents: str|TODJson, validate: bool = True) -> Node:
421422 if validate :
422423 validate_fromdict (jd , objtypes_i2s , objtypes_s2i )
423424
424- return node_fromdict (jd , objtypes_s2i )
425+ return node_fromdict (jd , objtypes_s2i , validate = validate )
425426
426427
427428def node_todict (node : Node , sort = False , rich = True , internal = False , validate = True ) -> TODJson :
@@ -484,7 +485,6 @@ def node_todict(node: Node, sort=False, rich=True, internal=False, validate=True
484485 finally :
485486 # Add in a fancyer index (do it here after index is finished being used)
486487 if rich :
487- index = obj ["index" ]
488488 obj ["index" ] = f'@@"0x{ index :04X} " // { index } @@'
489489
490490 dictionary .append (obj )
@@ -598,9 +598,12 @@ def indexentry_to_jsondict(ientry: TIndexEntry) -> TODObjJson:
598598 obj ['struct' ] = struct
599599 obj ['sub' ] = obj .pop ('values' , []) # type: ignore[typeddict-item] # values is about to be renamed
600600
601- # Move subindex[1] to 'each' on objecs that contain 'nbmax'
602- if len (obj ['sub' ]) > 1 and 'nbmax' in obj ['sub' ][1 ]:
603- obj ['each' ] = obj ['sub' ].pop (1 ) # type: ignore[typeddict-item]
601+ # Move subindex[-1] to 'each' on objecs that contain 'nbmax'
602+ if len (obj ['sub' ]) > 1 and 'nbmax' in obj ['sub' ][- 1 ]:
603+ obj ['each' ] = obj ['sub' ].pop (- 1 ) # type: ignore[typeddict-item]
604+ start_index = len (obj ["sub" ])
605+ if start_index > 1 :
606+ obj ["each" ]["start_index" ] = start_index
604607
605608 # Baseobj should have been emptied
606609 if odobj != {}:
@@ -892,20 +895,18 @@ def validate_indexentry(ientry: TIndexEntry):
892895 nbmaxok = True
893896
894897 elif struct in (OD .ARRAY , OD .NARRAY ):
895- if len (nbmax ) == 2 :
898+ if len (nbmax ) == 2 : # Array only have length + repeat
896899 lenok = True
897- if sum (nbmax ) == 1 and nbmax [1 ]:
900+ if sum (nbmax ) == 1 and nbmax [- 1 ]:
898901 nbmaxok = True
899902
900903 elif struct in (OD .RECORD , OD .NRECORD ):
901- if sum (nbmax ) and len (nbmax ) > 1 and nbmax [1 ]:
904+ if len (nbmax ) >= 2 : # Record can have more than one item before repeat
905+ lenok = True
906+ if sum (nbmax ) == 1 and nbmax [- 1 ]:
902907 nbmaxok = True
903- if len (nbmax ) == 2 :
904- lenok = True
905- elif sum (nbmax ) == 0 :
908+ if sum (nbmax ) == 0 :
906909 nbmaxok = True
907- if len (nbmax ) > 1 :
908- lenok = True
909910 else :
910911 raise ValidationError (f"Unknown struct '{ struct } '" )
911912
@@ -915,7 +916,7 @@ def validate_indexentry(ientry: TIndexEntry):
915916 raise ValidationError (f"Unexpexted count of subindexes in mapping object, found { len (nbmax )} " )
916917
917918
918- def node_fromdict (jd : TODJson , objtypes_s2i : dict [str , int ]) -> Node :
919+ def node_fromdict (jd : TODJson , objtypes_s2i : dict [str , int ], validate : bool = True ) -> Node :
919920 """ Convert a dict jd into a Node """
920921
921922 # Create the node and fill the most basic data
@@ -983,15 +984,16 @@ def node_fromdict(jd: TODJson, objtypes_s2i: dict[str, int]) -> Node:
983984 elif 'built-in' in groups :
984985 refobj = maps .MAPPING_DICTIONARY .get (index )
985986
986- diff = deepdiff .DeepDiff (refobj , ientry ['object' ], view = 'tree' )
987- if diff :
988- log .debug ("Index 0x%04x (%s) Difference between built-in object and imported:" , index , index )
989- for line in diff .pretty ().splitlines ():
990- log .debug (' %s' , line )
991- raise ValidationError (
992- f"Built-in object index 0x{ index :04x} ({ index } ) "
993- "does not match against system parameters"
994- )
987+ if validate :
988+ diff = deepdiff .DeepDiff (refobj , ientry ['object' ], view = 'tree' )
989+ if diff :
990+ log .debug ("Index 0x%04x (%s) Difference between built-in object and imported:" , index , index )
991+ for line in diff .pretty ().splitlines ():
992+ log .debug (' %s' , line )
993+ raise ValidationError (
994+ f"Built-in object index 0x{ index :04x} ({ index } ) "
995+ "does not match against system parameters"
996+ )
995997
996998 return node
997999
@@ -1093,7 +1095,9 @@ def rearrange_for_node(obj: TODObjJson, objtypes_s2i: dict[str, int]) -> TIndexE
10931095
10941096 # Move back the each object
10951097 if 'each' in obj :
1096- subitems .append (obj .pop ('each' )) # type: ignore[arg-type]
1098+ each = obj .pop ('each' )
1099+ each .pop ("start_index" , None ) # Remove the start_index if present
1100+ subitems .append (each ) # type: ignore[arg-type]
10971101
10981102 # Check if the object is a repeat object
10991103 repeat = obj .pop ('repeat' , False )
@@ -1173,7 +1177,7 @@ def validate_fromdict(jsonobj: TODJson, objtypes_i2s: dict[int, str], objtypes_s
11731177 # Verify that we have the expected members
11741178 member_compare (jsonobj .keys (), must = FIELDS_DATA_MUST , optional = FIELDS_DATA_OPT )
11751179
1176- def _validate_sub (obj , idx = 0 , is_var = False , is_repeat = False , is_each = False ):
1180+ def _validate_sub (obj , idx = 0 , is_var = False , is_repeat = False , is_each = False , is_index_each = False ):
11771181
11781182 # Validated: (See FIELDS_MAPVAPS_*, FIELDS_PARAMS and FIELDS_VALUE)
11791183 # ----------
@@ -1209,7 +1213,7 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False):
12091213
12101214 # Set what parameters should be present, optional or not present
12111215 if idx == - 1 : # Checking "each" section. No object or value
1212- params = 'no'
1216+ params = "each"
12131217
12141218 elif is_repeat : # Object repeat = defined elsewhere. No definition needed.
12151219 defs = 'no'
@@ -1219,10 +1223,9 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False):
12191223 elif is_var : # VAR type, guaranteed idx==0 here
12201224 value = 'opt'
12211225
1222- elif is_each : # Param have "each". Should never have any defs in idx > 0
1223- if idx > 0 :
1224- defs = 'no'
1225- value = 'must'
1226+ elif is_each and idx >= is_index_each : # Param have "each" and the index is in the "each" items
1227+ defs = 'no'
1228+ value = 'must'
12261229
12271230 else : # All other (not each, not repeat, not VAR)
12281231 if idx > 0 :
@@ -1240,6 +1243,8 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False):
12401243 # must |= FIELDS_PARAMS
12411244 if params == 'opt' :
12421245 opts |= FIELDS_PARAMS
1246+ if params == 'each' :
1247+ opts |= FIELDS_PARAMS_EACH
12431248 if value == 'must' :
12441249 must |= FIELDS_VALUE
12451250 if value == 'opt' :
@@ -1335,7 +1340,10 @@ def _validate_dictionary(index, obj):
13351340 for idx , sub in enumerate (subitems ):
13361341 try :
13371342 is_var = struct in (OD .VAR , OD .NVAR )
1338- _validate_sub (sub , idx , is_var = is_var , is_repeat = is_repeat , is_each = 'each' in obj )
1343+ _validate_sub (
1344+ sub , idx , is_var = is_var , is_repeat = is_repeat , is_each = "each" in obj ,
1345+ is_index_each = obj .get ("each" , {}).get ("start_index" , 1 ),
1346+ )
13391347 except Exception as exc :
13401348 exc_amend (exc , f"sub[{ idx } ]: " )
13411349 raise
@@ -1347,9 +1355,11 @@ def _validate_dictionary(index, obj):
13471355 if struct in (OD .VAR , OD .NVAR ):
13481356 raise ValidationError ("Unexpected 'each' use in VAR/NVAR object" )
13491357
1350- # Having 'each' requires use of only one sub item with 'name' in it
1351- if not (sum (has_name ) == 1 and has_name [0 ]):
1352- raise ValidationError ("Unexpected subitems. Subitem 0 must contain name" )
1358+ # When each is present, the other items must have a name field in them
1359+ if sum (has_name ) != sub .get ("start_index" , 1 ):
1360+ raise ValidationError (
1361+ f"Unexpected subitems. Subitems 0..{ sub ['start_index' ] - 1 } must contain name"
1362+ )
13531363
13541364 try :
13551365 _validate_sub (sub , idx = - 1 )
0 commit comments