sysmlpy 0.41.0__tar.gz → 0.42.0__tar.gz

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.
Files changed (148) hide show
  1. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/PKG-INFO +3 -1
  2. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/README.md +2 -0
  3. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/pyproject.toml +1 -1
  4. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/__init__.py +1 -1
  5. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr_visitor.py +135 -149
  6. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/classes.py +26 -17
  7. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/semantic.py +42 -9
  8. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/usage.py +19 -47
  9. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/LICENSE +0 -0
  10. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/__main__.py +0 -0
  11. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/README.md +0 -0
  12. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2Lexer.interp +0 -0
  13. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2Lexer.py +0 -0
  14. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2Lexer.tokens +0 -0
  15. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2Parser.interp +0 -0
  16. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2Parser.py +0 -0
  17. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2Parser.tokens +0 -0
  18. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2ParserListener.py +0 -0
  19. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/SysMLv2ParserVisitor.py +0 -0
  20. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
  21. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
  22. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
  23. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/antlr_parser.py +0 -0
  24. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/boxes_view.py +0 -0
  25. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/definition.py +0 -0
  26. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/examples/attribute_values.py +0 -0
  27. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/examples/part_attributes.py +0 -0
  28. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/examples/tuples_sequences.py +0 -0
  29. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/formatting.py +0 -0
  30. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/README.md +0 -0
  31. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.g4 +0 -0
  32. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
  33. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
  34. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
  35. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.g4 +0 -0
  36. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.interp +0 -0
  37. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.py +0 -0
  38. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.tokens +0 -0
  39. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserListener.py +0 -0
  40. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserVisitor.py +0 -0
  41. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/desc.xml +0 -0
  42. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/examples/camera.sysml +0 -0
  43. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/examples/toaster-system.sysml +0 -0
  44. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/examples/vehicle-model.sysml +0 -0
  45. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/grammar/antlr4/pom.xml +0 -0
  46. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/LICENSE +0 -0
  47. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/README.md +0 -0
  48. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/__init__.py +0 -0
  49. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Analysis/AnalysisTooling.sysml +0 -0
  50. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Analysis/SampledFunctions.sysml +0 -0
  51. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Analysis/StateSpaceRepresentation.sysml +0 -0
  52. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Analysis/TradeStudies.sysml +0 -0
  53. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Cause and Effect/CausationConnections.sysml +0 -0
  54. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Cause and Effect/CauseAndEffect.sysml +0 -0
  55. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Geometry/ShapeItems.sysml +0 -0
  56. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Geometry/SpatialItems.sysml +0 -0
  57. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Metadata/ImageMetadata.sysml +0 -0
  58. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Metadata/ModelingMetadata.sysml +0 -0
  59. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Metadata/ParametersOfInterestMetadata.sysml +0 -0
  60. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Metadata/RiskMetadata.sysml +0 -0
  61. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQ.sysml +0 -0
  62. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAcoustics.sysml +0 -0
  63. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAtomicNuclear.sysml +0 -0
  64. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQBase.sysml +0 -0
  65. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCharacteristicNumbers.sysml +0 -0
  66. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQChemistryMolecular.sysml +0 -0
  67. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCondensedMatter.sysml +0 -0
  68. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQElectromagnetism.sysml +0 -0
  69. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQInformation.sysml +0 -0
  70. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQLight.sysml +0 -0
  71. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQMechanics.sysml +0 -0
  72. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQSpaceTime.sysml +0 -0
  73. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/ISQThermodynamics.sysml +0 -0
  74. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementRefCalculations.sysml +0 -0
  75. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementReferences.sysml +0 -0
  76. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/Quantities.sysml +0 -0
  77. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/QuantityCalculations.sysml +0 -0
  78. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/SI.sysml +0 -0
  79. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/SIPrefixes.sysml +0 -0
  80. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/TensorCalculations.sysml +0 -0
  81. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/Time.sysml +0 -0
  82. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/USCustomaryUnits.sysml +0 -0
  83. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Quantities and Units/VectorCalculations.sysml +0 -0
  84. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Requirement Derivation/DerivationConnections.sysml +0 -0
  85. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/domain/Requirement Derivation/RequirementDerivation.sysml +0 -0
  86. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Base.kerml +0 -0
  87. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/BaseFunctions.kerml +0 -0
  88. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/BooleanFunctions.kerml +0 -0
  89. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Clocks.kerml +0 -0
  90. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/CollectionFunctions.kerml +0 -0
  91. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Collections.kerml +0 -0
  92. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/ComplexFunctions.kerml +0 -0
  93. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/ControlFunctions.kerml +0 -0
  94. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/ControlPerformances.kerml +0 -0
  95. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/DataFunctions.kerml +0 -0
  96. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/FeatureReferencingPerformances.kerml +0 -0
  97. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/IntegerFunctions.kerml +0 -0
  98. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/KerML.kerml +0 -0
  99. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Links.kerml +0 -0
  100. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Metaobjects.kerml +0 -0
  101. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/NaturalFunctions.kerml +0 -0
  102. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/NumericalFunctions.kerml +0 -0
  103. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Objects.kerml +0 -0
  104. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Observation.kerml +0 -0
  105. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/OccurrenceFunctions.kerml +0 -0
  106. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Occurrences.kerml +0 -0
  107. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Performances.kerml +0 -0
  108. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/RationalFunctions.kerml +0 -0
  109. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/RealFunctions.kerml +0 -0
  110. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/ScalarFunctions.kerml +0 -0
  111. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/ScalarValues.kerml +0 -0
  112. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/SequenceFunctions.kerml +0 -0
  113. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/SpatialFrames.kerml +0 -0
  114. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/StatePerformances.kerml +0 -0
  115. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/StringFunctions.kerml +0 -0
  116. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Transfers.kerml +0 -0
  117. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/TransitionPerformances.kerml +0 -0
  118. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/TrigFunctions.kerml +0 -0
  119. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/Triggers.kerml +0 -0
  120. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/VectorFunctions.kerml +0 -0
  121. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/kernel/VectorValues.kerml +0 -0
  122. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Actions.sysml +0 -0
  123. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Allocations.sysml +0 -0
  124. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/AnalysisCases.sysml +0 -0
  125. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Attributes.sysml +0 -0
  126. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Calculations.sysml +0 -0
  127. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Cases.sysml +0 -0
  128. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Connections.sysml +0 -0
  129. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Constraints.sysml +0 -0
  130. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Flows.sysml +0 -0
  131. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Interfaces.sysml +0 -0
  132. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Items.sysml +0 -0
  133. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Metadata.sysml +0 -0
  134. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Parts.sysml +0 -0
  135. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Ports.sysml +0 -0
  136. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Requirements.sysml +0 -0
  137. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/StandardViewDefinitions.sysml +0 -0
  138. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/States.sysml +0 -0
  139. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/SysML.sysml +0 -0
  140. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/UseCases.sysml +0 -0
  141. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/VerificationCases.sysml +0 -0
  142. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/library/systems/Views.sysml +0 -0
  143. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/navigate.py +0 -0
  144. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/plantuml.py +0 -0
  145. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/project.py +0 -0
  146. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/store.py +0 -0
  147. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/us_customary_units.txt +0 -0
  148. {sysmlpy-0.41.0 → sysmlpy-0.42.0}/src/sysmlpy/validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sysmlpy
3
- Version: 0.41.0
3
+ Version: 0.42.0
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: Jon R. Fox (mycr0ft)
@@ -39,6 +39,8 @@ The project had diverged so much from sysml2py that a new name, sysmlpy, was sel
39
39
 
40
40
  ![Lines of Code Over Time](loc_history.svg)
41
41
 
42
+ **v0.42.0:** Two deep fixes in the specialization layer. (1) `satisfy R : T by p;` and `assert someConstraint : CType;` crashed with `NameError` — the makers called a helper that was never defined; all three sites now use `_build_full_specialization_from_fsp`. (2) Chained re-declarations (`attribute :> base.x;`) dropped every segment after the first, at both visitor and grammar layers — new visitor helpers emit one Owned* dict per source chain (head + `OwnedFeatureChain`), grammar classes keep both parts, `redefined_name` resolves to the leaf segment, and the semantic analyzer now reports/resolves full dotted names (`Undefined symbol 'missing.chain'`). 14 tests in `tests/redefined_name_test.py`.
43
+
42
44
  **v0.41.0:** Full-specialization capture across all usage kinds — v0.40.0 fixed `attributeUsage` only; actions, calculations, constraints, requirements, use cases, interfaces and nested occurrences still dropped `:>`, `:>>`, `::>` specializations. New `_full_specialization_for_ctx` helper walks any usage-like context to its featureSpecializationPart; twelve visitor sites now use it (typed-by kept as fallback). `Action.load_from_grammar` captures all four kinds and the hand-rolled dumps on Action/Interface/UseCase/Requirement render them (`action a2 ::> RefType;`). `Usage.redefined_name` now works on deeper declaration nestings too. 10 tests in `tests/redefined_name_test.py`.
43
45
 
44
46
  **v0.40.0:** `References` (`::>` / `references` keyword) implemented end-to-end — previously the grammar-side `FeatureSpecialization` had a silent "not yet implemented" stub and the visitor never emitted dicts for it, so `ref attribute ::> X;` lost its specialization on round-trip (dump emitted `attribute ;`). New `References` grammar class mirrors `Redefinitions`; visitor branches in both full-specialization builders now cover the operator and keyword forms. The top-level `attributeUsage` path was the only one still using the typed-by-only helper; it now uses `_build_full_specialization_from_ctx` so all four kinds (Typings, Subsettings, Redefinitions, References) round-trip. `redefined_name` / `display_name` already handled this case. New `test_attribute_ref_references_operator_roundtrip` in `tests/grammar_test.py`; two new cases in `tests/redefined_name_test.py`. 97/97 grammar round-trip tests pass.
@@ -14,6 +14,8 @@ The project had diverged so much from sysml2py that a new name, sysmlpy, was sel
14
14
 
15
15
  ![Lines of Code Over Time](loc_history.svg)
16
16
 
17
+ **v0.42.0:** Two deep fixes in the specialization layer. (1) `satisfy R : T by p;` and `assert someConstraint : CType;` crashed with `NameError` — the makers called a helper that was never defined; all three sites now use `_build_full_specialization_from_fsp`. (2) Chained re-declarations (`attribute :> base.x;`) dropped every segment after the first, at both visitor and grammar layers — new visitor helpers emit one Owned* dict per source chain (head + `OwnedFeatureChain`), grammar classes keep both parts, `redefined_name` resolves to the leaf segment, and the semantic analyzer now reports/resolves full dotted names (`Undefined symbol 'missing.chain'`). 14 tests in `tests/redefined_name_test.py`.
18
+
17
19
  **v0.41.0:** Full-specialization capture across all usage kinds — v0.40.0 fixed `attributeUsage` only; actions, calculations, constraints, requirements, use cases, interfaces and nested occurrences still dropped `:>`, `:>>`, `::>` specializations. New `_full_specialization_for_ctx` helper walks any usage-like context to its featureSpecializationPart; twelve visitor sites now use it (typed-by kept as fallback). `Action.load_from_grammar` captures all four kinds and the hand-rolled dumps on Action/Interface/UseCase/Requirement render them (`action a2 ::> RefType;`). `Usage.redefined_name` now works on deeper declaration nestings too. 10 tests in `tests/redefined_name_test.py`.
18
20
 
19
21
  **v0.40.0:** `References` (`::>` / `references` keyword) implemented end-to-end — previously the grammar-side `FeatureSpecialization` had a silent "not yet implemented" stub and the visitor never emitted dicts for it, so `ref attribute ::> X;` lost its specialization on round-trip (dump emitted `attribute ;`). New `References` grammar class mirrors `Redefinitions`; visitor branches in both full-specialization builders now cover the operator and keyword forms. The top-level `attributeUsage` path was the only one still using the typed-by-only helper; it now uses `_build_full_specialization_from_ctx` so all four kinds (Typings, Subsettings, Redefinitions, References) round-trip. `redefined_name` / `display_name` already handled this case. New `test_attribute_ref_references_operator_roundtrip` in `tests/grammar_test.py`; two new cases in `tests/redefined_name_test.py`. 97/97 grammar round-trip tests pass.
@@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"
6
6
 
7
7
  [project]
8
8
  name = "sysmlpy"
9
- version = "0.41.0"
9
+ version = "0.42.0"
10
10
  description = ""
11
11
  authors = [{ name = "Jon R. Fox (mycr0ft)", email = "jon.fox@drfox.com" }]
12
12
  readme = "README.md"
@@ -25,7 +25,7 @@ __all__ = [
25
25
  "render_state_transition_view_svg", "boxes_view",
26
26
  ]
27
27
  __author__ = "Jon Fox"
28
- __version__ = "0.41.0"
28
+ __version__ = "0.42.0"
29
29
 
30
30
  from sysmlpy.usage import (
31
31
  Item, Attribute, Part, Port, Action, Reference, UseCase, Requirement, Interface, Message,
@@ -2212,7 +2212,7 @@ def _make_assert_constraint_usage_dict(acu_ctx, prefix=None):
2212
2212
 
2213
2213
  if hasattr(acu_ctx, 'featureSpecializationPart') and acu_ctx.featureSpecializationPart():
2214
2214
  fsp_ctx = acu_ctx.featureSpecializationPart()
2215
- fsp = _build_feature_specialization_part(fsp_ctx)
2215
+ fsp = _build_full_specialization_from_fsp(fsp_ctx)
2216
2216
 
2217
2217
  declaration = None
2218
2218
  if hasattr(acu_ctx, 'constraintUsageDeclaration') and acu_ctx.constraintUsageDeclaration():
@@ -2315,7 +2315,7 @@ def _make_satisfy_requirement_usage_dict(sru_ctx, prefix=None):
2315
2315
 
2316
2316
  if hasattr(sru_ctx, 'featureSpecializationPart') and sru_ctx.featureSpecializationPart():
2317
2317
  fsp_ctx = sru_ctx.featureSpecializationPart()
2318
- fsp = _build_feature_specialization_part(fsp_ctx)
2318
+ fsp = _build_full_specialization_from_fsp(fsp_ctx)
2319
2319
  elif hasattr(sru_ctx, 'REQUIREMENT') and sru_ctx.REQUIREMENT():
2320
2320
  if hasattr(sru_ctx, 'usageDeclaration') and sru_ctx.usageDeclaration():
2321
2321
  ud = sru_ctx.usageDeclaration()
@@ -9111,7 +9111,7 @@ def _visit_requirement_constraint_usage(rcu_ctx):
9111
9111
  fs = []
9112
9112
  if hasattr(rcu_ctx, 'featureSpecializationPart') and rcu_ctx.featureSpecializationPart():
9113
9113
  fsp_ctx = rcu_ctx.featureSpecializationPart()
9114
- fsp = _build_feature_specialization_part(fsp_ctx)
9114
+ fsp = _build_full_specialization_from_fsp(fsp_ctx)
9115
9115
  if fsp and "specialization" in fsp:
9116
9116
  fs = fsp["specialization"]
9117
9117
 
@@ -11561,6 +11561,68 @@ def _get_action_usage_subsetted_by(ctx):
11561
11561
  return None
11562
11562
 
11563
11563
 
11564
+ def _dotted_segments(owned_ctx):
11565
+ """Segments of an ownedSubsetting / ownedRedefinition /
11566
+ ownedReferenceSubsetting context.
11567
+
11568
+ Grammar: qualifiedName (DOT qualifiedName)* — e.g. ``base.x`` yields
11569
+ ``['base', 'x']``. Each segment may itself be '::'-qualified.
11570
+ """
11571
+ segs = []
11572
+ if owned_ctx is None:
11573
+ return segs
11574
+ for c2 in owned_ctx.children:
11575
+ if type(c2).__name__ == 'QualifiedNameContext':
11576
+ segs.append(c2.getText())
11577
+ return segs
11578
+
11579
+
11580
+ def _emit_owned_chain(dict_name, feature_key, segs):
11581
+ """Build an Owned{Subsetting,Redefinition,ReferenceSubsetting} dict
11582
+ from dotted segments: first segment becomes the direct feature, the
11583
+ remainder become an OwnedFeatureChain so chained forms like
11584
+ ``:> base.x`` survive round-trip."""
11585
+ head = {
11586
+ "name": "QualifiedName",
11587
+ "names": segs[0].split("::"),
11588
+ }
11589
+ owned_related = []
11590
+ if len(segs) > 1:
11591
+ chain_elements = [
11592
+ {
11593
+ "name": "OwnedFeatureChaining",
11594
+ "chainingFeature": {"name": "QualifiedName", "names": s.split("::")},
11595
+ }
11596
+ for s in segs[1:]
11597
+ ]
11598
+ owned_related.append({
11599
+ "name": "OwnedFeatureChain",
11600
+ "feature": {
11601
+ "name": "FeatureChain",
11602
+ "ownedRelationship": chain_elements,
11603
+ },
11604
+ })
11605
+ return {
11606
+ "name": dict_name,
11607
+ feature_key: head,
11608
+ "ownedRelatedElement": owned_related,
11609
+ }
11610
+
11611
+
11612
+ def _collect_owned_contexts(wrapper_children, direct_type, wrapper_types):
11613
+ """Yield each Owned*Context found under a Subsettings/Redefinitions/
11614
+ References rule context. Handles both direct children and one of the
11615
+ keyword-wrapper contexts (SubsetsContext, RedefinesContext, …)."""
11616
+ for child in wrapper_children:
11617
+ child_name = type(child).__name__
11618
+ if child_name == direct_type:
11619
+ yield child
11620
+ elif child_name in wrapper_types:
11621
+ for c3 in child.children:
11622
+ if type(c3).__name__ == direct_type:
11623
+ yield c3
11624
+
11625
+
11564
11626
  def _full_specialization_for_ctx(ctx):
11565
11627
  """Best-effort full specialization for any usage-like context.
11566
11628
 
@@ -11848,30 +11910,19 @@ def _build_full_specialization_from_ctx(ctx):
11848
11910
  # redef_ctx can be a single RedefinitionsContext or a list
11849
11911
  if not isinstance(redef_ctx, list):
11850
11912
  redef_ctx = [redef_ctx]
11851
- redef_names = []
11913
+ owned = []
11852
11914
  for rc in redef_ctx:
11853
- # RedefinitionsContext → [RedefinesContext, ...] → [OwnedRedefinitionContext, ...] → QualifiedNameContext
11854
- for child in rc.children:
11855
- child_name = type(child).__name__
11856
- if child_name == 'OwnedRedefinitionContext':
11857
- for c2 in child.children:
11858
- if type(c2).__name__ == 'QualifiedNameContext':
11859
- redef_names.append(c2.getText())
11860
- elif child_name == 'RedefinesContext':
11861
- for c2 in child.children:
11862
- if type(c2).__name__ == 'OwnedRedefinitionContext':
11863
- for c3 in c2.children:
11864
- if type(c3).__name__ == 'QualifiedNameContext':
11865
- redef_names.append(c3.getText())
11866
- if redef_names:
11867
- owned = [
11868
- {
11869
- "name": "OwnedRedefinition",
11870
- "redefinedFeature": {"name": "QualifiedName", "names": n.split("::")},
11871
- "ownedRelatedElement": []
11872
- }
11873
- for n in redef_names
11874
- ]
11915
+ # OwnedRedefinitionContext may be a dotted chain
11916
+ # (qualifiedName (DOT qualifiedName)*).
11917
+ for or_ctx in _collect_owned_contexts(
11918
+ rc.children, 'OwnedRedefinitionContext',
11919
+ ('RedefinesContext',),
11920
+ ):
11921
+ segs = _dotted_segments(or_ctx)
11922
+ if segs:
11923
+ owned.append(_emit_owned_chain(
11924
+ 'OwnedRedefinition', 'redefinedFeature', segs))
11925
+ if owned:
11875
11926
  specialization_list.append({
11876
11927
  "name": "FeatureSpecialization",
11877
11928
  "ownedRelationship": {
@@ -11886,36 +11937,19 @@ def _build_full_specialization_from_ctx(ctx):
11886
11937
  # sub_ctx can be a single SubsettingsContext or a list
11887
11938
  if not isinstance(sub_ctx, list):
11888
11939
  sub_ctx = [sub_ctx]
11889
- sub_names = []
11940
+ owned = []
11890
11941
  for sc in sub_ctx:
11891
- # SubsettingsContext → [SubsetsContext, ...] → [OwnedSubsettingContext] → QualifiedNameContext
11892
- for child in sc.children:
11893
- child_name = type(child).__name__
11894
- if child_name == 'OwnedSubsettingContext':
11895
- for c2 in child.children:
11896
- if type(c2).__name__ == 'QualifiedNameContext':
11897
- sub_names.append(c2.getText())
11898
- elif child_name == 'SubsetsContext':
11899
- for c2 in child.children:
11900
- if type(c2).__name__ == 'OwnedSubsettingContext':
11901
- for c3 in c2.children:
11902
- if type(c3).__name__ == 'QualifiedNameContext':
11903
- sub_names.append(c3.getText())
11904
- elif child_name == 'SpecializesContext':
11905
- for c2 in child.children:
11906
- if type(c2).__name__ == 'OwnedSubsettingContext':
11907
- for c3 in c2.children:
11908
- if type(c3).__name__ == 'QualifiedNameContext':
11909
- sub_names.append(c3.getText())
11910
- if sub_names:
11911
- owned = [
11912
- {
11913
- "name": "OwnedSubsetting",
11914
- "subsettedFeature": {"name": "QualifiedName", "names": n.split("::")},
11915
- "ownedRelatedElement": []
11916
- }
11917
- for n in sub_names
11918
- ]
11942
+ # Each Owned*Context may be a dotted chain
11943
+ # (qualifiedName (DOT qualifiedName)*).
11944
+ for os_ctx in _collect_owned_contexts(
11945
+ sc.children, 'OwnedSubsettingContext',
11946
+ ('SubsetsContext', 'SpecializesContext'),
11947
+ ):
11948
+ segs = _dotted_segments(os_ctx)
11949
+ if segs:
11950
+ owned.append(_emit_owned_chain(
11951
+ 'OwnedSubsetting', 'subsettedFeature', segs))
11952
+ if owned:
11919
11953
  specialization_list.append({
11920
11954
  "name": "FeatureSpecialization",
11921
11955
  "ownedRelationship": {
@@ -11929,29 +11963,18 @@ def _build_full_specialization_from_ctx(ctx):
11929
11963
  ref_ctx = spec.references()
11930
11964
  if not isinstance(ref_ctx, list):
11931
11965
  ref_ctx = [ref_ctx]
11932
- ref_names = []
11966
+ owned = []
11933
11967
  for rc in ref_ctx:
11934
- for child in rc.children:
11935
- child_name = type(child).__name__
11936
- if child_name == 'OwnedReferenceSubsettingContext':
11937
- for c2 in child.children:
11938
- if type(c2).__name__ == 'QualifiedNameContext':
11939
- ref_names.append(c2.getText())
11940
- elif child_name == 'ReferencesContext':
11941
- for c2 in child.children:
11942
- if type(c2).__name__ == 'OwnedReferenceSubsettingContext':
11943
- for c3 in c2.children:
11944
- if type(c3).__name__ == 'QualifiedNameContext':
11945
- ref_names.append(c3.getText())
11946
- if ref_names:
11947
- owned = [
11948
- {
11949
- "name": "OwnedReferenceSubsetting",
11950
- "referencedFeature": {"name": "QualifiedName", "names": n.split("::")},
11951
- "ownedRelatedElement": []
11952
- }
11953
- for n in ref_names
11954
- ]
11968
+ # OwnedReferenceSubsettingContext may be a dotted chain.
11969
+ for ors_ctx in _collect_owned_contexts(
11970
+ rc.children, 'OwnedReferenceSubsettingContext',
11971
+ ('ReferencesContext',),
11972
+ ):
11973
+ segs = _dotted_segments(ors_ctx)
11974
+ if segs:
11975
+ owned.append(_emit_owned_chain(
11976
+ 'OwnedReferenceSubsetting', 'referencedFeature', segs))
11977
+ if owned:
11955
11978
  specialization_list.append({
11956
11979
  "name": "FeatureSpecialization",
11957
11980
  "ownedRelationship": {
@@ -12242,29 +12265,19 @@ def _build_full_specialization_from_fsp(fsp):
12242
12265
  redef_ctx = spec.redefinitions()
12243
12266
  if not isinstance(redef_ctx, list):
12244
12267
  redef_ctx = [redef_ctx]
12245
- redef_names = []
12268
+ owned = []
12246
12269
  for rc in redef_ctx:
12247
- for child in rc.children:
12248
- child_name = type(child).__name__
12249
- if child_name == 'OwnedRedefinitionContext':
12250
- for c2 in child.children:
12251
- if type(c2).__name__ == 'QualifiedNameContext':
12252
- redef_names.append(c2.getText())
12253
- elif child_name == 'RedefinesContext':
12254
- for c2 in child.children:
12255
- if type(c2).__name__ == 'OwnedRedefinitionContext':
12256
- for c3 in c2.children:
12257
- if type(c3).__name__ == 'QualifiedNameContext':
12258
- redef_names.append(c3.getText())
12259
- if redef_names:
12260
- owned = [
12261
- {
12262
- "name": "OwnedRedefinition",
12263
- "redefinedFeature": {"name": "QualifiedName", "names": n.split("::")},
12264
- "ownedRelatedElement": []
12265
- }
12266
- for n in redef_names
12267
- ]
12270
+ # OwnedRedefinitionContext may be a dotted chain
12271
+ # (qualifiedName (DOT qualifiedName)*).
12272
+ for or_ctx in _collect_owned_contexts(
12273
+ rc.children, 'OwnedRedefinitionContext',
12274
+ ('RedefinesContext',),
12275
+ ):
12276
+ segs = _dotted_segments(or_ctx)
12277
+ if segs:
12278
+ owned.append(_emit_owned_chain(
12279
+ 'OwnedRedefinition', 'redefinedFeature', segs))
12280
+ if owned:
12268
12281
  specialization_list.append({
12269
12282
  "name": "FeatureSpecialization",
12270
12283
  "ownedRelationship": {
@@ -12278,35 +12291,19 @@ def _build_full_specialization_from_fsp(fsp):
12278
12291
  sub_ctx = spec.subsettings()
12279
12292
  if not isinstance(sub_ctx, list):
12280
12293
  sub_ctx = [sub_ctx]
12281
- sub_names = []
12294
+ owned = []
12282
12295
  for sc in sub_ctx:
12283
- for child in sc.children:
12284
- child_name = type(child).__name__
12285
- if child_name == 'OwnedSubsettingContext':
12286
- for c2 in child.children:
12287
- if type(c2).__name__ == 'QualifiedNameContext':
12288
- sub_names.append(c2.getText())
12289
- elif child_name == 'SubsetsContext':
12290
- for c2 in child.children:
12291
- if type(c2).__name__ == 'OwnedSubsettingContext':
12292
- for c3 in c2.children:
12293
- if type(c3).__name__ == 'QualifiedNameContext':
12294
- sub_names.append(c3.getText())
12295
- elif child_name == 'SpecializesContext':
12296
- for c2 in child.children:
12297
- if type(c2).__name__ == 'OwnedSubsettingContext':
12298
- for c3 in c2.children:
12299
- if type(c3).__name__ == 'QualifiedNameContext':
12300
- sub_names.append(c3.getText())
12301
- if sub_names:
12302
- owned = [
12303
- {
12304
- "name": "OwnedSubsetting",
12305
- "subsettedFeature": {"name": "QualifiedName", "names": n.split("::")},
12306
- "ownedRelatedElement": []
12307
- }
12308
- for n in sub_names
12309
- ]
12296
+ # Each Owned*Context may be a dotted chain
12297
+ # (qualifiedName (DOT qualifiedName)*).
12298
+ for os_ctx in _collect_owned_contexts(
12299
+ sc.children, 'OwnedSubsettingContext',
12300
+ ('SubsetsContext', 'SpecializesContext'),
12301
+ ):
12302
+ segs = _dotted_segments(os_ctx)
12303
+ if segs:
12304
+ owned.append(_emit_owned_chain(
12305
+ 'OwnedSubsetting', 'subsettedFeature', segs))
12306
+ if owned:
12310
12307
  specialization_list.append({
12311
12308
  "name": "FeatureSpecialization",
12312
12309
  "ownedRelationship": {
@@ -12320,29 +12317,18 @@ def _build_full_specialization_from_fsp(fsp):
12320
12317
  ref_ctx = spec.references()
12321
12318
  if not isinstance(ref_ctx, list):
12322
12319
  ref_ctx = [ref_ctx]
12323
- ref_names = []
12320
+ owned = []
12324
12321
  for rc in ref_ctx:
12325
- for child in rc.children:
12326
- child_name = type(child).__name__
12327
- if child_name == 'OwnedReferenceSubsettingContext':
12328
- for c2 in child.children:
12329
- if type(c2).__name__ == 'QualifiedNameContext':
12330
- ref_names.append(c2.getText())
12331
- elif child_name == 'ReferencesContext':
12332
- for c2 in child.children:
12333
- if type(c2).__name__ == 'OwnedReferenceSubsettingContext':
12334
- for c3 in c2.children:
12335
- if type(c3).__name__ == 'QualifiedNameContext':
12336
- ref_names.append(c3.getText())
12337
- if ref_names:
12338
- owned = [
12339
- {
12340
- "name": "OwnedReferenceSubsetting",
12341
- "referencedFeature": {"name": "QualifiedName", "names": n.split("::")},
12342
- "ownedRelatedElement": []
12343
- }
12344
- for n in ref_names
12345
- ]
12322
+ # OwnedReferenceSubsettingContext may be a dotted chain.
12323
+ for ors_ctx in _collect_owned_contexts(
12324
+ rc.children, 'OwnedReferenceSubsettingContext',
12325
+ ('ReferencesContext',),
12326
+ ):
12327
+ segs = _dotted_segments(ors_ctx)
12328
+ if segs:
12329
+ owned.append(_emit_owned_chain(
12330
+ 'OwnedReferenceSubsetting', 'referencedFeature', segs))
12331
+ if owned:
12346
12332
  specialization_list.append({
12347
12333
  "name": "FeatureSpecialization",
12348
12334
  "ownedRelationship": {
@@ -8243,33 +8243,40 @@ class References:
8243
8243
  class OwnedRedefinition:
8244
8244
  def __init__(self, definition):
8245
8245
  if valid_definition(definition, "OwnedRedefinition"):
8246
+ # A dotted chain (:>> base.x) carries BOTH the head
8247
+ # qualifiedName and one OwnedFeatureChain per remaining
8248
+ # segment. Keep both so chained forms survive round-trip.
8249
+ self.redefinedFeature = None
8246
8250
  if definition["redefinedFeature"] is not None:
8247
8251
  self.redefinedFeature = QualifiedName(definition["redefinedFeature"])
8248
- else:
8249
- self.redefinedFeature = None
8250
- self.elements = []
8251
- for element in definition["ownedRelatedElement"]:
8252
- self.elements.append(OwnedFeatureChain(element))
8252
+ self.elements = []
8253
+ for element in definition.get("ownedRelatedElement", []) or []:
8254
+ self.elements.append(OwnedFeatureChain(element))
8253
8255
 
8254
8256
  def dump(self):
8257
+ parts = []
8255
8258
  if self.redefinedFeature is not None:
8256
- return self.redefinedFeature.dump()
8257
- else:
8258
- return ".".join([e.dump() for e in self.elements])
8259
+ parts.append(self.redefinedFeature.dump())
8260
+ parts.extend([e.dump() for e in self.elements])
8261
+ return ".".join(parts) if parts else ""
8259
8262
 
8260
8263
  def get_definition(self):
8261
- if self.redefinedFeature is not None:
8264
+ if self.redefinedFeature is not None and not self.elements:
8262
8265
  return {
8263
8266
  "name": self.__class__.__name__,
8264
8267
  "redefinedFeature": self.redefinedFeature.get_definition(),
8265
8268
  "ownedRelatedElement": [],
8266
8269
  }
8267
8270
  else:
8268
- return {
8271
+ output = {
8269
8272
  "name": self.__class__.__name__,
8270
- "redefinedFeature": None,
8273
+ "redefinedFeature": self.redefinedFeature.get_definition() if self.redefinedFeature is not None else None,
8271
8274
  "ownedRelatedElement": [e.get_definition() for e in self.elements],
8272
8275
  }
8276
+ if self.redefinedFeature is None and not self.elements:
8277
+ # Fully empty: keep legacy shape
8278
+ pass
8279
+ return output
8273
8280
 
8274
8281
 
8275
8282
  class Subsettings:
@@ -8298,15 +8305,17 @@ class OwnedSubsetting:
8298
8305
  def __init__(self, definition):
8299
8306
  if valid_definition(definition, "OwnedSubsetting"):
8300
8307
  # subsettedFeature = QualifiedName | ownedRelatedElement += OwnedFeatureChain
8308
+ # A dotted chain (:> base.x) carries BOTH: the head qualifiedName
8309
+ # plus one OwnedFeatureChain per remaining segment. Keep both so
8310
+ # chained forms survive round-trip (v0.42.0).
8311
+ self.elements = []
8301
8312
  if definition["subsettedFeature"] is not None:
8302
- self.elements = [QualifiedName(definition["subsettedFeature"])]
8303
- else:
8304
- self.elements = []
8305
- for element in definition["ownedRelatedElement"]:
8306
- self.elements.append(OwnedFeatureChain(element))
8313
+ self.elements.append(QualifiedName(definition["subsettedFeature"]))
8314
+ for element in definition.get("ownedRelatedElement", []) or []:
8315
+ self.elements.append(OwnedFeatureChain(element))
8307
8316
 
8308
8317
  def dump(self):
8309
- return " ".join([child.dump() for child in self.elements])
8318
+ return ".".join([child.dump() for child in self.elements])
8310
8319
 
8311
8320
  def get_definition(self):
8312
8321
  output = {
@@ -948,20 +948,23 @@ class ReferenceCollector:
948
948
  self, sub: Any, element: Any, results: list[tuple[str, Any, list[str]]], scope_path: list[str]
949
949
  ) -> None:
950
950
  for child in getattr(sub, "children", []):
951
- for el in getattr(child, "elements", []):
952
- names = getattr(el, "names", [])
953
- if names:
954
- results.append(("::".join(names), element, scope_path))
951
+ segments = _chain_segments(child)
952
+ if segments:
953
+ # Namespace qualification joins with '::', feature chains
954
+ # with '.' (e.g. base.x).
955
+ # Namespace qualification stays inside a segment ('A::B');
956
+ # feature-chain segments join with '.'.
957
+ results.append((".".join(segments), element, scope_path))
955
958
 
956
959
  def _collect_redefinitions(
957
960
  self, red: Any, element: Any, results: list[tuple[str, Any, list[str]]], scope_path: list[str]
958
961
  ) -> None:
959
962
  for child in getattr(red, "children", []):
960
- rf = getattr(child, "redefinedFeature", None)
961
- if rf is not None:
962
- names = getattr(rf, "names", [])
963
- if names:
964
- results.append(("::".join(names), element, scope_path))
963
+ segments = _chain_segments(child)
964
+ if segments:
965
+ # Namespace qualification stays inside a segment ('A::B');
966
+ # feature-chain segments join with '.'.
967
+ results.append((".".join(segments), element, scope_path))
965
968
 
966
969
  def _collect_subclassification(
967
970
  self, sc: Any, element: Any, results: list[tuple[str, Any, list[str]]], scope_path: list[str]
@@ -973,6 +976,36 @@ class ReferenceCollector:
973
976
  results.append(("::".join(names), element, scope_path))
974
977
 
975
978
 
979
+
980
+ def _chain_segments(child: Any) -> list[str]:
981
+ """Reference segments of an Owned*Subsetting/Redefinition child, in order.
982
+
983
+ Each returned string is one feature-chain segment: a QualifiedName keeps
984
+ its internal ``::`` namespace qualification (``A::B``), while dotted
985
+ chain steps become separate segments (``:> base.x`` -> ``['base','x']``).
986
+ """
987
+ segments: list[str] = []
988
+ qn = getattr(child, "redefinedFeature", None) or getattr(
989
+ child, "referencedFeature", None
990
+ ) or getattr(child, "subsettedFeature", None)
991
+ if qn is not None:
992
+ names = getattr(qn, "names", [])
993
+ if names:
994
+ segments.append("::".join(names))
995
+ for el in getattr(child, "elements", []) or []:
996
+ if hasattr(el, "names") and getattr(el, "names", []):
997
+ # Bare QualifiedName element (OwnedSubsetting single form)
998
+ segments.append("::".join(el.names))
999
+ elif hasattr(el, "feature"):
1000
+ for seg in getattr(el.feature, "children", []) or []:
1001
+ cf = getattr(seg, "chainingFeature", None)
1002
+ if cf is not None:
1003
+ names = getattr(cf, "names", [])
1004
+ if names:
1005
+ segments.append("::".join(names))
1006
+ return segments
1007
+
1008
+
976
1009
  # ---------------------------------------------------------------------------
977
1010
  # Semantic Analyzer
978
1011
  # ---------------------------------------------------------------------------
@@ -33,29 +33,6 @@ def _is_uuid(s: str) -> bool:
33
33
  )
34
34
 
35
35
 
36
- def _qn_last_name(qn):
37
- """Return the last identifier segment of a QualifiedName, or None."""
38
- if qn is None:
39
- return None
40
- names = getattr(qn, "names", None)
41
- if names:
42
- return names[-1]
43
- return None
44
-
45
-
46
- def _feature_chain_last_name(chain):
47
- """Walk a chain of OwnedFeatureChain segments; return the last identifier
48
- segment of the final segment, or None."""
49
- if chain is None:
50
- return None
51
- feature = getattr(chain, "feature", None)
52
- children = getattr(feature, "children", None) or []
53
- last_seg = None
54
- for seg in children:
55
- last_seg = _qn_last_name(getattr(seg, "chainingFeature", None))
56
- return last_seg
57
-
58
-
59
36
  def _resolve_name_from_specialization(specialization):
60
37
  """Find the most user-visible identifier in a ``FeatureSpecializationPart``.
61
38
 
@@ -76,36 +53,31 @@ def _resolve_name_from_specialization(specialization):
76
53
  if rel is None:
77
54
  continue
78
55
  for child in getattr(rel, "children", None) or []:
79
- # OwnedRedefinition / OwnedReferenceSubsetting: prefer the
80
- # direct referenced feature, fall back to a chained path.
81
- qn = _qn_last_name(
56
+ # OwnedRedefinition / OwnedReferenceSubsetting / OwnedSubsetting:
57
+ # a dotted chain (:> base.x) keeps the head QualifiedName plus
58
+ # one OwnedFeatureChain per remaining segment. Collect every
59
+ # segment in order and resolve to the last one.
60
+ segments = []
61
+ qn = (
82
62
  getattr(child, "redefinedFeature", None)
83
- ) or _qn_last_name(
84
- getattr(child, "referencedFeature", None)
63
+ or getattr(child, "referencedFeature", None)
85
64
  )
86
- if qn:
87
- return qn
88
- # Chained path on either of the above (OwnedFeatureChain[])
89
- elements = getattr(child, "elements", None)
90
- if elements:
91
- head = elements[0]
92
- if isinstance(head, _qn_owner()):
93
- name = _qn_last_name(head)
94
- else:
95
- name = _feature_chain_last_name(head)
96
- if name:
97
- return name
65
+ if qn is not None and getattr(qn, "names", None):
66
+ segments.extend(qn.names)
67
+ for el in getattr(child, "elements", None) or []:
68
+ if hasattr(el, "names") and el.names:
69
+ segments.extend(el.names)
70
+ elif hasattr(el, "feature"):
71
+ for seg in getattr(el.feature, "children", []) or []:
72
+ cf = getattr(seg, "chainingFeature", None)
73
+ if cf is not None and getattr(cf, "names", None):
74
+ segments.extend(cf.names)
75
+ if segments:
76
+ return segments[-1]
98
77
  # First chain resolved; stop looking further.
99
78
  return None
100
79
  return None
101
80
 
102
-
103
- def _qn_owner():
104
- """Return the QualifiedName class without importing it at module
105
- load time (avoids a cycle with the visitor package)."""
106
- from sysmlpy.grammar.classes import QualifiedName
107
- return QualifiedName
108
-
109
81
  # Load custom US Customary unit definitions
110
82
  _us_customary_path = os.path.join(os.path.dirname(__file__), "us_customary_units.txt")
111
83
  if os.path.exists(_us_customary_path):