Coverage for pyVHDLModel/Association.py: 93%

214 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-25 10:05 +0000

1# ==================================================================================================================== # 

2# __ ___ _ ____ _ __ __ _ _ # 

3# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # 

4# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # 

5# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # 

6# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # 

7# |_| |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2017-2026 Patrick Lehmann - Boetzingen, Germany # 

15# Copyright 2016-2017 Patrick Lehmann - Dresden, Germany # 

16# # 

17# Licensed under the Apache License, Version 2.0 (the "License"); # 

18# you may not use this file except in compliance with the License. # 

19# You may obtain a copy of the License at # 

20# # 

21# http://www.apache.org/licenses/LICENSE-2.0 # 

22# # 

23# Unless required by applicable law or agreed to in writing, software # 

24# distributed under the License is distributed on an "AS IS" BASIS, # 

25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

26# See the License for the specific language governing permissions and # 

27# limitations under the License. # 

28# # 

29# SPDX-License-Identifier: Apache-2.0 # 

30# ==================================================================================================================== # 

31# 

32""" 

33This module contains parts of an abstract document language model for VHDL. 

34 

35Associations are used in generic maps, port maps and parameter maps. 

36""" 

37from __future__ import annotations 

38 

39from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Union, Self 

40 

41from pyTooling.Common import getFullyQualifiedName, getTypeName 

42from pyTooling.Decorators import export, readonly 

43from pyTooling.MetaClasses import ExtendedType, abstractclass 

44 

45from pyVHDLModel.Base import ModelEntity 

46from pyVHDLModel.Exception import AlreadyInDesignError, NotAChildError, AssociationOrderError, \ 

47 DuplicateGenericAssociationError, DuplicatePortAssociationError 

48from pyVHDLModel.Name import Name, SimpleName 

49from pyVHDLModel.Symbol import Symbol 

50from pyVHDLModel.Expression import BaseExpression, QualifiedExpression, FunctionCall, TypeConversion, Literal 

51if TYPE_CHECKING: 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 from pyVHDLModel.Visitor import Visitor 

53 

54 

55ExpressionUnion = Union[ 

56 BaseExpression, 

57 QualifiedExpression, 

58 FunctionCall, 

59 TypeConversion, 

60 # ConstantOrSymbol, TODO: ObjectSymbol 

61 Literal, 

62] 

63 

64 

65@export 

66@abstractclass 

67class AssociationItem(ModelEntity): 

68 """ 

69 A base-class for all association items. 

70 

71 The association item is the parent of its formal and its actual, if they are model entities: a 

72 :class:`~pyVHDLModel.Symbol.Symbol` has no parent. 

73 

74 .. seealso:: 

75 

76 * :class:`Generic association item <pyVHDLModel.Association.GenericAssociationItem>` 

77 * :class:`Port association item <pyVHDLModel.Association.PortAssociationItem>` 

78 * :class:`Parameter association item <pyVHDLModel.Association.ParameterAssociationItem>` 

79 """ 

80 

81 _formal: Nullable[Name | Symbol] #: Reference to the formal part, or ``None`` for a positional association. 

82 _actual: ExpressionUnion #: The actual part of this association. 

83 

84 def __init__(self, formal: Nullable[Name | Symbol], actual: ExpressionUnion) -> None: 

85 """ 

86 Initializes an association item. 

87 

88 :param formal: Reference to the formal part, or ``None`` for a positional association. 

89 :param actual: The actual part of this association. 

90 """ 

91 super().__init__() 

92 

93 self._formal = formal 

94 if isinstance(formal, ModelEntity): 

95 formal.Parent = self 

96 

97 self._actual = actual 

98 if isinstance(actual, ModelEntity): 

99 actual.Parent = self 

100 

101 @readonly 

102 def Formal(self) -> Nullable[Name | Symbol]: # TODO: can also be a conversion function !! 

103 """ 

104 Read-only property to access the formal (:attr:`_formal`). 

105 

106 :returns: The formal, or ``None`` if not set. 

107 """ 

108 return self._formal 

109 

110 @readonly 

111 def Actual(self) -> ExpressionUnion: 

112 """ 

113 Read-only property to access the actual (:attr:`_actual`). 

114 

115 :returns: The actual. 

116 """ 

117 return self._actual 

118 

119 def Copy(self) -> Self: 

120 """ 

121 Create a copy of this association item. 

122 

123 The copy has no parent. Its formal and actual are copies of this item's formal and actual. 

124 

125 :returns: The new association item. 

126 """ 

127 formal = None if self._formal is None else self._formal.Copy() 

128 actual = None if self._actual is None else self._actual.Copy() 

129 return self.__class__(formal, actual) 

130 

131 def Visit(self, visitor: Visitor) -> None: 

132 """ 

133 Walk this association item, its formal and its actual with a visitor. 

134 

135 :param visitor: The visitor receiving the events. 

136 """ 

137 visitor.VisitAssociationItem(self) 

138 if self._formal is not None: 

139 self._formal.Visit(visitor) 

140 visitor.VisitArrow(self) 

141 

142 if self._actual is not None: 142 ↛ 145line 142 didn't jump to line 145 because the condition on line 142 was always true

143 self._actual.Visit(visitor) 

144 

145 visitor.DepartAssociationItem(self) 

146 

147 def __str__(self) -> str: 

148 """ 

149 Formats the association item. 

150 

151 **Format:** ``formal => actual``, or ``actual`` alone when positional 

152 

153 :returns: Formatted association item. 

154 """ 

155 if self._formal is None: 

156 return str(self._actual) 

157 else: 

158 return f"{self._formal!s} => {self._actual!s}" 

159 

160 

161@export 

162class GenericAssociationItem(AssociationItem): 

163 """ 

164 A base-class for all generic association items used in generic map aspects. 

165 """ 

166 

167 

168@export 

169class PortAssociationItem(AssociationItem): 

170 """ 

171 A base-class for all port association items used in port map aspects. 

172 """ 

173 

174 

175@export 

176class ParameterAssociationItem(AssociationItem): 

177 """ 

178 A base-class for all parameter association items used in parameter map aspects. 

179 """ 

180 

181 

182@export 

183class GenericMapAspectMixin(metaclass=ExtendedType, mixin=True): 

184 """ 

185 A mixin-class for language constructs with a generic map aspect. 

186 

187 An association declares nothing: the mutators update neither a namespace nor a lookup table of the host class, so the 

188 mixin expects nothing from its host. 

189 

190 .. seealso:: 

191 

192 * :class:`Instantiation <pyVHDLModel.Concurrent.Instantiation>` 

193 * :class:`Concurrent block statement <pyVHDLModel.Concurrent.ConcurrentBlockStatement>` 

194 * :class:`Binding indication <pyVHDLModel.Configuration.BindingIndication>` 

195 * :class:`Port map aspect <pyVHDLModel.Association.PortMapAspectMixin>` 

196 """ 

197 

198 _genericAssociationItems: list[GenericAssociationItem] #: List of all generic associations. 

199 

200 def __init__(self, genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None) -> None: 

201 """ 

202 Initializes a generic map aspect. 

203 

204 :param genericAssociationItems: List of all generic associations. 

205 """ 

206 self._genericAssociationItems = genericAssociationItemList = [] 

207 if genericAssociationItems is not None: 

208 for association in genericAssociationItems: 

209 genericAssociationItemList.append(association) 

210 association.Parent = self 

211 

212 @readonly 

213 def GenericAssociationItems(self) -> list[GenericAssociationItem]: 

214 """ 

215 Read-only property to access the generic associations (:attr:`_genericAssociationItems`). 

216 

217 :returns: List of generic association items. 

218 """ 

219 return self._genericAssociationItems 

220 

221 def _VisitGenericMapAspect(self, visitor: Visitor) -> None: 

222 """ 

223 Walk the generic map aspect, if there are generic associations, with a visitor. 

224 

225 The associations are separated by commas. 

226 

227 :param visitor: The visitor receiving the events. 

228 """ 

229 if len(self._genericAssociationItems) == 0: 

230 return 

231 

232 visitor.VisitGenericMapAspect(self) 

233 for index, item in enumerate(self._genericAssociationItems): 

234 if index > 0: 

235 visitor.VisitComma(self) 

236 

237 item.Visit(visitor) 

238 

239 visitor.DepartGenericMapAspect(self) 

240 

241 def AddGenericAssociationItem(self, genericAssociationItem: GenericAssociationItem) -> None: 

242 """ 

243 Add a generic association at the end of the generic map aspect. 

244 

245 The association's parent is set to this language construct. 

246 

247 :param genericAssociationItem: The generic association to add. 

248 :raises ValueError: If parameter 'genericAssociationItem' is ``None``. 

249 :raises TypeError: If parameter 'genericAssociationItem' is not of type 

250 :class:`~pyVHDLModel.Association.GenericAssociationItem`. 

251 :raises AlreadyInDesignError: If parameter 'genericAssociationItem' already has a parent. 

252 :raises DuplicateGenericAssociationError: If the association is named, and another named association of the generic 

253 map aspect has the same formal. 

254 :raises AssociationOrderError: If the association is positional, and the generic map aspect has a named 

255 association. 

256 

257 .. seealso:: 

258 

259 :meth:`InsertGenericAssociationItem` 

260 |rarr| Insert a generic association at a position of the generic map aspect. 

261 :meth:`RemoveGenericAssociationItem` 

262 |rarr| Remove a generic association from the generic map aspect. 

263 """ 

264 self.InsertGenericAssociationItem(len(self._genericAssociationItems), genericAssociationItem) 

265 

266 def InsertGenericAssociationItem(self, index: int, genericAssociationItem: GenericAssociationItem) -> None: 

267 """ 

268 Insert a generic association at a position of the generic map aspect. 

269 

270 The association's parent is set to this language construct. 

271 

272 A named association (``formal => actual``) can't repeat the formal of another named association of the generic map 

273 aspect. Only formals given as a simple name are compared: a partial association (``a(0) => x``) or a conversion 

274 function (``to_integer(a) => x``) is accepted. All positional associations precede the named associations. 

275 

276 :param index: Position, from ``0`` (first) to the number of generic associations 

277 (append). A negative index counts from the end, as for 

278 :meth:`list.insert`: ``-1`` inserts before the last generic association. 

279 :param genericAssociationItem: The generic association to insert. 

280 :raises ValueError: If parameter 'index' is ``None``. 

281 :raises TypeError: If parameter 'index' is not of type :class:`int`. 

282 :raises ValueError: If parameter 'index' is out of range. 

283 :raises ValueError: If parameter 'genericAssociationItem' is ``None``. 

284 :raises TypeError: If parameter 'genericAssociationItem' is not of type 

285 :class:`~pyVHDLModel.Association.GenericAssociationItem`. 

286 :raises AlreadyInDesignError: If parameter 'genericAssociationItem' already has a parent. 

287 :raises DuplicateGenericAssociationError: If the association is named, and another named association of the generic 

288 map aspect has the same formal. 

289 :raises AssociationOrderError: If the association is positional, and a named association precedes the 

290 position. 

291 :raises AssociationOrderError: If the association is named, and a positional association follows the 

292 position. 

293 

294 .. seealso:: 

295 

296 :meth:`AddGenericAssociationItem` 

297 |rarr| Add a generic association at the end of the generic map aspect. 

298 :meth:`RemoveGenericAssociationItem` 

299 |rarr| Remove a generic association from the generic map aspect. 

300 """ 

301 count = len(self._genericAssociationItems) 

302 if index is None: 

303 raise ValueError("Parameter 'index' is None.") 

304 elif not isinstance(index, int): 

305 ex = TypeError("Parameter 'index' is not of type 'int'.") 

306 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.") 

307 raise ex 

308 elif not (-count <= index <= count): 

309 ex = ValueError(f"Parameter 'index' is out of range {-count}..{count}.") 

310 ex.add_note(f"Got value '{index}'.") 

311 raise ex 

312 

313 if genericAssociationItem is None: 

314 raise ValueError("Parameter 'genericAssociationItem' is None.") 

315 elif not isinstance(genericAssociationItem, GenericAssociationItem): 

316 ex = TypeError("Parameter 'genericAssociationItem' is not of type 'GenericAssociationItem'.") 

317 ex.add_note(f"Got type '{getFullyQualifiedName(genericAssociationItem)}'.") 

318 raise ex 

319 elif genericAssociationItem._parent is not None: 

320 ex = AlreadyInDesignError( 

321 f"{getTypeName(genericAssociationItem)} already has a parent.", 

322 element=genericAssociationItem, 

323 parent=genericAssociationItem._parent 

324 ) 

325 ex.add_note(f"Remove it from its {getTypeName(genericAssociationItem._parent)} first.") 

326 raise ex 

327 

328 formal = genericAssociationItem._formal 

329 if isinstance(formal, SimpleName): 

330 normalizedFormal = formal._normalizedIdentifier 

331 for existing in self._genericAssociationItems: 

332 if isinstance(existing._formal, SimpleName) and existing._formal._normalizedIdentifier == normalizedFormal: 

333 ex = DuplicateGenericAssociationError( 

334 f"{getTypeName(self)} already associates generic '{formal._identifier}'.", 

335 identifier=formal._identifier, 

336 parent=self, 

337 element=genericAssociationItem, 

338 existing=existing 

339 ) 

340 ex.add_note(f"Existing association is '{existing!s}'.") 

341 raise ex 

342 

343 position = index if index >= 0 else count + index 

344 if formal is None: 

345 for existing in self._genericAssociationItems[:position]: 

346 if existing._formal is not None: 

347 ex = AssociationOrderError( 

348 f"Positional generic association can't follow named association '{existing!s}'.", 

349 parent=self, 

350 element=genericAssociationItem, 

351 existing=existing 

352 ) 

353 ex.add_note("Insert it before the first named association, or name its formal.") 

354 raise ex 

355 else: 

356 for existing in self._genericAssociationItems[position:]: 

357 if existing._formal is None: 

358 ex = AssociationOrderError( 

359 f"Named generic association can't precede positional association '{existing!s}'.", 

360 parent=self, 

361 element=genericAssociationItem, 

362 existing=existing 

363 ) 

364 ex.add_note("Insert it after the last positional association.") 

365 raise ex 

366 

367 self._genericAssociationItems.insert(index, genericAssociationItem) 

368 genericAssociationItem.Parent = self 

369 

370 def RemoveGenericAssociationItem(self, genericAssociationItem: GenericAssociationItem) -> None: 

371 """ 

372 Remove a generic association from the generic map aspect. 

373 

374 The association is detached: its parent is cleared. The association itself is unchanged and can be added to another 

375 language construct, or inserted at another position. 

376 

377 :param genericAssociationItem: The generic association to remove. 

378 :raises ValueError: If parameter 'genericAssociationItem' is ``None``. 

379 :raises TypeError: If parameter 'genericAssociationItem' is not of type 

380 :class:`~pyVHDLModel.Association.GenericAssociationItem`. 

381 :raises NotAChildError: If parameter 'genericAssociationItem' is not a generic association of this language 

382 construct. 

383 

384 .. seealso:: 

385 

386 :meth:`AddGenericAssociationItem` 

387 |rarr| Add a generic association at the end of the generic map aspect. 

388 :meth:`InsertGenericAssociationItem` 

389 |rarr| Insert a generic association at a position of the generic map aspect. 

390 """ 

391 if genericAssociationItem is None: 

392 raise ValueError("Parameter 'genericAssociationItem' is None.") 

393 elif not isinstance(genericAssociationItem, GenericAssociationItem): 

394 ex = TypeError("Parameter 'genericAssociationItem' is not of type 'GenericAssociationItem'.") 

395 ex.add_note(f"Got type '{getFullyQualifiedName(genericAssociationItem)}'.") 

396 raise ex 

397 

398 for index, item in enumerate(self._genericAssociationItems): 

399 if item is genericAssociationItem: 399 ↛ 398line 399 didn't jump to line 398 because the condition on line 399 was always true

400 break 

401 else: 

402 raise NotAChildError( 

403 f"{getTypeName(genericAssociationItem)} is not a generic association of this {getTypeName(self)}.", 

404 element=genericAssociationItem, 

405 parent=self 

406 ) 

407 

408 del self._genericAssociationItems[index] 

409 genericAssociationItem._parent = None 

410 

411 

412@export 

413class PortMapAspectMixin(metaclass=ExtendedType, mixin=True): 

414 """ 

415 A mixin-class for language constructs with a port map aspect. 

416 

417 An association declares nothing: the mutators update neither a namespace nor a lookup table of the host class, so the 

418 mixin expects nothing from its host. 

419 

420 .. seealso:: 

421 

422 * :class:`Instantiation <pyVHDLModel.Concurrent.Instantiation>` 

423 * :class:`Concurrent block statement <pyVHDLModel.Concurrent.ConcurrentBlockStatement>` 

424 * :class:`Binding indication <pyVHDLModel.Configuration.BindingIndication>` 

425 * :class:`Generic map aspect <pyVHDLModel.Association.GenericMapAspectMixin>` 

426 """ 

427 

428 _portAssociationItems: list[PortAssociationItem] #: List of all port associations. 

429 

430 def __init__(self, portAssociationItems: Nullable[Iterable[PortAssociationItem]] = None) -> None: 

431 """ 

432 Initializes a port map aspect. 

433 

434 :param portAssociationItems: List of all port associations. 

435 """ 

436 self._portAssociationItems = portAssociationItemList = [] 

437 if portAssociationItems is not None: 

438 for association in portAssociationItems: 

439 portAssociationItemList.append(association) 

440 association.Parent = self 

441 

442 @readonly 

443 def PortAssociationItems(self) -> list[PortAssociationItem]: 

444 """ 

445 Read-only property to access the port associations (:attr:`_portAssociationItems`). 

446 

447 :returns: List of port association items. 

448 """ 

449 return self._portAssociationItems 

450 

451 def _VisitPortMapAspect(self, visitor: Visitor) -> None: 

452 """ 

453 Walk the port map aspect, if there are port associations, with a visitor. 

454 

455 The associations are separated by commas. 

456 

457 :param visitor: The visitor receiving the events. 

458 """ 

459 if len(self._portAssociationItems) == 0: 

460 return 

461 

462 visitor.VisitPortMapAspect(self) 

463 for index, item in enumerate(self._portAssociationItems): 

464 if index > 0: 

465 visitor.VisitComma(self) 

466 

467 item.Visit(visitor) 

468 

469 visitor.DepartPortMapAspect(self) 

470 

471 def AddPortAssociationItem(self, portAssociationItem: PortAssociationItem) -> None: 

472 """ 

473 Add a port association at the end of the port map aspect. 

474 

475 The association's parent is set to this language construct. 

476 

477 :param portAssociationItem: The port association to add. 

478 :raises ValueError: If parameter 'portAssociationItem' is ``None``. 

479 :raises TypeError: If parameter 'portAssociationItem' is not of type 

480 :class:`~pyVHDLModel.Association.PortAssociationItem`. 

481 :raises AlreadyInDesignError: If parameter 'portAssociationItem' already has a parent. 

482 :raises DuplicatePortAssociationError: If the association is named, and another named association of the port map 

483 aspect has the same formal. 

484 :raises AssociationOrderError: If the association is positional, and the port map aspect has a named 

485 association. 

486 

487 .. seealso:: 

488 

489 :meth:`InsertPortAssociationItem` 

490 |rarr| Insert a port association at a position of the port map aspect. 

491 :meth:`RemovePortAssociationItem` 

492 |rarr| Remove a port association from the port map aspect. 

493 """ 

494 self.InsertPortAssociationItem(len(self._portAssociationItems), portAssociationItem) 

495 

496 def InsertPortAssociationItem(self, index: int, portAssociationItem: PortAssociationItem) -> None: 

497 """ 

498 Insert a port association at a position of the port map aspect. 

499 

500 The association's parent is set to this language construct. 

501 

502 A named association (``formal => actual``) can't repeat the formal of another named association of the port map 

503 aspect. Only formals given as a simple name are compared: a partial association (``a(0) => x``) or a conversion 

504 function (``to_integer(a) => x``) is accepted. All positional associations precede the named associations. 

505 

506 :param index: Position, from ``0`` (first) to the number of port associations (append). A 

507 negative index counts from the end, as for :meth:`list.insert`: ``-1`` 

508 inserts before the last port association. 

509 :param portAssociationItem: The port association to insert. 

510 :raises ValueError: If parameter 'index' is ``None``. 

511 :raises TypeError: If parameter 'index' is not of type :class:`int`. 

512 :raises ValueError: If parameter 'index' is out of range. 

513 :raises ValueError: If parameter 'portAssociationItem' is ``None``. 

514 :raises TypeError: If parameter 'portAssociationItem' is not of type 

515 :class:`~pyVHDLModel.Association.PortAssociationItem`. 

516 :raises AlreadyInDesignError: If parameter 'portAssociationItem' already has a parent. 

517 :raises DuplicatePortAssociationError: If the association is named, and another named association of the port map 

518 aspect has the same formal. 

519 :raises AssociationOrderError: If the association is positional, and a named association precedes the 

520 position. 

521 :raises AssociationOrderError: If the association is named, and a positional association follows the 

522 position. 

523 

524 .. seealso:: 

525 

526 :meth:`AddPortAssociationItem` 

527 |rarr| Add a port association at the end of the port map aspect. 

528 :meth:`RemovePortAssociationItem` 

529 |rarr| Remove a port association from the port map aspect. 

530 """ 

531 count = len(self._portAssociationItems) 

532 if index is None: 532 ↛ 533line 532 didn't jump to line 533 because the condition on line 532 was never true

533 raise ValueError("Parameter 'index' is None.") 

534 elif not isinstance(index, int): 534 ↛ 535line 534 didn't jump to line 535 because the condition on line 534 was never true

535 ex = TypeError("Parameter 'index' is not of type 'int'.") 

536 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.") 

537 raise ex 

538 elif not (-count <= index <= count): 538 ↛ 539line 538 didn't jump to line 539 because the condition on line 538 was never true

539 ex = ValueError(f"Parameter 'index' is out of range {-count}..{count}.") 

540 ex.add_note(f"Got value '{index}'.") 

541 raise ex 

542 

543 if portAssociationItem is None: 

544 raise ValueError("Parameter 'portAssociationItem' is None.") 

545 elif not isinstance(portAssociationItem, PortAssociationItem): 

546 ex = TypeError("Parameter 'portAssociationItem' is not of type 'PortAssociationItem'.") 

547 ex.add_note(f"Got type '{getFullyQualifiedName(portAssociationItem)}'.") 

548 raise ex 

549 elif portAssociationItem._parent is not None: 

550 ex = AlreadyInDesignError( 

551 f"{getTypeName(portAssociationItem)} already has a parent.", 

552 element=portAssociationItem, 

553 parent=portAssociationItem._parent 

554 ) 

555 ex.add_note(f"Remove it from its {getTypeName(portAssociationItem._parent)} first.") 

556 raise ex 

557 

558 formal = portAssociationItem._formal 

559 if isinstance(formal, SimpleName): 

560 normalizedFormal = formal._normalizedIdentifier 

561 for existing in self._portAssociationItems: 

562 if isinstance(existing._formal, SimpleName) and existing._formal._normalizedIdentifier == normalizedFormal: 

563 ex = DuplicatePortAssociationError( 

564 f"{getTypeName(self)} already associates port '{formal._identifier}'.", 

565 identifier=formal._identifier, 

566 parent=self, 

567 element=portAssociationItem, 

568 existing=existing 

569 ) 

570 ex.add_note(f"Existing association is '{existing!s}'.") 

571 raise ex 

572 

573 position = index if index >= 0 else count + index 

574 if formal is None: 

575 for existing in self._portAssociationItems[:position]: 

576 if existing._formal is not None: 576 ↛ 575line 576 didn't jump to line 575 because the condition on line 576 was always true

577 ex = AssociationOrderError( 

578 f"Positional port association can't follow named association '{existing!s}'.", 

579 parent=self, 

580 element=portAssociationItem, 

581 existing=existing 

582 ) 

583 ex.add_note("Insert it before the first named association, or name its formal.") 

584 raise ex 

585 else: 

586 for existing in self._portAssociationItems[position:]: 

587 if existing._formal is None: 

588 ex = AssociationOrderError( 

589 f"Named port association can't precede positional association '{existing!s}'.", 

590 parent=self, 

591 element=portAssociationItem, 

592 existing=existing 

593 ) 

594 ex.add_note("Insert it after the last positional association.") 

595 raise ex 

596 

597 self._portAssociationItems.insert(index, portAssociationItem) 

598 portAssociationItem.Parent = self 

599 

600 def RemovePortAssociationItem(self, portAssociationItem: PortAssociationItem) -> None: 

601 """ 

602 Remove a port association from the port map aspect. 

603 

604 The association is detached: its parent is cleared. The association itself is unchanged and can be added to another 

605 language construct, or inserted at another position. 

606 

607 :param portAssociationItem: The port association to remove. 

608 :raises ValueError: If parameter 'portAssociationItem' is ``None``. 

609 :raises TypeError: If parameter 'portAssociationItem' is not of type 

610 :class:`~pyVHDLModel.Association.PortAssociationItem`. 

611 :raises NotAChildError: If parameter 'portAssociationItem' is not a port association of this language construct. 

612 

613 .. seealso:: 

614 

615 :meth:`AddPortAssociationItem` 

616 |rarr| Add a port association at the end of the port map aspect. 

617 :meth:`InsertPortAssociationItem` 

618 |rarr| Insert a port association at a position of the port map aspect. 

619 """ 

620 if portAssociationItem is None: 620 ↛ 621line 620 didn't jump to line 621 because the condition on line 620 was never true

621 raise ValueError("Parameter 'portAssociationItem' is None.") 

622 elif not isinstance(portAssociationItem, PortAssociationItem): 622 ↛ 623line 622 didn't jump to line 623 because the condition on line 622 was never true

623 ex = TypeError("Parameter 'portAssociationItem' is not of type 'PortAssociationItem'.") 

624 ex.add_note(f"Got type '{getFullyQualifiedName(portAssociationItem)}'.") 

625 raise ex 

626 

627 for index, item in enumerate(self._portAssociationItems): 

628 if item is portAssociationItem: 628 ↛ 627line 628 didn't jump to line 627 because the condition on line 628 was always true

629 break 

630 else: 

631 raise NotAChildError( 

632 f"{getTypeName(portAssociationItem)} is not a port association of this {getTypeName(self)}.", 

633 element=portAssociationItem, 

634 parent=self 

635 ) 

636 

637 del self._portAssociationItems[index] 

638 portAssociationItem._parent = None