sysmlpy 0.53.1__tar.gz → 0.55.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.53.1 → sysmlpy-0.55.0}/PKG-INFO +14 -12
  2. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/README.md +13 -11
  3. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/pyproject.toml +1 -1
  4. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/__init__.py +1 -1
  5. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr_visitor.py +204 -29
  6. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/classes.py +76 -5
  7. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/semantic.py +1635 -4
  8. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/LICENSE +0 -0
  9. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/__main__.py +0 -0
  10. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/README.md +0 -0
  11. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2Lexer.interp +0 -0
  12. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2Lexer.py +0 -0
  13. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2Lexer.tokens +0 -0
  14. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2Parser.interp +0 -0
  15. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2Parser.py +0 -0
  16. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2Parser.tokens +0 -0
  17. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2ParserListener.py +0 -0
  18. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/SysMLv2ParserVisitor.py +0 -0
  19. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
  20. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
  21. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
  22. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/antlr_parser.py +0 -0
  23. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/boxes_view.py +0 -0
  24. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/definition.py +0 -0
  25. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/examples/attribute_values.py +0 -0
  26. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/examples/part_attributes.py +0 -0
  27. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/examples/tuples_sequences.py +0 -0
  28. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/formatting.py +0 -0
  29. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/README.md +0 -0
  30. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.g4 +0 -0
  31. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
  32. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
  33. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
  34. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.g4 +0 -0
  35. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.interp +0 -0
  36. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.py +0 -0
  37. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.tokens +0 -0
  38. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserListener.py +0 -0
  39. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserVisitor.py +0 -0
  40. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/desc.xml +0 -0
  41. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/examples/camera.sysml +0 -0
  42. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/examples/toaster-system.sysml +0 -0
  43. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/examples/vehicle-model.sysml +0 -0
  44. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/grammar/antlr4/pom.xml +0 -0
  45. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/LICENSE +0 -0
  46. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/README.md +0 -0
  47. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/__init__.py +0 -0
  48. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Analysis/AnalysisTooling.sysml +0 -0
  49. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Analysis/SampledFunctions.sysml +0 -0
  50. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Analysis/StateSpaceRepresentation.sysml +0 -0
  51. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Analysis/TradeStudies.sysml +0 -0
  52. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Cause and Effect/CausationConnections.sysml +0 -0
  53. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Cause and Effect/CauseAndEffect.sysml +0 -0
  54. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Geometry/ShapeItems.sysml +0 -0
  55. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Geometry/SpatialItems.sysml +0 -0
  56. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Metadata/ImageMetadata.sysml +0 -0
  57. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Metadata/ModelingMetadata.sysml +0 -0
  58. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Metadata/ParametersOfInterestMetadata.sysml +0 -0
  59. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Metadata/RiskMetadata.sysml +0 -0
  60. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQ.sysml +0 -0
  61. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAcoustics.sysml +0 -0
  62. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAtomicNuclear.sysml +0 -0
  63. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQBase.sysml +0 -0
  64. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCharacteristicNumbers.sysml +0 -0
  65. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQChemistryMolecular.sysml +0 -0
  66. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCondensedMatter.sysml +0 -0
  67. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQElectromagnetism.sysml +0 -0
  68. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQInformation.sysml +0 -0
  69. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQLight.sysml +0 -0
  70. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQMechanics.sysml +0 -0
  71. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQSpaceTime.sysml +0 -0
  72. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/ISQThermodynamics.sysml +0 -0
  73. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementRefCalculations.sysml +0 -0
  74. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementReferences.sysml +0 -0
  75. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/Quantities.sysml +0 -0
  76. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/QuantityCalculations.sysml +0 -0
  77. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/SI.sysml +0 -0
  78. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/SIPrefixes.sysml +0 -0
  79. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/TensorCalculations.sysml +0 -0
  80. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/Time.sysml +0 -0
  81. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/USCustomaryUnits.sysml +0 -0
  82. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Quantities and Units/VectorCalculations.sysml +0 -0
  83. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Requirement Derivation/DerivationConnections.sysml +0 -0
  84. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/domain/Requirement Derivation/RequirementDerivation.sysml +0 -0
  85. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Base.kerml +0 -0
  86. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/BaseFunctions.kerml +0 -0
  87. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/BooleanFunctions.kerml +0 -0
  88. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Clocks.kerml +0 -0
  89. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/CollectionFunctions.kerml +0 -0
  90. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Collections.kerml +0 -0
  91. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/ComplexFunctions.kerml +0 -0
  92. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/ControlFunctions.kerml +0 -0
  93. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/ControlPerformances.kerml +0 -0
  94. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/DataFunctions.kerml +0 -0
  95. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/FeatureReferencingPerformances.kerml +0 -0
  96. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/IntegerFunctions.kerml +0 -0
  97. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/KerML.kerml +0 -0
  98. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Links.kerml +0 -0
  99. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Metaobjects.kerml +0 -0
  100. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/NaturalFunctions.kerml +0 -0
  101. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/NumericalFunctions.kerml +0 -0
  102. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Objects.kerml +0 -0
  103. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Observation.kerml +0 -0
  104. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/OccurrenceFunctions.kerml +0 -0
  105. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Occurrences.kerml +0 -0
  106. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Performances.kerml +0 -0
  107. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/RationalFunctions.kerml +0 -0
  108. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/RealFunctions.kerml +0 -0
  109. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/ScalarFunctions.kerml +0 -0
  110. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/ScalarValues.kerml +0 -0
  111. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/SequenceFunctions.kerml +0 -0
  112. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/SpatialFrames.kerml +0 -0
  113. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/StatePerformances.kerml +0 -0
  114. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/StringFunctions.kerml +0 -0
  115. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Transfers.kerml +0 -0
  116. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/TransitionPerformances.kerml +0 -0
  117. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/TrigFunctions.kerml +0 -0
  118. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/Triggers.kerml +0 -0
  119. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/VectorFunctions.kerml +0 -0
  120. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/kernel/VectorValues.kerml +0 -0
  121. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Actions.sysml +0 -0
  122. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Allocations.sysml +0 -0
  123. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/AnalysisCases.sysml +0 -0
  124. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Attributes.sysml +0 -0
  125. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Calculations.sysml +0 -0
  126. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Cases.sysml +0 -0
  127. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Connections.sysml +0 -0
  128. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Constraints.sysml +0 -0
  129. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Flows.sysml +0 -0
  130. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Interfaces.sysml +0 -0
  131. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Items.sysml +0 -0
  132. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Metadata.sysml +0 -0
  133. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Parts.sysml +0 -0
  134. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Ports.sysml +0 -0
  135. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Requirements.sysml +0 -0
  136. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/StandardViewDefinitions.sysml +0 -0
  137. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/States.sysml +0 -0
  138. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/SysML.sysml +0 -0
  139. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/UseCases.sysml +0 -0
  140. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/VerificationCases.sysml +0 -0
  141. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/library/systems/Views.sysml +0 -0
  142. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/navigate.py +0 -0
  143. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/plantuml.py +0 -0
  144. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/project.py +0 -0
  145. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/store.py +0 -0
  146. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/us_customary_units.txt +0 -0
  147. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/usage.py +0 -0
  148. {sysmlpy-0.53.1 → sysmlpy-0.55.0}/src/sysmlpy/validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sysmlpy
3
- Version: 0.53.1
3
+ Version: 0.55.0
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: Jon R. Fox (mycr0ft)
@@ -39,19 +39,21 @@ and dropped the textX parser in favor of [an ANTLR4 parser grammar](https://gith
39
39
  changed our unit library to pint.
40
40
  The project had diverged so much from sysml2py that a new name, sysmlpy, was selected.
41
41
 
42
- > **⚠️ Constraints are changing.** Upcoming releases (v0.54+) overhaul how
43
- > constraint, calc, and expression bodies are validated: names inside
44
- > expressions will be **resolved against the symbol table** (v0.54.0),
45
- > followed by operand type checking and unit-dimension compatibility via
46
- > pint (v0.55.0). Models that parse cleanly today may start producing
47
- > semantic errors once this lands. Track progress in
48
- > [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md).
42
+ > **⚠️ Constraints are changing.** As of v0.55.0, `analyze()` performs
43
+ > full expression validation: identifiers are **resolved against the
44
+ > symbol table** (v0.54.0) and operators are **type- and
45
+ > unit-checked** — `flag and n`, `"a" + 5`, and `[m] + [kg]` all raise
46
+ > `OPERAND_TYPE_MISMATCH` / `UNIT_DIMENSION_MISMATCH` errors. Deterministic
47
+ > literal expressions can be reduced with `const_fold()`. Coming next
48
+ > (Phase D): parsing performance and graph-store query extensions.
49
+ > Track progress in [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md).
49
50
 
50
51
  > **Scope reminder** — a green parse result means the file **parsed
51
- > syntactically**. Names inside constraint bodies and other OCL-style
52
- > expressions are not yet resolved by the semantic analyzer, so a
53
- > document that names an undefined symbol inside an `assert constraint
54
- > { ... }` will still parse cleanly. See [STATUS.md §Known Issues](STATUS.md#known-issues).
52
+ > syntactically**. Expression validation (name resolution, type and
53
+ > unit checks) runs only when you call `analyze(model)` explicitly.
54
+ > Multiplication/division dimension *derivation* (inferring
55
+ > `[N]` from `[kg] * [m/s^2]`) is not yet implemented. See
56
+ > [STATUS.md §Known Issues](STATUS.md#known-issues).
55
57
 
56
58
  For release history, see [CHANGELOG.md](CHANGELOG.md).
57
59
 
@@ -14,19 +14,21 @@ and dropped the textX parser in favor of [an ANTLR4 parser grammar](https://gith
14
14
  changed our unit library to pint.
15
15
  The project had diverged so much from sysml2py that a new name, sysmlpy, was selected.
16
16
 
17
- > **⚠️ Constraints are changing.** Upcoming releases (v0.54+) overhaul how
18
- > constraint, calc, and expression bodies are validated: names inside
19
- > expressions will be **resolved against the symbol table** (v0.54.0),
20
- > followed by operand type checking and unit-dimension compatibility via
21
- > pint (v0.55.0). Models that parse cleanly today may start producing
22
- > semantic errors once this lands. Track progress in
23
- > [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md).
17
+ > **⚠️ Constraints are changing.** As of v0.55.0, `analyze()` performs
18
+ > full expression validation: identifiers are **resolved against the
19
+ > symbol table** (v0.54.0) and operators are **type- and
20
+ > unit-checked** — `flag and n`, `"a" + 5`, and `[m] + [kg]` all raise
21
+ > `OPERAND_TYPE_MISMATCH` / `UNIT_DIMENSION_MISMATCH` errors. Deterministic
22
+ > literal expressions can be reduced with `const_fold()`. Coming next
23
+ > (Phase D): parsing performance and graph-store query extensions.
24
+ > Track progress in [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md).
24
25
 
25
26
  > **Scope reminder** — a green parse result means the file **parsed
26
- > syntactically**. Names inside constraint bodies and other OCL-style
27
- > expressions are not yet resolved by the semantic analyzer, so a
28
- > document that names an undefined symbol inside an `assert constraint
29
- > { ... }` will still parse cleanly. See [STATUS.md §Known Issues](STATUS.md#known-issues).
27
+ > syntactically**. Expression validation (name resolution, type and
28
+ > unit checks) runs only when you call `analyze(model)` explicitly.
29
+ > Multiplication/division dimension *derivation* (inferring
30
+ > `[N]` from `[kg] * [m/s^2]`) is not yet implemented. See
31
+ > [STATUS.md §Known Issues](STATUS.md#known-issues).
30
32
 
31
33
  For release history, see [CHANGELOG.md](CHANGELOG.md).
32
34
 
@@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"
6
6
 
7
7
  [project]
8
8
  name = "sysmlpy"
9
- version = "0.53.1"
9
+ version = "0.55.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.53.1"
28
+ __version__ = "0.55.0"
29
29
 
30
30
  from sysmlpy.usage import (
31
31
  Item, Attribute, Part, Port, Action, Reference, UseCase, Requirement, Interface, Message,
@@ -11134,45 +11134,109 @@ def _emit_null_coalescing_level(ctx):
11134
11134
 
11135
11135
  def _emit_implies_level(ctx):
11136
11136
  # orExpression ( IMPLIES orExpression )*
11137
+ # Operator text 'implies' goes into the ImpliesExpression
11138
+ # operator/operand lists (rendered by ImpliesExpression.dump since
11139
+ # v0.55.0).
11137
11140
  first, pairs = _split_level_ctx(ctx, "OrExpressionContext")
11138
- if pairs:
11139
- return None # 'implies' not dumped by the grammar class -> text
11140
11141
  or_expr = _emit_or_level(first)
11141
11142
  if or_expr is None:
11142
11143
  return None
11143
- return {"name": "ImpliesExpression", "operator": [], "operand": [], "or": or_expr}
11144
+ ops = []
11145
+ operands = []
11146
+ for op_text, or_ctx in pairs:
11147
+ or_dict = _emit_or_level(or_ctx)
11148
+ if or_dict is None:
11149
+ return None
11150
+ ops.append(op_text)
11151
+ operands.append(or_dict)
11152
+ return {
11153
+ "name": "ImpliesExpression",
11154
+ "operator": ops,
11155
+ "operand": operands,
11156
+ "or": or_expr,
11157
+ }
11144
11158
 
11145
11159
 
11146
11160
  def _emit_or_level(ctx):
11147
11161
  # xorExpression ( ( OR | PIPE ) xorExpression )*
11162
+ # Operator text 'or' or '|' goes into the OrExpression operator/operand
11163
+ # lists (rendered by OrExpression.dump since v0.55.0).
11148
11164
  first, pairs = _split_level_ctx(ctx, "XorExpressionContext")
11149
- if pairs:
11150
- return None # 'or'/'|' not dumped by the grammar class -> text
11151
11165
  xor_expr = _emit_xor_level(first)
11152
11166
  if xor_expr is None:
11153
11167
  return None
11154
- return {"name": "OrExpression", "operator": [], "operand": [], "xor": xor_expr}
11168
+ ops = []
11169
+ operands = []
11170
+ for op_text, xor_ctx in pairs:
11171
+ xor_dict = _emit_xor_level(xor_ctx)
11172
+ if xor_dict is None:
11173
+ return None
11174
+ ops.append(op_text)
11175
+ operands.append(xor_dict)
11176
+ return {
11177
+ "name": "OrExpression",
11178
+ "operator": ops,
11179
+ "operand": operands,
11180
+ "xor": xor_expr,
11181
+ }
11155
11182
 
11156
11183
 
11157
11184
  def _emit_xor_level(ctx):
11158
11185
  # andExpression ( XOR andExpression )*
11159
11186
  first, pairs = _split_level_ctx(ctx, "AndExpressionContext")
11160
- if pairs:
11161
- return None # 'xor' not dumped by the grammar class -> text
11162
11187
  and_expr = _emit_and_level(first)
11163
11188
  if and_expr is None:
11164
11189
  return None
11165
- return {"name": "XorExpression", "operator": [], "operand": [], "and": and_expr}
11190
+ ops = []
11191
+ operands = []
11192
+ for op_text, and_ctx in pairs:
11193
+ and_dict = _emit_and_level(and_ctx)
11194
+ if and_dict is None:
11195
+ return None
11196
+ ops.append(op_text)
11197
+ operands.append(and_dict)
11198
+ return {
11199
+ "name": "XorExpression",
11200
+ "operator": ops,
11201
+ "operand": operands,
11202
+ "and": and_expr,
11203
+ }
11166
11204
 
11167
11205
 
11168
11206
  def _emit_and_level(ctx):
11169
11207
  # equalityExpression ( ( AND | AMP ) equalityExpression )*
11208
+ # The 'and' keyword form takes an EqualityExpressionReference (a
11209
+ # membership wrapper) per the XText reference grammar; '&' keeps a
11210
+ # direct EqualityExpression operand.
11170
11211
  first, pairs = _split_level_ctx(ctx, "EqualityExpressionContext")
11171
- if pairs:
11172
- return None # 'and'/'&' not dumped by the grammar class -> text
11173
11212
  eq = _emit_equality_level(first)
11174
11213
  if eq is None:
11175
11214
  return None
11215
+ ops = []
11216
+ for op_text, cls_ctx in pairs:
11217
+ cls_dict = _emit_equality_level(cls_ctx)
11218
+ if cls_dict is None:
11219
+ return None
11220
+ if op_text == "&":
11221
+ ops.append(
11222
+ {"name": "AndOperand", "operator": "&", "operand": cls_dict}
11223
+ )
11224
+ else:
11225
+ ops.append(
11226
+ {
11227
+ "name": "AndOperand",
11228
+ "operator": "and",
11229
+ "operand": {
11230
+ "name": "EqualityExpressionReference",
11231
+ "ownedRelationship": {
11232
+ "name": "EqualityExpressionMember",
11233
+ "ownedRelatedElement": cls_dict,
11234
+ },
11235
+ },
11236
+ }
11237
+ )
11238
+ if ops:
11239
+ return {"name": "AndExpression", "operation": ops, "equality": eq}
11176
11240
  return {"name": "AndExpression", "operation": [], "equality": eq}
11177
11241
 
11178
11242
 
@@ -11292,10 +11356,29 @@ def _emit_multiplicative_level(ctx):
11292
11356
 
11293
11357
  def _emit_exponentiation_level(ctx):
11294
11358
  # unaryExpression ( ( STAR_STAR | CARET ) exponentiationExpression )?
11295
- # '**'/'^' are right-associative and not dumped by the grammar
11296
- # class, so keep the text when present
11359
+ # '**'/'^' are right-associative; the rhs of '**' is another
11360
+ # ExponentiationExpression (child layer of the same rule).
11297
11361
  if hasattr(ctx, "exponentiationExpression") and ctx.exponentiationExpression():
11298
- return None
11362
+ rhs_ctx = ctx.exponentiationExpression()
11363
+ unary_ctx = ctx.unaryExpression() if hasattr(ctx, "unaryExpression") else None
11364
+ if unary_ctx is None:
11365
+ return None
11366
+ unary = _emit_unary_level(unary_ctx)
11367
+ if unary is None:
11368
+ return None
11369
+ rhs_dict = _emit_exponentiation_level(rhs_ctx)
11370
+ if rhs_dict is None:
11371
+ return None
11372
+ op_text = "**"
11373
+ for c in (ctx.getChildren() if hasattr(ctx, "getChildren") else []):
11374
+ if type(c).__name__ == "TerminalNodeImpl" and c.getText().strip() in ("**", "^"):
11375
+ op_text = c.getText().strip()
11376
+ return {
11377
+ "name": "ExponentiationExpression",
11378
+ "operator": [op_text],
11379
+ "operand": [rhs_dict],
11380
+ "unary": unary,
11381
+ }
11299
11382
  unary_ctx = ctx.unaryExpression() if hasattr(ctx, "unaryExpression") else None
11300
11383
  if unary_ctx is None:
11301
11384
  return None
@@ -11312,6 +11395,41 @@ def _emit_unary_level(ctx):
11312
11395
  # | primaryExpression
11313
11396
  prim_ctx = ctx.primaryExpression() if hasattr(ctx, "primaryExpression") else None
11314
11397
  if prim_ctx is not None:
11398
+ # Parenthesized primary: baseExpression '(' sequenceExpression ')'
11399
+ # carries a full inner binary chain — recurse through the inner
11400
+ # nullCoalescing level so nested binary ops stay structured.
11401
+ be_ctx = prim_ctx.baseExpression() if hasattr(prim_ctx, "baseExpression") else None
11402
+ if be_ctx is not None and "(" in getattr(be_ctx, "text", "") or (
11403
+ be_ctx is not None and be_ctx.getText().startswith("(")
11404
+ ):
11405
+ inner_nce = None
11406
+ sel = (
11407
+ be_ctx.sequenceExpressionList()
11408
+ if hasattr(be_ctx, "sequenceExpressionList")
11409
+ else None
11410
+ )
11411
+ if sel is not None:
11412
+ oe = (
11413
+ sel.ownedExpression()
11414
+ if hasattr(sel, "ownedExpression")
11415
+ else None
11416
+ )
11417
+ # A comma-separated sequence (a, b) is not representable
11418
+ # in the paren-structured shape — let it fall back to text.
11419
+ if isinstance(oe, list) and len(oe) != 1:
11420
+ return None
11421
+ while isinstance(oe, list):
11422
+ oe = oe[0] if oe else None
11423
+ nce = (
11424
+ oe.nullCoalescingExpression()
11425
+ if oe is not None and hasattr(oe, "nullCoalescingExpression")
11426
+ else None
11427
+ )
11428
+ if isinstance(nce, list):
11429
+ nce = nce[0] if nce else None
11430
+ if nce is not None:
11431
+ inner_nce = _emit_null_coalescing_level(nce)
11432
+ return None # parens carry grouping — text fallback preserves them
11315
11433
  primary = _emit_primary_level(prim_ctx)
11316
11434
  if primary is None:
11317
11435
  return None
@@ -11330,9 +11448,9 @@ def _emit_unary_level(ctx):
11330
11448
  if children and type(children[0]).__name__ == "TerminalNodeImpl":
11331
11449
  op_text = children[0].getText().strip()
11332
11450
  if op_text in ("+", "-", "~", "not"):
11333
- # operand must be a unaryExpression without its own prefix
11334
- # operator (nested prefixes are not representable in the
11335
- # chain shape)
11451
+ # operand must be a unaryExpression; nested unary prefixes are
11452
+ # not representable in the chain shape, but a plain
11453
+ # primaryExpression (possibly parenthesized additive) is.
11336
11454
  operand_ctx = None
11337
11455
  for c in children[1:]:
11338
11456
  if type(c).__name__ == "UnaryExpressionContext":
@@ -11345,25 +11463,64 @@ def _emit_unary_level(ctx):
11345
11463
  if hasattr(operand_ctx, "primaryExpression")
11346
11464
  else None
11347
11465
  )
11348
- if inner_prim is None:
11466
+ if (
11467
+ inner_prim is not None
11468
+ and "(" not in getattr(operand_ctx, "text", "")
11469
+ ):
11470
+ primary = _emit_primary_level(inner_prim)
11471
+ if primary is not None:
11472
+ return {
11473
+ "name": "UnaryExpression",
11474
+ "operator": op_text,
11475
+ "operand": [],
11476
+ "extent": {
11477
+ "name": "ExtentExpression",
11478
+ "operator": "",
11479
+ "operand": [],
11480
+ "primary": primary,
11481
+ },
11482
+ }
11483
+ # Parenthesized operand: primary is '(' sequenceExpression ')'
11484
+ # whose additive/multiplicative chain is representable — emit
11485
+ # the inner chain as a nested UnaryExpression extent.
11486
+ inner = _emit_unary_level(operand_ctx)
11487
+ if inner is not None and not isinstance(inner.get("extent"), dict):
11349
11488
  return None
11350
- primary = _emit_primary_level(inner_prim)
11351
- if primary is None:
11489
+ # inner may be a primary-only unary; wrap so the outer '-'
11490
+ # sits above the inner (parenthesized) chain
11491
+ if inner is None:
11352
11492
  return None
11353
11493
  return {
11354
11494
  "name": "UnaryExpression",
11355
11495
  "operator": op_text,
11356
11496
  "operand": [],
11357
- "extent": {
11358
- "name": "ExtentExpression",
11359
- "operator": "",
11360
- "operand": [],
11361
- "primary": primary,
11362
- },
11497
+ "extent": inner.get("extent"),
11363
11498
  }
11364
11499
  return None # '@'/'@@'/'all' typeReference forms -> text
11365
11500
 
11366
11501
 
11502
+ def _chain_has_binary_operator(node):
11503
+ """True if an expression-layer dict contains any emitted binary operator."""
11504
+ if not isinstance(node, dict):
11505
+ return False
11506
+ for key in ("operation", "operator", "operand"):
11507
+ val = node.get(key)
11508
+ if key == "operation" and isinstance(val, list) and val:
11509
+ return True
11510
+ if key == "operator":
11511
+ if isinstance(val, str) and val not in (None, "") and node.get("name") != "ExponentiationExpression" or isinstance(val, list) and val:
11512
+ return True
11513
+ if key == "operand" and isinstance(val, list) and val and node.get("name") == "ExponentiationExpression":
11514
+ return True
11515
+ for child_key in ("implies", "or", "xor", "and", "equality", "classification",
11516
+ "relational", "range", "additive", "multiplicitive",
11517
+ "exponential", "unary", "extent", "primary"):
11518
+ child = node.get(child_key)
11519
+ if isinstance(child, dict) and _chain_has_binary_operator(child):
11520
+ return True
11521
+ return False
11522
+
11523
+
11367
11524
  def _base_feature_names(be_ctx):
11368
11525
  """Return the qualified-name parts of a baseExpression that is a
11369
11526
  plain feature reference, or None for any other base form."""
@@ -11479,9 +11636,27 @@ def _emit_base_primary(be_ctx):
11479
11636
  pass
11480
11637
  if lit_text.startswith('"') and lit_text.endswith('"'):
11481
11638
  return _make_literal_string_primary(lit_text)
11482
- # boolean/null keywords have no dedicated grammar class; keep
11483
- # them as feature references so the text round-trips
11484
- if lit_text in ("true", "false", "null"):
11639
+ # Boolean literal: captured as a LiteralBoolean primary so the
11640
+ # type checker can classify it; the grammar class round-trips
11641
+ # the keyword text via the value field.
11642
+ if lit_text in ("true", "false"):
11643
+ return {
11644
+ "name": "PrimaryExpression",
11645
+ "operator": [],
11646
+ "operand": [],
11647
+ "base": {
11648
+ "name": "BaseExpression",
11649
+ "ownedRelationship": {
11650
+ "name": "LiteralBoolean",
11651
+ "value": lit_text,
11652
+ },
11653
+ },
11654
+ "ownedRelationship1": [],
11655
+ "ownedRelationship2": [],
11656
+ }
11657
+ # null has no dedicated grammar class; keep it as a feature
11658
+ # reference so the text round-trips
11659
+ if lit_text == "null":
11485
11660
  return _make_feature_reference_primary(lit_text)
11486
11661
  return None
11487
11662
  names = _base_feature_names(be_ctx)
@@ -5754,6 +5754,18 @@ class ImpliesExpression:
5754
5754
  self.operator = definition.get("operator", [])
5755
5755
 
5756
5756
  def dump(self):
5757
+ if self.orexpression is not None and self.operator and self.operand:
5758
+ output = [self.orexpression.dump()]
5759
+ for op, rhs in zip(self.operator, self.operand):
5760
+ output.append(op)
5761
+ if isinstance(rhs, dict):
5762
+ try:
5763
+ output.append(OrExpression(rhs).dump())
5764
+ except Exception:
5765
+ output.append(str(rhs))
5766
+ else:
5767
+ output.append(str(rhs))
5768
+ return " ".join(output)
5757
5769
  if self.orexpression is not None:
5758
5770
  return self.orexpression.dump()
5759
5771
  return ""
@@ -5781,6 +5793,18 @@ class OrExpression:
5781
5793
  self.operator = definition.get("operator", [])
5782
5794
 
5783
5795
  def dump(self):
5796
+ if self.xor is not None and self.operator and self.operand:
5797
+ output = [self.xor.dump()]
5798
+ for op, rhs in zip(self.operator, self.operand):
5799
+ output.append(op)
5800
+ if isinstance(rhs, dict):
5801
+ try:
5802
+ output.append(XorExpression(rhs).dump())
5803
+ except Exception:
5804
+ output.append(str(rhs))
5805
+ else:
5806
+ output.append(str(rhs))
5807
+ return " ".join(output)
5784
5808
  if self.xor is not None:
5785
5809
  return self.xor.dump()
5786
5810
  return ""
@@ -5808,13 +5832,27 @@ class XorExpression:
5808
5832
  self.operator = definition.get("operator", [])
5809
5833
 
5810
5834
  def dump(self):
5811
- return self.andexpression.dump()
5835
+ if self.andexpression is not None and self.operator and self.operand:
5836
+ output = [self.andexpression.dump()]
5837
+ for op, rhs in zip(self.operator, self.operand):
5838
+ output.append(op)
5839
+ if isinstance(rhs, dict):
5840
+ try:
5841
+ output.append(AndExpression(rhs).dump())
5842
+ except Exception:
5843
+ output.append(str(rhs))
5844
+ else:
5845
+ output.append(str(rhs))
5846
+ return " ".join(output)
5847
+ if self.andexpression is not None:
5848
+ return self.andexpression.dump()
5849
+ return ""
5812
5850
 
5813
5851
  def get_definition(self):
5814
5852
  output = {
5815
5853
  "name": self.__class__.__name__,
5816
- "operator": [],
5817
- "operand": [],
5854
+ "operator": self.operator,
5855
+ "operand": self.operand,
5818
5856
  "and": self.andexpression.get_definition(),
5819
5857
  }
5820
5858
  return output
@@ -6149,6 +6187,18 @@ class ExponentiationExpression:
6149
6187
  self.operator = definition.get("operator", [])
6150
6188
 
6151
6189
  def dump(self):
6190
+ if self.unary is not None and self.operator and self.operand:
6191
+ output = [self.unary.dump()]
6192
+ for op, rhs in zip(self.operator, self.operand):
6193
+ output.append(op)
6194
+ if isinstance(rhs, dict):
6195
+ try:
6196
+ output.append(ExponentiationExpression(rhs).dump())
6197
+ except Exception:
6198
+ output.append(str(rhs))
6199
+ else:
6200
+ output.append(str(rhs))
6201
+ return " ".join(output)
6152
6202
  if self.unary is not None:
6153
6203
  return self.unary.dump()
6154
6204
  return ""
@@ -6200,15 +6250,21 @@ class UnaryExpression:
6200
6250
  class ExtentExpression:
6201
6251
  def __init__(self, definition):
6202
6252
  self.primary = None
6253
+ self.nested = None
6203
6254
  self.operator = ""
6204
6255
  if valid_definition(definition, self.__class__.__name__):
6205
- if definition["primary"] is not None:
6256
+ if definition.get("primary") is not None:
6206
6257
  self.primary = PrimaryExpression(definition["primary"])
6258
+ nested = definition.get("ownedRelationship")
6259
+ if isinstance(nested, dict):
6260
+ self.nested = globals()[nested["name"]](nested)
6207
6261
  self.operator = definition.get("operator", "")
6208
6262
 
6209
6263
  def dump(self):
6210
6264
  if self.primary is not None:
6211
6265
  return self.primary.dump()
6266
+ if self.nested is not None:
6267
+ return self.nested.dump()
6212
6268
  return ""
6213
6269
 
6214
6270
  def get_definition(self):
@@ -6219,7 +6275,8 @@ class ExtentExpression:
6219
6275
  }
6220
6276
  if self.primary is not None:
6221
6277
  output["primary"] = self.primary.get_definition()
6222
- output["ownedRelationship"] = []
6278
+ if self.nested is not None:
6279
+ output["ownedRelationship"] = self.nested.get_definition()
6223
6280
  return output
6224
6281
 
6225
6282
 
@@ -6455,6 +6512,8 @@ class BaseExpression:
6455
6512
  self.relationship = LiteralString(definition["ownedRelationship"])
6456
6513
  elif definition["ownedRelationship"]["name"] == "LiteralReal":
6457
6514
  self.relationship = LiteralReal(definition["ownedRelationship"])
6515
+ elif definition["ownedRelationship"]["name"] == "LiteralBoolean":
6516
+ self.relationship = LiteralBoolean(definition["ownedRelationship"])
6458
6517
  elif definition["ownedRelationship"]["name"] == "SequenceExpression":
6459
6518
  self.relationship = SequenceExpression(definition["ownedRelationship"])
6460
6519
  elif definition["ownedRelationship"]["name"] == "InvocationExpression":
@@ -8437,6 +8496,18 @@ class LiteralString:
8437
8496
  return {"name": self.__class__.__name__, "value": self.element}
8438
8497
 
8439
8498
 
8499
+ class LiteralBoolean:
8500
+ def __init__(self, definition):
8501
+ if valid_definition(definition, self.__class__.__name__):
8502
+ self.element = definition["value"]
8503
+
8504
+ def dump(self):
8505
+ return self.element
8506
+
8507
+ def get_definition(self):
8508
+ return {"name": self.__class__.__name__, "value": self.element}
8509
+
8510
+
8440
8511
  class LiteralInteger:
8441
8512
  def __init__(self, definition):
8442
8513
  if valid_definition(definition, "LiteralInteger"):